Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use a separate ProgramDateTime mapping to player time per timeline #1063

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/creating-content.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,25 @@ Copy only the first two video frames, leave out audio.
$ ffmpeg -i index0.ts -vframes 2 -an -vcodec copy video.ts
```

### videoOneSecond.ts

Blank video for 1 second, MMS-Small resolution, start at 0 PTS/DTS, 2 frames per second

```
$ ffmpeg -f lavfi -i color=c=black:s=128x96:r=2:d=1 -muxdelay 0 -c:v libx264 videoOneSecond.ts
```

### videoOneSecond1.ts through videoOneSecond4.ts

Same as videoOneSecond.ts, but follows timing in sequence, with videoOneSecond.ts acting as the 0 index. Each segment starts at the second that its index indicates (e.g., videoOneSecond2.ts has a start time of 2 seconds).

```
$ ffmpeg -i videoOneSecond.ts -muxdelay 0 -output_ts_offset 1 -vcodec copy videoOneSecond1.ts
$ ffmpeg -i videoOneSecond.ts -muxdelay 0 -output_ts_offset 2 -vcodec copy videoOneSecond2.ts
$ ffmpeg -i videoOneSecond.ts -muxdelay 0 -output_ts_offset 3 -vcodec copy videoOneSecond3.ts
$ ffmpeg -i videoOneSecond.ts -muxdelay 0 -output_ts_offset 4 -vcodec copy videoOneSecond4.ts
```

### audio.ts

Copy only the first two audio frames, leave out video.
Expand Down
14 changes: 11 additions & 3 deletions src/segment-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -855,9 +855,6 @@ export default class SegmentLoader extends videojs.EventTarget {
return;
}

// not sure if this is the best place for this
this.syncController_.setDateTimeMapping(this.playlist_);

// if all the configuration is ready, initialize and begin loading
if (this.state === 'INIT' && this.couldBeginLoading_()) {
return this.init_();
Expand Down Expand Up @@ -916,6 +913,17 @@ export default class SegmentLoader extends videojs.EventTarget {
mediaSequence: newPlaylist.mediaSequence,
time: 0
};
// Setting the date time mapping means mapping the program date time (if available)
// to time 0 on the player's timeline. The playlist's syncInfo serves a similar
// purpose, mapping the initial mediaSequence to time zero. Since the syncInfo can
// be updated as the playlist is refreshed before the loader starts loading, the
// program date time mapping needs to be updated as well.
//
// This mapping is only done for the main loader because a program date time should
// map equivalently between playlists.
if (this.loaderType_ === 'main') {
this.syncController_.setDateTimeMappingForStart(newPlaylist);
}
}

let oldId = null;
Expand Down
45 changes: 32 additions & 13 deletions src/sync-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const syncPointStrategies = [
{
name: 'ProgramDateTime',
run: (syncController, playlist, duration, currentTimeline, currentTime) => {
if (!syncController.datetimeToDisplayTime) {
if (!Object.keys(syncController.timelineToDatetimeMappings).length) {
return null;
}

Expand All @@ -39,10 +39,16 @@ export const syncPointStrategies = [

for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
const datetimeMapping =
syncController.timelineToDatetimeMappings[segment.timeline];

if (!datetimeMapping) {
continue;
}

if (segment.dateTimeObject) {
const segmentTime = segment.dateTimeObject.getTime() / 1000;
const segmentStart = segmentTime + syncController.datetimeToDisplayTime;
const segmentStart = segmentTime + datetimeMapping;
const distance = Math.abs(currentTime - segmentStart);

// Once the distance begins to increase, or if distance is 0, we have passed
Expand Down Expand Up @@ -161,7 +167,7 @@ export default class SyncController extends videojs.EventTarget {
// ...for synching across variants
this.timelines = [];
this.discontinuities = [];
this.datetimeToDisplayTime = null;
this.timelineToDatetimeMappings = {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if segment.timeline is a number, should this just be an array?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the timelines contiguous or would there be a danger of a sparse array? Just thinking that maybe it'll be easier to have it be an array since you could just .length it, but if a segment.timeline could have values like, 0, 1, 100, then it won't work since the length would then be 101.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly: originally had it as an array, but it can be sparse so changed it.

Copy link
Contributor Author

@gesinger gesinger Feb 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's mainly an issue when either updating a playlist before starting, updating while paused, or if on some refreshes you miss a timeline (a short one).


this.logger_ = logger('SyncController');
}
Expand Down Expand Up @@ -351,19 +357,25 @@ export default class SyncController extends videojs.EventTarget {
}

/**
* Save the mapping from playlist's ProgramDateTime to display. This should
* only ever happen once at the start of playback.
* Save the mapping from playlist's ProgramDateTime to display. This should only happen
* before segments start to load.
*
* @param {Playlist} playlist - The currently active playlist
*/
setDateTimeMapping(playlist) {
if (!this.datetimeToDisplayTime &&
playlist.segments &&
setDateTimeMappingForStart(playlist) {
// It's possible for the playlist to be updated before playback starts, meaning time
// zero is not yet set. If, during these playlist refreshes, a discontinuity is
// crossed, then the old time zero mapping (for the prior timeline) would be retained
// unless the mappings are cleared.
this.timelineToDatetimeMappings = {};

if (playlist.segments &&
playlist.segments.length &&
playlist.segments[0].dateTimeObject) {
const playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000;
const firstSegment = playlist.segments[0];
const playlistTimestamp = firstSegment.dateTimeObject.getTime() / 1000;

this.datetimeToDisplayTime = -playlistTimestamp;
this.timelineToDatetimeMappings[firstSegment.timeline] = -playlistTimestamp;
}
}

Expand All @@ -377,14 +389,15 @@ export default class SyncController extends videojs.EventTarget {
* The current active request information
* @param {boolean} options.shouldSaveTimelineMapping
* If there's a timeline change, determines if the timeline mapping should be
* saved in timelines.
* saved for timeline mapping and program date time mappings.
*/
saveSegmentTimingInfo({ segmentInfo, shouldSaveTimelineMapping }) {
const didCalculateSegmentTimeMapping = this.calculateSegmentTimeMapping_(
segmentInfo,
segmentInfo.timingInfo,
shouldSaveTimelineMapping
);
const segment = segmentInfo.segment;

if (didCalculateSegmentTimeMapping) {
this.saveDiscontinuitySyncInfo_(segmentInfo);
Expand All @@ -394,10 +407,16 @@ export default class SyncController extends videojs.EventTarget {
if (!segmentInfo.playlist.syncInfo) {
segmentInfo.playlist.syncInfo = {
mediaSequence: segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex,
time: segmentInfo.segment.start
time: segment.start
};
}
}

const dateTime = segment.dateTimeObject;

if (segment.discontinuity && shouldSaveTimelineMapping && dateTime) {
this.timelineToDatetimeMappings[segment.timeline] = -(dateTime.getTime() / 1000);
}
}

timestampOffsetForTimeline(timeline) {
Expand Down Expand Up @@ -433,7 +452,7 @@ export default class SyncController extends videojs.EventTarget {
const segment = segmentInfo.segment;
let mappingObj = this.timelines[segmentInfo.timeline];

if (segmentInfo.timestampOffset !== null) {
if (typeof segmentInfo.timestampOffset === 'number') {
mappingObj = {
time: segmentInfo.startOfSegment,
mapping: segmentInfo.startOfSegment - timingInfo.start
Expand Down
Loading