Skip to content

Commit

Permalink
Activate level capping only when a video codec is found (video-dev#1691)
Browse files Browse the repository at this point in the history
- Start level capping on `BUFFER_CODECS` if a video codec was found and `MANIFEST_PARSED` previously did not signal a video stream
  • Loading branch information
johnBartos authored May 14, 2018
1 parent 2c9d1d9 commit bbc8b36
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 13 deletions.
64 changes: 51 additions & 13 deletions src/controller/cap-level-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,21 @@ class CapLevelController extends EventHandler {
super(hls,
Event.FPS_DROP_LEVEL_CAPPING,
Event.MEDIA_ATTACHING,
Event.MANIFEST_PARSED);
Event.MANIFEST_PARSED,
Event.BUFFER_CODECS);

this.autoLevelCapping = Number.POSITIVE_INFINITY;
this.firstLevel = null;
this.levels = [];
this.media = null;
this.restrictedLevels = [];
this.timer = null;
}

destroy () {
if (this.hls.config.capLevelToPlayerSize) {
this.media = this.restrictedLevels = null;
this.autoLevelCapping = Number.POSITIVE_INFINITY;
if (this.timer) {
this.timer = clearInterval(this.timer);
}
this.media = null;
this._stopCapping();
}
}

Expand All @@ -37,17 +42,28 @@ class CapLevelController extends EventHandler {
onManifestParsed (data) {
const hls = this.hls;
this.restrictedLevels = [];
// Only fire getMaxLevel or detectPlayerSize if video is expected in the manifest
this.levels = data.levels;
this.firstLevel = data.firstLevel;
if (hls.config.capLevelToPlayerSize && (data.video || (data.levels.length && data.altAudio))) {
this.autoLevelCapping = Number.POSITIVE_INFINITY;
this.levels = data.levels;
hls.firstLevel = this.getMaxLevel(data.firstLevel);
clearInterval(this.timer);
this.timer = setInterval(this.detectPlayerSize.bind(this), 1000);
this.detectPlayerSize();
// Start capping immediately if the manifest has signaled video codecs
this._startCapping();
}
}

// Only activate capping when playing a video stream; otherwise, multi-bitrate audio-only streams will be restricted
// to the first level
onBufferCodecs (data) {
const hls = this.hls;
if (hls.config.capLevelToPlayerSize && data.video) {
// If the manifest did not signal a video codec capping has been deferred until we're certain video is present
this._startCapping();
}
}

onLevelsUpdated (data) {
this.levels = data.levels;
}

detectPlayerSize () {
if (this.media) {
let levelsLength = this.levels ? this.levels.length : 0;
Expand Down Expand Up @@ -79,6 +95,28 @@ class CapLevelController extends EventHandler {
return CapLevelController.getMaxLevelByMediaSize(validLevels, this.mediaWidth, this.mediaHeight);
}

_startCapping () {
if (this.timer) {
// Don't reset capping if started twice; this can happen if the manifest signals a video codec
return;
}
this.autoLevelCapping = Number.POSITIVE_INFINITY;
this.hls.firstLevel = this.getMaxLevel(this.firstLevel);
clearInterval(this.timer);
this.timer = setInterval(this.detectPlayerSize.bind(this), 1000);
this.detectPlayerSize();
}

_stopCapping () {
this.restrictedLevels = [];
this.firstLevel = null;
this.autoLevelCapping = Number.POSITIVE_INFINITY;
if (this.timer) {
this.timer = clearInterval(this.timer);
this.timer = null;
}
}

get mediaWidth () {
let width;
const media = this.media;
Expand Down
83 changes: 83 additions & 0 deletions tests/unit/controller/cap-level-controller.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const assert = require('assert');

import sinon from 'sinon';
import Hls from '../../../src/hls';
import CapLevelController from '../../../src/controller/cap-level-controller';

const levels = [
Expand Down Expand Up @@ -57,4 +59,85 @@ describe('CapLevelController', function () {
assert.equal(expected, actual);
});
});

describe('initialization', function () {
let capLevelController;
let firstLevelSpy;
let startCappingSpy;
beforeEach(function () {
const hls = new Hls({ capLevelToPlayerSize: true });
firstLevelSpy = sinon.spy(hls, 'firstLevel', ['set']);
capLevelController = new CapLevelController(hls);
startCappingSpy = sinon.spy(capLevelController, '_startCapping');
});

describe('start and stop', function () {
it('immediately caps and sets a timer for monitoring size size', function () {
const detectPlayerSizeSpy = sinon.spy(capLevelController, 'detectPlayerSize');
capLevelController._startCapping();
assert(capLevelController.timer);
assert(firstLevelSpy.set.calledOnce);
assert(detectPlayerSizeSpy.calledOnce);
});

it('stops the capping timer and resets capping', function () {
capLevelController.autoLevelCapping = 4;
capLevelController.timer = 1;
capLevelController._stopCapping();

assert.strictEqual(capLevelController.autoLevelCapping, Number.POSITIVE_INFINITY);
assert.strictEqual(capLevelController.restrictedLevels.length, 0);
assert.strictEqual(capLevelController.firstLevel, null);
assert.strictEqual(capLevelController.timer, null);
});
});

it('constructs with no restrictions', function () {
assert.strictEqual(capLevelController.levels.length, 0);
assert.strictEqual(capLevelController.restrictedLevels.length, 0);
assert.strictEqual(capLevelController.timer, null);
assert.strictEqual(capLevelController.autoLevelCapping, Number.POSITIVE_INFINITY);
assert(firstLevelSpy.set.notCalled);
});

it('starts capping on BUFFER_CODECS only if video is found', function () {
capLevelController.onBufferCodecs({ video: {} });
assert(startCappingSpy.calledOnce);
});

it('does not start capping on BUFFER_CODECS if video is not found', function () {
capLevelController.onBufferCodecs({ audio: {} });
assert(startCappingSpy.notCalled);
});

it('starts capping if the video codec was found after the audio codec', function () {
capLevelController.onBufferCodecs({ audio: {} });
assert(startCappingSpy.notCalled);
capLevelController.onBufferCodecs({ video: {} });
assert(startCappingSpy.calledOnce);
});

it('receives level information from the MANIFEST_PARSED event', function () {
capLevelController.restrictedLevels = [1];
let data = {
levels: [{ foo: 'bar' }],
firstLevel: 0
};

capLevelController.onManifestParsed(data);
assert.strictEqual(capLevelController.levels, data.levels);
assert.strictEqual(capLevelController.firstLevel, data.firstLevel);
assert.strictEqual(capLevelController.restrictedLevels.length, 0);
});

it('should start capping in MANIFEST_PARSED if a video codec was signaled', function () {
capLevelController.onManifestParsed({ video: {} });
assert(startCappingSpy.calledOnce);
});

it('should start capping in MANIFEST_PARSED if a levels and altAudio were signaled', function () {
capLevelController.onManifestParsed({ levels: [{}], altAudio: true });
assert(startCappingSpy.calledOnce);
});
});
});

0 comments on commit bbc8b36

Please sign in to comment.