@karnstack/kino 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.
@@ -0,0 +1,62 @@
1
+ import { a as Provider, c as TextTrackInfo, i as PlayerActions, n as MediaError, o as QualityLevel, r as MediaState, s as SourceOptions, t as Capabilities } from "./types-D0bLitH2.js";
2
+ import { ReactNode, RefObject } from "react";
3
+
4
+ //#region src/util/format-time.d.ts
5
+ declare function formatTime(seconds: number): string;
6
+ //#endregion
7
+ //#region src/core/store.d.ts
8
+ declare const PlayerContext: import("react").Context<Provider | null>;
9
+ declare function useMediaSelector<T>(selector: (s: MediaState) => T, isEqual?: (a: T, b: T) => boolean): T;
10
+ declare function usePlayer(): {
11
+ state: MediaState;
12
+ actions: PlayerActions;
13
+ };
14
+ declare function usePlayerActions(): PlayerActions;
15
+ //#endregion
16
+ //#region src/ui/player.d.ts
17
+ declare function useWrapperRef(): RefObject<HTMLDivElement | null> | null;
18
+ declare function useIsCompact(): boolean;
19
+ type PlayerProps = {
20
+ provider: Provider;
21
+ accentColor?: string;
22
+ theme?: Record<string, string>;
23
+ className?: string;
24
+ children?: ReactNode;
25
+ };
26
+ declare function Player({
27
+ provider,
28
+ accentColor,
29
+ theme,
30
+ className,
31
+ children
32
+ }: PlayerProps): import("react").JSX.Element;
33
+ declare namespace Player {
34
+ var Overlay: ({
35
+ children
36
+ }: {
37
+ children: ReactNode;
38
+ }) => import("react").JSX.Element;
39
+ }
40
+ declare function useControlsVisible(): boolean;
41
+ //#endregion
42
+ //#region src/ui/scrubber.d.ts
43
+ declare function Scrubber(): import("react").JSX.Element;
44
+ //#endregion
45
+ //#region src/ui/idle-overlay.d.ts
46
+ declare function IdleOverlay(): import("react").JSX.Element | null;
47
+ //#endregion
48
+ //#region src/ui/captions.d.ts
49
+ declare function Captions(): import("react").JSX.Element | null;
50
+ //#endregion
51
+ //#region src/ui/buttons.d.ts
52
+ declare function PlayPauseButton(): import("react").JSX.Element;
53
+ declare function SkipBackButton(): import("react").JSX.Element;
54
+ declare function SkipForwardButton(): import("react").JSX.Element;
55
+ declare function VolumeControl(): import("react").JSX.Element;
56
+ declare function PipButton(): import("react").JSX.Element | null;
57
+ declare function FullscreenButton(): import("react").JSX.Element | null;
58
+ //#endregion
59
+ //#region src/ui/control-bar.d.ts
60
+ declare function ControlBar(): import("react").JSX.Element;
61
+ //#endregion
62
+ export { type Capabilities, Captions, ControlBar, FullscreenButton, IdleOverlay, type MediaError, type MediaState, PipButton, PlayPauseButton, Player, type PlayerActions, PlayerContext, type Provider, type QualityLevel, Scrubber, SkipBackButton, SkipForwardButton, type SourceOptions, type TextTrackInfo, VolumeControl, formatTime, useControlsVisible, useIsCompact, useMediaSelector, usePlayer, usePlayerActions, useWrapperRef };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import { _ as usePlayer, a as SkipBackButton, c as Captions, d as Player, f as useControlsVisible, g as useMediaSelector, h as PlayerContext, i as PlayPauseButton, l as IdleOverlay, m as useWrapperRef, n as FullscreenButton, o as SkipForwardButton, p as useIsCompact, r as PipButton, s as VolumeControl, t as ControlBar, u as Scrubber, v as usePlayerActions, y as formatTime } from "./control-bar-DWzMIb23.js";
2
+ export { Captions, ControlBar, FullscreenButton, IdleOverlay, PipButton, PlayPauseButton, Player, PlayerContext, Scrubber, SkipBackButton, SkipForwardButton, VolumeControl, formatTime, useControlsVisible, useIsCompact, useMediaSelector, usePlayer, usePlayerActions, useWrapperRef };
package/dist/mux.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { a as Provider } from "./types-D0bLitH2.js";
2
+ import { ReactNode } from "react";
3
+ //#region src/mux/provider.d.ts
4
+ type MuxProviderOptions = {
5
+ playbackId: string;
6
+ tokens?: {
7
+ playback?: string;
8
+ thumbnail?: string;
9
+ storyboard?: string;
10
+ };
11
+ metadata?: {
12
+ videoId?: string;
13
+ videoTitle?: string;
14
+ viewerUserId?: string;
15
+ };
16
+ envKey?: string;
17
+ poster?: string;
18
+ autoPlay?: boolean;
19
+ defaultRate?: number;
20
+ };
21
+ declare function createMuxProvider(opts: MuxProviderOptions): Provider;
22
+ //#endregion
23
+ //#region src/mux/mux-player.d.ts
24
+ type MuxPlayerProps = MuxProviderOptions & {
25
+ accentColor?: string;
26
+ theme?: Record<string, string>;
27
+ className?: string;
28
+ children?: ReactNode;
29
+ };
30
+ declare function MuxPlayer({
31
+ accentColor,
32
+ theme,
33
+ className,
34
+ children,
35
+ ...opts
36
+ }: MuxPlayerProps): import("react").JSX.Element;
37
+ //#endregion
38
+ export { MuxPlayer, type MuxPlayerProps, type MuxProviderOptions, createMuxProvider };
package/dist/mux.js ADDED
@@ -0,0 +1,439 @@
1
+ import { c as Captions, d as Player, l as IdleOverlay, t as ControlBar } from "./control-bar-DWzMIb23.js";
2
+ import { useEffect, useRef } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import "@mux/mux-video";
5
+ //#region src/core/fake-provider.ts
6
+ const DEFAULT_CAPS = {
7
+ canSetQuality: true,
8
+ hasStoryboard: false,
9
+ canPiP: true,
10
+ canFullscreen: true,
11
+ canSetRate: true,
12
+ hasTextTracks: false
13
+ };
14
+ function defaultState() {
15
+ return {
16
+ paused: true,
17
+ currentTime: 0,
18
+ duration: 0,
19
+ buffered: [],
20
+ rate: 1,
21
+ volume: 1,
22
+ muted: false,
23
+ readyState: 0,
24
+ seeking: false,
25
+ ended: false,
26
+ error: null,
27
+ qualities: [],
28
+ activeQualityId: "auto",
29
+ videoHeight: 0,
30
+ textTracks: [],
31
+ activeTextTrackId: null,
32
+ activeCueText: "",
33
+ fullscreen: false,
34
+ pip: false,
35
+ storyboard: null,
36
+ capabilities: { ...DEFAULT_CAPS }
37
+ };
38
+ }
39
+ //#endregion
40
+ //#region src/mux/urls.ts
41
+ const IMAGE_HOST = "https://image.mux.com";
42
+ function buildImageUrl(playbackId, kind, token, ext = kind === "storyboard" ? "vtt" : "webp") {
43
+ const base = `${IMAGE_HOST}/${playbackId}/${kind}.${ext}`;
44
+ return token ? `${base}?token=${token}` : base;
45
+ }
46
+ function detectIOS(ua) {
47
+ return /iPhone|iPad|iPod/.test(ua);
48
+ }
49
+ //#endregion
50
+ //#region src/util/captions.ts
51
+ function joinCues(cues) {
52
+ if (!cues || cues.length === 0) return "";
53
+ const parts = [];
54
+ for (let i = 0; i < cues.length; i++) {
55
+ const c = cues[i];
56
+ if (c && typeof c.text === "string") parts.push(c.text);
57
+ }
58
+ return parts.join("\n").replace(/<[^>]+>/g, "").replace(/[^\S\n]+/g, " ").replace(/ *\n */g, "\n").replace(/\n{2,}/g, "\n").trim();
59
+ }
60
+ function activeCueText(tracks, now) {
61
+ if (!tracks) return "";
62
+ for (let i = 0; i < tracks.length; i++) {
63
+ const t = tracks[i];
64
+ if (!t) continue;
65
+ if (t.kind !== "subtitles" && t.kind !== "captions") continue;
66
+ if (t.mode === "disabled") continue;
67
+ const active = joinCues(t.activeCues);
68
+ if (active) return active;
69
+ const all = t.cues;
70
+ if (all && all.length) {
71
+ const hits = [];
72
+ for (let j = 0; j < all.length; j++) {
73
+ const c = all[j];
74
+ if (c && c.startTime <= now && now < c.endTime) hits.push(c);
75
+ }
76
+ const text = joinCues(hits);
77
+ if (text) return text;
78
+ }
79
+ }
80
+ return "";
81
+ }
82
+ //#endregion
83
+ //#region src/mux/provider.ts
84
+ function createMuxProvider(opts) {
85
+ const ios = typeof navigator !== "undefined" && detectIOS(navigator.userAgent);
86
+ let el = null;
87
+ let renditions = null;
88
+ let renditionsBound = false;
89
+ let state = {
90
+ ...defaultState(),
91
+ rate: opts.defaultRate ?? 1,
92
+ storyboard: { vttUrl: buildImageUrl(opts.playbackId, "storyboard", opts.tokens?.storyboard) },
93
+ capabilities: {
94
+ canSetQuality: !ios,
95
+ hasStoryboard: true,
96
+ canPiP: typeof document !== "undefined" && "pictureInPictureEnabled" in document,
97
+ canFullscreen: true,
98
+ canSetRate: true,
99
+ hasTextTracks: true
100
+ }
101
+ };
102
+ const listeners = /* @__PURE__ */ new Set();
103
+ const emit = () => listeners.forEach((l) => l());
104
+ const patch = (p) => {
105
+ state = {
106
+ ...state,
107
+ ...p
108
+ };
109
+ emit();
110
+ };
111
+ const onFullscreenChange = () => patch({ fullscreen: document.fullscreenElement != null });
112
+ const onWebkitBeginFullscreen = () => patch({ fullscreen: true });
113
+ const onWebkitEndFullscreen = () => patch({ fullscreen: false });
114
+ const onEnterPip = () => patch({ pip: true });
115
+ const onLeavePip = () => patch({ pip: false });
116
+ let activeTrack = null;
117
+ const readCueText = () => ios ? "" : activeCueText(el?.textTracks, el?.currentTime ?? 0);
118
+ const modeForActiveTrack = (t) => ios ? "showing" : t.cues && t.cues.length > 0 ? "hidden" : "showing";
119
+ const onCueChange = () => {
120
+ applyTextTrackModes();
121
+ patch({ activeCueText: readCueText() });
122
+ };
123
+ const bindActiveTrack = (track) => {
124
+ if (activeTrack) activeTrack.removeEventListener("cuechange", onCueChange);
125
+ activeTrack = track;
126
+ if (activeTrack) activeTrack.addEventListener("cuechange", onCueChange);
127
+ patch({ activeCueText: readCueText() });
128
+ };
129
+ const applyTextTrackModes = () => {
130
+ const tt = el?.textTracks;
131
+ if (!tt) return;
132
+ const id = state.activeTextTrackId;
133
+ let next = null;
134
+ for (let i = 0; i < tt.length; i++) {
135
+ const t = tt[i];
136
+ if (!t) continue;
137
+ if (t.kind !== "subtitles" && t.kind !== "captions") continue;
138
+ if (id != null && (t.id || String(i)) === id) {
139
+ const mode = modeForActiveTrack(t);
140
+ if (t.mode !== mode) t.mode = mode;
141
+ next = t;
142
+ } else if (t.mode !== "disabled") t.mode = "disabled";
143
+ }
144
+ if (next !== activeTrack) bindActiveTrack(next);
145
+ };
146
+ const onTextTracksChanged = () => {
147
+ applyTextTrackModes();
148
+ patch({
149
+ textTracks: readTextTracks(),
150
+ activeCueText: readCueText()
151
+ });
152
+ };
153
+ const readQualities = () => {
154
+ const r = el?.videoRenditions;
155
+ if (!r) return [];
156
+ const out = [];
157
+ for (let i = 0; i < r.length; i++) {
158
+ const item = r[i];
159
+ if (!item) continue;
160
+ out.push({
161
+ id: item.id,
162
+ height: item.height,
163
+ bitrate: item.bitrate,
164
+ selected: item.selected
165
+ });
166
+ }
167
+ return out;
168
+ };
169
+ const readTextTracks = () => {
170
+ const tt = el?.textTracks;
171
+ if (!tt) return [];
172
+ const out = [];
173
+ for (let i = 0; i < tt.length; i++) {
174
+ const t = tt[i];
175
+ if (!t) continue;
176
+ if (t.kind !== "subtitles" && t.kind !== "captions") continue;
177
+ out.push({
178
+ id: t.id || String(i),
179
+ kind: t.kind,
180
+ label: t.label,
181
+ lang: t.language,
182
+ mode: t.mode
183
+ });
184
+ }
185
+ return out;
186
+ };
187
+ const bindRenditions = () => {
188
+ if (renditionsBound || !el?.videoRenditions) return;
189
+ renditions = el.videoRenditions;
190
+ renditions.addEventListener("change", syncFromEl);
191
+ renditionsBound = true;
192
+ };
193
+ const syncFromEl = () => {
194
+ if (!el) return;
195
+ bindRenditions();
196
+ applyTextTrackModes();
197
+ const ranges = [];
198
+ for (let i = 0; i < el.buffered.length; i++) ranges.push([el.buffered.start(i), el.buffered.end(i)]);
199
+ patch({
200
+ paused: el.paused,
201
+ currentTime: el.currentTime,
202
+ duration: el.duration || 0,
203
+ buffered: ranges,
204
+ rate: el.playbackRate,
205
+ volume: el.volume,
206
+ muted: el.muted,
207
+ readyState: el.readyState,
208
+ seeking: el.seeking,
209
+ ended: el.ended,
210
+ error: el.error ? {
211
+ code: el.error.code,
212
+ message: el.error.message
213
+ } : null,
214
+ qualities: readQualities(),
215
+ textTracks: readTextTracks(),
216
+ videoHeight: el.videoHeight || 0,
217
+ activeCueText: readCueText()
218
+ });
219
+ if (typeof navigator !== "undefined" && "mediaSession" in navigator) navigator.mediaSession.playbackState = el.paused ? "paused" : "playing";
220
+ };
221
+ const MEDIA_SESSION_ACTIONS = [
222
+ "play",
223
+ "pause",
224
+ "seekbackward",
225
+ "seekforward",
226
+ "seekto"
227
+ ];
228
+ const setupMediaSession = () => {
229
+ if (typeof navigator === "undefined" || !("mediaSession" in navigator)) return;
230
+ const ms = navigator.mediaSession;
231
+ const set = (a, h) => {
232
+ try {
233
+ ms.setActionHandler(a, h);
234
+ } catch {}
235
+ };
236
+ set("play", () => actions.play());
237
+ set("pause", () => actions.pause());
238
+ set("seekbackward", (d) => actions.seek(Math.max(0, state.currentTime - (d.seekOffset || 10))));
239
+ set("seekforward", (d) => actions.seek(state.currentTime + (d.seekOffset || 10)));
240
+ set("seekto", (d) => {
241
+ if (typeof d.seekTime === "number") actions.seek(d.seekTime);
242
+ });
243
+ if (typeof MediaMetadata !== "undefined") try {
244
+ ms.metadata = new MediaMetadata({ title: opts.metadata?.videoTitle ?? "Video" });
245
+ } catch {}
246
+ };
247
+ const teardownMediaSession = () => {
248
+ if (typeof navigator === "undefined" || !("mediaSession" in navigator)) return;
249
+ for (const a of MEDIA_SESSION_ACTIONS) try {
250
+ navigator.mediaSession.setActionHandler(a, null);
251
+ } catch {}
252
+ };
253
+ const MEDIA_EVENTS = [
254
+ "play",
255
+ "pause",
256
+ "timeupdate",
257
+ "durationchange",
258
+ "progress",
259
+ "volumechange",
260
+ "ratechange",
261
+ "seeking",
262
+ "seeked",
263
+ "ended",
264
+ "loadedmetadata",
265
+ "canplay",
266
+ "waiting",
267
+ "error"
268
+ ];
269
+ const actions = {
270
+ play: () => {
271
+ el?.play?.();
272
+ },
273
+ pause: () => el?.pause(),
274
+ seek: (t) => {
275
+ if (el) el.currentTime = t;
276
+ },
277
+ setRate: (r) => {
278
+ if (el) el.playbackRate = r;
279
+ },
280
+ setVolume: (v) => {
281
+ if (el) el.volume = Math.min(1, Math.max(0, v));
282
+ },
283
+ setMuted: (m) => {
284
+ if (el) el.muted = m;
285
+ },
286
+ setQuality: (id) => {
287
+ const r = el?.videoRenditions;
288
+ if (!r) return;
289
+ if (id === "auto") {
290
+ r.selectedIndex = -1;
291
+ patch({ activeQualityId: "auto" });
292
+ return;
293
+ }
294
+ for (let i = 0; i < r.length; i++) {
295
+ const item = r[i];
296
+ if (item && item.id === id) {
297
+ item.selected = true;
298
+ r.selectedIndex = i;
299
+ }
300
+ }
301
+ patch({ activeQualityId: id });
302
+ },
303
+ setTextTrack: (id) => {
304
+ if (!el?.textTracks) return;
305
+ patch({ activeTextTrackId: id });
306
+ applyTextTrackModes();
307
+ },
308
+ enterFullscreen: (wrapper) => {
309
+ if (wrapper.requestFullscreen) wrapper.requestFullscreen();
310
+ else el?.webkitEnterFullscreen?.();
311
+ },
312
+ exitFullscreen: () => {
313
+ if (document.fullscreenElement) document.exitFullscreen?.();
314
+ else el?.webkitExitFullscreen?.();
315
+ },
316
+ enterPiP: () => {
317
+ el?.requestPictureInPicture?.();
318
+ },
319
+ exitPiP: () => {
320
+ document.exitPictureInPicture?.();
321
+ }
322
+ };
323
+ return {
324
+ mount(container) {
325
+ el = document.createElement("mux-video");
326
+ el.playbackId = opts.tokens?.playback ? `${opts.playbackId}?token=${opts.tokens.playback}` : opts.playbackId;
327
+ el.setAttribute("crossorigin", "");
328
+ el.setAttribute("playsinline", "");
329
+ el.playsInline = true;
330
+ el.poster = opts.poster ?? buildImageUrl(opts.playbackId, "thumbnail", opts.tokens?.thumbnail);
331
+ if (opts.autoPlay) el.autoplay = true;
332
+ el.playbackRate = state.rate;
333
+ if (opts.envKey) el.envKey = opts.envKey;
334
+ if (opts.metadata) el.metadata = {
335
+ video_id: opts.metadata.videoId,
336
+ video_title: opts.metadata.videoTitle,
337
+ viewer_user_id: opts.metadata.viewerUserId
338
+ };
339
+ for (const ev of MEDIA_EVENTS) el.addEventListener(ev, syncFromEl);
340
+ document.addEventListener("fullscreenchange", onFullscreenChange);
341
+ el.addEventListener("webkitbeginfullscreen", onWebkitBeginFullscreen);
342
+ el.addEventListener("webkitendfullscreen", onWebkitEndFullscreen);
343
+ el.addEventListener("enterpictureinpicture", onEnterPip);
344
+ el.addEventListener("leavepictureinpicture", onLeavePip);
345
+ container.appendChild(el);
346
+ el.textTracks?.addEventListener("addtrack", onTextTracksChanged);
347
+ el.textTracks?.addEventListener("removetrack", onTextTracksChanged);
348
+ el.textTracks?.addEventListener("change", onTextTracksChanged);
349
+ setupMediaSession();
350
+ },
351
+ swapSource(opts) {
352
+ if (!el) return;
353
+ if (opts.playbackId != null) el.playbackId = opts.tokens?.playback ? `${opts.playbackId}?token=${opts.tokens.playback}` : opts.playbackId;
354
+ if (opts.poster != null) el.poster = opts.poster;
355
+ else if (opts.playbackId != null) el.poster = buildImageUrl(opts.playbackId, "thumbnail", opts.tokens?.thumbnail);
356
+ if (opts.metadata) el.metadata = {
357
+ video_id: opts.metadata.videoId,
358
+ video_title: opts.metadata.videoTitle,
359
+ viewer_user_id: opts.metadata.viewerUserId
360
+ };
361
+ patch({
362
+ currentTime: 0,
363
+ duration: 0,
364
+ ended: false,
365
+ seeking: false,
366
+ error: null,
367
+ storyboard: opts.playbackId != null ? { vttUrl: buildImageUrl(opts.playbackId, "storyboard", opts.tokens?.storyboard) } : state.storyboard
368
+ });
369
+ },
370
+ getState: () => state,
371
+ subscribe: (l) => {
372
+ listeners.add(l);
373
+ return () => listeners.delete(l);
374
+ },
375
+ actions,
376
+ destroy() {
377
+ document.removeEventListener("fullscreenchange", onFullscreenChange);
378
+ teardownMediaSession();
379
+ if (activeTrack) activeTrack.removeEventListener("cuechange", onCueChange);
380
+ activeTrack = null;
381
+ if (renditionsBound) renditions?.removeEventListener("change", syncFromEl);
382
+ renditions = null;
383
+ renditionsBound = false;
384
+ if (el) {
385
+ for (const ev of MEDIA_EVENTS) el.removeEventListener(ev, syncFromEl);
386
+ el.removeEventListener("webkitbeginfullscreen", onWebkitBeginFullscreen);
387
+ el.removeEventListener("webkitendfullscreen", onWebkitEndFullscreen);
388
+ el.removeEventListener("enterpictureinpicture", onEnterPip);
389
+ el.removeEventListener("leavepictureinpicture", onLeavePip);
390
+ el.textTracks?.removeEventListener("addtrack", onTextTracksChanged);
391
+ el.textTracks?.removeEventListener("removetrack", onTextTracksChanged);
392
+ el.textTracks?.removeEventListener("change", onTextTracksChanged);
393
+ el.remove();
394
+ }
395
+ el = null;
396
+ listeners.clear();
397
+ }
398
+ };
399
+ }
400
+ //#endregion
401
+ //#region src/mux/mux-player.tsx
402
+ function MuxPlayer({ accentColor, theme, className, children, ...opts }) {
403
+ const providerRef = useRef(null);
404
+ if (providerRef.current === null) providerRef.current = createMuxProvider(opts);
405
+ const provider = providerRef.current;
406
+ const mountedRef = useRef(false);
407
+ useEffect(() => {
408
+ if (!mountedRef.current) {
409
+ mountedRef.current = true;
410
+ return;
411
+ }
412
+ provider.swapSource?.({
413
+ playbackId: opts.playbackId,
414
+ poster: opts.poster,
415
+ tokens: opts.tokens,
416
+ metadata: opts.metadata
417
+ });
418
+ }, [
419
+ opts.playbackId,
420
+ opts.tokens?.playback,
421
+ opts.tokens?.thumbnail,
422
+ opts.tokens?.storyboard,
423
+ opts.poster
424
+ ]);
425
+ return /* @__PURE__ */ jsxs(Player, {
426
+ provider,
427
+ accentColor,
428
+ theme,
429
+ className,
430
+ children: [
431
+ /* @__PURE__ */ jsx(IdleOverlay, {}),
432
+ /* @__PURE__ */ jsx(Captions, {}),
433
+ /* @__PURE__ */ jsx(ControlBar, {}),
434
+ children
435
+ ]
436
+ });
437
+ }
438
+ //#endregion
439
+ export { MuxPlayer, createMuxProvider };