@amazon/vinyl 3.1.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +236 -187
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +224 -174
- package/dist/index.js.map +4 -4
- package/dist/test/integ.es5.js +245 -231
- package/dist/test/integ.js +676 -271
- package/dist/test/integ.js.map +4 -4
- package/dist/types/current/ad/AdControllerImpl.d.ts +18 -2
- package/dist/types/current/ad/AdControllerImpl.d.ts.map +1 -1
- package/dist/types/current/ad/AdControllerImpl.js +42 -20
- package/dist/types/current/drm/DrmControllerImpl.d.ts +17 -1
- package/dist/types/current/drm/DrmControllerImpl.d.ts.map +1 -1
- package/dist/types/current/drm/DrmControllerImpl.js +45 -10
- package/dist/types/current/playback/createStallMonitor.d.ts.map +1 -1
- package/dist/types/current/playback/createStallMonitor.js +19 -14
- package/dist/types/current/streaming/LoadMetric.d.ts +15 -1
- package/dist/types/current/streaming/LoadMetric.d.ts.map +1 -1
- package/dist/types/current/streaming/mediaTimelineFilter.d.ts +4 -2
- package/dist/types/current/streaming/mediaTimelineFilter.d.ts.map +1 -1
- package/dist/types/current/streaming/mediaTimelineFilter.js +23 -8
- package/dist/types/current/track/filters/sampleRateFilter.d.ts +8 -4
- package/dist/types/current/track/filters/sampleRateFilter.d.ts.map +1 -1
- package/dist/types/current/track/filters/sampleRateFilter.js +31 -21
- package/dist/types/current/track/mse/MseTrack.d.ts +0 -9
- package/dist/types/current/track/mse/MseTrack.d.ts.map +1 -1
- package/dist/types/current/track/mse/MseTrack.js +2 -29
- package/dist/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/types/current/vinyl/VinylPlayer.d.ts +5 -0
- package/dist/types/current/vinyl/VinylPlayer.d.ts.map +1 -1
- package/dist/types/current/vinyl/VinylPlayer.js +23 -1
- package/dist/types/ts3.7/ad/AdControllerImpl.d.ts +18 -2
- package/dist/types/ts3.7/drm/DrmControllerImpl.d.ts +17 -1
- package/dist/types/ts3.7/streaming/LoadMetric.d.ts +15 -1
- package/dist/types/ts3.7/streaming/mediaTimelineFilter.d.ts +4 -2
- package/dist/types/ts3.7/track/filters/sampleRateFilter.d.ts +8 -4
- package/dist/types/ts3.7/track/mse/MseTrack.d.ts +0 -9
- package/dist/types/ts3.7/vinyl/VinylPlayer.d.ts +5 -0
- package/dist/types/ts4.1/ad/AdControllerImpl.d.ts +18 -2
- package/dist/types/ts4.1/drm/DrmControllerImpl.d.ts +17 -1
- package/dist/types/ts4.1/streaming/LoadMetric.d.ts +15 -1
- package/dist/types/ts4.1/streaming/mediaTimelineFilter.d.ts +4 -2
- package/dist/types/ts4.1/track/filters/sampleRateFilter.d.ts +8 -4
- package/dist/types/ts4.1/track/mse/MseTrack.d.ts +0 -9
- package/dist/types/ts4.1/vinyl/VinylPlayer.d.ts +5 -0
- package/dist/types/ts4.7/ad/AdControllerImpl.d.ts +18 -2
- package/dist/types/ts4.7/drm/DrmControllerImpl.d.ts +17 -1
- package/dist/types/ts4.7/streaming/LoadMetric.d.ts +15 -1
- package/dist/types/ts4.7/streaming/mediaTimelineFilter.d.ts +4 -2
- package/dist/types/ts4.7/track/filters/sampleRateFilter.d.ts +8 -4
- package/dist/types/ts4.7/track/mse/MseTrack.d.ts +0 -9
- package/dist/types/ts4.7/vinyl/VinylPlayer.d.ts +5 -0
- package/dist/unit/types/current/ad/AdControllerImpl.test.js +110 -4
- package/dist/unit/types/current/drm/DrmControllerImpl.test.js +53 -9
- package/dist/unit/types/current/playback/createStallMonitor.test.js +38 -0
- package/dist/unit/types/current/streaming/MseTrack.test.js +11 -8
- package/dist/unit/types/current/streaming/mediaTimelineFilter.test.js +60 -54
- package/dist/unit/types/current/track/filters/sampleRateFilter.test.js +75 -0
- package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/unit/types/current/vinyl/VinylPlayer.test.js +33 -2
- package/dist/vinylTestUtil/index.js +10 -1
- package/dist/vinylTestUtil/index.js.map +2 -2
- package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.d.ts +5 -0
- package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.d.ts.map +1 -1
- package/dist/vinylTestUtil/types/current/assets/vinylTestAssets.js +9 -0
- package/dist/vinylTestUtil/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/vinylTestUtil/types/ts3.7/assets/vinylTestAssets.d.ts +5 -0
- package/dist/vinylTestUtil/types/ts4.1/assets/vinylTestAssets.d.ts +5 -0
- package/dist/vinylTestUtil/types/ts4.7/assets/vinylTestAssets.d.ts +5 -0
- package/package.json +10 -10
package/dist/test/integ.js
CHANGED
|
@@ -5592,9 +5592,6 @@
|
|
|
5592
5592
|
function map(arrayLike, transform, thisArg) {
|
|
5593
5593
|
return Array.prototype.map.call(arrayLike, transform, thisArg);
|
|
5594
5594
|
}
|
|
5595
|
-
function every(arrayLike, predicate, thisArg) {
|
|
5596
|
-
return Array.prototype.every.call(arrayLike, predicate, thisArg);
|
|
5597
|
-
}
|
|
5598
5595
|
function first(arrayLike) {
|
|
5599
5596
|
if (!arrayLike) return void 0;
|
|
5600
5597
|
return arrayLike[0];
|
|
@@ -9956,6 +9953,7 @@ ${this.lastInitStack}` : "";
|
|
|
9956
9953
|
|
|
9957
9954
|
// src/ad/AdControllerImpl.ts
|
|
9958
9955
|
var AD_START_TOLERANCE = 0.05;
|
|
9956
|
+
var PLAYOUT_LIMIT_TOLERANCE = 1;
|
|
9959
9957
|
var defaultAdControllerImplOptions = {
|
|
9960
9958
|
adLoadTimeout: 15,
|
|
9961
9959
|
preloadAheadTime: 10
|
|
@@ -10446,23 +10444,29 @@ ${this.lastInitStack}` : "";
|
|
|
10446
10444
|
});
|
|
10447
10445
|
}
|
|
10448
10446
|
/**
|
|
10449
|
-
*
|
|
10450
|
-
*
|
|
10447
|
+
* Enforces the break-level playout limit (X-PLAYOUT-LIMIT): a budget shared
|
|
10448
|
+
* across all of the break's ads. When cumulative playout reaches it, the
|
|
10449
|
+
* whole break ends, dropping any ads that have not started.
|
|
10450
|
+
*
|
|
10451
|
+
* This is deliberately NOT a per-ad cap. Each ad asset is its own source and
|
|
10452
|
+
* ends on its media `ended` (see the 'ended' handler), which is what advances
|
|
10453
|
+
* a multi-ad break. Ending an ad at its declared per-ad DURATION instead would
|
|
10454
|
+
* truncate the real asset whenever a packager under-declares it — e.g. rounds
|
|
10455
|
+
* a 6.732s asset down to a whole-second 6 — cutting off the fractional tail.
|
|
10451
10456
|
*/
|
|
10452
10457
|
enforcePlayoutLimits(adState) {
|
|
10458
|
+
if (!adState.started) return;
|
|
10459
|
+
const { parentBreak } = adState;
|
|
10460
|
+
const limit = parentBreak.adBreak.playoutLimit;
|
|
10461
|
+
if (limit == null) return;
|
|
10453
10462
|
const pC = this.deps.playbackController;
|
|
10454
|
-
const
|
|
10455
|
-
if (
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
const limit = adBreak.playoutLimit;
|
|
10459
|
-
if (limit != null && parentBreak.playoutElapsed + elapsed >= limit) {
|
|
10460
|
-
parentBreak.clear();
|
|
10461
|
-
this.endAd(adState);
|
|
10462
|
-
} else if (ad.duration != null && elapsed >= ad.duration) {
|
|
10463
|
-
this.endAd(adState);
|
|
10464
|
-
}
|
|
10463
|
+
const elapsed = Math.max(0, pC.currentTime - adState.timeStart);
|
|
10464
|
+
if (parentBreak.playoutElapsed + elapsed < limit) return;
|
|
10465
|
+
if (isFinite(pC.duration) && pC.duration > 0 && pC.duration - pC.currentTime <= PLAYOUT_LIMIT_TOLERANCE) {
|
|
10466
|
+
return;
|
|
10465
10467
|
}
|
|
10468
|
+
parentBreak.clear();
|
|
10469
|
+
this.endAd(adState);
|
|
10466
10470
|
}
|
|
10467
10471
|
setPendingBreaks(newPending) {
|
|
10468
10472
|
this.pendingAdBreaks = newPending.filter(
|
|
@@ -14017,6 +14021,10 @@ ${this.lastInitStack}` : "";
|
|
|
14017
14021
|
__publicField(this, "bufferingTrackUri", null);
|
|
14018
14022
|
__publicField(this, "sessionAbort", null);
|
|
14019
14023
|
__publicField(this, "pendingMessageProps", null);
|
|
14024
|
+
// Armed per track load; latches on the first session so only the
|
|
14025
|
+
// critical-path session's EME/CDM setup is measured.
|
|
14026
|
+
__publicField(this, "keySetupSpanArmed", true);
|
|
14027
|
+
__publicField(this, "keySetupSpanStart", null);
|
|
14020
14028
|
__publicField(this, "disposer", createDisposer());
|
|
14021
14029
|
__publicField(this, "checkIsSupportedCached", memoize(
|
|
14022
14030
|
async (params) => {
|
|
@@ -14182,6 +14190,7 @@ ${this.lastInitStack}` : "";
|
|
|
14182
14190
|
if (!hasMimeType(drmInfo)) {
|
|
14183
14191
|
throw new DrmError("Encrypted content must have a mimeType.");
|
|
14184
14192
|
}
|
|
14193
|
+
this.markKeySetupStart();
|
|
14185
14194
|
const mediaKeys = await this.attachMediaKeys(drmInfo);
|
|
14186
14195
|
this.abortIfDisposed();
|
|
14187
14196
|
const keySystemOptions = (_a3 = this.options.keySystems[mediaKeys.keySystem]) != null ? _a3 : this.options.keySystems["*"];
|
|
@@ -14226,6 +14235,28 @@ ${this.lastInitStack}` : "";
|
|
|
14226
14235
|
});
|
|
14227
14236
|
return newSession;
|
|
14228
14237
|
}
|
|
14238
|
+
/**
|
|
14239
|
+
* Captures the key-setup span start at the top of the session-creation path
|
|
14240
|
+
* (before media keys are attached), latching so only the first session of
|
|
14241
|
+
* the current track load is measured.
|
|
14242
|
+
*/
|
|
14243
|
+
markKeySetupStart() {
|
|
14244
|
+
if (!this.keySetupSpanArmed) return;
|
|
14245
|
+
this.keySetupSpanArmed = false;
|
|
14246
|
+
this.keySetupSpanStart = Date.now();
|
|
14247
|
+
}
|
|
14248
|
+
dispatchLoadSpan(kind, startTime, endTime, messageProps) {
|
|
14249
|
+
this.dispatch("loadSpanMeasured", __spreadValues(__spreadValues({
|
|
14250
|
+
kind,
|
|
14251
|
+
startTime,
|
|
14252
|
+
endTime,
|
|
14253
|
+
mimeType: messageProps.mimeType
|
|
14254
|
+
}, messageProps.trackUri != null && {
|
|
14255
|
+
trackUri: messageProps.trackUri
|
|
14256
|
+
}), messageProps.contentType != null && {
|
|
14257
|
+
contentType: messageProps.contentType
|
|
14258
|
+
}));
|
|
14259
|
+
}
|
|
14229
14260
|
initializeForPlayback(drmInfo, options) {
|
|
14230
14261
|
var _a3;
|
|
14231
14262
|
logDebug(this, "initializeForPlayback", drmInfo);
|
|
@@ -14249,6 +14280,8 @@ ${this.lastInitStack}` : "";
|
|
|
14249
14280
|
this.drmInfo = drmInfo;
|
|
14250
14281
|
this.bufferingTrackUri = (_a3 = options == null ? void 0 : options.trackUri) != null ? _a3 : null;
|
|
14251
14282
|
this.sessionAbort = (_b = options == null ? void 0 : options.abort) != null ? _b : null;
|
|
14283
|
+
this.keySetupSpanArmed = true;
|
|
14284
|
+
this.keySetupSpanStart = null;
|
|
14252
14285
|
}
|
|
14253
14286
|
/**
|
|
14254
14287
|
* If the drm metadata contains initialization data for the current key system, create a new session.
|
|
@@ -14260,6 +14293,7 @@ ${this.lastInitStack}` : "";
|
|
|
14260
14293
|
this.handleError(new DrmError("DRM not supported."));
|
|
14261
14294
|
return;
|
|
14262
14295
|
}
|
|
14296
|
+
this.markKeySetupStart();
|
|
14263
14297
|
const mediaKeys = await this.attachMediaKeys(drmInfo);
|
|
14264
14298
|
const drmProtection = (_a3 = drmInfo.contentProtections.find((cP) => {
|
|
14265
14299
|
return cP.keySystem === mediaKeys.keySystem;
|
|
@@ -14398,7 +14432,9 @@ ${this.lastInitStack}` : "";
|
|
|
14398
14432
|
event,
|
|
14399
14433
|
// Captured per session, so a concurrent preloaded track's
|
|
14400
14434
|
// license exchange keeps its own attribution.
|
|
14401
|
-
trackUri
|
|
14435
|
+
trackUri,
|
|
14436
|
+
mimeType: drmInfo.mimeType,
|
|
14437
|
+
contentType: drmInfo.contentType
|
|
14402
14438
|
}),
|
|
14403
14439
|
LICENSE_TIMEOUT,
|
|
14404
14440
|
{ message: LICENSE_TIMEOUT_MESSAGE }
|
|
@@ -14420,6 +14456,15 @@ ${this.lastInitStack}` : "";
|
|
|
14420
14456
|
this.pendingMessageProps = messageProps;
|
|
14421
14457
|
const keySystem = mediaKeys.keySystem;
|
|
14422
14458
|
logDebug(this, "message", keySystem, event.message.byteLength);
|
|
14459
|
+
if (this.keySetupSpanStart != null) {
|
|
14460
|
+
this.dispatchLoadSpan(
|
|
14461
|
+
"keySetup",
|
|
14462
|
+
this.keySetupSpanStart,
|
|
14463
|
+
Date.now(),
|
|
14464
|
+
messageProps
|
|
14465
|
+
);
|
|
14466
|
+
this.keySetupSpanStart = null;
|
|
14467
|
+
}
|
|
14423
14468
|
const keySystemOptions = (_a3 = this.options.keySystems[keySystem]) != null ? _a3 : this.options.keySystems["*"];
|
|
14424
14469
|
const licenseServerOptionsProvider = keySystemOptions == null ? void 0 : keySystemOptions.licenseServer;
|
|
14425
14470
|
const licenseServerOptions = (_b = clone(await resolveValueProvider(licenseServerOptionsProvider))) != null ? _b : {};
|
|
@@ -14441,13 +14486,12 @@ ${this.lastInitStack}` : "";
|
|
|
14441
14486
|
challenge
|
|
14442
14487
|
);
|
|
14443
14488
|
if (this.disposed) return;
|
|
14444
|
-
this.
|
|
14445
|
-
|
|
14446
|
-
|
|
14447
|
-
|
|
14448
|
-
|
|
14449
|
-
|
|
14450
|
-
}));
|
|
14489
|
+
this.dispatchLoadSpan(
|
|
14490
|
+
"license",
|
|
14491
|
+
licenseSpanStart,
|
|
14492
|
+
Date.now(),
|
|
14493
|
+
messageProps
|
|
14494
|
+
);
|
|
14451
14495
|
this.pendingMessageProps = null;
|
|
14452
14496
|
if (session.disposed) return;
|
|
14453
14497
|
logDebug(this, "update");
|
|
@@ -15177,7 +15221,7 @@ ${this.lastInitStack}` : "";
|
|
|
15177
15221
|
// src/global/environment.ts
|
|
15178
15222
|
var vinylVersion = parseVersion(
|
|
15179
15223
|
/* istanbul ignore next */
|
|
15180
|
-
"3.1
|
|
15224
|
+
"3.2.1"
|
|
15181
15225
|
);
|
|
15182
15226
|
|
|
15183
15227
|
// src/global/globalRef.ts
|
|
@@ -15334,6 +15378,7 @@ ${this.lastInitStack}` : "";
|
|
|
15334
15378
|
];
|
|
15335
15379
|
|
|
15336
15380
|
// src/playback/createStallMonitor.ts
|
|
15381
|
+
var PROGRESS_EPSILON = 1e-6;
|
|
15337
15382
|
function createStallMonitor(controller, callbacks, options = {}) {
|
|
15338
15383
|
var _a3, _b;
|
|
15339
15384
|
const stallThresholdMs = ((_a3 = options.stallThreshold) != null ? _a3 : 1) * 1e3;
|
|
@@ -15342,6 +15387,7 @@ ${this.lastInitStack}` : "";
|
|
|
15342
15387
|
let lastProgressAt = Date.now();
|
|
15343
15388
|
let inStall = false;
|
|
15344
15389
|
let pollId = null;
|
|
15390
|
+
const { add, dispose } = createDisposer();
|
|
15345
15391
|
const endStall = (reason) => {
|
|
15346
15392
|
if (!inStall) return;
|
|
15347
15393
|
inStall = false;
|
|
@@ -15358,6 +15404,7 @@ ${this.lastInitStack}` : "";
|
|
|
15358
15404
|
clearInterval(pollId);
|
|
15359
15405
|
pollId = null;
|
|
15360
15406
|
};
|
|
15407
|
+
add(stopPoll);
|
|
15361
15408
|
const startPoll = () => {
|
|
15362
15409
|
if (pollId != null) return;
|
|
15363
15410
|
pollId = setInterval(() => {
|
|
@@ -15373,17 +15420,18 @@ ${this.lastInitStack}` : "";
|
|
|
15373
15420
|
observedPlaying = false;
|
|
15374
15421
|
stopPoll();
|
|
15375
15422
|
};
|
|
15376
|
-
const { add, dispose } = createDisposer();
|
|
15377
15423
|
add(
|
|
15378
15424
|
controller.on("playing", () => {
|
|
15379
15425
|
if (inStall) endStall("playing" /* PLAYING */);
|
|
15380
|
-
|
|
15426
|
+
lastProgressAt = Date.now();
|
|
15381
15427
|
observedPlaying = true;
|
|
15382
15428
|
startPoll();
|
|
15383
15429
|
})
|
|
15384
15430
|
);
|
|
15385
15431
|
add(
|
|
15386
|
-
controller.on("timeUpdate", () => {
|
|
15432
|
+
controller.on("timeUpdate", ({ previous, current }) => {
|
|
15433
|
+
if (previous != null && current - previous <= PROGRESS_EPSILON)
|
|
15434
|
+
return;
|
|
15387
15435
|
if (inStall) endStall("playing" /* PLAYING */);
|
|
15388
15436
|
lastProgressAt = Date.now();
|
|
15389
15437
|
})
|
|
@@ -15391,13 +15439,7 @@ ${this.lastInitStack}` : "";
|
|
|
15391
15439
|
add(controller.on("pause", () => suspend("pause" /* PAUSE */)));
|
|
15392
15440
|
add(controller.on("seeking", () => suspend("seeking" /* SEEKING */)));
|
|
15393
15441
|
add(controller.on("emptied", () => suspend("emptied" /* EMPTIED */)));
|
|
15394
|
-
|
|
15395
|
-
return () => {
|
|
15396
|
-
if (stopped) return;
|
|
15397
|
-
stopped = true;
|
|
15398
|
-
stopPoll();
|
|
15399
|
-
dispose();
|
|
15400
|
-
};
|
|
15442
|
+
return dispose;
|
|
15401
15443
|
}
|
|
15402
15444
|
|
|
15403
15445
|
// src/playback/error/InvalidSeekError.ts
|
|
@@ -17743,6 +17785,86 @@ ${this.lastInitStack}` : "";
|
|
|
17743
17785
|
return filtered;
|
|
17744
17786
|
}
|
|
17745
17787
|
|
|
17788
|
+
// src/streaming/DescriptorRecord.ts
|
|
17789
|
+
function getDescriptorValue(record2, uri, id) {
|
|
17790
|
+
var _a3, _b;
|
|
17791
|
+
let arr = record2[uri];
|
|
17792
|
+
if (!arr) return null;
|
|
17793
|
+
if (id !== void 0) arr = arr.filter((descriptor) => descriptor.id === id);
|
|
17794
|
+
return (_b = (_a3 = first(arr)) == null ? void 0 : _a3.value) != null ? _b : null;
|
|
17795
|
+
}
|
|
17796
|
+
var descriptorValidator = object({
|
|
17797
|
+
id: string().maybe().optional(),
|
|
17798
|
+
value: string().maybe().optional()
|
|
17799
|
+
});
|
|
17800
|
+
var descriptorRecordValidator = record(string(), array(descriptorValidator).readonly().orUndefined());
|
|
17801
|
+
|
|
17802
|
+
// src/streaming/MediaQualityMetadata.ts
|
|
17803
|
+
var ALL_CONTENT_TYPES = [
|
|
17804
|
+
"video",
|
|
17805
|
+
"audio",
|
|
17806
|
+
"text"
|
|
17807
|
+
];
|
|
17808
|
+
var RESTRICTABLE_CONTENT_TYPES = [
|
|
17809
|
+
"video",
|
|
17810
|
+
"audio"
|
|
17811
|
+
];
|
|
17812
|
+
var drmProtectionValidator = descriptorValidator.extend({
|
|
17813
|
+
keySystem: enumOf(DrmKeySystem),
|
|
17814
|
+
pro: string().maybe().optional(),
|
|
17815
|
+
pssh: string().maybe().optional()
|
|
17816
|
+
});
|
|
17817
|
+
var mediaFormatMetadataValidator = object({
|
|
17818
|
+
contentProtections: array(drmProtectionValidator).readonly(),
|
|
17819
|
+
contentType: isOneOf("audio", "video", "text").orNull(),
|
|
17820
|
+
initDataType: string().orNull(),
|
|
17821
|
+
encryptionScheme: string().orNull(),
|
|
17822
|
+
mimeType: string().orNull()
|
|
17823
|
+
});
|
|
17824
|
+
function createEmptyMediaFormatMetadata() {
|
|
17825
|
+
return {
|
|
17826
|
+
contentType: null,
|
|
17827
|
+
mimeType: null,
|
|
17828
|
+
initDataType: null,
|
|
17829
|
+
encryptionScheme: null,
|
|
17830
|
+
contentProtections: []
|
|
17831
|
+
};
|
|
17832
|
+
}
|
|
17833
|
+
function createEmptyMediaQualityMetadata() {
|
|
17834
|
+
return __spreadProps(__spreadValues({}, createEmptyMediaFormatMetadata()), {
|
|
17835
|
+
decoderId: createShortUid(),
|
|
17836
|
+
qualityId: createShortUid(),
|
|
17837
|
+
codecs: null,
|
|
17838
|
+
bandwidth: null,
|
|
17839
|
+
bandwidthTotal: null,
|
|
17840
|
+
supplementalProperties: {},
|
|
17841
|
+
audioSamplingRate: null,
|
|
17842
|
+
frameRate: null,
|
|
17843
|
+
height: null,
|
|
17844
|
+
width: null,
|
|
17845
|
+
lang: null,
|
|
17846
|
+
characteristics: [],
|
|
17847
|
+
groupId: createShortUid(),
|
|
17848
|
+
switchingGroupIds: null
|
|
17849
|
+
});
|
|
17850
|
+
}
|
|
17851
|
+
var mediaQualityMetadataValidator = mediaFormatMetadataValidator.extend({
|
|
17852
|
+
audioSamplingRate: array(number()).readonly().orNull(),
|
|
17853
|
+
bandwidth: number().orNull(),
|
|
17854
|
+
bandwidthTotal: number().orNull(),
|
|
17855
|
+
codecs: string().orNull(),
|
|
17856
|
+
decoderId: string(),
|
|
17857
|
+
frameRate: tuple(number(), number()).readonly().orNull(),
|
|
17858
|
+
height: number().orNull(),
|
|
17859
|
+
qualityId: string(),
|
|
17860
|
+
supplementalProperties: descriptorRecordValidator,
|
|
17861
|
+
width: number().orNull(),
|
|
17862
|
+
lang: string().orNull(),
|
|
17863
|
+
characteristics: array(string()).readonly(),
|
|
17864
|
+
groupId: string(),
|
|
17865
|
+
switchingGroupIds: array(string()).readonly().orNull()
|
|
17866
|
+
});
|
|
17867
|
+
|
|
17746
17868
|
// src/track/filters/languageFilter.ts
|
|
17747
17869
|
function throwLanguagesUnsupported() {
|
|
17748
17870
|
throw new MediaUnsupportedError(
|
|
@@ -18024,6 +18146,13 @@ ${this.lastInitStack}` : "";
|
|
|
18024
18146
|
}
|
|
18025
18147
|
|
|
18026
18148
|
// src/streaming/mediaTimelineFilter.ts
|
|
18149
|
+
function assertMediaStreamsRetained(original, filtered, throwError) {
|
|
18150
|
+
for (const contentType2 of RESTRICTABLE_CONTENT_TYPES) {
|
|
18151
|
+
if (original.some((q) => q.metadata.contentType === contentType2) && !filtered.some((q) => q.metadata.contentType === contentType2)) {
|
|
18152
|
+
throwError();
|
|
18153
|
+
}
|
|
18154
|
+
}
|
|
18155
|
+
}
|
|
18027
18156
|
function filterTimelineQualities(filter, throwError, timeline) {
|
|
18028
18157
|
if (!filter) return timeline;
|
|
18029
18158
|
const mapMetadataArray = memoize(
|
|
@@ -18035,11 +18164,9 @@ ${this.lastInitStack}` : "";
|
|
|
18035
18164
|
const filtered = period.qualities.filter(
|
|
18036
18165
|
(q, index) => filter(q.metadata, index, mapMetadataArray(period.qualities))
|
|
18037
18166
|
);
|
|
18167
|
+
assertMediaStreamsRetained(period.qualities, filtered, throwError);
|
|
18038
18168
|
return __spreadProps(__spreadValues({}, period), { qualities: filtered });
|
|
18039
18169
|
});
|
|
18040
|
-
if (periods.some((p) => p.qualities.length === 0)) {
|
|
18041
|
-
throwError();
|
|
18042
|
-
}
|
|
18043
18170
|
return __spreadProps(__spreadValues({}, timeline), { periods });
|
|
18044
18171
|
}
|
|
18045
18172
|
async function filterTimelineQualitiesAsync(filter, throwError, timeline) {
|
|
@@ -18057,11 +18184,9 @@ ${this.lastInitStack}` : "";
|
|
|
18057
18184
|
filtered.push(period.qualities[i]);
|
|
18058
18185
|
}
|
|
18059
18186
|
}
|
|
18187
|
+
assertMediaStreamsRetained(period.qualities, filtered, throwError);
|
|
18060
18188
|
periods.push(__spreadProps(__spreadValues({}, period), { qualities: filtered }));
|
|
18061
18189
|
}
|
|
18062
|
-
if (periods.some((p) => p.qualities.length === 0)) {
|
|
18063
|
-
throwError();
|
|
18064
|
-
}
|
|
18065
18190
|
return __spreadProps(__spreadValues({}, timeline), { periods });
|
|
18066
18191
|
}
|
|
18067
18192
|
|
|
@@ -18135,23 +18260,28 @@ ${this.lastInitStack}` : "";
|
|
|
18135
18260
|
if (metadata.contentType !== "audio") return true;
|
|
18136
18261
|
const samplingRate = last(metadata.audioSamplingRate);
|
|
18137
18262
|
if (!samplingRate) return true;
|
|
18263
|
+
const { minSampleRate } = options;
|
|
18264
|
+
const maxSampleRate = (_a3 = options.maxSampleRate) != null ? _a3 : options.capabilities.sampleRate;
|
|
18138
18265
|
const audioRates = () => map(
|
|
18139
18266
|
array2,
|
|
18140
18267
|
(item) => item.contentType === "audio" ? last(item.audioSamplingRate) : void 0
|
|
18141
18268
|
).filter((rate) => rate != null);
|
|
18142
|
-
const
|
|
18269
|
+
const outOfBandFallback = () => {
|
|
18270
|
+
const rates = audioRates();
|
|
18271
|
+
const inBand = (rate) => (minSampleRate == null || rate >= minSampleRate) && (!maxSampleRate || rate <= maxSampleRate);
|
|
18272
|
+
if (rates.some(inBand)) return void 0;
|
|
18273
|
+
const decodable = maxSampleRate ? rates.filter((rate) => rate <= maxSampleRate) : rates;
|
|
18274
|
+
return decodable.length ? max(decodable) : min(rates);
|
|
18275
|
+
};
|
|
18143
18276
|
if (minSampleRate != null && samplingRate < minSampleRate) {
|
|
18144
|
-
|
|
18145
|
-
return every(rates2, (rate) => rate < minSampleRate) && samplingRate === max(rates2);
|
|
18277
|
+
return samplingRate === outOfBandFallback();
|
|
18146
18278
|
}
|
|
18147
|
-
const maxSampleRate = (_a3 = options.maxSampleRate) != null ? _a3 : options.capabilities.sampleRate;
|
|
18148
18279
|
if (!maxSampleRate) return true;
|
|
18149
18280
|
if (hasBrowser("Firefox" /* FIREFOX */)) {
|
|
18150
18281
|
return samplingRate <= 48e3;
|
|
18151
18282
|
}
|
|
18152
18283
|
if (samplingRate <= maxSampleRate) return true;
|
|
18153
|
-
|
|
18154
|
-
return every(rates, (rate) => rate > maxSampleRate) && samplingRate === min(rates);
|
|
18284
|
+
return samplingRate === outOfBandFallback();
|
|
18155
18285
|
}
|
|
18156
18286
|
|
|
18157
18287
|
// src/util/media/codecOverrides.ts
|
|
@@ -18238,20 +18368,6 @@ ${this.lastInitStack}` : "";
|
|
|
18238
18368
|
});
|
|
18239
18369
|
}
|
|
18240
18370
|
|
|
18241
|
-
// src/streaming/DescriptorRecord.ts
|
|
18242
|
-
function getDescriptorValue(record2, uri, id) {
|
|
18243
|
-
var _a3, _b;
|
|
18244
|
-
let arr = record2[uri];
|
|
18245
|
-
if (!arr) return null;
|
|
18246
|
-
if (id !== void 0) arr = arr.filter((descriptor) => descriptor.id === id);
|
|
18247
|
-
return (_b = (_a3 = first(arr)) == null ? void 0 : _a3.value) != null ? _b : null;
|
|
18248
|
-
}
|
|
18249
|
-
var descriptorValidator = object({
|
|
18250
|
-
id: string().maybe().optional(),
|
|
18251
|
-
value: string().maybe().optional()
|
|
18252
|
-
});
|
|
18253
|
-
var descriptorRecordValidator = record(string(), array(descriptorValidator).readonly().orUndefined());
|
|
18254
|
-
|
|
18255
18371
|
// src/streaming/ManifestControllerImpl.ts
|
|
18256
18372
|
var ManifestControllerImpl = class {
|
|
18257
18373
|
constructor(deps) {
|
|
@@ -18351,72 +18467,6 @@ ${this.lastInitStack}` : "";
|
|
|
18351
18467
|
}
|
|
18352
18468
|
};
|
|
18353
18469
|
|
|
18354
|
-
// src/streaming/MediaQualityMetadata.ts
|
|
18355
|
-
var ALL_CONTENT_TYPES = [
|
|
18356
|
-
"video",
|
|
18357
|
-
"audio",
|
|
18358
|
-
"text"
|
|
18359
|
-
];
|
|
18360
|
-
var RESTRICTABLE_CONTENT_TYPES = [
|
|
18361
|
-
"video",
|
|
18362
|
-
"audio"
|
|
18363
|
-
];
|
|
18364
|
-
var drmProtectionValidator = descriptorValidator.extend({
|
|
18365
|
-
keySystem: enumOf(DrmKeySystem),
|
|
18366
|
-
pro: string().maybe().optional(),
|
|
18367
|
-
pssh: string().maybe().optional()
|
|
18368
|
-
});
|
|
18369
|
-
var mediaFormatMetadataValidator = object({
|
|
18370
|
-
contentProtections: array(drmProtectionValidator).readonly(),
|
|
18371
|
-
contentType: isOneOf("audio", "video", "text").orNull(),
|
|
18372
|
-
initDataType: string().orNull(),
|
|
18373
|
-
encryptionScheme: string().orNull(),
|
|
18374
|
-
mimeType: string().orNull()
|
|
18375
|
-
});
|
|
18376
|
-
function createEmptyMediaFormatMetadata() {
|
|
18377
|
-
return {
|
|
18378
|
-
contentType: null,
|
|
18379
|
-
mimeType: null,
|
|
18380
|
-
initDataType: null,
|
|
18381
|
-
encryptionScheme: null,
|
|
18382
|
-
contentProtections: []
|
|
18383
|
-
};
|
|
18384
|
-
}
|
|
18385
|
-
function createEmptyMediaQualityMetadata() {
|
|
18386
|
-
return __spreadProps(__spreadValues({}, createEmptyMediaFormatMetadata()), {
|
|
18387
|
-
decoderId: createShortUid(),
|
|
18388
|
-
qualityId: createShortUid(),
|
|
18389
|
-
codecs: null,
|
|
18390
|
-
bandwidth: null,
|
|
18391
|
-
bandwidthTotal: null,
|
|
18392
|
-
supplementalProperties: {},
|
|
18393
|
-
audioSamplingRate: null,
|
|
18394
|
-
frameRate: null,
|
|
18395
|
-
height: null,
|
|
18396
|
-
width: null,
|
|
18397
|
-
lang: null,
|
|
18398
|
-
characteristics: [],
|
|
18399
|
-
groupId: createShortUid(),
|
|
18400
|
-
switchingGroupIds: null
|
|
18401
|
-
});
|
|
18402
|
-
}
|
|
18403
|
-
var mediaQualityMetadataValidator = mediaFormatMetadataValidator.extend({
|
|
18404
|
-
audioSamplingRate: array(number()).readonly().orNull(),
|
|
18405
|
-
bandwidth: number().orNull(),
|
|
18406
|
-
bandwidthTotal: number().orNull(),
|
|
18407
|
-
codecs: string().orNull(),
|
|
18408
|
-
decoderId: string(),
|
|
18409
|
-
frameRate: tuple(number(), number()).readonly().orNull(),
|
|
18410
|
-
height: number().orNull(),
|
|
18411
|
-
qualityId: string(),
|
|
18412
|
-
supplementalProperties: descriptorRecordValidator,
|
|
18413
|
-
width: number().orNull(),
|
|
18414
|
-
lang: string().orNull(),
|
|
18415
|
-
characteristics: array(string()).readonly(),
|
|
18416
|
-
groupId: string(),
|
|
18417
|
-
switchingGroupIds: array(string()).readonly().orNull()
|
|
18418
|
-
});
|
|
18419
|
-
|
|
18420
18470
|
// src/streaming/MediaTimeline.ts
|
|
18421
18471
|
function getMediaPeriodAtTime(timeline, time) {
|
|
18422
18472
|
const index = sortedInsertionIndex(timeline.periods, time, (time2, period2) => {
|
|
@@ -20529,10 +20579,7 @@ ${this.lastInitStack}` : "";
|
|
|
20529
20579
|
this.deps = deps;
|
|
20530
20580
|
add(
|
|
20531
20581
|
deps.contentTypesValue.onData((contentTypesPromise) => {
|
|
20532
|
-
contentTypesPromise.then((value) =>
|
|
20533
|
-
this.setContentTypes(value);
|
|
20534
|
-
this.validateRequiredContentTypesPlayable();
|
|
20535
|
-
}).catch(this.errorHandler);
|
|
20582
|
+
contentTypesPromise.then((value) => this.setContentTypes(value)).catch(this.errorHandler);
|
|
20536
20583
|
})
|
|
20537
20584
|
);
|
|
20538
20585
|
this.on("streamingQualityChange", (event) => {
|
|
@@ -20633,33 +20680,8 @@ ${this.lastInitStack}` : "";
|
|
|
20633
20680
|
});
|
|
20634
20681
|
}
|
|
20635
20682
|
}
|
|
20636
|
-
this.validateRequiredContentTypesPlayable();
|
|
20637
20683
|
});
|
|
20638
20684
|
}
|
|
20639
|
-
/**
|
|
20640
|
-
* Fails fast when a media content type the manifest declared (audio/video)
|
|
20641
|
-
* has no playable qualities left after timeline filtering. Its stream can
|
|
20642
|
-
* never create a source buffer, so without this the track waits forever for
|
|
20643
|
-
* a source buffer that never arrives, surfacing only as an opaque
|
|
20644
|
-
* `readyToAppend` timeout. Runs once both the required content types and the
|
|
20645
|
-
* filtered timeline qualities are known (either order).
|
|
20646
|
-
*/
|
|
20647
|
-
validateRequiredContentTypesPlayable() {
|
|
20648
|
-
const qualities = this._qualities;
|
|
20649
|
-
if (!qualities) return;
|
|
20650
|
-
const present = new Set(qualities.map((q) => q.contentType));
|
|
20651
|
-
for (const contentType2 of this._contentTypes) {
|
|
20652
|
-
if (!present.has(contentType2)) {
|
|
20653
|
-
this.errorHandler(
|
|
20654
|
-
new MediaUnsupportedError(
|
|
20655
|
-
`No playable ${contentType2} rendition`,
|
|
20656
|
-
`no-playable-${contentType2}`
|
|
20657
|
-
)
|
|
20658
|
-
);
|
|
20659
|
-
return;
|
|
20660
|
-
}
|
|
20661
|
-
}
|
|
20662
|
-
}
|
|
20663
20685
|
async getAds() {
|
|
20664
20686
|
const timeline = await this.deps.mediaTimeline.value;
|
|
20665
20687
|
return {
|
|
@@ -24081,6 +24103,7 @@ ${this.lastInitStack}` : "";
|
|
|
24081
24103
|
playerRegistryRef.value.addPlayer(this);
|
|
24082
24104
|
this.redispatchSubControllerEvents();
|
|
24083
24105
|
this.redispatchCurrentTrackEvents();
|
|
24106
|
+
this.initializeLoadSpanLogging();
|
|
24084
24107
|
this.initializeAutoResetHandling();
|
|
24085
24108
|
}
|
|
24086
24109
|
get [Symbol.toStringTag]() {
|
|
@@ -24098,6 +24121,26 @@ ${this.lastInitStack}` : "";
|
|
|
24098
24121
|
get autoResetController() {
|
|
24099
24122
|
return this.deps.autoResetController;
|
|
24100
24123
|
}
|
|
24124
|
+
/**
|
|
24125
|
+
* Logs each republished load span once at the verbose level, formatting its
|
|
24126
|
+
* bounds as ISO date strings and its duration in milliseconds.
|
|
24127
|
+
*/
|
|
24128
|
+
initializeLoadSpanLogging() {
|
|
24129
|
+
this.disposer.add(
|
|
24130
|
+
this.on("loadSpan", (span) => {
|
|
24131
|
+
if (getLogLevel() > 0 /* VERBOSE */) return;
|
|
24132
|
+
logVerbose(this, "loadSpan", {
|
|
24133
|
+
kind: span.kind,
|
|
24134
|
+
trackUri: span.trackUri,
|
|
24135
|
+
mimeType: span.mimeType,
|
|
24136
|
+
contentType: span.contentType,
|
|
24137
|
+
startTime: new Date(span.startTime).toISOString(),
|
|
24138
|
+
endTime: new Date(span.endTime).toISOString(),
|
|
24139
|
+
durationMs: (span.endTime - span.startTime).toFixed(2)
|
|
24140
|
+
});
|
|
24141
|
+
})
|
|
24142
|
+
);
|
|
24143
|
+
}
|
|
24101
24144
|
redispatchSubControllerEvents() {
|
|
24102
24145
|
const add = this.disposer.add;
|
|
24103
24146
|
add(
|
|
@@ -24913,7 +24956,8 @@ ${this.lastInitStack}` : "";
|
|
|
24913
24956
|
var vinylTestAssets = {
|
|
24914
24957
|
prog: {
|
|
24915
24958
|
libmp3lame_60s_2ch_16bit_44100Hz_48kbps: `https://assets.dev.vinyl.music.amazon.dev/prog/libmp3lame_60s_2ch_16bit_44100Hz_48kbps.mp3`,
|
|
24916
|
-
libmp3lame_1800s_2ch_16bit_44100Hz_48kbps: `https://assets.dev.vinyl.music.amazon.dev/prog/libmp3lame_1800s_2ch_16bit_44100Hz_48kbps.mp3
|
|
24959
|
+
libmp3lame_1800s_2ch_16bit_44100Hz_48kbps: `https://assets.dev.vinyl.music.amazon.dev/prog/libmp3lame_1800s_2ch_16bit_44100Hz_48kbps.mp3`,
|
|
24960
|
+
libmp3lame_1s_2ch_16bit_44100Hz_48kbps: `https://assets.dev.vinyl.music.amazon.dev/prog/libmp3lame_1s_2ch_16bit_44100Hz_48kbps.mp3`
|
|
24917
24961
|
},
|
|
24918
24962
|
dash: {
|
|
24919
24963
|
live_static_aac_opus_flac_60s_segmentTemplate: `https://assets.dev.vinyl.music.amazon.dev/dash/live_static_aac_opus_flac_60s_segmentTemplate/manifest.mpd`,
|
|
@@ -24928,6 +24972,10 @@ ${this.lastInitStack}` : "";
|
|
|
24928
24972
|
live_static_aac_opus_flac_60s_segmentBase_widevine_cl10: `https://assets.dev.vinyl.music.amazon.dev/dash/live_static_aac_opus_flac_60s_segmentBase_widevine_cl10/manifest.mpd`,
|
|
24929
24973
|
live_static_aac_opus_flac_60s_segmentBase_widevine: `https://assets.dev.vinyl.music.amazon.dev/dash/live_static_aac_opus_flac_60s_segmentBase_widevine/manifest.mpd`,
|
|
24930
24974
|
live_static_aac_1800s_segmentBase_widevine_cl30: `https://assets.dev.vinyl.music.amazon.dev/dash/live_static_aac_1800s_segmentBase_widevine_cl30/manifest.mpd`,
|
|
24975
|
+
// Edge-case tracks: ultra-short 1s assets (audio-only + video), no
|
|
24976
|
+
// captions or translations.
|
|
24977
|
+
edge_static_aac_1s_segmentTemplate: `https://assets.dev.vinyl.music.amazon.dev/dash/edge_static_aac_1s_segmentTemplate/manifest.mpd`,
|
|
24978
|
+
edge_static_video_1s_segmentTemplate: `https://assets.dev.vinyl.music.amazon.dev/dash/edge_static_video_1s_segmentTemplate/manifest.mpd`,
|
|
24931
24979
|
// Ad-break assets (see DMTestAssetBuilder src/adBreaks): a ~3-min
|
|
24932
24980
|
// labeled ABR ladder with five-language narration, full + forced
|
|
24933
24981
|
// captions. Base content only — DASH ad-break scenarios are not
|
|
@@ -24947,6 +24995,10 @@ ${this.lastInitStack}` : "";
|
|
|
24947
24995
|
live_static_video_60s_4s_mpegts: `https://assets.dev.vinyl.music.amazon.dev/hls/live_static_video_60s_4s_mpegts/main.m3u8`,
|
|
24948
24996
|
live_static_video_audio_60s_2s_mpegts: `https://assets.dev.vinyl.music.amazon.dev/hls/live_static_video_audio_60s_2s_mpegts/main.m3u8`,
|
|
24949
24997
|
live_static_video_audio_60s_4s_mpegts: `https://assets.dev.vinyl.music.amazon.dev/hls/live_static_video_audio_60s_4s_mpegts/main.m3u8`,
|
|
24998
|
+
// Edge-case tracks: ultra-short 1s assets (audio-only + video), no
|
|
24999
|
+
// captions or translations.
|
|
25000
|
+
edge_static_aac_1s: `https://assets.dev.vinyl.music.amazon.dev/hls/edge_static_aac_1s/main.m3u8`,
|
|
25001
|
+
edge_static_video_1s: `https://assets.dev.vinyl.music.amazon.dev/hls/edge_static_video_1s/main.m3u8`,
|
|
24950
25002
|
// Ad-break assets: base content + HLS Interstitial scenario variants.
|
|
24951
25003
|
// `*_av` = video+audio, `*_audio` = audio-only; each `__<scenario>`
|
|
24952
25004
|
// injects a different ad-break configuration. See DMTestAssetBuilder
|
|
@@ -27707,7 +27759,6 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
27707
27759
|
makeBreak({
|
|
27708
27760
|
startTime: 10,
|
|
27709
27761
|
duration: 20,
|
|
27710
|
-
// Null per-ad duration so the per-ad cap does not end it.
|
|
27711
27762
|
ads: [
|
|
27712
27763
|
{
|
|
27713
27764
|
id: "a1",
|
|
@@ -27811,8 +27862,8 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
27811
27862
|
});
|
|
27812
27863
|
});
|
|
27813
27864
|
describe("ad duration and playout limits", () => {
|
|
27814
|
-
it("advances to the next ad
|
|
27815
|
-
var _a3, _b;
|
|
27865
|
+
it("advances to the next ad on the ad media ended, not its declared duration", async () => {
|
|
27866
|
+
var _a3, _b, _c;
|
|
27816
27867
|
const c = createController();
|
|
27817
27868
|
await setContent(
|
|
27818
27869
|
c,
|
|
@@ -27825,6 +27876,7 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
27825
27876
|
id: "a1",
|
|
27826
27877
|
startTime: 0,
|
|
27827
27878
|
duration: 5,
|
|
27879
|
+
// declared, server-rounded
|
|
27828
27880
|
uri: "ad1.m3u8"
|
|
27829
27881
|
},
|
|
27830
27882
|
{
|
|
@@ -27843,7 +27895,51 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
27843
27895
|
playbackController.dispatch("playing", {});
|
|
27844
27896
|
updateTime(6);
|
|
27845
27897
|
await flush();
|
|
27846
|
-
expect((_b = c.currentAd) == null ? void 0 : _b.id).toBe("
|
|
27898
|
+
expect((_b = c.currentAd) == null ? void 0 : _b.id).toBe("a1");
|
|
27899
|
+
playbackController.dispatch("ended", {
|
|
27900
|
+
previous: false,
|
|
27901
|
+
current: true
|
|
27902
|
+
});
|
|
27903
|
+
await flush();
|
|
27904
|
+
expect((_c = c.currentAd) == null ? void 0 : _c.id).toBe("a2");
|
|
27905
|
+
});
|
|
27906
|
+
it("does not cut the final/only ad at its declared duration; it ends on media ended", async () => {
|
|
27907
|
+
var _a3, _b;
|
|
27908
|
+
const c = createController();
|
|
27909
|
+
const ended = [];
|
|
27910
|
+
c.on("adEnded", (e) => ended.push(e.ad.id));
|
|
27911
|
+
await setContent(
|
|
27912
|
+
c,
|
|
27913
|
+
trackAds(
|
|
27914
|
+
makeBreak({
|
|
27915
|
+
startTime: 0,
|
|
27916
|
+
duration: 30,
|
|
27917
|
+
ads: [
|
|
27918
|
+
{
|
|
27919
|
+
id: "a1",
|
|
27920
|
+
startTime: 0,
|
|
27921
|
+
duration: 6,
|
|
27922
|
+
// declared, server-rounded
|
|
27923
|
+
uri: "ad1.m3u8"
|
|
27924
|
+
}
|
|
27925
|
+
]
|
|
27926
|
+
})
|
|
27927
|
+
)
|
|
27928
|
+
);
|
|
27929
|
+
updateTime(0);
|
|
27930
|
+
await flush();
|
|
27931
|
+
expect((_a3 = c.currentAd) == null ? void 0 : _a3.id).toBe("a1");
|
|
27932
|
+
playbackController.dispatch("playing", {});
|
|
27933
|
+
updateTime(6.5);
|
|
27934
|
+
await flush();
|
|
27935
|
+
expect((_b = c.currentAd) == null ? void 0 : _b.id).toBe("a1");
|
|
27936
|
+
expect(ended).toEqual([]);
|
|
27937
|
+
playbackController.dispatch("ended", {
|
|
27938
|
+
previous: false,
|
|
27939
|
+
current: true
|
|
27940
|
+
});
|
|
27941
|
+
expect(ended).toEqual(["a1"]);
|
|
27942
|
+
expect(c.currentAdBreak).toBeNull();
|
|
27847
27943
|
});
|
|
27848
27944
|
it("ends the whole break when the playout limit is reached, dropping remaining ads", async () => {
|
|
27849
27945
|
var _a3;
|
|
@@ -27884,6 +27980,70 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
27884
27980
|
expect(c.currentAdBreak).toBeNull();
|
|
27885
27981
|
expect(resumes.at(-1)).toBe(0);
|
|
27886
27982
|
});
|
|
27983
|
+
it("does not cut at the playout limit when the ad is within tolerance of its media end", async () => {
|
|
27984
|
+
var _a3;
|
|
27985
|
+
const c = createController();
|
|
27986
|
+
await setContent(
|
|
27987
|
+
c,
|
|
27988
|
+
trackAds(
|
|
27989
|
+
makeBreak({
|
|
27990
|
+
startTime: 0,
|
|
27991
|
+
duration: null,
|
|
27992
|
+
playoutLimit: 8,
|
|
27993
|
+
ads: [
|
|
27994
|
+
{
|
|
27995
|
+
id: "a1",
|
|
27996
|
+
startTime: 0,
|
|
27997
|
+
duration: 20,
|
|
27998
|
+
uri: "ad1.m3u8"
|
|
27999
|
+
}
|
|
28000
|
+
]
|
|
28001
|
+
})
|
|
28002
|
+
)
|
|
28003
|
+
);
|
|
28004
|
+
updateTime(0);
|
|
28005
|
+
await flush();
|
|
28006
|
+
playbackController.dispatch("playing", {});
|
|
28007
|
+
playbackController.duration = 8.5;
|
|
28008
|
+
updateTime(8);
|
|
28009
|
+
await flush();
|
|
28010
|
+
expect(c.currentAdBreak).not.toBeNull();
|
|
28011
|
+
expect((_a3 = c.currentAd) == null ? void 0 : _a3.id).toBe("a1");
|
|
28012
|
+
playbackController.dispatch("ended", {
|
|
28013
|
+
previous: false,
|
|
28014
|
+
current: true
|
|
28015
|
+
});
|
|
28016
|
+
await flush();
|
|
28017
|
+
expect(c.currentAdBreak).toBeNull();
|
|
28018
|
+
});
|
|
28019
|
+
it("cuts at the playout limit when the ad has more than the tolerance of media left", async () => {
|
|
28020
|
+
const c = createController();
|
|
28021
|
+
await setContent(
|
|
28022
|
+
c,
|
|
28023
|
+
trackAds(
|
|
28024
|
+
makeBreak({
|
|
28025
|
+
startTime: 0,
|
|
28026
|
+
duration: null,
|
|
28027
|
+
playoutLimit: 8,
|
|
28028
|
+
ads: [
|
|
28029
|
+
{
|
|
28030
|
+
id: "a1",
|
|
28031
|
+
startTime: 0,
|
|
28032
|
+
duration: 20,
|
|
28033
|
+
uri: "ad1.m3u8"
|
|
28034
|
+
}
|
|
28035
|
+
]
|
|
28036
|
+
})
|
|
28037
|
+
)
|
|
28038
|
+
);
|
|
28039
|
+
updateTime(0);
|
|
28040
|
+
await flush();
|
|
28041
|
+
playbackController.dispatch("playing", {});
|
|
28042
|
+
playbackController.duration = 20;
|
|
28043
|
+
updateTime(8);
|
|
28044
|
+
await flush();
|
|
28045
|
+
expect(c.currentAdBreak).toBeNull();
|
|
28046
|
+
});
|
|
27887
28047
|
});
|
|
27888
28048
|
describe("CUE=ONCE and replay", () => {
|
|
27889
28049
|
it("replays a non-ONCE break after the playhead seeks back before it", async () => {
|
|
@@ -31554,7 +31714,7 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
31554
31714
|
drmController.setBufferingDrmInfo(drmInfo);
|
|
31555
31715
|
await emitEncrypted(new Uint8Array([1, 2, 3]), "cenc");
|
|
31556
31716
|
await emitMessage(0, new ArrayBuffer(1));
|
|
31557
|
-
expect(spy).
|
|
31717
|
+
expect(spy).toHaveBeenCalledWith(
|
|
31558
31718
|
objectContaining2({
|
|
31559
31719
|
kind: "license",
|
|
31560
31720
|
startTime: any4(Number),
|
|
@@ -31562,22 +31722,65 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
31562
31722
|
})
|
|
31563
31723
|
);
|
|
31564
31724
|
});
|
|
31725
|
+
it("measures the key setup, contiguous with and before the license", async () => {
|
|
31726
|
+
licenseProvider.and.resolveTo(new Uint8Array([1]).buffer);
|
|
31727
|
+
const spy = createEventSpy(drmController, "loadSpanMeasured");
|
|
31728
|
+
drmController.setBufferingDrmInfo(drmInfo, { trackUri: "track-a" });
|
|
31729
|
+
await emitEncrypted(new Uint8Array([1, 2, 3]), "cenc");
|
|
31730
|
+
await emitMessage(0, new ArrayBuffer(1));
|
|
31731
|
+
const spans = spy.calls.all().map((call) => call.args[0]);
|
|
31732
|
+
expect(spans.map((span) => span.kind)).toEqual([
|
|
31733
|
+
"keySetup",
|
|
31734
|
+
"license"
|
|
31735
|
+
]);
|
|
31736
|
+
const [keySetup, license] = spans;
|
|
31737
|
+
expect(keySetup.trackUri).toEqual("track-a");
|
|
31738
|
+
expect(keySetup.endTime).toEqual(license.startTime);
|
|
31739
|
+
});
|
|
31740
|
+
it("carries the content mime and content type on the license span", async () => {
|
|
31741
|
+
licenseProvider.and.resolveTo(new Uint8Array([1]).buffer);
|
|
31742
|
+
const spy = createEventSpy(drmController, "loadSpanMeasured");
|
|
31743
|
+
drmController.setBufferingDrmInfo(drmInfo);
|
|
31744
|
+
await emitEncrypted(new Uint8Array([1, 2, 3]), "cenc");
|
|
31745
|
+
await emitMessage(0, new ArrayBuffer(1));
|
|
31746
|
+
expect(spy).toHaveBeenCalledWith(
|
|
31747
|
+
objectContaining2({
|
|
31748
|
+
kind: "license",
|
|
31749
|
+
mimeType: "audio/mp4",
|
|
31750
|
+
contentType: "audio"
|
|
31751
|
+
})
|
|
31752
|
+
);
|
|
31753
|
+
});
|
|
31565
31754
|
it("does not measure a failed license exchange", async () => {
|
|
31566
31755
|
licenseProvider.and.rejectWith(new Error("license failed"));
|
|
31567
31756
|
const spy = createEventSpy(drmController, "loadSpanMeasured");
|
|
31568
31757
|
drmController.setBufferingDrmInfo(drmInfo);
|
|
31569
31758
|
await emitEncrypted(new Uint8Array([1, 2, 3]), "cenc");
|
|
31570
31759
|
await emitMessage(0, new ArrayBuffer(1));
|
|
31571
|
-
expect(spy).not.
|
|
31760
|
+
expect(spy).not.toHaveBeenCalledWith(
|
|
31761
|
+
objectContaining2({ kind: "license" })
|
|
31762
|
+
);
|
|
31572
31763
|
errorSpy.calls.reset();
|
|
31573
31764
|
});
|
|
31765
|
+
it("measures key setup only for the first session of a track load", async () => {
|
|
31766
|
+
licenseProvider.and.resolveTo(new Uint8Array([1]).buffer);
|
|
31767
|
+
const spy = createEventSpy(drmController, "loadSpanMeasured");
|
|
31768
|
+
drmController.setBufferingDrmInfo(drmInfo, { trackUri: "track-a" });
|
|
31769
|
+
await emitEncrypted(new Uint8Array([1]), "cenc");
|
|
31770
|
+
await emitEncrypted(new Uint8Array([2]), "cenc");
|
|
31771
|
+
expect(mediaKeys.createSession).toHaveBeenCalledTimes(2);
|
|
31772
|
+
await emitMessage(0, new ArrayBuffer(1));
|
|
31773
|
+
await emitMessage(1, new ArrayBuffer(1));
|
|
31774
|
+
const keySetupSpans = spy.calls.all().filter((call) => call.args[0].kind === "keySetup");
|
|
31775
|
+
expect(keySetupSpans.length).toEqual(1);
|
|
31776
|
+
});
|
|
31574
31777
|
it("attributes the license span to the buffering track uri", async () => {
|
|
31575
31778
|
licenseProvider.and.resolveTo(new Uint8Array([1]).buffer);
|
|
31576
31779
|
const spy = createEventSpy(drmController, "loadSpanMeasured");
|
|
31577
31780
|
drmController.setBufferingDrmInfo(drmInfo, { trackUri: "track-a" });
|
|
31578
31781
|
await emitEncrypted(new Uint8Array([1, 2, 3]), "cenc");
|
|
31579
31782
|
await emitMessage(0, new ArrayBuffer(1));
|
|
31580
|
-
expect(spy).
|
|
31783
|
+
expect(spy).toHaveBeenCalledWith(
|
|
31581
31784
|
objectContaining2({
|
|
31582
31785
|
kind: "license",
|
|
31583
31786
|
trackUri: "track-a"
|
|
@@ -31606,16 +31809,10 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
31606
31809
|
expect(mediaKeys.createSession).toHaveBeenCalledTimes(2);
|
|
31607
31810
|
await emitMessage(1, new ArrayBuffer(1));
|
|
31608
31811
|
await emitMessage(0, new ArrayBuffer(1));
|
|
31609
|
-
|
|
31610
|
-
expect(
|
|
31611
|
-
|
|
31612
|
-
|
|
31613
|
-
trackUri: "track-preloaded"
|
|
31614
|
-
})
|
|
31615
|
-
);
|
|
31616
|
-
expect(spy).toHaveBeenCalledWith(
|
|
31617
|
-
objectContaining2({ kind: "license", trackUri: "track-active" })
|
|
31618
|
-
);
|
|
31812
|
+
const licenseTrackUris = spy.calls.all().map((call) => call.args[0]).filter((span) => span.kind === "license").map((span) => span.trackUri);
|
|
31813
|
+
expect(licenseTrackUris.length).toEqual(2);
|
|
31814
|
+
expect(licenseTrackUris).toContain("track-preloaded");
|
|
31815
|
+
expect(licenseTrackUris).toContain("track-active");
|
|
31619
31816
|
});
|
|
31620
31817
|
});
|
|
31621
31818
|
describe("serverCertificate", () => {
|
|
@@ -33128,10 +33325,38 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
33128
33325
|
expect(onStallEntered).toHaveBeenCalledTimes(2);
|
|
33129
33326
|
expect(onStallEnded).toHaveBeenCalledTimes(2);
|
|
33130
33327
|
});
|
|
33328
|
+
it("does not end and re-detect a stall on a non-advancing timeUpdate", async () => {
|
|
33329
|
+
monitor();
|
|
33330
|
+
host.dispatch("playing", {});
|
|
33331
|
+
host.dispatch("timeUpdate", { previous: 4, current: 5 });
|
|
33332
|
+
await poll2(4);
|
|
33333
|
+
expect(onStallEntered).toHaveBeenCalledTimes(1);
|
|
33334
|
+
host.dispatch("timeUpdate", { previous: 5, current: 5 });
|
|
33335
|
+
host.dispatch("timeUpdate", { previous: 5, current: 4.99 });
|
|
33336
|
+
host.dispatch("timeUpdate", { previous: 5, current: 5 + 1e-9 });
|
|
33337
|
+
await poll2(4);
|
|
33338
|
+
expect(onStallEnded).not.toHaveBeenCalled();
|
|
33339
|
+
expect(onStallEntered).toHaveBeenCalledTimes(1);
|
|
33340
|
+
host.dispatch("timeUpdate", { previous: 5, current: 6 });
|
|
33341
|
+
expect(onStallEnded).toHaveBeenCalledTimes(1);
|
|
33342
|
+
expect(onStallEntered).toHaveBeenCalledTimes(1);
|
|
33343
|
+
});
|
|
33344
|
+
it("does not re-detect a stall immediately after resuming via a playing event", async () => {
|
|
33345
|
+
monitor();
|
|
33346
|
+
host.dispatch("playing", {});
|
|
33347
|
+
host.dispatch("timeUpdate", { previous: 0, current: 1 });
|
|
33348
|
+
await poll2(4);
|
|
33349
|
+
expect(onStallEntered).toHaveBeenCalledTimes(1);
|
|
33350
|
+
host.dispatch("playing", {});
|
|
33351
|
+
expect(onStallEnded).toHaveBeenCalledTimes(1);
|
|
33352
|
+
await poll2(3);
|
|
33353
|
+
expect(onStallEntered).toHaveBeenCalledTimes(1);
|
|
33354
|
+
});
|
|
33131
33355
|
it("stops reporting after disposal", async () => {
|
|
33132
33356
|
monitor();
|
|
33133
33357
|
host.dispatch("playing", {});
|
|
33134
33358
|
stop();
|
|
33359
|
+
stop = void 0;
|
|
33135
33360
|
await poll2(8);
|
|
33136
33361
|
expect(onStallEntered).not.toHaveBeenCalled();
|
|
33137
33362
|
});
|
|
@@ -38396,105 +38621,101 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
38396
38621
|
getSegment: () => Promise.resolve(null)
|
|
38397
38622
|
};
|
|
38398
38623
|
}
|
|
38399
|
-
|
|
38400
|
-
|
|
38401
|
-
periods: [
|
|
38402
|
-
{
|
|
38403
|
-
startTime: 0,
|
|
38404
|
-
endTime: 10,
|
|
38405
|
-
qualities: [
|
|
38406
|
-
createQuality2("audio", 128e3),
|
|
38407
|
-
createQuality2("audio", 256e3),
|
|
38408
|
-
createQuality2("video", 5e5)
|
|
38409
|
-
]
|
|
38410
|
-
}
|
|
38411
|
-
],
|
|
38624
|
+
function timelineOf2(qualities) {
|
|
38625
|
+
return {
|
|
38626
|
+
periods: [{ startTime: 0, endTime: 10, qualities: [...qualities] }],
|
|
38412
38627
|
minBufferTime: 2,
|
|
38413
38628
|
getAdBreaks: () => Promise.resolve([]),
|
|
38414
38629
|
getDuration: () => Promise.resolve(Infinity)
|
|
38415
38630
|
};
|
|
38631
|
+
}
|
|
38632
|
+
function contentTypesOf(timeline) {
|
|
38633
|
+
return new Set(
|
|
38634
|
+
timeline.periods[0].qualities.map((q) => q.metadata.contentType)
|
|
38635
|
+
);
|
|
38636
|
+
}
|
|
38637
|
+
function throwEmpty() {
|
|
38638
|
+
throw new Error("no qualities");
|
|
38639
|
+
}
|
|
38640
|
+
describe("filterTimelineQualities", () => {
|
|
38641
|
+
const timeline = timelineOf2([
|
|
38642
|
+
createQuality2("audio", 128e3),
|
|
38643
|
+
createQuality2("audio", 256e3),
|
|
38644
|
+
createQuality2("video", 5e5)
|
|
38645
|
+
]);
|
|
38416
38646
|
it("returns the timeline unchanged when the filter is null", () => {
|
|
38417
|
-
const result = filterTimelineQualities(
|
|
38418
|
-
null,
|
|
38419
|
-
() => {
|
|
38420
|
-
throw new Error("empty");
|
|
38421
|
-
},
|
|
38422
|
-
timeline
|
|
38423
|
-
);
|
|
38647
|
+
const result = filterTimelineQualities(null, throwEmpty, timeline);
|
|
38424
38648
|
expect(result).toBe(timeline);
|
|
38425
38649
|
});
|
|
38426
|
-
it("filters qualities by predicate", () => {
|
|
38650
|
+
it("filters qualities by predicate while keeping every present stream", () => {
|
|
38427
38651
|
const result = filterTimelineQualities(
|
|
38428
|
-
(q) => q.
|
|
38429
|
-
|
|
38430
|
-
throw new Error("empty");
|
|
38431
|
-
},
|
|
38652
|
+
(q) => q.bandwidth !== 256e3,
|
|
38653
|
+
throwEmpty,
|
|
38432
38654
|
timeline
|
|
38433
38655
|
);
|
|
38434
38656
|
expect(result.periods[0].qualities.length).toBe(2);
|
|
38435
|
-
expect(
|
|
38436
|
-
result.periods[0].qualities.every(
|
|
38437
|
-
(q) => q.metadata.contentType === "audio"
|
|
38438
|
-
)
|
|
38439
|
-
).toBeTrue();
|
|
38657
|
+
expect(contentTypesOf(result)).toEqual(/* @__PURE__ */ new Set(["audio", "video"]));
|
|
38440
38658
|
});
|
|
38441
38659
|
it("preserves minBufferTime", () => {
|
|
38442
|
-
const result = filterTimelineQualities(
|
|
38443
|
-
() => true,
|
|
38444
|
-
() => {
|
|
38445
|
-
throw new Error("empty");
|
|
38446
|
-
},
|
|
38447
|
-
timeline
|
|
38448
|
-
);
|
|
38660
|
+
const result = filterTimelineQualities(() => true, throwEmpty, timeline);
|
|
38449
38661
|
expect(result.minBufferTime).toBe(2);
|
|
38450
38662
|
});
|
|
38451
38663
|
it("throws when filtering removes all qualities", () => {
|
|
38664
|
+
expect(
|
|
38665
|
+
() => filterTimelineQualities(() => false, throwEmpty, timeline)
|
|
38666
|
+
).toThrowError("no qualities");
|
|
38667
|
+
});
|
|
38668
|
+
it("throws when filtering drops a present video stream to zero", () => {
|
|
38452
38669
|
expect(
|
|
38453
38670
|
() => filterTimelineQualities(
|
|
38454
|
-
() =>
|
|
38455
|
-
|
|
38456
|
-
throw new Error("no qualities");
|
|
38457
|
-
},
|
|
38671
|
+
(q) => q.contentType === "audio",
|
|
38672
|
+
throwEmpty,
|
|
38458
38673
|
timeline
|
|
38459
38674
|
)
|
|
38460
38675
|
).toThrowError("no qualities");
|
|
38461
38676
|
});
|
|
38677
|
+
it("throws when filtering drops a present audio stream to zero", () => {
|
|
38678
|
+
expect(
|
|
38679
|
+
() => filterTimelineQualities(
|
|
38680
|
+
(q) => q.contentType === "video",
|
|
38681
|
+
throwEmpty,
|
|
38682
|
+
timeline
|
|
38683
|
+
)
|
|
38684
|
+
).toThrowError("no qualities");
|
|
38685
|
+
});
|
|
38686
|
+
it("does not throw when losing only an optional text sidecar stream", () => {
|
|
38687
|
+
const withText = timelineOf2([
|
|
38688
|
+
createQuality2("audio", 128e3),
|
|
38689
|
+
createQuality2("video", 5e5),
|
|
38690
|
+
createQuality2("text", 1e3)
|
|
38691
|
+
]);
|
|
38692
|
+
const result = filterTimelineQualities(
|
|
38693
|
+
(q) => q.contentType !== "text",
|
|
38694
|
+
throwEmpty,
|
|
38695
|
+
withText
|
|
38696
|
+
);
|
|
38697
|
+
expect(contentTypesOf(result)).toEqual(/* @__PURE__ */ new Set(["audio", "video"]));
|
|
38698
|
+
});
|
|
38462
38699
|
});
|
|
38463
38700
|
describe("filterTimelineQualitiesAsync", () => {
|
|
38464
|
-
const timeline =
|
|
38465
|
-
|
|
38466
|
-
|
|
38467
|
-
|
|
38468
|
-
|
|
38469
|
-
|
|
38470
|
-
createQuality2("audio", 128e3),
|
|
38471
|
-
createQuality2("video", 5e5)
|
|
38472
|
-
]
|
|
38473
|
-
}
|
|
38474
|
-
],
|
|
38475
|
-
minBufferTime: 2,
|
|
38476
|
-
getAdBreaks: () => Promise.resolve([]),
|
|
38477
|
-
getDuration: () => Promise.resolve(Infinity)
|
|
38478
|
-
};
|
|
38479
|
-
it("filters qualities by async predicate", async () => {
|
|
38701
|
+
const timeline = timelineOf2([
|
|
38702
|
+
createQuality2("audio", 128e3),
|
|
38703
|
+
createQuality2("audio", 256e3),
|
|
38704
|
+
createQuality2("video", 5e5)
|
|
38705
|
+
]);
|
|
38706
|
+
it("filters qualities by async predicate while keeping every present stream", async () => {
|
|
38480
38707
|
const result = await filterTimelineQualitiesAsync(
|
|
38481
|
-
(q) => Promise.resolve(q.
|
|
38482
|
-
|
|
38483
|
-
throw new Error("empty");
|
|
38484
|
-
},
|
|
38708
|
+
(q) => Promise.resolve(q.bandwidth !== 256e3),
|
|
38709
|
+
throwEmpty,
|
|
38485
38710
|
timeline
|
|
38486
38711
|
);
|
|
38487
|
-
expect(result.periods[0].qualities.length).toBe(
|
|
38488
|
-
expect(result.
|
|
38489
|
-
"audio"
|
|
38490
|
-
);
|
|
38712
|
+
expect(result.periods[0].qualities.length).toBe(2);
|
|
38713
|
+
expect(contentTypesOf(result)).toEqual(/* @__PURE__ */ new Set(["audio", "video"]));
|
|
38491
38714
|
});
|
|
38492
38715
|
it("preserves minBufferTime", async () => {
|
|
38493
38716
|
const result = await filterTimelineQualitiesAsync(
|
|
38494
38717
|
() => Promise.resolve(true),
|
|
38495
|
-
|
|
38496
|
-
throw new Error("empty");
|
|
38497
|
-
},
|
|
38718
|
+
throwEmpty,
|
|
38498
38719
|
timeline
|
|
38499
38720
|
);
|
|
38500
38721
|
expect(result.minBufferTime).toBe(2);
|
|
@@ -38503,13 +38724,33 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
38503
38724
|
await expectAsync(
|
|
38504
38725
|
filterTimelineQualitiesAsync(
|
|
38505
38726
|
() => Promise.resolve(false),
|
|
38506
|
-
|
|
38507
|
-
|
|
38508
|
-
|
|
38727
|
+
throwEmpty,
|
|
38728
|
+
timeline
|
|
38729
|
+
)
|
|
38730
|
+
).toBeRejectedWithError("no qualities");
|
|
38731
|
+
});
|
|
38732
|
+
it("throws when filtering drops a present video stream to zero", async () => {
|
|
38733
|
+
await expectAsync(
|
|
38734
|
+
filterTimelineQualitiesAsync(
|
|
38735
|
+
(q) => Promise.resolve(q.contentType === "audio"),
|
|
38736
|
+
throwEmpty,
|
|
38509
38737
|
timeline
|
|
38510
38738
|
)
|
|
38511
38739
|
).toBeRejectedWithError("no qualities");
|
|
38512
38740
|
});
|
|
38741
|
+
it("does not throw when losing only an optional text sidecar stream", async () => {
|
|
38742
|
+
const withText = timelineOf2([
|
|
38743
|
+
createQuality2("audio", 128e3),
|
|
38744
|
+
createQuality2("video", 5e5),
|
|
38745
|
+
createQuality2("text", 1e3)
|
|
38746
|
+
]);
|
|
38747
|
+
const result = await filterTimelineQualitiesAsync(
|
|
38748
|
+
(q) => Promise.resolve(q.contentType !== "text"),
|
|
38749
|
+
throwEmpty,
|
|
38750
|
+
withText
|
|
38751
|
+
);
|
|
38752
|
+
expect(contentTypesOf(result)).toEqual(/* @__PURE__ */ new Set(["audio", "video"]));
|
|
38753
|
+
});
|
|
38513
38754
|
});
|
|
38514
38755
|
|
|
38515
38756
|
// test/unit/streaming/mediaTimelineLanguageFilter.test.ts
|
|
@@ -39341,7 +39582,8 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
39341
39582
|
expect(track2.qualities).toEqual([qualityMetadata]);
|
|
39342
39583
|
expect(track2.qualitiesUnfiltered).toEqual([qualityMetadata]);
|
|
39343
39584
|
});
|
|
39344
|
-
it("
|
|
39585
|
+
it("plays the surviving content type when another is entirely filtered out", async () => {
|
|
39586
|
+
deps.contentTypesValue.value = Promise.resolve(/* @__PURE__ */ new Set(["video"]));
|
|
39345
39587
|
const videoQuality2 = __spreadProps(__spreadValues({}, createEmptyMediaQualityMetadata()), {
|
|
39346
39588
|
contentType: "video",
|
|
39347
39589
|
qualityId: "v1"
|
|
@@ -39365,10 +39607,10 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
39365
39607
|
});
|
|
39366
39608
|
track2 = createTrack();
|
|
39367
39609
|
await flushPromises();
|
|
39368
|
-
expect(track2.error).
|
|
39369
|
-
expect(track2.
|
|
39370
|
-
|
|
39371
|
-
);
|
|
39610
|
+
expect(track2.error).toBeNull();
|
|
39611
|
+
expect(track2.contentTypes).toEqual(/* @__PURE__ */ new Set(["video"]));
|
|
39612
|
+
expect(getStream("video")).not.toBeNull();
|
|
39613
|
+
expect(getStream("audio")).toBeNull();
|
|
39372
39614
|
});
|
|
39373
39615
|
it("reads qualities from the filtered timeline but qualitiesUnfiltered from the raw one", async () => {
|
|
39374
39616
|
const makeTimeline = (qualities) => ({
|
|
@@ -47894,6 +48136,25 @@ x`);
|
|
|
47894
48136
|
expect(withinMin(mid, 1, [low, mid])).toBe(true);
|
|
47895
48137
|
expect(withinMin(low, 0, [low, mid])).toBe(false);
|
|
47896
48138
|
});
|
|
48139
|
+
it("keeps the highest when every rate (of three) is below the floor", () => {
|
|
48140
|
+
const low = audioQuality2([16e3]);
|
|
48141
|
+
const mid = audioQuality2([24e3]);
|
|
48142
|
+
const high = audioQuality2([32e3]);
|
|
48143
|
+
expect(withinMin(high, 2, [low, mid, high])).toBe(true);
|
|
48144
|
+
expect(withinMin(mid, 1, [low, mid, high])).toBe(false);
|
|
48145
|
+
expect(withinMin(low, 0, [low, mid, high])).toBe(false);
|
|
48146
|
+
});
|
|
48147
|
+
it("keeps the highest below-floor rate when no platform max is reported", () => {
|
|
48148
|
+
capabilities.sampleRate = null;
|
|
48149
|
+
const low = audioQuality2([16e3]);
|
|
48150
|
+
const high = audioQuality2([32e3]);
|
|
48151
|
+
expect(withinMin(high, 1, [low, high])).toBe(true);
|
|
48152
|
+
expect(withinMin(low, 0, [low, high])).toBe(false);
|
|
48153
|
+
});
|
|
48154
|
+
it("keeps a single audio rendition below the floor", () => {
|
|
48155
|
+
const only = audioQuality2([24e3]);
|
|
48156
|
+
expect(withinMin(only, 0, [only])).toBe(true);
|
|
48157
|
+
});
|
|
47897
48158
|
it("ignores non-audio qualities in the floor fallback", () => {
|
|
47898
48159
|
const audio = audioQuality2([24e3]);
|
|
47899
48160
|
const video = createEmptyMediaQualityMetadata();
|
|
@@ -47903,6 +48164,55 @@ x`);
|
|
|
47903
48164
|
expect(withinMin(video, 1, [audio, video])).toBe(true);
|
|
47904
48165
|
});
|
|
47905
48166
|
});
|
|
48167
|
+
describe("withinAudioSampleRateRange straddling an empty band", () => {
|
|
48168
|
+
let capabilities;
|
|
48169
|
+
beforeEach(() => {
|
|
48170
|
+
setUserAgent("Chrome");
|
|
48171
|
+
capabilities = new MockCapabilities();
|
|
48172
|
+
capabilities.sampleRate = 48e3;
|
|
48173
|
+
});
|
|
48174
|
+
function audioQuality2(rate) {
|
|
48175
|
+
const quality = createEmptyMediaQualityMetadata();
|
|
48176
|
+
quality.contentType = "audio";
|
|
48177
|
+
quality.audioSamplingRate = rate;
|
|
48178
|
+
return quality;
|
|
48179
|
+
}
|
|
48180
|
+
function within(metadata, index, array2) {
|
|
48181
|
+
return withinAudioSampleRateRange(
|
|
48182
|
+
{ capabilities, minSampleRate: 44100 },
|
|
48183
|
+
metadata,
|
|
48184
|
+
index,
|
|
48185
|
+
array2
|
|
48186
|
+
);
|
|
48187
|
+
}
|
|
48188
|
+
it("keeps the highest decodable rate when one is below the floor and one above the ceiling", () => {
|
|
48189
|
+
const low = audioQuality2([22050]);
|
|
48190
|
+
const high = audioQuality2([96e3]);
|
|
48191
|
+
expect(within(low, 0, [low, high])).toBe(true);
|
|
48192
|
+
expect(within(high, 1, [low, high])).toBe(false);
|
|
48193
|
+
});
|
|
48194
|
+
it("keeps the below-floor rate when the ceiling matches the output device rate", () => {
|
|
48195
|
+
capabilities.sampleRate = 44100;
|
|
48196
|
+
const low = audioQuality2([22050]);
|
|
48197
|
+
const high = audioQuality2([48e3]);
|
|
48198
|
+
expect(within(low, 0, [low, high])).toBe(true);
|
|
48199
|
+
expect(within(high, 1, [low, high])).toBe(false);
|
|
48200
|
+
});
|
|
48201
|
+
it("still prefers an in-band rate over the straddle fallback", () => {
|
|
48202
|
+
const low = audioQuality2([22050]);
|
|
48203
|
+
const mid = audioQuality2([44100]);
|
|
48204
|
+
const high = audioQuality2([96e3]);
|
|
48205
|
+
expect(within(mid, 1, [low, mid, high])).toBe(true);
|
|
48206
|
+
expect(within(low, 0, [low, mid, high])).toBe(false);
|
|
48207
|
+
expect(within(high, 2, [low, mid, high])).toBe(false);
|
|
48208
|
+
});
|
|
48209
|
+
it("keeps the lowest when every rate is above the ceiling", () => {
|
|
48210
|
+
const low = audioQuality2([88200]);
|
|
48211
|
+
const high = audioQuality2([96e3]);
|
|
48212
|
+
expect(within(low, 0, [low, high])).toBe(true);
|
|
48213
|
+
expect(within(high, 1, [low, high])).toBe(false);
|
|
48214
|
+
});
|
|
48215
|
+
});
|
|
47906
48216
|
|
|
47907
48217
|
// test/unit/track/hls/buildHlsMediaTimeline.test.ts
|
|
47908
48218
|
describe("buildHlsMediaTimeline", () => {
|
|
@@ -53347,6 +53657,7 @@ x`);
|
|
|
53347
53657
|
let depFactories;
|
|
53348
53658
|
let player;
|
|
53349
53659
|
let mockOptions;
|
|
53660
|
+
const loggerRef2 = useMockLogger();
|
|
53350
53661
|
polyfillCustomEvent();
|
|
53351
53662
|
beforeEach(() => {
|
|
53352
53663
|
mockOptions = { media: new MockHTMLAudioElement() };
|
|
@@ -53571,6 +53882,40 @@ x`);
|
|
|
53571
53882
|
});
|
|
53572
53883
|
expect(spy).not.toHaveBeenCalled();
|
|
53573
53884
|
});
|
|
53885
|
+
it("logs each republished span at the verbose level with formatted times", () => {
|
|
53886
|
+
loggerRef2.value.logLevel = 0 /* VERBOSE */;
|
|
53887
|
+
deps.drmController.dispatch("loadSpanMeasured", {
|
|
53888
|
+
kind: "license",
|
|
53889
|
+
startTime: 1e3,
|
|
53890
|
+
endTime: 3500,
|
|
53891
|
+
trackUri: "track-a",
|
|
53892
|
+
mimeType: "audio/mp4",
|
|
53893
|
+
contentType: "audio"
|
|
53894
|
+
});
|
|
53895
|
+
expect(loggerRef2.value.verbose).toHaveBeenCalledOnceWith(
|
|
53896
|
+
player,
|
|
53897
|
+
"loadSpan",
|
|
53898
|
+
{
|
|
53899
|
+
kind: "license",
|
|
53900
|
+
trackUri: "track-a",
|
|
53901
|
+
mimeType: "audio/mp4",
|
|
53902
|
+
contentType: "audio",
|
|
53903
|
+
startTime: (/* @__PURE__ */ new Date(1e3)).toISOString(),
|
|
53904
|
+
endTime: (/* @__PURE__ */ new Date(3500)).toISOString(),
|
|
53905
|
+
durationMs: "2500.00"
|
|
53906
|
+
}
|
|
53907
|
+
);
|
|
53908
|
+
});
|
|
53909
|
+
it("does not format or log a span when the verbose level is off", () => {
|
|
53910
|
+
loggerRef2.value.logLevel = 1 /* DEBUG */;
|
|
53911
|
+
deps.drmController.dispatch("loadSpanMeasured", {
|
|
53912
|
+
kind: "license",
|
|
53913
|
+
startTime: 1e3,
|
|
53914
|
+
endTime: 3500,
|
|
53915
|
+
trackUri: "track-a"
|
|
53916
|
+
});
|
|
53917
|
+
expect(loggerRef2.value.verbose).not.toHaveBeenCalled();
|
|
53918
|
+
});
|
|
53574
53919
|
});
|
|
53575
53920
|
it("reloads the current track on a codecUnsupported event", () => {
|
|
53576
53921
|
const trackController = deps.trackController;
|
|
@@ -54816,27 +55161,31 @@ x`);
|
|
|
54816
55161
|
if (!supportsMse()) pending("MSE not supported");
|
|
54817
55162
|
});
|
|
54818
55163
|
const suite = createVinylSuite();
|
|
55164
|
+
async function arrangeStall() {
|
|
55165
|
+
const player = suite.player;
|
|
55166
|
+
const stallEnteredSpy = createEventSpy(player, "stallEntered");
|
|
55167
|
+
const stallEndedSpy = createEventSpy(player, "stallEnded");
|
|
55168
|
+
const mediaSource = new ControlledMediaSource();
|
|
55169
|
+
player.load({
|
|
55170
|
+
type: "src",
|
|
55171
|
+
uri: URL.createObjectURL(await mediaSource.getMediaSource())
|
|
55172
|
+
});
|
|
55173
|
+
void player.play();
|
|
55174
|
+
await mediaSource.appendNext();
|
|
55175
|
+
await mediaSource.appendNext();
|
|
55176
|
+
await onPlaying(player);
|
|
55177
|
+
expect(stallEnteredSpy).not.toHaveBeenCalled();
|
|
55178
|
+
const entered = stallEnteredSpy.next(
|
|
55179
|
+
30,
|
|
55180
|
+
"stallEntered after hitting end of buffer timed out after {timeout}s"
|
|
55181
|
+
);
|
|
55182
|
+
await player.seekTo(5);
|
|
55183
|
+
await entered;
|
|
55184
|
+
return { player, mediaSource, stallEnteredSpy, stallEndedSpy };
|
|
55185
|
+
}
|
|
54819
55186
|
describe("when the play head freezes mid-playback", () => {
|
|
54820
55187
|
it("emits stallEntered then stallEnded when playback resumes", async () => {
|
|
54821
|
-
const player =
|
|
54822
|
-
const stallEnteredSpy = createEventSpy(player, "stallEntered");
|
|
54823
|
-
const stallEndedSpy = createEventSpy(player, "stallEnded");
|
|
54824
|
-
const mediaSource = new ControlledMediaSource();
|
|
54825
|
-
player.load({
|
|
54826
|
-
type: "src",
|
|
54827
|
-
uri: URL.createObjectURL(await mediaSource.getMediaSource())
|
|
54828
|
-
});
|
|
54829
|
-
void player.play();
|
|
54830
|
-
await mediaSource.appendNext();
|
|
54831
|
-
await mediaSource.appendNext();
|
|
54832
|
-
await onPlaying(player);
|
|
54833
|
-
expect(stallEnteredSpy).not.toHaveBeenCalled();
|
|
54834
|
-
const nextEntered = stallEnteredSpy.next(
|
|
54835
|
-
30,
|
|
54836
|
-
"stallEntered after hitting end of buffer timed out after {timeout}s"
|
|
54837
|
-
);
|
|
54838
|
-
await player.seekTo(5);
|
|
54839
|
-
await nextEntered;
|
|
55188
|
+
const { player, mediaSource, stallEnteredSpy, stallEndedSpy } = await arrangeStall();
|
|
54840
55189
|
const nextEnded = stallEndedSpy.next(
|
|
54841
55190
|
10,
|
|
54842
55191
|
"stallEnded after data buffered timed out after {timeout}s"
|
|
@@ -54845,7 +55194,62 @@ x`);
|
|
|
54845
55194
|
const ended = await nextEnded;
|
|
54846
55195
|
expect(ended.reason).toBe("playing");
|
|
54847
55196
|
expect(stallEnteredSpy).toHaveBeenCalledTimes(1);
|
|
55197
|
+
expect(stallEndedSpy).toHaveBeenCalledTimes(1);
|
|
54848
55198
|
expect(ended.duration).toBeGreaterThan(0.5);
|
|
55199
|
+
for (let i = 0; i < 5; i++) await onTimeUpdate(player);
|
|
55200
|
+
expect(stallEnteredSpy).toHaveBeenCalledTimes(1);
|
|
55201
|
+
expect(stallEndedSpy).toHaveBeenCalledTimes(1);
|
|
55202
|
+
});
|
|
55203
|
+
it('ends the stall with reason "seeking" when the user seeks away mid-stall', async () => {
|
|
55204
|
+
const { player, stallEnteredSpy, stallEndedSpy } = await arrangeStall();
|
|
55205
|
+
const nextEnded = stallEndedSpy.next(
|
|
55206
|
+
10,
|
|
55207
|
+
"stallEnded after seeking away timed out after {timeout}s"
|
|
55208
|
+
);
|
|
55209
|
+
await player.seekTo(1);
|
|
55210
|
+
const ended = await nextEnded;
|
|
55211
|
+
expect(ended.reason).toBe("seeking");
|
|
55212
|
+
expect(stallEnteredSpy).toHaveBeenCalledTimes(1);
|
|
55213
|
+
expect(stallEndedSpy).toHaveBeenCalledTimes(1);
|
|
55214
|
+
});
|
|
55215
|
+
it('ends the stall with reason "emptied" when the track is unloaded mid-stall', async () => {
|
|
55216
|
+
const { player, stallEnteredSpy, stallEndedSpy } = await arrangeStall();
|
|
55217
|
+
const nextEnded = stallEndedSpy.next(
|
|
55218
|
+
10,
|
|
55219
|
+
"stallEnded after unload timed out after {timeout}s"
|
|
55220
|
+
);
|
|
55221
|
+
player.unload();
|
|
55222
|
+
const ended = await nextEnded;
|
|
55223
|
+
expect(ended.reason).toBe("emptied");
|
|
55224
|
+
expect(stallEnteredSpy).toHaveBeenCalledTimes(1);
|
|
55225
|
+
expect(stallEndedSpy).toHaveBeenCalledTimes(1);
|
|
55226
|
+
});
|
|
55227
|
+
it("reports each distinct freeze exactly once", async () => {
|
|
55228
|
+
const { player, mediaSource, stallEnteredSpy, stallEndedSpy } = await arrangeStall();
|
|
55229
|
+
const firstEnded = stallEndedSpy.next(
|
|
55230
|
+
10,
|
|
55231
|
+
"stallEnded for freeze 1 timed out after {timeout}s"
|
|
55232
|
+
);
|
|
55233
|
+
await mediaSource.appendNext();
|
|
55234
|
+
expect((await firstEnded).reason).toBe("playing");
|
|
55235
|
+
expect(stallEnteredSpy).toHaveBeenCalledTimes(1);
|
|
55236
|
+
expect(stallEndedSpy).toHaveBeenCalledTimes(1);
|
|
55237
|
+
const secondEntered = stallEnteredSpy.next(
|
|
55238
|
+
30,
|
|
55239
|
+
"stallEntered for freeze 2 timed out after {timeout}s"
|
|
55240
|
+
);
|
|
55241
|
+
await player.seekTo(15);
|
|
55242
|
+
await secondEntered;
|
|
55243
|
+
expect(stallEnteredSpy).toHaveBeenCalledTimes(2);
|
|
55244
|
+
const secondEnded = stallEndedSpy.next(
|
|
55245
|
+
10,
|
|
55246
|
+
"stallEnded for freeze 2 timed out after {timeout}s"
|
|
55247
|
+
);
|
|
55248
|
+
await mediaSource.appendNext();
|
|
55249
|
+
expect((await secondEnded).reason).toBe("playing");
|
|
55250
|
+
for (let i = 0; i < 5; i++) await onTimeUpdate(player);
|
|
55251
|
+
expect(stallEnteredSpy).toHaveBeenCalledTimes(2);
|
|
55252
|
+
expect(stallEndedSpy).toHaveBeenCalledTimes(2);
|
|
54849
55253
|
});
|
|
54850
55254
|
});
|
|
54851
55255
|
});
|
|
@@ -55574,6 +55978,7 @@ x`);
|
|
|
55574
55978
|
var ANCHOR_MS = Date.parse(ANCHOR_ISO);
|
|
55575
55979
|
var CONTENT_ASSET = vinylTestAssets.hls.live_static_video_audio_60s_4s;
|
|
55576
55980
|
var AD_ASSET = vinylTestAssets.hls.live_static_video_audio_60s_2s;
|
|
55981
|
+
var SHORT_AD_ASSET = vinylTestAssets.hls.edge_static_video_1s;
|
|
55577
55982
|
function injectingManifestProvider(mainUrl, interstitials) {
|
|
55578
55983
|
return async () => {
|
|
55579
55984
|
const mainText = await (await fetch(mainUrl)).text();
|
|
@@ -56208,8 +56613,8 @@ x`);
|
|
|
56208
56613
|
id: "postroll-1",
|
|
56209
56614
|
startTime: 0,
|
|
56210
56615
|
duration: 4,
|
|
56211
|
-
//
|
|
56212
|
-
assetList: [{ uri:
|
|
56616
|
+
// A 1s ad so the break ends quickly on its own `ended`.
|
|
56617
|
+
assetList: [{ uri: SHORT_AD_ASSET, duration: 1 }],
|
|
56213
56618
|
cue: "POST"
|
|
56214
56619
|
}
|
|
56215
56620
|
])
|