diff --git a/src/segment-loader.js b/src/segment-loader.js index b2c2dc5dc..abc4fcf2c 100644 --- a/src/segment-loader.js +++ b/src/segment-loader.js @@ -533,9 +533,17 @@ export default class SegmentLoader extends videojs.EventTarget { }; } - const oldId = oldPlaylist ? oldPlaylist.id : null; + let oldId = null; - this.logger_(`playlist update [${oldId} => ${newPlaylist.id}]`); + if (oldPlaylist) { + if (oldPlaylist.id) { + oldId = oldPlaylist.id; + } else if (oldPlaylist.uri) { + oldId = oldPlaylist.uri; + } + } + + this.logger_(`playlist update [${oldId} => ${newPlaylist.id || newPlaylist.uri}]`); // in VOD, this is always a rendition switch (or we updated our syncInfo above) // in LIVE, we always want to update with new playlists (including refreshes) @@ -776,11 +784,7 @@ export default class SegmentLoader extends videojs.EventTarget { return; } - let isEndOfStream = detectEndOfStream(this.playlist_, - this.mediaSource_, - segmentInfo.mediaIndex); - - if (isEndOfStream) { + if (this.isEndOfStream_(segmentInfo.mediaIndex)) { this.endOfStream(); return; } @@ -826,6 +830,21 @@ export default class SegmentLoader extends videojs.EventTarget { return segmentInfo.startOfSegment < this.sourceUpdater_.audioTimestampOffset(); } + /** + * Determines if this segment loader is at the end of it's stream. + * + * @param {Number} mediaIndex the index of segment we last appended + * @param {Object} [playlist=this.playlist_] a media playlist object + * @returns {Boolean} true if at end of stream, false otherwise. + */ + isEndOfStream_(mediaIndex, playlist = this.playlist_) { + return detectEndOfStream( + playlist, + this.mediaSource_, + mediaIndex + ) && !this.sourceUpdater_.updating(); + } + /** * Determines what segment request should be made, given current playback * state. @@ -1466,7 +1485,10 @@ export default class SegmentLoader extends videojs.EventTarget { segments }); - this.sourceUpdater_.appendBuffer(type, bytes, (error) => { + const videoSegmentTimingInfoCallback = + this.handleVideoSegmentTimingInfo_.bind(this, initSegment.requestId) + + this.sourceUpdater_.appendBuffer({type, bytes, videoSegmentTimingInfoCallback}, (error) => { if (error) { this.error(error); // If an append errors, we can't recover. @@ -1478,6 +1500,28 @@ export default class SegmentLoader extends videojs.EventTarget { }); } + handleVideoSegmentTimingInfo_(requestId, event) { + if (!this.pendingSegment_ || requestId !== this.pendingSegment_.requestId) { + return; + } + + const segment = this.pendingSegment_.segment; + + if (!segment.videoTimingInfo) { + segment.videoTimingInfo = {}; + } + + segment.videoTimingInfo.transmuxerPrependedSeconds = + event.videoSegmentTimingInfo.prependedContentDuration || 0; + segment.videoTimingInfo.transmuxedPresentationStart = + event.videoSegmentTimingInfo.start.presentation; + segment.videoTimingInfo.transmuxedPresentationEnd = + event.videoSegmentTimingInfo.end.presentation; + // mainly used as a reference for debugging + segment.videoTimingInfo.baseMediaDecodeTime = + event.videoSegmentTimingInfo.baseMediaDecodeTime; + } + appendData_(segmentInfo, result) { const { type, @@ -2016,11 +2060,7 @@ export default class SegmentLoader extends videojs.EventTarget { // any time an update finishes and the last segment is in the // buffer, end the stream. this ensures the "ended" event will // fire if playback reaches that point. - const isEndOfStream = detectEndOfStream(segmentInfo.playlist, - this.mediaSource_, - segmentInfo.mediaIndex + 1); - - if (isEndOfStream) { + if (this.isEndOfStream_(segmentInfo.mediaIndex + 1, segmentInfo.playlist)) { this.endOfStream(); } @@ -2081,6 +2121,7 @@ export default class SegmentLoader extends videojs.EventTarget { const Cue = window.WebKitDataCue || window.VTTCue; const value = { + custom: segment.custom, dateTimeObject: segment.dateTimeObject, dateTimeString: segment.dateTimeString, bandwidth: segmentInfo.playlist.attributes.BANDWIDTH, diff --git a/src/source-updater.js b/src/source-updater.js index 44624064f..e7cfc17f3 100644 --- a/src/source-updater.js +++ b/src/source-updater.js @@ -26,7 +26,11 @@ const actions = { callback(); }, duration: (duration) => (sourceUpdater) => { - sourceUpdater.mediaSource.duration = duration; + try { + sourceUpdater.mediaSource.duration = duration; + } catch (e) { + videojs.log.warn('Failed to set media source duration', e); + } } }; @@ -245,17 +249,64 @@ export default class SourceUpdater extends videojs.EventTarget { * @param {Function} done the function to call when done * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data */ - appendBuffer(type, bytes, doneFn) { + appendBuffer({type, bytes, videoSegmentTimingInfoCallback}, doneFn) { this.processedAppend_ = true; + let originalAction = action = actions.appendBuffer(bytes); + let originalDoneFn = doneFn; + + if (videoSegmentTimingInfoCallback) { + action = (type, sourceUpdater) => { + if (type === 'video' && this.videoBuffer) { + this.videoBuffer.addEventListener('videoSegmentTimingInfo', videoSegmentTimingInfoCallback); + } + originalAction(type, sourceUpdater); + }; + + doneFn = (err) => { + if (this.videoBuffer) { + this.videoBuffer.removeEventListener('videoSegmentTimingInfo', videoSegmentTimingInfoCallback); + } + originalDoneFn(err); + }; + } + + pushQueue({ type, sourceUpdater: this, - action: actions.appendBuffer(bytes), + action, doneFn, name: 'appendBuffer' }); } + /******** + ------- + This chunk is related to https://github.com/videojs/http-streaming/pull/371 + The interface here is different from the one above, as well + as how things are appended (pushqueue vs doing things on the sourcebuffer) + sall related calls will need to be modified as well. + ------- + * + * + appendBuffer(config, done) { + this.processedAppend_ = true; + this.queueCallback_(() => { + if (config.videoSegmentTimingInfoCallback) { + this.sourceBuffer_.addEventListener( + 'videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback); + } + this.sourceBuffer_.appendBuffer(config.bytes); + }, () => { + if (config.videoSegmentTimingInfoCallback) { + this.sourceBuffer_.removeEventListener( + 'videoSegmentTimingInfo', config.videoSegmentTimingInfoCallback); + } + done(); + }); + } + */ + audioBuffered() { return this.audioBuffer && this.audioBuffer.buffered ? this.audioBuffer.buffered : videojs.createTimeRange(); @@ -338,15 +389,22 @@ export default class SourceUpdater extends videojs.EventTarget { * @return {Boolean} the updating status of the SourceBuffer */ updating() { + // we are updating if: + // the audio source buffer is updating if (this.audioBuffer && this.audioBuffer.updating) { return true; } + + // the video source buffer is updating if (this.videoBuffer && this.videoBuffer.updating) { return true; } - if (this.pendingCallback_) { + + // or we have a pending callback that is not our internal noop + if (this.pendingCallback_ && this.pendingCallback_ !== noop) { return true; } + return false; }