-
Notifications
You must be signed in to change notification settings - Fork 428
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: playback watcher should fix seeking into a gap. #1192
Conversation
Codecov Report
@@ Coverage Diff @@
## main #1192 +/- ##
==========================================
+ Coverage 86.55% 86.59% +0.03%
==========================================
Files 39 39
Lines 9619 9645 +26
Branches 2225 2233 +8
==========================================
+ Hits 8326 8352 +26
Misses 1293 1293
Continue to review full report at Codecov.
|
@@ -253,7 +267,7 @@ export default class PlaybackWatcher { | |||
* @private | |||
*/ | |||
checkCurrentTime_() { | |||
if (this.tech_.seeking() && this.fixesBadSeeks_()) { | |||
if (this.fixesBadSeeks_()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixesBadSeeks_
already returns false if seeking
is false
on tech
so checking seeking
is redundant.
@@ -426,7 +445,7 @@ export default class PlaybackWatcher { | |||
const seekable = this.seekable(); | |||
const currentTime = this.tech_.currentTime(); | |||
|
|||
if (this.tech_.seeking() && this.fixesBadSeeks_()) { | |||
if (this.fixesBadSeeks_()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixesBadSeeks_
already returns false if seeking
is false
on tech
so checking seeking
is redundant.
@@ -340,7 +340,8 @@ export const updateMaster = (master, newMedia, unchangedCheck = isPlaylistUnchan | |||
* The time in ms to wait before refreshing the live playlist | |||
*/ | |||
export const refreshDelay = (media, update) => { | |||
const lastSegment = media.segments[media.segments.length - 1]; | |||
const segments = media.segments || []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixes an issue I was seeing in our multi period source where sometimes media does not have segments. I can make this a different pull request, but it was easy enough.
@@ -445,3 +445,32 @@ export const lastBufferedEnd = function(a) { | |||
|
|||
return a.end(a.length - 1); | |||
}; | |||
|
|||
export const timeAheadOf = function(range, startTime) { | |||
let time = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function allows us to keep the previous logic where we made sure that two segment appends had happened while the player is seeking
. The problem with the previous logic is that it did not consider all of the buffer after currentTime. This code adds up all of the buffer after currentTime gaps and all.
targetDuration: this.media().targetDuration, | ||
currentTime | ||
}) | ||
) { | ||
seekTo = buffered.start(0) + Ranges.SAFE_TIME_DELTA; | ||
this.logger_(`Buffered region starts (${buffered.start(0)}) ` + | ||
const nextRange = Ranges.findNextRange(buffered, currentTime); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Example values for why we need this:
buffered = [[0, 10], [21, 40]]
currentTime = 20
Previously we would just take buffered.start(0)
which is in this case is before currentTime. Now we find the TimeRange
that is after currentTime
and use that.
} | ||
export const closeToBufferedContent = ({ buffered, audioBuffered, videoBuffered, targetDuration, currentTime }) => { | ||
const twoSegmentDurations = (targetDuration - Ranges.TIME_FUDGE_FACTOR) * 2; | ||
const bufferedToCheck = [audioBuffered, videoBuffered]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure of a better way to fix this. Sometimes we will have one or the other and sometimes we will have both. We need to make sure that both buffers have two segment durations appended ahead of current time before we do anything. We do this because fixesBadSeeks
on a setTimeout
whenever tech.seeking()
is true. Ideally we would go back to what I had using events and never using a timer, but that would require too much testing.
src/playback-watcher.js
Outdated
if (currentTime > buffered.start(0)) { | ||
// default to using buffered, but if we don't have one | ||
// use video or audio buffered | ||
buffered = buffered && buffered.length ? buffered : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw a case were we had no bufferIntersection but we did have a video/audio buffer. Seeking to the start of the next videoBuffer fixed it for us.
return false; | ||
} | ||
|
||
// Since target duration generally represents the max (or close to max) duration of a | ||
// segment, if the buffer is within a segment of the current time, the gap probably | ||
// won't be closed, and current time should be considered close to buffered content. | ||
return buffered.start(0) - currentTime < targetDuration; | ||
return nextRange.start(0) - currentTime < (targetDuration + Ranges.TIME_FUDGE_FACTOR); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added time fudge factor here because segment durations are never exact.
@@ -1006,6 +1006,12 @@ QUnit.test('jumps to buffered content if seeking just before', function(assert) | |||
currentTime: () => currentTime, | |||
buffered: () => buffered | |||
}; | |||
|
|||
Object.assign(playbackWatcher.masterPlaylistController_.sourceUpdater_, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
had to update this test to pass videoBuffered, other than that all tests continue to work.
src/ranges.js
Outdated
export const timeAheadOf = function(range, startTime) { | ||
let time = 0; | ||
|
||
if (!range || !range.length || !range.end) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is !range.end
necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure we do it in other functions in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it was a mistake? I think end
is a method.
Co-authored-by: Garrett Singer <[email protected]>
Co-authored-by: Gary Katsevman <[email protected]>
Co-authored-by: Gary Katsevman <[email protected]>
src/ranges.js
Outdated
export const timeAheadOf = function(range, startTime) { | ||
let time = 0; | ||
|
||
if (!range || !range.length || !range.end) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it was a mistake? I think end
is a method.
Co-authored-by: Garrett Singer <[email protected]>
Description
playback watcher should seek us out of a gap that was seeked into.