Skip to content

Commit

Permalink
fix: add part level sync points, fix LL hls sync issues, add part tim…
Browse files Browse the repository at this point in the history
…ing info (#1125)
  • Loading branch information
brandonocasey committed May 26, 2021
1 parent ce03f66 commit ee5841d
Show file tree
Hide file tree
Showing 5 changed files with 537 additions and 176 deletions.
95 changes: 64 additions & 31 deletions src/playlist.js
Expand Up @@ -18,13 +18,13 @@ const {createTimeRange} = videojs;
*
* @return {Array} The part/segment list.
*/
const getPartsAndSegments = (playlist) => (playlist.segments || []).reduce((acc, segment, si) => {
export const getPartsAndSegments = (playlist) => (playlist.segments || []).reduce((acc, segment, si) => {
if (segment.parts) {
segment.parts.forEach(function(part, pi) {
acc.push({duration: part.duration, segmentIndex: si, partIndex: pi});
acc.push({duration: part.duration, segmentIndex: si, partIndex: pi, part, segment});
});
} else {
acc.push({duration: segment.duration, segmentIndex: si, partIndex: null});
acc.push({duration: segment.duration, segmentIndex: si, partIndex: null, segment, part: null});
}
return acc;
}, []);
Expand Down Expand Up @@ -261,12 +261,13 @@ export const duration = function(playlist, endSequence, expired) {
* playlist in which case, the targetDuration of the playlist is used
* to approximate the durations of the segments
*
* @param {Object} playlist a media playlist object
* @param {number} startIndex
* @param {number} endIndex
* @param {Array} options.durationList list to iterate over for durations.
* @param {number} options.defaultDuration duration to use for elements before or after the durationList
* @param {number} options.startIndex partsAndSegments index to start
* @param {number} options.endIndex partsAndSegments index to end.
* @return {number} the number of seconds between startIndex and endIndex
*/
export const sumDurations = function(playlist, startIndex, endIndex) {
export const sumDurations = function({defaultDuration, durationList, startIndex, endIndex}) {
let durations = 0;

if (startIndex > endIndex) {
Expand All @@ -275,13 +276,13 @@ export const sumDurations = function(playlist, startIndex, endIndex) {

if (startIndex < 0) {
for (let i = startIndex; i < Math.min(0, endIndex); i++) {
durations += playlist.targetDuration;
durations += defaultDuration;
}
startIndex = 0;
}

for (let i = startIndex; i < endIndex; i++) {
durations += playlist.segments[i].duration;
durations += durationList[i].duration;
}

return durations;
Expand Down Expand Up @@ -367,38 +368,64 @@ export const seekable = function(playlist, expired, liveEdgePadding) {
* Determine the index and estimated starting time of the segment that
* contains a specified playback position in a media playlist.
*
* @param {Object} playlist the media playlist to query
* @param {number} currentTime The number of seconds since the earliest
* @param {Object} options.playlist the media playlist to query
* @param {number} options.currentTime The number of seconds since the earliest
* possible position to determine the containing segment for
* @param {number} startIndex
* @param {number} startTime
* @return {Object}
* @param {number} options.startTime the time when the segment/part starts
* @param {number} options.startingSegmentIndex the segment index to start looking at.
* @param {number?} [options.startingPartIndex] the part index to look at within the segment.
*
* @return {Object} an object with partIndex, segmentIndex, and startTime.
*/
export const getMediaInfoForTime = function(
export const getMediaInfoForTime = function({
playlist,
currentTime,
startIndex,
startingSegmentIndex,
startingPartIndex,
startTime
) {
}) {

const partsAndSegments = getPartsAndSegments(playlist);
let time = currentTime - startTime;
const partsAndSegments = getPartsAndSegments(playlist);

let startIndex = 0;

for (let i = 0; i < partsAndSegments.length; i++) {
const partAndSegment = partsAndSegments[i];

if (startingSegmentIndex !== partAndSegment.segmentIndex) {
continue;
}

// skip this if part index does not match.
if (typeof startingPartIndex === 'number' && typeof partAndSegment.partIndex === 'number' && startingPartIndex !== partAndSegment.partIndex) {
continue;
}

startIndex = i;
break;
}

if (time < 0) {
// Walk backward from startIndex in the playlist, adding durations
// until we find a segment that contains `time` and return it
if (startIndex > 0) {
for (let i = startIndex - 1; i >= 0; i--) {
const segment = partsAndSegments[i];
const partAndSegment = partsAndSegments[i];

time += segment.duration;
time += partAndSegment.duration;

// TODO: consider not using TIME_FUDGE_FACTOR at all here
if ((time + TIME_FUDGE_FACTOR) > 0) {
return {
mediaIndex: segment.segmentIndex,
startTime: startTime - sumDurations(playlist, startIndex, segment.segmentIndex),
partIndex: segment.partIndex
partIndex: partAndSegment.partIndex,
segmentIndex: partAndSegment.segmentIndex,
startTime: startTime - sumDurations({
defaultDuration: playlist.targetDuration,
durationList: partsAndSegments,
startIndex,
endIndex: i
})
};
}
}
Expand All @@ -407,8 +434,8 @@ export const getMediaInfoForTime = function(
// We were unable to find a good segment within the playlist
// so select the first segment
return {
mediaIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
startTime: currentTime
};
}
Expand All @@ -421,7 +448,8 @@ export const getMediaInfoForTime = function(
time -= playlist.targetDuration;
if (time < 0) {
return {
mediaIndex: partsAndSegments[0].segmentIndex,
partIndex: partsAndSegments[0] && partsAndSegments[0].partIndex || null,
segmentIndex: partsAndSegments[0] && partsAndSegments[0].segmentIndex || 0,
startTime: currentTime
};
}
Expand All @@ -432,23 +460,28 @@ export const getMediaInfoForTime = function(
// Walk forward from startIndex in the playlist, subtracting durations
// until we find a segment that contains `time` and return it
for (let i = startIndex; i < partsAndSegments.length; i++) {
const partSegment = partsAndSegments[i];
const partAndSegment = partsAndSegments[i];

time -= partSegment.duration;
time -= partAndSegment.duration;

// TODO: consider not using TIME_FUDGE_FACTOR at all here
if ((time - TIME_FUDGE_FACTOR) < 0) {
return {
mediaIndex: partSegment.segmentIndex,
startTime: startTime + sumDurations(playlist, startIndex, partSegment.segmentIndex),
partIndex: partSegment.partIndex
partIndex: partAndSegment.partIndex,
segmentIndex: partAndSegment.segmentIndex,
startTime: startTime + sumDurations({
defaultDuration: playlist.targetDuration,
durationList: partsAndSegments,
startIndex,
endIndex: i
})
};
}
}

// We are out of possible candidates so load the last one...
return {
mediaIndex: partsAndSegments[partsAndSegments.length - 1].segmentIndex,
segmentIndex: partsAndSegments[partsAndSegments.length - 1].segmentIndex,
partIndex: partsAndSegments[partsAndSegments.length - 1].partIndex,
startTime: currentTime
};
Expand Down
21 changes: 11 additions & 10 deletions src/segment-loader.js
Expand Up @@ -1400,17 +1400,18 @@ export default class SegmentLoader extends videojs.EventTarget {
}
} else {
// Find the segment containing the end of the buffer or current time.
const mediaSourceInfo = Playlist.getMediaInfoForTime(
this.playlist_,
this.fetchAtBuffer_ ? bufferedEnd : this.currentTime_(),
this.syncPoint_.segmentIndex,
this.syncPoint_.time
);
const {segmentIndex, startTime, partIndex} = Playlist.getMediaInfoForTime({
playlist: this.playlist_,
currentTime: this.fetchAtBuffer_ ? bufferedEnd : this.currentTime_(),
startingPartIndex: this.syncPoint_.partIndex,
startingSegmentIndex: this.syncPoint_.segmentIndex,
startTime: this.syncPoint_.time
});

next.getMediaInfoForTime = this.fetchAtBuffer_ ? 'bufferedEnd' : 'currentTime';
next.mediaIndex = mediaSourceInfo.mediaIndex;
next.startOfSegment = mediaSourceInfo.startTime;
next.partIndex = mediaSourceInfo.partIndex;
next.mediaIndex = segmentIndex;
next.startOfSegment = startTime;
next.partIndex = partIndex;
}

const nextSegment = segments[next.mediaIndex];
Expand Down Expand Up @@ -1479,7 +1480,7 @@ export default class SegmentLoader extends videojs.EventTarget {
// The timeline that the segment is in
timeline: segment.timeline,
// The expected duration of the segment in seconds
duration: segment.duration,
duration: part && part.duration || segment.duration,
// retain the segment in case the playlist updates while doing an async process
segment,
part,
Expand Down

0 comments on commit ee5841d

Please sign in to comment.