diff --git a/lib/player.js b/lib/player.js index a837c4c56..c86988463 100644 --- a/lib/player.js +++ b/lib/player.js @@ -1090,6 +1090,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget { /** @private {?shaka.text.SpeechToText} */ this.speechToText_ = null; + /** @private {!Set} */ + this.pendingExtraTrackPromises_ = new Set(); + // Even though |attach| will start in later interpreter cycles, it should be // the LAST thing we do in the constructor because conceptually it relies on // player having been initialized. @@ -1657,6 +1660,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { this.speechToText_.disable(); } + this.pendingExtraTrackPromises_.clear(); + if (this.video_) { // The life cycle of tracks that created by addTextTrackAsync() and // their associated resources should be the same as the loaded video. @@ -3071,6 +3076,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { this.streamingEngine_.applyPlayRange( this.config_.playRangeStart, this.config_.playRangeEnd); + await this.waitForPendingExtraTrackPromises_(); this.fullyLoaded_ = true; this.dispatchEvent(shaka.Player.makeEvent_( @@ -3554,6 +3560,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { } } + await this.waitForPendingExtraTrackPromises_(); this.fullyLoaded_ = true; } @@ -6815,135 +6822,143 @@ shaka.Player = class extends shaka.util.FakeEventTarget { * @return {!Promise} * @export */ - async addTextTrackAsync(uri, language, kind, mimeType, codec, label, + addTextTrackAsync(uri, language, kind, mimeType, codec, label, forced = false) { - if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE && - this.loadMode_ != shaka.Player.LoadMode.SRC_EQUALS) { - shaka.log.error( - 'Must call load() and wait for it to resolve before adding text ' + - 'tracks.'); - throw new shaka.util.Error( - shaka.util.Error.Severity.RECOVERABLE, - shaka.util.Error.Category.PLAYER, - shaka.util.Error.Code.CONTENT_NOT_LOADED); - } - - if (kind != 'subtitles' && kind != 'captions') { - shaka.log.alwaysWarn( - 'Using a kind value different of `subtitles` or `captions` can ' + - 'cause unwanted issues.'); - } - - if (!mimeType) { - mimeType = await this.getTextMimetype_(uri); - } - - let adCuePoints = []; - if (this.adManager_) { - adCuePoints = this.adManager_.getCuePoints(); - } - - if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) { - const device = shaka.device.DeviceFactory.getDevice(); - if (forced && device.getBrowserEngine() === - shaka.device.IDevice.BrowserEngine.WEBKIT) { - // See: https://github.com/whatwg/html/issues/4472 - kind = 'forced'; + const p = (async () => { + if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE && + this.loadMode_ != shaka.Player.LoadMode.SRC_EQUALS) { + shaka.log.error( + 'Must call load() and wait for it to resolve before adding text ' + + 'tracks.'); + throw new shaka.util.Error( + shaka.util.Error.Severity.RECOVERABLE, + shaka.util.Error.Category.PLAYER, + shaka.util.Error.Code.CONTENT_NOT_LOADED); } - const trackNode = await this.addSrcTrackElement_(uri, language, kind, - mimeType, label || '', adCuePoints); - if (trackNode.track) { - this.onTracksChanged_(); - this.onTextChanged_(); - return shaka.util.StreamUtils.html5TextTrackToTrack( - trackNode.track, this.textDisplayer_.isTextVisible()); + + if (kind != 'subtitles' && kind != 'captions') { + shaka.log.alwaysWarn( + 'Using a kind value different of `subtitles` or `captions` can ' + + 'cause unwanted issues.'); } - // This should not happen, but there are browser implementations that may - // not support the Track element. - shaka.log.error('Cannot add this text when loaded with src='); - throw new shaka.util.Error( - shaka.util.Error.Severity.RECOVERABLE, - shaka.util.Error.Category.TEXT, - shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_TEXT_TO_SRC_EQUALS); + + if (!mimeType) { + mimeType = await this.getTextMimetype_(uri); + } + + let adCuePoints = []; + if (this.adManager_) { + adCuePoints = this.adManager_.getCuePoints(); + } + + if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) { + const device = shaka.device.DeviceFactory.getDevice(); + if (forced && device.getBrowserEngine() === + shaka.device.IDevice.BrowserEngine.WEBKIT) { + // See: https://github.com/whatwg/html/issues/4472 + kind = 'forced'; + } + const trackNode = await this.addSrcTrackElement_(uri, language, kind, + mimeType, label || '', adCuePoints); + if (trackNode.track) { + this.onTracksChanged_(); + this.onTextChanged_(); + return shaka.util.StreamUtils.html5TextTrackToTrack( + trackNode.track, this.textDisplayer_.isTextVisible()); + } + // This should not happen, but there are browser implementations + // that may not support the Track element. + shaka.log.error('Cannot add this text when loaded with src='); + throw new shaka.util.Error( + shaka.util.Error.Severity.RECOVERABLE, + shaka.util.Error.Category.TEXT, + shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_TEXT_TO_SRC_EQUALS); + } + + const ContentType = shaka.util.ManifestParserUtils.ContentType; + + const seekRange = this.seekRange(); + let duration = seekRange.end - seekRange.start; + if (this.manifest_) { + duration = this.manifest_.presentationTimeline.getDuration(); + } + if (duration == Infinity) { + throw new shaka.util.Error( + shaka.util.Error.Severity.RECOVERABLE, + shaka.util.Error.Category.MANIFEST, + shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_TEXT_TO_LIVE_STREAM); + } + + if (adCuePoints.length) { + const data = await this.getTextData_(uri); + const vvtText = this.convertToWebVTT_(data, mimeType, adCuePoints); + const blob = new Blob([vvtText], {type: 'text/vtt'}); + uri = shaka.media.MediaSourceEngine.createObjectURL(blob); + mimeType = 'text/vtt'; + } + + /** @type {shaka.extern.Stream} */ + const stream = { + id: this.nextExternalStreamId_++, + originalId: null, + groupId: null, + createSegmentIndex: () => Promise.resolve(), + segmentIndex: shaka.media.SegmentIndex.forSingleSegment( + /* startTime= */ 0, + /* duration= */ duration, + /* uris= */ [uri]), + mimeType: mimeType || '', + codecs: codec || '', + supplementalCodecs: '', + kind: kind, + encrypted: false, + drmInfos: [], + keyIds: new Set(), + language: language, + originalLanguage: language, + label: label || null, + type: ContentType.TEXT, + primary: false, + trickModeVideo: null, + dependencyStream: null, + emsgSchemeIdUris: null, + roles: [], + forced: !!forced, + channelsCount: null, + audioSamplingRate: null, + spatialAudio: false, + closedCaptions: null, + accessibilityPurpose: null, + external: true, + fastSwitching: false, + fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType( + mimeType || '', codec || '')]), + isAudioMuxedInVideo: false, + baseOriginalId: null, + }; + + const fullMimeType = shaka.util.MimeUtils.getFullType( + stream.mimeType, stream.codecs); + const supported = shaka.text.TextEngine.isTypeSupported(fullMimeType); + if (!supported) { + throw new shaka.util.Error( + shaka.util.Error.Severity.CRITICAL, + shaka.util.Error.Category.TEXT, + shaka.util.Error.Code.MISSING_TEXT_PLUGIN, + mimeType); + } + + this.manifest_.textStreams.push(stream); + this.onTracksChanged_(); + this.onTextChanged_(); + return shaka.util.StreamUtils.textStreamToTrack(stream); + })(); + + if (!this.fullyLoaded_) { + this.registerExtraTrackPromise_(p); } - const ContentType = shaka.util.ManifestParserUtils.ContentType; - - const seekRange = this.seekRange(); - let duration = seekRange.end - seekRange.start; - if (this.manifest_) { - duration = this.manifest_.presentationTimeline.getDuration(); - } - if (duration == Infinity) { - throw new shaka.util.Error( - shaka.util.Error.Severity.RECOVERABLE, - shaka.util.Error.Category.MANIFEST, - shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_TEXT_TO_LIVE_STREAM); - } - - if (adCuePoints.length) { - const data = await this.getTextData_(uri); - const vvtText = this.convertToWebVTT_(data, mimeType, adCuePoints); - const blob = new Blob([vvtText], {type: 'text/vtt'}); - uri = shaka.media.MediaSourceEngine.createObjectURL(blob); - mimeType = 'text/vtt'; - } - - /** @type {shaka.extern.Stream} */ - const stream = { - id: this.nextExternalStreamId_++, - originalId: null, - groupId: null, - createSegmentIndex: () => Promise.resolve(), - segmentIndex: shaka.media.SegmentIndex.forSingleSegment( - /* startTime= */ 0, - /* duration= */ duration, - /* uris= */ [uri]), - mimeType: mimeType || '', - codecs: codec || '', - supplementalCodecs: '', - kind: kind, - encrypted: false, - drmInfos: [], - keyIds: new Set(), - language: language, - originalLanguage: language, - label: label || null, - type: ContentType.TEXT, - primary: false, - trickModeVideo: null, - dependencyStream: null, - emsgSchemeIdUris: null, - roles: [], - forced: !!forced, - channelsCount: null, - audioSamplingRate: null, - spatialAudio: false, - closedCaptions: null, - accessibilityPurpose: null, - external: true, - fastSwitching: false, - fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType( - mimeType || '', codec || '')]), - isAudioMuxedInVideo: false, - baseOriginalId: null, - }; - - const fullMimeType = shaka.util.MimeUtils.getFullType( - stream.mimeType, stream.codecs); - const supported = shaka.text.TextEngine.isTypeSupported(fullMimeType); - if (!supported) { - throw new shaka.util.Error( - shaka.util.Error.Severity.CRITICAL, - shaka.util.Error.Category.TEXT, - shaka.util.Error.Code.MISSING_TEXT_PLUGIN, - mimeType); - } - - this.manifest_.textStreams.push(stream); - this.onTracksChanged_(); - this.onTextChanged_(); - return shaka.util.StreamUtils.textStreamToTrack(stream); + return p; } /** @@ -6959,148 +6974,158 @@ shaka.Player = class extends shaka.util.FakeEventTarget { * @return {!Promise} * @export */ - async addThumbnailsTrack(uri, mimeType) { - if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE && - this.loadMode_ != shaka.Player.LoadMode.SRC_EQUALS) { - shaka.log.error( - 'Must call load() and wait for it to resolve before adding image ' + - 'tracks.'); - throw new shaka.util.Error( - shaka.util.Error.Severity.RECOVERABLE, - shaka.util.Error.Category.PLAYER, - shaka.util.Error.Code.CONTENT_NOT_LOADED); - } - - if (!mimeType) { - mimeType = await this.getTextMimetype_(uri); - } - - if (mimeType != 'text/vtt') { - throw new shaka.util.Error( - shaka.util.Error.Severity.RECOVERABLE, - shaka.util.Error.Category.TEXT, - shaka.util.Error.Code.UNSUPPORTED_EXTERNAL_THUMBNAILS_URI, - uri); - } - - const ContentType = shaka.util.ManifestParserUtils.ContentType; - const seekRange = this.seekRange(); - let duration = seekRange.end - seekRange.start; - if (this.manifest_) { - duration = this.manifest_.presentationTimeline.getDuration(); - } - if (duration == Infinity) { - throw new shaka.util.Error( - shaka.util.Error.Severity.RECOVERABLE, - shaka.util.Error.Category.MANIFEST, - shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_THUMBNAILS_TO_LIVE_STREAM); - } - - const buffer = await this.getTextData_(uri); - - const factory = shaka.text.TextEngine.findParser(mimeType); - if (!factory) { - throw new shaka.util.Error( - shaka.util.Error.Severity.CRITICAL, - shaka.util.Error.Category.TEXT, - shaka.util.Error.Code.MISSING_TEXT_PLUGIN, - mimeType); - } - const TextParser = factory(); - const time = { - periodStart: 0, - segmentStart: 0, - segmentEnd: duration, - vttOffset: 0, - }; - const data = shaka.util.BufferUtils.toUint8(buffer); - const cues = TextParser.parseMedia(data, time, uri, /* images= */ []); - - const references = []; - for (const cue of cues) { - let uris = null; - const getUris = () => { - if (uris == null) { - uris = shaka.util.ManifestParserUtils.resolveUris( - [uri], [cue.payload]); - } - return uris || []; - }; - const reference = new shaka.media.SegmentReference( - cue.startTime, - cue.endTime, - getUris, - /* startByte= */ 0, - /* endByte= */ null, - /* initSegmentReference= */ null, - /* timestampOffset= */ 0, - /* appendWindowStart= */ 0, - /* appendWindowEnd= */ Infinity, - ); - if (cue.payload.includes('#xywh')) { - const spriteInfo = cue.payload.split('#xywh=')[1].split(','); - if (spriteInfo.length === 4) { - reference.setThumbnailSprite({ - height: parseInt(spriteInfo[3], 10), - positionX: parseInt(spriteInfo[0], 10), - positionY: parseInt(spriteInfo[1], 10), - width: parseInt(spriteInfo[2], 10), - }); - } + addThumbnailsTrack(uri, mimeType) { + const p = (async () => { + if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE && + this.loadMode_ != shaka.Player.LoadMode.SRC_EQUALS) { + shaka.log.error( + 'Must call load() and wait for it to resolve before adding image ' + + 'tracks.'); + throw new shaka.util.Error( + shaka.util.Error.Severity.RECOVERABLE, + shaka.util.Error.Category.PLAYER, + shaka.util.Error.Code.CONTENT_NOT_LOADED); } - references.push(reference); + + if (!mimeType) { + mimeType = await this.getTextMimetype_(uri); + } + + if (mimeType != 'text/vtt') { + throw new shaka.util.Error( + shaka.util.Error.Severity.RECOVERABLE, + shaka.util.Error.Category.TEXT, + shaka.util.Error.Code.UNSUPPORTED_EXTERNAL_THUMBNAILS_URI, + uri); + } + + const ContentType = shaka.util.ManifestParserUtils.ContentType; + const seekRange = this.seekRange(); + let duration = seekRange.end - seekRange.start; + if (this.manifest_) { + duration = this.manifest_.presentationTimeline.getDuration(); + } + if (duration == Infinity) { + throw new shaka.util.Error( + shaka.util.Error.Severity.RECOVERABLE, + shaka.util.Error.Category.MANIFEST, + // eslint-disable-next-line @stylistic/max-len + shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_THUMBNAILS_TO_LIVE_STREAM); + } + + const buffer = await this.getTextData_(uri); + + const factory = shaka.text.TextEngine.findParser(mimeType); + if (!factory) { + throw new shaka.util.Error( + shaka.util.Error.Severity.CRITICAL, + shaka.util.Error.Category.TEXT, + shaka.util.Error.Code.MISSING_TEXT_PLUGIN, + mimeType); + } + const TextParser = factory(); + const time = { + periodStart: 0, + segmentStart: 0, + segmentEnd: duration, + vttOffset: 0, + }; + const data = shaka.util.BufferUtils.toUint8(buffer); + const cues = TextParser.parseMedia(data, time, uri, /* images= */ []); + + const references = []; + for (const cue of cues) { + let uris = null; + const getUris = () => { + if (uris == null) { + uris = shaka.util.ManifestParserUtils.resolveUris( + [uri], [cue.payload]); + } + return uris || []; + }; + const reference = new shaka.media.SegmentReference( + cue.startTime, + cue.endTime, + getUris, + /* startByte= */ 0, + /* endByte= */ null, + /* initSegmentReference= */ null, + /* timestampOffset= */ 0, + /* appendWindowStart= */ 0, + /* appendWindowEnd= */ Infinity, + ); + if (cue.payload.includes('#xywh')) { + const spriteInfo = cue.payload.split('#xywh=')[1].split(','); + if (spriteInfo.length === 4) { + reference.setThumbnailSprite({ + height: parseInt(spriteInfo[3], 10), + positionX: parseInt(spriteInfo[0], 10), + positionY: parseInt(spriteInfo[1], 10), + width: parseInt(spriteInfo[2], 10), + }); + } + } + references.push(reference); + } + + let segmentMimeType = mimeType; + if (references.length) { + segmentMimeType = + await this.getTextMimetype_(references[0].getUris()[0]); + } + + /** @type {shaka.extern.Stream} */ + const stream = { + id: this.nextExternalStreamId_++, + originalId: null, + groupId: null, + createSegmentIndex: () => Promise.resolve(), + segmentIndex: new shaka.media.SegmentIndex(references), + mimeType: segmentMimeType || '', + codecs: '', + supplementalCodecs: '', + kind: '', + encrypted: false, + drmInfos: [], + keyIds: new Set(), + language: 'und', + originalLanguage: null, + label: null, + type: ContentType.IMAGE, + primary: false, + trickModeVideo: null, + dependencyStream: null, + emsgSchemeIdUris: null, + roles: [], + forced: false, + channelsCount: null, + audioSamplingRate: null, + spatialAudio: false, + closedCaptions: null, + tilesLayout: '1x1', + accessibilityPurpose: null, + external: true, + fastSwitching: false, + fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType( + segmentMimeType || '', '')]), + isAudioMuxedInVideo: false, + baseOriginalId: null, + }; + + if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) { + this.externalSrcEqualsThumbnailsStreams_.push(stream); + } else { + this.manifest_.imageStreams.push(stream); + } + this.onTracksChanged_(); + return shaka.util.StreamUtils.imageStreamToTrack(stream); + })(); + + if (!this.fullyLoaded_) { + this.registerExtraTrackPromise_(p); } - let segmentMimeType = mimeType; - if (references.length) { - segmentMimeType = await this.getTextMimetype_(references[0].getUris()[0]); - } - - /** @type {shaka.extern.Stream} */ - const stream = { - id: this.nextExternalStreamId_++, - originalId: null, - groupId: null, - createSegmentIndex: () => Promise.resolve(), - segmentIndex: new shaka.media.SegmentIndex(references), - mimeType: segmentMimeType || '', - codecs: '', - supplementalCodecs: '', - kind: '', - encrypted: false, - drmInfos: [], - keyIds: new Set(), - language: 'und', - originalLanguage: null, - label: null, - type: ContentType.IMAGE, - primary: false, - trickModeVideo: null, - dependencyStream: null, - emsgSchemeIdUris: null, - roles: [], - forced: false, - channelsCount: null, - audioSamplingRate: null, - spatialAudio: false, - closedCaptions: null, - tilesLayout: '1x1', - accessibilityPurpose: null, - external: true, - fastSwitching: false, - fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType( - segmentMimeType || '', '')]), - isAudioMuxedInVideo: false, - baseOriginalId: null, - }; - - if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) { - this.externalSrcEqualsThumbnailsStreams_.push(stream); - } else { - this.manifest_.imageStreams.push(stream); - } - this.onTracksChanged_(); - return shaka.util.StreamUtils.imageStreamToTrack(stream); + return p; } /** @@ -7115,123 +7140,131 @@ shaka.Player = class extends shaka.util.FakeEventTarget { * @return {!Promise} * @export */ - async addChaptersTrack(uri, language, mimeType) { - if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE && - this.loadMode_ != shaka.Player.LoadMode.SRC_EQUALS) { - shaka.log.error( - 'Must call load() and wait for it to resolve before adding ' + - 'chapters tracks.'); - throw new shaka.util.Error( - shaka.util.Error.Severity.RECOVERABLE, - shaka.util.Error.Category.PLAYER, - shaka.util.Error.Code.CONTENT_NOT_LOADED); - } + addChaptersTrack(uri, language, mimeType) { + const p = (async () => { + if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE && + this.loadMode_ != shaka.Player.LoadMode.SRC_EQUALS) { + shaka.log.error( + 'Must call load() and wait for it to resolve before adding ' + + 'chapters tracks.'); + throw new shaka.util.Error( + shaka.util.Error.Severity.RECOVERABLE, + shaka.util.Error.Category.PLAYER, + shaka.util.Error.Code.CONTENT_NOT_LOADED); + } - if (!mimeType) { - mimeType = await this.getTextMimetype_(uri); - } + if (!mimeType) { + mimeType = await this.getTextMimetype_(uri); + } - const ContentType = shaka.util.ManifestParserUtils.ContentType; - const seekRange = this.seekRange(); - let duration = seekRange.end - seekRange.start; - if (this.manifest_) { - duration = this.manifest_.presentationTimeline.getDuration(); - } - if (duration == Infinity) { - throw new shaka.util.Error( - shaka.util.Error.Severity.RECOVERABLE, - shaka.util.Error.Category.MANIFEST, - shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_CHAPTERS_TO_LIVE_STREAM); - } + const ContentType = shaka.util.ManifestParserUtils.ContentType; + const seekRange = this.seekRange(); + let duration = seekRange.end - seekRange.start; + if (this.manifest_) { + duration = this.manifest_.presentationTimeline.getDuration(); + } + if (duration == Infinity) { + throw new shaka.util.Error( + shaka.util.Error.Severity.RECOVERABLE, + shaka.util.Error.Category.MANIFEST, + shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_CHAPTERS_TO_LIVE_STREAM); + } - const factory = shaka.text.TextEngine.findParser(mimeType); - if (!factory) { - throw new shaka.util.Error( - shaka.util.Error.Severity.CRITICAL, - shaka.util.Error.Category.TEXT, - shaka.util.Error.Code.MISSING_TEXT_PLUGIN, - mimeType); - } + const factory = shaka.text.TextEngine.findParser(mimeType); + if (!factory) { + throw new shaka.util.Error( + shaka.util.Error.Severity.CRITICAL, + shaka.util.Error.Category.TEXT, + shaka.util.Error.Code.MISSING_TEXT_PLUGIN, + mimeType); + } - const buffer = await this.getTextData_(uri); - const textParser = factory(); - const time = { - periodStart: 0, - segmentStart: 0, - segmentEnd: duration, - vttOffset: 0, - }; - const data = shaka.util.BufferUtils.toUint8(buffer); - const cues = textParser.parseMedia(data, time, uri, /* images= */ []); - - const references = []; - for (const cue of cues) { - const reference = new shaka.media.SegmentReference( - cue.startTime, - cue.endTime, - () => [], - /* startByte= */ 0, - /* endByte= */ null, - /* initSegmentReference= */ null, - /* timestampOffset= */ 0, - /* appendWindowStart= */ 0, - /* appendWindowEnd= */ Infinity, - ); - /** @type {shaka.media.SegmentReference.Metadata} */ - const metadata = { - title: cue.payload, - images: [], + const buffer = await this.getTextData_(uri); + const textParser = factory(); + const time = { + periodStart: 0, + segmentStart: 0, + segmentEnd: duration, + vttOffset: 0, }; - reference.setMetadata(metadata); - references.push(reference); + const data = shaka.util.BufferUtils.toUint8(buffer); + const cues = textParser.parseMedia(data, time, uri, /* images= */ []); + + const references = []; + for (const cue of cues) { + const reference = new shaka.media.SegmentReference( + cue.startTime, + cue.endTime, + () => [], + /* startByte= */ 0, + /* endByte= */ null, + /* initSegmentReference= */ null, + /* timestampOffset= */ 0, + /* appendWindowStart= */ 0, + /* appendWindowEnd= */ Infinity, + ); + /** @type {shaka.media.SegmentReference.Metadata} */ + const metadata = { + title: cue.payload, + images: [], + }; + reference.setMetadata(metadata); + references.push(reference); + } + + const chaptersMimeType = 'text/plain'; + + /** @type {shaka.extern.Stream} */ + const stream = { + id: this.nextExternalStreamId_++, + originalId: null, + groupId: null, + createSegmentIndex: () => Promise.resolve(), + segmentIndex: new shaka.media.SegmentIndex(references), + mimeType: chaptersMimeType, + codecs: '', + supplementalCodecs: '', + kind: '', + encrypted: false, + drmInfos: [], + keyIds: new Set(), + language: language, + originalLanguage: language, + label: null, + type: ContentType.CHAPTER, + primary: false, + trickModeVideo: null, + dependencyStream: null, + emsgSchemeIdUris: null, + roles: [], + forced: false, + channelsCount: null, + audioSamplingRate: null, + spatialAudio: false, + closedCaptions: null, + accessibilityPurpose: null, + external: true, + fastSwitching: false, + fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType( + chaptersMimeType, '')]), + isAudioMuxedInVideo: false, + baseOriginalId: null, + }; + + if (this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE) { + this.manifest_.chapterStreams.push(stream); + } else { + this.externalSrcEqualsChapterStreams_.push(stream); + } + this.onTracksChanged_(); + return shaka.util.StreamUtils.chapterStreamToTrack(stream); + })(); + + if (!this.fullyLoaded_) { + this.registerExtraTrackPromise_(p); } - const chaptersMimeType = 'text/plain'; - - /** @type {shaka.extern.Stream} */ - const stream = { - id: this.nextExternalStreamId_++, - originalId: null, - groupId: null, - createSegmentIndex: () => Promise.resolve(), - segmentIndex: new shaka.media.SegmentIndex(references), - mimeType: chaptersMimeType, - codecs: '', - supplementalCodecs: '', - kind: '', - encrypted: false, - drmInfos: [], - keyIds: new Set(), - language: language, - originalLanguage: language, - label: null, - type: ContentType.CHAPTER, - primary: false, - trickModeVideo: null, - dependencyStream: null, - emsgSchemeIdUris: null, - roles: [], - forced: false, - channelsCount: null, - audioSamplingRate: null, - spatialAudio: false, - closedCaptions: null, - accessibilityPurpose: null, - external: true, - fastSwitching: false, - fullMimeTypes: new Set([shaka.util.MimeUtils.getFullType( - chaptersMimeType, '')]), - isAudioMuxedInVideo: false, - baseOriginalId: null, - }; - - if (this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE) { - this.manifest_.chapterStreams.push(stream); - } else { - this.externalSrcEqualsChapterStreams_.push(stream); - } - this.onTracksChanged_(); - return shaka.util.StreamUtils.chapterStreamToTrack(stream); + return p; } /** @@ -9340,6 +9373,26 @@ shaka.Player = class extends shaka.util.FakeEventTarget { delete config['preferredVideoCodecs']; } } + + /** + * @param {!Promise} p + * @private + */ + registerExtraTrackPromise_(p) { + this.pendingExtraTrackPromises_.add(p); + p.finally(() => this.pendingExtraTrackPromises_.delete(p)); + } + + /** + * @return {!Promise} + * @private + */ + async waitForPendingExtraTrackPromises_() { + if (!this.pendingExtraTrackPromises_.size) { + return; + } + await Promise.all([...this.pendingExtraTrackPromises_]); + } }; /** diff --git a/lib/queue/queue_manager.js b/lib/queue/queue_manager.js index c81bb5083..c3e3308bb 100644 --- a/lib/queue/queue_manager.js +++ b/lib/queue/queue_manager.js @@ -224,11 +224,17 @@ shaka.queue.QueueManager = class extends shaka.util.FakeEventTarget { this.player_.configure(item.config); } + if (item.extraText?.length || + item.extraThumbnail?.length || + item.extraChapter?.length) { + this.eventManager_.listenOnce(this.player_, 'streaming', async () => { + await this.addExtraTracks_(item); + }); + } + await this.player_.load(assetUriOrPreloader, item.startTime, item.mimeType); this.preloadNext_ = null; - - await this.addExtraTracks_(item); } /**