Skip to content

Commit

Permalink
fix: issues with live playback timing (#1553)
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-barstow authored Nov 18, 2024
1 parent 9f1c4ad commit 6b4b7e2
Show file tree
Hide file tree
Showing 6 changed files with 307 additions and 143 deletions.
140 changes: 81 additions & 59 deletions src/playlist-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -1642,9 +1642,73 @@ export class PlaylistController extends videojs.EventTarget {
return this.seekable_;
}

onSyncInfoUpdate_() {
let audioSeekable;
getSeekableRange_(playlistLoader, mediaType) {
const media = playlistLoader.media();

if (!media) {
return null;
}

const mediaSequenceSync = this.syncController_.getMediaSequenceSync(mediaType);

if (mediaSequenceSync && mediaSequenceSync.isReliable) {
const start = mediaSequenceSync.start;
const end = mediaSequenceSync.end;

if (!isFinite(start) || !isFinite(end)) {
return null;
}

const liveEdgeDelay = Vhs.Playlist.liveEdgeDelay(this.mainPlaylistLoader_.main, media);

// Make sure our seekable end is not negative
const calculatedEnd = Math.max(0, end - liveEdgeDelay);

if (calculatedEnd < start) {
return null;
}

return createTimeRanges([[start, calculatedEnd]]);
}

const expired = this.syncController_.getExpiredTime(media, this.duration());

if (expired === null) {
return null;
}

const seekable = Vhs.Playlist.seekable(
media,
expired,
Vhs.Playlist.liveEdgeDelay(this.mainPlaylistLoader_.main, media)
);

return seekable.length ? seekable : null;
}

computeFinalSeekable_(mainSeekable, audioSeekable) {
if (!audioSeekable) {
return mainSeekable;
}

const mainStart = mainSeekable.start(0);
const mainEnd = mainSeekable.end(0);
const audioStart = audioSeekable.start(0);
const audioEnd = audioSeekable.end(0);

if (audioStart > mainEnd || mainStart > audioEnd) {
// Seekables are far apart, rely on main
return mainSeekable;
}

// Return the overlapping seekable range
return createTimeRanges([[
Math.max(mainStart, audioStart),
Math.min(mainEnd, audioEnd)
]]);
}

onSyncInfoUpdate_() {
// TODO check for creation of both source buffers before updating seekable
//
// A fix was made to this function where a check for
Expand All @@ -1668,87 +1732,45 @@ export class PlaylistController extends videojs.EventTarget {
return;
}

let media = this.mainPlaylistLoader_.media();

if (!media) {
return;
}

let expired = this.syncController_.getExpiredTime(media, this.duration());
const mainSeekable = this.getSeekableRange_(this.mainPlaylistLoader_, 'main');

if (expired === null) {
// not enough information to update seekable
if (!mainSeekable) {
return;
}

const main = this.mainPlaylistLoader_.main;
const mainSeekable = Vhs.Playlist.seekable(
media,
expired,
Vhs.Playlist.liveEdgeDelay(main, media)
);

if (mainSeekable.length === 0) {
return;
}
let audioSeekable;

if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
expired = this.syncController_.getExpiredTime(media, this.duration());

if (expired === null) {
return;
}
audioSeekable = this.getSeekableRange_(this.mediaTypes_.AUDIO.activePlaylistLoader, 'audio');

audioSeekable = Vhs.Playlist.seekable(
media,
expired,
Vhs.Playlist.liveEdgeDelay(main, media)
);

if (audioSeekable.length === 0) {
if (!audioSeekable) {
return;
}
}

let oldEnd;
let oldStart;
const oldSeekable = this.seekable_;

if (this.seekable_ && this.seekable_.length) {
oldEnd = this.seekable_.end(0);
oldStart = this.seekable_.start(0);
}
this.seekable_ = this.computeFinalSeekable_(mainSeekable, audioSeekable);

if (!audioSeekable) {
// seekable has been calculated based on buffering video data so it
// can be returned directly
this.seekable_ = mainSeekable;
} else if (audioSeekable.start(0) > mainSeekable.end(0) ||
mainSeekable.start(0) > audioSeekable.end(0)) {
// seekables are pretty far off, rely on main
this.seekable_ = mainSeekable;
} else {
this.seekable_ = createTimeRanges([[
(audioSeekable.start(0) > mainSeekable.start(0)) ? audioSeekable.start(0) :
mainSeekable.start(0),
(audioSeekable.end(0) < mainSeekable.end(0)) ? audioSeekable.end(0) :
mainSeekable.end(0)
]]);
if (!this.seekable_) {
return;
}

// seekable is the same as last time
if (this.seekable_ && this.seekable_.length) {
if (this.seekable_.end(0) === oldEnd && this.seekable_.start(0) === oldStart) {
if (oldSeekable && oldSeekable.length && this.seekable_.length) {
if (oldSeekable.start(0) === this.seekable_.start(0) &&
oldSeekable.end(0) === this.seekable_.end(0)) {
// Seekable range hasn't changed
return;
}
}

this.logger_(`seekable updated [${Ranges.printableRange(this.seekable_)}]`);

const metadata = {
seekableRanges: this.seekable_
};

this.trigger({type: 'seekablerangeschanged', metadata});
this.trigger({ type: 'seekablerangeschanged', metadata });
this.tech_.trigger('seekablechanged');
}

Expand Down
1 change: 1 addition & 0 deletions src/segment-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,7 @@ export default class SegmentLoader extends videojs.EventTarget {
if (!newPlaylist) {
return;
}

const oldPlaylist = this.playlist_;
const segmentInfo = this.pendingSegment_;

Expand Down
25 changes: 21 additions & 4 deletions src/util/media-sequence-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class MediaSequenceSync {
return this.updateStorage_(
segments,
mediaSequence,
this.calculateBaseTime_(mediaSequence, currentTime)
this.calculateBaseTime_(mediaSequence, segments, currentTime)
);
}

Expand Down Expand Up @@ -228,7 +228,7 @@ export class MediaSequenceSync {
this.diagnostics_ = newDiagnostics;
}

calculateBaseTime_(mediaSequence, fallback) {
calculateBaseTime_(mediaSequence, segments, fallback) {
if (!this.storage_.size) {
// Initial setup flow.
return 0;
Expand All @@ -239,6 +239,23 @@ export class MediaSequenceSync {
return this.storage_.get(mediaSequence).segmentSyncInfo.start;
}

const minMediaSequenceFromStorage = Math.min(...this.storage_.keys());

// This case captures a race condition that can occur if we switch to a new media playlist that is out of date
// and still has an older Media Sequence. If this occurs, we extrapolate backwards to get the base time.
if (mediaSequence < minMediaSequenceFromStorage) {
const mediaSequenceDiff = minMediaSequenceFromStorage - mediaSequence;
let baseTime = this.storage_.get(minMediaSequenceFromStorage).segmentSyncInfo.start;

for (let i = 0; i < mediaSequenceDiff; i++) {
const segment = segments[i];

baseTime -= segment.duration;
}

return baseTime;
}

// Fallback flow.
// There is a gap between last recorded playlist and a new one received.
return fallback;
Expand All @@ -256,7 +273,7 @@ export class DependantMediaSequenceSync extends MediaSequenceSync {
this.parent_ = parent;
}

calculateBaseTime_(mediaSequence, fallback) {
calculateBaseTime_(mediaSequence, segments, fallback) {
if (!this.storage_.size) {
const info = this.parent_.getSyncInfoForMediaSequence(mediaSequence);

Expand All @@ -267,6 +284,6 @@ export class DependantMediaSequenceSync extends MediaSequenceSync {
return 0;
}

return super.calculateBaseTime_(mediaSequence, fallback);
return super.calculateBaseTime_(mediaSequence, segments, fallback);
}
}
Loading

0 comments on commit 6b4b7e2

Please sign in to comment.