bobinejs 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,108 +1,114 @@
1
- # bobine
2
-
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
1
+ # bobine
2
+
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 theme tokens. In a new project, `npx shadcn@latest init` sets them up. Without it, define `background`, `foreground`, `card`, `muted`, `popover`, `primary` and `border` (with their `-foreground` pairs) yourself, or the audio player renders unstyled.
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
+ `bobinejs/hls` needs hls.js as a dependency. It loads only when an `.m3u8` source appears.
37
+
38
+ ```bash
39
+ pnpm add hls.js
40
+ ```
41
+
42
+ ```tsx
43
+ "use client"
44
+ import { VideoPlayer } from "bobinejs"
45
+ import { hlsLoader } from "bobinejs/hls" // loads hls.js on demand
46
+
47
+ <VideoPlayer src="https://example.com/stream.m3u8" loader={hlsLoader} />
48
+ ```
49
+
50
+ ### Compose your own
51
+
52
+ ```tsx
53
+ import { MediaRoot, MediaVideo, MediaSurface, MediaControls, MediaSeek, MediaPlayButton, MediaTime } from "bobinejs"
54
+
55
+ <MediaRoot className="aspect-video bg-media-scrim text-media-foreground">
56
+ <MediaVideo src="/clip.mp4" className="size-full" />
57
+ <MediaSurface />
58
+ <MediaControls className="absolute inset-x-3 bottom-3 flex-col">
59
+ <MediaSeek />
60
+ <div className="flex items-center gap-1">
61
+ <MediaPlayButton />
62
+ <MediaTime /> / <MediaTime type="duration" />
63
+ </div>
64
+ </MediaControls>
65
+ </MediaRoot>
66
+ ```
67
+
68
+ ### Types
69
+
70
+ Every component exports its props type under its own name plus `Props`, so a
71
+ wrapper can extend it.
72
+
73
+ ```tsx
74
+ import { AudioPlayer, type AudioPlayerProps } from "bobinejs"
75
+
76
+ function EpisodePlayer(props: AudioPlayerProps) {
77
+ return <AudioPlayer showSpeed persistKey="episodes" {...props} />
78
+ }
79
+ ```
80
+
81
+ `MediaState`, `MediaStore` and `MediaInfo` type the store and its metadata.
82
+
83
+ ### Framework-agnostic core
84
+
85
+ `bobinejs/core` exports `createMediaStore()`, a dependency-free store you can `attach` to any `HTMLMediaElement`.
86
+
87
+ ```ts
88
+ import { createMediaStore } from "bobinejs/core"
89
+
90
+ const store = createMediaStore()
91
+ store.attach(document.querySelector("video")!)
92
+ store.subscribe(() => console.log(store.getState().currentTime))
93
+ ```
94
+
95
+ ## Keyboard
96
+
97
+ | Key | Action |
98
+ | --- | --- |
99
+ | Space / K | Play or pause |
100
+ | J / L | Seek 10s |
101
+ | ← / → | Seek 5s |
102
+ | ↑ / ↓ | Volume |
103
+ | 0–9 | Jump to 0–90% |
104
+ | Home / End | Start / end |
105
+ | M | Mute |
106
+ | C | Captions |
107
+ | I | Picture-in-picture |
108
+ | F | Fullscreen |
109
+
110
+ Shortcuts only apply to the focused player.
111
+
112
+ ## License
113
+
114
+ MIT
package/dist/core.d.ts CHANGED
@@ -27,6 +27,8 @@ type MediaState = {
27
27
  fullscreen: boolean;
28
28
  pictureInPicture: boolean;
29
29
  error: MediaError | null;
30
+ /** False while the element has no source to play. */
31
+ hasSource: boolean;
30
32
  /** Caption and subtitle tracks, in document order. */
31
33
  textTracks: MediaTextTrack[];
32
34
  /** Index into `textTracks`, or -1 when captions are off. */
@@ -70,9 +72,14 @@ declare function createMediaStore({ persistKey }?: MediaStoreOptions): {
70
72
  setContainer: (el: HTMLElement | null) => void;
71
73
  };
72
74
  type MediaStore = ReturnType<typeof createMediaStore>;
73
- declare function formatTime(seconds: number): string;
75
+ /**
76
+ * `m:ss`, or `h:mm:ss` from one hour. Pass the duration as `reference` so
77
+ * every time in a player uses the same fields: `0:00:05` beside `10:00:01`,
78
+ * and the label does not widen when playback passes an hour.
79
+ */
80
+ declare function formatTime(seconds: number, reference?: number): string;
74
81
  /** 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.";
82
+ declare function describeMediaError(error: MediaError | null): "Playback was aborted." | "A network error stopped the download." | "This media could not be decoded." | "This media couldn't be loaded." | "Something went wrong during playback.";
76
83
 
77
84
  export { createMediaStore, describeMediaError, formatTime };
78
85
  export type { MediaInfo, MediaState, MediaStore, MediaStoreOptions, MediaTextTrack };
package/dist/core.js CHANGED
@@ -15,6 +15,7 @@ var MEDIA_EVENTS = [
15
15
  "volumechange",
16
16
  "ratechange",
17
17
  "emptied",
18
+ "loadstart",
18
19
  "error",
19
20
  "enterpictureinpicture",
20
21
  "leavepictureinpicture"
@@ -34,6 +35,7 @@ var initialState = {
34
35
  fullscreen: false,
35
36
  pictureInPicture: false,
36
37
  error: null,
38
+ hasSource: false,
37
39
  textTracks: [],
38
40
  textTrack: -1,
39
41
  cues: []
@@ -54,7 +56,13 @@ function captionTracks(media) {
54
56
  );
55
57
  }
56
58
  function cueText(cue) {
57
- return (cue.text ?? "").replace(/<[^>]+>/g, "").trim();
59
+ const vtt = cue;
60
+ const text = typeof vtt.getCueAsHTML === "function" ? vtt.getCueAsHTML().textContent : vtt.text?.replace(/<[^>]+>/g, "");
61
+ return (text ?? "").trim();
62
+ }
63
+ function defaultTrack(media, tracks) {
64
+ const el = Array.from(media?.querySelectorAll("track") ?? []).find((track) => track.default);
65
+ return el ? tracks.indexOf(el.track) : -1;
58
66
  }
59
67
  var sameList = (a, b) => a.length === b.length && a.every((value, i) => Object.is(value, b[i]));
60
68
  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);
@@ -71,6 +79,7 @@ function createMediaStore({ persistKey } = {}) {
71
79
  let container = null;
72
80
  let cueTrack = null;
73
81
  let metadata = null;
82
+ let trackChosen = false;
74
83
  const listeners = /* @__PURE__ */ new Set();
75
84
  const set = (patch) => {
76
85
  let changed = false;
@@ -100,7 +109,8 @@ function createMediaStore({ persistKey } = {}) {
100
109
  muted: media.muted,
101
110
  playbackRate: media.playbackRate,
102
111
  pictureInPicture: typeof document !== "undefined" && document.pictureInPictureElement === media,
103
- error: media.error
112
+ error: media.error,
113
+ hasSource: Boolean(media.src || media.currentSrc || media.querySelector("source[src]"))
104
114
  });
105
115
  };
106
116
  const readCues = () => {
@@ -115,7 +125,7 @@ function createMediaStore({ persistKey } = {}) {
115
125
  };
116
126
  const syncTracks = (requested = state.textTrack) => {
117
127
  const tracks = captionTracks(media);
118
- let active = requested;
128
+ let active = requested === -1 && !trackChosen ? defaultTrack(media, tracks) : requested;
119
129
  tracks.forEach((track, i) => {
120
130
  if (track.mode === "showing") active = i;
121
131
  });
@@ -323,6 +333,7 @@ function createMediaStore({ persistKey } = {}) {
323
333
  }
324
334
  };
325
335
  const syncTracksExplicit = (index) => {
336
+ trackChosen = true;
326
337
  captionTracks(media).forEach((track, i) => {
327
338
  if (i !== index && track.mode === "showing") track.mode = "disabled";
328
339
  });
@@ -341,13 +352,14 @@ function createMediaStore({ persistKey } = {}) {
341
352
  ...actions
342
353
  };
343
354
  }
344
- function formatTime(seconds) {
355
+ function formatTime(seconds, reference = seconds) {
345
356
  if (!Number.isFinite(seconds) || seconds < 0) seconds = 0;
346
357
  const s = Math.floor(seconds % 60);
347
358
  const m = Math.floor(seconds / 60 % 60);
348
359
  const h = Math.floor(seconds / 3600);
349
360
  const ss = s.toString().padStart(2, "0");
350
- return h > 0 ? `${h}:${m.toString().padStart(2, "0")}:${ss}` : `${m}:${ss}`;
361
+ const hours = h > 0 || Number.isFinite(reference) && reference >= 3600;
362
+ return hours ? `${h}:${m.toString().padStart(2, "0")}:${ss}` : `${m}:${ss}`;
351
363
  }
352
364
  function describeMediaError(error) {
353
365
  switch (error?.code) {
@@ -358,7 +370,7 @@ function describeMediaError(error) {
358
370
  case 3:
359
371
  return "This media could not be decoded.";
360
372
  case 4:
361
- return "This format or source isn't supported.";
373
+ return "This media couldn't be loaded.";
362
374
  default:
363
375
  return "Something went wrong during playback.";
364
376
  }
package/dist/core.js.map CHANGED
@@ -1 +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"]}
1
+ {"version":3,"sources":["../../../lib/media-store.ts"],"names":[],"mappings":";AA4CA,IAAM,YAAA,GAAe;AAAA,EACnB,MAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA,YAAA;AAAA,EACA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,UAAA;AAAA,EACA,cAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA;AAAA,EACA,WAAA;AAAA,EACA,OAAA;AAAA,EACA,uBAAA;AAAA,EACA;AACF,CAAA;AAEA,IAAM,YAAA,GAAe,CAAC,UAAA,EAAY,aAAA,EAAe,QAAQ,CAAA;AAEzD,IAAM,YAAA,GAA2B;AAAA,EAC/B,MAAA,EAAQ,IAAA;AAAA,EACR,KAAA,EAAO,KAAA;AAAA,EACP,OAAA,EAAS,KAAA;AAAA,EACT,OAAA,EAAS,KAAA;AAAA,EACT,WAAA,EAAa,CAAA;AAAA,EACb,QAAA,EAAU,CAAA;AAAA,EACV,WAAA,EAAa,CAAA;AAAA,EACb,MAAA,EAAQ,CAAA;AAAA,EACR,KAAA,EAAO,KAAA;AAAA,EACP,YAAA,EAAc,CAAA;AAAA,EACd,UAAA,EAAY,KAAA;AAAA,EACZ,gBAAA,EAAkB,KAAA;AAAA,EAClB,KAAA,EAAO,IAAA;AAAA,EACP,SAAA,EAAW,KAAA;AAAA,EACX,YAAY,EAAC;AAAA,EACb,SAAA,EAAW,EAAA;AAAA,EACX,MAAM;AACR,CAAA;AAOA,SAAS,gBAAgB,KAAA,EAAyB;AAChD,EAAA,MAAM,EAAE,QAAA,EAAU,WAAA,EAAY,GAAI,KAAA;AAClC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,IAAA,IAAI,QAAA,CAAS,MAAM,CAAC,CAAA,IAAK,eAAe,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,IAAK,WAAA,EAAa;AACtE,MAAA,OAAO,QAAA,CAAS,IAAI,CAAC,CAAA;AAAA,IACvB;AAAA,EACF;AACA,EAAA,OAAO,SAAS,MAAA,GAAS,QAAA,CAAS,IAAI,QAAA,CAAS,MAAA,GAAS,CAAC,CAAA,GAAI,CAAA;AAC/D;AAEA,SAAS,cAAc,KAAA,EAAgC;AACrD,EAAA,IAAI,CAAC,KAAA,EAAO,UAAA,EAAY,OAAO,EAAC;AAChC,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,UAAU,CAAA,CAAE,MAAA;AAAA,IAClC,CAAC,KAAA,KAAU,KAAA,CAAM,IAAA,KAAS,UAAA,IAAc,MAAM,IAAA,KAAS;AAAA,GACzD;AACF;AAMA,SAAS,QAAQ,GAAA,EAAmB;AAClC,EAAA,MAAM,GAAA,GAAM,GAAA;AACZ,EAAA,MAAM,IAAA,GAAO,OAAO,GAAA,CAAI,YAAA,KAAiB,UAAA,GAAa,GAAA,CAAI,YAAA,EAAa,CAAE,WAAA,GAAc,GAAA,CAAI,IAAA,EAAM,OAAA,CAAQ,YAAY,EAAE,CAAA;AACvH,EAAA,OAAA,CAAQ,IAAA,IAAQ,IAAI,IAAA,EAAK;AAC3B;AAGA,SAAS,YAAA,CAAa,OAAgC,MAAA,EAAqB;AACzE,EAAA,MAAM,EAAA,GAAK,KAAA,CAAM,IAAA,CAAK,KAAA,EAAO,iBAAiB,OAAO,CAAA,IAAK,EAAE,CAAA,CAAE,IAAA,CAAK,CAAC,KAAA,KAAU,MAAM,OAAO,CAAA;AAC3F,EAAA,OAAO,EAAA,GAAK,MAAA,CAAO,OAAA,CAAQ,EAAA,CAAG,KAAK,CAAA,GAAI,EAAA;AACzC;AAEA,IAAM,WAAW,CAAC,CAAA,EAAuB,MACvC,CAAA,CAAE,MAAA,KAAW,EAAE,MAAA,IAAU,CAAA,CAAE,MAAM,CAAC,KAAA,EAAO,MAAM,MAAA,CAAO,EAAA,CAAG,OAAO,CAAA,CAAE,CAAC,CAAC,CAAC,CAAA;AAEvE,IAAM,UAAA,GAAa,CAAC,CAAA,EAAqB,CAAA,KACvC,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,MAAA,IACf,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA,EAAG,MAAM,CAAA,CAAE,KAAA,KAAU,CAAA,CAAE,CAAC,CAAA,CAAE,KAAA,IAAS,CAAA,CAAE,QAAA,KAAa,CAAA,CAAE,CAAC,CAAA,CAAE,QAAA,IAAY,CAAA,CAAE,IAAA,KAAS,CAAA,CAAE,CAAC,EAAE,IAAI,CAAA;AAIlG,SAAS,gBAAgB,GAAA,EAAmC;AAC1D,EAAA,IAAI;AACF,IAAA,OAAO,KAAK,KAAA,CAAM,YAAA,CAAa,OAAA,CAAQ,GAAG,KAAK,IAAI,CAAA;AAAA,EACrD,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAC;AAAA,EACV;AACF;AAMO,SAAS,gBAAA,CAAiB,EAAE,UAAA,EAAW,GAAuB,EAAC,EAAG;AACvE,EAAA,IAAI,KAAA,GAAQ,YAAA;AACZ,EAAA,IAAI,KAAA,GAAiC,IAAA;AACrC,EAAA,IAAI,SAAA,GAAgC,IAAA;AACpC,EAAA,IAAI,QAAA,GAA6B,IAAA;AACjC,EAAA,IAAI,QAAA,GAA6B,IAAA;AAGjC,EAAA,IAAI,WAAA,GAAc,KAAA;AAClB,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAAc;AAEpC,EAAA,MAAM,GAAA,GAAM,CAAC,KAAA,KAA+B;AAC1C,IAAA,IAAI,OAAA,GAAU,KAAA;AACd,IAAA,KAAA,MAAW,OAAO,KAAA,EAAO;AACvB,MAAA,MAAM,CAAA,GAAI,GAAA;AACV,MAAA,MAAM,IAAA,GAAO,MAAM,CAAC,CAAA;AACpB,MAAA,MAAM,IAAA,GAAO,MAAM,CAAC,CAAA;AACpB,MAAA,IAAI,MAAM,MAAA,GAAS,CAAC,SAAS,IAAA,EAAkB,IAAgB,IAAI,CAAA,KAAM,YAAA,GAAe,CAAC,UAAA,CAAW,IAAA,EAA0B,IAAwB,CAAA,GAAI,CAAC,OAAO,EAAA,CAAG,IAAA,EAAM,IAAI,CAAA,EAAG;AAChL,QAAA,OAAA,GAAU,IAAA;AACV,QAAA;AAAA,MACF;AAAA,IACF;AACA,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,KAAA,GAAQ,EAAE,GAAG,KAAA,EAAO,GAAG,KAAA,EAAM;AAC7B,IAAA,SAAA,CAAU,OAAA,CAAQ,CAAC,CAAA,KAAM,CAAA,EAAG,CAAA;AAAA,EAC9B,CAAA;AAEA,EAAA,MAAM,OAAO,MAAM;AACjB,IAAA,IAAI,CAAC,KAAA,EAAO;AACZ,IAAA,GAAA,CAAI;AAAA,MACF,QAAQ,KAAA,CAAM,MAAA;AAAA,MACd,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,SAAS,KAAA,CAAM,OAAA,IAAW,CAAC,KAAA,CAAM,MAAA,IAAU,MAAM,WAAA,GAAc,CAAA;AAAA,MAC/D,aAAa,KAAA,CAAM,WAAA;AAAA,MACnB,UAAU,MAAA,CAAO,QAAA,CAAS,MAAM,QAAQ,CAAA,GAAI,MAAM,QAAA,GAAW,CAAA;AAAA,MAC7D,WAAA,EAAa,gBAAgB,KAAK,CAAA;AAAA,MAClC,QAAQ,KAAA,CAAM,MAAA;AAAA,MACd,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,cAAc,KAAA,CAAM,YAAA;AAAA,MACpB,gBAAA,EACE,OAAO,QAAA,KAAa,WAAA,IAAe,SAAS,uBAAA,KAA4B,KAAA;AAAA,MAC1E,OAAO,KAAA,CAAM,KAAA;AAAA,MACb,SAAA,EAAW,QAAQ,KAAA,CAAM,GAAA,IAAO,MAAM,UAAA,IAAc,KAAA,CAAM,aAAA,CAAc,aAAa,CAAC;AAAA,KACvF,CAAA;AAAA,EACH,CAAA;AAIA,EAAA,MAAM,WAAW,MAAM;AACrB,IAAA,MAAM,SAAS,QAAA,EAAU,UAAA;AACzB,IAAA,GAAA,CAAI,EAAE,IAAA,EAAM,MAAA,GAAS,KAAA,CAAM,KAAK,MAAM,CAAA,CAAE,GAAA,CAAI,OAAO,EAAE,MAAA,CAAO,OAAO,CAAA,GAAI,IAAI,CAAA;AAAA,EAC7E,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,CAAC,KAAA,KAA4B;AAChD,IAAA,IAAI,aAAa,KAAA,EAAO;AACxB,IAAA,QAAA,EAAU,mBAAA,CAAoB,aAAa,QAAQ,CAAA;AACnD,IAAA,QAAA,GAAW,KAAA;AACX,IAAA,QAAA,EAAU,gBAAA,CAAiB,aAAa,QAAQ,CAAA;AAAA,EAClD,CAAA;AAIA,EAAA,MAAM,UAAA,GAAa,CAAC,SAAA,GAAY,KAAA,CAAM,SAAA,KAAc;AAClD,IAAA,MAAM,MAAA,GAAS,cAAc,KAAK,CAAA;AAKlC,IAAA,IAAI,MAAA,GAAS,cAAc,EAAA,IAAM,CAAC,cAAc,YAAA,CAAa,KAAA,EAAO,MAAM,CAAA,GAAI,SAAA;AAC9E,IAAA,MAAA,CAAO,OAAA,CAAQ,CAAC,KAAA,EAAO,CAAA,KAAM;AAC3B,MAAA,IAAI,KAAA,CAAM,IAAA,KAAS,SAAA,EAAW,MAAA,GAAS,CAAA;AAAA,IACzC,CAAC,CAAA;AACD,IAAA,IAAI,MAAA,IAAU,MAAA,CAAO,MAAA,EAAQ,MAAA,GAAS,EAAA;AACtC,IAAA,MAAA,CAAO,OAAA,CAAQ,CAAC,KAAA,EAAO,CAAA,KAAM;AAC3B,MAAA,MAAM,IAAA,GAAO,CAAA,KAAM,MAAA,GAAS,QAAA,GAAW,UAAA;AACvC,MAAA,IAAI,KAAA,CAAM,IAAA,KAAS,IAAA,EAAM,KAAA,CAAM,IAAA,GAAO,IAAA;AAAA,IACxC,CAAC,CAAA;AACD,IAAA,YAAA,CAAa,MAAA,CAAO,MAAM,CAAA,IAAK,IAAI,CAAA;AACnC,IAAA,GAAA,CAAI;AAAA,MACF,UAAA,EAAY,MAAA,CAAO,GAAA,CAAI,CAAC,OAAO,CAAA,MAAO;AAAA,QACpC,OAAO,KAAA,CAAM,KAAA,IAAS,MAAM,QAAA,IAAY,CAAA,MAAA,EAAS,IAAI,CAAC,CAAA,CAAA;AAAA,QACtD,UAAU,KAAA,CAAM,QAAA;AAAA,QAChB,MAAM,KAAA,CAAM;AAAA,OACd,CAAE,CAAA;AAAA,MACF,SAAA,EAAW;AAAA,KACZ,CAAA;AACD,IAAA,QAAA,EAAS;AAAA,EACX,CAAA;AAEA,EAAA,MAAM,cAAA,GAAiB,MAAM,UAAA,EAAW;AAIxC,EAAA,MAAM,oBAAoB,MAAM;AAC9B,IAAA,IAAI,OAAO,SAAA,KAAc,WAAA,IAAe,EAAE,cAAA,IAAkB,SAAA,CAAA,IAAc,CAAC,QAAA,EAAU;AACrF,IAAA,MAAM,UAAU,SAAA,CAAU,YAAA;AAC1B,IAAA,OAAA,CAAQ,QAAA,GAAW,IAAI,aAAA,CAAc,QAAQ,CAAA;AAC7C,IAAA,MAAM,QAAA,GAA8D;AAAA,MAClE,CAAC,MAAA,EAAQ,MAAM,OAAA,CAAQ,MAAM,CAAA;AAAA,MAC7B,CAAC,OAAA,EAAS,MAAM,OAAA,CAAQ,OAAO,CAAA;AAAA,MAC/B,CAAC,cAAA,EAAgB,CAAC,CAAA,KAAM,OAAA,CAAQ,OAAO,EAAE,CAAA,CAAE,UAAA,IAAc,EAAA,CAAG,CAAC,CAAA;AAAA,MAC7D,CAAC,eAAe,CAAC,CAAA,KAAM,QAAQ,MAAA,CAAO,CAAA,CAAE,UAAA,IAAc,EAAE,CAAC,CAAA;AAAA,MACzD,CAAC,QAAA,EAAU,CAAC,CAAA,KAAM,CAAA,CAAE,QAAA,KAAa,MAAA,IAAa,OAAA,CAAQ,IAAA,CAAK,CAAA,CAAE,QAAQ,CAAC;AAAA,KACxE;AACA,IAAA,KAAA,MAAW,CAAC,MAAA,EAAQ,OAAO,CAAA,IAAK,QAAA,EAAU;AACxC,MAAA,IAAI;AACF,QAAA,OAAA,CAAQ,gBAAA,CAAiB,QAAQ,OAAO,CAAA;AAAA,MAC1C,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,sBAAsB,MAAM;AAChC,IAAA,IAAI,CAAC,YAAY,CAAC,KAAA,IAAS,OAAO,SAAA,KAAc,WAAA,IAAe,EAAE,cAAA,IAAkB,SAAA,CAAA,EAAY;AAC/F,IAAA,IAAI,CAAC,OAAO,QAAA,CAAS,KAAA,CAAM,QAAQ,CAAA,IAAK,KAAA,CAAM,YAAY,CAAA,EAAG;AAC7D,IAAA,IAAI;AACF,MAAA,SAAA,CAAU,aAAa,gBAAA,CAAiB;AAAA,QACtC,UAAU,KAAA,CAAM,QAAA;AAAA,QAChB,cAAc,KAAA,CAAM,YAAA;AAAA,QACpB,UAAU,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,WAAA,EAAa,MAAM,QAAQ;AAAA,OACrD,CAAA;AAAA,IACH,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF,CAAA;AAIA,EAAA,MAAM,kBAAkB,MAAM;AAC5B,IAAA,IAAI,CAAC,UAAA,IAAc,CAAC,KAAA,EAAO;AAC3B,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,GAAqB;AAAA,QACzB,QAAQ,KAAA,CAAM,MAAA;AAAA,QACd,OAAO,KAAA,CAAM,KAAA;AAAA,QACb,cAAc,KAAA,CAAM;AAAA,OACtB;AACA,MAAA,YAAA,CAAa,OAAA,CAAQ,UAAA,EAAY,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,IACxD,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,OAAA,GAAU,CAAC,KAAA,KAAiB;AAChC,IAAA,QAAQ,MAAM,IAAA;AAAM,MAClB,KAAK,SAAA;AACH,QAAA,GAAA,CAAI,EAAE,OAAA,EAAS,KAAA,EAAO,OAAA,EAAS,OAAO,CAAA;AACtC,QAAA;AAAA,MACF,KAAK,SAAA;AACH,QAAA,GAAA,CAAI,EAAE,OAAA,EAAS,IAAA,EAAM,CAAA;AACrB,QAAA;AAAA,MACF,KAAK,SAAA;AAAA,MACL,KAAK,SAAA;AAAA,MACL,KAAK,OAAA;AAAA,MACL,KAAK,OAAA;AACH,QAAA,GAAA,CAAI,EAAE,OAAA,EAAS,KAAA,EAAO,CAAA;AACtB,QAAA;AAAA,MACF,KAAK,MAAA;AACH,QAAA,iBAAA,EAAkB;AAClB,QAAA;AAAA,MACF,KAAK,cAAA;AAAA,MACL,KAAK,YAAA;AACH,QAAA,eAAA,EAAgB;AAChB,QAAA;AAAA,MACF,KAAK,gBAAA;AACH,QAAA,UAAA,EAAW;AACX,QAAA;AAAA;AAEJ,IAAA,IAAI,KAAA,CAAM,SAAS,gBAAA,IAAoB,KAAA,CAAM,SAAS,YAAA,IAAgB,KAAA,CAAM,SAAS,QAAA,EAAU;AAC7F,MAAA,mBAAA,EAAoB;AAAA,IACtB;AACA,IAAA,IAAA,EAAK;AAAA,EACP,CAAA;AAEA,EAAA,MAAM,qBAAqB,MAAM;AAC/B,IAAA,MAAM,KAAK,QAAA,CAAS,iBAAA;AACpB,IAAA,MAAM,KAAA,GAAQ,KAAA;AACd,IAAA,GAAA,CAAI;AAAA,MACF,UAAA,EACG,CAAC,CAAC,EAAA,KAAO,EAAA,KAAO,aAAa,EAAA,KAAO,KAAA,CAAA,IAAW,CAAC,CAAC,KAAA,EAAO;AAAA,KAC5D,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,MAAA,GAAS,CAAC,EAAA,KAAgC;AAC9C,IAAA,IAAI,UAAU,EAAA,EAAI;AAClB,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,YAAA,CAAa,QAAQ,CAAC,CAAA,KAAM,MAAO,mBAAA,CAAoB,CAAA,EAAG,OAAO,CAAC,CAAA;AAClE,MAAA,YAAA,CAAa,OAAA,CAAQ,CAAC,CAAA,KAAM,KAAA,CAAO,YAAY,mBAAA,GAAsB,CAAA,EAAG,cAAc,CAAC,CAAA;AACvF,MAAA,KAAA,CAAM,mBAAA,CAAoB,yBAAyB,kBAAkB,CAAA;AACrE,MAAA,KAAA,CAAM,mBAAA,CAAoB,uBAAuB,kBAAkB,CAAA;AACnE,MAAA,YAAA,CAAa,IAAI,CAAA;AAAA,IACnB;AACA,IAAA,KAAA,GAAQ,EAAA;AACR,IAAA,IAAI,CAAC,EAAA,EAAI;AACP,MAAA,KAAA,GAAQ,YAAA;AACR,MAAA,SAAA,CAAU,OAAA,CAAQ,CAAC,CAAA,KAAM,CAAA,EAAG,CAAA;AAC5B,MAAA;AAAA,IACF;AACA,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,MAAM,KAAA,GAAQ,gBAAgB,UAAU,CAAA;AACxC,MAAA,IAAI,OAAO,KAAA,CAAM,MAAA,KAAW,QAAA,KAAa,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,MAAA,EAAQ,CAAC,GAAG,CAAC,CAAA;AACvF,MAAA,IAAI,OAAO,KAAA,CAAM,KAAA,KAAU,SAAA,EAAW,EAAA,CAAG,QAAQ,KAAA,CAAM,KAAA;AACvD,MAAA,IAAI,OAAO,KAAA,CAAM,YAAA,KAAiB,QAAA,EAAU,EAAA,CAAG,eAAe,KAAA,CAAM,YAAA;AAAA,IACtE;AACA,IAAA,YAAA,CAAa,QAAQ,CAAC,CAAA,KAAM,GAAG,gBAAA,CAAiB,CAAA,EAAG,OAAO,CAAC,CAAA;AAC3D,IAAA,YAAA,CAAa,OAAA,CAAQ,CAAC,CAAA,KAAM,EAAA,CAAG,YAAY,gBAAA,GAAmB,CAAA,EAAG,cAAc,CAAC,CAAA;AAChF,IAAA,EAAA,CAAG,gBAAA,CAAiB,yBAAyB,kBAAkB,CAAA;AAC/D,IAAA,EAAA,CAAG,gBAAA,CAAiB,uBAAuB,kBAAkB,CAAA;AAC7D,IAAA,IAAA,EAAK;AACL,IAAA,UAAA,EAAW;AAAA,EACb,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,CAAC,EAAA,KAA2B;AAC/C,IAAA,IAAI,OAAO,aAAa,WAAA,EAAa;AACrC,IAAA,QAAA,CAAS,mBAAA,CAAoB,oBAAoB,kBAAkB,CAAA;AACnE,IAAA,SAAA,GAAY,EAAA;AACZ,IAAA,IAAI,EAAA,EAAI,QAAA,CAAS,gBAAA,CAAiB,kBAAA,EAAoB,kBAAkB,CAAA;AAAA,EAC1E,CAAA;AAEA,EAAA,MAAM,OAAO,YAAY;AACvB,IAAA,IAAI,CAAC,KAAA,EAAO;AACZ,IAAA,IAAI;AACF,MAAA,MAAM,MAAM,IAAA,EAAK;AAAA,IACnB,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,KAAA,GAAQ,MAAM,KAAA,EAAO,KAAA,EAAM;AAEjC,EAAA,MAAM,OAAA,GAAU;AAAA,IACd,IAAA;AAAA,IACA,KAAA;AAAA,IACA,MAAA,EAAQ,MAAO,KAAA,EAAO,MAAA,IAAU,OAAO,KAAA,GAAQ,IAAA,KAAS,KAAA,EAAM;AAAA,IAC9D,IAAA,EAAM,CAAC,IAAA,KAAiB;AACtB,MAAA,IAAI,CAAC,KAAA,EAAO;AACZ,MAAA,MAAM,MAAM,MAAA,CAAO,QAAA,CAAS,MAAM,QAAQ,CAAA,GAAI,MAAM,QAAA,GAAW,IAAA;AAC/D,MAAA,KAAA,CAAM,WAAA,GAAc,KAAK,GAAA,CAAI,IAAA,CAAK,IAAI,IAAA,EAAM,CAAC,GAAG,GAAG,CAAA;AACnD,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,IACA,MAAA,EAAQ,CAAC,KAAA,KAAkB,OAAA,CAAQ,MAAM,KAAA,EAAO,WAAA,IAAe,KAAK,KAAK,CAAA;AAAA;AAAA,IAEzE,WAAA,EAAa,CAAC,KAAA,KAAkB,OAAA,CAAQ,KAAK,KAAA,IAAS,KAAA,EAAO,YAAY,CAAA,CAAE,CAAA;AAAA,IAC3E,SAAA,EAAW,CAAC,MAAA,KAAmB;AAC7B,MAAA,IAAI,CAAC,KAAA,EAAO;AACZ,MAAA,KAAA,CAAM,MAAA,GAAS,KAAK,GAAA,CAAI,IAAA,CAAK,IAAI,MAAA,EAAQ,CAAC,GAAG,CAAC,CAAA;AAC9C,MAAA,KAAA,CAAM,KAAA,GAAQ,MAAM,MAAA,KAAW,CAAA;AAAA,IACjC,CAAA;AAAA,IACA,YAAY,MAAM;AAChB,MAAA,IAAI,CAAC,KAAA,EAAO;AACZ,MAAA,IAAI,MAAM,KAAA,IAAS,KAAA,CAAM,MAAA,KAAW,CAAA,QAAS,MAAA,GAAS,CAAA;AACtD,MAAA,KAAA,CAAM,KAAA,GAAQ,CAAC,KAAA,CAAM,KAAA;AAAA,IACvB,CAAA;AAAA,IACA,eAAA,EAAiB,CAAC,IAAA,KAAiB;AACjC,MAAA,IAAI,KAAA,QAAa,YAAA,GAAe,IAAA;AAAA,IAClC,CAAA;AAAA;AAAA,IAEA,YAAA,EAAc,CAAC,KAAA,KAAkB,kBAAA,CAAmB,KAAK,CAAA;AAAA,IACzD,gBAAgB,MAAM;AACpB,MAAA,IAAI,CAAC,KAAA,CAAM,UAAA,CAAW,MAAA,EAAQ;AAC9B,MAAA,kBAAA,CAAmB,KAAA,CAAM,SAAA,KAAc,EAAA,GAAK,CAAA,GAAI,EAAE,CAAA;AAAA,IACpD,CAAA;AAAA;AAAA,IAEA,OAAO,MAAM;AACX,MAAA,IAAI,CAAC,KAAA,EAAO;AACZ,MAAA,KAAA,CAAM,IAAA,EAAK;AACX,MAAA,KAAK,IAAA,EAAK;AAAA,IACZ,CAAA;AAAA,IACA,WAAA,EAAa,CAAC,IAAA,KAA2B;AACvC,MAAA,QAAA,GAAW,IAAA;AACX,MAAA,IAAI,KAAA,IAAS,CAAC,KAAA,CAAM,MAAA,EAAQ,iBAAA,EAAkB;AAAA,IAChD,CAAA;AAAA,IACA,kBAAkB,YAAY;AAC5B,MAAA,IAAI,OAAO,aAAa,WAAA,EAAa;AACrC,MAAA,IAAI,SAAS,iBAAA,EAAmB;AAC9B,QAAA,MAAM,QAAA,CAAS,cAAA,EAAe,CAAE,KAAA,CAAM,MAAM;AAAA,QAAC,CAAC,CAAA;AAC9C,QAAA;AAAA,MACF;AACA,MAAA,IAAI,WAAW,iBAAA,EAAmB;AAChC,QAAA,MAAM,SAAA,CAAU,iBAAA,EAAkB,CAAE,KAAA,CAAM,MAAM;AAAA,QAAC,CAAC,CAAA;AAClD,QAAA;AAAA,MACF;AAEC,MAAC,OAA8B,qBAAA,IAAwB;AAAA,IAC1D,CAAA;AAAA,IACA,wBAAwB,YAAY;AAClC,MAAA,IAAI,EAAE,iBAAiB,gBAAA,CAAA,EAAmB;AAC1C,MAAA,IAAI;AACF,QAAA,IAAI,QAAA,CAAS,uBAAA,EAAyB,MAAM,QAAA,CAAS,oBAAA,EAAqB;AAAA,aACrE,MAAM,MAAM,uBAAA,EAAwB;AAAA,MAC3C,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AAAA,GACF;AAGA,EAAA,MAAM,kBAAA,GAAqB,CAAC,KAAA,KAAkB;AAC5C,IAAA,WAAA,GAAc,IAAA;AACd,IAAA,aAAA,CAAc,KAAK,CAAA,CAAE,OAAA,CAAQ,CAAC,OAAO,CAAA,KAAM;AACzC,MAAA,IAAI,MAAM,KAAA,IAAS,KAAA,CAAM,IAAA,KAAS,SAAA,QAAiB,IAAA,GAAO,UAAA;AAAA,IAC5D,CAAC,CAAA;AACD,IAAA,UAAA,CAAW,KAAK,CAAA;AAAA,EAClB,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,UAAU,MAAM,KAAA;AAAA,IAChB,gBAAgB,MAAM,YAAA;AAAA,IACtB,UAAU,MAAM,KAAA;AAAA,IAChB,SAAA,EAAW,CAAC,QAAA,KAAuB;AACjC,MAAA,SAAA,CAAU,IAAI,QAAQ,CAAA;AACtB,MAAA,OAAO,MAAM,SAAA,CAAU,MAAA,CAAO,QAAQ,CAAA;AAAA,IACxC,CAAA;AAAA,IACA,MAAA;AAAA,IACA,YAAA;AAAA,IACA,GAAG;AAAA,GACL;AACF;AASO,SAAS,UAAA,CAAW,OAAA,EAAiB,SAAA,GAAY,OAAA,EAAS;AAC/D,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,OAAO,CAAA,IAAK,OAAA,GAAU,GAAG,OAAA,GAAU,CAAA;AACxD,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,OAAA,GAAU,EAAE,CAAA;AACjC,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAO,OAAA,GAAU,KAAM,EAAE,CAAA;AACxC,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,OAAA,GAAU,IAAI,CAAA;AACnC,EAAA,MAAM,KAAK,CAAA,CAAE,QAAA,EAAS,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AACvC,EAAA,MAAM,QAAQ,CAAA,GAAI,CAAA,IAAM,OAAO,QAAA,CAAS,SAAS,KAAK,SAAA,IAAa,IAAA;AACnE,EAAA,OAAO,QAAQ,CAAA,EAAG,CAAC,CAAA,CAAA,EAAI,CAAA,CAAE,UAAS,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA,EAAI,EAAE,KAAK,CAAA,EAAG,CAAC,IAAI,EAAE,CAAA,CAAA;AAC3E;AAGO,SAAS,mBAAmB,KAAA,EAA0B;AAC3D,EAAA,QAAQ,OAAO,IAAA;AAAM,IACnB,KAAK,CAAA;AACH,MAAA,OAAO,uBAAA;AAAA,IACT,KAAK,CAAA;AACH,MAAA,OAAO,uCAAA;AAAA,IACT,KAAK,CAAA;AACH,MAAA,OAAO,kCAAA;AAAA,IACT,KAAK,CAAA;AACH,MAAA,OAAO,gCAAA;AAAA,IACT;AACE,MAAA,OAAO,uCAAA;AAAA;AAEb","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 /** False while the element has no source to play. */\r\n hasSource: boolean\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 \"loadstart\",\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 hasSource: false,\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\n/**\r\n * The HTML form drops voice and class tags and decodes entities, so a cue\r\n * written as `Q&amp;A` reads `Q&A`. jsdom has no `getCueAsHTML`.\r\n */\r\nfunction cueText(cue: TextTrackCue) {\r\n const vtt = cue as VTTCue\r\n const text = typeof vtt.getCueAsHTML === \"function\" ? vtt.getCueAsHTML().textContent : vtt.text?.replace(/<[^>]+>/g, \"\")\r\n return (text ?? \"\").trim()\r\n}\r\n\r\n/* the track a `<track default>` element asks for, or -1 */\r\nfunction defaultTrack(media: HTMLMediaElement | null, tracks: TextTrack[]) {\r\n const el = Array.from(media?.querySelectorAll(\"track\") ?? []).find((track) => track.default)\r\n return el ? tracks.indexOf(el.track) : -1\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\r\n /* set once the viewer picks a track, so `default` stops applying */\r\n let trackChosen = false\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 hasSource: Boolean(media.src || media.currentSrc || media.querySelector(\"source[src]\")),\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 /**\r\n * Attaching sets every track's mode before the browser applies `default`,\r\n * which cancels it, so the default is applied here instead.\r\n */\r\n let active = requested === -1 && !trackChosen ? defaultTrack(media, tracks) : 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 trackChosen = true\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\n/**\r\n * `m:ss`, or `h:mm:ss` from one hour. Pass the duration as `reference` so\r\n * every time in a player uses the same fields: `0:00:05` beside `10:00:01`,\r\n * and the label does not widen when playback passes an hour.\r\n */\r\nexport function formatTime(seconds: number, reference = seconds) {\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 const hours = h > 0 || (Number.isFinite(reference) && reference >= 3600)\r\n return hours ? `${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 media couldn't be loaded.\"\r\n default:\r\n return \"Something went wrong during playback.\"\r\n }\r\n}\r\n"]}
package/dist/hls.d.ts CHANGED
@@ -5,8 +5,11 @@
5
5
  type MediaSourceLoader = (media: HTMLMediaElement, src: string) => void | (() => void);
6
6
 
7
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.
8
+ * Plays `.m3u8` streams with hls.js, loaded on demand so it never lands in
9
+ * the main bundle. Native playback is only the fallback for browsers without
10
+ * Media Source Extensions: Chrome 142 and later report native HLS support,
11
+ * but cannot parse common MPEG-TS streams, so `canPlayType` is not a safe
12
+ * test.
10
13
  */
11
14
  declare const hlsLoader: MediaSourceLoader;
12
15
 
package/dist/hls.js CHANGED
@@ -1,19 +1,20 @@
1
1
  // ../../lib/media-hls.ts
2
- var canPlayNatively = (media) => media.canPlayType("application/vnd.apple.mpegurl") !== "";
2
+ var hasMediaSource = () => typeof window !== "undefined" && ("MediaSource" in window || "ManagedMediaSource" in window);
3
3
  var hlsLoader = (media, src) => {
4
- if (!/\.m3u8($|\?)/i.test(src) || canPlayNatively(media)) {
4
+ const playNatively = () => {
5
5
  media.src = src;
6
6
  return () => {
7
7
  media.removeAttribute("src");
8
8
  media.load();
9
9
  };
10
- }
10
+ };
11
+ if (!/\.m3u8($|\?)/i.test(src) || !hasMediaSource()) return playNatively();
11
12
  let disposed = false;
12
13
  let destroy;
13
14
  import('hls.js').then(({ default: Hls }) => {
14
15
  if (disposed) return;
15
16
  if (!Hls.isSupported()) {
16
- media.src = src;
17
+ destroy = playNatively();
17
18
  return;
18
19
  }
19
20
  const hls = new Hls({ enableWorker: true });
package/dist/hls.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../lib/media-hls.ts"],"names":[],"mappings":";AAEA,IAAM,kBAAkB,CAAC,KAAA,KACvB,KAAA,CAAM,WAAA,CAAY,+BAA+B,CAAA,KAAM,EAAA;AAMlD,IAAM,SAAA,GAA+B,CAAC,KAAA,EAAO,GAAA,KAAQ;AAC1D,EAAA,IAAI,CAAC,eAAA,CAAgB,IAAA,CAAK,GAAG,CAAA,IAAK,eAAA,CAAgB,KAAK,CAAA,EAAG;AACxD,IAAA,KAAA,CAAM,GAAA,GAAM,GAAA;AACZ,IAAA,OAAO,MAAM;AACX,MAAA,KAAA,CAAM,gBAAgB,KAAK,CAAA;AAC3B,MAAA,KAAA,CAAM,IAAA,EAAK;AAAA,IACb,CAAA;AAAA,EACF;AAEA,EAAA,IAAI,QAAA,GAAW,KAAA;AACf,EAAA,IAAI,OAAA;AAEJ,EAAA,OAAO,QAAQ,CAAA,CAAE,IAAA,CAAK,CAAC,EAAE,OAAA,EAAS,KAAI,KAAM;AAC1C,IAAA,IAAI,QAAA,EAAU;AACd,IAAA,IAAI,CAAC,GAAA,CAAI,WAAA,EAAY,EAAG;AACtB,MAAA,KAAA,CAAM,GAAA,GAAM,GAAA;AACZ,MAAA;AAAA,IACF;AACA,IAAA,MAAM,MAAM,IAAI,GAAA,CAAI,EAAE,YAAA,EAAc,MAAM,CAAA;AAC1C,IAAA,GAAA,CAAI,GAAG,GAAA,CAAI,MAAA,CAAO,KAAA,EAAO,CAAC,GAAG,IAAA,KAAS;AACpC,MAAA,IAAI,CAAC,KAAK,KAAA,EAAO;AACjB,MAAA,IAAI,KAAK,IAAA,KAAS,GAAA,CAAI,UAAA,CAAW,aAAA,MAAmB,SAAA,EAAU;AAAA,WAAA,IACrD,KAAK,IAAA,KAAS,GAAA,CAAI,UAAA,CAAW,WAAA,MAAiB,iBAAA,EAAkB;AAAA,eAChE,OAAA,EAAQ;AAAA,IACnB,CAAC,CAAA;AACD,IAAA,GAAA,CAAI,WAAW,GAAG,CAAA;AAClB,IAAA,GAAA,CAAI,YAAY,KAAK,CAAA;AACrB,IAAA,OAAA,GAAU,MAAM,IAAI,OAAA,EAAQ;AAAA,EAC9B,CAAC,CAAA;AAED,EAAA,OAAO,MAAM;AACX,IAAA,QAAA,GAAW,IAAA;AACX,IAAA,OAAA,IAAU;AAAA,EACZ,CAAA;AACF","file":"hls.js","sourcesContent":["import type { MediaSourceLoader } from \"@/components/ui/media\"\r\n\r\nconst canPlayNatively = (media: HTMLMediaElement) =>\r\n media.canPlayType(\"application/vnd.apple.mpegurl\") !== \"\"\r\n\r\n/**\r\n * Plays `.m3u8` streams. Safari plays HLS natively; everywhere else hls.js\r\n * is loaded on demand so it never lands in the main bundle.\r\n */\r\nexport const hlsLoader: MediaSourceLoader = (media, src) => {\r\n if (!/\\.m3u8($|\\?)/i.test(src) || canPlayNatively(media)) {\r\n media.src = src\r\n return () => {\r\n media.removeAttribute(\"src\")\r\n media.load()\r\n }\r\n }\r\n\r\n let disposed = false\r\n let destroy: (() => void) | undefined\r\n\r\n import(\"hls.js\").then(({ default: Hls }) => {\r\n if (disposed) return\r\n if (!Hls.isSupported()) {\r\n media.src = src\r\n return\r\n }\r\n const hls = new Hls({ enableWorker: true })\r\n hls.on(Hls.Events.ERROR, (_, data) => {\r\n if (!data.fatal) return\r\n if (data.type === Hls.ErrorTypes.NETWORK_ERROR) hls.startLoad()\r\n else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) hls.recoverMediaError()\r\n else hls.destroy()\r\n })\r\n hls.loadSource(src)\r\n hls.attachMedia(media)\r\n destroy = () => hls.destroy()\r\n })\r\n\r\n return () => {\r\n disposed = true\r\n destroy?.()\r\n }\r\n}\r\n"]}
1
+ {"version":3,"sources":["../../../lib/media-hls.ts"],"names":[],"mappings":";AAMA,IAAM,iBAAiB,MACrB,OAAO,WAAW,WAAA,KAAgB,aAAA,IAAiB,UAAU,oBAAA,IAAwB,MAAA,CAAA;AAShF,IAAM,SAAA,GAA+B,CAAC,KAAA,EAAO,GAAA,KAAQ;AAC1D,EAAA,MAAM,eAAe,MAAM;AACzB,IAAA,KAAA,CAAM,GAAA,GAAM,GAAA;AACZ,IAAA,OAAO,MAAM;AACX,MAAA,KAAA,CAAM,gBAAgB,KAAK,CAAA;AAC3B,MAAA,KAAA,CAAM,IAAA,EAAK;AAAA,IACb,CAAA;AAAA,EACF,CAAA;AAEA,EAAA,IAAI,CAAC,gBAAgB,IAAA,CAAK,GAAG,KAAK,CAAC,cAAA,EAAe,EAAG,OAAO,YAAA,EAAa;AAEzE,EAAA,IAAI,QAAA,GAAW,KAAA;AACf,EAAA,IAAI,OAAA;AAEJ,EAAA,OAAO,QAAQ,CAAA,CAAE,IAAA,CAAK,CAAC,EAAE,OAAA,EAAS,KAAI,KAAM;AAC1C,IAAA,IAAI,QAAA,EAAU;AACd,IAAA,IAAI,CAAC,GAAA,CAAI,WAAA,EAAY,EAAG;AACtB,MAAA,OAAA,GAAU,YAAA,EAAa;AACvB,MAAA;AAAA,IACF;AACA,IAAA,MAAM,MAAM,IAAI,GAAA,CAAI,EAAE,YAAA,EAAc,MAAM,CAAA;AAC1C,IAAA,GAAA,CAAI,GAAG,GAAA,CAAI,MAAA,CAAO,KAAA,EAAO,CAAC,GAAG,IAAA,KAAS;AACpC,MAAA,IAAI,CAAC,KAAK,KAAA,EAAO;AACjB,MAAA,IAAI,KAAK,IAAA,KAAS,GAAA,CAAI,UAAA,CAAW,aAAA,MAAmB,SAAA,EAAU;AAAA,WAAA,IACrD,KAAK,IAAA,KAAS,GAAA,CAAI,UAAA,CAAW,WAAA,MAAiB,iBAAA,EAAkB;AAAA,eAChE,OAAA,EAAQ;AAAA,IACnB,CAAC,CAAA;AACD,IAAA,GAAA,CAAI,WAAW,GAAG,CAAA;AAClB,IAAA,GAAA,CAAI,YAAY,KAAK,CAAA;AACrB,IAAA,OAAA,GAAU,MAAM,IAAI,OAAA,EAAQ;AAAA,EAC9B,CAAC,CAAA;AAED,EAAA,OAAO,MAAM;AACX,IAAA,QAAA,GAAW,IAAA;AACX,IAAA,OAAA,IAAU;AAAA,EACZ,CAAA;AACF","file":"hls.js","sourcesContent":["import type { MediaSourceLoader } from \"@/components/ui/media\"\r\n\r\n/**\r\n * Media Source Extensions, which hls.js needs. iPhones before iOS 17.1 have\r\n * neither API, and they are the browsers that must play HLS natively.\r\n */\r\nconst hasMediaSource = () =>\r\n typeof window !== \"undefined\" && (\"MediaSource\" in window || \"ManagedMediaSource\" in window)\r\n\r\n/**\r\n * Plays `.m3u8` streams with hls.js, loaded on demand so it never lands in\r\n * the main bundle. Native playback is only the fallback for browsers without\r\n * Media Source Extensions: Chrome 142 and later report native HLS support,\r\n * but cannot parse common MPEG-TS streams, so `canPlayType` is not a safe\r\n * test.\r\n */\r\nexport const hlsLoader: MediaSourceLoader = (media, src) => {\r\n const playNatively = () => {\r\n media.src = src\r\n return () => {\r\n media.removeAttribute(\"src\")\r\n media.load()\r\n }\r\n }\r\n\r\n if (!/\\.m3u8($|\\?)/i.test(src) || !hasMediaSource()) return playNatively()\r\n\r\n let disposed = false\r\n let destroy: (() => void) | undefined\r\n\r\n import(\"hls.js\").then(({ default: Hls }) => {\r\n if (disposed) return\r\n if (!Hls.isSupported()) {\r\n destroy = playNatively()\r\n return\r\n }\r\n const hls = new Hls({ enableWorker: true })\r\n hls.on(Hls.Events.ERROR, (_, data) => {\r\n if (!data.fatal) return\r\n if (data.type === Hls.ErrorTypes.NETWORK_ERROR) hls.startLoad()\r\n else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) hls.recoverMediaError()\r\n else hls.destroy()\r\n })\r\n hls.loadSource(src)\r\n hls.attachMedia(media)\r\n destroy = () => hls.destroy()\r\n })\r\n\r\n return () => {\r\n disposed = true\r\n destroy?.()\r\n }\r\n}\r\n"]}
package/dist/index.d.ts CHANGED
@@ -30,6 +30,8 @@ type MediaState = {
30
30
  fullscreen: boolean;
31
31
  pictureInPicture: boolean;
32
32
  error: MediaError | null;
33
+ /** False while the element has no source to play. */
34
+ hasSource: boolean;
33
35
  /** Caption and subtitle tracks, in document order. */
34
36
  textTracks: MediaTextTrack[];
35
37
  /** Index into `textTracks`, or -1 when captions are off. */
@@ -73,9 +75,14 @@ declare function createMediaStore({ persistKey }?: MediaStoreOptions): {
73
75
  setContainer: (el: HTMLElement | null) => void;
74
76
  };
75
77
  type MediaStore = ReturnType<typeof createMediaStore>;
76
- declare function formatTime(seconds: number): string;
78
+ /**
79
+ * `m:ss`, or `h:mm:ss` from one hour. Pass the duration as `reference` so
80
+ * every time in a player uses the same fields: `0:00:05` beside `10:00:01`,
81
+ * and the label does not widen when playback passes an hour.
82
+ */
83
+ declare function formatTime(seconds: number, reference?: number): string;
77
84
  /** Human-readable message for a MediaError code. */
78
- 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.";
85
+ declare function describeMediaError(error: MediaError | null): "Playback was aborted." | "A network error stopped the download." | "This media could not be decoded." | "This media couldn't be loaded." | "Something went wrong during playback.";
79
86
 
80
87
  declare function useMediaStore(): {
81
88
  play: () => Promise<void>;