@amazon/vinyl 3.2.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 +30 -24
- package/dist/index.cjs.map +2 -2
- package/dist/index.js +30 -24
- package/dist/index.js.map +2 -2
- package/dist/test/integ.es5.js +167 -155
- package/dist/test/integ.js +260 -50
- package/dist/test/integ.js.map +2 -2
- 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/playback/createStallMonitor.d.ts.map +1 -1
- package/dist/types/current/playback/createStallMonitor.js +19 -14
- package/dist/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/types/ts3.7/ad/AdControllerImpl.d.ts +18 -2
- package/dist/types/ts4.1/ad/AdControllerImpl.d.ts +18 -2
- package/dist/types/ts4.7/ad/AdControllerImpl.d.ts +18 -2
- package/dist/unit/types/current/ad/AdControllerImpl.test.js +110 -4
- package/dist/unit/types/current/playback/createStallMonitor.test.js +38 -0
- package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -1
- 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
|
@@ -9953,6 +9953,7 @@ ${this.lastInitStack}` : "";
|
|
|
9953
9953
|
|
|
9954
9954
|
// src/ad/AdControllerImpl.ts
|
|
9955
9955
|
var AD_START_TOLERANCE = 0.05;
|
|
9956
|
+
var PLAYOUT_LIMIT_TOLERANCE = 1;
|
|
9956
9957
|
var defaultAdControllerImplOptions = {
|
|
9957
9958
|
adLoadTimeout: 15,
|
|
9958
9959
|
preloadAheadTime: 10
|
|
@@ -10443,23 +10444,29 @@ ${this.lastInitStack}` : "";
|
|
|
10443
10444
|
});
|
|
10444
10445
|
}
|
|
10445
10446
|
/**
|
|
10446
|
-
*
|
|
10447
|
-
*
|
|
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.
|
|
10448
10456
|
*/
|
|
10449
10457
|
enforcePlayoutLimits(adState) {
|
|
10458
|
+
if (!adState.started) return;
|
|
10459
|
+
const { parentBreak } = adState;
|
|
10460
|
+
const limit = parentBreak.adBreak.playoutLimit;
|
|
10461
|
+
if (limit == null) return;
|
|
10450
10462
|
const pC = this.deps.playbackController;
|
|
10451
|
-
const
|
|
10452
|
-
if (
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
const limit = adBreak.playoutLimit;
|
|
10456
|
-
if (limit != null && parentBreak.playoutElapsed + elapsed >= limit) {
|
|
10457
|
-
parentBreak.clear();
|
|
10458
|
-
this.endAd(adState);
|
|
10459
|
-
} else if (ad.duration != null && elapsed >= ad.duration) {
|
|
10460
|
-
this.endAd(adState);
|
|
10461
|
-
}
|
|
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;
|
|
10462
10467
|
}
|
|
10468
|
+
parentBreak.clear();
|
|
10469
|
+
this.endAd(adState);
|
|
10463
10470
|
}
|
|
10464
10471
|
setPendingBreaks(newPending) {
|
|
10465
10472
|
this.pendingAdBreaks = newPending.filter(
|
|
@@ -15214,7 +15221,7 @@ ${this.lastInitStack}` : "";
|
|
|
15214
15221
|
// src/global/environment.ts
|
|
15215
15222
|
var vinylVersion = parseVersion(
|
|
15216
15223
|
/* istanbul ignore next */
|
|
15217
|
-
"3.2.
|
|
15224
|
+
"3.2.1"
|
|
15218
15225
|
);
|
|
15219
15226
|
|
|
15220
15227
|
// src/global/globalRef.ts
|
|
@@ -15371,6 +15378,7 @@ ${this.lastInitStack}` : "";
|
|
|
15371
15378
|
];
|
|
15372
15379
|
|
|
15373
15380
|
// src/playback/createStallMonitor.ts
|
|
15381
|
+
var PROGRESS_EPSILON = 1e-6;
|
|
15374
15382
|
function createStallMonitor(controller, callbacks, options = {}) {
|
|
15375
15383
|
var _a3, _b;
|
|
15376
15384
|
const stallThresholdMs = ((_a3 = options.stallThreshold) != null ? _a3 : 1) * 1e3;
|
|
@@ -15379,6 +15387,7 @@ ${this.lastInitStack}` : "";
|
|
|
15379
15387
|
let lastProgressAt = Date.now();
|
|
15380
15388
|
let inStall = false;
|
|
15381
15389
|
let pollId = null;
|
|
15390
|
+
const { add, dispose } = createDisposer();
|
|
15382
15391
|
const endStall = (reason) => {
|
|
15383
15392
|
if (!inStall) return;
|
|
15384
15393
|
inStall = false;
|
|
@@ -15395,6 +15404,7 @@ ${this.lastInitStack}` : "";
|
|
|
15395
15404
|
clearInterval(pollId);
|
|
15396
15405
|
pollId = null;
|
|
15397
15406
|
};
|
|
15407
|
+
add(stopPoll);
|
|
15398
15408
|
const startPoll = () => {
|
|
15399
15409
|
if (pollId != null) return;
|
|
15400
15410
|
pollId = setInterval(() => {
|
|
@@ -15410,17 +15420,18 @@ ${this.lastInitStack}` : "";
|
|
|
15410
15420
|
observedPlaying = false;
|
|
15411
15421
|
stopPoll();
|
|
15412
15422
|
};
|
|
15413
|
-
const { add, dispose } = createDisposer();
|
|
15414
15423
|
add(
|
|
15415
15424
|
controller.on("playing", () => {
|
|
15416
15425
|
if (inStall) endStall("playing" /* PLAYING */);
|
|
15417
|
-
|
|
15426
|
+
lastProgressAt = Date.now();
|
|
15418
15427
|
observedPlaying = true;
|
|
15419
15428
|
startPoll();
|
|
15420
15429
|
})
|
|
15421
15430
|
);
|
|
15422
15431
|
add(
|
|
15423
|
-
controller.on("timeUpdate", () => {
|
|
15432
|
+
controller.on("timeUpdate", ({ previous, current }) => {
|
|
15433
|
+
if (previous != null && current - previous <= PROGRESS_EPSILON)
|
|
15434
|
+
return;
|
|
15424
15435
|
if (inStall) endStall("playing" /* PLAYING */);
|
|
15425
15436
|
lastProgressAt = Date.now();
|
|
15426
15437
|
})
|
|
@@ -15428,13 +15439,7 @@ ${this.lastInitStack}` : "";
|
|
|
15428
15439
|
add(controller.on("pause", () => suspend("pause" /* PAUSE */)));
|
|
15429
15440
|
add(controller.on("seeking", () => suspend("seeking" /* SEEKING */)));
|
|
15430
15441
|
add(controller.on("emptied", () => suspend("emptied" /* EMPTIED */)));
|
|
15431
|
-
|
|
15432
|
-
return () => {
|
|
15433
|
-
if (stopped) return;
|
|
15434
|
-
stopped = true;
|
|
15435
|
-
stopPoll();
|
|
15436
|
-
dispose();
|
|
15437
|
-
};
|
|
15442
|
+
return dispose;
|
|
15438
15443
|
}
|
|
15439
15444
|
|
|
15440
15445
|
// src/playback/error/InvalidSeekError.ts
|
|
@@ -24951,7 +24956,8 @@ ${this.lastInitStack}` : "";
|
|
|
24951
24956
|
var vinylTestAssets = {
|
|
24952
24957
|
prog: {
|
|
24953
24958
|
libmp3lame_60s_2ch_16bit_44100Hz_48kbps: `https://assets.dev.vinyl.music.amazon.dev/prog/libmp3lame_60s_2ch_16bit_44100Hz_48kbps.mp3`,
|
|
24954
|
-
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`
|
|
24955
24961
|
},
|
|
24956
24962
|
dash: {
|
|
24957
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`,
|
|
@@ -24966,6 +24972,10 @@ ${this.lastInitStack}` : "";
|
|
|
24966
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`,
|
|
24967
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`,
|
|
24968
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`,
|
|
24969
24979
|
// Ad-break assets (see DMTestAssetBuilder src/adBreaks): a ~3-min
|
|
24970
24980
|
// labeled ABR ladder with five-language narration, full + forced
|
|
24971
24981
|
// captions. Base content only — DASH ad-break scenarios are not
|
|
@@ -24985,6 +24995,10 @@ ${this.lastInitStack}` : "";
|
|
|
24985
24995
|
live_static_video_60s_4s_mpegts: `https://assets.dev.vinyl.music.amazon.dev/hls/live_static_video_60s_4s_mpegts/main.m3u8`,
|
|
24986
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`,
|
|
24987
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`,
|
|
24988
25002
|
// Ad-break assets: base content + HLS Interstitial scenario variants.
|
|
24989
25003
|
// `*_av` = video+audio, `*_audio` = audio-only; each `__<scenario>`
|
|
24990
25004
|
// injects a different ad-break configuration. See DMTestAssetBuilder
|
|
@@ -27745,7 +27759,6 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
27745
27759
|
makeBreak({
|
|
27746
27760
|
startTime: 10,
|
|
27747
27761
|
duration: 20,
|
|
27748
|
-
// Null per-ad duration so the per-ad cap does not end it.
|
|
27749
27762
|
ads: [
|
|
27750
27763
|
{
|
|
27751
27764
|
id: "a1",
|
|
@@ -27849,8 +27862,8 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
27849
27862
|
});
|
|
27850
27863
|
});
|
|
27851
27864
|
describe("ad duration and playout limits", () => {
|
|
27852
|
-
it("advances to the next ad
|
|
27853
|
-
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;
|
|
27854
27867
|
const c = createController();
|
|
27855
27868
|
await setContent(
|
|
27856
27869
|
c,
|
|
@@ -27863,6 +27876,7 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
27863
27876
|
id: "a1",
|
|
27864
27877
|
startTime: 0,
|
|
27865
27878
|
duration: 5,
|
|
27879
|
+
// declared, server-rounded
|
|
27866
27880
|
uri: "ad1.m3u8"
|
|
27867
27881
|
},
|
|
27868
27882
|
{
|
|
@@ -27881,7 +27895,51 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
27881
27895
|
playbackController.dispatch("playing", {});
|
|
27882
27896
|
updateTime(6);
|
|
27883
27897
|
await flush();
|
|
27884
|
-
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();
|
|
27885
27943
|
});
|
|
27886
27944
|
it("ends the whole break when the playout limit is reached, dropping remaining ads", async () => {
|
|
27887
27945
|
var _a3;
|
|
@@ -27922,6 +27980,70 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
27922
27980
|
expect(c.currentAdBreak).toBeNull();
|
|
27923
27981
|
expect(resumes.at(-1)).toBe(0);
|
|
27924
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
|
+
});
|
|
27925
28047
|
});
|
|
27926
28048
|
describe("CUE=ONCE and replay", () => {
|
|
27927
28049
|
it("replays a non-ONCE break after the playhead seeks back before it", async () => {
|
|
@@ -33203,10 +33325,38 @@ ${(_a3 = this.failedResult.failedExpectations[0]) == null ? void 0 : _a3.message
|
|
|
33203
33325
|
expect(onStallEntered).toHaveBeenCalledTimes(2);
|
|
33204
33326
|
expect(onStallEnded).toHaveBeenCalledTimes(2);
|
|
33205
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
|
+
});
|
|
33206
33355
|
it("stops reporting after disposal", async () => {
|
|
33207
33356
|
monitor();
|
|
33208
33357
|
host.dispatch("playing", {});
|
|
33209
33358
|
stop();
|
|
33359
|
+
stop = void 0;
|
|
33210
33360
|
await poll2(8);
|
|
33211
33361
|
expect(onStallEntered).not.toHaveBeenCalled();
|
|
33212
33362
|
});
|
|
@@ -55011,27 +55161,31 @@ x`);
|
|
|
55011
55161
|
if (!supportsMse()) pending("MSE not supported");
|
|
55012
55162
|
});
|
|
55013
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
|
+
}
|
|
55014
55186
|
describe("when the play head freezes mid-playback", () => {
|
|
55015
55187
|
it("emits stallEntered then stallEnded when playback resumes", async () => {
|
|
55016
|
-
const player =
|
|
55017
|
-
const stallEnteredSpy = createEventSpy(player, "stallEntered");
|
|
55018
|
-
const stallEndedSpy = createEventSpy(player, "stallEnded");
|
|
55019
|
-
const mediaSource = new ControlledMediaSource();
|
|
55020
|
-
player.load({
|
|
55021
|
-
type: "src",
|
|
55022
|
-
uri: URL.createObjectURL(await mediaSource.getMediaSource())
|
|
55023
|
-
});
|
|
55024
|
-
void player.play();
|
|
55025
|
-
await mediaSource.appendNext();
|
|
55026
|
-
await mediaSource.appendNext();
|
|
55027
|
-
await onPlaying(player);
|
|
55028
|
-
expect(stallEnteredSpy).not.toHaveBeenCalled();
|
|
55029
|
-
const nextEntered = stallEnteredSpy.next(
|
|
55030
|
-
30,
|
|
55031
|
-
"stallEntered after hitting end of buffer timed out after {timeout}s"
|
|
55032
|
-
);
|
|
55033
|
-
await player.seekTo(5);
|
|
55034
|
-
await nextEntered;
|
|
55188
|
+
const { player, mediaSource, stallEnteredSpy, stallEndedSpy } = await arrangeStall();
|
|
55035
55189
|
const nextEnded = stallEndedSpy.next(
|
|
55036
55190
|
10,
|
|
55037
55191
|
"stallEnded after data buffered timed out after {timeout}s"
|
|
@@ -55040,7 +55194,62 @@ x`);
|
|
|
55040
55194
|
const ended = await nextEnded;
|
|
55041
55195
|
expect(ended.reason).toBe("playing");
|
|
55042
55196
|
expect(stallEnteredSpy).toHaveBeenCalledTimes(1);
|
|
55197
|
+
expect(stallEndedSpy).toHaveBeenCalledTimes(1);
|
|
55043
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);
|
|
55044
55253
|
});
|
|
55045
55254
|
});
|
|
55046
55255
|
});
|
|
@@ -55769,6 +55978,7 @@ x`);
|
|
|
55769
55978
|
var ANCHOR_MS = Date.parse(ANCHOR_ISO);
|
|
55770
55979
|
var CONTENT_ASSET = vinylTestAssets.hls.live_static_video_audio_60s_4s;
|
|
55771
55980
|
var AD_ASSET = vinylTestAssets.hls.live_static_video_audio_60s_2s;
|
|
55981
|
+
var SHORT_AD_ASSET = vinylTestAssets.hls.edge_static_video_1s;
|
|
55772
55982
|
function injectingManifestProvider(mainUrl, interstitials) {
|
|
55773
55983
|
return async () => {
|
|
55774
55984
|
const mainText = await (await fetch(mainUrl)).text();
|
|
@@ -56403,8 +56613,8 @@ x`);
|
|
|
56403
56613
|
id: "postroll-1",
|
|
56404
56614
|
startTime: 0,
|
|
56405
56615
|
duration: 4,
|
|
56406
|
-
//
|
|
56407
|
-
assetList: [{ uri:
|
|
56616
|
+
// A 1s ad so the break ends quickly on its own `ended`.
|
|
56617
|
+
assetList: [{ uri: SHORT_AD_ASSET, duration: 1 }],
|
|
56408
56618
|
cue: "POST"
|
|
56409
56619
|
}
|
|
56410
56620
|
])
|