From 2eb68fae1e1ff9fcf866dac08247ca07a5bb8f71 Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Mon, 18 Dec 2023 19:47:14 -0800 Subject: [PATCH 01/13] fix: loadedplaylist listener for excludeNonUsablePlaylistsByKeyId --- src/playlist-controller.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/playlist-controller.js b/src/playlist-controller.js index 62cf366c1..dd6f899a8 100644 --- a/src/playlist-controller.js +++ b/src/playlist-controller.js @@ -2445,5 +2445,8 @@ export class PlaylistController extends videojs.EventTarget { this.addKeyStatus_(keyId, status); this.excludeNonUsablePlaylistsByKeyId_(); this.fastQualityChange_(); + // Listen to loadedplaylist with a single listener and check for new contentProtection elements when a playlist is updated. + this.off('loadedplaylist', this.excludeNonUsablePlaylistsByKeyId_.bind(this)); + this.on('loadedplaylist', this.excludeNonUsablePlaylistsByKeyId_.bind(this)); } } From bdfe1058b7ecf606fc0077c524019340ea26adff Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Mon, 18 Dec 2023 20:03:58 -0800 Subject: [PATCH 02/13] fix listener --- src/playlist-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/playlist-controller.js b/src/playlist-controller.js index dd6f899a8..f5f33e40d 100644 --- a/src/playlist-controller.js +++ b/src/playlist-controller.js @@ -2446,7 +2446,7 @@ export class PlaylistController extends videojs.EventTarget { this.excludeNonUsablePlaylistsByKeyId_(); this.fastQualityChange_(); // Listen to loadedplaylist with a single listener and check for new contentProtection elements when a playlist is updated. - this.off('loadedplaylist', this.excludeNonUsablePlaylistsByKeyId_.bind(this)); - this.on('loadedplaylist', this.excludeNonUsablePlaylistsByKeyId_.bind(this)); + this.mainPlaylistLoader_.off('loadedplaylist', this.excludeNonUsablePlaylistsByKeyId_.bind(this)); + this.mainPlaylistLoader_.on('loadedplaylist', this.excludeNonUsablePlaylistsByKeyId_.bind(this)); } } From ad2ce18215c228cb85f1978bc88fcf7af9b12c2e Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Mon, 18 Dec 2023 20:08:59 -0800 Subject: [PATCH 03/13] call fastQualityChange --- src/playlist-controller.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/playlist-controller.js b/src/playlist-controller.js index f5f33e40d..cf193002d 100644 --- a/src/playlist-controller.js +++ b/src/playlist-controller.js @@ -2443,10 +2443,14 @@ export class PlaylistController extends videojs.EventTarget { */ updatePlaylistByKeyStatus(keyId, status) { this.addKeyStatus_(keyId, status); + this.excludeNonUsableThenChangePlaylist_(); + // Listen to loadedplaylist with a single listener and check for new contentProtection elements when a playlist is updated. + this.mainPlaylistLoader_.off('loadedplaylist', this.excludeNonUsableThenChangePlaylist_.bind(this)); + this.mainPlaylistLoader_.on('loadedplaylist', this.excludeNonUsableThenChangePlaylist_.bind(this)); + } + + excludeNonUsableThenChangePlaylist_() { this.excludeNonUsablePlaylistsByKeyId_(); this.fastQualityChange_(); - // Listen to loadedplaylist with a single listener and check for new contentProtection elements when a playlist is updated. - this.mainPlaylistLoader_.off('loadedplaylist', this.excludeNonUsablePlaylistsByKeyId_.bind(this)); - this.mainPlaylistLoader_.on('loadedplaylist', this.excludeNonUsablePlaylistsByKeyId_.bind(this)); } } From e9ce21e05fe8d6a34688a68d961abab4254be9e6 Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Mon, 18 Dec 2023 20:40:06 -0800 Subject: [PATCH 04/13] add SD fallback --- src/playlist-controller.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/playlist-controller.js b/src/playlist-controller.js index cf193002d..e4a811274 100644 --- a/src/playlist-controller.js +++ b/src/playlist-controller.js @@ -2390,11 +2390,13 @@ export class PlaylistController extends videojs.EventTarget { * has no keyId leave it enabled by default. */ excludeNonUsablePlaylistsByKeyId_() { - if (!this.mainPlaylistLoader_ || !this.mainPlaylistLoader_.main) { return; } + let excludedPlaylistCount = 0; + const NON_USABLE = 'non-usable'; + this.mainPlaylistLoader_.main.playlists.forEach((playlist) => { const keyIdSet = this.mainPlaylistLoader_.getKeyIdSet(playlist); @@ -2404,13 +2406,14 @@ export class PlaylistController extends videojs.EventTarget { } keyIdSet.forEach((key) => { const USABLE = 'usable'; - const NON_USABLE = 'non-usable'; const hasUsableKeyStatus = this.keyStatusMap_.has(key) && this.keyStatusMap_.get(key) === USABLE; const nonUsableExclusion = playlist.lastExcludeReason_ === NON_USABLE && playlist.excludeUntil === Infinity; + // Lets add a failsafe here where we won't exclude the last possible playlist and try and play. if (!hasUsableKeyStatus) { playlist.excludeUntil = Infinity; playlist.lastExcludeReason_ = NON_USABLE; + excludedPlaylistCount++; this.logger_(`excluding playlist ${playlist.id} because the key ID ${key} doesn't exist in the keyStatusMap or is not ${USABLE}`); } else if (hasUsableKeyStatus && nonUsableExclusion) { delete playlist.excludeUntil; @@ -2419,6 +2422,20 @@ export class PlaylistController extends videojs.EventTarget { } }); }); + + // If for whatever reason we have disabled all the playlists. Lets try re-including the SD renditions as a failsafe. + if (excludedPlaylistCount >= this.mainPlaylistLoader_.main.playlists.length) { + this.mainPlaylistLoader_.main.playlists.forEach((playlist) => { + const isNonHD = playlist && playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height < 720; + const excludedForNonUsableKey = playlist.excludeUntil === Infinity && playlist.lastExcludeReason_ === NON_USABLE; + + if (isNonHD && excludedForNonUsableKey) { + delete playlist.excludeUntil; + delete playlist.lastExcludeReason_; + videojs.log.warn(`enabling non-HD playlist ${playlist.id} because all playlists were excluded due to ${NON_USABLE} keyIds`); + } + }); + } } /** From 0c72ac43cbdccbb5a12c97e3271d5ef1265a2833 Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Mon, 18 Dec 2023 20:52:22 -0800 Subject: [PATCH 05/13] exclude only once for non usable --- src/playlist-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/playlist-controller.js b/src/playlist-controller.js index e4a811274..d345557b2 100644 --- a/src/playlist-controller.js +++ b/src/playlist-controller.js @@ -2410,7 +2410,7 @@ export class PlaylistController extends videojs.EventTarget { const nonUsableExclusion = playlist.lastExcludeReason_ === NON_USABLE && playlist.excludeUntil === Infinity; // Lets add a failsafe here where we won't exclude the last possible playlist and try and play. - if (!hasUsableKeyStatus) { + if (!hasUsableKeyStatus && !nonUsableExclusion) { playlist.excludeUntil = Infinity; playlist.lastExcludeReason_ = NON_USABLE; excludedPlaylistCount++; From 64367a838b5fd09964b3aba143039cd12c20be4e Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Mon, 18 Dec 2023 20:58:35 -0800 Subject: [PATCH 06/13] fix nonUsableKeyStatusCount --- src/playlist-controller.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/playlist-controller.js b/src/playlist-controller.js index d345557b2..d5699228a 100644 --- a/src/playlist-controller.js +++ b/src/playlist-controller.js @@ -2394,7 +2394,7 @@ export class PlaylistController extends videojs.EventTarget { return; } - let excludedPlaylistCount = 0; + let nonUsableKeyStatusCount = 0; const NON_USABLE = 'non-usable'; this.mainPlaylistLoader_.main.playlists.forEach((playlist) => { @@ -2410,11 +2410,14 @@ export class PlaylistController extends videojs.EventTarget { const nonUsableExclusion = playlist.lastExcludeReason_ === NON_USABLE && playlist.excludeUntil === Infinity; // Lets add a failsafe here where we won't exclude the last possible playlist and try and play. - if (!hasUsableKeyStatus && !nonUsableExclusion) { - playlist.excludeUntil = Infinity; - playlist.lastExcludeReason_ = NON_USABLE; - excludedPlaylistCount++; - this.logger_(`excluding playlist ${playlist.id} because the key ID ${key} doesn't exist in the keyStatusMap or is not ${USABLE}`); + if (!hasUsableKeyStatus) { + if (!nonUsableExclusion) { + playlist.excludeUntil = Infinity; + playlist.lastExcludeReason_ = NON_USABLE; + this.logger_(`excluding playlist ${playlist.id} because the key ID ${key} doesn't exist in the keyStatusMap or is not ${USABLE}`); + } + // count all nonUsableKeyStatus + nonUsableKeyStatusCount++; } else if (hasUsableKeyStatus && nonUsableExclusion) { delete playlist.excludeUntil; delete playlist.lastExcludeReason_; @@ -2423,8 +2426,8 @@ export class PlaylistController extends videojs.EventTarget { }); }); - // If for whatever reason we have disabled all the playlists. Lets try re-including the SD renditions as a failsafe. - if (excludedPlaylistCount >= this.mainPlaylistLoader_.main.playlists.length) { + // If for whatever reason every playlist has a non usable key status. Lets try re-including the SD renditions as a failsafe. + if (nonUsableKeyStatusCount >= this.mainPlaylistLoader_.main.playlists.length) { this.mainPlaylistLoader_.main.playlists.forEach((playlist) => { const isNonHD = playlist && playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height < 720; const excludedForNonUsableKey = playlist.excludeUntil === Infinity && playlist.lastExcludeReason_ === NON_USABLE; From f7852a26203ca451614fd870fb5b72248c18dceb Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Mon, 18 Dec 2023 21:11:37 -0800 Subject: [PATCH 07/13] additional logging small fixes --- src/playlist-controller.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/playlist-controller.js b/src/playlist-controller.js index d5699228a..3fe154a7e 100644 --- a/src/playlist-controller.js +++ b/src/playlist-controller.js @@ -2450,9 +2450,10 @@ export class PlaylistController extends videojs.EventTarget { addKeyStatus_(keyId, status) { const isString = typeof keyId === 'string'; const keyIdHexString = isString ? keyId : bufferToHexString(keyId); + const formattedKeyIdString = keyIdHexString.slice(0, 32).toLowerCase(); - // 32 digit keyId hex string. - this.keyStatusMap_.set(keyIdHexString.slice(0, 32), status); + this.logger_(`KeyStatus ${status} with keyId ${formattedKeyIdString} added to the keyStatusMap`); + this.keyStatusMap_.set(formattedKeyIdString, status); } /** @@ -2463,7 +2464,9 @@ export class PlaylistController extends videojs.EventTarget { */ updatePlaylistByKeyStatus(keyId, status) { this.addKeyStatus_(keyId, status); - this.excludeNonUsableThenChangePlaylist_(); + if (!this.waitingForFastQualityPlaylistReceived_) { + this.excludeNonUsableThenChangePlaylist_(); + } // Listen to loadedplaylist with a single listener and check for new contentProtection elements when a playlist is updated. this.mainPlaylistLoader_.off('loadedplaylist', this.excludeNonUsableThenChangePlaylist_.bind(this)); this.mainPlaylistLoader_.on('loadedplaylist', this.excludeNonUsableThenChangePlaylist_.bind(this)); From 380c82a237d2a1dd54609b037757936eb99393a4 Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Mon, 18 Dec 2023 21:21:22 -0800 Subject: [PATCH 08/13] fix fallback --- src/playlist-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/playlist-controller.js b/src/playlist-controller.js index 3fe154a7e..1ff6001c5 100644 --- a/src/playlist-controller.js +++ b/src/playlist-controller.js @@ -2433,8 +2433,8 @@ export class PlaylistController extends videojs.EventTarget { const excludedForNonUsableKey = playlist.excludeUntil === Infinity && playlist.lastExcludeReason_ === NON_USABLE; if (isNonHD && excludedForNonUsableKey) { + // Only delete the excludeUntil so we don't try and re-exclude these playlists. delete playlist.excludeUntil; - delete playlist.lastExcludeReason_; videojs.log.warn(`enabling non-HD playlist ${playlist.id} because all playlists were excluded due to ${NON_USABLE} keyIds`); } }); From c5910477e1770cef596c3567408e1a13a9ff52e8 Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Mon, 18 Dec 2023 21:30:08 -0800 Subject: [PATCH 09/13] only excludeUntil Infinity once --- src/playlist-controller.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/playlist-controller.js b/src/playlist-controller.js index 1ff6001c5..43c9958f3 100644 --- a/src/playlist-controller.js +++ b/src/playlist-controller.js @@ -2409,9 +2409,8 @@ export class PlaylistController extends videojs.EventTarget { const hasUsableKeyStatus = this.keyStatusMap_.has(key) && this.keyStatusMap_.get(key) === USABLE; const nonUsableExclusion = playlist.lastExcludeReason_ === NON_USABLE && playlist.excludeUntil === Infinity; - // Lets add a failsafe here where we won't exclude the last possible playlist and try and play. if (!hasUsableKeyStatus) { - if (!nonUsableExclusion) { + if (!playlist.excludeUntil === Infinity) { playlist.excludeUntil = Infinity; playlist.lastExcludeReason_ = NON_USABLE; this.logger_(`excluding playlist ${playlist.id} because the key ID ${key} doesn't exist in the keyStatusMap or is not ${USABLE}`); From e22a66c2b9027064be6df98cf844350ac012925c Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Mon, 18 Dec 2023 21:30:47 -0800 Subject: [PATCH 10/13] syntax --- src/playlist-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/playlist-controller.js b/src/playlist-controller.js index 43c9958f3..40abdc61a 100644 --- a/src/playlist-controller.js +++ b/src/playlist-controller.js @@ -2410,7 +2410,7 @@ export class PlaylistController extends videojs.EventTarget { const nonUsableExclusion = playlist.lastExcludeReason_ === NON_USABLE && playlist.excludeUntil === Infinity; if (!hasUsableKeyStatus) { - if (!playlist.excludeUntil === Infinity) { + if (playlist.excludeUntil !== Infinity) { playlist.excludeUntil = Infinity; playlist.lastExcludeReason_ = NON_USABLE; this.logger_(`excluding playlist ${playlist.id} because the key ID ${key} doesn't exist in the keyStatusMap or is not ${USABLE}`); From 72acf3c05f4f0422ff5ea1b9c4350e0424871f4e Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Mon, 18 Dec 2023 21:39:18 -0800 Subject: [PATCH 11/13] log fixes --- src/playlist-controller.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/playlist-controller.js b/src/playlist-controller.js index 40abdc61a..e7f97bcae 100644 --- a/src/playlist-controller.js +++ b/src/playlist-controller.js @@ -2410,7 +2410,8 @@ export class PlaylistController extends videojs.EventTarget { const nonUsableExclusion = playlist.lastExcludeReason_ === NON_USABLE && playlist.excludeUntil === Infinity; if (!hasUsableKeyStatus) { - if (playlist.excludeUntil !== Infinity) { + // Only exclude playlists that haven't already been excluded as non-usable. + if (!nonUsableExclusion && playlist.lastExcludeReason_ !== NON_USABLE) { playlist.excludeUntil = Infinity; playlist.lastExcludeReason_ = NON_USABLE; this.logger_(`excluding playlist ${playlist.id} because the key ID ${key} doesn't exist in the keyStatusMap or is not ${USABLE}`); @@ -2434,7 +2435,7 @@ export class PlaylistController extends videojs.EventTarget { if (isNonHD && excludedForNonUsableKey) { // Only delete the excludeUntil so we don't try and re-exclude these playlists. delete playlist.excludeUntil; - videojs.log.warn(`enabling non-HD playlist ${playlist.id} because all playlists were excluded due to ${NON_USABLE} keyIds`); + videojs.log.warn(`enabling non-HD playlist ${playlist.id} because all playlists were excluded due to ${NON_USABLE} key IDs`); } }); } @@ -2451,7 +2452,7 @@ export class PlaylistController extends videojs.EventTarget { const keyIdHexString = isString ? keyId : bufferToHexString(keyId); const formattedKeyIdString = keyIdHexString.slice(0, 32).toLowerCase(); - this.logger_(`KeyStatus ${status} with keyId ${formattedKeyIdString} added to the keyStatusMap`); + this.logger_(`KeyStatus '${status}' with key ID ${formattedKeyIdString} added to the keyStatusMap`); this.keyStatusMap_.set(formattedKeyIdString, status); } From 4b99dff1ca6be192f062678649d46db347fdb680 Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Tue, 19 Dec 2023 22:42:39 -0800 Subject: [PATCH 12/13] add test --- test/playlist-controller.test.js | 53 ++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/test/playlist-controller.test.js b/test/playlist-controller.test.js index 05c6276aa..3a4f5fb14 100644 --- a/test/playlist-controller.test.js +++ b/test/playlist-controller.test.js @@ -5061,6 +5061,59 @@ QUnit.test('excludeNonUsablePlaylistsByKeyId_ re includes non usable DASH playli }); }); +QUnit.test('excludeNonUsablePlaylistsByKeyId_ re-includes SD playlists when all playlists are excluded', function(assert) { + const options = { + src: 'test', + tech: this.player.tech_, + sourceType: 'dash' + }; + const pc = new PlaylistController(options); + const origWarn = videojs.log.warn; + const warnings = []; + + videojs.log.warn = (text) => warnings.push(text); + + const excludedPlaylist = { + contentProtection: { + mp4protection: { + attributes: { + 'cenc:default_KID': 'd0bebaf8a3cb4c52bae03d20a71e3df3' + } + } + }, + attributes: { + RESOLUTION: { + height: 480 + } + } + }; + + const includedPlaylist = { + contentProtection: { + mp4protection: { + attributes: { + 'cenc:default_KID': '89256e53dbe544e9afba38d2ca17d176' + } + } + }, + attributes: { + RESOLUTION: { + height: 360 + } + } + }; + + pc.mainPlaylistLoader_.main = { playlists: [includedPlaylist, excludedPlaylist] }; + pc.excludeNonUsablePlaylistsByKeyId_(); + + assert.notOk(pc.mainPlaylistLoader_.main.playlists[0].excludeUntil, 'excludeUntil is Infinity'); + assert.equal(pc.mainPlaylistLoader_.main.playlists[0].lastExcludeReason_, 'non-usable', 'lastExcludeReason is non-usable'); + assert.notOk(pc.mainPlaylistLoader_.main.playlists[1].excludeUntil, 'excludeUntil is Infinity'); + assert.equal(pc.mainPlaylistLoader_.main.playlists[1].lastExcludeReason_, 'non-usable', 'lastExcludeReason is non-usable'); + assert.equal(warnings.length, 2, 're-include warning for both playlists'); + videojs.log.warn = origWarn; +}); + QUnit.module('PlaylistController codecs', { beforeEach(assert) { sharedHooks.beforeEach.call(this, assert); From 4eeafc57a59b5fa78c7b7b6dca270c11be5251f2 Mon Sep 17 00:00:00 2001 From: Adam Waldron Date: Wed, 20 Dec 2023 09:56:20 -0800 Subject: [PATCH 13/13] fix test case change conditional for clarity --- src/playlist-controller.js | 2 +- test/playlist-controller.test.js | 27 ++++++++++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/playlist-controller.js b/src/playlist-controller.js index e7f97bcae..4af9996e0 100644 --- a/src/playlist-controller.js +++ b/src/playlist-controller.js @@ -2411,7 +2411,7 @@ export class PlaylistController extends videojs.EventTarget { if (!hasUsableKeyStatus) { // Only exclude playlists that haven't already been excluded as non-usable. - if (!nonUsableExclusion && playlist.lastExcludeReason_ !== NON_USABLE) { + if (playlist.excludeUntil !== Infinity && playlist.lastExcludeReason_ !== NON_USABLE) { playlist.excludeUntil = Infinity; playlist.lastExcludeReason_ = NON_USABLE; this.logger_(`excluding playlist ${playlist.id} because the key ID ${key} doesn't exist in the keyStatusMap or is not ${USABLE}`); diff --git a/test/playlist-controller.test.js b/test/playlist-controller.test.js index 3a4f5fb14..d0d1b1a9b 100644 --- a/test/playlist-controller.test.js +++ b/test/playlist-controller.test.js @@ -5073,7 +5073,7 @@ QUnit.test('excludeNonUsablePlaylistsByKeyId_ re-includes SD playlists when all videojs.log.warn = (text) => warnings.push(text); - const excludedPlaylist = { + const reIncludedPlaylist1 = { contentProtection: { mp4protection: { attributes: { @@ -5088,7 +5088,7 @@ QUnit.test('excludeNonUsablePlaylistsByKeyId_ re-includes SD playlists when all } }; - const includedPlaylist = { + const reIncludedPlaylist2 = { contentProtection: { mp4protection: { attributes: { @@ -5103,14 +5103,31 @@ QUnit.test('excludeNonUsablePlaylistsByKeyId_ re-includes SD playlists when all } }; - pc.mainPlaylistLoader_.main = { playlists: [includedPlaylist, excludedPlaylist] }; + const excludedPlaylist = { + contentProtection: { + mp4protection: { + attributes: { + 'cenc:default_KID': '89256e53dbe544e9afba38d2ca17d176' + } + } + }, + attributes: { + RESOLUTION: { + height: 1080 + } + } + }; + + pc.mainPlaylistLoader_.main = { playlists: [reIncludedPlaylist1, reIncludedPlaylist2, excludedPlaylist] }; pc.excludeNonUsablePlaylistsByKeyId_(); - assert.notOk(pc.mainPlaylistLoader_.main.playlists[0].excludeUntil, 'excludeUntil is Infinity'); + assert.notOk(pc.mainPlaylistLoader_.main.playlists[0].excludeUntil, 'excludeUntil is not Infinity'); assert.equal(pc.mainPlaylistLoader_.main.playlists[0].lastExcludeReason_, 'non-usable', 'lastExcludeReason is non-usable'); - assert.notOk(pc.mainPlaylistLoader_.main.playlists[1].excludeUntil, 'excludeUntil is Infinity'); + assert.notOk(pc.mainPlaylistLoader_.main.playlists[1].excludeUntil, 'excludeUntil is not Infinity'); assert.equal(pc.mainPlaylistLoader_.main.playlists[1].lastExcludeReason_, 'non-usable', 'lastExcludeReason is non-usable'); assert.equal(warnings.length, 2, 're-include warning for both playlists'); + assert.equal(pc.mainPlaylistLoader_.main.playlists[2].excludeUntil, Infinity, 'excludeUntil is Infinity'); + assert.equal(pc.mainPlaylistLoader_.main.playlists[2].lastExcludeReason_, 'non-usable', 'lastExcludeReason is non-usable'); videojs.log.warn = origWarn; });