Skip to content

Commit 5e9b4f1

Browse files
authored
fix: use a separate ProgramDateTime mapping to player time per timeline (#1063)
Previously, the ProgramDateTime would map to time 0 at the start of playback, and that mapping would be used for the rest of playback. However, in the case of a discontinuity, ProgramDateTime can jump (as in, can be a larger difference in time than a single segment duration, e.g., if the encoder went down for a period of time). Because the ProgramDateTime to player time mapping never changed, this led to seeking errors. This fix uses a mapping of ProgramDateTime to player time per timeline, allowing those "jumps" in time on discontinuities.
1 parent 49249d5 commit 5e9b4f1

11 files changed

Lines changed: 426 additions & 33 deletions

docs/creating-content.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,25 @@ Copy only the first two video frames, leave out audio.
3333
$ ffmpeg -i index0.ts -vframes 2 -an -vcodec copy video.ts
3434
```
3535

36+
### videoOneSecond.ts
37+
38+
Blank video for 1 second, MMS-Small resolution, start at 0 PTS/DTS, 2 frames per second
39+
40+
```
41+
$ ffmpeg -f lavfi -i color=c=black:s=128x96:r=2:d=1 -muxdelay 0 -c:v libx264 videoOneSecond.ts
42+
```
43+
44+
### videoOneSecond1.ts through videoOneSecond4.ts
45+
46+
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).
47+
48+
```
49+
$ ffmpeg -i videoOneSecond.ts -muxdelay 0 -output_ts_offset 1 -vcodec copy videoOneSecond1.ts
50+
$ ffmpeg -i videoOneSecond.ts -muxdelay 0 -output_ts_offset 2 -vcodec copy videoOneSecond2.ts
51+
$ ffmpeg -i videoOneSecond.ts -muxdelay 0 -output_ts_offset 3 -vcodec copy videoOneSecond3.ts
52+
$ ffmpeg -i videoOneSecond.ts -muxdelay 0 -output_ts_offset 4 -vcodec copy videoOneSecond4.ts
53+
```
54+
3655
### audio.ts
3756

3857
Copy only the first two audio frames, leave out video.

src/segment-loader.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -855,9 +855,6 @@ export default class SegmentLoader extends videojs.EventTarget {
855855
return;
856856
}
857857

858-
// not sure if this is the best place for this
859-
this.syncController_.setDateTimeMapping(this.playlist_);
860-
861858
// if all the configuration is ready, initialize and begin loading
862859
if (this.state === 'INIT' && this.couldBeginLoading_()) {
863860
return this.init_();
@@ -916,6 +913,17 @@ export default class SegmentLoader extends videojs.EventTarget {
916913
mediaSequence: newPlaylist.mediaSequence,
917914
time: 0
918915
};
916+
// Setting the date time mapping means mapping the program date time (if available)
917+
// to time 0 on the player's timeline. The playlist's syncInfo serves a similar
918+
// purpose, mapping the initial mediaSequence to time zero. Since the syncInfo can
919+
// be updated as the playlist is refreshed before the loader starts loading, the
920+
// program date time mapping needs to be updated as well.
921+
//
922+
// This mapping is only done for the main loader because a program date time should
923+
// map equivalently between playlists.
924+
if (this.loaderType_ === 'main') {
925+
this.syncController_.setDateTimeMappingForStart(newPlaylist);
926+
}
919927
}
920928

921929
let oldId = null;

src/sync-controller.js

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const syncPointStrategies = [
2727
{
2828
name: 'ProgramDateTime',
2929
run: (syncController, playlist, duration, currentTimeline, currentTime) => {
30-
if (!syncController.datetimeToDisplayTime) {
30+
if (!Object.keys(syncController.timelineToDatetimeMappings).length) {
3131
return null;
3232
}
3333

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

4040
for (let i = 0; i < segments.length; i++) {
4141
const segment = segments[i];
42+
const datetimeMapping =
43+
syncController.timelineToDatetimeMappings[segment.timeline];
44+
45+
if (!datetimeMapping) {
46+
continue;
47+
}
4248

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

4854
// Once the distance begins to increase, or if distance is 0, we have passed
@@ -161,7 +167,7 @@ export default class SyncController extends videojs.EventTarget {
161167
// ...for synching across variants
162168
this.timelines = [];
163169
this.discontinuities = [];
164-
this.datetimeToDisplayTime = null;
170+
this.timelineToDatetimeMappings = {};
165171

166172
this.logger_ = logger('SyncController');
167173
}
@@ -351,19 +357,25 @@ export default class SyncController extends videojs.EventTarget {
351357
}
352358

353359
/**
354-
* Save the mapping from playlist's ProgramDateTime to display. This should
355-
* only ever happen once at the start of playback.
360+
* Save the mapping from playlist's ProgramDateTime to display. This should only happen
361+
* before segments start to load.
356362
*
357363
* @param {Playlist} playlist - The currently active playlist
358364
*/
359-
setDateTimeMapping(playlist) {
360-
if (!this.datetimeToDisplayTime &&
361-
playlist.segments &&
365+
setDateTimeMappingForStart(playlist) {
366+
// It's possible for the playlist to be updated before playback starts, meaning time
367+
// zero is not yet set. If, during these playlist refreshes, a discontinuity is
368+
// crossed, then the old time zero mapping (for the prior timeline) would be retained
369+
// unless the mappings are cleared.
370+
this.timelineToDatetimeMappings = {};
371+
372+
if (playlist.segments &&
362373
playlist.segments.length &&
363374
playlist.segments[0].dateTimeObject) {
364-
const playlistTimestamp = playlist.segments[0].dateTimeObject.getTime() / 1000;
375+
const firstSegment = playlist.segments[0];
376+
const playlistTimestamp = firstSegment.dateTimeObject.getTime() / 1000;
365377

366-
this.datetimeToDisplayTime = -playlistTimestamp;
378+
this.timelineToDatetimeMappings[firstSegment.timeline] = -playlistTimestamp;
367379
}
368380
}
369381

@@ -377,14 +389,15 @@ export default class SyncController extends videojs.EventTarget {
377389
* The current active request information
378390
* @param {boolean} options.shouldSaveTimelineMapping
379391
* If there's a timeline change, determines if the timeline mapping should be
380-
* saved in timelines.
392+
* saved for timeline mapping and program date time mappings.
381393
*/
382394
saveSegmentTimingInfo({ segmentInfo, shouldSaveTimelineMapping }) {
383395
const didCalculateSegmentTimeMapping = this.calculateSegmentTimeMapping_(
384396
segmentInfo,
385397
segmentInfo.timingInfo,
386398
shouldSaveTimelineMapping
387399
);
400+
const segment = segmentInfo.segment;
388401

389402
if (didCalculateSegmentTimeMapping) {
390403
this.saveDiscontinuitySyncInfo_(segmentInfo);
@@ -394,10 +407,16 @@ export default class SyncController extends videojs.EventTarget {
394407
if (!segmentInfo.playlist.syncInfo) {
395408
segmentInfo.playlist.syncInfo = {
396409
mediaSequence: segmentInfo.playlist.mediaSequence + segmentInfo.mediaIndex,
397-
time: segmentInfo.segment.start
410+
time: segment.start
398411
};
399412
}
400413
}
414+
415+
const dateTime = segment.dateTimeObject;
416+
417+
if (segment.discontinuity && shouldSaveTimelineMapping && dateTime) {
418+
this.timelineToDatetimeMappings[segment.timeline] = -(dateTime.getTime() / 1000);
419+
}
401420
}
402421

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

436-
if (segmentInfo.timestampOffset !== null) {
455+
if (typeof segmentInfo.timestampOffset === 'number') {
437456
mappingObj = {
438457
time: segmentInfo.startOfSegment,
439458
mapping: segmentInfo.startOfSegment - timingInfo.start

0 commit comments

Comments
 (0)