Skip to content

Commit

Permalink
feat: Add an option to use the NetworkInformation API, when available (
Browse files Browse the repository at this point in the history
…#1218)

When enabled, if the NetworkInformation API is available, it will be used for bandwidth estimation, If our estimation is greater than 10MBps and the downlink returns 10MBps, then our estimation is used.
  • Loading branch information
evanfarina authored Nov 8, 2021
1 parent 25c33ca commit 061cf3c
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 2 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@
<label class="form-check-label" for="autoplay">Autoplay</label>
</div>

<div class="form-check">
<input id=network-info type="checkbox" class="form-check-input">
<label class="form-check-label" for="network-info">Use networkInfo API for bandwidth estimations (reloads player)</label>
</div>

<div class="form-check">
<input id=llhls type="checkbox" class="form-check-input">
<label class="form-check-label" for="llhls">[EXPERIMENTAL] Enables support for ll-hls (reloads player)</label>
Expand Down
5 changes: 4 additions & 1 deletion scripts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@
'buffer-water',
'exact-manifest-timings',
'pixel-diff-selector',
'network-info',
'override-native',
'preload',
'mirror-source'
Expand Down Expand Up @@ -499,6 +500,7 @@
'override-native',
'liveui',
'pixel-diff-selector',
'network-info',
'exact-manifest-timings'
].forEach(function(name) {
stateEls[name].addEventListener('change', function(event) {
Expand Down Expand Up @@ -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'])
}
}
});
Expand Down
24 changes: 23 additions & 1 deletion src/videojs-http-streaming.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -682,6 +683,7 @@ class VhsHandler extends Component {
'experimentalBufferBasedABR',
'liveRangeSafeTimeDelta',
'experimentalLLHLS',
'useNetworkInformationApi',
'experimentalExactManifestTimings',
'experimentalLeastPixelDiffSelector'
].forEach((option) => {
Expand Down Expand Up @@ -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;
Expand Down
114 changes: 114 additions & 0 deletions test/videojs-http-streaming.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit 061cf3c

Please sign in to comment.