Skip to content

Commit 1289dd4

Browse files
feat: useNetworkInfo API by default + exclude audio only renditions when we have video renditions alongside (#1565)
* chore: filter audio-only playlists when we have playlists with video-only or muxed * chore: set use network info api to true by default * chore: simplify filter logic * chore: use infinity exclude * chore: default to true only if unset * chore: fix tests * feat: use default as true for networkInformation api
1 parent 14ac65a commit 1289dd4

10 files changed

Lines changed: 83 additions & 52 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ This option defaults to `false`.
477477

478478
##### useNetworkInformationApi
479479
* Type: `boolean`,
480-
* Default: `false`
480+
* Default: `true`
481481
* Use [window.networkInformation.downlink](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlink) to estimate the network's bandwidth. Per mdn, _The value is never greater than 10 Mbps, as a non-standard anti-fingerprinting measure_. Given this, if bandwidth estimates from both the player and networkInfo are >= 10 Mbps, the player will use the larger of the two values as its bandwidth estimate.
482482

483483
##### useDtsForTimestampOffset

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@
143143
</div>
144144

145145
<div class="form-check">
146-
<input id=network-info type="checkbox" class="form-check-input">
146+
<input id=network-info type="checkbox" class="form-check-input" checked>
147147
<label class="form-check-label" for="network-info">Use networkInfo API for bandwidth estimations (reloads player)</label>
148148
</div>
149149

src/playlist-loader.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {getKnownPartCount} from './playlist.js';
2222
import {merge} from './util/vjs-compat';
2323
import DateRangesStorage from './util/date-ranges';
2424
import { getStreamingNetworkErrorMetadata } from './error-codes.js';
25+
import {getCodecs, unwrapCodecList} from './util/codecs';
2526

2627
const { EventTarget } = videojs;
2728

@@ -523,14 +524,27 @@ export default class PlaylistLoader extends EventTarget {
523524

524525
parseManifest_({url, manifestString}) {
525526
try {
526-
return parseManifest({
527+
const parsed = parseManifest({
527528
onwarn: ({message}) => this.logger_(`m3u8-parser warn for ${url}: ${message}`),
528529
oninfo: ({message}) => this.logger_(`m3u8-parser info for ${url}: ${message}`),
529530
manifestString,
530531
customTagParsers: this.customTagParsers,
531532
customTagMappers: this.customTagMappers,
532533
llhls: this.llhls
533534
});
535+
536+
/**
537+
* VHS does not support switching between variants with and without audio and video
538+
* so we want to filter out audio-only variants when variants with video and(or) audio are also detected.
539+
*/
540+
541+
if (!parsed.playlists || !parsed.playlists.length) {
542+
return parsed;
543+
}
544+
545+
this.excludeAudioOnlyVariants(parsed.playlists);
546+
547+
return parsed;
534548
} catch (error) {
535549
this.error = error;
536550
this.error.metadata = {
@@ -540,6 +554,33 @@ export default class PlaylistLoader extends EventTarget {
540554
}
541555
}
542556

557+
excludeAudioOnlyVariants(playlists) {
558+
// helper function
559+
const hasVideo = (playlist) => {
560+
const attributes = playlist.attributes || {};
561+
const { width, height } = attributes.RESOLUTION || {};
562+
563+
if (width && height) {
564+
return true;
565+
}
566+
567+
// parse codecs string from playlist attributes
568+
const codecsList = getCodecs(playlist) || [];
569+
// unwrap list
570+
const codecsInfo = unwrapCodecList(codecsList);
571+
572+
return Boolean(codecsInfo.video);
573+
};
574+
575+
if (playlists.some(hasVideo)) {
576+
playlists.forEach((playlist) => {
577+
if (!hasVideo(playlist)) {
578+
playlist.excludeUntil = Infinity;
579+
}
580+
});
581+
}
582+
}
583+
543584
/**
544585
* Update the playlist loader's state in response to a new or updated playlist.
545586
*

src/util/codecs.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const logFn = logger('CodecUtils');
1919
* @param {Playlist} media the current media playlist
2020
* @return {Object} an object with the video and audio codecs
2121
*/
22-
const getCodecs = function(media) {
22+
export const getCodecs = function(media) {
2323
// if the codecs were explicitly specified, use them instead of the
2424
// defaults
2525
const mediaAttributes = media.attributes || {};

src/videojs-http-streaming.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,8 @@ class VhsHandler extends Component {
696696
this.source_.useBandwidthFromLocalStorage :
697697
this.options_.useBandwidthFromLocalStorage || false;
698698
this.options_.useForcedSubtitles = this.options_.useForcedSubtitles || false;
699-
this.options_.useNetworkInformationApi = this.options_.useNetworkInformationApi || false;
699+
this.options_.useNetworkInformationApi = typeof this.options_.useNetworkInformationApi !== 'undefined' ?
700+
this.options_.useNetworkInformationApi : true;
700701
this.options_.useDtsForTimestampOffset = this.options_.useDtsForTimestampOffset || false;
701702
this.options_.customTagParsers = this.options_.customTagParsers || [];
702703
this.options_.customTagMappers = this.options_.customTagMappers || [];

test/manifests/multipleAudioGroupsCombinedMain.m3u8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-hi",LANGUAGE="fre",NAME="Français",AUTOSELECT=YES, DEFAULT=NO,URI="fre/prog_index.m3u8"
88
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-hi",LANGUAGE="sp",NAME="Espanol",AUTOSELECT=YES, DEFAULT=NO,URI="sp/prog_index.m3u8"
99

10-
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=195023,CODECS="mp4a.40.5", AUDIO="audio-lo"
10+
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=195023,CODECS="avc1.42e01e,mp4a.40.5", AUDIO="audio-lo"
1111
lo/prog_index.m3u8
1212
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=260000,CODECS="avc1.42e01e,mp4a.40.2", AUDIO="audio-lo"
1313
lo2/prog_index.m3u8

test/manifests/two-renditions.m3u8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
#EXTM3U
33
#EXT-X-STREAM-INF:BANDWIDTH=240000,RESOLUTION=396x224
44
media.m3u8
5-
#EXT-X-STREAM-INF:BANDWIDTH=40000
5+
#EXT-X-STREAM-INF:BANDWIDTH=40000,RESOLUTION=396x224
66
media1.m3u8

test/playlist-controller.test.js

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,11 +1478,7 @@ QUnit.test('excludes switching from video+audio playlists to audio only', functi
14781478
this.standardXHRResponse(this.requests.shift());
14791479

14801480
const pc = this.playlistController;
1481-
let debugLogs = [];
14821481

1483-
pc.logger_ = (...logs) => {
1484-
debugLogs = debugLogs.concat(logs);
1485-
};
14861482
// segment must be appended before the exclusion logic runs
14871483
return requestAndAppendSegment({
14881484
request: this.requests.shift(),
@@ -1498,11 +1494,6 @@ QUnit.test('excludes switching from video+audio playlists to audio only', functi
14981494
const audioPlaylist = pc.mainPlaylistLoader_.main.playlists[0];
14991495

15001496
assert.equal(audioPlaylist.excludeUntil, Infinity, 'excluded incompatible playlist');
1501-
assert.notEqual(
1502-
debugLogs.indexOf('excluding 0-media.m3u8: codec count "1" !== "2"'),
1503-
-1,
1504-
'debug logs about codec count'
1505-
);
15061497
});
15071498
});
15081499

@@ -1525,11 +1516,6 @@ QUnit.test('excludes switching from audio-only playlists to video+audio', functi
15251516
// media1
15261517
this.standardXHRResponse(this.requests.shift());
15271518

1528-
let debugLogs = [];
1529-
1530-
pc.logger_ = (...logs) => {
1531-
debugLogs = debugLogs.concat(logs);
1532-
};
15331519
// segment must be appended before the exclusion logic runs
15341520
return requestAndAppendSegment({
15351521
request: this.requests.shift(),
@@ -1540,23 +1526,17 @@ QUnit.test('excludes switching from audio-only playlists to video+audio', functi
15401526
}).then(() => {
15411527
assert.equal(
15421528
pc.mainPlaylistLoader_.media(),
1543-
pc.mainPlaylistLoader_.main.playlists[0],
1544-
'selected audio only'
1529+
pc.mainPlaylistLoader_.main.playlists[1],
1530+
'selected audio+video'
15451531
);
15461532

1547-
const videoAudioPlaylist = pc.mainPlaylistLoader_.main.playlists[1];
1533+
const audioOnly = pc.mainPlaylistLoader_.main.playlists[0];
15481534

15491535
assert.equal(
1550-
videoAudioPlaylist.excludeUntil,
1536+
audioOnly.excludeUntil,
15511537
Infinity,
15521538
'excluded incompatible playlist'
15531539
);
1554-
1555-
assert.notEqual(
1556-
debugLogs.indexOf('excluding 1-media1.m3u8: codec count "2" !== "1"'),
1557-
-1,
1558-
'debug logs about codec count'
1559-
);
15601540
});
15611541
});
15621542

@@ -1682,7 +1662,6 @@ QUnit.test('excludes switching between playlists with different codecs', functio
16821662
'excluding 1-media1.m3u8: video codec "hvc1" !== "avc1"',
16831663
'excluding 2-media2.m3u8: audio codec "ac-3" !== "mp4a"',
16841664
'excluding 3-media3.m3u8: video codec "hvc1" !== "avc1" && audio codec "ac-3" !== "mp4a"',
1685-
'excluding 5-media5.m3u8: codec count "1" !== "2" && audio codec "ac-3" !== "mp4a"',
16861665
'excluding 6-media6.m3u8: codec count "1" !== "2" && video codec "hvc1" !== "avc1"'
16871666
].forEach(function(message) {
16881667
assert.notEqual(

test/test-helpers.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,17 @@ export const createPlayer = function(options, src, clock) {
367367
}
368368
}
369369
document.querySelector('#qunit-fixture').appendChild(video);
370-
const player = videojs(video, options || {});
370+
371+
options = options || {};
372+
options.html5 = options.html5 || {};
373+
options.html5.vhs = options.html5.vhs || {};
374+
375+
// we should disable useNetworkInformationApi for tests, unless it is explicitly set to some value
376+
if (typeof options.html5.vhs.useNetworkInformationApi === 'undefined') {
377+
options.html5.vhs.useNetworkInformationApi = false;
378+
}
379+
380+
const player = videojs(video, options);
371381

372382
player.buffered = function() {
373383
return createTimeRanges(0, 0);

test/videojs-http-streaming.test.js

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,7 +1248,7 @@ QUnit.test('buffer checks are noops when only the main is ready', function(asser
12481248
assert.strictEqual(this.requests.length, 1, 'one request was made');
12491249
assert.strictEqual(
12501250
this.requests[0].url,
1251-
absoluteUrl('manifest/media1.m3u8'),
1251+
absoluteUrl('manifest/media.m3u8'),
12521252
'media playlist requested'
12531253
);
12541254

@@ -1269,16 +1269,16 @@ QUnit.test('selects a playlist below the current bandwidth', function(assert) {
12691269

12701270
// the default playlist has a really high bitrate
12711271
this.player.tech_.vhs.playlists.main.playlists[0].attributes.BANDWIDTH = 9e10;
1272-
// playlist 1 has a very low bitrate
1273-
this.player.tech_.vhs.playlists.main.playlists[1].attributes.BANDWIDTH = 1;
1272+
// playlist 2 has a very low bitrate
1273+
this.player.tech_.vhs.playlists.main.playlists[2].attributes.BANDWIDTH = 1;
12741274
// but the detected client bandwidth is really low
12751275
this.player.tech_.vhs.bandwidth = 10;
12761276

12771277
const playlist = this.player.tech_.vhs.selectPlaylist();
12781278

12791279
assert.strictEqual(
12801280
playlist,
1281-
this.player.tech_.vhs.playlists.main.playlists[1],
1281+
this.player.tech_.vhs.playlists.main.playlists[2],
12821282
'the low bitrate stream is selected'
12831283
);
12841284

@@ -1383,12 +1383,12 @@ QUnit.test('raises the minimum bitrate for a stream proportionially', function(a
13831383
this.player.tech_.vhs.bandwidth = 11;
13841384

13851385
// 9.9 * 1.1 < 11
1386-
this.player.tech_.vhs.playlists.main.playlists[1].attributes.BANDWIDTH = 9.9;
1386+
this.player.tech_.vhs.playlists.main.playlists[2].attributes.BANDWIDTH = 9.9;
13871387
const playlist = this.player.tech_.vhs.selectPlaylist();
13881388

13891389
assert.strictEqual(
13901390
playlist,
1391-
this.player.tech_.vhs.playlists.main.playlists[1],
1391+
this.player.tech_.vhs.playlists.main.playlists[2],
13921392
'a lower bitrate stream is selected'
13931393
);
13941394

@@ -1416,7 +1416,7 @@ QUnit.test('uses the lowest bitrate if no other is suitable', function(assert) {
14161416
// playlist 1 has the lowest advertised bitrate
14171417
assert.strictEqual(
14181418
playlist,
1419-
this.player.tech_.vhs.playlists.main.playlists[1],
1419+
this.player.tech_.vhs.playlists.main.playlists[0],
14201420
'the lowest bitrate stream is selected'
14211421
);
14221422

@@ -2892,7 +2892,7 @@ QUnit.test('resets the switching algorithm if a request times out', function(ass
28922892

28932893
assert.strictEqual(
28942894
this.player.tech_.vhs.playlists.media(),
2895-
this.player.tech_.vhs.playlists.main.playlists[1],
2895+
this.player.tech_.vhs.playlists.main.playlists[0],
28962896
'reset to the lowest bitrate playlist'
28972897
);
28982898

@@ -4693,7 +4693,7 @@ QUnit.test('populates quality levels list when available', function(assert) {
46934693
// media
46944694
this.standardXHRResponse(this.requests.shift());
46954695

4696-
assert.equal(addCount, 4, 'four levels added from main');
4696+
assert.equal(addCount, 3, 'three levels added from main');
46974697
assert.equal(changeCount, 1, 'selected initial quality level');
46984698

46994699
this.player.dispose();
@@ -5837,7 +5837,7 @@ QUnit.test('aborts all in-flight work when disposed', function(assert) {
58375837
const vhs = VhsSourceHandler.handleSource({
58385838
src: 'manifest/main.m3u8',
58395839
type: 'application/vnd.apple.mpegurl'
5840-
}, this.tech);
5840+
}, this.tech, { vhs: { useNetworkInformationApi: false } });
58415841

58425842
vhs.mediaSource.trigger('sourceopen');
58435843
// main
@@ -5859,7 +5859,7 @@ QUnit.test('stats are reset on dispose', function(assert) {
58595859
const vhs = VhsSourceHandler.handleSource({
58605860
src: 'manifest/main.m3u8',
58615861
type: 'application/vnd.apple.mpegurl'
5862-
}, this.tech);
5862+
}, this.tech, { vhs: { useNetworkInformationApi: false } });
58635863

58645864
vhs.mediaSource.trigger('sourceopen');
58655865
// main
@@ -5891,7 +5891,7 @@ QUnit.skip('detects fullscreen and triggers a fast quality change', function(ass
58915891
const vhs = VhsSourceHandler.handleSource({
58925892
src: 'manifest/main.m3u8',
58935893
type: 'application/vnd.apple.mpegurl'
5894-
}, this.tech);
5894+
}, this.tech, { vhs: { useNetworkInformationApi: false } });
58955895

58965896
let qualityChanges = 0;
58975897
let fullscreenElementName;
@@ -5933,7 +5933,7 @@ QUnit.test('downloads additional playlists if required', function(assert) {
59335933
const vhs = VhsSourceHandler.handleSource({
59345934
src: 'manifest/main.m3u8',
59355935
type: 'application/vnd.apple.mpegurl'
5936-
}, this.tech);
5936+
}, this.tech, { vhs: { useNetworkInformationApi: false } });
59375937

59385938
// Make segment metadata noop since most test segments dont have real data
59395939
vhs.playlistController_.mainSegmentLoader_.addSegmentMetadataCue_ = () => {};
@@ -5987,7 +5987,7 @@ QUnit.test('waits to download new segments until the media playlist is stable',
59875987
const vhs = VhsSourceHandler.handleSource({
59885988
src: 'manifest/main.m3u8',
59895989
type: 'application/vnd.apple.mpegurl'
5990-
}, this.tech);
5990+
}, this.tech, { vhs: { useNetworkInformationApi: false } });
59915991
const pc = vhs.playlistController_;
59925992

59935993
pc.mainSegmentLoader_.addSegmentMetadataCue_ = () => {};
@@ -6039,7 +6039,7 @@ QUnit.test('live playlist starts three target durations before live', function(a
60396039
const vhs = VhsSourceHandler.handleSource({
60406040
src: 'manifest/main.m3u8',
60416041
type: 'application/vnd.apple.mpegurl'
6042-
}, this.tech);
6042+
}, this.tech, { vhs: { useNetworkInformationApi: false } });
60436043

60446044
vhs.mediaSource.trigger('sourceopen');
60456045
this.requests.shift().respond(
@@ -6099,7 +6099,7 @@ QUnit.test(
60996099
let vhs = VhsSourceHandler.handleSource({
61006100
src: 'manifest/main.m3u8',
61016101
type: 'application/vnd.apple.mpegurl'
6102-
}, this.tech);
6102+
}, this.tech, { vhs: { useNetworkInformationApi: false } });
61036103

61046104
vhs.playlistController_.selectPlaylist();
61056105
assert.equal(defaultSelectPlaylistCount, 1, 'uses default playlist selector');
@@ -6116,7 +6116,7 @@ QUnit.test(
61166116
vhs = VhsSourceHandler.handleSource({
61176117
src: 'manifest/main.m3u8',
61186118
type: 'application/vnd.apple.mpegurl'
6119-
}, this.tech);
6119+
}, this.tech, { vhs: { useNetworkInformationApi: false } });
61206120

61216121
vhs.playlistController_.selectPlaylist();
61226122
assert.equal(defaultSelectPlaylistCount, 0, 'standard playlist selector not run');
@@ -6170,7 +6170,7 @@ QUnit.test('excludes playlist if key requests fail', function(assert) {
61706170
const vhs = VhsSourceHandler.handleSource({
61716171
src: 'manifest/encrypted-main.m3u8',
61726172
type: 'application/vnd.apple.mpegurl'
6173-
}, this.tech);
6173+
}, this.tech, { vhs: { useNetworkInformationApi: false } });
61746174

61756175
vhs.mediaSource.trigger('sourceopen');
61766176
this.requests.shift()
@@ -6219,7 +6219,7 @@ QUnit.test(
62196219
const vhs = VhsSourceHandler.handleSource({
62206220
src: 'manifest/encrypted-main.m3u8',
62216221
type: 'application/vnd.apple.mpegurl'
6222-
}, this.tech);
6222+
}, this.tech, { vhs: { useNetworkInformationApi: false } });
62236223

62246224
vhs.mediaSource.trigger('sourceopen');
62256225
this.requests.shift()

0 commit comments

Comments
 (0)