Skip to content

Commit f12c197

Browse files
author
Adam Waldron
authored
fix: keyId filtering loadedplaylist listener and improvements (#1468)
1 parent 7debc17 commit f12c197

File tree

2 files changed

+107
-7
lines changed

2 files changed

+107
-7
lines changed

src/playlist-controller.js

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2390,11 +2390,13 @@ export class PlaylistController extends videojs.EventTarget {
23902390
* has no keyId leave it enabled by default.
23912391
*/
23922392
excludeNonUsablePlaylistsByKeyId_() {
2393-
23942393
if (!this.mainPlaylistLoader_ || !this.mainPlaylistLoader_.main) {
23952394
return;
23962395
}
23972396

2397+
let nonUsableKeyStatusCount = 0;
2398+
const NON_USABLE = 'non-usable';
2399+
23982400
this.mainPlaylistLoader_.main.playlists.forEach((playlist) => {
23992401
const keyIdSet = this.mainPlaylistLoader_.getKeyIdSet(playlist);
24002402

@@ -2404,21 +2406,39 @@ export class PlaylistController extends videojs.EventTarget {
24042406
}
24052407
keyIdSet.forEach((key) => {
24062408
const USABLE = 'usable';
2407-
const NON_USABLE = 'non-usable';
24082409
const hasUsableKeyStatus = this.keyStatusMap_.has(key) && this.keyStatusMap_.get(key) === USABLE;
24092410
const nonUsableExclusion = playlist.lastExcludeReason_ === NON_USABLE && playlist.excludeUntil === Infinity;
24102411

24112412
if (!hasUsableKeyStatus) {
2412-
playlist.excludeUntil = Infinity;
2413-
playlist.lastExcludeReason_ = NON_USABLE;
2414-
this.logger_(`excluding playlist ${playlist.id} because the key ID ${key} doesn't exist in the keyStatusMap or is not ${USABLE}`);
2413+
// Only exclude playlists that haven't already been excluded as non-usable.
2414+
if (playlist.excludeUntil !== Infinity && playlist.lastExcludeReason_ !== NON_USABLE) {
2415+
playlist.excludeUntil = Infinity;
2416+
playlist.lastExcludeReason_ = NON_USABLE;
2417+
this.logger_(`excluding playlist ${playlist.id} because the key ID ${key} doesn't exist in the keyStatusMap or is not ${USABLE}`);
2418+
}
2419+
// count all nonUsableKeyStatus
2420+
nonUsableKeyStatusCount++;
24152421
} else if (hasUsableKeyStatus && nonUsableExclusion) {
24162422
delete playlist.excludeUntil;
24172423
delete playlist.lastExcludeReason_;
24182424
this.logger_(`enabling playlist ${playlist.id} because key ID ${key} is ${USABLE}`);
24192425
}
24202426
});
24212427
});
2428+
2429+
// If for whatever reason every playlist has a non usable key status. Lets try re-including the SD renditions as a failsafe.
2430+
if (nonUsableKeyStatusCount >= this.mainPlaylistLoader_.main.playlists.length) {
2431+
this.mainPlaylistLoader_.main.playlists.forEach((playlist) => {
2432+
const isNonHD = playlist && playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height < 720;
2433+
const excludedForNonUsableKey = playlist.excludeUntil === Infinity && playlist.lastExcludeReason_ === NON_USABLE;
2434+
2435+
if (isNonHD && excludedForNonUsableKey) {
2436+
// Only delete the excludeUntil so we don't try and re-exclude these playlists.
2437+
delete playlist.excludeUntil;
2438+
videojs.log.warn(`enabling non-HD playlist ${playlist.id} because all playlists were excluded due to ${NON_USABLE} key IDs`);
2439+
}
2440+
});
2441+
}
24222442
}
24232443

24242444
/**
@@ -2430,9 +2450,10 @@ export class PlaylistController extends videojs.EventTarget {
24302450
addKeyStatus_(keyId, status) {
24312451
const isString = typeof keyId === 'string';
24322452
const keyIdHexString = isString ? keyId : bufferToHexString(keyId);
2453+
const formattedKeyIdString = keyIdHexString.slice(0, 32).toLowerCase();
24332454

2434-
// 32 digit keyId hex string.
2435-
this.keyStatusMap_.set(keyIdHexString.slice(0, 32), status);
2455+
this.logger_(`KeyStatus '${status}' with key ID ${formattedKeyIdString} added to the keyStatusMap`);
2456+
this.keyStatusMap_.set(formattedKeyIdString, status);
24362457
}
24372458

24382459
/**
@@ -2443,6 +2464,15 @@ export class PlaylistController extends videojs.EventTarget {
24432464
*/
24442465
updatePlaylistByKeyStatus(keyId, status) {
24452466
this.addKeyStatus_(keyId, status);
2467+
if (!this.waitingForFastQualityPlaylistReceived_) {
2468+
this.excludeNonUsableThenChangePlaylist_();
2469+
}
2470+
// Listen to loadedplaylist with a single listener and check for new contentProtection elements when a playlist is updated.
2471+
this.mainPlaylistLoader_.off('loadedplaylist', this.excludeNonUsableThenChangePlaylist_.bind(this));
2472+
this.mainPlaylistLoader_.on('loadedplaylist', this.excludeNonUsableThenChangePlaylist_.bind(this));
2473+
}
2474+
2475+
excludeNonUsableThenChangePlaylist_() {
24462476
this.excludeNonUsablePlaylistsByKeyId_();
24472477
this.fastQualityChange_();
24482478
}

test/playlist-controller.test.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5061,6 +5061,76 @@ QUnit.test('excludeNonUsablePlaylistsByKeyId_ re includes non usable DASH playli
50615061
});
50625062
});
50635063

5064+
QUnit.test('excludeNonUsablePlaylistsByKeyId_ re-includes SD playlists when all playlists are excluded', function(assert) {
5065+
const options = {
5066+
src: 'test',
5067+
tech: this.player.tech_,
5068+
sourceType: 'dash'
5069+
};
5070+
const pc = new PlaylistController(options);
5071+
const origWarn = videojs.log.warn;
5072+
const warnings = [];
5073+
5074+
videojs.log.warn = (text) => warnings.push(text);
5075+
5076+
const reIncludedPlaylist1 = {
5077+
contentProtection: {
5078+
mp4protection: {
5079+
attributes: {
5080+
'cenc:default_KID': 'd0bebaf8a3cb4c52bae03d20a71e3df3'
5081+
}
5082+
}
5083+
},
5084+
attributes: {
5085+
RESOLUTION: {
5086+
height: 480
5087+
}
5088+
}
5089+
};
5090+
5091+
const reIncludedPlaylist2 = {
5092+
contentProtection: {
5093+
mp4protection: {
5094+
attributes: {
5095+
'cenc:default_KID': '89256e53dbe544e9afba38d2ca17d176'
5096+
}
5097+
}
5098+
},
5099+
attributes: {
5100+
RESOLUTION: {
5101+
height: 360
5102+
}
5103+
}
5104+
};
5105+
5106+
const excludedPlaylist = {
5107+
contentProtection: {
5108+
mp4protection: {
5109+
attributes: {
5110+
'cenc:default_KID': '89256e53dbe544e9afba38d2ca17d176'
5111+
}
5112+
}
5113+
},
5114+
attributes: {
5115+
RESOLUTION: {
5116+
height: 1080
5117+
}
5118+
}
5119+
};
5120+
5121+
pc.mainPlaylistLoader_.main = { playlists: [reIncludedPlaylist1, reIncludedPlaylist2, excludedPlaylist] };
5122+
pc.excludeNonUsablePlaylistsByKeyId_();
5123+
5124+
assert.notOk(pc.mainPlaylistLoader_.main.playlists[0].excludeUntil, 'excludeUntil is not Infinity');
5125+
assert.equal(pc.mainPlaylistLoader_.main.playlists[0].lastExcludeReason_, 'non-usable', 'lastExcludeReason is non-usable');
5126+
assert.notOk(pc.mainPlaylistLoader_.main.playlists[1].excludeUntil, 'excludeUntil is not Infinity');
5127+
assert.equal(pc.mainPlaylistLoader_.main.playlists[1].lastExcludeReason_, 'non-usable', 'lastExcludeReason is non-usable');
5128+
assert.equal(warnings.length, 2, 're-include warning for both playlists');
5129+
assert.equal(pc.mainPlaylistLoader_.main.playlists[2].excludeUntil, Infinity, 'excludeUntil is Infinity');
5130+
assert.equal(pc.mainPlaylistLoader_.main.playlists[2].lastExcludeReason_, 'non-usable', 'lastExcludeReason is non-usable');
5131+
videojs.log.warn = origWarn;
5132+
});
5133+
50645134
QUnit.module('PlaylistController codecs', {
50655135
beforeEach(assert) {
50665136
sharedHooks.beforeEach.call(this, assert);

0 commit comments

Comments
 (0)