mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-24 17:35:10 +03:00
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 https://github.com/shaka-project/shaka-player/issues/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).
This commit is contained in:
@@ -551,6 +551,13 @@ shaka.media.MediaSourceEngine = class {
|
||||
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
||||
|
||||
await this.mediaSourceOpen_;
|
||||
if (this.ended() || this.closed()) {
|
||||
shaka.log.alwaysError('Expected MediaSource to be open during init(); ' +
|
||||
'reopening the media source.');
|
||||
this.mediaSourceOpen_ = new shaka.util.PublicPromise();
|
||||
this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
|
||||
await this.mediaSourceOpen_;
|
||||
}
|
||||
|
||||
this.sequenceMode_ = sequenceMode;
|
||||
this.manifestType_ = manifestType;
|
||||
@@ -724,6 +731,17 @@ shaka.media.MediaSourceEngine = class {
|
||||
return this.mediaSource_ ? this.mediaSource_.readyState == 'ended' : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the MediaSource is in an "closed" state, or if
|
||||
* the object has been destroyed.
|
||||
*/
|
||||
closed() {
|
||||
if (this.reloadingMediaSource_) {
|
||||
return false;
|
||||
}
|
||||
return this.mediaSource_ ? this.mediaSource_.readyState == 'closed' : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first timestamp in buffer for the given content type.
|
||||
*
|
||||
@@ -1608,7 +1626,7 @@ shaka.media.MediaSourceEngine = class {
|
||||
// don't call it again. Also do not call if readyState is
|
||||
// 'closed' (not attached to video element) since it is not a
|
||||
// valid operation.
|
||||
if (this.ended() || this.mediaSource_.readyState === 'closed') {
|
||||
if (this.ended() || this.closed()) {
|
||||
return;
|
||||
}
|
||||
// Tizen won't let us pass undefined, but it will let us omit the
|
||||
|
||||
@@ -165,6 +165,10 @@ describe('MediaSourceEngine', () => {
|
||||
videoSourceBuffer = createMockSourceBuffer();
|
||||
mockMediaSource = createMockMediaSource();
|
||||
mockMediaSource.addSourceBuffer.and.callFake((mimeType) => {
|
||||
if (mockMediaSource.readyState !== 'open') {
|
||||
// https://w3c.github.io/media-source/#addsourcebuffer-method
|
||||
throw new Error('InvalidStateError');
|
||||
}
|
||||
const type = mimeType.split('/')[0];
|
||||
const buffer = type == 'audio' ? audioSourceBuffer : videoSourceBuffer;
|
||||
|
||||
@@ -200,6 +204,7 @@ describe('MediaSourceEngine', () => {
|
||||
createMediaSourceSpy = jasmine.createSpy('createMediaSource');
|
||||
createMediaSourceSpy.and.callFake((p) => {
|
||||
p.resolve();
|
||||
mockMediaSource.readyState = 'open';
|
||||
return mockMediaSource;
|
||||
});
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
@@ -400,6 +405,32 @@ describe('MediaSourceEngine', () => {
|
||||
expect(shaka.text.TextEngine).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates SourceBuffers when MediaSource readyState is closed',
|
||||
async () => {
|
||||
const initObject = new Map();
|
||||
initObject.set(ContentType.AUDIO, fakeAudioStream);
|
||||
initObject.set(ContentType.VIDEO, fakeVideoStream);
|
||||
|
||||
await mediaSourceEngine.open();
|
||||
|
||||
mockMediaSource.readyState = 'closed';
|
||||
await expectAsync(
|
||||
mediaSourceEngine.init(initObject, false)).not.toBeRejected();
|
||||
});
|
||||
|
||||
it('creates SourceBuffers when MediaSource readyState is ended',
|
||||
async () => {
|
||||
const initObject = new Map();
|
||||
initObject.set(ContentType.AUDIO, fakeAudioStream);
|
||||
initObject.set(ContentType.VIDEO, fakeVideoStream);
|
||||
|
||||
await mediaSourceEngine.open();
|
||||
|
||||
mockMediaSource.readyState = 'ended';
|
||||
await expectAsync(
|
||||
mediaSourceEngine.init(initObject, false)).not.toBeRejected();
|
||||
});
|
||||
|
||||
it('creates TextEngines for text types', async () => {
|
||||
const initObject = new Map();
|
||||
initObject.set(ContentType.TEXT, fakeTextStream);
|
||||
|
||||
Reference in New Issue
Block a user