videojs-contrib-hls
Version:
Play back HLS with video.js, even where it's not natively supported.
210 lines (182 loc) • 6.86 kB
JavaScript
/**
* Playlist related utilities.
*/
(function(window, videojs) {
'use strict';
var Playlist = {
/**
* The number of segments that are unsafe to start playback at in
* a live stream. Changing this value can cause playback stalls.
* See HTTP Live Streaming, "Playing the Media Playlist File"
* https://tools.ietf.org/html/draft-pantos-http-live-streaming-18#section-6.3.3
*/
UNSAFE_LIVE_SEGMENTS: 3
};
var duration, intervalDuration, backwardDuration, forwardDuration, seekable;
backwardDuration = function(playlist, endSequence) {
var result = 0, segment, i;
i = endSequence - playlist.mediaSequence;
// if a start time is available for segment immediately following
// the interval, use it
segment = playlist.segments[i];
// Walk backward until we find the latest segment with timeline
// information that is earlier than endSequence
if (segment) {
if (segment.start !== undefined) {
return { result: segment.start, precise: true };
}
if (segment.end !== undefined) {
return {
result: segment.end - segment.duration,
precise: true
};
}
}
while (i--) {
segment = playlist.segments[i];
if (segment.end !== undefined) {
return { result: result + segment.end, precise: true };
}
result += segment.duration;
if (segment.start !== undefined) {
return { result: result + segment.start, precise: true };
}
}
return { result: result, precise: false };
};
forwardDuration = function(playlist, endSequence) {
var result = 0, segment, i;
i = endSequence - playlist.mediaSequence;
// Walk forward until we find the earliest segment with timeline
// information
for (; i < playlist.segments.length; i++) {
segment = playlist.segments[i];
if (segment.start !== undefined) {
return {
result: segment.start - result,
precise: true
};
}
result += segment.duration;
if (segment.end !== undefined) {
return {
result: segment.end - result,
precise: true
};
}
}
// indicate we didn't find a useful duration estimate
return { result: -1, precise: false };
};
/**
* Calculate the media duration from the segments associated with a
* playlist. The duration of a subinterval of the available segments
* may be calculated by specifying an end index.
*
* @param playlist {object} a media playlist object
* @param endSequence {number} (optional) an exclusive upper boundary
* for the playlist. Defaults to playlist length.
* @return {number} the duration between the first available segment
* and end index.
*/
intervalDuration = function(playlist, endSequence) {
var backward, forward;
if (endSequence === undefined) {
endSequence = playlist.mediaSequence + playlist.segments.length;
}
if (endSequence < playlist.mediaSequence) {
return 0;
}
// do a backward walk to estimate the duration
backward = backwardDuration(playlist, endSequence);
if (backward.precise) {
// if we were able to base our duration estimate on timing
// information provided directly from the Media Source, return
// it
return backward.result;
}
// walk forward to see if a precise duration estimate can be made
// that way
forward = forwardDuration(playlist, endSequence);
if (forward.precise) {
// we found a segment that has been buffered and so it's
// position is known precisely
return forward.result;
}
// return the less-precise, playlist-based duration estimate
return backward.result;
};
/**
* Calculates the duration of a playlist. If a start and end index
* are specified, the duration will be for the subset of the media
* timeline between those two indices. The total duration for live
* playlists is always Infinity.
* @param playlist {object} a media playlist object
* @param endSequence {number} (optional) an exclusive upper
* boundary for the playlist. Defaults to the playlist media
* sequence number plus its length.
* @param includeTrailingTime {boolean} (optional) if false, the
* interval between the final segment and the subsequent segment
* will not be included in the result
* @return {number} the duration between the start index and end
* index.
*/
duration = function(playlist, endSequence, includeTrailingTime) {
if (!playlist) {
return 0;
}
if (includeTrailingTime === undefined) {
includeTrailingTime = true;
}
// if a slice of the total duration is not requested, use
// playlist-level duration indicators when they're present
if (endSequence === undefined) {
// if present, use the duration specified in the playlist
if (playlist.totalDuration) {
return playlist.totalDuration;
}
// duration should be Infinity for live playlists
if (!playlist.endList) {
return window.Infinity;
}
}
// calculate the total duration based on the segment durations
return intervalDuration(playlist,
endSequence,
includeTrailingTime);
};
/**
* Calculates the interval of time that is currently seekable in a
* playlist. The returned time ranges are relative to the earliest
* moment in the specified playlist that is still available. A full
* seekable implementation for live streams would need to offset
* these values by the duration of content that has expired from the
* stream.
* @param playlist {object} a media playlist object
* @return {TimeRanges} the periods of time that are valid targets
* for seeking
*/
seekable = function(playlist) {
var start, end;
// without segments, there are no seekable ranges
if (!playlist.segments) {
return videojs.createTimeRange();
}
// when the playlist is complete, the entire duration is seekable
if (playlist.endList) {
return videojs.createTimeRange(0, duration(playlist));
}
// live playlists should not expose three segment durations worth
// of content from the end of the playlist
// https://tools.ietf.org/html/draft-pantos-http-live-streaming-16#section-6.3.3
start = intervalDuration(playlist, playlist.mediaSequence);
end = intervalDuration(playlist,
playlist.mediaSequence +
Math.max(0, playlist.segments.length - Playlist.UNSAFE_LIVE_SEGMENTS));
return videojs.createTimeRange(start, end);
};
// exports
Playlist.duration = duration;
Playlist.seekable = seekable;
videojs.Hls.Playlist = Playlist;
})(window, window.videojs);