diff --git a/README.md b/README.md
index 27f28ceb4..ef4c480eb 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,7 @@ Video.js Compatibility: 6.0, 7.0
- [cacheEncryptionKeys](#cacheencryptionkeys)
- [handlePartialData](#handlepartialdata)
- [liveRangeSafeTimeDelta](#liverangesafetimedelta)
+ - [useNetworkInformationApi](#usenetworkinformationapi)
- [captionServices](#captionservices)
- [Format](#format)
- [Example](#example)
@@ -473,6 +474,11 @@ This option defaults to `false`.
* Default: [`SAFE_TIME_DELTA`](https://github.com/videojs/http-streaming/blob/e7cb63af010779108336eddb5c8fd138d6390e95/src/ranges.js#L17)
* Allow to re-define length (in seconds) of time delta when you compare current time and the end of the buffered range.
+##### useNetworkInformationApi
+* Type: `boolean`,
+* Default: `false`
+* 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.
+
##### captionServices
* Type: `object`
* Default: undefined
diff --git a/index.html b/index.html
index ef2d08342..77ccf83fd 100644
--- a/index.html
+++ b/index.html
@@ -146,6 +146,11 @@
+
+
+
+
+
diff --git a/scripts/index.js b/scripts/index.js
index 27c314d3f..4a6d2c35e 100644
--- a/scripts/index.js
+++ b/scripts/index.js
@@ -447,6 +447,7 @@
'buffer-water',
'exact-manifest-timings',
'pixel-diff-selector',
+ 'network-info',
'override-native',
'preload',
'mirror-source'
@@ -499,6 +500,7 @@
'override-native',
'liveui',
'pixel-diff-selector',
+ 'network-info',
'exact-manifest-timings'
].forEach(function(name) {
stateEls[name].addEventListener('change', function(event) {
@@ -565,7 +567,8 @@
experimentalBufferBasedABR: getInputValue(stateEls['buffer-water']),
experimentalLLHLS: getInputValue(stateEls.llhls),
experimentalExactManifestTimings: getInputValue(stateEls['exact-manifest-timings']),
- experimentalLeastPixelDiffSelector: getInputValue(stateEls['pixel-diff-selector'])
+ experimentalLeastPixelDiffSelector: getInputValue(stateEls['pixel-diff-selector']),
+ useNetworkInformationApi: getInputValue(stateEls['network-info'])
}
}
});
diff --git a/src/videojs-http-streaming.js b/src/videojs-http-streaming.js
index d5e78284d..813ed4230 100644
--- a/src/videojs-http-streaming.js
+++ b/src/videojs-http-streaming.js
@@ -630,6 +630,7 @@ class VhsHandler extends Component {
typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ?
this.source_.useBandwidthFromLocalStorage :
this.options_.useBandwidthFromLocalStorage || false;
+ this.options_.useNetworkInformationApi = this.options_.useNetworkInformationApi || false;
this.options_.customTagParsers = this.options_.customTagParsers || [];
this.options_.customTagMappers = this.options_.customTagMappers || [];
this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
@@ -682,6 +683,7 @@ class VhsHandler extends Component {
'experimentalBufferBasedABR',
'liveRangeSafeTimeDelta',
'experimentalLLHLS',
+ 'useNetworkInformationApi',
'experimentalExactManifestTimings',
'experimentalLeastPixelDiffSelector'
].forEach((option) => {
@@ -789,7 +791,27 @@ class VhsHandler extends Component {
},
bandwidth: {
get() {
- return this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
+ let playerBandwidthEst = this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
+
+ const networkInformation = window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection;
+ const tenMbpsAsBitsPerSecond = 10e6;
+
+ if (this.options_.useNetworkInformationApi && networkInformation) {
+ // downlink returns Mbps
+ // https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlink
+ const networkInfoBandwidthEstBitsPerSec = networkInformation.downlink * 1000 * 1000;
+
+ // downlink maxes out at 10 Mbps. In the event that both networkInformationApi and the player
+ // estimate a bandwidth greater than 10 Mbps, use the larger of the two estimates to ensure that
+ // high quality streams are not filtered out.
+ if (networkInfoBandwidthEstBitsPerSec >= tenMbpsAsBitsPerSecond && playerBandwidthEst >= tenMbpsAsBitsPerSecond) {
+ playerBandwidthEst = Math.max(playerBandwidthEst, networkInfoBandwidthEstBitsPerSec);
+ } else {
+ playerBandwidthEst = networkInfoBandwidthEstBitsPerSec;
+ }
+ }
+
+ return playerBandwidthEst;
},
set(bandwidth) {
this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth;
diff --git a/test/videojs-http-streaming.test.js b/test/videojs-http-streaming.test.js
index dd552cd90..2825d6c95 100644
--- a/test/videojs-http-streaming.test.js
+++ b/test/videojs-http-streaming.test.js
@@ -1160,6 +1160,120 @@ QUnit.test(
}
);
+QUnit.module('NetworkInformationApi', hooks => {
+ hooks.beforeEach(function(assert) {
+ this.env = useFakeEnvironment(assert);
+ this.ogNavigator = window.navigator;
+ this.clock = this.env.clock;
+
+ this.resetNavigatorConnection = (connection = {}) => {
+ // Need to delete the property before setting since navigator doesn't have a setter
+ delete window.navigator;
+ window.navigator = {
+ connection
+ };
+ };
+ });
+
+ hooks.afterEach(function() {
+ this.env.restore();
+ window.navigator = this.ogNavigator;
+ });
+
+ QUnit.test(
+ 'bandwidth returns networkInformation.downlink when useNetworkInformationApi option is enabled',
+ function(assert) {
+ this.resetNavigatorConnection({
+ downlink: 10
+ });
+ this.player = createPlayer({ html5: { vhs: { useNetworkInformationApi: true } } });
+ this.player.src({
+ src: 'manifest/master.m3u8',
+ type: 'application/vnd.apple.mpegurl'
+ });
+
+ this.clock.tick(1);
+
+ // downlink in bits = 10 * 1000000 = 10e6
+ assert.strictEqual(
+ this.player.tech_.vhs.bandwidth,
+ 10e6,
+ 'bandwidth equals networkInfo.downlink represented as bits per second'
+ );
+ }
+ );
+
+ QUnit.test(
+ 'bandwidth uses player-estimated bandwidth when its value is greater than networkInformation.downLink and both values are >= 10 Mbps',
+ function(assert) {
+ this.resetNavigatorConnection({
+ // 10 Mbps or 10e6
+ downlink: 10
+ });
+ this.player = createPlayer({ html5: { vhs: { useNetworkInformationApi: true } } });
+ this.player.src({
+ src: 'manifest/master.m3u8',
+ type: 'application/vnd.apple.mpegurl'
+ });
+
+ this.clock.tick(1);
+
+ this.player.tech_.vhs.bandwidth = 20e6;
+ assert.strictEqual(
+ this.player.tech_.vhs.bandwidth,
+ 20e6,
+ 'bandwidth getter returned the player-estimated bandwidth value'
+ );
+ }
+ );
+
+ QUnit.test(
+ 'bandwidth uses network-information-api bandwidth when its value is less than the player bandwidth and 10 Mbps',
+ function(assert) {
+ this.resetNavigatorConnection({
+ // 9 Mbps or 9e6
+ downlink: 9
+ });
+ this.player = createPlayer({ html5: { vhs: { useNetworkInformationApi: true } } });
+ this.player.src({
+ src: 'manifest/master.m3u8',
+ type: 'application/vnd.apple.mpegurl'
+ });
+
+ this.clock.tick(1);
+
+ this.player.tech_.vhs.bandwidth = 20e10;
+ assert.strictEqual(
+ this.player.tech_.vhs.bandwidth,
+ 9e6,
+ 'bandwidth getter returned the network-information-api bandwidth value since it was less than 10 Mbps'
+ );
+ }
+ );
+
+ QUnit.test(
+ 'bandwidth uses player-estimated bandwidth when networkInformation is not supported',
+ function(assert) {
+ // Nullify the `connection` property on Navigator
+ this.resetNavigatorConnection(null);
+ this.player = createPlayer({ html5: { vhs: { useNetworkInformationApi: true } } });
+ this.player.src({
+ src: 'manifest/master.m3u8',
+ type: 'application/vnd.apple.mpegurl'
+ });
+
+ this.clock.tick(1);
+
+ this.player.tech_.vhs.bandwidth = 20e10;
+ assert.strictEqual(
+ this.player.tech_.vhs.bandwidth,
+ 20e10,
+ 'bandwidth getter returned the player-estimated bandwidth value'
+ );
+ }
+ );
+});
+
QUnit.test('requests a reasonable rendition to start', function(assert) {
this.player.src({
src: 'manifest/master.m3u8',