@f8team/reel-core 1.0.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/CHANGELOG.md +103 -0
- package/LICENSE +21 -0
- package/README.md +43 -0
- package/dist/index.cjs +2014 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +846 -0
- package/dist/index.d.ts +846 -0
- package/dist/index.js +1997 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/index.cjs +4 -0
- package/dist/internal/index.cjs.map +1 -0
- package/dist/internal/index.d.cts +2 -0
- package/dist/internal/index.d.ts +2 -0
- package/dist/internal/index.js +3 -0
- package/dist/internal/index.js.map +1 -0
- package/package.json +76 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only view of the reactive store, handed to plugins via `PluginHost`.
|
|
3
|
+
* Plugins observe state but never mutate it directly.
|
|
4
|
+
*/
|
|
5
|
+
interface ReadableStore<T> {
|
|
6
|
+
/** Snapshot read. */
|
|
7
|
+
getState(): T;
|
|
8
|
+
/** Subscribe to a slice. The listener fires only when the slice changes (===). */
|
|
9
|
+
subscribe<U>(selector: (state: T) => U, listener: (value: U) => void): () => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Internal mutable store. Adapters never see this; only `createPlayer` does.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
interface MutableStore<T> extends ReadableStore<T> {
|
|
17
|
+
/**
|
|
18
|
+
* Apply a partial update. Updates are coalesced inside one microtask, so
|
|
19
|
+
* multiple `setState` calls within the same tick fire one round of
|
|
20
|
+
* subscribers.
|
|
21
|
+
*/
|
|
22
|
+
setState(patch: Partial<T> | ((current: T) => Partial<T>)): void;
|
|
23
|
+
/** Drop every subscriber. */
|
|
24
|
+
dispose(): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Reactive store with microtask-batched updates and selector subscriptions.
|
|
29
|
+
*
|
|
30
|
+
* Design goals:
|
|
31
|
+
* - **Tiny**: ~30 lines of logic, no external runtime.
|
|
32
|
+
* - **Predictable**: subscribers fire synchronously after the microtask flush.
|
|
33
|
+
* Multiple `setState` calls in the same tick fire one round of subscribers.
|
|
34
|
+
* - **Selector-aware**: a subscriber registered with a selector only re-runs
|
|
35
|
+
* when its selected slice changes (`Object.is` equality by default).
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Optional knobs handed to `createStore`.
|
|
40
|
+
*
|
|
41
|
+
* The generic `T` is unused at the type level today, but it locks the option
|
|
42
|
+
* shape to a specific store so future per-state hooks (e.g. `selectorOf`)
|
|
43
|
+
* stay backwards-compatible.
|
|
44
|
+
*/
|
|
45
|
+
interface CreateStoreOptions<_T> {
|
|
46
|
+
/**
|
|
47
|
+
* Override the global microtask scheduler (used by tests so the store
|
|
48
|
+
* can flush synchronously). Default: `queueMicrotask`.
|
|
49
|
+
*/
|
|
50
|
+
scheduleFlush?: (run: () => void) => void;
|
|
51
|
+
/** Custom equality at the store level, used by `subscribe` if none is passed. */
|
|
52
|
+
isEqual?: <U>(a: U, b: U) => boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Player error contract. Surfaced through the `error` event and `state.error`.
|
|
57
|
+
*
|
|
58
|
+
* Golden cases: G13 (unauthorized HLS), every error path in G1..G16.
|
|
59
|
+
*/
|
|
60
|
+
/** Stable error codes. The string literal IS the public contract. */
|
|
61
|
+
type PlayerErrorCode = "source" | "network" | "decode" | "unauthorized" | "unsupported" | "internal";
|
|
62
|
+
/**
|
|
63
|
+
* Structured player error. Adapters render the message; plugins can inspect
|
|
64
|
+
* `code`, `status`, and `retryable` to decide on retry/UI strategy.
|
|
65
|
+
*/
|
|
66
|
+
interface PlayerError {
|
|
67
|
+
/** Stable code. */
|
|
68
|
+
code: PlayerErrorCode;
|
|
69
|
+
/** Human-readable message. Adapters MAY localize. */
|
|
70
|
+
message: string;
|
|
71
|
+
/** Optional underlying cause (`Error`, `MediaError`, `Hls.errors.*`). */
|
|
72
|
+
cause?: unknown;
|
|
73
|
+
/** HTTP status, when the error came from an XHR (G13). */
|
|
74
|
+
status?: number;
|
|
75
|
+
/** Failing URL, when known. */
|
|
76
|
+
url?: string;
|
|
77
|
+
/** Whether the consumer can usefully retry (e.g. transient network). */
|
|
78
|
+
retryable: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Media source descriptors and related contracts.
|
|
83
|
+
*
|
|
84
|
+
* Every adapter and plugin compiles against the public types declared here.
|
|
85
|
+
* @see {@link ../../docs/spec/api-contract.md} for the frozen contract.
|
|
86
|
+
*/
|
|
87
|
+
/**
|
|
88
|
+
* Subtitle / captions track attached to the underlying `<video>`.
|
|
89
|
+
*
|
|
90
|
+
* Maps to a `<track kind="subtitles">` child. Default-language priority is
|
|
91
|
+
* implemented in the `subtitles` plugin (see G1, G15).
|
|
92
|
+
*
|
|
93
|
+
* Golden cases: G1 (course learning), G15 (VTT cross-origin).
|
|
94
|
+
*/
|
|
95
|
+
interface SubtitleTrack {
|
|
96
|
+
/** VTT URL. The BE must respond with `Access-Control-Allow-Origin: *` (G15). */
|
|
97
|
+
src: string;
|
|
98
|
+
/** BCP-47 language code (e.g. `"vi"`, `"en-US"`). */
|
|
99
|
+
srcLang: string;
|
|
100
|
+
/** Human-readable label (e.g. `"Tiếng Việt"`). */
|
|
101
|
+
label: string;
|
|
102
|
+
/** When `true`, this track wins the default-selection vote unconditionally. */
|
|
103
|
+
default?: boolean;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Sprite thumbnails (timeline hover preview) descriptor.
|
|
107
|
+
*
|
|
108
|
+
* The `src` URL must point to a WebVTT file whose cues map a time range to a
|
|
109
|
+
* sprite tile, e.g.:
|
|
110
|
+
*
|
|
111
|
+
* ```
|
|
112
|
+
* WEBVTT
|
|
113
|
+
*
|
|
114
|
+
* 00:00:00.000 --> 00:00:10.000
|
|
115
|
+
* sprites/00001.jpg#xywh=0,0,160,90
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
118
|
+
* Image URLs in the cue body may be absolute or relative; relative paths are
|
|
119
|
+
* resolved against the VTT URL. The `@f8team/reel-plugin-thumbnails` plugin
|
|
120
|
+
* fetches and parses the VTT, then exposes a `thumbnails:getAt(time)` command
|
|
121
|
+
* consumed by the React/Lit `SeekBar` hover preview.
|
|
122
|
+
*/
|
|
123
|
+
interface ThumbnailsDescriptor {
|
|
124
|
+
/** Absolute VTT URL describing sprite cues. */
|
|
125
|
+
src: string;
|
|
126
|
+
/**
|
|
127
|
+
* Send credentials when fetching the VTT (and resolved sprite images
|
|
128
|
+
* when the browser permits). Defaults to `false`.
|
|
129
|
+
*/
|
|
130
|
+
withCredentials?: boolean;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The kind of source. `auto` lets the registry pick.
|
|
134
|
+
*
|
|
135
|
+
* - `hls` — HLS m3u8 manifest, dispatched to `hls.js` (or native on Safari).
|
|
136
|
+
* - `mp4` — Progressive MP4/WebM, dispatched to the native engine.
|
|
137
|
+
* - `youtube` — YouTube IFrame API.
|
|
138
|
+
* - `dash` — MPEG-DASH (reserved for a future dash provider).
|
|
139
|
+
* - `native` — Force the native engine even if the URL looks like HLS (G4 Safari escape).
|
|
140
|
+
* - `auto` — Detect from the URL via `looksLikeHls` / `detectSourceType`.
|
|
141
|
+
*/
|
|
142
|
+
type SourceType = "auto" | "hls" | "mp4" | "youtube" | "dash" | "native";
|
|
143
|
+
/**
|
|
144
|
+
* Single playable source. The registry picks the right provider based on
|
|
145
|
+
* `type` and the URL.
|
|
146
|
+
*
|
|
147
|
+
* Golden cases: G1, G2, G3, G4, G5, G7, G10, G11, G14.
|
|
148
|
+
*/
|
|
149
|
+
interface SourceDescriptor {
|
|
150
|
+
/** Absolute or relative URL. May be a `blob:`, `data:`, or `http(s):` URL. */
|
|
151
|
+
src: string;
|
|
152
|
+
/** Optional explicit type hint. Defaults to `"auto"`. */
|
|
153
|
+
type?: SourceType;
|
|
154
|
+
/**
|
|
155
|
+
* Cookie / credentials policy for HLS XHRs.
|
|
156
|
+
*
|
|
157
|
+
* - `boolean` — sent for every request.
|
|
158
|
+
* - `(url) => boolean` — predicate (e.g. allowlist `https://api-gateway*`, see G7, G13).
|
|
159
|
+
*/
|
|
160
|
+
withCredentials?: boolean | ((url: string) => boolean);
|
|
161
|
+
/** Subtitle tracks to attach (G1, G15). */
|
|
162
|
+
tracks?: SubtitleTrack[];
|
|
163
|
+
/**
|
|
164
|
+
* Sprite-thumbnails VTT descriptor for timeline hover previews. When set,
|
|
165
|
+
* the `@f8team/reel-plugin-thumbnails` plugin fetches and parses the VTT;
|
|
166
|
+
* the `Controls.SeekBar` (React) and the Lit default chrome render a tile
|
|
167
|
+
* tooltip during pointer hover.
|
|
168
|
+
*/
|
|
169
|
+
thumbnails?: ThumbnailsDescriptor;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* One quality variant exposed by the engine (HLS levels, DASH representations).
|
|
173
|
+
*
|
|
174
|
+
* Golden case: G7 (course video lesson editor).
|
|
175
|
+
*/
|
|
176
|
+
interface QualityLevel {
|
|
177
|
+
/** Stable identifier produced by the loader (e.g. HLS level index). */
|
|
178
|
+
id: string;
|
|
179
|
+
/** Vertical resolution in pixels (e.g. `720`). */
|
|
180
|
+
height: number;
|
|
181
|
+
/** Average bitrate in bits per second. */
|
|
182
|
+
bitrate: number;
|
|
183
|
+
/** Display label (e.g. `"720p"`, `"Auto"`). */
|
|
184
|
+
label: string;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Imperative loader returned by a `SourceProvider`. The core attaches a single
|
|
188
|
+
* loader at a time; switching sources detaches the previous loader before
|
|
189
|
+
* attaching the new one.
|
|
190
|
+
*/
|
|
191
|
+
interface SourceLoader {
|
|
192
|
+
/** Attach the source to the underlying `<video>`. Resolves on `loadedmetadata`. */
|
|
193
|
+
attach(video: HTMLVideoElement, source: SourceDescriptor): Promise<void>;
|
|
194
|
+
/** Detach the source. Called before the next `attach` and on `dispose`. */
|
|
195
|
+
detach(): void;
|
|
196
|
+
/**
|
|
197
|
+
* Optional: cancel any in-flight network work without tearing down the
|
|
198
|
+
* already-attached media element. The core calls this on rapid
|
|
199
|
+
* `setSource(A) → setSource(B)` transitions so the pending A loader can
|
|
200
|
+
* stop firing callbacks before `detach()` executes.
|
|
201
|
+
*
|
|
202
|
+
* Implementations should:
|
|
203
|
+
* - Abort outstanding XHRs / `fetch` requests.
|
|
204
|
+
* - Reject the pending `attach()` Promise with an `AbortError`.
|
|
205
|
+
* - Remove any listeners whose only purpose is to resolve that Promise.
|
|
206
|
+
*
|
|
207
|
+
* `abort()` MUST be safe to call before `detach()`, after `detach()`, and
|
|
208
|
+
* multiple times. Leave actual media-element / runtime cleanup to `detach()`.
|
|
209
|
+
*/
|
|
210
|
+
abort?(): void;
|
|
211
|
+
/** Optional: enumerate available quality levels (HLS, DASH). */
|
|
212
|
+
getQualities?(): QualityLevel[];
|
|
213
|
+
/** Optional: switch quality. `"auto"` re-enables ABR. */
|
|
214
|
+
setQuality?(level: QualityLevel | "auto"): void;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Provider that knows how to handle a class of sources (HLS, native, YouTube).
|
|
218
|
+
*/
|
|
219
|
+
interface SourceProvider {
|
|
220
|
+
/** Identifier used by `unregisterSource` and debug output. */
|
|
221
|
+
name: string;
|
|
222
|
+
/**
|
|
223
|
+
* Decide whether this provider can handle the given source.
|
|
224
|
+
*
|
|
225
|
+
* - `true` — definite match.
|
|
226
|
+
* - `"maybe"` — handle if no `true` match exists (used by the native fallback).
|
|
227
|
+
* - `false` — skip.
|
|
228
|
+
*/
|
|
229
|
+
canHandle(source: SourceDescriptor): boolean | "maybe";
|
|
230
|
+
/** Construct a fresh loader. Called once per source attach. */
|
|
231
|
+
createLoader(): SourceLoader;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* `unauthorized` event payload. Surfaced once per `source.src` for HLS XHRs
|
|
236
|
+
* that end with HTTP 401 or 403 (G13).
|
|
237
|
+
*/
|
|
238
|
+
interface UnauthorizedEvent {
|
|
239
|
+
/** Failing URL (manifest or segment). */
|
|
240
|
+
url: string;
|
|
241
|
+
/** Always 401 or 403. */
|
|
242
|
+
status: 401 | 403;
|
|
243
|
+
/** The source that triggered the failure. */
|
|
244
|
+
source: SourceDescriptor;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Built-in player events. Plugin events use the `${string}:${string}` form
|
|
248
|
+
* (e.g. `subtitles:change`, `markers:hover`); they pass through the bus
|
|
249
|
+
* untyped because plugins are loaded dynamically.
|
|
250
|
+
*
|
|
251
|
+
* Golden cases:
|
|
252
|
+
* - G1: `timeupdate` powers the 500 ms `onProgress` aggregation.
|
|
253
|
+
* - G2: `play`, `pause`, `ended` drive story progress segments.
|
|
254
|
+
* - G7: `qualitychange` fires when the lesson editor flips levels.
|
|
255
|
+
* - G13: `unauthorized` triggers the auth callback.
|
|
256
|
+
*/
|
|
257
|
+
interface PlayerEvents {
|
|
258
|
+
/** Source loaded; metadata and duration are now valid. */
|
|
259
|
+
ready: {
|
|
260
|
+
duration: number;
|
|
261
|
+
};
|
|
262
|
+
/** Playback started (or resumed). */
|
|
263
|
+
play: void;
|
|
264
|
+
/** Playback paused (user, programmatic, or background). */
|
|
265
|
+
pause: void;
|
|
266
|
+
/** Media ended. */
|
|
267
|
+
ended: void;
|
|
268
|
+
/**
|
|
269
|
+
* Time progress. Fires on every native `timeupdate`; plugins can throttle.
|
|
270
|
+
*
|
|
271
|
+
* - `currentTime` — wall-clock playback position.
|
|
272
|
+
* - `playedSeconds` — alias kept for parity with the legacy F8 player.
|
|
273
|
+
* - `duration` — current duration (matches `state.duration`).
|
|
274
|
+
*/
|
|
275
|
+
timeupdate: {
|
|
276
|
+
currentTime: number;
|
|
277
|
+
playedSeconds: number;
|
|
278
|
+
duration: number;
|
|
279
|
+
};
|
|
280
|
+
/** Duration changed (live HLS, source switch). */
|
|
281
|
+
durationchange: {
|
|
282
|
+
duration: number;
|
|
283
|
+
};
|
|
284
|
+
/** Playback rate changed (G7). */
|
|
285
|
+
ratechange: {
|
|
286
|
+
playbackRate: number;
|
|
287
|
+
};
|
|
288
|
+
/** Volume or mute flipped. */
|
|
289
|
+
volumechange: {
|
|
290
|
+
volume: number;
|
|
291
|
+
muted: boolean;
|
|
292
|
+
};
|
|
293
|
+
/** Seek started (user or programmatic). */
|
|
294
|
+
seeking: {
|
|
295
|
+
time: number;
|
|
296
|
+
};
|
|
297
|
+
/** Seek finished. */
|
|
298
|
+
seeked: {
|
|
299
|
+
time: number;
|
|
300
|
+
};
|
|
301
|
+
/** Buffering toggled. `isBuffering=true` means stalled (`waiting`), including after a seek scrub. */
|
|
302
|
+
buffering: {
|
|
303
|
+
isBuffering: boolean;
|
|
304
|
+
};
|
|
305
|
+
/** Quality level changed. `auto=true` means ABR picked it. */
|
|
306
|
+
qualitychange: {
|
|
307
|
+
quality: QualityLevel | null;
|
|
308
|
+
auto: boolean;
|
|
309
|
+
};
|
|
310
|
+
/**
|
|
311
|
+
* User explicitly chose a rendition via `hls:setQuality` (manual height or
|
|
312
|
+
* auto when `qualityHeight` is `null`). Does **not** fire on source attach or
|
|
313
|
+
* ABR-driven switches — safe for persisting preferences without clobbering.
|
|
314
|
+
*/
|
|
315
|
+
qualitypreference: {
|
|
316
|
+
qualityHeight: number | null;
|
|
317
|
+
};
|
|
318
|
+
/**
|
|
319
|
+
* Manual HLS rendition switch in flight (manual level or AUTO). Clears once
|
|
320
|
+
* hls.js reports `LEVEL_SWITCHED` for the awaited target / auto step, or after
|
|
321
|
+
* a safety timeout. Use for a transient center spinner.
|
|
322
|
+
*/
|
|
323
|
+
qualityswitch: {
|
|
324
|
+
active: boolean;
|
|
325
|
+
};
|
|
326
|
+
/** Any error (transport, decode, source). */
|
|
327
|
+
error: PlayerError;
|
|
328
|
+
/** Authentication failure (G13). Fires at most once per source.src. */
|
|
329
|
+
unauthorized: UnauthorizedEvent;
|
|
330
|
+
/** Fullscreen toggled. */
|
|
331
|
+
fullscreenchange: {
|
|
332
|
+
fullscreen: boolean;
|
|
333
|
+
};
|
|
334
|
+
/** Picture-in-Picture toggled. */
|
|
335
|
+
pipchange: {
|
|
336
|
+
pip: boolean;
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Lifecycle status of the player. The state machine guarantees these
|
|
342
|
+
* transitions; see `docs/spec/architecture.md` for the transition diagram.
|
|
343
|
+
*/
|
|
344
|
+
type PlayerStatus = "idle" | "loading" | "ready" | "playing" | "paused" | "ended" | "error";
|
|
345
|
+
/** Buffered range (start/end in seconds). */
|
|
346
|
+
interface BufferedRange {
|
|
347
|
+
start: number;
|
|
348
|
+
end: number;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Snapshot of the player's reactive state. Every adapter renders against
|
|
352
|
+
* either the snapshot (`getState()`) or a memoized selector via `subscribe`.
|
|
353
|
+
*
|
|
354
|
+
* Golden cases: G7 (qualities, playback rate), G8 (currentTime/duration drives
|
|
355
|
+
* seek bar), G13 (error.code === "unauthorized"), G14 (videoWidth/Height
|
|
356
|
+
* confirm playback started inline on iOS).
|
|
357
|
+
*/
|
|
358
|
+
interface PlayerState {
|
|
359
|
+
/** Lifecycle status; never overlapping. */
|
|
360
|
+
status: PlayerStatus;
|
|
361
|
+
/** Active source. `null` until `setSource` is called. */
|
|
362
|
+
source: SourceDescriptor | null;
|
|
363
|
+
/** Current playback position in seconds. */
|
|
364
|
+
currentTime: number;
|
|
365
|
+
/** Media duration in seconds. `0` while loading; `Infinity` for live HLS. */
|
|
366
|
+
duration: number;
|
|
367
|
+
/** Buffered ranges in seconds. */
|
|
368
|
+
buffered: BufferedRange[];
|
|
369
|
+
/** Playback rate (`1` is normal speed). G7 lists [0.25..1.75]. */
|
|
370
|
+
playbackRate: number;
|
|
371
|
+
/** Volume on the `[0, 1]` scale. */
|
|
372
|
+
volume: number;
|
|
373
|
+
/** Whether the underlying `<video>` is muted. */
|
|
374
|
+
muted: boolean;
|
|
375
|
+
/** Native video width (after `loadedmetadata`). */
|
|
376
|
+
videoWidth: number;
|
|
377
|
+
/** Native video height (after `loadedmetadata`). */
|
|
378
|
+
videoHeight: number;
|
|
379
|
+
/** Whether Picture-in-Picture is currently active. */
|
|
380
|
+
pip: boolean;
|
|
381
|
+
/** Whether the document/element is in fullscreen. */
|
|
382
|
+
fullscreen: boolean;
|
|
383
|
+
/** Quality levels exposed by the loader (G7). Empty for native engines. */
|
|
384
|
+
qualities: QualityLevel[];
|
|
385
|
+
/** Active quality level. `null` while ABR is auto / loader does not expose levels. */
|
|
386
|
+
activeQuality: QualityLevel | null;
|
|
387
|
+
/** Latest error, if any. Cleared on `setSource` or successful retry. */
|
|
388
|
+
error: PlayerError | null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Disposer returned by every subscription / listener helper. Idempotent.
|
|
393
|
+
*/
|
|
394
|
+
type Disposer = () => void;
|
|
395
|
+
/**
|
|
396
|
+
* Public Player surface. Returned by `createPlayer(options)`.
|
|
397
|
+
*
|
|
398
|
+
* Lifecycle: `createPlayer` → `attach(<video>)` → use → `detach` (optional)
|
|
399
|
+
* → `dispose`. After `dispose`, every subsequent call is a no-op (or throws,
|
|
400
|
+
* for clearly invalid use).
|
|
401
|
+
*
|
|
402
|
+
* Golden cases:
|
|
403
|
+
* - G12: `play / pause / paused / seekTo` shape-conform with `VideoPlayerHandle`.
|
|
404
|
+
* - G7: `setPlaybackRate`, `setVolume`, `setMuted`.
|
|
405
|
+
* - G8: `seekTo` accepts seconds; the React adapter additionally accepts `"00:01:23"`.
|
|
406
|
+
*/
|
|
407
|
+
interface Player {
|
|
408
|
+
/** Replace (or clear) the active source. */
|
|
409
|
+
setSource(source: SourceDescriptor | null): void;
|
|
410
|
+
/** Snapshot of the active source. */
|
|
411
|
+
getSource(): SourceDescriptor | null;
|
|
412
|
+
/**
|
|
413
|
+
* Re-attempt loading the current source. No-op if no source is set.
|
|
414
|
+
*
|
|
415
|
+
* Internally equivalent to `setSource(getSource())`: the state machine
|
|
416
|
+
* detaches the failed loader, clears the error, and starts a fresh
|
|
417
|
+
* `loading → ready` cycle. Use this from a "Try again" button on an
|
|
418
|
+
* error overlay.
|
|
419
|
+
*
|
|
420
|
+
* @returns `true` when retry was dispatched, `false` when there was
|
|
421
|
+
* nothing to retry.
|
|
422
|
+
*/
|
|
423
|
+
retry(): boolean;
|
|
424
|
+
/** Start playback. May reject (autoplay policy, no source, dispose). */
|
|
425
|
+
play(): Promise<void>;
|
|
426
|
+
/** Pause playback. */
|
|
427
|
+
pause(): void;
|
|
428
|
+
/** Whether the player is paused. */
|
|
429
|
+
paused(): boolean;
|
|
430
|
+
/** Seek to a time in seconds. Clamped to `[0, duration]`. */
|
|
431
|
+
seekTo(seconds: number): void;
|
|
432
|
+
/** Set playback rate. */
|
|
433
|
+
setPlaybackRate(rate: number): void;
|
|
434
|
+
/** Set volume on `[0, 1]`. */
|
|
435
|
+
setVolume(volume: number): void;
|
|
436
|
+
/** Set muted state. */
|
|
437
|
+
setMuted(muted: boolean): void;
|
|
438
|
+
/** Snapshot read of the full state. */
|
|
439
|
+
getState(): PlayerState;
|
|
440
|
+
/** Current playback position in seconds. */
|
|
441
|
+
getCurrentTime(): number;
|
|
442
|
+
/** Current duration in seconds. */
|
|
443
|
+
getDuration(): number;
|
|
444
|
+
/** Buffered ranges. */
|
|
445
|
+
getBuffered(): BufferedRange[];
|
|
446
|
+
/**
|
|
447
|
+
* Subscribe to a built-in event. Returns a disposer.
|
|
448
|
+
*
|
|
449
|
+
* Plugin events (`subtitles:change`, etc.) ride the same bus but lose
|
|
450
|
+
* type-safety because they are loaded dynamically. Plugins augment via
|
|
451
|
+
* declaration-merging if they need typed events.
|
|
452
|
+
*/
|
|
453
|
+
on<K extends keyof PlayerEvents>(event: K, handler: (payload: PlayerEvents[K]) => void): Disposer;
|
|
454
|
+
/** Remove an event handler. */
|
|
455
|
+
off<K extends keyof PlayerEvents>(event: K, handler: (payload: PlayerEvents[K]) => void): void;
|
|
456
|
+
/** Subscribe to a slice of state. The listener fires only when the slice changes (===). */
|
|
457
|
+
subscribe<T>(selector: (state: PlayerState) => T, listener: (value: T) => void): Disposer;
|
|
458
|
+
/** Register a plugin after construction. */
|
|
459
|
+
use(plugin: PluginInstance): void;
|
|
460
|
+
/** Remove a plugin by name. Calls its teardown. */
|
|
461
|
+
removePlugin(name: string): void;
|
|
462
|
+
/** Shared command registry. */
|
|
463
|
+
commands: CommandRegistry;
|
|
464
|
+
/** Attach to a `<video>` element. Resolves once `loadedmetadata` fires. */
|
|
465
|
+
attach(el: HTMLVideoElement): Promise<void>;
|
|
466
|
+
/** Detach from the current `<video>` without disposing the player. */
|
|
467
|
+
detach(): void;
|
|
468
|
+
/** Dispose every resource. After this, the player is dead. */
|
|
469
|
+
dispose(): void;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Command registry shared between the player and plugins. Commands are the
|
|
474
|
+
* extension point for keyboard handlers, plugin contributions, and UI buttons.
|
|
475
|
+
*/
|
|
476
|
+
interface CommandRegistry {
|
|
477
|
+
/**
|
|
478
|
+
* Register a command. Returns a disposer.
|
|
479
|
+
*
|
|
480
|
+
* Commands MUST be namespaced with a `<plugin>:<verb>` form
|
|
481
|
+
* (e.g. `markers:seek`). Built-in commands use the `core:` prefix.
|
|
482
|
+
*
|
|
483
|
+
* Re-registering an identical name throws.
|
|
484
|
+
*/
|
|
485
|
+
add<P = void>(name: string, handler: (payload: P) => void): () => void;
|
|
486
|
+
/** Run a command. No-op if the command does not exist (idempotent). */
|
|
487
|
+
run<P = void>(name: string, payload?: P): void;
|
|
488
|
+
/** Whether the command exists. */
|
|
489
|
+
has(name: string): boolean;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Plugin host handed to each plugin's `setup` function. Provides the only
|
|
493
|
+
* sanctioned ways to extend the player (controls, commands, store, events).
|
|
494
|
+
*/
|
|
495
|
+
interface PluginHost {
|
|
496
|
+
controls: {
|
|
497
|
+
/**
|
|
498
|
+
* Contribute a UI fragment to a slot owned by an adapter (e.g. `seekbar.overlay`,
|
|
499
|
+
* `menu.settings`). Adapters render the returned `Element` directly.
|
|
500
|
+
*
|
|
501
|
+
* Returns a disposer. Plugins SHOULD store the disposer and call it on
|
|
502
|
+
* cleanup; the player calls every disposer on `dispose`.
|
|
503
|
+
*/
|
|
504
|
+
contribute(slot: string, render: () => Element): () => void;
|
|
505
|
+
};
|
|
506
|
+
/** Shared command registry. Plugins MUST namespace their commands. */
|
|
507
|
+
commands: CommandRegistry;
|
|
508
|
+
/** Read-only state. */
|
|
509
|
+
store: ReadableStore<PlayerState>;
|
|
510
|
+
/** Emit a custom plugin event. Use the `${plugin}:${verb}` namespace. */
|
|
511
|
+
emit<K extends string>(event: K, payload?: unknown): void;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Plugin instance. The factory pattern (`subtitles({ defaultLang })`) returns
|
|
515
|
+
* an instance.
|
|
516
|
+
*/
|
|
517
|
+
interface PluginInstance {
|
|
518
|
+
/** Unique name. Re-registering the same name throws. */
|
|
519
|
+
readonly name: string;
|
|
520
|
+
/**
|
|
521
|
+
* Setup hook. Called once when the plugin is registered (either via
|
|
522
|
+
* `PlayerOptions.plugins` or `player.use(...)`).
|
|
523
|
+
*
|
|
524
|
+
* Optional return value: a teardown function called on `removePlugin` or
|
|
525
|
+
* `dispose`.
|
|
526
|
+
*/
|
|
527
|
+
setup(player: Player, host: PluginHost): void | (() => void);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Theme tokens. Every visible token is a CSS custom property that adapters
|
|
532
|
+
* inject via `style` or scoped CSS. Phase 1 ships the type only; Phase 1.K
|
|
533
|
+
* defines the runtime token defaults; Phase 3 ships the four shipping themes
|
|
534
|
+
* (`classroom`, `story`, `admin`, `minimal`).
|
|
535
|
+
*/
|
|
536
|
+
type ThemeName = "classroom" | "story" | "admin" | "minimal";
|
|
537
|
+
/**
|
|
538
|
+
* Subset of overridable design tokens. Adapters merge a partial set into the
|
|
539
|
+
* defaults at runtime.
|
|
540
|
+
*/
|
|
541
|
+
interface ThemeTokens {
|
|
542
|
+
"color-bg"?: string;
|
|
543
|
+
"color-fg"?: string;
|
|
544
|
+
"color-accent"?: string;
|
|
545
|
+
"color-overlay"?: string;
|
|
546
|
+
"control-size"?: string;
|
|
547
|
+
"control-radius"?: string;
|
|
548
|
+
"control-gap"?: string;
|
|
549
|
+
"control-padding"?: string;
|
|
550
|
+
"focus-ring-color"?: string;
|
|
551
|
+
"focus-ring-width"?: string;
|
|
552
|
+
"seekbar-height"?: string;
|
|
553
|
+
"seekbar-buffered"?: string;
|
|
554
|
+
"seekbar-played"?: string;
|
|
555
|
+
"seekbar-knob-size"?: string;
|
|
556
|
+
"font-family"?: string;
|
|
557
|
+
"font-size-sm"?: string;
|
|
558
|
+
"font-size-md"?: string;
|
|
559
|
+
"motion-fast"?: string;
|
|
560
|
+
"motion-base"?: string;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Autoplay policy.
|
|
565
|
+
*
|
|
566
|
+
* - `off` (default) — never auto-play.
|
|
567
|
+
* - `muted` — `play()` after `attach`, with `muted = true` (browser-friendly).
|
|
568
|
+
* - `on` — attempt to play with the current `muted` state. May reject.
|
|
569
|
+
*/
|
|
570
|
+
type AutoplayMode = "off" | "muted" | "on";
|
|
571
|
+
/**
|
|
572
|
+
* Keyboard scope.
|
|
573
|
+
*
|
|
574
|
+
* - `global` — listen on `document.body` (default — G1, G3).
|
|
575
|
+
* - `container` — listen on the player root only (G16 — embedded surfaces).
|
|
576
|
+
* - `off` — no built-in handlers (G2 story owns its own keys).
|
|
577
|
+
*/
|
|
578
|
+
type KeyboardScope = "global" | "container" | "off";
|
|
579
|
+
/**
|
|
580
|
+
* Hooks called once per source / event. Hooks are sticky listeners — they
|
|
581
|
+
* survive across renders / state updates because they live on the player
|
|
582
|
+
* instance.
|
|
583
|
+
*/
|
|
584
|
+
interface PlayerHooks {
|
|
585
|
+
/**
|
|
586
|
+
* Authenticated HLS / DASH XHR ended with 401 or 403 (G13). The legacy
|
|
587
|
+
* F8 alias `onStreamUnauthorized` maps to this hook in `f8-ui`.
|
|
588
|
+
*/
|
|
589
|
+
onUnauthorized?: (event: UnauthorizedEvent) => void;
|
|
590
|
+
/** Any error (transport, decode, source). */
|
|
591
|
+
onError?: (error: PlayerError) => void;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Options accepted by `createPlayer(options)`.
|
|
595
|
+
*
|
|
596
|
+
* Every field is optional. The player attaches with no source until
|
|
597
|
+
* `setSource` is called or `options.source` is provided.
|
|
598
|
+
*/
|
|
599
|
+
interface PlayerOptions {
|
|
600
|
+
/** Initial source. Omit to attach without playable media. */
|
|
601
|
+
source?: SourceDescriptor;
|
|
602
|
+
/** Autoplay policy. Defaults to `"off"`. */
|
|
603
|
+
autoplay?: AutoplayMode;
|
|
604
|
+
/** Seconds to seek to on first ready. */
|
|
605
|
+
startTime?: number;
|
|
606
|
+
/** Whether to loop on `ended`. */
|
|
607
|
+
loop?: boolean;
|
|
608
|
+
/** Initial muted state. Defaults to `false` (or `true` if autoplay is `muted`). */
|
|
609
|
+
muted?: boolean;
|
|
610
|
+
/** Initial volume on `[0, 1]`. */
|
|
611
|
+
volume?: number;
|
|
612
|
+
/** Initial playback rate. */
|
|
613
|
+
playbackRate?: number;
|
|
614
|
+
/** Poster URL — passed to `<video poster>`. */
|
|
615
|
+
poster?: string;
|
|
616
|
+
/** `<video preload>`. */
|
|
617
|
+
preload?: "auto" | "metadata" | "none";
|
|
618
|
+
/** iOS inline playback (G2, G14). Defaults to `true`. */
|
|
619
|
+
playsInline?: boolean;
|
|
620
|
+
/**
|
|
621
|
+
* `<video crossorigin>`. Defaults to `"anonymous"` (T3.5) so WebVTT tracks
|
|
622
|
+
* load with valid CORS, HLS segments can be fetched with `withCredentials`,
|
|
623
|
+
* and `<canvas drawImage(video)>` does not taint the canvas.
|
|
624
|
+
*
|
|
625
|
+
* Pass `null` explicitly to omit the attribute (rare — only for legacy
|
|
626
|
+
* embeds where the response lacks `Access-Control-Allow-Origin`).
|
|
627
|
+
*/
|
|
628
|
+
crossOrigin?: "anonymous" | "use-credentials" | null;
|
|
629
|
+
/** Plugins to register synchronously at `createPlayer` time. */
|
|
630
|
+
plugins?: PluginInstance[];
|
|
631
|
+
/** Theme name or runtime token overrides. */
|
|
632
|
+
theme?: ThemeName | ThemeTokens;
|
|
633
|
+
/** Lifecycle hooks. */
|
|
634
|
+
hooks?: PlayerHooks;
|
|
635
|
+
/** Keyboard scoping. Adapters may override per-mount (G16). */
|
|
636
|
+
keyboard?: {
|
|
637
|
+
scope?: KeyboardScope;
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* `createPlayer(options)` — wires every piece of `@f8team/reel-core` into a
|
|
643
|
+
* single `Player`. The factory is the one and only public entry point.
|
|
644
|
+
*
|
|
645
|
+
* Responsibilities:
|
|
646
|
+
* 1. Build the reactive store seeded with the initial PlayerOptions.
|
|
647
|
+
* 2. Build the event bus, command registry, controls registry, plugin bus.
|
|
648
|
+
* 3. Register the always-on native source provider (and HLS / YouTube
|
|
649
|
+
* providers with callbacks bound to this player's store and bus).
|
|
650
|
+
* 4. Bridge `<video>` events ⇄ the state-machine reducer.
|
|
651
|
+
* 5. Apply machine effects (attach/detach source, mediaPlay/Pause/Seek,
|
|
652
|
+
* clearError/setError) against the live `<video>` element.
|
|
653
|
+
* 6. Expose the `Player` interface defined in
|
|
654
|
+
* `docs/spec/api-contract.md`.
|
|
655
|
+
*
|
|
656
|
+
* Tests live next to this file and validate the external behavior; lower-level
|
|
657
|
+
* pieces (store, machine, sources, plugins, etc.) are covered by their own
|
|
658
|
+
* unit tests.
|
|
659
|
+
*/
|
|
660
|
+
|
|
661
|
+
/** @internal — extra knobs surfaced for tests and adapters. */
|
|
662
|
+
interface CreatePlayerInternalOptions {
|
|
663
|
+
/** Override the microtask scheduler for the store. Tests use this. */
|
|
664
|
+
storeOptions?: CreateStoreOptions<PlayerState>;
|
|
665
|
+
/** Inject extra source providers (e.g. dash, in-house DRM). */
|
|
666
|
+
extraProviders?: SourceProvider[];
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Build a fresh `Player`. Safe to construct in the browser, jsdom, or
|
|
670
|
+
* server-rendering tests (only `attach()` touches the DOM).
|
|
671
|
+
*/
|
|
672
|
+
declare function createPlayer(options?: PlayerOptions, internal?: CreatePlayerInternalOptions): Player;
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Identity helper used by plugin authors so editors get type narrowing
|
|
676
|
+
* on `setup` arguments without typing `as PluginInstance` at every call site.
|
|
677
|
+
*
|
|
678
|
+
* @example
|
|
679
|
+
* const subtitles = definePlugin({
|
|
680
|
+
* name: "subtitles",
|
|
681
|
+
* setup: (player, host) => { ... },
|
|
682
|
+
* });
|
|
683
|
+
*/
|
|
684
|
+
|
|
685
|
+
declare function definePlugin(spec: PluginInstance): PluginInstance;
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Apply theme tokens to an element's `style`.
|
|
689
|
+
*
|
|
690
|
+
* Adapters call this once per mount and again whenever the theme prop changes.
|
|
691
|
+
* Plugins can overlay extra tokens through the same helper.
|
|
692
|
+
*/
|
|
693
|
+
|
|
694
|
+
interface ApplyThemeArgs {
|
|
695
|
+
/** Theme preset name. `undefined` skips preset application. */
|
|
696
|
+
preset?: ThemeName;
|
|
697
|
+
/** Extra token overrides. Wins over the preset. */
|
|
698
|
+
overrides?: ThemeTokens;
|
|
699
|
+
/** Whether to apply the neutral defaults first. Default: `true`. */
|
|
700
|
+
applyDefaults?: boolean;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Resolve a final token map by layering defaults → preset → overrides.
|
|
704
|
+
*/
|
|
705
|
+
declare function resolveTokens(args?: ApplyThemeArgs): Partial<ThemeTokens>;
|
|
706
|
+
/**
|
|
707
|
+
* Apply tokens to `element.style`. Returns a disposer that resets the
|
|
708
|
+
* variables to their previous values (useful for adapters that need to
|
|
709
|
+
* roll back on theme change).
|
|
710
|
+
*/
|
|
711
|
+
declare function applyTheme(element: HTMLElement, args?: ApplyThemeArgs): () => void;
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Default token values. Phase 3 ships theme CSS files (`classroom.css`,
|
|
715
|
+
* `story.css`, `admin.css`, `minimal.css`) that override these via class
|
|
716
|
+
* selectors. The defaults below are intentionally neutral so an unstyled
|
|
717
|
+
* player still looks reasonable.
|
|
718
|
+
*/
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Token name → CSS custom property name. Adapters write to the variable
|
|
722
|
+
* directly so consumers can also use plain CSS to override.
|
|
723
|
+
*/
|
|
724
|
+
declare const TOKEN_TO_CSS_VAR: Record<keyof ThemeTokens, string>;
|
|
725
|
+
/**
|
|
726
|
+
* Neutral default tokens. Adapters apply these on the player root before any
|
|
727
|
+
* theme class wins. Values use `1rem = 10px` (F8 frontends use the 62.5%
|
|
728
|
+
* font-size convention).
|
|
729
|
+
*/
|
|
730
|
+
declare const DEFAULT_TOKENS: Required<ThemeTokens>;
|
|
731
|
+
/** Built-in theme presets. Phase 3 packages the CSS counterpart. */
|
|
732
|
+
declare const THEME_PRESETS: Record<ThemeName, ThemeTokens>;
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Single shared off-screen `aria-live` region. Adapters / plugins call
|
|
736
|
+
* `announce("Subtitles on")` to push messages to assistive tech without
|
|
737
|
+
* jumping focus.
|
|
738
|
+
*
|
|
739
|
+
* The region is created lazily (so SSR-only consumers pay nothing) and
|
|
740
|
+
* polite by default. Screen readers respect rate-limit conventions, so
|
|
741
|
+
* announcements queue up and read sequentially.
|
|
742
|
+
*/
|
|
743
|
+
/**
|
|
744
|
+
* Announce a message to assistive tech. No-op outside the browser.
|
|
745
|
+
*
|
|
746
|
+
* Subsequent calls with the same string force a fresh DOM mutation (we
|
|
747
|
+
* blank the region for one frame) so screen readers re-read identical
|
|
748
|
+
* messages — useful for repeated state toggles like Mute on/off.
|
|
749
|
+
*/
|
|
750
|
+
declare function announce(message: string): void;
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Minimal focus trap helper.
|
|
754
|
+
*
|
|
755
|
+
* Used by adapters in fullscreen so Tab / Shift+Tab stays inside the player.
|
|
756
|
+
* Not a replacement for full focus-management libraries (focus-trap-react)
|
|
757
|
+
* — but enough for the player's small, well-known interactive surface.
|
|
758
|
+
*/
|
|
759
|
+
interface FocusTrap {
|
|
760
|
+
/** Activate the trap. Optionally focuses the first focusable. */
|
|
761
|
+
activate(opts?: {
|
|
762
|
+
focusFirst?: boolean;
|
|
763
|
+
}): void;
|
|
764
|
+
/** Deactivate. Optionally restores focus to the previously focused element. */
|
|
765
|
+
deactivate(opts?: {
|
|
766
|
+
restoreFocus?: boolean;
|
|
767
|
+
}): void;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Build a focus trap rooted at `container`.
|
|
771
|
+
*
|
|
772
|
+
* The trap installs a `keydown` listener and rotates focus among the
|
|
773
|
+
* descendants matching `FOCUSABLE_SELECTOR` when Tab/Shift+Tab fires near
|
|
774
|
+
* the boundary.
|
|
775
|
+
*/
|
|
776
|
+
declare function createFocusTrap(container: HTMLElement): FocusTrap;
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Time helpers shared by the seek bar, transcript markers (G8), and the
|
|
780
|
+
* keyboard / commands layer.
|
|
781
|
+
*/
|
|
782
|
+
/**
|
|
783
|
+
* Parse a time string like `"00:01:23"`, `"1:23"`, `"00:01:23.456"` to seconds.
|
|
784
|
+
* Returns `NaN` for invalid input — callers should guard.
|
|
785
|
+
*
|
|
786
|
+
* Accepted forms:
|
|
787
|
+
* - `"H:MM:SS"` / `"H:MM:SS.fff"`
|
|
788
|
+
* - `"M:SS"` / `"MM:SS"` / `"MM:SS.fff"`
|
|
789
|
+
*
|
|
790
|
+
* Anything else (including bare numbers, negative values, NaN strings) is
|
|
791
|
+
* `NaN`. Callers that want to accept numbers SHOULD test `typeof === "number"`
|
|
792
|
+
* first.
|
|
793
|
+
*/
|
|
794
|
+
declare function parseTime(input: string): number;
|
|
795
|
+
/**
|
|
796
|
+
* Format seconds to `H:MM:SS` (when ≥ 1h) or `MM:SS` (otherwise). NaN /
|
|
797
|
+
* negative inputs are coerced to `0:00`.
|
|
798
|
+
*
|
|
799
|
+
* The ARIA-friendly verbose form (`"1 hour 2 minutes"`) is the adapter's job.
|
|
800
|
+
*/
|
|
801
|
+
declare function formatTime(seconds: number): string;
|
|
802
|
+
/**
|
|
803
|
+
* Coerce a time-like input to seconds. Accepts numbers, formatted strings,
|
|
804
|
+
* and clamps negatives to 0.
|
|
805
|
+
*
|
|
806
|
+
* Returns `NaN` if the input cannot be coerced (so callers can refuse to
|
|
807
|
+
* call `seekTo` rather than seeking to 0 silently).
|
|
808
|
+
*/
|
|
809
|
+
declare function toSeconds(input: number | string): number;
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* URL helpers used by the source registry to dispatch a source descriptor to
|
|
813
|
+
* the right provider. Pure string predicates — no DOM, no I/O.
|
|
814
|
+
*/
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Heuristic for HLS m3u8 URLs.
|
|
818
|
+
*
|
|
819
|
+
* Accepts:
|
|
820
|
+
* - `*.m3u8` (case-insensitive) at any path/query position
|
|
821
|
+
* - `application/vnd.apple.mpegurl` MIME hints inside `?type=`
|
|
822
|
+
*
|
|
823
|
+
* Rejects:
|
|
824
|
+
* - YouTube-hosted m3u8 (handled by the YouTube provider).
|
|
825
|
+
* - Empty / non-string inputs.
|
|
826
|
+
*/
|
|
827
|
+
declare function looksLikeHls(input: string | undefined | null): boolean;
|
|
828
|
+
/**
|
|
829
|
+
* YouTube URL recognition. Matches `youtube.com/watch?v=`, `youtu.be/<id>`,
|
|
830
|
+
* `youtube.com/embed/<id>`, and `youtube.com/shorts/<id>`.
|
|
831
|
+
*/
|
|
832
|
+
declare function looksLikeYouTube(input: string | undefined | null): boolean;
|
|
833
|
+
/**
|
|
834
|
+
* MP4 / WebM / Ogg recognition by extension or `blob:` / `data:` schemes.
|
|
835
|
+
*/
|
|
836
|
+
declare function looksLikeProgressive(input: string | undefined | null): boolean;
|
|
837
|
+
/**
|
|
838
|
+
* Resolve the effective `SourceType` for a descriptor.
|
|
839
|
+
*
|
|
840
|
+
* Honors an explicit non-`auto` type. For `auto`, dispatch by URL: HLS first,
|
|
841
|
+
* then YouTube, then progressive, falling back to `native` (which dispatches
|
|
842
|
+
* to the always-on native loader).
|
|
843
|
+
*/
|
|
844
|
+
declare function detectSourceType(source: SourceDescriptor): SourceType;
|
|
845
|
+
|
|
846
|
+
export { type AutoplayMode, type BufferedRange, type CommandRegistry, DEFAULT_TOKENS, type Disposer, type KeyboardScope, type MutableStore, type Player, type PlayerError, type PlayerErrorCode, type PlayerEvents, type PlayerHooks, type PlayerOptions, type PlayerState, type PlayerStatus, type PluginHost, type PluginInstance, type QualityLevel, type ReadableStore, type SourceDescriptor, type SourceLoader, type SourceProvider, type SourceType, type SubtitleTrack, THEME_PRESETS, TOKEN_TO_CSS_VAR, type ThemeName, type ThemeTokens, type ThumbnailsDescriptor, type UnauthorizedEvent, announce, applyTheme, createFocusTrap, createPlayer, definePlugin, detectSourceType, formatTime, looksLikeHls, looksLikeProgressive, looksLikeYouTube, parseTime, resolveTokens, toSeconds };
|