Skip to content

Commit

Permalink
fix: wait for endedtimeline event from transmuxer when reaching the e…
Browse files Browse the repository at this point in the history
…nd of a timeline (#1058)

Previously, when the end of a timeline was reached (i.e., the last
segment in a given timeline was appended), `endTimeline` would be
called, but the loader would not wait for the event to finish. This
lead to issues where segment information came back after a segment is
considered done with processing, and segment information could be
dropped. This fix waits for the `endedtimeline` message from the
transmuxer before it considers the transmux complete.
  • Loading branch information
gesinger authored Feb 8, 2021
1 parent a34b4da commit b01ab72
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 14 deletions.
53 changes: 53 additions & 0 deletions src/media-segment-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@ const transmuxAndNotify = ({
audioSegmentTimingInfoFn,
id3Fn,
captionsFn,
isEndOfTimeline,
endedTimelineFn,
dataFn,
doneFn
}) => {
Expand Down Expand Up @@ -360,6 +362,13 @@ const transmuxAndNotify = ({
onCaptions: (captions) => {
captionsFn(segment, [captions]);
},
// if this is a partial transmux, the end of the timeline has not yet been reached
// until the last part of the segment is processed (at which point isPartial will
// be false)
isEndOfTimeline: isEndOfTimeline && !isPartial,
onEndedTimeline: () => {
endedTimelineFn();
},
onDone: (result) => {
// To handle partial appends, there won't be a done function passed in (since
// there's still, potentially, more segment to process), so there's nothing to do.
Expand All @@ -382,6 +391,8 @@ const handleSegmentBytes = ({
audioSegmentTimingInfoFn,
id3Fn,
captionsFn,
isEndOfTimeline,
endedTimelineFn,
dataFn,
doneFn
}) => {
Expand Down Expand Up @@ -513,6 +524,8 @@ const handleSegmentBytes = ({
audioSegmentTimingInfoFn,
id3Fn,
captionsFn,
isEndOfTimeline,
endedTimelineFn,
dataFn,
doneFn
});
Expand All @@ -533,6 +546,11 @@ const handleSegmentBytes = ({
* @param {Function} audioSegmentTimingInfoFn
* a callback that receives audio timing info based on media times and
* any adjustments made by the transmuxer
* @param {boolean} isEndOfTimeline
* true if this segment represents the last segment in a timeline
* @param {Function} endedTimelineFn
* a callback made when a timeline is ended, will only be called if
* isEndOfTimeline is true
* @param {Function} dataFn - a callback that is executed when segment bytes are available
* and ready to use
* @param {Function} doneFn - a callback that is executed after decryption has completed
Expand All @@ -546,6 +564,8 @@ const decryptSegment = ({
audioSegmentTimingInfoFn,
id3Fn,
captionsFn,
isEndOfTimeline,
endedTimelineFn,
dataFn,
doneFn
}) => {
Expand All @@ -570,6 +590,8 @@ const decryptSegment = ({
audioSegmentTimingInfoFn,
id3Fn,
captionsFn,
isEndOfTimeline,
endedTimelineFn,
dataFn,
doneFn
});
Expand Down Expand Up @@ -618,6 +640,11 @@ const decryptSegment = ({
* any adjustments made by the transmuxer
* @param {Function} id3Fn - a callback that receives ID3 metadata
* @param {Function} captionsFn - a callback that receives captions
* @param {boolean} isEndOfTimeline
* true if this segment represents the last segment in a timeline
* @param {Function} endedTimelineFn
* a callback made when a timeline is ended, will only be called if
* isEndOfTimeline is true
* @param {Function} dataFn - a callback that is executed when segment bytes are available
* and ready to use
* @param {Function} doneFn - a callback that is executed after all resources have been
Expand All @@ -632,6 +659,8 @@ const waitForCompletion = ({
audioSegmentTimingInfoFn,
id3Fn,
captionsFn,
isEndOfTimeline,
endedTimelineFn,
dataFn,
doneFn
}) => {
Expand Down Expand Up @@ -678,6 +707,8 @@ const waitForCompletion = ({
audioSegmentTimingInfoFn,
id3Fn,
captionsFn,
isEndOfTimeline,
endedTimelineFn,
dataFn,
doneFn
});
Expand All @@ -693,6 +724,8 @@ const waitForCompletion = ({
audioSegmentTimingInfoFn,
id3Fn,
captionsFn,
isEndOfTimeline,
endedTimelineFn,
dataFn,
doneFn
});
Expand Down Expand Up @@ -732,6 +765,11 @@ const handleLoadEnd = ({ loadendState, abortFn }) => (event) => {
* @param {Function} audioSegmentTimingInfoFn
* a callback that receives audio timing info based on media times and
* any adjustments made by the transmuxer
* @param {boolean} isEndOfTimeline
* true if this segment represents the last segment in a timeline
* @param {Function} endedTimelineFn
* a callback made when a timeline is ended, will only be called if
* isEndOfTimeline is true
* @param {Function} dataFn - a callback that is executed when segment bytes are available
* and ready to use
* @param {Event} event - the progress event object from XMLHttpRequest
Expand All @@ -745,6 +783,8 @@ const handleProgress = ({
audioSegmentTimingInfoFn,
id3Fn,
captionsFn,
isEndOfTimeline,
endedTimelineFn,
dataFn,
handlePartialData
}) => (event) => {
Expand Down Expand Up @@ -782,6 +822,8 @@ const handleProgress = ({
audioSegmentTimingInfoFn,
id3Fn,
captionsFn,
isEndOfTimeline,
endedTimelineFn,
dataFn
});
}
Expand Down Expand Up @@ -853,6 +895,11 @@ const handleProgress = ({
* any adjustments made by the transmuxer
* @param {Function} id3Fn - a callback that receives ID3 metadata
* @param {Function} captionsFn - a callback that receives captions
* @param {boolean} isEndOfTimeline
* true if this segment represents the last segment in a timeline
* @param {Function} endedTimelineFn
* a callback made when a timeline is ended, will only be called if
* isEndOfTimeline is true
* @param {Function} dataFn - a callback that receives data from the main segment's xhr
* request, transmuxed if needed
* @param {Function} doneFn - a callback that is executed only once all requests have
Expand All @@ -873,6 +920,8 @@ export const mediaSegmentRequest = ({
audioSegmentTimingInfoFn,
id3Fn,
captionsFn,
isEndOfTimeline,
endedTimelineFn,
dataFn,
doneFn,
handlePartialData
Expand All @@ -887,6 +936,8 @@ export const mediaSegmentRequest = ({
audioSegmentTimingInfoFn,
id3Fn,
captionsFn,
isEndOfTimeline,
endedTimelineFn,
dataFn,
doneFn
});
Expand Down Expand Up @@ -954,6 +1005,8 @@ export const mediaSegmentRequest = ({
audioSegmentTimingInfoFn,
id3Fn,
captionsFn,
isEndOfTimeline,
endedTimelineFn,
dataFn,
handlePartialData
})
Expand Down
24 changes: 11 additions & 13 deletions src/segment-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2193,6 +2193,13 @@ export default class SegmentLoader extends videojs.EventTarget {
}

const simpleSegment = this.createSimplifiedSegmentObj_(segmentInfo);
const isEndOfStream = this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist);
const isWalkingForward = this.mediaIndex !== null;
const isDiscontinuity = segmentInfo.timeline !== this.currentTimeline_ &&
// currentTimeline starts at -1, so we shouldn't end the timeline switching to 0,
// the first timeline
segmentInfo.timeline > 0;
const isEndOfTimeline = isEndOfStream || (isWalkingForward && isDiscontinuity);

segmentInfo.abortRequests = mediaSegmentRequest({
xhr: this.vhs_.xhr,
Expand All @@ -2207,6 +2214,10 @@ export default class SegmentLoader extends videojs.EventTarget {
videoSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'video', segmentInfo.requestId),
audioSegmentTimingInfoFn: this.handleSegmentTimingInfo_.bind(this, 'audio', segmentInfo.requestId),
captionsFn: this.handleCaptions_.bind(this),
isEndOfTimeline,
endedTimelineFn: () => {
this.logger_('received endedtimeline callback');
},
id3Fn: this.handleId3_.bind(this),

dataFn: this.handleData_.bind(this),
Expand Down Expand Up @@ -2410,19 +2421,6 @@ export default class SegmentLoader extends videojs.EventTarget {
// state away from loading until we are officially done loading the segment data.
this.state = 'APPENDING';

const isEndOfStream = this.isEndOfStream_(segmentInfo.mediaIndex, segmentInfo.playlist);
const isWalkingForward = this.mediaIndex !== null;
const isDiscontinuity = segmentInfo.timeline !== this.currentTimeline_ &&
// TODO verify this behavior
// currentTimeline starts at -1, but we shouldn't end the timeline switching to 0,
// the first timeline
segmentInfo.timeline > 0;

if (!segmentInfo.isFmp4 &&
(isEndOfStream || (isWalkingForward && isDiscontinuity))) {
segmentTransmuxer.endTimeline(this.transmuxer_);
}

// used for testing
this.trigger('appending');

Expand Down
21 changes: 20 additions & 1 deletion src/segment-transmuxer.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,15 @@ export const processTransmux = (options) => {
onAudioSegmentTimingInfo,
onId3,
onCaptions,
onDone
onDone,
onEndedTimeline,
isEndOfTimeline
} = options;
const transmuxedData = {
isPartial,
buffer: []
};
let waitForEndedTimelineEvent = isEndOfTimeline;

const handleMessage = (event) => {
if (transmuxer.currentTransmux !== options) {
Expand Down Expand Up @@ -121,12 +124,24 @@ export const processTransmux = (options) => {
if (event.data.action === 'caption') {
onCaptions(event.data.caption);
}
if (event.data.action === 'endedtimeline') {
waitForEndedTimelineEvent = false;
onEndedTimeline();
}

// wait for the transmuxed event since we may have audio and video
if (event.data.type !== 'transmuxed') {
return;
}

// If the "endedtimeline" event has not yet fired, and this segment represents the end
// of a timeline, that means there may still be data events before the segment
// processing can be considerred complete. In that case, the final event should be
// an "endedtimeline" event with the type "transmuxed."
if (waitForEndedTimelineEvent) {
return;
}

transmuxer.onmessage = null;
handleDone_({
transmuxedData,
Expand Down Expand Up @@ -185,6 +200,10 @@ export const processTransmux = (options) => {
// even if we didn't push any bytes, we have to make sure we flush in case we reached
// the end of the segment
transmuxer.postMessage({ action: isPartial ? 'partialFlush' : 'flush' });

if (isEndOfTimeline) {
transmuxer.postMessage({ action: 'endTimeline' });
}
};

export const dequeue = (transmuxer) => {
Expand Down
Loading

0 comments on commit b01ab72

Please sign in to comment.