Skip to content

Commit

Permalink
Rename convertToStreamTime and seekToStreamTime as convertToProgramTime
Browse files Browse the repository at this point in the history
and seekToProgramTime

Program time more accurately reflects the times being converted, as
EXT-X-PROGRAM-DATE-TIME is used for HLS, and stream time can be
ambiguous as to whether it references a real world time or the
timestamps present in the stream.
  • Loading branch information
gesinger committed Feb 1, 2019
1 parent 64f87a7 commit 97c3a9a
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 132 deletions.
2 changes: 1 addition & 1 deletion docs/creating-content.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Commands for creating tests streams

### Streams with EXT-X-PROGRAM-DATE-TIME for testing seekToStreamTime and convertToStreamTime
### Streams with EXT-X-PROGRAM-DATE-TIME for testing seekToProgramTime and convertToProgramTime

lavfi and testsrc are provided for creating a test stream in ffmpeg
-g 300 sets the GOP size to 300 (keyframe interval, at 30fps, one keyframe every 10 seconds)
Expand Down
2 changes: 1 addition & 1 deletion docs/program-time-from-player-time.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ NOTE: All times referenced in seconds unless otherwise specified.

In order to convert from a *player time* to a *stream time*, an "anchor point" is required to match up a *player time*, *stream time*, and *program time*.

Two anchor points that are usable are the time since the start of a new timeline (e.g., the time since the last discontinuity or start of the stream), and the start of a segment. Because, in our requirements for this conversion, each segment is tagged with its *program time* in the form of an [EXT-X-PROGRAM-DATE-TIME tag](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.6), using the segment start as the anchor point is the easiest solution. It's the closest potential anchor point to the time to convert, and it doesn't require us to track time changes across segments.
Two anchor points that are usable are the time since the start of a new timeline (e.g., the time since the last discontinuity or start of the stream), and the start of a segment. Because, in our requirements for this conversion, each segment is tagged with its *program time* in the form of an [EXT-X-PROGRAM-DATE-TIME tag](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.6), using the segment start as the anchor point is the easiest solution. It's the closest potential anchor point to the time to convert, and it doesn't require us to track time changes across segments (e.g., trimmed or prepended content).

Those time changes are the result of the transmuxer, which can add/remove content in order to keep the content playable (without gaps or other breaking changes between segments), particularly when a segment doesn't start with a key frame.

Expand Down
71 changes: 36 additions & 35 deletions src/util/time.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,18 @@ export const originalSegmentVideoDuration = (videoTimingInfo) => {
* Finds a segment that contains the time requested given as an ISO-8601 string. The
* returned segment might be an estimate or an accurate match.
*
* @param {String} streamTime The ISO-8601 streamTime to find a match for
* @param {String} programTime The ISO-8601 programTime to find a match for
* @param {Object} playlist A playlist object to search within
*/
export const findSegmentForStreamTime = (streamTime, playlist) => {
export const findSegmentForProgramTime = (programTime, playlist) => {
// Assumptions:
// - verifyProgramDateTimeTags has already been run
// - live streams have been started

let dateTimeObject;

try {
dateTimeObject = new Date(streamTime);
dateTimeObject = new Date(programTime);
} catch (e) {
return null;
}
Expand Down Expand Up @@ -191,29 +191,30 @@ export const findSegmentForPlayerTime = (time, playlist) => {
};

/**
* Gives the offset of the comparisonTimestamp from the streamTime timestamp in seconds.
* If the offset returned is positive, the streamTime occurs before the comparisonTimestamp.
* If the offset is negative, the streamTime occurs before the comparisonTimestamp.
* Gives the offset of the comparisonTimestamp from the programTime timestamp in seconds.
* If the offset returned is positive, the programTime occurs after the
* comparisonTimestamp.
* If the offset is negative, the programTime occurs before the comparisonTimestamp.
*
* @param {String} comparisonTimeStamp An ISO-8601 timestamp to compare against
* @param {String} streamTime The streamTime as an ISO-8601 string
* @param {String} programTime The programTime as an ISO-8601 string
* @return {Number} offset
*/
export const getOffsetFromTimestamp = (comparisonTimeStamp, streamTime) => {
export const getOffsetFromTimestamp = (comparisonTimeStamp, programTime) => {
let segmentDateTime;
let streamDateTime;
let programDateTime;

try {
segmentDateTime = new Date(comparisonTimeStamp);
streamDateTime = new Date(streamTime);
programDateTime = new Date(programTime);
} catch (e) {
// TODO handle error
}

const segmentTimeEpoch = segmentDateTime.getTime();
const streamTimeEpoch = streamDateTime.getTime();
const programTimeEpoch = programDateTime.getTime();

return (streamTimeEpoch - segmentTimeEpoch) / 1000;
return (programTimeEpoch - segmentTimeEpoch) / 1000;
};

/**
Expand All @@ -238,48 +239,48 @@ export const verifyProgramDateTimeTags = (playlist) => {
};

/**
* Returns the streamTime of the media given a playlist and a playerTime.
* Returns the programTime of the media given a playlist and a playerTime.
* The playlist must have programDateTime tags for a programDateTime tag to be returned.
* If the segments containing the time requested have not been buffered yet, an estimate
* may be returned to the callback.
*
* @param {Object} args
* @param {Object} args.playlist A playlist object to search within
* @param {Number} time A playerTime in seconds
* @param {Function} callback(err, streamTime)
* @param {Function} callback(err, programTime)
* @returns {String} err.message A detailed error message
* @returns {Object} streamTime
* @returns {Number} streamTime.mediaSeconds The streamTime in seconds
* @returns {String} streamTime.programDateTime The streamTime as an ISO-8601 String
* @returns {Object} programTime
* @returns {Number} programTime.mediaSeconds The streamTime in seconds
* @returns {String} programTime.programDateTime The programTime as an ISO-8601 String
*/
export const getStreamTime = ({
export const getProgramTime = ({
playlist,
time = undefined,
callback
}) => {

if (!callback) {
throw new Error('getStreamTime: callback must be provided');
throw new Error('getProgramTime: callback must be provided');
}

if (!playlist || time === undefined) {
return callback({
message: 'getStreamTime: playlist and time must be provided'
message: 'getProgramTime: playlist and time must be provided'
});
}

const matchedSegment = findSegmentForPlayerTime(time, playlist);

if (!matchedSegment) {
return callback({
message: 'valid streamTime was not found'
message: 'valid programTime was not found'
});
}

if (matchedSegment.type === 'estimate') {
return callback({
message:
'Accurate streamTime could not be determined.' +
'Accurate programTime could not be determined.' +
' Please seek to e.seekTime and try again',
seekTime: matchedSegment.estimatedStart
});
Expand All @@ -298,10 +299,10 @@ export const getStreamTime = ({
};

/**
* Seeks in the player to a time that matches the given streamTime ISO-8601 string.
* Seeks in the player to a time that matches the given programTime ISO-8601 string.
*
* @param {Object} args
* @param {String} args.streamTime A streamTime to seek to as an ISO-8601 String
* @param {String} args.programTime A programTime to seek to as an ISO-8601 String
* @param {Object} args.playlist A playlist to look within
* @param {Number} args.retryCount The number of times to try for an accurate seek. Default is 2.
* @param {Function} args.seekTo A method to perform a seek
Expand All @@ -311,8 +312,8 @@ export const getStreamTime = ({
* @returns {String} err.message A detailed error message
* @returns {Number} newTime The exact time that was seeked to in seconds
*/
export const seekToStreamTime = ({
streamTime,
export const seekToProgramTime = ({
programTime,
playlist,
retryCount = 2,
seekTo,
Expand All @@ -322,12 +323,12 @@ export const seekToStreamTime = ({
}) => {

if (!callback) {
throw new Error('seekToStreamTime: callback must be provided');
throw new Error('seekToProgramTime: callback must be provided');
}

if (typeof streamTime === 'undefined' || !playlist || !seekTo) {
if (typeof programTime === 'undefined' || !playlist || !seekTo) {
return callback({
message: 'seekToStreamTime: streamTime, seekTo and playlist must be provided'
message: 'seekToProgramTime: programTime, seekTo and playlist must be provided'
});
}

Expand All @@ -343,34 +344,34 @@ export const seekToStreamTime = ({
});
}

const matchedSegment = findSegmentForStreamTime(streamTime, playlist);
const matchedSegment = findSegmentForProgramTime(programTime, playlist);

// no match
if (!matchedSegment) {
return callback({
message: `${streamTime} was not found in the stream`
message: `${programTime} was not found in the stream`
});
}

const segment = matchedSegment.segment;
const mediaOffset = getOffsetFromTimestamp(
segment.dateTimeObject,
streamTime
programTime
);

if (matchedSegment.type === 'estimate') {
// we've run out of retries
if (retryCount === 0) {
return callback({
message: `${streamTime} is not buffered yet. Try again`
message: `${programTime} is not buffered yet. Try again`
});
}

seekTo(matchedSegment.estimatedStart + mediaOffset);

tech.one('seeked', () => {
seekToStreamTime({
streamTime,
seekToProgramTime({
programTime,
playlist,
retryCount: retryCount - 1,
seekTo,
Expand Down
14 changes: 7 additions & 7 deletions src/videojs-http-streaming.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import xhrFactory from './xhr';
import { Decrypter, AsyncStream, decrypt } from 'aes-decrypter';
import * as utils from './bin-utils';
import {
getStreamTime,
seekToStreamTime
getProgramTime,
seekToProgramTime
} from './util/time';
import { timeRangesToArray } from './ranges';
import { MediaSource, URL } from './mse/index';
Expand Down Expand Up @@ -753,18 +753,18 @@ class HlsHandler extends Component {
super.dispose();
}

convertToStreamTime(time, callback) {
return getStreamTime({
convertToProgramTime(time, callback) {
return getProgramTime({
playlist: this.masterPlaylistController_.media(),
time,
callback
});
}

// the player must be playing before calling this
seekToStreamTime(streamTime, callback, pauseAfterSeek = true, retryCount = 2) {
return seekToStreamTime({
streamTime,
seekToProgramTime(programTime, callback, pauseAfterSeek = true, retryCount = 2) {
return seekToProgramTime({
programTime,
playlist: this.masterPlaylistController_.media(),
retryCount,
pauseAfterSeek,
Expand Down
Loading

0 comments on commit 97c3a9a

Please sign in to comment.