Skip to content

Commit 6610fa3

Browse files
fix: re-open the MediaSource if readyState is not open when the init() method is called. (#7783)
Builds on top of @tykus160's observation in #4903 where `MediaSource.readyState` was either in a `closed` or `ended` state when the `MediaSourceEngine.init()` logic is executed. This fix will simply re-open the `MediaSource` if non-open, resulting in fewer scenarios where the `MEDIA_SOURCE_OPERATION_THREW` error: https://github.com/shaka-project/shaka-player/blob/de0f33c2623b057e80b7cafd53e19fac2f984961/lib/media/media_source_engine.js#L648-L651 is thrown because of an [`InvalidStateError`](https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer#exceptions).
1 parent de0f33c commit 6610fa3

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

lib/media/media_source_engine.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,13 @@ shaka.media.MediaSourceEngine = class {
551551
const ContentType = shaka.util.ManifestParserUtils.ContentType;
552552

553553
await this.mediaSourceOpen_;
554+
if (this.ended() || this.closed()) {
555+
shaka.log.alwaysError('Expected MediaSource to be open during init(); ' +
556+
'reopening the media source.');
557+
this.mediaSourceOpen_ = new shaka.util.PublicPromise();
558+
this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
559+
await this.mediaSourceOpen_;
560+
}
554561

555562
this.sequenceMode_ = sequenceMode;
556563
this.manifestType_ = manifestType;
@@ -724,6 +731,17 @@ shaka.media.MediaSourceEngine = class {
724731
return this.mediaSource_ ? this.mediaSource_.readyState == 'ended' : true;
725732
}
726733

734+
/**
735+
* @return {boolean} True if the MediaSource is in an "closed" state, or if
736+
* the object has been destroyed.
737+
*/
738+
closed() {
739+
if (this.reloadingMediaSource_) {
740+
return false;
741+
}
742+
return this.mediaSource_ ? this.mediaSource_.readyState == 'closed' : true;
743+
}
744+
727745
/**
728746
* Gets the first timestamp in buffer for the given content type.
729747
*
@@ -1608,7 +1626,7 @@ shaka.media.MediaSourceEngine = class {
16081626
// don't call it again. Also do not call if readyState is
16091627
// 'closed' (not attached to video element) since it is not a
16101628
// valid operation.
1611-
if (this.ended() || this.mediaSource_.readyState === 'closed') {
1629+
if (this.ended() || this.closed()) {
16121630
return;
16131631
}
16141632
// Tizen won't let us pass undefined, but it will let us omit the

test/media/media_source_engine_unit.js

+31
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ describe('MediaSourceEngine', () => {
165165
videoSourceBuffer = createMockSourceBuffer();
166166
mockMediaSource = createMockMediaSource();
167167
mockMediaSource.addSourceBuffer.and.callFake((mimeType) => {
168+
if (mockMediaSource.readyState !== 'open') {
169+
// https://w3c.github.io/media-source/#addsourcebuffer-method
170+
throw new Error('InvalidStateError');
171+
}
168172
const type = mimeType.split('/')[0];
169173
const buffer = type == 'audio' ? audioSourceBuffer : videoSourceBuffer;
170174

@@ -200,6 +204,7 @@ describe('MediaSourceEngine', () => {
200204
createMediaSourceSpy = jasmine.createSpy('createMediaSource');
201205
createMediaSourceSpy.and.callFake((p) => {
202206
p.resolve();
207+
mockMediaSource.readyState = 'open';
203208
return mockMediaSource;
204209
});
205210
// eslint-disable-next-line no-restricted-syntax
@@ -400,6 +405,32 @@ describe('MediaSourceEngine', () => {
400405
expect(shaka.text.TextEngine).not.toHaveBeenCalled();
401406
});
402407

408+
it('creates SourceBuffers when MediaSource readyState is closed',
409+
async () => {
410+
const initObject = new Map();
411+
initObject.set(ContentType.AUDIO, fakeAudioStream);
412+
initObject.set(ContentType.VIDEO, fakeVideoStream);
413+
414+
await mediaSourceEngine.open();
415+
416+
mockMediaSource.readyState = 'closed';
417+
await expectAsync(
418+
mediaSourceEngine.init(initObject, false)).not.toBeRejected();
419+
});
420+
421+
it('creates SourceBuffers when MediaSource readyState is ended',
422+
async () => {
423+
const initObject = new Map();
424+
initObject.set(ContentType.AUDIO, fakeAudioStream);
425+
initObject.set(ContentType.VIDEO, fakeVideoStream);
426+
427+
await mediaSourceEngine.open();
428+
429+
mockMediaSource.readyState = 'ended';
430+
await expectAsync(
431+
mediaSourceEngine.init(initObject, false)).not.toBeRejected();
432+
});
433+
403434
it('creates TextEngines for text types', async () => {
404435
const initObject = new Map();
405436
initObject.set(ContentType.TEXT, fakeTextStream);

0 commit comments

Comments
 (0)