@karnstack/kino 0.1.0 → 0.1.1

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
@@ -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: [/* @__PURE__ */ jsx("div", {
500
- ref: videoHostRef,
501
- className: "kino-video-host"
502
- }), /* @__PURE__ */ jsx(PlayerChrome, {
503
- compact,
504
- children
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-DWzMIb23.js";
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-DWzMIb23.js";
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";
@@ -275,7 +275,9 @@ function createMuxProvider(opts) {
275
275
  if (el) el.currentTime = t;
276
276
  },
277
277
  setRate: (r) => {
278
- if (el) el.playbackRate = r;
278
+ if (!el) return;
279
+ el.playbackRate = r;
280
+ el.defaultPlaybackRate = r;
279
281
  },
280
282
  setVolume: (v) => {
281
283
  if (el) el.volume = Math.min(1, Math.max(0, v));
@@ -330,6 +332,7 @@ function createMuxProvider(opts) {
330
332
  el.poster = opts.poster ?? buildImageUrl(opts.playbackId, "thumbnail", opts.tokens?.thumbnail);
331
333
  if (opts.autoPlay) el.autoplay = true;
332
334
  el.playbackRate = state.rate;
335
+ el.defaultPlaybackRate = state.rate;
333
336
  if (opts.envKey) el.envKey = opts.envKey;
334
337
  if (opts.metadata) el.metadata = {
335
338
  video_id: opts.metadata.videoId,
@@ -399,7 +402,7 @@ function createMuxProvider(opts) {
399
402
  }
400
403
  //#endregion
401
404
  //#region src/mux/mux-player.tsx
402
- function MuxPlayer({ accentColor, theme, className, children, ...opts }) {
405
+ function MuxPlayer({ accentColor, theme, className, placeholder, children, ...opts }) {
403
406
  const providerRef = useRef(null);
404
407
  if (providerRef.current === null) providerRef.current = createMuxProvider(opts);
405
408
  const provider = providerRef.current;
@@ -427,6 +430,7 @@ function MuxPlayer({ accentColor, theme, className, children, ...opts }) {
427
430
  accentColor,
428
431
  theme,
429
432
  className,
433
+ placeholder,
430
434
  children: [
431
435
  /* @__PURE__ */ jsx(IdleOverlay, {}),
432
436
  /* @__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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karnstack/kino",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Themeable React video player with pluggable providers.",
5
5
  "license": "MIT",
6
6
  "author": "Karn",