@karnstack/kino 0.1.0 → 0.1.2
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 +10 -0
- package/dist/{control-bar-DWzMIb23.js → control-bar-DrAeqaap.js} +18 -8
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1 -1
- package/dist/mux.d.ts +3 -1
- package/dist/mux.js +9 -4
- package/dist/styles.css +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,6 +57,16 @@ Give the player a sized container. It fills `100%` width and height of its paren
|
|
|
57
57
|
|
|
58
58
|
kino is auth-agnostic. For signed playback you mint the `playback`, `thumbnail`, and `storyboard` tokens server-side and hand them to the player through the `tokens` prop. The player never holds a signing key and never talks to your auth layer; it only appends the tokens you give it to the media, thumbnail, and storyboard URLs. For public playback you can omit `tokens` entirely.
|
|
59
59
|
|
|
60
|
+
### Blur-up placeholder
|
|
61
|
+
|
|
62
|
+
Before the poster and first frame load, the video box is empty. Pass a small `placeholder` (a base64 data URI or a URL) and kino paints it behind the video as a blur-up; the sharp poster covers it once decoded, and it reappears briefly across source swaps.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<MuxPlayer playbackId="..." placeholder={blurDataUrl} />
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The poster itself stays the signed Mux thumbnail (kino derives it from `playbackId` + the `thumbnail` token), so `placeholder` is purely the instant low-res layer underneath.
|
|
69
|
+
|
|
60
70
|
## Theming
|
|
61
71
|
|
|
62
72
|
The quickest knob is the `accentColor` prop, which drives the scrubber fill, active menu items, and range controls.
|
|
@@ -403,7 +403,7 @@ function useIsCompact() {
|
|
|
403
403
|
return useContext(CompactContext);
|
|
404
404
|
}
|
|
405
405
|
const ControlsVisibilityContext = createContext(null);
|
|
406
|
-
function Player({ provider, accentColor, theme, className, children }) {
|
|
406
|
+
function Player({ provider, accentColor, theme, className, placeholder, children }) {
|
|
407
407
|
const wrapperRef = useRef(null);
|
|
408
408
|
const videoHostRef = useRef(null);
|
|
409
409
|
const hoveredRef = useRef(false);
|
|
@@ -496,13 +496,23 @@ function Player({ provider, accentColor, theme, className, children }) {
|
|
|
496
496
|
className: ["kino", className].filter(Boolean).join(" "),
|
|
497
497
|
style,
|
|
498
498
|
tabIndex: 0,
|
|
499
|
-
children: [
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
499
|
+
children: [
|
|
500
|
+
placeholder && /* @__PURE__ */ jsx("img", {
|
|
501
|
+
className: "kino-placeholder",
|
|
502
|
+
src: placeholder,
|
|
503
|
+
alt: "",
|
|
504
|
+
"aria-hidden": "true",
|
|
505
|
+
draggable: false
|
|
506
|
+
}),
|
|
507
|
+
/* @__PURE__ */ jsx("div", {
|
|
508
|
+
ref: videoHostRef,
|
|
509
|
+
className: "kino-video-host"
|
|
510
|
+
}),
|
|
511
|
+
/* @__PURE__ */ jsx(PlayerChrome, {
|
|
512
|
+
compact,
|
|
513
|
+
children
|
|
514
|
+
})
|
|
515
|
+
]
|
|
506
516
|
})
|
|
507
517
|
})
|
|
508
518
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ type PlayerProps = {
|
|
|
21
21
|
accentColor?: string;
|
|
22
22
|
theme?: Record<string, string>;
|
|
23
23
|
className?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Low-res still (data URI or URL) painted behind the video while the poster
|
|
26
|
+
* and first frame load — a blur-up. The sharp poster covers it once decoded,
|
|
27
|
+
* so it only shows during the initial load and across source swaps.
|
|
28
|
+
*/
|
|
29
|
+
placeholder?: string;
|
|
24
30
|
children?: ReactNode;
|
|
25
31
|
};
|
|
26
32
|
declare function Player({
|
|
@@ -28,6 +34,7 @@ declare function Player({
|
|
|
28
34
|
accentColor,
|
|
29
35
|
theme,
|
|
30
36
|
className,
|
|
37
|
+
placeholder,
|
|
31
38
|
children
|
|
32
39
|
}: PlayerProps): import("react").JSX.Element;
|
|
33
40
|
declare namespace Player {
|
package/dist/index.js
CHANGED
|
@@ -1,2 +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-
|
|
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-DrAeqaap.js";
|
|
2
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
CHANGED
|
@@ -24,13 +24,15 @@ declare function createMuxProvider(opts: MuxProviderOptions): Provider;
|
|
|
24
24
|
type MuxPlayerProps = MuxProviderOptions & {
|
|
25
25
|
accentColor?: string;
|
|
26
26
|
theme?: Record<string, string>;
|
|
27
|
-
className?: string;
|
|
27
|
+
className?: string; /** Blur-up still painted behind the video until the poster/first frame loads. */
|
|
28
|
+
placeholder?: string;
|
|
28
29
|
children?: ReactNode;
|
|
29
30
|
};
|
|
30
31
|
declare function MuxPlayer({
|
|
31
32
|
accentColor,
|
|
32
33
|
theme,
|
|
33
34
|
className,
|
|
35
|
+
placeholder,
|
|
34
36
|
children,
|
|
35
37
|
...opts
|
|
36
38
|
}: MuxPlayerProps): import("react").JSX.Element;
|
package/dist/mux.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as Captions, d as Player, l as IdleOverlay, t as ControlBar } from "./control-bar-
|
|
1
|
+
import { c as Captions, d as Player, l as IdleOverlay, t as ControlBar } from "./control-bar-DrAeqaap.js";
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
import "@mux/mux-video";
|
|
@@ -99,6 +99,7 @@ function createMuxProvider(opts) {
|
|
|
99
99
|
hasTextTracks: true
|
|
100
100
|
}
|
|
101
101
|
};
|
|
102
|
+
let desiredRate = opts.defaultRate ?? 1;
|
|
102
103
|
const listeners = /* @__PURE__ */ new Set();
|
|
103
104
|
const emit = () => listeners.forEach((l) => l());
|
|
104
105
|
const patch = (p) => {
|
|
@@ -193,6 +194,7 @@ function createMuxProvider(opts) {
|
|
|
193
194
|
const syncFromEl = () => {
|
|
194
195
|
if (!el) return;
|
|
195
196
|
bindRenditions();
|
|
197
|
+
if (el.playbackRate !== desiredRate) el.playbackRate = desiredRate;
|
|
196
198
|
applyTextTrackModes();
|
|
197
199
|
const ranges = [];
|
|
198
200
|
for (let i = 0; i < el.buffered.length; i++) ranges.push([el.buffered.start(i), el.buffered.end(i)]);
|
|
@@ -201,7 +203,7 @@ function createMuxProvider(opts) {
|
|
|
201
203
|
currentTime: el.currentTime,
|
|
202
204
|
duration: el.duration || 0,
|
|
203
205
|
buffered: ranges,
|
|
204
|
-
rate:
|
|
206
|
+
rate: desiredRate,
|
|
205
207
|
volume: el.volume,
|
|
206
208
|
muted: el.muted,
|
|
207
209
|
readyState: el.readyState,
|
|
@@ -275,7 +277,9 @@ function createMuxProvider(opts) {
|
|
|
275
277
|
if (el) el.currentTime = t;
|
|
276
278
|
},
|
|
277
279
|
setRate: (r) => {
|
|
280
|
+
desiredRate = r;
|
|
278
281
|
if (el) el.playbackRate = r;
|
|
282
|
+
patch({ rate: r });
|
|
279
283
|
},
|
|
280
284
|
setVolume: (v) => {
|
|
281
285
|
if (el) el.volume = Math.min(1, Math.max(0, v));
|
|
@@ -329,7 +333,7 @@ function createMuxProvider(opts) {
|
|
|
329
333
|
el.playsInline = true;
|
|
330
334
|
el.poster = opts.poster ?? buildImageUrl(opts.playbackId, "thumbnail", opts.tokens?.thumbnail);
|
|
331
335
|
if (opts.autoPlay) el.autoplay = true;
|
|
332
|
-
el.playbackRate =
|
|
336
|
+
el.playbackRate = desiredRate;
|
|
333
337
|
if (opts.envKey) el.envKey = opts.envKey;
|
|
334
338
|
if (opts.metadata) el.metadata = {
|
|
335
339
|
video_id: opts.metadata.videoId,
|
|
@@ -399,7 +403,7 @@ function createMuxProvider(opts) {
|
|
|
399
403
|
}
|
|
400
404
|
//#endregion
|
|
401
405
|
//#region src/mux/mux-player.tsx
|
|
402
|
-
function MuxPlayer({ accentColor, theme, className, children, ...opts }) {
|
|
406
|
+
function MuxPlayer({ accentColor, theme, className, placeholder, children, ...opts }) {
|
|
403
407
|
const providerRef = useRef(null);
|
|
404
408
|
if (providerRef.current === null) providerRef.current = createMuxProvider(opts);
|
|
405
409
|
const provider = providerRef.current;
|
|
@@ -427,6 +431,7 @@ function MuxPlayer({ accentColor, theme, className, children, ...opts }) {
|
|
|
427
431
|
accentColor,
|
|
428
432
|
theme,
|
|
429
433
|
className,
|
|
434
|
+
placeholder,
|
|
430
435
|
children: [
|
|
431
436
|
/* @__PURE__ */ jsx(IdleOverlay, {}),
|
|
432
437
|
/* @__PURE__ */ jsx(Captions, {}),
|
package/dist/styles.css
CHANGED
|
@@ -34,6 +34,18 @@
|
|
|
34
34
|
width: 100%;
|
|
35
35
|
height: 100%;
|
|
36
36
|
}
|
|
37
|
+
.kino .kino-placeholder {
|
|
38
|
+
position: absolute;
|
|
39
|
+
inset: 0;
|
|
40
|
+
width: 100%;
|
|
41
|
+
height: 100%;
|
|
42
|
+
object-fit: cover;
|
|
43
|
+
/* Same level as the video host but earlier in the DOM, so the video element
|
|
44
|
+
(and its sharp poster, once decoded) paints over this blur-up. */
|
|
45
|
+
z-index: 0;
|
|
46
|
+
pointer-events: none;
|
|
47
|
+
user-select: none;
|
|
48
|
+
}
|
|
37
49
|
.kino .kino-video-host {
|
|
38
50
|
position: absolute;
|
|
39
51
|
inset: 0;
|