Skip to content

Commit 6b4b7e2

Browse files
authored
fix: issues with live playback timing (#1553)
1 parent 9f1c4ad commit 6b4b7e2

File tree

6 files changed

+307
-143
lines changed

6 files changed

+307
-143
lines changed

src/playlist-controller.js

Lines changed: 81 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1642,9 +1642,73 @@ export class PlaylistController extends videojs.EventTarget {
16421642
return this.seekable_;
16431643
}
16441644

1645-
onSyncInfoUpdate_() {
1646-
let audioSeekable;
1645+
getSeekableRange_(playlistLoader, mediaType) {
1646+
const media = playlistLoader.media();
1647+
1648+
if (!media) {
1649+
return null;
1650+
}
1651+
1652+
const mediaSequenceSync = this.syncController_.getMediaSequenceSync(mediaType);
1653+
1654+
if (mediaSequenceSync && mediaSequenceSync.isReliable) {
1655+
const start = mediaSequenceSync.start;
1656+
const end = mediaSequenceSync.end;
1657+
1658+
if (!isFinite(start) || !isFinite(end)) {
1659+
return null;
1660+
}
1661+
1662+
const liveEdgeDelay = Vhs.Playlist.liveEdgeDelay(this.mainPlaylistLoader_.main, media);
1663+
1664+
// Make sure our seekable end is not negative
1665+
const calculatedEnd = Math.max(0, end - liveEdgeDelay);
1666+
1667+
if (calculatedEnd < start) {
1668+
return null;
1669+
}
1670+
1671+
return createTimeRanges([[start, calculatedEnd]]);
1672+
}
1673+
1674+
const expired = this.syncController_.getExpiredTime(media, this.duration());
1675+
1676+
if (expired === null) {
1677+
return null;
1678+
}
1679+
1680+
const seekable = Vhs.Playlist.seekable(
1681+
media,
1682+
expired,
1683+
Vhs.Playlist.liveEdgeDelay(this.mainPlaylistLoader_.main, media)
1684+
);
1685+
1686+
return seekable.length ? seekable : null;
1687+
}
1688+
1689+
computeFinalSeekable_(mainSeekable, audioSeekable) {
1690+
if (!audioSeekable) {
1691+
return mainSeekable;
1692+
}
1693+
1694+
const mainStart = mainSeekable.start(0);
1695+
const mainEnd = mainSeekable.end(0);
1696+
const audioStart = audioSeekable.start(0);
1697+
const audioEnd = audioSeekable.end(0);
16471698

1699+
if (audioStart > mainEnd || mainStart > audioEnd) {
1700+
// Seekables are far apart, rely on main
1701+
return mainSeekable;
1702+
}
1703+
1704+
// Return the overlapping seekable range
1705+
return createTimeRanges([[
1706+
Math.max(mainStart, audioStart),
1707+
Math.min(mainEnd, audioEnd)
1708+
]]);
1709+
}
1710+
1711+
onSyncInfoUpdate_() {
16481712
// TODO check for creation of both source buffers before updating seekable
16491713
//
16501714
// A fix was made to this function where a check for
@@ -1668,87 +1732,45 @@ export class PlaylistController extends videojs.EventTarget {
16681732
return;
16691733
}
16701734

1671-
let media = this.mainPlaylistLoader_.media();
1672-
1673-
if (!media) {
1674-
return;
1675-
}
1676-
1677-
let expired = this.syncController_.getExpiredTime(media, this.duration());
1735+
const mainSeekable = this.getSeekableRange_(this.mainPlaylistLoader_, 'main');
16781736

1679-
if (expired === null) {
1680-
// not enough information to update seekable
1737+
if (!mainSeekable) {
16811738
return;
16821739
}
16831740

1684-
const main = this.mainPlaylistLoader_.main;
1685-
const mainSeekable = Vhs.Playlist.seekable(
1686-
media,
1687-
expired,
1688-
Vhs.Playlist.liveEdgeDelay(main, media)
1689-
);
1690-
1691-
if (mainSeekable.length === 0) {
1692-
return;
1693-
}
1741+
let audioSeekable;
16941742

16951743
if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
1696-
media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
1697-
expired = this.syncController_.getExpiredTime(media, this.duration());
1698-
1699-
if (expired === null) {
1700-
return;
1701-
}
1744+
audioSeekable = this.getSeekableRange_(this.mediaTypes_.AUDIO.activePlaylistLoader, 'audio');
17021745

1703-
audioSeekable = Vhs.Playlist.seekable(
1704-
media,
1705-
expired,
1706-
Vhs.Playlist.liveEdgeDelay(main, media)
1707-
);
1708-
1709-
if (audioSeekable.length === 0) {
1746+
if (!audioSeekable) {
17101747
return;
17111748
}
17121749
}
17131750

1714-
let oldEnd;
1715-
let oldStart;
1751+
const oldSeekable = this.seekable_;
17161752

1717-
if (this.seekable_ && this.seekable_.length) {
1718-
oldEnd = this.seekable_.end(0);
1719-
oldStart = this.seekable_.start(0);
1720-
}
1753+
this.seekable_ = this.computeFinalSeekable_(mainSeekable, audioSeekable);
17211754

1722-
if (!audioSeekable) {
1723-
// seekable has been calculated based on buffering video data so it
1724-
// can be returned directly
1725-
this.seekable_ = mainSeekable;
1726-
} else if (audioSeekable.start(0) > mainSeekable.end(0) ||
1727-
mainSeekable.start(0) > audioSeekable.end(0)) {
1728-
// seekables are pretty far off, rely on main
1729-
this.seekable_ = mainSeekable;
1730-
} else {
1731-
this.seekable_ = createTimeRanges([[
1732-
(audioSeekable.start(0) > mainSeekable.start(0)) ? audioSeekable.start(0) :
1733-
mainSeekable.start(0),
1734-
(audioSeekable.end(0) < mainSeekable.end(0)) ? audioSeekable.end(0) :
1735-
mainSeekable.end(0)
1736-
]]);
1755+
if (!this.seekable_) {
1756+
return;
17371757
}
17381758

1739-
// seekable is the same as last time
1740-
if (this.seekable_ && this.seekable_.length) {
1741-
if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) {
1759+
if (oldSeekable && oldSeekable.length && this.seekable_.length) {
1760+
if (oldSeekable.start(0) === this.seekable_.start(0) &&
1761+
oldSeekable.end(0) === this.seekable_.end(0)) {
1762+
// Seekable range hasn't changed
17421763
return;
17431764
}
17441765
}
17451766

17461767
this.logger_(`seekable updated [${Ranges.printableRange(this.seekable_)}]`);
1768+
17471769
const metadata = {
17481770
seekableRanges: this.seekable_
17491771
};
17501772

1751-
this.trigger({type: 'seekablerangeschanged', metadata});
1773+
this.trigger({ type: 'seekablerangeschanged', metadata });
17521774
this.tech_.trigger('seekablechanged');
17531775
}
17541776

src/segment-loader.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,7 @@ export default class SegmentLoader extends videojs.EventTarget {
11171117
if (!newPlaylist) {
11181118
return;
11191119
}
1120+
11201121
const oldPlaylist = this.playlist_;
11211122
const segmentInfo = this.pendingSegment_;
11221123

src/util/media-sequence-sync.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export class MediaSequenceSync {
132132
return this.updateStorage_(
133133
segments,
134134
mediaSequence,
135-
this.calculateBaseTime_(mediaSequence, currentTime)
135+
this.calculateBaseTime_(mediaSequence, segments, currentTime)
136136
);
137137
}
138138

@@ -228,7 +228,7 @@ export class MediaSequenceSync {
228228
this.diagnostics_ = newDiagnostics;
229229
}
230230

231-
calculateBaseTime_(mediaSequence, fallback) {
231+
calculateBaseTime_(mediaSequence, segments, fallback) {
232232
if (!this.storage_.size) {
233233
// Initial setup flow.
234234
return 0;
@@ -239,6 +239,23 @@ export class MediaSequenceSync {
239239
return this.storage_.get(mediaSequence).segmentSyncInfo.start;
240240
}
241241

242+
const minMediaSequenceFromStorage = Math.min(...this.storage_.keys());
243+
244+
// This case captures a race condition that can occur if we switch to a new media playlist that is out of date
245+
// and still has an older Media Sequence. If this occurs, we extrapolate backwards to get the base time.
246+
if (mediaSequence < minMediaSequenceFromStorage) {
247+
const mediaSequenceDiff = minMediaSequenceFromStorage - mediaSequence;
248+
let baseTime = this.storage_.get(minMediaSequenceFromStorage).segmentSyncInfo.start;
249+
250+
for (let i = 0; i < mediaSequenceDiff; i++) {
251+
const segment = segments[i];
252+
253+
baseTime -= segment.duration;
254+
}
255+
256+
return baseTime;
257+
}
258+
242259
// Fallback flow.
243260
// There is a gap between last recorded playlist and a new one received.
244261
return fallback;
@@ -256,7 +273,7 @@ export class DependantMediaSequenceSync extends MediaSequenceSync {
256273
this.parent_ = parent;
257274
}
258275

259-
calculateBaseTime_(mediaSequence, fallback) {
276+
calculateBaseTime_(mediaSequence, segments, fallback) {
260277
if (!this.storage_.size) {
261278
const info = this.parent_.getSyncInfoForMediaSequence(mediaSequence);
262279

@@ -267,6 +284,6 @@ export class DependantMediaSequenceSync extends MediaSequenceSync {
267284
return 0;
268285
}
269286

270-
return super.calculateBaseTime_(mediaSequence, fallback);
287+
return super.calculateBaseTime_(mediaSequence, segments, fallback);
271288
}
272289
}

0 commit comments

Comments
 (0)