diff --git a/src/master-playlist-controller.js b/src/master-playlist-controller.js index 949fb2538..b84d6ae9b 100644 --- a/src/master-playlist-controller.js +++ b/src/master-playlist-controller.js @@ -1045,9 +1045,10 @@ export class MasterPlaylistController extends videojs.EventTarget { let isEndOfStream = this.mainSegmentLoader_.ended_; if (this.mediaTypes_.AUDIO.activePlaylistLoader) { + const mainMediaInfo = this.mainSegmentLoader_.getCurrentMediaInfo_(); + // if the audio playlist loader exists, then alternate audio is active - if (!this.mainSegmentLoader_.currentMediaInfo_ || - this.mainSegmentLoader_.currentMediaInfo_.hasVideo) { + if (!mainMediaInfo || mainMediaInfo.hasVideo) { // if we do not know if the main segment loader contains video yet or if we // definitively know the main segment loader contains video, then we need to wait // for both main and audio segment loaders to call endOfStream @@ -1619,9 +1620,13 @@ export class MasterPlaylistController extends videojs.EventTarget { areMediaTypesKnown_() { const usingAudioLoader = !!this.mediaTypes_.AUDIO.activePlaylistLoader; + const hasMainMediaInfo = !!this.mainSegmentLoader_.getCurrentMediaInfo_(); + // if we are not using an audio loader, then we have audio media info + // otherwise check on the segment loader. + const hasAudioMediaInfo = !usingAudioLoader ? true : !!this.audioSegmentLoader_.getCurrentMediaInfo_(); // one or both loaders has not loaded sufficently to get codecs - if (!this.mainSegmentLoader_.currentMediaInfo_ || (usingAudioLoader && !this.audioSegmentLoader_.currentMediaInfo_)) { + if (!hasMainMediaInfo || !hasAudioMediaInfo) { return false; } @@ -1630,8 +1635,8 @@ export class MasterPlaylistController extends videojs.EventTarget { getCodecsOrExclude_() { const media = { - main: this.mainSegmentLoader_.currentMediaInfo_ || {}, - audio: this.audioSegmentLoader_.currentMediaInfo_ || {} + main: this.mainSegmentLoader_.getCurrentMediaInfo_() || {}, + audio: this.audioSegmentLoader_.getCurrentMediaInfo_() || {} }; // set "main" media equal to video diff --git a/src/segment-loader.js b/src/segment-loader.js index 4b3598f15..9ea2a43b9 100644 --- a/src/segment-loader.js +++ b/src/segment-loader.js @@ -811,12 +811,14 @@ export default class SegmentLoader extends videojs.EventTarget { * TimeRange object representing the current buffered ranges */ buffered_() { - if (!this.sourceUpdater_ || !this.startingMediaInfo_) { + const trackInfo = this.getMediaInfo_(); + + if (!this.sourceUpdater_ || !trackInfo) { return videojs.createTimeRanges(); } if (this.loaderType_ === 'main') { - const { hasAudio, hasVideo, isMuxed } = this.startingMediaInfo_; + const { hasAudio, hasVideo, isMuxed } = trackInfo; if (hasVideo && hasAudio && !this.audioDisabled_ && !isMuxed) { return this.sourceUpdater_.buffered(); @@ -1206,7 +1208,7 @@ export default class SegmentLoader extends videojs.EventTarget { return; } - if (!this.sourceUpdater_ || !this.startingMediaInfo_) { + if (!this.sourceUpdater_ || !this.getMediaInfo_()) { this.logger_('skipping remove because no source updater or starting media info'); // nothing to remove if we haven't processed any media return; @@ -1871,7 +1873,7 @@ export default class SegmentLoader extends videojs.EventTarget { // created together (before appending). Source buffer creation uses the presence of // audio and video data to determine whether to create audio/video source buffers, and // uses processed (transmuxed or parsed) media to determine the types required. - if (!this.currentMediaInfo_) { + if (!this.getCurrentMediaInfo_()) { return true; } @@ -1905,6 +1907,14 @@ export default class SegmentLoader extends videojs.EventTarget { return true; } + getCurrentMediaInfo_(segmentInfo = this.pendingSegment_) { + return segmentInfo && segmentInfo.trackInfo || this.currentMediaInfo_; + } + + getMediaInfo_(segmentInfo = this.pendingSegment_) { + return this.getCurrentMediaInfo_(segmentInfo) || this.startingMediaInfo_; + } + hasEnoughInfoToAppend_() { if (!this.sourceUpdater_.ready()) { return false; @@ -1917,15 +1927,16 @@ export default class SegmentLoader extends videojs.EventTarget { } const segmentInfo = this.pendingSegment_; + const trackInfo = this.getCurrentMediaInfo_(); // no segment to append any data for or // we do not have information on this specific // segment yet - if (!segmentInfo || !segmentInfo.trackInfo) { + if (!segmentInfo || !trackInfo) { return false; } - const {hasAudio, hasVideo, isMuxed} = this.currentMediaInfo_; + const {hasAudio, hasVideo, isMuxed} = trackInfo; if (hasVideo && !segmentInfo.videoTimingInfo) { return false; @@ -2005,8 +2016,9 @@ export default class SegmentLoader extends videojs.EventTarget { segmentInfo.timingInfo.start = segmentInfo[timingInfoPropertyForMedia(result.type)].start; } else { + const trackInfo = this.getCurrentMediaInfo_(); const useVideoTimingInfo = - this.loaderType_ === 'main' && this.currentMediaInfo_.hasVideo; + this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo; let firstVideoFrameTimeForData; if (useVideoTimingInfo) { @@ -2714,7 +2726,9 @@ export default class SegmentLoader extends videojs.EventTarget { } waitForAppendsToComplete_(segmentInfo) { - if (!this.currentMediaInfo_) { + const trackInfo = this.getCurrentMediaInfo_(segmentInfo); + + if (!trackInfo) { this.error({ message: 'No starting media returned, likely due to an unsupported media format.', blacklistDuration: Infinity @@ -2725,7 +2739,7 @@ export default class SegmentLoader extends videojs.EventTarget { // Although transmuxing is done, appends may not yet be finished. Throw a marker // on each queue this loader is responsible for to ensure that the appends are // complete. - const {hasAudio, hasVideo, isMuxed} = this.currentMediaInfo_; + const {hasAudio, hasVideo, isMuxed} = trackInfo; const waitForVideo = this.loaderType_ === 'main' && hasVideo; const waitForAudio = !this.audioDisabled_ && hasAudio && !isMuxed; @@ -2793,7 +2807,7 @@ export default class SegmentLoader extends videojs.EventTarget { checkForIllegalMediaSwitch(trackInfo) { const illegalMediaSwitchError = - illegalMediaSwitch(this.loaderType_, this.currentMediaInfo_, trackInfo); + illegalMediaSwitch(this.loaderType_, this.getCurrentMediaInfo_(), trackInfo); if (illegalMediaSwitchError) { this.error({ @@ -2848,8 +2862,9 @@ export default class SegmentLoader extends videojs.EventTarget { updateTimingInfoEnd_(segmentInfo) { segmentInfo.timingInfo = segmentInfo.timingInfo || {}; + const trackInfo = this.getMediaInfo_(); const useVideoTimingInfo = - this.loaderType_ === 'main' && this.currentMediaInfo_.hasVideo; + this.loaderType_ === 'main' && trackInfo && trackInfo.hasVideo; const prioritizedTimingInfo = useVideoTimingInfo && segmentInfo.videoTimingInfo ? segmentInfo.videoTimingInfo : segmentInfo.audioTimingInfo; diff --git a/test/loader-common.js b/test/loader-common.js index d7e8deb51..3e74c1bc6 100644 --- a/test/loader-common.js +++ b/test/loader-common.js @@ -22,7 +22,8 @@ import { Vhs } from '../src/videojs-http-streaming'; import { muxed as muxedSegment, mp4Video as mp4VideoSegment, - mp4VideoInit as mp4VideoInitSegment + mp4VideoInit as mp4VideoInitSegment, + videoOneSecond as tsVideoSegment } from 'create-test-data!segments'; /** @@ -821,8 +822,38 @@ export const LoaderCommonFactory = ({ }); }); - // only main/fmp4 segment loaders use parts/partIndex + // only main/fmp4 segment loaders use async appends and parts/partIndex if (usesAsyncAppends) { + QUnit.test('playlist change before any appends does not error', function(assert) { + return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { + loader.playlist(playlistWithDuration(50, { + uri: 'bar-720.m3u8', + mediaSequence: 0, + endList: true + })); + + loader.load(); + this.clock.tick(1); + return Promise.resolve(); + }).then(() => new Promise((resolve, reject) => { + loader.on('playlistupdate', () => { + this.clock.tick(1); + resolve(); + }); + loader.on('trackinfo', () => { + loader.playlist(playlistWithDuration(50, { + uri: 'bar-1080.m3u8', + mediaSequence: 0, + endList: true + })); + }); + standardXHRResponse(this.requests.shift(), tsVideoSegment()); + + })).then(() => { + assert.equal(loader.pendingSegment_.playlist.uri, 'bar-720.m3u8', 'previous playlist segment'); + }); + }); + QUnit.test('mediaIndex and partIndex are used', function(assert) { return setupMediaSource(loader.mediaSource_, loader.sourceUpdater_).then(() => { loader.playlist(playlistWithDuration(50, {