cursedbelt 5.101.0 → 5.103.0
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/react/engagement/beacon.js +3 -3
- package/dist/react/engagement/beacon.js.map +1 -1
- package/dist/react/engagement/beaconClient.d.ts +25 -2
- package/dist/react/engagement/beaconClient.d.ts.map +1 -1
- package/dist/react/engagement/beaconClient.js +105 -14
- package/dist/react/engagement/beaconClient.js.map +1 -1
- package/dist/react/engagement/index.d.ts +2 -1
- package/dist/react/engagement/index.d.ts.map +1 -1
- package/dist/react/engagement/index.js +4 -2
- package/dist/react/engagement/index.js.map +1 -1
- package/dist/react/engagement/playbackBeacon.d.ts +3 -0
- package/dist/react/engagement/playbackBeacon.d.ts.map +1 -0
- package/dist/react/engagement/playbackBeacon.js +27 -0
- package/dist/react/engagement/playbackBeacon.js.map +1 -0
- package/dist/react/media/MediaLightbox.js +1 -1
- package/dist/react/media/MediaLightbox.js.map +1 -1
- package/dist/react/media/VideoPlayer.d.ts +3 -1
- package/dist/react/media/VideoPlayer.d.ts.map +1 -1
- package/dist/react/media/VideoPlayer.js +62 -14
- package/dist/react/media/VideoPlayer.js.map +1 -1
- package/dist/react/media/VideoSlideContent.d.ts +12 -7
- package/dist/react/media/VideoSlideContent.d.ts.map +1 -1
- package/dist/react/media/VideoSlideContent.js +19 -9
- package/dist/react/media/VideoSlideContent.js.map +1 -1
- package/dist/react/media/mediaPhoto.d.ts +3 -0
- package/dist/react/media/mediaPhoto.d.ts.map +1 -1
- package/dist/react/media/mediaPhoto.js.map +1 -1
- package/dist/react/media/playbackWatch.d.ts +83 -0
- package/dist/react/media/playbackWatch.d.ts.map +1 -0
- package/dist/react/media/playbackWatch.js +167 -0
- package/dist/react/media/playbackWatch.js.map +1 -0
- package/dist/react/media/playerSource.d.ts +24 -0
- package/dist/react/media/playerSource.d.ts.map +1 -0
- package/dist/react/media/playerSource.js +22 -0
- package/dist/react/media/playerSource.js.map +1 -0
- package/dist/react/media/sourceFault.d.ts +5 -59
- package/dist/react/media/sourceFault.d.ts.map +1 -1
- package/dist/react/media/sourceFault.js +69 -2
- package/dist/react/media/sourceFault.js.map +1 -1
- package/dist/react/media/usePlaybackWatch.d.ts +14 -0
- package/dist/react/media/usePlaybackWatch.d.ts.map +1 -0
- package/dist/react/media/usePlaybackWatch.js +32 -0
- package/dist/react/media/usePlaybackWatch.js.map +1 -0
- package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
- package/dist/react/media-gallery/MediaGallery.js +21 -5
- package/dist/react/media-gallery/MediaGallery.js.map +1 -1
- package/dist/testing/designBaseline.d.ts +5 -1
- package/dist/testing/designBaseline.d.ts.map +1 -1
- package/dist/testing/designBaseline.js +27 -13
- package/dist/testing/designBaseline.js.map +1 -1
- package/package.json +2 -2
- package/src/react/engagement/beacon.spec.tsx +2 -1
- package/src/react/engagement/beacon.tsx +3 -3
- package/src/react/engagement/beaconClient.spec.ts +63 -6
- package/src/react/engagement/beaconClient.ts +119 -13
- package/src/react/engagement/index.ts +4 -1
- package/src/react/engagement/playbackBeacon.ts +27 -0
- package/src/react/media/MediaLightbox.tsx +2 -2
- package/src/react/media/VideoPlayer.spec.tsx +24 -0
- package/src/react/media/VideoPlayer.tsx +72 -12
- package/src/react/media/VideoSlideContent.spec.tsx +28 -0
- package/src/react/media/VideoSlideContent.tsx +49 -32
- package/src/react/media/mediaPhoto.ts +3 -0
- package/src/react/media/playbackWatch.spec.ts +196 -0
- package/src/react/media/playbackWatch.ts +218 -0
- package/src/react/media/playerSource.ts +30 -0
- package/src/react/media/sourceFault.spec.tsx +12 -0
- package/src/react/media/sourceFault.tsx +25 -1
- package/src/react/media/usePlaybackWatch.tsx +34 -0
- package/src/react/media-gallery/MediaGallery.tsx +32 -13
- package/src/testing/designBaseline.spec.ts +36 -0
- package/src/testing/designBaseline.ts +34 -15
|
@@ -35,10 +35,10 @@ export function useEngagementBeacon(place, options = {}) {
|
|
|
35
35
|
* may and may not carry.
|
|
36
36
|
*/
|
|
37
37
|
export function useEngagementAction(action, options = {}) {
|
|
38
|
-
const { basePath, fetchImpl, sessionPath, assumeSignedIn, place } = options;
|
|
38
|
+
const { basePath, fetchImpl, sessionPath, assumeSignedIn, place, beaconImpl } = options;
|
|
39
39
|
return useCallback(() => {
|
|
40
|
-
void reportAction(action, { basePath, fetchImpl, sessionPath, assumeSignedIn, place });
|
|
41
|
-
}, [action, basePath, fetchImpl, sessionPath, assumeSignedIn, place]);
|
|
40
|
+
void reportAction(action, { basePath, fetchImpl, sessionPath, assumeSignedIn, place, beaconImpl });
|
|
41
|
+
}, [action, basePath, fetchImpl, sessionPath, assumeSignedIn, place, beaconImpl]);
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
44
|
* Report wherever the browser IS, and keep reporting as it moves.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"beacon.js","sourceRoot":"","sources":["../../../src/react/engagement/beacon.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAGN,YAAY,EACZ,WAAW,EACX,mBAAmB,GACnB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAoB,EAAE,UAA8B,EAAE;IACzF,iGAAiG;IACjG,+FAA+F;IAC/F,kFAAkF;IAClF,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC;IACrE,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,yEAAyE;QACzE,6CAA6C;QAC7C,KAAK,WAAW,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAAC;IAC/E,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,UAA+B,EAAE;IACpF,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"beacon.js","sourceRoot":"","sources":["../../../src/react/engagement/beacon.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAGN,YAAY,EACZ,WAAW,EACX,mBAAmB,GACnB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAoB,EAAE,UAA8B,EAAE;IACzF,iGAAiG;IACjG,+FAA+F;IAC/F,kFAAkF;IAClF,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC;IACrE,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,yEAAyE;QACzE,6CAA6C;QAC7C,KAAK,WAAW,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAAC;IAC/E,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,UAA+B,EAAE;IACpF,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IACxF,OAAO,WAAW,CAAC,GAAG,EAAE;QACvB,KAAK,YAAY,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;IACpG,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;AACnF,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAA8B,EAAE;IACjE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC;IACrE,0EAA0E;IAC1E,uEAAuE;IACvE,uEAAuE;IACvE,SAAS,CACR,GAAG,EAAE,CAAC,mBAAmB,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,EAC/E,CAAC,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,CAAC,CAClD,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,gEAAgE;AAChE,MAAM,UAAU,gBAAgB,CAAC,KAA4B;IAC5D,iBAAiB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACtE,OAAO,IAAI,CAAC;AACb,CAAC"}
|
|
@@ -85,7 +85,7 @@ export interface ReportPlaceOptions {
|
|
|
85
85
|
* not sending it in the first place means user text does not travel at all.
|
|
86
86
|
*/
|
|
87
87
|
export declare function currentLocationPlace(): string;
|
|
88
|
-
/** Reset the de-duplication memory. Tests, and a sign-out. */
|
|
88
|
+
/** Reset the de-duplication memory, and drop any queued action unsent. Tests, and a sign-out. */
|
|
89
89
|
export declare function resetEngagementBeacon(): void;
|
|
90
90
|
/**
|
|
91
91
|
* Report an arrival at `place`. Returns whether anything was sent.
|
|
@@ -100,10 +100,27 @@ export interface ReportActionOptions extends ReportPlaceOptions {
|
|
|
100
100
|
* against the manifest exactly as it resolves a view, so the default is right for every app.
|
|
101
101
|
*/
|
|
102
102
|
place?: string;
|
|
103
|
+
/** Injected in tests; defaults to `navigator.sendBeacon`. The page-leave flush sends through it. */
|
|
104
|
+
beaconImpl?: (url: string, body: Blob) => boolean;
|
|
103
105
|
}
|
|
106
|
+
/** How long the action queue waits for quiet before it sends (forge task 4034). */
|
|
107
|
+
export declare const ACTION_QUIET_MS = 5000;
|
|
108
|
+
/** The longest an action waits however busy the page is, so a page that crashes loses 30 s at most. */
|
|
109
|
+
export declare const ACTION_MAX_WAIT_MS = 30000;
|
|
110
|
+
/** One POST carries at most this many: the recorder's `MAX_ACTION_BATCH` (cursedbelt-server). */
|
|
111
|
+
export declare const ACTION_BATCH_MAX = 100;
|
|
112
|
+
/**
|
|
113
|
+
* Send what is queued, now: ONE POST per mount. A queue of one posts `{ place, action }` as
|
|
114
|
+
* `reportAction` always has, so a recorder older than the batch reads it; more posts
|
|
115
|
+
* `{ actions }`, and a recorder that refuses the batch (an older one answers 400) gets them one by
|
|
116
|
+
* one instead. `leaving` sends through `sendBeacon`, which outlives the page. Never throws.
|
|
117
|
+
*/
|
|
118
|
+
export declare function flushEngagementActions(options?: {
|
|
119
|
+
leaving?: boolean;
|
|
120
|
+
}): Promise<void>;
|
|
104
121
|
/**
|
|
105
122
|
* Report that a FEATURE was used — `reportAction("search")` — beside the place it was used on
|
|
106
|
-
* (forge task 3470). Returns whether
|
|
123
|
+
* (forge task 3470). Returns whether it was queued to send.
|
|
107
124
|
*
|
|
108
125
|
* The owner's question is "what features do they use and ignore"; a page view cannot answer it,
|
|
109
126
|
* because one page holds several features. So the app names the action at the press, from the
|
|
@@ -117,6 +134,12 @@ export interface ReportActionOptions extends ReportPlaceOptions {
|
|
|
117
134
|
* · the same silence: session-probed (the probe's answer is shared with `reportPlace`, one per
|
|
118
135
|
* page load), fire-and-forget, never throws, never logs.
|
|
119
136
|
*
|
|
137
|
+
* 🔴 BATCHED (forge task 4034): an action is queued, and the queue goes as ONE request after
|
|
138
|
+
* {@link ACTION_QUIET_MS} of quiet, {@link ACTION_MAX_WAIT_MS} after its first action at most, at
|
|
139
|
+
* {@link ACTION_BATCH_MAX}, and when the page is left ({@link flushEngagementActions}). So a
|
|
140
|
+
* feature the owner adds to the record costs no request per click — his words: *"batching some
|
|
141
|
+
* interactions with debounces will lower traffic"*.
|
|
142
|
+
*
|
|
120
143
|
* NOT de-duplicated, unlike `reportPlace`: three plays are three uses. Only call it from apps
|
|
121
144
|
* whose recorder was mounted WITH `actions` — elsewhere `/action` is a 404 in the console.
|
|
122
145
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"beaconClient.d.ts","sourceRoot":"","sources":["../../../src/react/engagement/beaconClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,2EAA2E;AAC3E,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AAEtD;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,sBAAsB,CAAC;AAEhD,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAaD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAI7C;AAYD,
|
|
1
|
+
{"version":3,"file":"beaconClient.d.ts","sourceRoot":"","sources":["../../../src/react/engagement/beaconClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,2EAA2E;AAC3E,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AAEtD;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,sBAAsB,CAAC;AAEhD,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAaD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAI7C;AAYD,iGAAiG;AACjG,wBAAgB,qBAAqB,IAAI,IAAI,CAQ5C;AA6CD;;;;;GAKG;AACH,wBAAsB,WAAW,CAChC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,kBAAuB,GAC9B,OAAO,CAAC,OAAO,CAAC,CAgClB;AAED,MAAM,WAAW,mBAAoB,SAAQ,kBAAkB;IAC9D;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oGAAoG;IACpG,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC;CAClD;AAED,mFAAmF;AACnF,eAAO,MAAM,eAAe,OAAQ,CAAC;AACrC,uGAAuG;AACvG,eAAO,MAAM,kBAAkB,QAAS,CAAC;AACzC,iGAAiG;AACjG,eAAO,MAAM,gBAAgB,MAAM,CAAC;AA8CpC;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/F;AAuBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,OAAO,CAAC,CAwBtG;AAED,8DAA8D;AAC9D,MAAM,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC;AAIpC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,kBAAuB,GAAG,UAAU,CAehF"}
|
|
@@ -92,10 +92,17 @@ export function currentLocationPlace() {
|
|
|
92
92
|
* dozens.
|
|
93
93
|
*/
|
|
94
94
|
let sessionAnswer = null;
|
|
95
|
-
/** Reset the de-duplication memory. Tests, and a sign-out. */
|
|
95
|
+
/** Reset the de-duplication memory, and drop any queued action unsent. Tests, and a sign-out. */
|
|
96
96
|
export function resetEngagementBeacon() {
|
|
97
97
|
lastReported = null;
|
|
98
98
|
sessionAnswer = null;
|
|
99
|
+
for (const queue of queues.values()) {
|
|
100
|
+
if (queue.quiet)
|
|
101
|
+
clearTimeout(queue.quiet);
|
|
102
|
+
if (queue.due)
|
|
103
|
+
clearTimeout(queue.due);
|
|
104
|
+
}
|
|
105
|
+
queues.clear();
|
|
99
106
|
}
|
|
100
107
|
/**
|
|
101
108
|
* Read an answer to its end, and drop it.
|
|
@@ -183,9 +190,85 @@ export async function reportPlace(place, options = {}) {
|
|
|
183
190
|
return false;
|
|
184
191
|
}
|
|
185
192
|
}
|
|
193
|
+
/** How long the action queue waits for quiet before it sends (forge task 4034). */
|
|
194
|
+
export const ACTION_QUIET_MS = 5_000;
|
|
195
|
+
/** The longest an action waits however busy the page is, so a page that crashes loses 30 s at most. */
|
|
196
|
+
export const ACTION_MAX_WAIT_MS = 30_000;
|
|
197
|
+
/** One POST carries at most this many: the recorder's `MAX_ACTION_BATCH` (cursedbelt-server). */
|
|
198
|
+
export const ACTION_BATCH_MAX = 100;
|
|
199
|
+
const queues = new Map();
|
|
200
|
+
/** The window the page-leave listeners sit on, so a second one is never added (tests swap it). */
|
|
201
|
+
let leaveTarget = null;
|
|
202
|
+
function defaultBeacon() {
|
|
203
|
+
const nav = globalThis.navigator;
|
|
204
|
+
return typeof nav?.sendBeacon === "function" ? nav.sendBeacon.bind(nav) : undefined;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Flush on the way out: `pagehide` (a close, a navigation, the bfcache) and a tab going hidden,
|
|
208
|
+
* which is the last moment a phone gives a page it is about to kill. A timer cannot fire after
|
|
209
|
+
* either, so this is what delivers the actions of a page closed inside its quiet window.
|
|
210
|
+
*/
|
|
211
|
+
function listenForLeave() {
|
|
212
|
+
const win = globalThis.window;
|
|
213
|
+
if (!win || typeof win.addEventListener !== "function" || leaveTarget === win)
|
|
214
|
+
return;
|
|
215
|
+
leaveTarget = win;
|
|
216
|
+
const leave = () => {
|
|
217
|
+
void flushEngagementActions({ leaving: true });
|
|
218
|
+
};
|
|
219
|
+
win.addEventListener("pagehide", leave);
|
|
220
|
+
const doc = globalThis.document;
|
|
221
|
+
doc?.addEventListener?.("visibilitychange", () => {
|
|
222
|
+
if (doc.visibilityState === "hidden")
|
|
223
|
+
leave();
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Send what is queued, now: ONE POST per mount. A queue of one posts `{ place, action }` as
|
|
228
|
+
* `reportAction` always has, so a recorder older than the batch reads it; more posts
|
|
229
|
+
* `{ actions }`, and a recorder that refuses the batch (an older one answers 400) gets them one by
|
|
230
|
+
* one instead. `leaving` sends through `sendBeacon`, which outlives the page. Never throws.
|
|
231
|
+
*/
|
|
232
|
+
export async function flushEngagementActions(options = {}) {
|
|
233
|
+
const sends = [];
|
|
234
|
+
for (const [basePath, queue] of queues) {
|
|
235
|
+
if (queue.quiet)
|
|
236
|
+
clearTimeout(queue.quiet);
|
|
237
|
+
if (queue.due)
|
|
238
|
+
clearTimeout(queue.due);
|
|
239
|
+
queues.delete(basePath);
|
|
240
|
+
for (let i = 0; i < queue.actions.length; i += ACTION_BATCH_MAX) {
|
|
241
|
+
sends.push(sendActions(`${basePath}/action`, queue.actions.slice(i, i + ACTION_BATCH_MAX), queue, options.leaving === true));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
await Promise.all(sends);
|
|
245
|
+
}
|
|
246
|
+
async function sendActions(url, actions, queue, leaving) {
|
|
247
|
+
const [only] = actions;
|
|
248
|
+
const body = JSON.stringify(actions.length === 1 && only ? { place: only.place, action: only.action } : { actions });
|
|
249
|
+
try {
|
|
250
|
+
if (leaving && queue.beaconImpl?.(url, new Blob([body], { type: "application/json" })))
|
|
251
|
+
return;
|
|
252
|
+
const res = await queue.fetchImpl(url, {
|
|
253
|
+
method: "POST",
|
|
254
|
+
headers: { "content-type": "application/json" },
|
|
255
|
+
credentials: "same-origin",
|
|
256
|
+
body,
|
|
257
|
+
keepalive: true,
|
|
258
|
+
});
|
|
259
|
+
await drain(res);
|
|
260
|
+
if (res.status === 400 && actions.length > 1) {
|
|
261
|
+
for (const a of actions)
|
|
262
|
+
await sendActions(url, [a], queue, false);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// See the header: telemetry never becomes the user's problem.
|
|
267
|
+
}
|
|
268
|
+
}
|
|
186
269
|
/**
|
|
187
270
|
* Report that a FEATURE was used — `reportAction("search")` — beside the place it was used on
|
|
188
|
-
* (forge task 3470). Returns whether
|
|
271
|
+
* (forge task 3470). Returns whether it was queued to send.
|
|
189
272
|
*
|
|
190
273
|
* The owner's question is "what features do they use and ignore"; a page view cannot answer it,
|
|
191
274
|
* because one page holds several features. So the app names the action at the press, from the
|
|
@@ -199,6 +282,12 @@ export async function reportPlace(place, options = {}) {
|
|
|
199
282
|
* · the same silence: session-probed (the probe's answer is shared with `reportPlace`, one per
|
|
200
283
|
* page load), fire-and-forget, never throws, never logs.
|
|
201
284
|
*
|
|
285
|
+
* 🔴 BATCHED (forge task 4034): an action is queued, and the queue goes as ONE request after
|
|
286
|
+
* {@link ACTION_QUIET_MS} of quiet, {@link ACTION_MAX_WAIT_MS} after its first action at most, at
|
|
287
|
+
* {@link ACTION_BATCH_MAX}, and when the page is left ({@link flushEngagementActions}). So a
|
|
288
|
+
* feature the owner adds to the record costs no request per click — his words: *"batching some
|
|
289
|
+
* interactions with debounces will lower traffic"*.
|
|
290
|
+
*
|
|
202
291
|
* NOT de-duplicated, unlike `reportPlace`: three plays are three uses. Only call it from apps
|
|
203
292
|
* whose recorder was mounted WITH `actions` — elsewhere `/action` is a 404 in the console.
|
|
204
293
|
*/
|
|
@@ -214,20 +303,22 @@ export async function reportAction(action, options = {}) {
|
|
|
214
303
|
return false;
|
|
215
304
|
}
|
|
216
305
|
const place = options.place ?? (typeof window === "undefined" ? "/" : currentLocationPlace());
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
306
|
+
const basePath = options.basePath ?? ENGAGEMENT_BASE_PATH;
|
|
307
|
+
const queue = queues.get(basePath) ?? { actions: [], fetchImpl: doFetch, beaconImpl: undefined, quiet: null, due: null };
|
|
308
|
+
queue.fetchImpl = doFetch;
|
|
309
|
+
queue.beaconImpl = options.beaconImpl ?? defaultBeacon();
|
|
310
|
+
queue.actions.push({ place, action, at: Date.now() });
|
|
311
|
+
queues.set(basePath, queue);
|
|
312
|
+
listenForLeave();
|
|
313
|
+
if (queue.actions.length >= ACTION_BATCH_MAX) {
|
|
314
|
+
void flushEngagementActions();
|
|
226
315
|
return true;
|
|
227
316
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
317
|
+
if (queue.quiet)
|
|
318
|
+
clearTimeout(queue.quiet);
|
|
319
|
+
queue.quiet = setTimeout(() => void flushEngagementActions(), ACTION_QUIET_MS);
|
|
320
|
+
queue.due ??= setTimeout(() => void flushEngagementActions(), ACTION_MAX_WAIT_MS);
|
|
321
|
+
return true;
|
|
231
322
|
}
|
|
232
323
|
let running = null;
|
|
233
324
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"beaconClient.js","sourceRoot":"","sources":["../../../src/react/engagement/beaconClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,oBAAoB,GAAG,iBAAiB,CAAC;AAEtD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,mBAAmB,CAAC;AAmBhD;;;;;;;;GAQG;AACH,IAAI,YAAY,GAAkB,IAAI,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB;IACnC,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC3C,MAAM,GAAG,GAAG,IAAI,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;IACnD,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;AACjC,CAAC;AAED;;;;;;;GAOG;AACH,IAAI,aAAa,GAA4B,IAAI,CAAC;AAElD,
|
|
1
|
+
{"version":3,"file":"beaconClient.js","sourceRoot":"","sources":["../../../src/react/engagement/beaconClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export { ENGAGEMENT_BASE_PATH, type ReportActionOptions, type ReportPlaceOptions, reportAction, reportPlace, resetEngagementBeacon, SESSION_PATH, type StopBeacon, startLocationBeacon, currentLocationPlace, } from './beaconClient.js';
|
|
1
|
+
export { ENGAGEMENT_BASE_PATH, flushEngagementActions, type ReportActionOptions, type ReportPlaceOptions, reportAction, reportPlace, resetEngagementBeacon, SESSION_PATH, type StopBeacon, startLocationBeacon, currentLocationPlace, } from './beaconClient.js';
|
|
2
|
+
export { startPlaybackBeacon } from './playbackBeacon.js';
|
|
2
3
|
export { EngagementBeacon, useEngagementAction, useEngagementBeacon, useLocationBeacon } from './beacon.js';
|
|
3
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/engagement/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/engagement/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACN,oBAAoB,EACpB,sBAAsB,EACtB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,YAAY,EACZ,KAAK,UAAU,EACf,mBAAmB,EACnB,oBAAoB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// `cursedbelt/react/engagement-beacon` — the engagement beacon: `reportPlace`, `reportAction` and
|
|
2
|
-
// `startLocationBeacon` (plain JS, no React) and the React hooks over them
|
|
3
|
-
|
|
2
|
+
// `startLocationBeacon` (plain JS, no React) and the React hooks over them,
|
|
3
|
+
// and `startPlaybackBeacon`, which turns on the player's load-time reports.
|
|
4
|
+
export { ENGAGEMENT_BASE_PATH, flushEngagementActions, reportAction, reportPlace, resetEngagementBeacon, SESSION_PATH, startLocationBeacon, currentLocationPlace, } from './beaconClient.js';
|
|
5
|
+
export { startPlaybackBeacon } from './playbackBeacon.js';
|
|
4
6
|
export { EngagementBeacon, useEngagementAction, useEngagementBeacon, useLocationBeacon } from './beacon.js';
|
|
5
7
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/engagement/index.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,4EAA4E;AAC5E,OAAO,EACN,oBAAoB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/engagement/index.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,4EAA4E;AAC5E,4EAA4E;AAC5E,OAAO,EACN,oBAAoB,EACpB,sBAAsB,EAGtB,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,YAAY,EAEZ,mBAAmB,EACnB,oBAAoB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playbackBeacon.d.ts","sourceRoot":"","sources":["../../../src/react/engagement/playbackBeacon.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,uBAAuB,4BAA4B,CAAC;AAEjE,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,MAAgC,GAAG,MAAM,IAAI,CActF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `startPlaybackBeacon` — send what `media/playbackWatch.ts` measures to the app's own API.
|
|
3
|
+
*
|
|
4
|
+
* A POST while the page is alive, and `sendBeacon` when it may not be: a report that a person gave
|
|
5
|
+
* up on a spinner is written as they leave, and a plain `fetch` there is canceled with the page.
|
|
6
|
+
* Same-origin, so the session cookie says whose it is and the body never has to. Failure is silence
|
|
7
|
+
* (`beaconClient.ts`): telemetry never becomes the viewer's problem.
|
|
8
|
+
*/
|
|
9
|
+
import { setPlaybackReporter } from '../media/playbackWatch.js';
|
|
10
|
+
export const PLAYBACK_TELEMETRY_PATH = '/api/playback-telemetry';
|
|
11
|
+
export function startPlaybackBeacon(path = PLAYBACK_TELEMETRY_PATH) {
|
|
12
|
+
const report = (playback, leaving) => {
|
|
13
|
+
const body = JSON.stringify(playback);
|
|
14
|
+
if (leaving && navigator.sendBeacon?.(path, new Blob([body], { type: 'application/json' })))
|
|
15
|
+
return;
|
|
16
|
+
void fetch(path, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
credentials: 'same-origin',
|
|
19
|
+
keepalive: true,
|
|
20
|
+
headers: { 'content-type': 'application/json' },
|
|
21
|
+
body,
|
|
22
|
+
}).catch(() => undefined);
|
|
23
|
+
};
|
|
24
|
+
setPlaybackReporter(report);
|
|
25
|
+
return () => setPlaybackReporter(null);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=playbackBeacon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playbackBeacon.js","sourceRoot":"","sources":["../../../src/react/engagement/playbackBeacon.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAA8C,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAE5G,MAAM,CAAC,MAAM,uBAAuB,GAAG,yBAAyB,CAAC;AAEjE,MAAM,UAAU,mBAAmB,CAAC,OAAe,uBAAuB;IACxE,MAAM,MAAM,GAAqB,CAAC,QAAwB,EAAE,OAAO,EAAE,EAAE;QACrE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACtC,IAAI,OAAO,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAAE,OAAO;QACpG,KAAK,KAAK,CAAC,IAAI,EAAE;YACf,MAAM,EAAE,MAAM;YACd,WAAW,EAAE,aAAa;YAC1B,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI;SACL,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC5B,CAAC,CAAC;IACF,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO,GAAG,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -18,7 +18,7 @@ import { VideoSlideContent } from './VideoSlideContent.js';
|
|
|
18
18
|
*/
|
|
19
19
|
export function MediaLightbox({ open, index, slides, close, onView, ...rest }) {
|
|
20
20
|
return (_jsx(Lightbox, { ...rest, open: open, index: index, close: close, slides: slides, plugins: [Thumbnails, Zoom, Captions, Counter], carousel: { finite: true }, thumbnails: { position: 'bottom', showToggle: true }, on: { view: ({ index: i }) => onView?.(i) }, render: {
|
|
21
|
-
slide: ({ slide }) => slide.type === 'video' ? _jsx(VideoSlideContent, { slide: slide }) : undefined,
|
|
21
|
+
slide: ({ slide, offset }) => slide.type === 'video' ? _jsx(VideoSlideContent, { active: offset === 0, slide: slide }) : undefined,
|
|
22
22
|
} }));
|
|
23
23
|
}
|
|
24
24
|
//# sourceMappingURL=MediaLightbox.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MediaLightbox.js","sourceRoot":"","sources":["../../../src/react/media/MediaLightbox.tsx"],"names":[],"mappings":";AAAA,OAAO,uCAAuC,CAAC;AAC/C,OAAO,iDAAiD,CAAC;AACzD,OAAO,gDAAgD,CAAC;AACxD,OAAO,mDAAmD,CAAC;AAC3D,OAAO,QAAQ,EAAE,EAAc,MAAM,4BAA4B,CAAC;AAClE,OAAO,QAAQ,MAAM,6CAA6C,CAAC;AACnE,OAAO,OAAO,MAAM,4CAA4C,CAAC;AACjE,OAAO,UAAU,MAAM,+CAA+C,CAAC;AACvE,OAAO,IAAI,MAAM,yCAAyC,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAW3D;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAsB;IAC/F,OAAO,CACL,KAAC,QAAQ,OACH,IAAI,EACR,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CAAC,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,EAC9C,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAC1B,UAAU,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,EACpD,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAC3C,MAAM,EAAE;YACN,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,
|
|
1
|
+
{"version":3,"file":"MediaLightbox.js","sourceRoot":"","sources":["../../../src/react/media/MediaLightbox.tsx"],"names":[],"mappings":";AAAA,OAAO,uCAAuC,CAAC;AAC/C,OAAO,iDAAiD,CAAC;AACzD,OAAO,gDAAgD,CAAC;AACxD,OAAO,mDAAmD,CAAC;AAC3D,OAAO,QAAQ,EAAE,EAAc,MAAM,4BAA4B,CAAC;AAClE,OAAO,QAAQ,MAAM,6CAA6C,CAAC;AACnE,OAAO,OAAO,MAAM,4CAA4C,CAAC;AACjE,OAAO,UAAU,MAAM,+CAA+C,CAAC;AACvE,OAAO,IAAI,MAAM,yCAAyC,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAW3D;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAsB;IAC/F,OAAO,CACL,KAAC,QAAQ,OACH,IAAI,EACR,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CAAC,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,EAC9C,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAC1B,UAAU,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,EACpD,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAC3C,MAAM,EAAE;YACN,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAC3B,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAC,iBAAiB,IAAC,MAAM,EAAE,MAAM,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,GAAI,CAAC,CAAC,CAAC,SAAS;SACjG,GACD,CACH,CAAC;AACJ,CAAC"}
|
|
@@ -55,6 +55,8 @@ export interface VideoPlayerProps {
|
|
|
55
55
|
* two optional fields, so every existing source still satisfies this unchanged.
|
|
56
56
|
*/
|
|
57
57
|
video: PlayerVideo;
|
|
58
|
+
/** Where this player is, in the load-time reports (`playbackWatch.ts`). */
|
|
59
|
+
surface?: string;
|
|
58
60
|
/**
|
|
59
61
|
* Start playing on mount. **Defaults to `true`** — see {@link usePrefersReducedMotion} and
|
|
60
62
|
* the block on {@link AUTOPLAY_DEFAULT}.
|
|
@@ -400,7 +402,7 @@ export declare function useLiveScrub(hostRef: RefObject<HTMLElement | null>, pla
|
|
|
400
402
|
*/
|
|
401
403
|
export declare function preferBundledHls(provider: MediaProviderAdapter | null): void;
|
|
402
404
|
export declare function usePrefersReducedMotion(): boolean;
|
|
403
|
-
export declare function VideoPlayer({ video, autoPlay, muted, volumeKey, className, aspectRatio, onEnded, loop, liveScrub, smallLayoutWhen, onStep, canStep, fullscreenOnDoubleClick, fillWindow, onFillWindowChange, ...rest }: VideoPlayerProps): import("react").JSX.Element;
|
|
405
|
+
export declare function VideoPlayer({ video, autoPlay, muted, volumeKey, className, aspectRatio, onEnded, loop, liveScrub, smallLayoutWhen, onStep, canStep, fullscreenOnDoubleClick, fillWindow, onFillWindowChange, surface, ...rest }: VideoPlayerProps): import("react").JSX.Element;
|
|
404
406
|
/**
|
|
405
407
|
* The transport state for a video surface: a thin progress bar pinned across the bottom edge
|
|
406
408
|
* while derivatives build (indeterminate shimmer until real progress arrives), plus whatever
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoPlayer.d.ts","sourceRoot":"","sources":["../../../src/react/media/VideoPlayer.tsx"],"names":[],"mappings":"AAAA,OAAO,iDAAiD,CAAC;AACzD,OAAO,yDAAyD,CAAC;AACjE,OAAO,mBAAmB,CAAC;AAC3B,OAAO,EAIL,KAAK,mBAAmB,EAExB,KAAK,oBAAoB,EAI1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,KAAK,uBAAuB,EAE7B,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAEL,KAAK,SAAS,EAKf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"VideoPlayer.d.ts","sourceRoot":"","sources":["../../../src/react/media/VideoPlayer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -6,8 +6,10 @@ import { Gesture, isHLSProvider, MediaPlayer, MediaProvider, Poster, Track, useM
|
|
|
6
6
|
import { DefaultVideoLayout, defaultLayoutIcons, } from '@vidstack/react/player/layouts/default';
|
|
7
7
|
import { useEffect, useMemo, useRef, useState, } from 'react';
|
|
8
8
|
import { PLAYER_VOLUME_KEY, readSessionVolume, writeSessionVolume, } from '../media-gallery/playbackPreferences.js';
|
|
9
|
-
import { hlsErrorIsAnswer, probeSourceFault, SourceFaultOverlay, } from './sourceFault.js';
|
|
9
|
+
import { hlsErrorIsAnswer, probeSourceFault, SourceFaultOverlay, hlsErrorName, } from './sourceFault.js';
|
|
10
10
|
import { BarButton, mergeSlots, sectionSlots } from './controlBar.js';
|
|
11
|
+
import { playerSource } from './playerSource.js';
|
|
12
|
+
import { usePlaybackWatch } from './usePlaybackWatch.js';
|
|
11
13
|
import { takePlayhead } from './playheadHandoff.js';
|
|
12
14
|
import { isSurfaceControl } from './surfaceGesture.js';
|
|
13
15
|
import { chapterSpans } from './videoChapters.js';
|
|
@@ -362,7 +364,7 @@ export function usePrefersReducedMotion() {
|
|
|
362
364
|
});
|
|
363
365
|
return reduced;
|
|
364
366
|
}
|
|
365
|
-
export function VideoPlayer({ video, autoPlay, muted, volumeKey = PLAYER_VOLUME_KEY, className, aspectRatio = DEFAULT_VIDEO_ASPECT_RATIO, onEnded, loop, liveScrub = true, smallLayoutWhen = isSmallVideoLayout, onStep, canStep = true, fullscreenOnDoubleClick = true, fillWindow = false, onFillWindowChange, ...rest }) {
|
|
367
|
+
export function VideoPlayer({ video, autoPlay, muted, volumeKey = PLAYER_VOLUME_KEY, className, aspectRatio = DEFAULT_VIDEO_ASPECT_RATIO, onEnded, loop, liveScrub = true, smallLayoutWhen = isSmallVideoLayout, onStep, canStep = true, fullscreenOnDoubleClick = true, fillWindow = false, onFillWindowChange, surface = 'player', ...rest }) {
|
|
366
368
|
/*
|
|
367
369
|
* 🔴 What the SOURCES have answered, and why a player needs to hold that at all.
|
|
368
370
|
*
|
|
@@ -382,10 +384,38 @@ export function VideoPlayer({ video, autoPlay, muted, volumeKey = PLAYER_VOLUME_
|
|
|
382
384
|
*/
|
|
383
385
|
const [ladderAnswered, setLadderAnswered] = useState(false);
|
|
384
386
|
const [fault, setFault] = useState(null);
|
|
385
|
-
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
387
|
+
/** Bumped by the fault's Try again: a fresh element, and the ladder gets its turn back. */
|
|
388
|
+
const [attempt, setAttempt] = useState(0);
|
|
389
|
+
// Memoized: a new object is a new source to Vidstack, and that restarts a load on every render.
|
|
390
|
+
const { hlsUrl, streamUrl } = video;
|
|
391
|
+
const { src, onLadder, progressive } = useMemo(() => playerSource({ hlsUrl, streamUrl }, ladderAnswered), [hlsUrl, streamUrl, ladderAnswered]);
|
|
392
|
+
const watch = usePlaybackWatch(video.id, surface);
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
watch.current?.source(onLadder ? 'ladder' : progressive ? 'progressive' : 'none');
|
|
395
|
+
}, [watch, onLadder, progressive]);
|
|
396
|
+
/*
|
|
397
|
+
* 🔴 A typed source is a `<source>` child, and when one fails the `error` fires on the `<source>`,
|
|
398
|
+
* never on the `<video>`. Vidstack listens on the `<video>`, so a 404 there was silence and a
|
|
399
|
+
* spinner for ever (forge task 4062, reproduced in `e2e/playbackSources.browser.test.ts`). `error`
|
|
400
|
+
* does not bubble but it does capture, so the host hears it.
|
|
401
|
+
*/
|
|
402
|
+
useEffect(() => {
|
|
403
|
+
const host = hostRef.current;
|
|
404
|
+
if (!host)
|
|
405
|
+
return;
|
|
406
|
+
const onSourceError = (event) => {
|
|
407
|
+
if (event.target instanceof HTMLSourceElement)
|
|
408
|
+
sourceFailed.current();
|
|
409
|
+
};
|
|
410
|
+
host.addEventListener('error', onSourceError, true);
|
|
411
|
+
return () => host.removeEventListener('error', onSourceError, true);
|
|
412
|
+
}, []);
|
|
413
|
+
const giveUp = (url) => {
|
|
414
|
+
void probeSourceFault(url).then((found) => {
|
|
415
|
+
setFault(found);
|
|
416
|
+
watch.current?.failed(`${found.kind}${found.status ? `:${found.status}` : ''}`);
|
|
417
|
+
});
|
|
418
|
+
};
|
|
389
419
|
// See AUTOPLAY_DEFAULT. `muted` follows `autoPlay` because an unmuted autoplay is refused
|
|
390
420
|
// by every browser and the refusal LOOKS like the spinner this batch removed — unless he
|
|
391
421
|
// has already set a level on this surface this session, which is what `session` carries.
|
|
@@ -411,6 +441,11 @@ export function VideoPlayer({ video, autoPlay, muted, volumeKey = PLAYER_VOLUME_
|
|
|
411
441
|
const applied = useRef({ volume: startVolume, muted: shouldMute });
|
|
412
442
|
/** A mute the BROWSER forced on us — see `onAutoPlayFail` below. Never his choice, so never stored. */
|
|
413
443
|
const forcedMute = useRef(false);
|
|
444
|
+
const sourceFailed = useRef(() => { });
|
|
445
|
+
sourceFailed.current = () => {
|
|
446
|
+
watch.current?.note('media:source');
|
|
447
|
+
giveUp(progressive);
|
|
448
|
+
};
|
|
414
449
|
const status = video.processingStatus;
|
|
415
450
|
// 🔴 `playbackDegraded` is its own reason to draw, not a processing state — a video that
|
|
416
451
|
// optimized perfectly can still be served off the progressive copy because the ladder probe
|
|
@@ -425,19 +460,26 @@ export function VideoPlayer({ video, autoPlay, muted, volumeKey = PLAYER_VOLUME_
|
|
|
425
460
|
? {
|
|
426
461
|
onDoubleClick: (event) => toggleFullscreenOnSurfaceDoubleClick(playerRef.current, event.target),
|
|
427
462
|
}
|
|
428
|
-
: {}), children: [_jsxs(MediaPlayer, { ref: playerRef, src:
|
|
463
|
+
: {}), children: [_jsxs(MediaPlayer, { ref: playerRef, ...(src ? { src } : {}),
|
|
429
464
|
// 🔴 The hls.js engine is served from this origin, not from jsdelivr. See
|
|
430
465
|
// `preferBundledHls` — without it an adaptive ladder depends on a third-party CDN
|
|
431
466
|
// and fails as a permanent spinner when that CDN is unreachable.
|
|
432
467
|
onProviderChange: (provider) => {
|
|
433
468
|
preferBundledHls(provider);
|
|
434
|
-
bindVidstackQuality(provider, qualityStore,
|
|
469
|
+
bindVidstackQuality(provider, qualityStore, onLadder);
|
|
435
470
|
},
|
|
436
471
|
/* See {@link hlsErrorIsAnswer}. Typed `unknown` on purpose: `hls.js` is an OPTIONAL
|
|
437
472
|
peer, so its `ErrorData` must not become a static type dependency of this file. */
|
|
438
473
|
onHlsError: (detail) => {
|
|
439
|
-
|
|
474
|
+
const error = hlsErrorName(detail);
|
|
475
|
+
if (error)
|
|
476
|
+
watch.current?.note(error);
|
|
477
|
+
if (!hlsErrorIsAnswer(detail))
|
|
478
|
+
return;
|
|
479
|
+
if (progressive)
|
|
440
480
|
setLadderAnswered(true);
|
|
481
|
+
else
|
|
482
|
+
giveUp(video.hlsUrl);
|
|
441
483
|
},
|
|
442
484
|
/* The element itself gave up, and what that means depends on what was playing.
|
|
443
485
|
With a LADDER in play it is the case Vidstack has no answer for at all — a browser
|
|
@@ -448,12 +490,13 @@ export function VideoPlayer({ video, autoPlay, muted, volumeKey = PLAYER_VOLUME_
|
|
|
448
490
|
same call `hlsSource.ts` makes when its recovery budget is spent, and a playing film
|
|
449
491
|
beats a dead one. On the progressive copy there is no turn left, so the URL is
|
|
450
492
|
probed for what it actually said and the picture says it. */
|
|
451
|
-
onError: () => {
|
|
452
|
-
|
|
493
|
+
onError: (detail) => {
|
|
494
|
+
watch.current?.note(`media:${detail?.code ?? 'unknown'}`);
|
|
495
|
+
if (onLadder && progressive) {
|
|
453
496
|
setLadderAnswered(true);
|
|
454
497
|
return;
|
|
455
498
|
}
|
|
456
|
-
|
|
499
|
+
giveUp(progressive ?? video.hlsUrl);
|
|
457
500
|
}, title: video.name, poster: video.posterUrl, crossOrigin: true, playsInline: true, autoPlay: shouldAutoPlay, muted: shouldMute,
|
|
458
501
|
// 🔴 The level he set this session, restored on the element — see `resolveAutoplay`
|
|
459
502
|
// and `SessionVolume`. Both values are constant for the life of this mount, so
|
|
@@ -493,12 +536,13 @@ export function VideoPlayer({ video, autoPlay, muted, volumeKey = PLAYER_VOLUME_
|
|
|
493
536
|
// Load eagerly rather than on-visible: the watch surface can size to a
|
|
494
537
|
// zero-height box during iOS Safari's container-query resolution, so a
|
|
495
538
|
// `visible` gate never fired and the player sat on a perpetual spinner.
|
|
496
|
-
load: 'eager', viewType: 'video', aspectRatio: aspectRatio, className: className, style: { width: '100%', height: '100%', maxHeight: '100%' }, loop: loop, onEnded: onEnded,
|
|
539
|
+
load: 'eager', viewType: 'video', aspectRatio: aspectRatio, className: className, style: { width: '100%', height: '100%', maxHeight: '100%' }, loop: loop, onEnded: onEnded, onWaiting: () => watch.current?.stalled(), onPlaying: () => watch.current?.resumed(),
|
|
497
540
|
/* 🔴 Where a surface that just CLOSED left this video — the focus viewer's ‹ Back, for
|
|
498
541
|
one. See `playheadHandoff.ts`. On `can-play`, because a seek before the element can
|
|
499
542
|
play is dropped, and taking it consumes it: a later load of the same element (the
|
|
500
543
|
ladder handing over to the progressive copy) must not jump back there again. */
|
|
501
544
|
onCanPlay: () => {
|
|
545
|
+
watch.current?.ready();
|
|
502
546
|
const at = takePlayhead(video.id);
|
|
503
547
|
const player = playerRef.current;
|
|
504
548
|
if (at !== null && player)
|
|
@@ -512,7 +556,11 @@ export function VideoPlayer({ video, autoPlay, muted, volumeKey = PLAYER_VOLUME_
|
|
|
512
556
|
// "many options from very slow to very fast": 0.1 is frame-crawling, which is the
|
|
513
557
|
// speed you actually want for studying a moment, and 4 is for skimming a long
|
|
514
558
|
// clip. The default five steps cover neither end.
|
|
515
|
-
playbackRates: PLAYBACK_RATES, ...(slots ? { slots } : {}) })] }), showProcessing ? (_jsx(ProcessingIndicator, { status: status, progress: video.processingProgress, playbackDegraded: video.playbackDegraded })) : null, fault ? _jsx(SourceFaultOverlay, { fault: fault, name: video.name
|
|
559
|
+
playbackRates: PLAYBACK_RATES, ...(slots ? { slots } : {}) })] }, attempt), showProcessing ? (_jsx(ProcessingIndicator, { status: status, progress: video.processingProgress, playbackDegraded: video.playbackDegraded })) : null, fault ? (_jsx(SourceFaultOverlay, { fault: fault, name: video.name, onRetry: () => {
|
|
560
|
+
setFault(null);
|
|
561
|
+
setLadderAnswered(false);
|
|
562
|
+
setAttempt((n) => n + 1);
|
|
563
|
+
} })) : null] }));
|
|
516
564
|
}
|
|
517
565
|
/**
|
|
518
566
|
* The named sections, as the `kind='chapters'` text track Vidstack's default layout is already
|