Skip to content

Commit 51725fc

Browse files
authored
fix: handle page restoration from bfcache in Safari (#7683)
1 parent 02d8217 commit 51725fc

File tree

2 files changed

+31
-0
lines changed

2 files changed

+31
-0
lines changed

src/controller/buffer-controller.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,6 +1575,15 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe
15751575

15761576
private _onMediaSourceClose = () => {
15771577
this.log('Media source closed');
1578+
// Safari/WebKit bug: MediaSource becomes invalid after bfcache restoration.
1579+
// When the user navigates back, the MediaSource is in a 'closed' state and cannot be used.
1580+
// If sourceclose fires while media is still attached, trigger recovery to reattach media.
1581+
if (this.media) {
1582+
this.warn(
1583+
'MediaSource closed while media attached - triggering recovery',
1584+
);
1585+
this.hls.recoverMediaError();
1586+
}
15781587
};
15791588

15801589
private _onMediaSourceEnded = () => {

tests/unit/controller/buffer-controller.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type BufferControllerTestable = Omit<
5757
sourceBuffers: SourceBuffersTuple;
5858
tracks: SourceBufferTrackSet;
5959
tracksReady: boolean;
60+
_onMediaSourceClose: () => void;
6061
};
6162

6263
describe('BufferController', function () {
@@ -557,4 +558,25 @@ describe('BufferController', function () {
557558
expect(bufferController.bufferedToEnd).to.be.true;
558559
});
559560
});
561+
562+
describe('Safari MediaSource bfcache close recovery', function () {
563+
it('triggers recoverMediaError when sourceclose fires with media attached', function () {
564+
const media = new MockMediaElement() as unknown as HTMLMediaElement;
565+
const mediaSource = new MockMediaSource() as unknown as MediaSource;
566+
const recoverMediaErrorSpy = sandbox.spy(hls, 'recoverMediaError');
567+
bufferController.media = media;
568+
bufferController.mediaSource = mediaSource;
569+
bufferController._onMediaSourceClose();
570+
expect(recoverMediaErrorSpy).to.have.been.calledOnce;
571+
});
572+
573+
it('does not trigger recovery when sourceclose fires without media attached', function () {
574+
const mediaSource = new MockMediaSource() as unknown as MediaSource;
575+
const recoverMediaErrorSpy = sandbox.spy(hls, 'recoverMediaError');
576+
bufferController.media = null;
577+
bufferController.mediaSource = mediaSource;
578+
bufferController._onMediaSourceClose();
579+
expect(recoverMediaErrorSpy).to.not.have.been.called;
580+
});
581+
});
560582
});

0 commit comments

Comments
 (0)