@amazon/vinyl 1.2.1 → 1.2.2
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 +13 -4
- package/dist/index.cjs.map +2 -2
- package/dist/index.js +18 -9
- package/dist/index.js.map +3 -3
- package/dist/test/integ.es5.js +60 -60
- package/dist/test/integ.js +49 -4
- package/dist/test/integ.js.map +2 -2
- package/dist/types/current/ad/AdBreakInfo.d.ts.map +1 -1
- 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 +58 -22
- package/dist/types/current/ad/discoverHlsInterstitials.d.ts.map +1 -1
- package/dist/types/current/ad/discoverHlsInterstitials.js +5 -3
- package/dist/types/current/drm/DrmControllerImpl.d.ts +6 -0
- package/dist/types/current/drm/DrmControllerImpl.d.ts.map +1 -1
- package/dist/types/current/drm/DrmControllerImpl.js +13 -1
- package/dist/types/current/playback/createStallDetector.d.ts +43 -0
- package/dist/types/current/playback/createStallDetector.d.ts.map +1 -0
- package/dist/types/current/playback/createStallDetector.js +101 -0
- package/dist/types/current/playback/createStallMonitor.d.ts +45 -0
- package/dist/types/current/playback/createStallMonitor.d.ts.map +1 -0
- package/dist/types/current/playback/createStallMonitor.js +99 -0
- package/dist/types/current/streaming/AudioOptions.d.ts +27 -0
- package/dist/types/current/streaming/AudioOptions.d.ts.map +1 -1
- package/dist/types/current/streaming/AudioOptions.js +5 -1
- 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/createTimelineContentTypesValue.d.ts +21 -0
- package/dist/types/current/streaming/createTimelineContentTypesValue.d.ts.map +1 -0
- package/dist/types/current/streaming/createTimelineContentTypesValue.js +25 -0
- package/dist/types/current/text/SidecarTextTrackLoader.d.ts.map +1 -1
- package/dist/types/current/text/SidecarTextTrackLoader.js +4 -4
- package/dist/types/current/text/TextTrack.d.ts.map +1 -1
- package/dist/types/current/text/discoverDashTextTracks.d.ts +2 -2
- package/dist/types/current/text/discoverDashTextTracks.js +4 -3
- package/dist/types/current/text/parseWebVtt.d.ts.map +1 -1
- package/dist/types/current/text/parseWebVtt.js +3 -2
- package/dist/types/current/track/mse/MseTrack.d.ts +0 -1
- package/dist/types/current/track/mse/MseTrack.d.ts.map +1 -1
- package/dist/types/current/track/mse/MseTrack.js +4 -5
- package/dist/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/types/ts3.7/ad/AdControllerImpl.d.ts +18 -2
- package/dist/types/ts3.7/drm/DrmControllerImpl.d.ts +6 -0
- package/dist/types/ts3.7/playback/createStallDetector.d.ts +43 -0
- package/dist/types/ts3.7/playback/createStallMonitor.d.ts +45 -0
- package/dist/types/ts3.7/streaming/AudioOptions.d.ts +27 -0
- package/dist/types/ts3.7/streaming/LoadMetric.d.ts +15 -1
- package/dist/types/ts3.7/streaming/createTimelineContentTypesValue.d.ts +21 -0
- package/dist/types/ts3.7/text/discoverDashTextTracks.d.ts +2 -2
- package/dist/types/ts3.7/track/mse/MseTrack.d.ts +0 -1
- package/dist/types/ts4.1/ad/AdControllerImpl.d.ts +18 -2
- package/dist/types/ts4.1/drm/DrmControllerImpl.d.ts +6 -0
- package/dist/types/ts4.1/playback/createStallDetector.d.ts +43 -0
- package/dist/types/ts4.1/playback/createStallMonitor.d.ts +45 -0
- package/dist/types/ts4.1/streaming/AudioOptions.d.ts +27 -0
- package/dist/types/ts4.1/streaming/LoadMetric.d.ts +15 -1
- package/dist/types/ts4.1/streaming/createTimelineContentTypesValue.d.ts +21 -0
- package/dist/types/ts4.1/text/discoverDashTextTracks.d.ts +2 -2
- package/dist/types/ts4.1/track/mse/MseTrack.d.ts +0 -1
- package/dist/types/ts4.7/ad/AdControllerImpl.d.ts +18 -2
- package/dist/types/ts4.7/drm/DrmControllerImpl.d.ts +6 -0
- package/dist/types/ts4.7/playback/createStallDetector.d.ts +43 -0
- package/dist/types/ts4.7/playback/createStallMonitor.d.ts +45 -0
- package/dist/types/ts4.7/streaming/AudioOptions.d.ts +27 -0
- package/dist/types/ts4.7/streaming/LoadMetric.d.ts +15 -1
- package/dist/types/ts4.7/streaming/createTimelineContentTypesValue.d.ts +21 -0
- package/dist/types/ts4.7/text/discoverDashTextTracks.d.ts +2 -2
- package/dist/types/ts4.7/track/mse/MseTrack.d.ts +0 -1
- package/dist/unit/types/current/ad/AdControllerImpl.test.js +203 -5
- package/dist/unit/types/current/ad/discoverHlsInterstitials.test.js +2 -1
- package/dist/unit/types/current/drm/DrmControllerImpl.test.js +30 -1
- package/dist/unit/types/current/playback/createStallDetector.test.d.ts +2 -0
- package/dist/unit/types/current/playback/createStallDetector.test.d.ts.map +1 -0
- package/dist/unit/types/current/playback/createStallDetector.test.js +163 -0
- package/dist/unit/types/current/playback/createStallMonitor.test.d.ts +2 -0
- package/dist/unit/types/current/playback/createStallMonitor.test.d.ts.map +1 -0
- package/dist/unit/types/current/playback/createStallMonitor.test.js +163 -0
- package/dist/unit/types/current/streaming/MseTrack.test.js +15 -0
- package/dist/unit/types/current/streaming/createTimelineContentTypesValue.test.d.ts +2 -0
- package/dist/unit/types/current/streaming/createTimelineContentTypesValue.test.d.ts.map +1 -0
- package/dist/unit/types/current/streaming/createTimelineContentTypesValue.test.js +57 -0
- package/dist/unit/types/current/text/TextTrack.test.d.ts +2 -0
- package/dist/unit/types/current/text/TextTrack.test.d.ts.map +1 -0
- package/dist/unit/types/current/text/TextTrack.test.js +12 -0
- package/dist/unit/types/current/text/discoverDashTextTracks.test.js +17 -0
- package/dist/unit/types/current/text/parseWebVtt.test.js +26 -0
- package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/vinylTestUtil/types/current/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdBreakInfo.d.ts","sourceRoot":"","sources":["../../../../src/ad/AdBreakInfo.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvD,MAAM,WAAW,QAAQ;IACrB;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAE3B;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAA;CACjC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAA;AAEjE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IAEvB;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IACxB;;;OAGG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IAEnB;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAE1B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAEhC;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAA;IAEpC;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IAEnC;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;IAEhC;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IAEtB;;;;;;;;;OASG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAEpC;;;;;;;OAOG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAEpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAA;IAE5C;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;CAC1D;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IACxB,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IAEvB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACnC;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,WAAW,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"AdBreakInfo.d.ts","sourceRoot":"","sources":["../../../../src/ad/AdBreakInfo.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvD,MAAM,WAAW,QAAQ;IACrB;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAE3B;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAA;CACjC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAA;AAEjE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IAEvB;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IACxB;;;OAGG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IAEnB;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAE1B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAEhC;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAA;IAEpC;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IAEnC;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;IAEhC;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IAEtB;;;;;;;;;OASG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAEpC;;;;;;;OAOG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAEpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAA;IAE5C;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;CAC1D;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IACxB,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IAEvB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACnC;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,WAAW,EAAE,CAAA;AAEhD;;GAEG;AACH,MAAM,WAAW,MAAM;IACnB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IAEnB,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAE1B,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAEhC;;;;OAIG;IACH,QAAQ,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAA;CAChC;AAED,MAAM,MAAM,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA"}
|
|
@@ -7,6 +7,15 @@ import type { AdsProvider } from './AdsProvider';
|
|
|
7
7
|
* The % playhead start tolerance for playback progress when emitting ad progress events.
|
|
8
8
|
*/
|
|
9
9
|
export declare const AD_START_TOLERANCE = 0.05;
|
|
10
|
+
/**
|
|
11
|
+
* When a break's X-PLAYOUT-LIMIT budget is reached, an ad whose real media end is
|
|
12
|
+
* within this many seconds is allowed to finish on its own `ended` rather than
|
|
13
|
+
* being cut. The limit is a coarse, often whole-second budget while the media's
|
|
14
|
+
* true length is fractional, so without this grace an ad effectively as long as
|
|
15
|
+
* its budget would be clipped a fraction short. The tolerance is measured against
|
|
16
|
+
* the playback-reported (media) duration, not the ad's declared duration.
|
|
17
|
+
*/
|
|
18
|
+
export declare const PLAYOUT_LIMIT_TOLERANCE = 1;
|
|
10
19
|
export interface AdControllerImplDeps {
|
|
11
20
|
readonly playbackController: ReadonlyPlaybackController;
|
|
12
21
|
}
|
|
@@ -129,8 +138,15 @@ export declare class AdControllerImpl extends EventHostImpl<AdEventMap> implemen
|
|
|
129
138
|
private emitAdProgressEvents;
|
|
130
139
|
private emitTimeUpdate;
|
|
131
140
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
141
|
+
* Enforces the break-level playout limit (X-PLAYOUT-LIMIT): a budget shared
|
|
142
|
+
* across all of the break's ads. When cumulative playout reaches it, the
|
|
143
|
+
* whole break ends, dropping any ads that have not started.
|
|
144
|
+
*
|
|
145
|
+
* This is deliberately NOT a per-ad cap. Each ad asset is its own source and
|
|
146
|
+
* ends on its media `ended` (see the 'ended' handler), which is what advances
|
|
147
|
+
* a multi-ad break. Ending an ad at its declared per-ad DURATION instead would
|
|
148
|
+
* truncate the real asset whenever a packager under-declares it — e.g. rounds
|
|
149
|
+
* a 6.732s asset down to a whole-second 6 — cutting off the fractional tail.
|
|
134
150
|
*/
|
|
135
151
|
private enforcePlayoutLimits;
|
|
136
152
|
private setPendingBreaks;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdControllerImpl.d.ts","sourceRoot":"","sources":["../../../../src/ad/AdControllerImpl.ts"],"names":[],"mappings":"AAKA,OAAO,EAGH,aAAa,EAehB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,KAAK,WAAW,EAEhB,KAAK,MAAM,EACX,KAAK,QAAQ,EAChB,MAAM,eAAe,CAAA;AACtB,OAAO,KAAK,EAER,YAAY,EACZ,UAAU,EAEb,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,wCAAwC,CAAA;AAExF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD;;GAEG;AACH,eAAO,MAAM,kBAAkB,OAAO,CAAA;AAEtC,MAAM,WAAW,oBAAoB;IACjC,QAAQ,CAAC,kBAAkB,EAAE,0BAA0B,CAAA;CAC1D;AAED,MAAM,WAAW,uBAAuB;IACpC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAE9B;;;;;;OAMG;IACH,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;CACpC;AAED,eAAO,MAAM,8BAA8B,EAAE,uBAG5C,CAAA;AA2CD;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,gBACT,SAAQ,aAAa,CAAC,UAAU,CAChC,YAAW,YAAY;IAkCnB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAhCzB,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,MAAM,CAEjC;IAED,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,SAAS,CAAwB;IACzC,OAAO,CAAC,cAAc,CAA2B;IACjD,OAAO,CAAC,WAAW,CAA2B;IAE9C,OAAO,CAAC,UAAU,CAAkB;IAGpC,OAAO,CAAC,iBAAiB,CAAkB;IAI3C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAwB;IAE1D,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,eAAe,CAAoB;IAG3C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAwB;IAI3D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwB;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,WAAW,CAAQ;gBAGN,IAAI,EAAE,oBAAoB,EAC3C,OAAO,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC;IAuD9C,iBAAiB,IAAI,IAAI;IAMzB,OAAO,CAAC,iBAAiB;IAKzB,8EAA8E;IAC9E,OAAO,CAAC,SAAS;IAUjB;;;;;OAKG;YACW,gBAAgB;IAwB9B,IAAI,eAAe,IAAI,QAAQ,GAAG,IAAI,CAErC;IAED,OAAO,CAAC,WAAW;IAqBnB;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAK3B,cAAc,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI;IAkC5C,YAAY,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAI3C,aAAa,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;YAIpC,SAAS;IAgCvB,OAAO,KAAK,WAAW,GAEtB;IAED,WAAW,IAAI,IAAI;IAWnB;;OAEG;IACH,OAAO,CAAC,eAAe;IA4CvB,IAAI,cAAc,IAAI,WAAW,GAAG,IAAI,CAEvC;IAED;;OAEG;IACH,OAAO,CAAC,UAAU;IAqBlB;;;OAGG;IACH,OAAO,CAAC,aAAa;
|
|
1
|
+
{"version":3,"file":"AdControllerImpl.d.ts","sourceRoot":"","sources":["../../../../src/ad/AdControllerImpl.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,15 @@ import { AdError } from './AdError';
|
|
|
8
8
|
* The % playhead start tolerance for playback progress when emitting ad progress events.
|
|
9
9
|
*/
|
|
10
10
|
export const AD_START_TOLERANCE = 0.05;
|
|
11
|
+
/**
|
|
12
|
+
* When a break's X-PLAYOUT-LIMIT budget is reached, an ad whose real media end is
|
|
13
|
+
* within this many seconds is allowed to finish on its own `ended` rather than
|
|
14
|
+
* being cut. The limit is a coarse, often whole-second budget while the media's
|
|
15
|
+
* true length is fractional, so without this grace an ad effectively as long as
|
|
16
|
+
* its budget would be clipped a fraction short. The tolerance is measured against
|
|
17
|
+
* the playback-reported (media) duration, not the ad's declared duration.
|
|
18
|
+
*/
|
|
19
|
+
export const PLAYOUT_LIMIT_TOLERANCE = 1;
|
|
11
20
|
export const defaultAdControllerImplOptions = {
|
|
12
21
|
adLoadTimeout: 15,
|
|
13
22
|
preloadAheadTime: 10,
|
|
@@ -173,7 +182,7 @@ export class AdControllerImpl extends EventHostImpl {
|
|
|
173
182
|
*/
|
|
174
183
|
providerInterrupted() {
|
|
175
184
|
const provider = this.adsProvider;
|
|
176
|
-
return () => provider !== this.adsProvider;
|
|
185
|
+
return () => this.disposed || provider !== this.adsProvider;
|
|
177
186
|
}
|
|
178
187
|
setAdsProvider(provider) {
|
|
179
188
|
if (this.adsProvider === provider)
|
|
@@ -331,8 +340,16 @@ export class AdControllerImpl extends EventHostImpl {
|
|
|
331
340
|
// The break may have been completed or swapped out (a content
|
|
332
341
|
// change, skip, or playout-limit) while its ads were resolving;
|
|
333
342
|
// don't start an ad for an abandoned break.
|
|
334
|
-
if (this.disposed
|
|
343
|
+
if (this.disposed)
|
|
344
|
+
return;
|
|
345
|
+
if (adBreakState.completedReason) {
|
|
346
|
+
// Whoever completed it could not advance (this call still
|
|
347
|
+
// holds `loadingNext`), and nothing else drains the pending
|
|
348
|
+
// list — a non-empty list blocks midroll ingress outright.
|
|
349
|
+
if (!this.adBreakState)
|
|
350
|
+
await nextBreak();
|
|
335
351
|
return;
|
|
352
|
+
}
|
|
336
353
|
if (ad) {
|
|
337
354
|
this.startAd(ad);
|
|
338
355
|
}
|
|
@@ -508,16 +525,19 @@ export class AdControllerImpl extends EventHostImpl {
|
|
|
508
525
|
if (!adState.quartile.first &&
|
|
509
526
|
currProgress > 0.25 &&
|
|
510
527
|
progressStart < AD_START_TOLERANCE) {
|
|
528
|
+
adState.quartile.first = true;
|
|
511
529
|
this.dispatch('adFirstQuartile', createAdProgressEvent(adState));
|
|
512
530
|
}
|
|
513
531
|
if (!adState.quartile.midpoint &&
|
|
514
532
|
currProgress > 0.5 &&
|
|
515
533
|
progressStart < 0.25 + AD_START_TOLERANCE) {
|
|
534
|
+
adState.quartile.midpoint = true;
|
|
516
535
|
this.dispatch('adMidpoint', createAdProgressEvent(adState));
|
|
517
536
|
}
|
|
518
537
|
if (!adState.quartile.third &&
|
|
519
538
|
currProgress > 0.75 &&
|
|
520
539
|
progressStart < 0.5 + AD_START_TOLERANCE) {
|
|
540
|
+
adState.quartile.third = true;
|
|
521
541
|
this.dispatch('adThirdQuartile', createAdProgressEvent(adState));
|
|
522
542
|
}
|
|
523
543
|
// adEnded is not on progress but on a call to `endAd()`.
|
|
@@ -560,30 +580,43 @@ export class AdControllerImpl extends EventHostImpl {
|
|
|
560
580
|
});
|
|
561
581
|
}
|
|
562
582
|
/**
|
|
563
|
-
*
|
|
564
|
-
*
|
|
583
|
+
* Enforces the break-level playout limit (X-PLAYOUT-LIMIT): a budget shared
|
|
584
|
+
* across all of the break's ads. When cumulative playout reaches it, the
|
|
585
|
+
* whole break ends, dropping any ads that have not started.
|
|
586
|
+
*
|
|
587
|
+
* This is deliberately NOT a per-ad cap. Each ad asset is its own source and
|
|
588
|
+
* ends on its media `ended` (see the 'ended' handler), which is what advances
|
|
589
|
+
* a multi-ad break. Ending an ad at its declared per-ad DURATION instead would
|
|
590
|
+
* truncate the real asset whenever a packager under-declares it — e.g. rounds
|
|
591
|
+
* a 6.732s asset down to a whole-second 6 — cutting off the fractional tail.
|
|
565
592
|
*/
|
|
566
593
|
enforcePlayoutLimits(adState) {
|
|
594
|
+
if (!adState.started)
|
|
595
|
+
return;
|
|
596
|
+
const { parentBreak } = adState;
|
|
597
|
+
// Enforce independent of a finite content duration so live ads with a
|
|
598
|
+
// declared playout limit are still bounded.
|
|
599
|
+
const limit = parentBreak.adBreak.playoutLimit;
|
|
600
|
+
if (limit == null)
|
|
601
|
+
return;
|
|
567
602
|
const pC = this.deps.playbackController;
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
if
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
this.endAd(adState);
|
|
581
|
-
}
|
|
582
|
-
else if (ad.duration != null && elapsed >= ad.duration) {
|
|
583
|
-
// This ad reached its declared duration: advance to the next.
|
|
584
|
-
this.endAd(adState);
|
|
585
|
-
}
|
|
603
|
+
const elapsed = Math.max(0, pC.currentTime - adState.timeStart);
|
|
604
|
+
if (parentBreak.playoutElapsed + elapsed < limit)
|
|
605
|
+
return;
|
|
606
|
+
// Budget reached — but if the current ad is essentially at its natural
|
|
607
|
+
// media end (within PLAYOUT_LIMIT_TOLERANCE of the playback-reported
|
|
608
|
+
// duration), let it finish on its own `ended` rather than clipping the
|
|
609
|
+
// tail. A known finite, positive duration is required; an unknown (0) or
|
|
610
|
+
// non-finite duration (e.g. live) enforces the limit strictly.
|
|
611
|
+
if (isFinite(pC.duration) &&
|
|
612
|
+
pC.duration > 0 &&
|
|
613
|
+
pC.duration - pC.currentTime <= PLAYOUT_LIMIT_TOLERANCE) {
|
|
614
|
+
return;
|
|
586
615
|
}
|
|
616
|
+
// The break's total playout limit is reached: end the whole break,
|
|
617
|
+
// dropping any ads that have not started.
|
|
618
|
+
parentBreak.clear();
|
|
619
|
+
this.endAd(adState);
|
|
587
620
|
}
|
|
588
621
|
setPendingBreaks(newPending) {
|
|
589
622
|
this.pendingAdBreaks = newPending.filter((adBreak) => !this.isBreakSuppressed(adBreak));
|
|
@@ -623,6 +656,9 @@ export class AdControllerImpl extends EventHostImpl {
|
|
|
623
656
|
*/
|
|
624
657
|
dispose() {
|
|
625
658
|
super.dispose();
|
|
659
|
+
// Tear down the ads-provider subscription so a late adsChange can't
|
|
660
|
+
// re-run ad discovery (and log) after dispose.
|
|
661
|
+
this.adsProviderSub?.();
|
|
626
662
|
this.disposer.dispose();
|
|
627
663
|
// Detach any active break so currentAd/currentAdBreak read null after
|
|
628
664
|
// dispose, even if a break's ad list resolves late (startAd is already
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discoverHlsInterstitials.d.ts","sourceRoot":"","sources":["../../../../src/ad/discoverHlsInterstitials.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAgB,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"discoverHlsInterstitials.d.ts","sourceRoot":"","sources":["../../../../src/ad/discoverHlsInterstitials.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAgB,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAU9E,OAAO,KAAK,EAER,WAAW,EAMd,MAAM,eAAe,CAAA;AActB;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CACpC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,MAAM,EACf,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,GAChC,OAAO,CAAC,WAAW,CAAC,CAmDtB"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
5
|
import { HLS_INTERSTITIAL_CLASS } from '@amazon/vinyl-hls-parser';
|
|
6
|
-
import { memoize, requestWithRetry, resolveUrl, } from '@amazon/vinyl-util';
|
|
6
|
+
import { memoize, readResponseBody, requestWithRetry, resolveUrl, } from '@amazon/vinyl-util';
|
|
7
7
|
/**
|
|
8
8
|
* How close to the start or end of content a break must be to be classified as
|
|
9
9
|
* a pre-roll or post-roll rather than a mid-roll, in seconds.
|
|
@@ -215,14 +215,16 @@ function resolveHlsAssetContent({ range, startTime, duration, baseUrl, }) {
|
|
|
215
215
|
async function resolveHlsAssetList({ assetListUrl, rangeId, startTime, baseUrl, }) {
|
|
216
216
|
const url = resolveUrl(assetListUrl, baseUrl);
|
|
217
217
|
const response = await requestWithRetry(url);
|
|
218
|
-
const json = await response
|
|
218
|
+
const json = await readResponseBody(response, 'json');
|
|
219
219
|
const assets = json.ASSETS ?? [];
|
|
220
220
|
const ads = assets.map((asset, i) => {
|
|
221
221
|
return {
|
|
222
222
|
id: `${rangeId}-${i}`,
|
|
223
223
|
startTime,
|
|
224
224
|
duration: asset.DURATION ?? null,
|
|
225
|
-
|
|
225
|
+
// Relative to the asset list, which need not sit alongside the
|
|
226
|
+
// media playlist.
|
|
227
|
+
uri: resolveUrl(asset.URI, url),
|
|
226
228
|
};
|
|
227
229
|
});
|
|
228
230
|
return { ads, skipControl: parseSkipControl(json['SKIP-CONTROL']) };
|
|
@@ -11,6 +11,12 @@ import type { MediaFormatMetadata } from '../streaming/MediaQualityMetadata';
|
|
|
11
11
|
* indicates a stalled promise result from the license provider.
|
|
12
12
|
*/
|
|
13
13
|
export declare const LICENSE_TIMEOUT = 90;
|
|
14
|
+
/**
|
|
15
|
+
* An excessive number of concurrent key sessions. Sessions are expected to be
|
|
16
|
+
* closed as tracks deactivate; a count this high signals sessions are leaking
|
|
17
|
+
* (never closing) rather than a legitimate playback need.
|
|
18
|
+
*/
|
|
19
|
+
export declare const EXCESSIVE_SESSION_COUNT = 20;
|
|
14
20
|
/**
|
|
15
21
|
* Dependencies for creating DrmController.
|
|
16
22
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DrmControllerImpl.d.ts","sourceRoot":"","sources":["../../../../src/drm/DrmControllerImpl.ts"],"names":[],"mappings":"AAKA,OAAO,EAUH,aAAa,
|
|
1
|
+
{"version":3,"file":"DrmControllerImpl.d.ts","sourceRoot":"","sources":["../../../../src/drm/DrmControllerImpl.ts"],"names":[],"mappings":"AAKA,OAAO,EAUH,aAAa,EAMb,KAAK,KAAK,EAKV,KAAK,aAAa,EAIrB,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EAEH,KAAK,UAAU,EAGlB,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EACR,aAAa,EACb,qBAAqB,EACrB,mBAAmB,EACtB,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EACR,SAAS,EAET,0BAA0B,EAC1B,eAAe,EACf,qBAAqB,EAExB,MAAM,uBAAuB,CAAA;AAK9B,OAAO,KAAK,EAER,mBAAmB,EACtB,MAAM,mCAAmC,CAAA;AAU1C;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,KAAK,CAAA;AAGjC;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,KAAK,CAAA;AAEzC;;GAEG;AACH,MAAM,WAAW,qBAAqB;IAClC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAA;IAChC,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAA;CACvC;AAED,MAAM,WAAW,yBAAyB;IACtC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAA;IACvC,QAAQ,CAAC,KAAK,EAAE,0BAA0B,CAAA;CAC7C;AAED,qBAAa,iBACT,SAAQ,aAAa,CAAC,qBAAqB,CAC3C,YAAW,aAAa;IAqBpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAnBzB,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,MAAM,CAEjC;IAED,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAY;IACxC,OAAO,CAAC,OAAO,CAAY;IAE3B,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,gBAAgB,CAAwC;IAChE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8B;IAEvD,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,YAAY,CAA6B;IAEjD,OAAO,CAAC,mBAAmB,CAAyC;IACpE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;gBAGvB,IAAI,EAAE,qBAAqB,EAC5C,OAAO,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAgBxC,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,IAAI;IAWpD,OAAO,KAAK,eAAe,GAE1B;IAED,IAAI,KAAK,IAAI,KAAK,GAAG,IAAI,CAExB;IAED,cAAc,IAAI,OAAO;IAInB,WAAW,CACb,OAAO,EAAE,mBAAmB,GAC7B,OAAO,CAAC,mBAAmB,CAAC;IAS/B,OAAO,CAAC,sBAAsB,CA0B7B;IAED,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CA8BpC;IAED;;;OAGG;YACW,eAAe;IAqB7B;;;;OAIG;YACW,kBAAkB;IAyDhC,qBAAqB,CACjB,OAAO,EAAE,mBAAmB,GAAG,IAAI,EACnC,KAAK,CAAC,EAAE,aAAa,GACtB,IAAI;IASP,mBAAmB,CACf,OAAO,EAAE,mBAAmB,GAAG,IAAI,EACnC,KAAK,CAAC,EAAE,aAAa,GACtB,IAAI;IAQP;;;OAGG;YACW,yBAAyB;IA+BvC;;OAEG;YACW,YAAY;IAiC1B;;OAEG;IACH,OAAO,CAAC,gCAAgC;IAyDxC,OAAO,CAAC,wBAAwB;IAiBhC;;OAEG;YACW,gBAAgB;IAmD9B;;OAEG;YACW,cAAc;IA8C5B;;;OAGG;IACH,aAAa;IAKb,OAAO,CAAC,YAAY;IAUpB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAK3B;IAED;;OAEG;IACH,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED;;OAEG;IACH,OAAO,CAAC,cAAc;IAUtB,SAAS,CAAC,eAAe;IAIzB,KAAK;IA2BL,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,OAAO,IAAI,IAAI;IAOf,OAAO,CAAC,oBAAoB;IAU5B;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CASnC;CACJ"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
3
|
* SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
*/
|
|
5
|
-
import { AbortError, base64ToByteArray, buffersEqual, bufferToByteArray, clone, compareBy, createDisposer, ErrorOrigin, EventHostImpl, getUserAgentInfo, isSilentError, logDebug, logVerbose, memoize, noop, normalizeHeadersInit, Os, remove, resolveValueProvider, withTimeout, } from '@amazon/vinyl-util';
|
|
5
|
+
import { AbortError, base64ToByteArray, buffersEqual, bufferToByteArray, clone, compareBy, createDisposer, ErrorOrigin, EventHostImpl, getUserAgentInfo, isSilentError, logDebug, logVerbose, logWarn, memoize, noop, normalizeHeadersInit, Os, remove, resolveValueProvider, withTimeout, } from '@amazon/vinyl-util';
|
|
6
6
|
import { instanceOf, object } from '@amazon/vinyl-validation';
|
|
7
7
|
import { DrmRobustness, } from './DrmOptions';
|
|
8
8
|
import { DrmError } from './error/DrmError';
|
|
@@ -20,6 +20,12 @@ import { createFairPlaySessionInitData } from './util/createFairPlaySessionInitD
|
|
|
20
20
|
*/
|
|
21
21
|
export const LICENSE_TIMEOUT = 90;
|
|
22
22
|
const LICENSE_TIMEOUT_MESSAGE = 'License provider timed out after {time}s';
|
|
23
|
+
/**
|
|
24
|
+
* An excessive number of concurrent key sessions. Sessions are expected to be
|
|
25
|
+
* closed as tracks deactivate; a count this high signals sessions are leaking
|
|
26
|
+
* (never closing) rather than a legitimate playback need.
|
|
27
|
+
*/
|
|
28
|
+
export const EXCESSIVE_SESSION_COUNT = 20;
|
|
23
29
|
export class DrmControllerImpl extends EventHostImpl {
|
|
24
30
|
deps;
|
|
25
31
|
get [Symbol.toStringTag]() {
|
|
@@ -152,12 +158,18 @@ export class DrmControllerImpl extends EventHostImpl {
|
|
|
152
158
|
else {
|
|
153
159
|
const newSession = await this.createNewSession(licenseServerOptions.serverCertificate, mediaKeys, initData, initDataType, drmInfo, keySystemOptions?.initDataTransformer);
|
|
154
160
|
this.sessions.push(newSession);
|
|
161
|
+
logVerbose(this, `total sessions: ${this.sessions.length}`);
|
|
162
|
+
if (this.sessions.length === EXCESSIVE_SESSION_COUNT + 1) {
|
|
163
|
+
logWarn(this, `${this.sessions.length} open key sessions exceeds ${EXCESSIVE_SESSION_COUNT}; sessions may be leaking (not closing on track deactivation)`);
|
|
164
|
+
}
|
|
155
165
|
this.dispatch('sessionCreate', {
|
|
156
166
|
initDataType: newSession.initDataType,
|
|
157
167
|
mimeType: newSession.mimeType,
|
|
158
168
|
});
|
|
159
169
|
newSession.on('closed', () => this.closeSession(newSession));
|
|
160
170
|
abort?.onAborted(() => {
|
|
171
|
+
if (this.disposed)
|
|
172
|
+
return;
|
|
161
173
|
logDebug(this, 'abort session');
|
|
162
174
|
this.closeSession(newSession);
|
|
163
175
|
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface StallDetectorOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Called when playback resumes without emitting a `playing` event (or after the detector
|
|
4
|
+
* revived it), so the caller can announce playback.
|
|
5
|
+
*/
|
|
6
|
+
readonly fabricatePlaying: () => void;
|
|
7
|
+
/** Poll interval in seconds. Default: 0.25 */
|
|
8
|
+
readonly pollInterval?: number;
|
|
9
|
+
/** Seconds the play head must be frozen before recovering. Default: 0.75 */
|
|
10
|
+
readonly stallThreshold?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Don't nudge this close to the end of buffered data; a freeze there is a
|
|
13
|
+
* legitimate wait for more data. Seconds. Default: 0.5
|
|
14
|
+
*/
|
|
15
|
+
readonly bufferEndMargin?: number;
|
|
16
|
+
/** Seconds to nudge the play head forward to prod a resume. Default: 0.1 */
|
|
17
|
+
readonly stallSkip?: number;
|
|
18
|
+
/** Max recovery attempts before giving up. Default: 3 */
|
|
19
|
+
readonly maxAttempts?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Nudge even when the playhead isn't inside a buffered range. Some WebKit versions leave a
|
|
22
|
+
* seek perpetually pending (no `seeked`) with a stale empty `buffered` even though data is
|
|
23
|
+
* appended; re-issuing the seek via a nudge can complete it. Off by default (only nudge over
|
|
24
|
+
* genuinely buffered data). Default: false
|
|
25
|
+
*/
|
|
26
|
+
readonly nudgeUnbuffered?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Watches a media element after a seek/play and drives it back into a playing state. Some WebKit
|
|
30
|
+
* versions stall: the play head freezes inside buffered data and playback never resumes (often with
|
|
31
|
+
* a stale low readyState). This polls the play head and, while the element should be advancing:
|
|
32
|
+
*
|
|
33
|
+
* - if it's frozen inside buffered data, nudges currentTime forward to prod a resume;
|
|
34
|
+
* - if there's no data at the playhead, waits (it's legitimately buffering, or between sources).
|
|
35
|
+
*
|
|
36
|
+
* When the play head progresses it calls {@link StallDetectorOptions.fabricatePlaying} if playback
|
|
37
|
+
* resumed without a `playing` event (or after the detector revived it), then stops. It also stops
|
|
38
|
+
* on pause/ended/emptied or after {@link StallDetectorOptions.maxAttempts}.
|
|
39
|
+
*
|
|
40
|
+
* Start it after a seek or play only when playback is intended — a paused element won't advance.
|
|
41
|
+
*/
|
|
42
|
+
export declare function createStallDetector(media: HTMLMediaElement, options: StallDetectorOptions): () => void;
|
|
43
|
+
//# sourceMappingURL=createStallDetector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createStallDetector.d.ts","sourceRoot":"","sources":["../../../../src/playback/createStallDetector.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,oBAAoB;IACjC;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,EAAE,MAAM,IAAI,CAAA;IACrC,8CAA8C;IAC9C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,4EAA4E;IAC5E,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CACrC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAC/B,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE,oBAAoB,GAC9B,MAAM,IAAI,CAgEZ"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Watches a media element after a seek/play and drives it back into a playing state. Some WebKit
|
|
7
|
+
* versions stall: the play head freezes inside buffered data and playback never resumes (often with
|
|
8
|
+
* a stale low readyState). This polls the play head and, while the element should be advancing:
|
|
9
|
+
*
|
|
10
|
+
* - if it's frozen inside buffered data, nudges currentTime forward to prod a resume;
|
|
11
|
+
* - if there's no data at the playhead, waits (it's legitimately buffering, or between sources).
|
|
12
|
+
*
|
|
13
|
+
* When the play head progresses it calls {@link StallDetectorOptions.fabricatePlaying} if playback
|
|
14
|
+
* resumed without a `playing` event (or after the detector revived it), then stops. It also stops
|
|
15
|
+
* on pause/ended/emptied or after {@link StallDetectorOptions.maxAttempts}.
|
|
16
|
+
*
|
|
17
|
+
* Start it after a seek or play only when playback is intended — a paused element won't advance.
|
|
18
|
+
*/
|
|
19
|
+
export function createStallDetector(media, options) {
|
|
20
|
+
const { fabricatePlaying } = options;
|
|
21
|
+
const pollInterval = options.pollInterval ?? 0.25;
|
|
22
|
+
const stallThreshold = options.stallThreshold ?? 0.75;
|
|
23
|
+
const bufferEndMargin = options.bufferEndMargin ?? 0.5;
|
|
24
|
+
const stallSkip = options.stallSkip ?? 0.1;
|
|
25
|
+
const maxAttempts = options.maxAttempts ?? 3;
|
|
26
|
+
const nudgeUnbuffered = options.nudgeUnbuffered ?? false;
|
|
27
|
+
let lastTime = media.currentTime;
|
|
28
|
+
let stalledFor = 0;
|
|
29
|
+
let attempts = 0;
|
|
30
|
+
let sawPlaying = false;
|
|
31
|
+
let recovered = false;
|
|
32
|
+
const onPlaying = () => {
|
|
33
|
+
sawPlaying = true;
|
|
34
|
+
};
|
|
35
|
+
// A cleared source (track unload) means there's nothing to recover.
|
|
36
|
+
const onEmptied = () => stop();
|
|
37
|
+
media.addEventListener('playing', onPlaying);
|
|
38
|
+
media.addEventListener('emptied', onEmptied);
|
|
39
|
+
const stop = () => {
|
|
40
|
+
clearInterval(id);
|
|
41
|
+
media.removeEventListener('playing', onPlaying);
|
|
42
|
+
media.removeEventListener('emptied', onEmptied);
|
|
43
|
+
};
|
|
44
|
+
const id = setInterval(() => {
|
|
45
|
+
// Only a playing element is expected to advance.
|
|
46
|
+
if (media.paused || media.ended)
|
|
47
|
+
return stop();
|
|
48
|
+
if (media.currentTime !== lastTime) {
|
|
49
|
+
lastTime = media.currentTime;
|
|
50
|
+
// Progressing. Announce playback if it resumed without a `playing`
|
|
51
|
+
// event, or if the detector had to revive it (a prior `playing` was
|
|
52
|
+
// cleared by the intervening stall).
|
|
53
|
+
if (!sawPlaying || recovered)
|
|
54
|
+
fabricatePlaying();
|
|
55
|
+
return stop();
|
|
56
|
+
}
|
|
57
|
+
// Frozen. Recover only over buffered data; otherwise it's legitimately
|
|
58
|
+
// waiting to buffer (or between sources) and nudging can disrupt
|
|
59
|
+
// loading — except on platforms whose `buffered` is unreliable here
|
|
60
|
+
// ({@link nudgeUnbuffered}), where re-issuing the seek is the recovery.
|
|
61
|
+
if (!nudgeUnbuffered && !withinBufferedData(media, bufferEndMargin)) {
|
|
62
|
+
stalledFor = 0;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
stalledFor += pollInterval;
|
|
66
|
+
if (stalledFor < stallThreshold)
|
|
67
|
+
return;
|
|
68
|
+
stalledFor = 0;
|
|
69
|
+
if (attempts++ >= maxAttempts)
|
|
70
|
+
return stop();
|
|
71
|
+
recovered = true;
|
|
72
|
+
// Data is buffered at the playhead — a small nudge prods a resume.
|
|
73
|
+
// (iOS can sit here reporting a stale low readyState even though the
|
|
74
|
+
// range around the playhead is buffered.)
|
|
75
|
+
media.currentTime += stallSkip;
|
|
76
|
+
lastTime = media.currentTime;
|
|
77
|
+
}, pollInterval * 1000);
|
|
78
|
+
return stop;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* True when the play head sits inside a buffered range, clear of its end by
|
|
82
|
+
* {@link endMargin} — i.e. there is data to keep playing.
|
|
83
|
+
*
|
|
84
|
+
* A range that ends at the end of the media is treated as nudgeable up to its
|
|
85
|
+
* end: no more data is coming, so a freeze there is a stuck end-of-track (seen
|
|
86
|
+
* on progressive `src` tracks), and a nudge pushes the element to `ended`.
|
|
87
|
+
*/
|
|
88
|
+
function withinBufferedData(media, endMargin) {
|
|
89
|
+
const t = media.currentTime;
|
|
90
|
+
const duration = media.duration;
|
|
91
|
+
const buffered = media.buffered;
|
|
92
|
+
for (let i = 0; i < buffered.length; i++) {
|
|
93
|
+
const end = buffered.end(i);
|
|
94
|
+
const isEndOfMedia = Number.isFinite(duration) &&
|
|
95
|
+
duration > 0 &&
|
|
96
|
+
end >= duration - endMargin;
|
|
97
|
+
if (t >= buffered.start(i) && (t < end - endMargin || isEndOfMedia))
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type Timestamp } from '@amazon/vinyl-util';
|
|
2
|
+
import type { ReadonlyPlaybackController } from './ReadonlyPlaybackController';
|
|
3
|
+
import { StallEndedReason } from './ReadonlyPlaybackController';
|
|
4
|
+
export interface StallMonitorOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Seconds the play head must be frozen (no `timeUpdate`) while playing before a stall is
|
|
7
|
+
* reported. Default: 1
|
|
8
|
+
*/
|
|
9
|
+
readonly stallThreshold?: number;
|
|
10
|
+
/**
|
|
11
|
+
* How often (seconds) the play head freeze is checked. Default: 0.25
|
|
12
|
+
*/
|
|
13
|
+
readonly pollInterval?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface StallEndedInfo {
|
|
16
|
+
/** Time of the last `timeUpdate` observed before the play head froze. */
|
|
17
|
+
readonly started: Timestamp;
|
|
18
|
+
/** Time the stall ended. */
|
|
19
|
+
readonly ended: Timestamp;
|
|
20
|
+
/** Total seconds the play head was frozen. */
|
|
21
|
+
readonly duration: number;
|
|
22
|
+
/** Why the stall ended. */
|
|
23
|
+
readonly reason: StallEndedReason;
|
|
24
|
+
}
|
|
25
|
+
export interface StallMonitorCallbacks {
|
|
26
|
+
/** Called when a stall is first detected. */
|
|
27
|
+
readonly onStallEntered: () => void;
|
|
28
|
+
/** Called when a detected stall ends. */
|
|
29
|
+
readonly onStallEnded: (info: StallEndedInfo) => void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Watches a {@link ReadonlyPlaybackController} for stalls while playing: the play head freezing
|
|
33
|
+
* (no `timeUpdate`) for longer than {@link StallMonitorOptions.stallThreshold} seconds.
|
|
34
|
+
*
|
|
35
|
+
* Detection only runs once playback has actually been observed (a `playing` event) and is
|
|
36
|
+
* suspended on `pause`/`seeking`/`emptied` until playback is observed again — so a stall is never
|
|
37
|
+
* reported during initial loading or while awaiting a seek. A detected stall ends when the play
|
|
38
|
+
* head advances again (`playing`) or playback stops for another reason (`pause`/`seeking`/
|
|
39
|
+
* `emptied`); {@link StallEndedInfo.duration} is measured from the last `timeUpdate` before the
|
|
40
|
+
* freeze, so it reflects the full frozen time rather than only the time past the threshold.
|
|
41
|
+
*
|
|
42
|
+
* Returns a function that stops the monitor.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createStallMonitor(controller: ReadonlyPlaybackController, callbacks: StallMonitorCallbacks, options?: StallMonitorOptions): () => void;
|
|
45
|
+
//# sourceMappingURL=createStallMonitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createStallMonitor.d.ts","sourceRoot":"","sources":["../../../../src/playback/createStallMonitor.ts"],"names":[],"mappings":"AAKA,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAE/D,MAAM,WAAW,mBAAmB;IAChC;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAEhC;;OAEG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CACjC;AAED,MAAM,WAAW,cAAc;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAA;IAC3B,4BAA4B;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,2BAA2B;IAC3B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAA;CACpC;AAED,MAAM,WAAW,qBAAqB;IAClC,6CAA6C;IAC7C,QAAQ,CAAC,cAAc,EAAE,MAAM,IAAI,CAAA;IACnC,yCAAyC;IACzC,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAA;CACxD;AASD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAC9B,UAAU,EAAE,0BAA0B,EACtC,SAAS,EAAE,qBAAqB,EAChC,OAAO,GAAE,mBAAwB,GAClC,MAAM,IAAI,CA+EZ"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
import { createDisposer } from '@amazon/vinyl-util';
|
|
6
|
+
import { StallEndedReason } from './ReadonlyPlaybackController';
|
|
7
|
+
/**
|
|
8
|
+
* Minimum forward `currentTime` change (seconds) that counts as real progress. Guards the
|
|
9
|
+
* progress check against floating-point jitter so a negligible advance isn't mistaken for the
|
|
10
|
+
* play head moving.
|
|
11
|
+
*/
|
|
12
|
+
const PROGRESS_EPSILON = 1e-6;
|
|
13
|
+
/**
|
|
14
|
+
* Watches a {@link ReadonlyPlaybackController} for stalls while playing: the play head freezing
|
|
15
|
+
* (no `timeUpdate`) for longer than {@link StallMonitorOptions.stallThreshold} seconds.
|
|
16
|
+
*
|
|
17
|
+
* Detection only runs once playback has actually been observed (a `playing` event) and is
|
|
18
|
+
* suspended on `pause`/`seeking`/`emptied` until playback is observed again — so a stall is never
|
|
19
|
+
* reported during initial loading or while awaiting a seek. A detected stall ends when the play
|
|
20
|
+
* head advances again (`playing`) or playback stops for another reason (`pause`/`seeking`/
|
|
21
|
+
* `emptied`); {@link StallEndedInfo.duration} is measured from the last `timeUpdate` before the
|
|
22
|
+
* freeze, so it reflects the full frozen time rather than only the time past the threshold.
|
|
23
|
+
*
|
|
24
|
+
* Returns a function that stops the monitor.
|
|
25
|
+
*/
|
|
26
|
+
export function createStallMonitor(controller, callbacks, options = {}) {
|
|
27
|
+
const stallThresholdMs = (options.stallThreshold ?? 1) * 1000;
|
|
28
|
+
const pollIntervalMs = (options.pollInterval ?? 0.25) * 1000;
|
|
29
|
+
// Only detect once playback has been observed; gates out initial load and post-seek buffering.
|
|
30
|
+
let observedPlaying = false;
|
|
31
|
+
// Wall-clock time of the last `timeUpdate` — the reference start of any freeze.
|
|
32
|
+
let lastProgressAt = Date.now();
|
|
33
|
+
let inStall = false;
|
|
34
|
+
let pollId = null;
|
|
35
|
+
const { add, dispose } = createDisposer();
|
|
36
|
+
const endStall = (reason) => {
|
|
37
|
+
if (!inStall)
|
|
38
|
+
return;
|
|
39
|
+
inStall = false;
|
|
40
|
+
const ended = Date.now();
|
|
41
|
+
callbacks.onStallEnded({
|
|
42
|
+
started: lastProgressAt,
|
|
43
|
+
ended,
|
|
44
|
+
duration: (ended - lastProgressAt) / 1000,
|
|
45
|
+
reason,
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
const stopPoll = () => {
|
|
49
|
+
if (pollId == null)
|
|
50
|
+
return;
|
|
51
|
+
clearInterval(pollId);
|
|
52
|
+
pollId = null;
|
|
53
|
+
};
|
|
54
|
+
add(stopPoll);
|
|
55
|
+
const startPoll = () => {
|
|
56
|
+
if (pollId != null)
|
|
57
|
+
return;
|
|
58
|
+
pollId = setInterval(() => {
|
|
59
|
+
if (inStall || !observedPlaying)
|
|
60
|
+
return;
|
|
61
|
+
if (Date.now() - lastProgressAt >= stallThresholdMs) {
|
|
62
|
+
inStall = true;
|
|
63
|
+
callbacks.onStallEntered();
|
|
64
|
+
}
|
|
65
|
+
}, pollIntervalMs);
|
|
66
|
+
};
|
|
67
|
+
// Playback stopped for a non-resume reason: close any open stall and suspend detection until
|
|
68
|
+
// playback is observed again.
|
|
69
|
+
const suspend = (reason) => {
|
|
70
|
+
endStall(reason);
|
|
71
|
+
observedPlaying = false;
|
|
72
|
+
stopPoll();
|
|
73
|
+
};
|
|
74
|
+
add(controller.on('playing', () => {
|
|
75
|
+
// Resuming from a stall closes it (using the pre-resume freeze start for its duration).
|
|
76
|
+
if (inStall)
|
|
77
|
+
endStall(StallEndedReason.PLAYING);
|
|
78
|
+
// Whether resuming or freshly starting, the play head is advancing again, so restart the
|
|
79
|
+
// freeze clock. Without this the poll could immediately re-detect a stall from the stale
|
|
80
|
+
// pre-freeze timestamp before the first post-resume `timeUpdate` resets it.
|
|
81
|
+
lastProgressAt = Date.now();
|
|
82
|
+
observedPlaying = true;
|
|
83
|
+
startPoll();
|
|
84
|
+
}));
|
|
85
|
+
add(controller.on('timeUpdate', ({ previous, current }) => {
|
|
86
|
+
// Browsers emit `timeUpdate` while frozen (unchanged/backward `currentTime`); only a
|
|
87
|
+
// forward advance is progress, else a stall is ended and re-detected, doubling events.
|
|
88
|
+
if (previous != null && current - previous <= PROGRESS_EPSILON)
|
|
89
|
+
return;
|
|
90
|
+
// The play head advanced: close any open stall, then reset the freeze clock.
|
|
91
|
+
if (inStall)
|
|
92
|
+
endStall(StallEndedReason.PLAYING);
|
|
93
|
+
lastProgressAt = Date.now();
|
|
94
|
+
}));
|
|
95
|
+
add(controller.on('pause', () => suspend(StallEndedReason.PAUSE)));
|
|
96
|
+
add(controller.on('seeking', () => suspend(StallEndedReason.SEEKING)));
|
|
97
|
+
add(controller.on('emptied', () => suspend(StallEndedReason.EMPTIED)));
|
|
98
|
+
return dispose;
|
|
99
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ObjectSchema } from '@amazon/vinyl-validation';
|
|
2
|
+
import type { Maybe } from '@amazon/vinyl-util';
|
|
2
3
|
/**
|
|
3
4
|
* Criteria used to pick which audio rendition to play.
|
|
4
5
|
*/
|
|
@@ -33,6 +34,32 @@ export interface AudioOptions {
|
|
|
33
34
|
* The audio selection criteria (language / descriptive).
|
|
34
35
|
*/
|
|
35
36
|
readonly selection?: AudioSelection;
|
|
37
|
+
/**
|
|
38
|
+
* When `true`, disables the audio sampling-rate support filter, so audio
|
|
39
|
+
* renditions whose sampling rate exceeds what the platform reports (via the
|
|
40
|
+
* `AudioContext` output rate) are kept and offered to playback anyway.
|
|
41
|
+
* `false` (the default) applies the filter. Changing this re-filters the
|
|
42
|
+
* timeline immediately.
|
|
43
|
+
*/
|
|
44
|
+
readonly disableSampleRateFilter?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* The maximum audio sampling rate (Hz) the sampling-rate filter treats as
|
|
47
|
+
* supported. When set, it takes precedence over the platform's
|
|
48
|
+
* `AudioContext`-reported output rate. Ignored when
|
|
49
|
+
* {@link disableSampleRateFilter} is `true`. Changing this re-filters the
|
|
50
|
+
* timeline immediately. `null`/`undefined` falls back to the reported rate.
|
|
51
|
+
*/
|
|
52
|
+
readonly maxSampleRate?: Maybe<number>;
|
|
53
|
+
/**
|
|
54
|
+
* The minimum audio sampling rate (Hz) the sampling-rate filter treats as
|
|
55
|
+
* supported. When set, renditions below it are filtered out as a soft floor
|
|
56
|
+
* (if every audio rendition is below it, the highest is kept so playback is
|
|
57
|
+
* never stranded). Applied alongside the max cap whenever the filter runs,
|
|
58
|
+
* regardless of how the max was determined; ignored when
|
|
59
|
+
* {@link disableSampleRateFilter} is `true`. Changing this re-filters the
|
|
60
|
+
* timeline immediately.
|
|
61
|
+
*/
|
|
62
|
+
readonly minSampleRate?: Maybe<number>;
|
|
36
63
|
}
|
|
37
64
|
export declare const defaultAudioOptions: AudioOptions;
|
|
38
65
|
export declare const audioOptionsValidator: ObjectSchema<AudioOptions>;
|