-
Notifications
You must be signed in to change notification settings - Fork 793
Add option to select lowest rendition available on startup #1212
Changes from 6 commits
ba8b1d1
01bc928
6a5bd11
382ad9a
06d4814
b4a89b2
a2ca2fa
8ccd7e2
da8b0c5
362c246
f1e3550
4f431fe
8afae79
024f74a
c51469a
c08c114
4139386
bb79b6e
39f8e44
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ import { translateLegacyCodecs } from 'videojs-contrib-media-sources/es5/codec-u | |
import worker from 'webworkify'; | ||
import Decrypter from './decrypter-worker'; | ||
import Config from './config'; | ||
import { parseCodecs } from './util/codecs.js'; | ||
|
||
let Hls; | ||
|
||
|
@@ -65,36 +66,6 @@ const objectChanged = function(a, b) { | |
return false; | ||
}; | ||
|
||
/** | ||
* Parses a codec string to retrieve the number of codecs specified, | ||
* the video codec and object type indicator, and the audio profile. | ||
* | ||
* @private | ||
*/ | ||
const parseCodecs = function(codecs) { | ||
let result = { | ||
codecCount: 0 | ||
}; | ||
let parsed; | ||
|
||
result.codecCount = codecs.split(',').length; | ||
result.codecCount = result.codecCount || 2; | ||
|
||
// parse the video codec | ||
parsed = (/(^|\s|,)+(avc1)([^ ,]*)/i).exec(codecs); | ||
if (parsed) { | ||
result.videoCodec = parsed[2]; | ||
result.videoObjectTypeIndicator = parsed[3]; | ||
} | ||
|
||
// parse the last field of the audio codec | ||
result.audioProfile = | ||
(/(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i).exec(codecs); | ||
result.audioProfile = result.audioProfile && result.audioProfile[2]; | ||
|
||
return result; | ||
}; | ||
|
||
/** | ||
* Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the | ||
* standard `avc1.<hhhhhh>`. | ||
|
@@ -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 commentThe reason will be displayed to describe this comment to others. Learn more. We'll want this added to the README eventually |
||
} = options; | ||
|
||
if (!url) { | ||
|
@@ -298,6 +270,7 @@ export class MasterPlaylistController extends videojs.EventTarget { | |
this.mode_ = mode; | ||
this.useCueTags_ = useCueTags; | ||
this.blacklistDuration = blacklistDuration; | ||
this.enableLowInitialPlaylist = enableLowInitialPlaylist; | ||
if (this.useCueTags_) { | ||
this.cueTagsTrack_ = this.tech_.addTextTrack('metadata', | ||
'ad-cues'); | ||
|
@@ -430,7 +403,9 @@ export class MasterPlaylistController extends videojs.EventTarget { | |
|
||
if (!updatedPlaylist) { | ||
// select the initial variant | ||
this.initialMedia_ = this.selectPlaylist(); | ||
this.initialMedia_ = this.enableLowInitialPlaylist ? | ||
this.selectInitialPlaylist() : this.selectPlaylist(); | ||
|
||
this.masterPlaylistLoader_.media(this.initialMedia_); | ||
return; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import Config from './config'; | ||
import Playlist from './playlist'; | ||
import { parseCodecs } from './util/codecs.js'; | ||
|
||
// Utilities | ||
|
||
|
@@ -356,3 +357,25 @@ export const minRebufferMaxBandwidthSelector = function(settings) { | |
|
||
return rebufferingEstimates[0] || null; | ||
}; | ||
|
||
/** | ||
* 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 commentThe reason will be displayed to describe this comment to others. Learn more. Can you add |
||
* @return {Object|null} | ||
* {Object} return.playlist | ||
* The lowest bitrate playlist that contains a video codec. If no such rendition | ||
* 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 commentThe reason will be displayed to describe this comment to others. Learn more. could replace this |
||
|
||
// Sort ascending by bitrate | ||
stableSort(playlists, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, I believe the latest commit should deal with that scenario. |
||
(a, b) => comparePlaylistBandwidth(a, b)); | ||
|
||
const playlistsWithVideo = | ||
playlists.filter(playlist => parseCodecs(playlist.attributes.CODECS).videoCodec); | ||
|
||
return playlistsWithVideo[0] || playlists[0]; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
|
||
/** | ||
* @file - codecs.js - Handles tasks regarding codec strings such as translating them to | ||
* codec strings, or translating codec strings into objects that can be examined. | ||
*/ | ||
|
||
/** | ||
* Parses a codec string to retrieve the number of codecs specified, | ||
* the video codec and object type indicator, and the audio profile. | ||
*/ | ||
|
||
export const parseCodecs = function(codecs = '') { | ||
let result = { | ||
codecCount: 0 | ||
}; | ||
let parsed; | ||
|
||
result.codecCount = codecs.split(',').length; | ||
result.codecCount = result.codecCount || 2; | ||
|
||
// parse the video codec | ||
parsed = (/(^|\s|,)+(avc1)([^ ,]*)/i).exec(codecs); | ||
if (parsed) { | ||
result.videoCodec = parsed[2]; | ||
result.videoObjectTypeIndicator = parsed[3]; | ||
} | ||
|
||
// parse the last field of the audio codec | ||
result.audioProfile = | ||
(/(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i).exec(codecs); | ||
result.audioProfile = result.audioProfile && result.audioProfile[2]; | ||
|
||
return result; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -212,14 +212,64 @@ QUnit.test('resets SegmentLoader when seeking in flash for both in and out of bu | |
|
||
}); | ||
|
||
QUnit.test('selects lowest bitrate rendition when enableLowInitialPlaylist is set', | ||
function(assert) { | ||
// Set requests.length to 0, otherwise it will use the requests generated in the | ||
// beforeEach function | ||
this.requests.length = 0; | ||
this.player = createPlayer({ html5: { hls: { enableLowInitialPlaylist: true } } }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since the initial selector is called within the |
||
|
||
this.player.src({ | ||
src: 'manifest/master.m3u8', | ||
type: 'application/vnd.apple.mpegurl' | ||
}); | ||
|
||
this.clock.tick(1); | ||
|
||
this.masterPlaylistController = this.player.tech_.hls.masterPlaylistController_; | ||
|
||
let numCallsToSelectInitialPlaylistCalls = 0; | ||
let numCallsToSelectPlaylist = 0; | ||
|
||
this.masterPlaylistController.selectPlaylist = () => { | ||
numCallsToSelectPlaylist++; | ||
return this.masterPlaylistController.master().playlists[0]; | ||
}; | ||
|
||
this.masterPlaylistController.selectInitialPlaylist = () => { | ||
numCallsToSelectInitialPlaylistCalls++; | ||
return this.masterPlaylistController.master().playlists[0]; | ||
}; | ||
|
||
this.masterPlaylistController.mediaSource.trigger('sourceopen'); | ||
// master | ||
this.standardXHRResponse(this.requests.shift()); | ||
// media | ||
this.standardXHRResponse(this.requests.shift()); | ||
|
||
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 commentThe reason will be displayed to describe this comment to others. Learn more. this comment is a bit outdated now |
||
// not selectPlaylist | ||
assert.equal(numCallsToSelectInitialPlaylistCalls, 1, 'selectInitialPlaylist'); | ||
assert.equal(numCallsToSelectPlaylist, 0, 'selectPlaylist'); | ||
|
||
// 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 commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't need this tick |
||
|
||
assert.equal(numCallsToSelectInitialPlaylistCalls, 1, 'selectInitialPlaylist'); | ||
assert.equal(numCallsToSelectPlaylist, 0, 'selectPlaylist'); | ||
}); | ||
|
||
QUnit.test('resyncs SegmentLoader for a fast quality change', function(assert) { | ||
let resyncs = 0; | ||
|
||
this.masterPlaylistController.mediaSource.trigger('sourceopen'); | ||
// master | ||
this.standardXHRResponse(this.requests.shift()); | ||
// media | ||
this.standardXHRResponse(this.requests.shift()); | ||
this.masterPlaylistController.mediaSource.trigger('sourceopen'); | ||
|
||
let segmentLoader = this.masterPlaylistController.mainSegmentLoader_; | ||
|
||
|
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.