-
Notifications
You must be signed in to change notification settings - Fork 793
Add option to select lowest rendition available on startup #1212
Conversation
@@ -66,36 +67,6 @@ const objectChanged = function(a, b) { | |||
}; | |||
|
|||
/** | |||
* Parses a codec string to retrieve the number of codecs specified, |
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 doesn't seem to be reliant on being here (there are a few other functions here that could be moved as well). I shifted this into a util module as I wanted to use this to check whether a playlist contained video or not in the playlist selection algorithm below.
src/playlist-selectors.js
Outdated
return playlistsWithVideo[0]; | ||
} | ||
|
||
return 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.
Made an assumption here that may or may not be true, where the playlist are sorted by bitrate. If that's not true I can add a sort in here.
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.
You'll have to sort, no guarantee of order
src/videojs-contrib-hls.js
Outdated
@@ -328,6 +330,10 @@ class HlsHandler extends Component { | |||
this.selectPlaylist ? | |||
this.selectPlaylist.bind(this) : Hls.STANDARD_PLAYLIST_SELECTOR.bind(this); | |||
|
|||
this.masterPlaylistController_.initialSelectPlaylist = | |||
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.
After a source change, this will set the initialSelectPlaylist
to be the standard selector and not the initial one. You should add another properties to the source handler for selectInitialPlaylist
and then this becomes this.selectInitialPlaylist ? this.selectInitialPlaylist.bind(this) : Hls.INITIAL_PLAYLIST_SELECTOR.bind(this)
.
side note, by my snippet you'll see i personally prefer selectInitialPlaylist
over initialSelectPlaylist
, but either is descriptive and fine, so feel free to change or keep the naming. @gesinger probably has his own preference
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.
Good catch, I reworked things a bit (I set the selection in the master-playlist-controller) and must have mucked that up.
src/playlist-selectors.js
Outdated
* exists pick the lowest audio rendition. | ||
*/ | ||
export const lowestBitrateCompatibleVariantSelector = function() { | ||
const { playlists } = this.playlists.master; |
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.
since sort messes with the array, you should use a copy of playslists with slice
src/playlist-selectors.js
Outdated
const playlistsWithVideo = | ||
playlists.filter(playlist => parseCodecs(playlist.attributes.CODECS).videoCodec); | ||
|
||
const selectedPlaylists = playlistsWithVideo.length !== 0 ? playlistsWithVideo : playlists; |
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.
you could omit this and just return playlistsWithVideo[0] || playlists[0];
@@ -212,6 +212,50 @@ QUnit.test('resets SegmentLoader when seeking in flash for both in and out of bu | |||
|
|||
}); | |||
|
|||
QUnit.only('selects lowest bitrate rendition when enableLowInitialPlaylist is set', | |||
function(assert) { | |||
this.player = createPlayer({ html5: { hls: { enableLowInitialPlaylist: 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.
since the initial selector is called within the loadedplaylist
event handler which fires everytime a live playlist is refreshed, I think it would be worth simulating a live refresh and confirming that the initial selector is not called a second time
let numCallsToSelectPlaylist = 0; | ||
|
||
// master | ||
this.standardXHRResponse(this.requests.shift()); |
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.
These two responses should go after the sourceopen
trigger
, also I would expect that the numCallsToSelectInitialPlaylistCalls
should increment after just the master response
src/master-playlist-controller.js
Outdated
this.enableLowInitialPlaylist ? | ||
this.selectInitialPlaylist : this.selectPlaylist; | ||
|
||
this.initialMedia_ = initialPlaylistSelector(); |
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.
You can do this without needing to create the initialPlaylistSelector
variable
|
||
this.clock.tick(1); | ||
|
||
// Trigger playlist event which should utilize selectInitialPlaylist and |
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 comment is a bit outdated now
|
||
// Simulate a live reload | ||
this.masterPlaylistController.masterPlaylistLoader_.trigger('loadedplaylist'); | ||
this.clock.tick(5); |
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.
shouldn't need this tick
/** | ||
* Chooses the appropriate media playlist, which in this case is the lowest bitrate | ||
* one with video. If no renditions with video exist, return the lowest audio rendition. | ||
* |
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.
Can you add Expects to be called within the context of an instance of HlsHandler
to this. see lastBandwidthSelector
jsdoc
Do you think it would be appropriate to move the other codec functions/constants within specifically
|
I do think so as I kind of started doing that then pulled back. I was trying to go with a softer touch on this PR (also it's unclear if this will end up getting folded into the greater project). I think doing that in a separate PR might make the most sense. |
@@ -283,7 +254,8 @@ export class MasterPlaylistController extends videojs.EventTarget { | |||
bandwidth, | |||
externHls, | |||
useCueTags, | |||
blacklistDuration | |||
blacklistDuration, | |||
enableLowInitialPlaylist |
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'll want this added to the README eventually
@@ -429,8 +402,17 @@ export class MasterPlaylistController extends videojs.EventTarget { | |||
let updatedPlaylist = this.masterPlaylistLoader_.media(); | |||
|
|||
if (!updatedPlaylist) { | |||
// select the initial variant | |||
this.initialMedia_ = this.selectPlaylist(); | |||
let selectedMedia; |
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.
Just a note here.
One of the tests was breaking on this (https://github.com/videojs/videojs-contrib-hls/blob/fast-start-time/test/videojs-contrib-hls.test.js#L1221) (I didn't have the temp variable, and instead set this.initialMedia_ twice, once in each if block).
Once I set things to the temporary variable the problem resolved itself. I'm assuming there are side effects off of setting this.initialMedia_... I didn't really dig deep enough to understand what was going on. It seems a little dangerous to have side effects off of setting a variable (when the setter isn't explicitly a function) if this is the case.
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 should probably dig into why this was happening since I don't think it should cause the test to break
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.
Looked into this a bit more, its because if you set this.initialMedia_
and then try and load that media, but the request fails e.g. 404, when on the final rendition, we get back to here, and updatedPlaylist
is still undefined
because we haven't successfully loaded one yet, but initialMedia_
has been set from the previous time here. I think it would make sense to set initialMedia_
in the loadedmetadata
handler if it hasnt been set yet as the first time we get into loadedmetadata
is after the first media playlist has been loaded.
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.
That loop seems like a code smell to me, though I can't think of a better way to deal with it in the current context.
I believe I handled the other scenario in your comment below with the latest commit. I'll look at this one as well.
@@ -1786,7 +1787,7 @@ QUnit.test('uses mobile default bandwidth if browser is Android', function(asser | |||
openMediaSource(this.player, this.clock); | |||
|
|||
assert.equal(this.player.tech_.hls.bandwidth, | |||
500000, | |||
4194304, |
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 discussed that we were going to use the desktop bandwidth number everywhere, so this test had to be updated to account for that (I figured I would update over removing).
src/playlist-selectors.js
Outdated
@@ -375,5 +375,5 @@ export const lowestBitrateCompatibleVariantSelector = function() { | |||
const playlistsWithVideo = | |||
playlists.filter(playlist => parseCodecs(playlist.attributes.CODECS).videoCodec); | |||
|
|||
return playlistsWithVideo[0] || playlists[0]; | |||
return playlistsWithVideo[0] || 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.
instead of returning null, you could call the last bandwidth selector with the current context
return playlistsWithVideo[0] || lasBandwidthSelector.call(this);
I think this would also remove the need to for the temporary selectedMedia
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 possible I'd like to avoid coupling this function to a separate algorithm, and allow the consumer to decide the alternative. I think long term adding the complexity here over elsewhere will be a bit confusing, as well as making unit testing and changes harder/more complex.
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.
makes sense 👍
src/playlist-selectors.js
Outdated
const playlists = this.playlists.master.playlists.slice(); | ||
|
||
// Sort ascending by bitrate | ||
stableSort(playlists, |
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.
Based on the findings from the 404 unit test failing (discussion in other comment thread), this selector should probably filter by isEnabled
similar to the simpleSelector
since this may try and re-select a playlist that just 404'd
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.
Good catch, I believe the latest commit should deal with that scenario.
src/playlist-selectors.js
Outdated
|
||
// filter out any playlists that have been excluded due to | ||
// incompatible configurations or playback errors | ||
const enabledPlaylists = playlists.filter( |
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 could be one line const enabledPlaylists = playlists.filter(Playlist.isEnabled);
src/videojs-contrib-hls.js
Outdated
// then this takes precedence over the enableLowInitialPlaylist option | ||
this.options_.enableLowInitialPlaylist = | ||
(this.options_.enableLowInitialPlaylist && | ||
this.options_.bandwidth === INITIAL_BANDWIDTH) || 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.
the || false
is not necessary a && b
will just be the value of b
when a == true
test/playlist-selectors.test.js
Outdated
'Selected lowest compatible playlist with video assets'); | ||
}); | ||
|
||
test('lowestBitrateCompatibleVariantSelector picks lowest audio rendition if no video exists', |
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.
The description is no longer accurate (now returns null) Same goes for the assertion description below
src/playlist-selectors.js
Outdated
* exists pick the lowest audio rendition. | ||
*/ | ||
export const lowestBitrateCompatibleVariantSelector = function() { | ||
const playlists = this.playlists.master.playlists.slice(); |
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.
could replace this slice
call with the enabled filter, then you wouldn't need to do it later or have the local const
Description
I'm still working through unit tests because many of them broke, but would like eyes on this sooner rather than later in case I went down a bizarre path.
This PR will select the lowest bitrate playlist with video on startup. If no such playlist exists, it picks the lowest bitrate playlist (assuming everything else is audo only).
This feature is gated behind an options flag. In order to enable it set options to:
Requirements Checklist