bobinejs 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 the bobine authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,108 @@
1
- # Temporary Holding Version
1
+ # bobine
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Drop-in video and audio players for React. Swap `<video>` for `<VideoPlayer>` and keep every native prop.
4
+
5
+ Two ways to use it:
6
+
7
+ - **Own the source** (recommended): `npx shadcn@latest add https://bobine.fyi/r/video-player.json`
8
+ - **Install the package**: `pnpm add bobinejs`
9
+
10
+ ## Package setup
11
+
12
+ Requires React 19 and Tailwind CSS v4 with shadcn-style theme tokens (`--background`, `--foreground`, `--muted`, `--primary`, `--border`, …).
13
+
14
+ ```css
15
+ @import "tailwindcss";
16
+ @import "bobinejs/styles.css";
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```tsx
22
+ import { VideoPlayer, AudioPlayer } from "bobinejs"
23
+
24
+ <VideoPlayer
25
+ src="/clip.mp4"
26
+ poster="/clip.jpg"
27
+ tracks={[{ src: "/clip.en.vtt", label: "English", srcLang: "en", default: true }]}
28
+ persistKey="bobine"
29
+ />
30
+
31
+ <AudioPlayer src="/episode.mp3" title="Episode 12" description="Field notes" artwork="/cover.jpg" showSpeed />
32
+ ```
33
+
34
+ ### HLS
35
+
36
+ ```tsx
37
+ "use client"
38
+ import { VideoPlayer } from "bobinejs"
39
+ import { hlsLoader } from "bobinejs/hls" // requires `hls.js`, loaded on demand
40
+
41
+ <VideoPlayer src="https://example.com/stream.m3u8" loader={hlsLoader} />
42
+ ```
43
+
44
+ ### Compose your own
45
+
46
+ ```tsx
47
+ import { MediaRoot, MediaVideo, MediaSurface, MediaControls, MediaSeek, MediaPlayButton, MediaTime } from "bobinejs"
48
+
49
+ <MediaRoot className="aspect-video bg-media-scrim text-media-foreground">
50
+ <MediaVideo src="/clip.mp4" className="size-full" />
51
+ <MediaSurface />
52
+ <MediaControls className="absolute inset-x-3 bottom-3 flex-col">
53
+ <MediaSeek />
54
+ <div className="flex items-center gap-1">
55
+ <MediaPlayButton />
56
+ <MediaTime /> / <MediaTime type="duration" />
57
+ </div>
58
+ </MediaControls>
59
+ </MediaRoot>
60
+ ```
61
+
62
+ ### Types
63
+
64
+ Every component exports its props type under its own name plus `Props`, so a
65
+ wrapper can extend it.
66
+
67
+ ```tsx
68
+ import { AudioPlayer, type AudioPlayerProps } from "bobinejs"
69
+
70
+ function EpisodePlayer(props: AudioPlayerProps) {
71
+ return <AudioPlayer showSpeed persistKey="episodes" {...props} />
72
+ }
73
+ ```
74
+
75
+ `MediaState`, `MediaStore` and `MediaInfo` type the store and its metadata.
76
+
77
+ ### Framework-agnostic core
78
+
79
+ `bobinejs/core` exports `createMediaStore()`, a dependency-free store you can `attach` to any `HTMLMediaElement`.
80
+
81
+ ```ts
82
+ import { createMediaStore } from "bobinejs/core"
83
+
84
+ const store = createMediaStore()
85
+ store.attach(document.querySelector("video")!)
86
+ store.subscribe(() => console.log(store.getState().currentTime))
87
+ ```
88
+
89
+ ## Keyboard
90
+
91
+ | Key | Action |
92
+ | --- | --- |
93
+ | Space / K | Play or pause |
94
+ | J / L | Seek 10s |
95
+ | ← / → | Seek 5s |
96
+ | ↑ / ↓ | Volume |
97
+ | 0–9 | Jump to 0–90% |
98
+ | Home / End | Start / end |
99
+ | M | Mute |
100
+ | C | Captions |
101
+ | I | Picture-in-picture |
102
+ | F | Fullscreen |
103
+
104
+ Shortcuts only apply to the focused player.
105
+
106
+ ## License
107
+
108
+ MIT
package/dist/core.d.ts ADDED
@@ -0,0 +1,78 @@
1
+ type MediaTextTrack = {
2
+ label: string;
3
+ language: string;
4
+ kind: "captions" | "subtitles";
5
+ };
6
+ type MediaInfo = {
7
+ title?: string;
8
+ artist?: string;
9
+ album?: string;
10
+ artwork?: {
11
+ src: string;
12
+ sizes?: string;
13
+ type?: string;
14
+ }[];
15
+ };
16
+ type MediaState = {
17
+ paused: boolean;
18
+ ended: boolean;
19
+ started: boolean;
20
+ waiting: boolean;
21
+ currentTime: number;
22
+ duration: number;
23
+ bufferedEnd: number;
24
+ volume: number;
25
+ muted: boolean;
26
+ playbackRate: number;
27
+ fullscreen: boolean;
28
+ pictureInPicture: boolean;
29
+ error: MediaError | null;
30
+ /** Caption and subtitle tracks, in document order. */
31
+ textTracks: MediaTextTrack[];
32
+ /** Index into `textTracks`, or -1 when captions are off. */
33
+ textTrack: number;
34
+ /** Text of the cues active right now, one entry per cue. */
35
+ cues: string[];
36
+ };
37
+ type MediaStoreOptions = {
38
+ /** localStorage key used to remember volume, mute and speed. Off by default. */
39
+ persistKey?: string;
40
+ };
41
+ type Listener = () => void;
42
+ /**
43
+ * Framework-agnostic store over any HTMLMediaElement. React bindings,
44
+ * web components, or anything else can subscribe to it.
45
+ */
46
+ declare function createMediaStore({ persistKey }?: MediaStoreOptions): {
47
+ play: () => Promise<void>;
48
+ pause: () => void | undefined;
49
+ toggle: () => void | Promise<void>;
50
+ seek: (time: number) => void;
51
+ seekBy: (delta: number) => void;
52
+ /** Jump to a fraction (0–1) of the duration. */
53
+ seekToRatio: (ratio: number) => void;
54
+ setVolume: (volume: number) => void;
55
+ toggleMute: () => void;
56
+ setPlaybackRate: (rate: number) => void;
57
+ /** Select a caption track by index, or -1 to turn captions off. */
58
+ setTextTrack: (index: number) => void;
59
+ toggleCaptions: () => void;
60
+ /** Reload the current source after an error. */
61
+ retry: () => void;
62
+ setMetadata: (next: MediaInfo | null) => void;
63
+ toggleFullscreen: () => Promise<void>;
64
+ togglePictureInPicture: () => Promise<void>;
65
+ getState: () => MediaState;
66
+ getServerState: () => MediaState;
67
+ getMedia: () => HTMLMediaElement | null;
68
+ subscribe: (listener: Listener) => () => boolean;
69
+ attach: (el: HTMLMediaElement | null) => void;
70
+ setContainer: (el: HTMLElement | null) => void;
71
+ };
72
+ type MediaStore = ReturnType<typeof createMediaStore>;
73
+ declare function formatTime(seconds: number): string;
74
+ /** Human-readable message for a MediaError code. */
75
+ declare function describeMediaError(error: MediaError | null): "Playback was aborted." | "A network error stopped the download." | "This media could not be decoded." | "This format or source isn't supported." | "Something went wrong during playback.";
76
+
77
+ export { createMediaStore, describeMediaError, formatTime };
78
+ export type { MediaInfo, MediaState, MediaStore, MediaStoreOptions, MediaTextTrack };
package/dist/core.js ADDED
@@ -0,0 +1,369 @@
1
+ // ../../lib/media-store.ts
2
+ var MEDIA_EVENTS = [
3
+ "play",
4
+ "pause",
5
+ "playing",
6
+ "ended",
7
+ "waiting",
8
+ "canplay",
9
+ "seeking",
10
+ "seeked",
11
+ "timeupdate",
12
+ "durationchange",
13
+ "loadedmetadata",
14
+ "progress",
15
+ "volumechange",
16
+ "ratechange",
17
+ "emptied",
18
+ "error",
19
+ "enterpictureinpicture",
20
+ "leavepictureinpicture"
21
+ ];
22
+ var TRACK_EVENTS = ["addtrack", "removetrack", "change"];
23
+ var initialState = {
24
+ paused: true,
25
+ ended: false,
26
+ started: false,
27
+ waiting: false,
28
+ currentTime: 0,
29
+ duration: 0,
30
+ bufferedEnd: 0,
31
+ volume: 1,
32
+ muted: false,
33
+ playbackRate: 1,
34
+ fullscreen: false,
35
+ pictureInPicture: false,
36
+ error: null,
37
+ textTracks: [],
38
+ textTrack: -1,
39
+ cues: []
40
+ };
41
+ function readBufferedEnd(media) {
42
+ const { buffered, currentTime } = media;
43
+ for (let i = 0; i < buffered.length; i++) {
44
+ if (buffered.start(i) <= currentTime && buffered.end(i) >= currentTime) {
45
+ return buffered.end(i);
46
+ }
47
+ }
48
+ return buffered.length ? buffered.end(buffered.length - 1) : 0;
49
+ }
50
+ function captionTracks(media) {
51
+ if (!media?.textTracks) return [];
52
+ return Array.from(media.textTracks).filter(
53
+ (track) => track.kind === "captions" || track.kind === "subtitles"
54
+ );
55
+ }
56
+ function cueText(cue) {
57
+ return (cue.text ?? "").replace(/<[^>]+>/g, "").trim();
58
+ }
59
+ var sameList = (a, b) => a.length === b.length && a.every((value, i) => Object.is(value, b[i]));
60
+ var sameTracks = (a, b) => a.length === b.length && a.every((t, i) => t.label === b[i].label && t.language === b[i].language && t.kind === b[i].kind);
61
+ function readPreferences(key) {
62
+ try {
63
+ return JSON.parse(localStorage.getItem(key) ?? "{}");
64
+ } catch {
65
+ return {};
66
+ }
67
+ }
68
+ function createMediaStore({ persistKey } = {}) {
69
+ let state = initialState;
70
+ let media = null;
71
+ let container = null;
72
+ let cueTrack = null;
73
+ let metadata = null;
74
+ const listeners = /* @__PURE__ */ new Set();
75
+ const set = (patch) => {
76
+ let changed = false;
77
+ for (const key in patch) {
78
+ const k = key;
79
+ const next = patch[k];
80
+ const prev = state[k];
81
+ if (k === "cues" ? !sameList(prev, next) : k === "textTracks" ? !sameTracks(prev, next) : !Object.is(prev, next)) {
82
+ changed = true;
83
+ break;
84
+ }
85
+ }
86
+ if (!changed) return;
87
+ state = { ...state, ...patch };
88
+ listeners.forEach((l) => l());
89
+ };
90
+ const sync = () => {
91
+ if (!media) return;
92
+ set({
93
+ paused: media.paused,
94
+ ended: media.ended,
95
+ started: state.started || !media.paused || media.currentTime > 0,
96
+ currentTime: media.currentTime,
97
+ duration: Number.isFinite(media.duration) ? media.duration : 0,
98
+ bufferedEnd: readBufferedEnd(media),
99
+ volume: media.volume,
100
+ muted: media.muted,
101
+ playbackRate: media.playbackRate,
102
+ pictureInPicture: typeof document !== "undefined" && document.pictureInPictureElement === media,
103
+ error: media.error
104
+ });
105
+ };
106
+ const readCues = () => {
107
+ const active = cueTrack?.activeCues;
108
+ set({ cues: active ? Array.from(active).map(cueText).filter(Boolean) : [] });
109
+ };
110
+ const bindCueTrack = (track) => {
111
+ if (cueTrack === track) return;
112
+ cueTrack?.removeEventListener("cuechange", readCues);
113
+ cueTrack = track;
114
+ cueTrack?.addEventListener("cuechange", readCues);
115
+ };
116
+ const syncTracks = (requested = state.textTrack) => {
117
+ const tracks = captionTracks(media);
118
+ let active = requested;
119
+ tracks.forEach((track, i) => {
120
+ if (track.mode === "showing") active = i;
121
+ });
122
+ if (active >= tracks.length) active = -1;
123
+ tracks.forEach((track, i) => {
124
+ const mode = i === active ? "hidden" : "disabled";
125
+ if (track.mode !== mode) track.mode = mode;
126
+ });
127
+ bindCueTrack(tracks[active] ?? null);
128
+ set({
129
+ textTracks: tracks.map((track, i) => ({
130
+ label: track.label || track.language || `Track ${i + 1}`,
131
+ language: track.language,
132
+ kind: track.kind
133
+ })),
134
+ textTrack: active
135
+ });
136
+ readCues();
137
+ };
138
+ const onTracksChange = () => syncTracks();
139
+ const applyMediaSession = () => {
140
+ if (typeof navigator === "undefined" || !("mediaSession" in navigator) || !metadata) return;
141
+ const session = navigator.mediaSession;
142
+ session.metadata = new MediaMetadata(metadata);
143
+ const handlers = [
144
+ ["play", () => actions.play()],
145
+ ["pause", () => actions.pause()],
146
+ ["seekbackward", (d) => actions.seekBy(-(d.seekOffset ?? 10))],
147
+ ["seekforward", (d) => actions.seekBy(d.seekOffset ?? 10)],
148
+ ["seekto", (d) => d.seekTime !== void 0 && actions.seek(d.seekTime)]
149
+ ];
150
+ for (const [action, handler] of handlers) {
151
+ try {
152
+ session.setActionHandler(action, handler);
153
+ } catch {
154
+ }
155
+ }
156
+ };
157
+ const updatePositionState = () => {
158
+ if (!metadata || !media || typeof navigator === "undefined" || !("mediaSession" in navigator)) return;
159
+ if (!Number.isFinite(media.duration) || media.duration <= 0) return;
160
+ try {
161
+ navigator.mediaSession.setPositionState({
162
+ duration: media.duration,
163
+ playbackRate: media.playbackRate,
164
+ position: Math.min(media.currentTime, media.duration)
165
+ });
166
+ } catch {
167
+ }
168
+ };
169
+ const savePreferences = () => {
170
+ if (!persistKey || !media) return;
171
+ try {
172
+ const prefs = {
173
+ volume: media.volume,
174
+ muted: media.muted,
175
+ playbackRate: media.playbackRate
176
+ };
177
+ localStorage.setItem(persistKey, JSON.stringify(prefs));
178
+ } catch {
179
+ }
180
+ };
181
+ const onEvent = (event) => {
182
+ switch (event.type) {
183
+ case "emptied":
184
+ set({ started: false, waiting: false });
185
+ break;
186
+ case "waiting":
187
+ set({ waiting: true });
188
+ break;
189
+ case "playing":
190
+ case "canplay":
191
+ case "pause":
192
+ case "error":
193
+ set({ waiting: false });
194
+ break;
195
+ case "play":
196
+ applyMediaSession();
197
+ break;
198
+ case "volumechange":
199
+ case "ratechange":
200
+ savePreferences();
201
+ break;
202
+ case "loadedmetadata":
203
+ syncTracks();
204
+ break;
205
+ }
206
+ if (event.type === "durationchange" || event.type === "ratechange" || event.type === "seeked") {
207
+ updatePositionState();
208
+ }
209
+ sync();
210
+ };
211
+ const onFullscreenChange = () => {
212
+ const el = document.fullscreenElement;
213
+ const video = media;
214
+ set({
215
+ fullscreen: !!el && (el === container || el === media) || !!video?.webkitDisplayingFullscreen
216
+ });
217
+ };
218
+ const attach = (el) => {
219
+ if (media === el) return;
220
+ if (media) {
221
+ MEDIA_EVENTS.forEach((e) => media.removeEventListener(e, onEvent));
222
+ TRACK_EVENTS.forEach((e) => media.textTracks?.removeEventListener?.(e, onTracksChange));
223
+ media.removeEventListener("webkitbeginfullscreen", onFullscreenChange);
224
+ media.removeEventListener("webkitendfullscreen", onFullscreenChange);
225
+ bindCueTrack(null);
226
+ }
227
+ media = el;
228
+ if (!el) {
229
+ state = initialState;
230
+ listeners.forEach((l) => l());
231
+ return;
232
+ }
233
+ if (persistKey) {
234
+ const prefs = readPreferences(persistKey);
235
+ if (typeof prefs.volume === "number") el.volume = Math.min(Math.max(prefs.volume, 0), 1);
236
+ if (typeof prefs.muted === "boolean") el.muted = prefs.muted;
237
+ if (typeof prefs.playbackRate === "number") el.playbackRate = prefs.playbackRate;
238
+ }
239
+ MEDIA_EVENTS.forEach((e) => el.addEventListener(e, onEvent));
240
+ TRACK_EVENTS.forEach((e) => el.textTracks?.addEventListener?.(e, onTracksChange));
241
+ el.addEventListener("webkitbeginfullscreen", onFullscreenChange);
242
+ el.addEventListener("webkitendfullscreen", onFullscreenChange);
243
+ sync();
244
+ syncTracks();
245
+ };
246
+ const setContainer = (el) => {
247
+ if (typeof document === "undefined") return;
248
+ document.removeEventListener("fullscreenchange", onFullscreenChange);
249
+ container = el;
250
+ if (el) document.addEventListener("fullscreenchange", onFullscreenChange);
251
+ };
252
+ const play = async () => {
253
+ if (!media) return;
254
+ try {
255
+ await media.play();
256
+ } catch {
257
+ }
258
+ };
259
+ const pause = () => media?.pause();
260
+ const actions = {
261
+ play,
262
+ pause,
263
+ toggle: () => media?.paused || media?.ended ? play() : pause(),
264
+ seek: (time) => {
265
+ if (!media) return;
266
+ const max = Number.isFinite(media.duration) ? media.duration : time;
267
+ media.currentTime = Math.min(Math.max(time, 0), max);
268
+ sync();
269
+ },
270
+ seekBy: (delta) => actions.seek((media?.currentTime ?? 0) + delta),
271
+ /** Jump to a fraction (0–1) of the duration. */
272
+ seekToRatio: (ratio) => actions.seek(ratio * (media?.duration || 0)),
273
+ setVolume: (volume) => {
274
+ if (!media) return;
275
+ media.volume = Math.min(Math.max(volume, 0), 1);
276
+ media.muted = media.volume === 0;
277
+ },
278
+ toggleMute: () => {
279
+ if (!media) return;
280
+ if (media.muted && media.volume === 0) media.volume = 1;
281
+ media.muted = !media.muted;
282
+ },
283
+ setPlaybackRate: (rate) => {
284
+ if (media) media.playbackRate = rate;
285
+ },
286
+ /** Select a caption track by index, or -1 to turn captions off. */
287
+ setTextTrack: (index) => syncTracksExplicit(index),
288
+ toggleCaptions: () => {
289
+ if (!state.textTracks.length) return;
290
+ syncTracksExplicit(state.textTrack === -1 ? 0 : -1);
291
+ },
292
+ /** Reload the current source after an error. */
293
+ retry: () => {
294
+ if (!media) return;
295
+ media.load();
296
+ void play();
297
+ },
298
+ setMetadata: (next) => {
299
+ metadata = next;
300
+ if (media && !media.paused) applyMediaSession();
301
+ },
302
+ toggleFullscreen: async () => {
303
+ if (typeof document === "undefined") return;
304
+ if (document.fullscreenElement) {
305
+ await document.exitFullscreen().catch(() => {
306
+ });
307
+ return;
308
+ }
309
+ if (container?.requestFullscreen) {
310
+ await container.requestFullscreen().catch(() => {
311
+ });
312
+ return;
313
+ }
314
+ media?.webkitEnterFullscreen?.();
315
+ },
316
+ togglePictureInPicture: async () => {
317
+ if (!(media instanceof HTMLVideoElement)) return;
318
+ try {
319
+ if (document.pictureInPictureElement) await document.exitPictureInPicture();
320
+ else await media.requestPictureInPicture();
321
+ } catch {
322
+ }
323
+ }
324
+ };
325
+ const syncTracksExplicit = (index) => {
326
+ captionTracks(media).forEach((track, i) => {
327
+ if (i !== index && track.mode === "showing") track.mode = "disabled";
328
+ });
329
+ syncTracks(index);
330
+ };
331
+ return {
332
+ getState: () => state,
333
+ getServerState: () => initialState,
334
+ getMedia: () => media,
335
+ subscribe: (listener) => {
336
+ listeners.add(listener);
337
+ return () => listeners.delete(listener);
338
+ },
339
+ attach,
340
+ setContainer,
341
+ ...actions
342
+ };
343
+ }
344
+ function formatTime(seconds) {
345
+ if (!Number.isFinite(seconds) || seconds < 0) seconds = 0;
346
+ const s = Math.floor(seconds % 60);
347
+ const m = Math.floor(seconds / 60 % 60);
348
+ const h = Math.floor(seconds / 3600);
349
+ const ss = s.toString().padStart(2, "0");
350
+ return h > 0 ? `${h}:${m.toString().padStart(2, "0")}:${ss}` : `${m}:${ss}`;
351
+ }
352
+ function describeMediaError(error) {
353
+ switch (error?.code) {
354
+ case 1:
355
+ return "Playback was aborted.";
356
+ case 2:
357
+ return "A network error stopped the download.";
358
+ case 3:
359
+ return "This media could not be decoded.";
360
+ case 4:
361
+ return "This format or source isn't supported.";
362
+ default:
363
+ return "Something went wrong during playback.";
364
+ }
365
+ }
366
+
367
+ export { createMediaStore, describeMediaError, formatTime };
368
+ //# sourceMappingURL=core.js.map
369
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../lib/media-store.ts"],"names":[],"mappings":"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","file":"core.js","sourcesContent":["export type MediaTextTrack = {\r\n label: string\r\n language: string\r\n kind: \"captions\" | \"subtitles\"\r\n}\r\n\r\nexport type MediaInfo = {\r\n title?: string\r\n artist?: string\r\n album?: string\r\n artwork?: { src: string; sizes?: string; type?: string }[]\r\n}\r\n\r\nexport type MediaState = {\r\n paused: boolean\r\n ended: boolean\r\n started: boolean\r\n waiting: boolean\r\n currentTime: number\r\n duration: number\r\n bufferedEnd: number\r\n volume: number\r\n muted: boolean\r\n playbackRate: number\r\n fullscreen: boolean\r\n pictureInPicture: boolean\r\n error: MediaError | null\r\n /** Caption and subtitle tracks, in document order. */\r\n textTracks: MediaTextTrack[]\r\n /** Index into `textTracks`, or -1 when captions are off. */\r\n textTrack: number\r\n /** Text of the cues active right now, one entry per cue. */\r\n cues: string[]\r\n}\r\n\r\nexport type MediaStoreOptions = {\r\n /** localStorage key used to remember volume, mute and speed. Off by default. */\r\n persistKey?: string\r\n}\r\n\r\ntype Listener = () => void\r\n\r\nconst MEDIA_EVENTS = [\r\n \"play\",\r\n \"pause\",\r\n \"playing\",\r\n \"ended\",\r\n \"waiting\",\r\n \"canplay\",\r\n \"seeking\",\r\n \"seeked\",\r\n \"timeupdate\",\r\n \"durationchange\",\r\n \"loadedmetadata\",\r\n \"progress\",\r\n \"volumechange\",\r\n \"ratechange\",\r\n \"emptied\",\r\n \"error\",\r\n \"enterpictureinpicture\",\r\n \"leavepictureinpicture\",\r\n] as const\r\n\r\nconst TRACK_EVENTS = [\"addtrack\", \"removetrack\", \"change\"] as const\r\n\r\nconst initialState: MediaState = {\r\n paused: true,\r\n ended: false,\r\n started: false,\r\n waiting: false,\r\n currentTime: 0,\r\n duration: 0,\r\n bufferedEnd: 0,\r\n volume: 1,\r\n muted: false,\r\n playbackRate: 1,\r\n fullscreen: false,\r\n pictureInPicture: false,\r\n error: null,\r\n textTracks: [],\r\n textTrack: -1,\r\n cues: [],\r\n}\r\n\r\ntype WebkitVideo = HTMLVideoElement & {\r\n webkitEnterFullscreen?: () => void\r\n webkitDisplayingFullscreen?: boolean\r\n}\r\n\r\nfunction readBufferedEnd(media: HTMLMediaElement) {\r\n const { buffered, currentTime } = media\r\n for (let i = 0; i < buffered.length; i++) {\r\n if (buffered.start(i) <= currentTime && buffered.end(i) >= currentTime) {\r\n return buffered.end(i)\r\n }\r\n }\r\n return buffered.length ? buffered.end(buffered.length - 1) : 0\r\n}\r\n\r\nfunction captionTracks(media: HTMLMediaElement | null) {\r\n if (!media?.textTracks) return []\r\n return Array.from(media.textTracks).filter(\r\n (track) => track.kind === \"captions\" || track.kind === \"subtitles\"\r\n )\r\n}\r\n\r\nfunction cueText(cue: TextTrackCue) {\r\n return ((cue as VTTCue).text ?? \"\").replace(/<[^>]+>/g, \"\").trim()\r\n}\r\n\r\nconst sameList = (a: readonly unknown[], b: readonly unknown[]) =>\r\n a.length === b.length && a.every((value, i) => Object.is(value, b[i]))\r\n\r\nconst sameTracks = (a: MediaTextTrack[], b: MediaTextTrack[]) =>\r\n a.length === b.length &&\r\n a.every((t, i) => t.label === b[i].label && t.language === b[i].language && t.kind === b[i].kind)\r\n\r\ntype Preferences = Pick<MediaState, \"volume\" | \"muted\" | \"playbackRate\">\r\n\r\nfunction readPreferences(key: string): Partial<Preferences> {\r\n try {\r\n return JSON.parse(localStorage.getItem(key) ?? \"{}\")\r\n } catch {\r\n return {}\r\n }\r\n}\r\n\r\n/**\r\n * Framework-agnostic store over any HTMLMediaElement. React bindings,\r\n * web components, or anything else can subscribe to it.\r\n */\r\nexport function createMediaStore({ persistKey }: MediaStoreOptions = {}) {\r\n let state = initialState\r\n let media: HTMLMediaElement | null = null\r\n let container: HTMLElement | null = null\r\n let cueTrack: TextTrack | null = null\r\n let metadata: MediaInfo | null = null\r\n const listeners = new Set<Listener>()\r\n\r\n const set = (patch: Partial<MediaState>) => {\r\n let changed = false\r\n for (const key in patch) {\r\n const k = key as keyof MediaState\r\n const next = patch[k]\r\n const prev = state[k]\r\n if (k === \"cues\" ? !sameList(prev as string[], next as string[]) : k === \"textTracks\" ? !sameTracks(prev as MediaTextTrack[], next as MediaTextTrack[]) : !Object.is(prev, next)) {\r\n changed = true\r\n break\r\n }\r\n }\r\n if (!changed) return\r\n state = { ...state, ...patch }\r\n listeners.forEach((l) => l())\r\n }\r\n\r\n const sync = () => {\r\n if (!media) return\r\n set({\r\n paused: media.paused,\r\n ended: media.ended,\r\n started: state.started || !media.paused || media.currentTime > 0,\r\n currentTime: media.currentTime,\r\n duration: Number.isFinite(media.duration) ? media.duration : 0,\r\n bufferedEnd: readBufferedEnd(media),\r\n volume: media.volume,\r\n muted: media.muted,\r\n playbackRate: media.playbackRate,\r\n pictureInPicture:\r\n typeof document !== \"undefined\" && document.pictureInPictureElement === media,\r\n error: media.error,\r\n })\r\n }\r\n\r\n /* ------------------------------ Captions ------------------------------ */\r\n\r\n const readCues = () => {\r\n const active = cueTrack?.activeCues\r\n set({ cues: active ? Array.from(active).map(cueText).filter(Boolean) : [] })\r\n }\r\n\r\n const bindCueTrack = (track: TextTrack | null) => {\r\n if (cueTrack === track) return\r\n cueTrack?.removeEventListener(\"cuechange\", readCues)\r\n cueTrack = track\r\n cueTrack?.addEventListener(\"cuechange\", readCues)\r\n }\r\n\r\n // Native rendering is replaced by our own cue layer: the active track runs\r\n // in \"hidden\" mode (cues load, nothing paints) and every other track is off.\r\n const syncTracks = (requested = state.textTrack) => {\r\n const tracks = captionTracks(media)\r\n let active = requested\r\n tracks.forEach((track, i) => {\r\n if (track.mode === \"showing\") active = i\r\n })\r\n if (active >= tracks.length) active = -1\r\n tracks.forEach((track, i) => {\r\n const mode = i === active ? \"hidden\" : \"disabled\"\r\n if (track.mode !== mode) track.mode = mode\r\n })\r\n bindCueTrack(tracks[active] ?? null)\r\n set({\r\n textTracks: tracks.map((track, i) => ({\r\n label: track.label || track.language || `Track ${i + 1}`,\r\n language: track.language,\r\n kind: track.kind as MediaTextTrack[\"kind\"],\r\n })),\r\n textTrack: active,\r\n })\r\n readCues()\r\n }\r\n\r\n const onTracksChange = () => syncTracks()\r\n\r\n /* ---------------------------- Media Session --------------------------- */\r\n\r\n const applyMediaSession = () => {\r\n if (typeof navigator === \"undefined\" || !(\"mediaSession\" in navigator) || !metadata) return\r\n const session = navigator.mediaSession\r\n session.metadata = new MediaMetadata(metadata)\r\n const handlers: [MediaSessionAction, MediaSessionActionHandler][] = [\r\n [\"play\", () => actions.play()],\r\n [\"pause\", () => actions.pause()],\r\n [\"seekbackward\", (d) => actions.seekBy(-(d.seekOffset ?? 10))],\r\n [\"seekforward\", (d) => actions.seekBy(d.seekOffset ?? 10)],\r\n [\"seekto\", (d) => d.seekTime !== undefined && actions.seek(d.seekTime)],\r\n ]\r\n for (const [action, handler] of handlers) {\r\n try {\r\n session.setActionHandler(action, handler)\r\n } catch {\r\n // Action not supported by this browser.\r\n }\r\n }\r\n }\r\n\r\n const updatePositionState = () => {\r\n if (!metadata || !media || typeof navigator === \"undefined\" || !(\"mediaSession\" in navigator)) return\r\n if (!Number.isFinite(media.duration) || media.duration <= 0) return\r\n try {\r\n navigator.mediaSession.setPositionState({\r\n duration: media.duration,\r\n playbackRate: media.playbackRate,\r\n position: Math.min(media.currentTime, media.duration),\r\n })\r\n } catch {\r\n // Ignore transient invalid states.\r\n }\r\n }\r\n\r\n /* ------------------------------- Events ------------------------------- */\r\n\r\n const savePreferences = () => {\r\n if (!persistKey || !media) return\r\n try {\r\n const prefs: Preferences = {\r\n volume: media.volume,\r\n muted: media.muted,\r\n playbackRate: media.playbackRate,\r\n }\r\n localStorage.setItem(persistKey, JSON.stringify(prefs))\r\n } catch {\r\n // Storage may be full or disabled.\r\n }\r\n }\r\n\r\n const onEvent = (event: Event) => {\r\n switch (event.type) {\r\n case \"emptied\":\r\n set({ started: false, waiting: false })\r\n break\r\n case \"waiting\":\r\n set({ waiting: true })\r\n break\r\n case \"playing\":\r\n case \"canplay\":\r\n case \"pause\":\r\n case \"error\":\r\n set({ waiting: false })\r\n break\r\n case \"play\":\r\n applyMediaSession()\r\n break\r\n case \"volumechange\":\r\n case \"ratechange\":\r\n savePreferences()\r\n break\r\n case \"loadedmetadata\":\r\n syncTracks()\r\n break\r\n }\r\n if (event.type === \"durationchange\" || event.type === \"ratechange\" || event.type === \"seeked\") {\r\n updatePositionState()\r\n }\r\n sync()\r\n }\r\n\r\n const onFullscreenChange = () => {\r\n const el = document.fullscreenElement\r\n const video = media as WebkitVideo | null\r\n set({\r\n fullscreen:\r\n (!!el && (el === container || el === media)) || !!video?.webkitDisplayingFullscreen,\r\n })\r\n }\r\n\r\n const attach = (el: HTMLMediaElement | null) => {\r\n if (media === el) return\r\n if (media) {\r\n MEDIA_EVENTS.forEach((e) => media!.removeEventListener(e, onEvent))\r\n TRACK_EVENTS.forEach((e) => media!.textTracks?.removeEventListener?.(e, onTracksChange))\r\n media.removeEventListener(\"webkitbeginfullscreen\", onFullscreenChange)\r\n media.removeEventListener(\"webkitendfullscreen\", onFullscreenChange)\r\n bindCueTrack(null)\r\n }\r\n media = el\r\n if (!el) {\r\n state = initialState\r\n listeners.forEach((l) => l())\r\n return\r\n }\r\n if (persistKey) {\r\n const prefs = readPreferences(persistKey)\r\n if (typeof prefs.volume === \"number\") el.volume = Math.min(Math.max(prefs.volume, 0), 1)\r\n if (typeof prefs.muted === \"boolean\") el.muted = prefs.muted\r\n if (typeof prefs.playbackRate === \"number\") el.playbackRate = prefs.playbackRate\r\n }\r\n MEDIA_EVENTS.forEach((e) => el.addEventListener(e, onEvent))\r\n TRACK_EVENTS.forEach((e) => el.textTracks?.addEventListener?.(e, onTracksChange))\r\n el.addEventListener(\"webkitbeginfullscreen\", onFullscreenChange)\r\n el.addEventListener(\"webkitendfullscreen\", onFullscreenChange)\r\n sync()\r\n syncTracks()\r\n }\r\n\r\n const setContainer = (el: HTMLElement | null) => {\r\n if (typeof document === \"undefined\") return\r\n document.removeEventListener(\"fullscreenchange\", onFullscreenChange)\r\n container = el\r\n if (el) document.addEventListener(\"fullscreenchange\", onFullscreenChange)\r\n }\r\n\r\n const play = async () => {\r\n if (!media) return\r\n try {\r\n await media.play()\r\n } catch {\r\n // Autoplay policies may reject; the paused state stays accurate.\r\n }\r\n }\r\n\r\n const pause = () => media?.pause()\r\n\r\n const actions = {\r\n play,\r\n pause,\r\n toggle: () => (media?.paused || media?.ended ? play() : pause()),\r\n seek: (time: number) => {\r\n if (!media) return\r\n const max = Number.isFinite(media.duration) ? media.duration : time\r\n media.currentTime = Math.min(Math.max(time, 0), max)\r\n sync()\r\n },\r\n seekBy: (delta: number) => actions.seek((media?.currentTime ?? 0) + delta),\r\n /** Jump to a fraction (0–1) of the duration. */\r\n seekToRatio: (ratio: number) => actions.seek(ratio * (media?.duration || 0)),\r\n setVolume: (volume: number) => {\r\n if (!media) return\r\n media.volume = Math.min(Math.max(volume, 0), 1)\r\n media.muted = media.volume === 0\r\n },\r\n toggleMute: () => {\r\n if (!media) return\r\n if (media.muted && media.volume === 0) media.volume = 1\r\n media.muted = !media.muted\r\n },\r\n setPlaybackRate: (rate: number) => {\r\n if (media) media.playbackRate = rate\r\n },\r\n /** Select a caption track by index, or -1 to turn captions off. */\r\n setTextTrack: (index: number) => syncTracksExplicit(index),\r\n toggleCaptions: () => {\r\n if (!state.textTracks.length) return\r\n syncTracksExplicit(state.textTrack === -1 ? 0 : -1)\r\n },\r\n /** Reload the current source after an error. */\r\n retry: () => {\r\n if (!media) return\r\n media.load()\r\n void play()\r\n },\r\n setMetadata: (next: MediaInfo | null) => {\r\n metadata = next\r\n if (media && !media.paused) applyMediaSession()\r\n },\r\n toggleFullscreen: async () => {\r\n if (typeof document === \"undefined\") return\r\n if (document.fullscreenElement) {\r\n await document.exitFullscreen().catch(() => {})\r\n return\r\n }\r\n if (container?.requestFullscreen) {\r\n await container.requestFullscreen().catch(() => {})\r\n return\r\n }\r\n // iOS Safari only supports fullscreen on the video element itself.\r\n ;(media as WebkitVideo | null)?.webkitEnterFullscreen?.()\r\n },\r\n togglePictureInPicture: async () => {\r\n if (!(media instanceof HTMLVideoElement)) return\r\n try {\r\n if (document.pictureInPictureElement) await document.exitPictureInPicture()\r\n else await media.requestPictureInPicture()\r\n } catch {\r\n // Unsupported or blocked by the browser.\r\n }\r\n },\r\n }\r\n\r\n // An explicit choice must win over a track the browser marked \"showing\".\r\n const syncTracksExplicit = (index: number) => {\r\n captionTracks(media).forEach((track, i) => {\r\n if (i !== index && track.mode === \"showing\") track.mode = \"disabled\"\r\n })\r\n syncTracks(index)\r\n }\r\n\r\n return {\r\n getState: () => state,\r\n getServerState: () => initialState,\r\n getMedia: () => media,\r\n subscribe: (listener: Listener) => {\r\n listeners.add(listener)\r\n return () => listeners.delete(listener)\r\n },\r\n attach,\r\n setContainer,\r\n ...actions,\r\n }\r\n}\r\n\r\nexport type MediaStore = ReturnType<typeof createMediaStore>\r\n\r\nexport function formatTime(seconds: number) {\r\n if (!Number.isFinite(seconds) || seconds < 0) seconds = 0\r\n const s = Math.floor(seconds % 60)\r\n const m = Math.floor((seconds / 60) % 60)\r\n const h = Math.floor(seconds / 3600)\r\n const ss = s.toString().padStart(2, \"0\")\r\n return h > 0 ? `${h}:${m.toString().padStart(2, \"0\")}:${ss}` : `${m}:${ss}`\r\n}\r\n\r\n/** Human-readable message for a MediaError code. */\r\nexport function describeMediaError(error: MediaError | null) {\r\n switch (error?.code) {\r\n case 1:\r\n return \"Playback was aborted.\"\r\n case 2:\r\n return \"A network error stopped the download.\"\r\n case 3:\r\n return \"This media could not be decoded.\"\r\n case 4:\r\n return \"This format or source isn't supported.\"\r\n default:\r\n return \"Something went wrong during playback.\"\r\n }\r\n}\r\n"]}
package/dist/hls.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Attaches a source the element can't play natively (HLS, DASH, MSE).
3
+ * Return a cleanup function to tear the adapter down when `src` changes.
4
+ */
5
+ type MediaSourceLoader = (media: HTMLMediaElement, src: string) => void | (() => void);
6
+
7
+ /**
8
+ * Plays `.m3u8` streams. Safari plays HLS natively; everywhere else hls.js
9
+ * is loaded on demand so it never lands in the main bundle.
10
+ */
11
+ declare const hlsLoader: MediaSourceLoader;
12
+
13
+ export { hlsLoader };