-
Notifications
You must be signed in to change notification settings - Fork 425
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
Remove buffered data on fast quality switches #113
Remove buffered data on fast quality switches #113
Conversation
This would also fix videojs/video.js#5044, I believe. |
Did we want to put this behind a feature flag (either enabled or disabled by default) so that users can keep the smooth manual switch if they want? |
@forbesjo Hm I'll into that. Interestingly, Firefox handles the switch smoothly |
Pushed my changes. I believe the solution for the stutter is to seek in place after clearing the buffer. Chrome seems to cache a few frames from the previous rendition, and seeking in place gives the browser the "kick" it needs to clear them. |
src/master-playlist-controller.js
Outdated
* @private | ||
*/ | ||
fastQualityChange_() { | ||
let media = this.selectPlaylist(); |
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.
minor: this can be const
src/master-playlist-controller.js
Outdated
fastQualityChange_() { | ||
let media = this.selectPlaylist(); | ||
|
||
if (media !== this.masterPlaylistLoader_.media()) { |
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.
To save some indentation, we can change the condition to just return if (media === this.masterPlaylistLoader_.media())
src/master-playlist-controller.js
Outdated
|
||
// Wait for the buffer to finish clearing, then seek in place to give the | ||
// browser a kick to remove any cached frames from the previous rendition | ||
sourceBuffer.one('updateend', function() { |
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.
Might be better to use an arrow function here (can also remove the one time hls
const above and can just use this.hls_.ignoreNextSeekingEvent_ = true
below, and not have to bind setCurrentTime
)
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.
Depending on line length, also may not need to create the const for sourceBuffer
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.
Same goes for the currentTime-- I think it can just be this.tech_.setCurrentTime(this.tech_.currentTime());
src/master-playlist-controller.js
Outdated
|
||
// Wait for the buffer to finish clearing, then seek in place to give the | ||
// browser a kick to remove any cached frames from the previous rendition | ||
sourceBuffer.one('updateend', function() { |
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 it possible that this updateend
will occur for a different action (e.g., an append)? Is it OK for it to run then?
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.
@gesinger I suppose if we are in the middle of an append operation when the quality switch is initiated it might be possible? When we get the updateend
we could check the segment loader state to make sure it isn't 'APPENDING'
. Thoughts?
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.
We might run into a timing issue if we are listening on the updateend
but segment loader is also listening on updateend
, as segment-loader can transition to another state before this function runs (it may not be the case right now, but to determine that MPC would need to have internal logic of the segment-loader state).
Optimally, we could avoid using the wrapped source buffer directly. It might make sense for segment-loader's resetEverything to take a callback for when it's finished. Then resetEverything can pass the callback along to source updater's remove function, and the remove function can execute it on updateend
instead of a noop (as an optional parameter).
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.
@gesinger Makes sense. Went ahead and made those changes so let me know how they look.
|
||
let segmentLoader = this.masterPlaylistController.mainSegmentLoader_; | ||
|
||
segmentLoader.resyncLoader = function() { |
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.
minor: but for this (and below) we can save some lines using arrows, e.g., () => resyncs++
|
||
assert.deepEqual(removeFuncArgs, {start: 0, end: 60}, 'remove() called with correct arguments'); | ||
|
||
// verify stats |
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 kinda hung on in too many tests, but I think it's OK to remove here.
}); | ||
|
||
this.masterPlaylistController.selectPlaylist = () => { | ||
return this.masterPlaylistController.master().playlists[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.
We may also want a test for not resetting if the media remains the same.
src/master-playlist-controller.js
Outdated
this.mainSegmentLoader_.resetEverything(() => { | ||
// we want SegmentLoader.load() to be called only once the new playlist | ||
// has finished loading. This prevents it from being called too soon. | ||
this.hls_.ignoreNextSeekingEvent_ = true; |
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.
With the merge of #161 ignoreNextSeekingEvent_
was removed, and a new method of seeking was introduced. It's possible seekTo
may be used directly and ignoreNextSeekingEvent_
removed.
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.
@gesinger Hm just calling seekTo
with the flag removed is problematic since the logic in MasterPlaylistController.setCurrentTime()
results in SegmentLoader.load()
getting called before the new playlist has finished loading, so we append another segment from the previous rendition before appending the new quality segments. Perhaps adding an another return
in MPC.setCurrentTime()
if the PlaylistLoader state is 'SWITCHING_MEDIA'
would be a possible solution to this? Off the top of my head I'm not sure what other consequences that could have though.
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.
If we can avoid looking at the PlaylistLoader state, that would be best. One thing that could be done here is use the normal this.tech_.setCurrentTime
call directly, if we want to avoid the MasterPlaylistController.setCurrentTime()
call, since seekTo
is meant to add exactly that to the setCurrentTime
calls. If we do use this.tech_.setCurrentTime
directly though, it would be good to add a comment about why we can't go through the normal seekTo
method (in this case to avoid resetting the loader, since it's a seek meant to kick things instead of actually performing a seek with buffer clearing).
src/segment-loader.js
Outdated
@@ -582,10 +584,12 @@ export default class SegmentLoader extends videojs.EventTarget { | |||
* Remove any data in the source buffer between start and end times | |||
* @param {Number} start - the start time of the region to remove from the buffer | |||
* @param {Number} end - the end time of the region to remove from the buffer | |||
* @param {Function} done - an optional callback to be executed when the remove |
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.
For JSDoc optionals we can use [done]
src/source-updater.js
Outdated
@@ -131,14 +131,16 @@ export default class SourceUpdater { | |||
* | |||
* @param {Number} start where to start the removal | |||
* @param {Number} end where to end the removal | |||
* @param {Function} done optional callback to be executed when the remove |
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.
For JSDoc optionals we can use [done]
, or even [done=noop]
here
|
||
this.masterPlaylistController.fastQualityChange_(); | ||
|
||
assert.equal(segmentLoader.ended_, false, 'segment loader ended property is false'); |
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.
Was there a specific reason for the ended check?
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 included it since ended_
gets set to false
as a side effect of calling resetEverything()
. If you think it's superfluous I can remove it.
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.
Probably don't need it here. While it's a decent test, since these tests are meant for MasterPlaylistController we can probably leave checking that state change within the SegmentLoader tests.
a23cf57
to
59c7c3c
Compare
Had to rebase to get the netlify deploy to succeed. @gesinger Ready for another look. |
Congrats on merging your first pull request! 🎉🎉🎉 |
Description
This adds logic to remove all buffered data when a new playlist is enabled via
Representation.enabled
, such as with manual quality selection. Currently, the rendition switch following the enabling of a new playlist is slow due to the new segments being appended to the end of the buffer. This change comes following discussion with @gesinger, @forbesjo, and @mjneil. There was also some talk about modifying this conditional to checkfetchAtBuffer_
to reduce delay before fetching new segments at the playhead. Let me know if you would like me to add that to this PR.Specific Changes proposed
fastQualityChange_
function tosmoothQualityChange_
fastQualityChange_
which callsresetEverything()
instead of justresetLoader()
fastQualityChange_
method to reflect the new function namefastQualityChange_
method