|
| 1 | +# @karnstack/kino |
| 2 | + |
| 3 | +A premium, themeable React video player with a pluggable-provider architecture. Translucent glass / macOS-style chrome, keyboard-first controls, and a small typed surface. Mux is the first provider. |
| 4 | + |
| 5 | +kino ships the player UI and a provider contract. Each provider adapts a streaming engine to that contract, so the same glass chrome can sit on top of different backends. The Mux provider is built on the `@mux/mux-video` custom element. |
| 6 | + |
| 7 | +## Install |
| 8 | + |
| 9 | +```sh |
| 10 | +pnpm add @karnstack/kino |
| 11 | +``` |
| 12 | + |
| 13 | +The Mux engine (`@mux/mux-video`) is pulled in transitively, so you do not install it yourself. React 19 is a peer dependency (`react` and `react-dom` `>=19`). |
| 14 | + |
| 15 | +## Quick start |
| 16 | + |
| 17 | +```tsx |
| 18 | +import { MuxPlayer } from "@karnstack/kino/mux" |
| 19 | +import "@karnstack/kino/styles.css" |
| 20 | + |
| 21 | +export function Clip() { |
| 22 | + return ( |
| 23 | + <MuxPlayer |
| 24 | + playbackId="your-playback-id" |
| 25 | + tokens={{ playback, thumbnail, storyboard }} |
| 26 | + accentColor="oklch(50.8% 0.118 165.612)" |
| 27 | + /> |
| 28 | + ) |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +Give the player a sized container. It fills `100%` width and height of its parent, so wrap it in an element with the aspect ratio or dimensions you want. |
| 33 | + |
| 34 | +### Tokens are passed in |
| 35 | + |
| 36 | +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. |
| 37 | + |
| 38 | +## Theming |
| 39 | + |
| 40 | +The quickest knob is the `accentColor` prop, which drives the scrubber fill, active menu items, and range controls. |
| 41 | + |
| 42 | +```tsx |
| 43 | +<MuxPlayer playbackId="..." accentColor="oklch(50.8% 0.118 165.612)" /> |
| 44 | +``` |
| 45 | + |
| 46 | +For deeper control, every visual is driven by CSS custom properties on the `.kino` root. Override them in your own stylesheet, or pass a `theme` object of property/value pairs to set them inline. |
| 47 | + |
| 48 | +| Custom property | Default | Role | |
| 49 | +| --- | --- | --- | |
| 50 | +| `--kino-accent` | `oklch(50.8% 0.118 165.612)` | Accent color (progress, active items, ranges) | |
| 51 | +| `--kino-radius` | `12px` | Corner radius of glass surfaces | |
| 52 | +| `--kino-surface` | `color-mix(in oklab, black 55%, transparent)` | Glass surface fill | |
| 53 | +| `--kino-surface-strong` | `color-mix(in oklab, black 70%, transparent)` | Stronger surface (idle play button) | |
| 54 | +| `--kino-border` | `color-mix(in oklab, white 14%, transparent)` | Hairline borders | |
| 55 | +| `--kino-text` | `oklch(98% 0 0)` | Primary text and icons | |
| 56 | +| `--kino-text-dim` | `color-mix(in oklab, white 65%, transparent)` | Secondary text (timecode) | |
| 57 | +| `--kino-blur` | `18px` | Backdrop blur radius | |
| 58 | +| `--kino-shadow` | `0 8px 40px rgba(0, 0, 0, 0.45)` | Surface drop shadow | |
| 59 | +| `--kino-ease` | `cubic-bezier(0.22, 1, 0.36, 1)` | Shared transition easing | |
| 60 | + |
| 61 | +```css |
| 62 | +.kino { |
| 63 | + --kino-accent: oklch(70% 0.15 250); |
| 64 | + --kino-radius: 16px; |
| 65 | + --kino-blur: 24px; |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +## Keyboard shortcuts |
| 70 | + |
| 71 | +The player is keyboard-first. Shortcuts are ignored while a text input, textarea, select, or contenteditable element is focused, and modifier-key combinations (Ctrl/Cmd/Alt) are passed through. |
| 72 | + |
| 73 | +| Key | Action | |
| 74 | +| --- | --- | |
| 75 | +| `Space` / `K` | Play / pause | |
| 76 | +| `<` / `>` | Decrease / increase playback rate (0.25 step) | |
| 77 | +| `M` | Toggle mute | |
| 78 | +| `C` | Toggle captions | |
| 79 | +| `S` | Open the speed menu | |
| 80 | +| `F` | Toggle fullscreen | |
| 81 | +| `0`-`9` | Seek to 0%-90% of the duration | |
| 82 | + |
| 83 | +## Capability gating |
| 84 | + |
| 85 | +Controls hide themselves when the active provider or platform cannot support them, rather than presenting a dead button. The provider reports a capability set, and each control checks it: |
| 86 | + |
| 87 | +- Quality switching is hidden when the engine exposes no renditions, and is off on iOS where the system owns adaptive playback. |
| 88 | +- Custom-chrome fullscreen is off on iOS (the platform uses its native fullscreen for the underlying video element). |
| 89 | +- Picture-in-picture is hidden when the browser does not support it. |
| 90 | +- The captions menu appears only when the media actually carries subtitle or caption tracks. |
| 91 | + |
| 92 | +## Local development |
| 93 | + |
| 94 | +```sh |
| 95 | +pnpm install |
| 96 | +pnpm dev # demo harness at http://localhost:5173 |
| 97 | +pnpm test # vitest |
| 98 | +pnpm build # bundle to dist/ |
| 99 | +pnpm typecheck # tsc --noEmit |
| 100 | +pnpm lint # eslint |
| 101 | +``` |
| 102 | + |
| 103 | +`pnpm dev` runs the demo harness, which plays a public sample clip through a small demo file provider. No Mux account or signed tokens are needed to see the real glass UI. To exercise the Mux provider in the demo, set `VITE_MUX_PLAYBACK_ID` (and optionally the matching token env vars) before running `pnpm dev`. |
| 104 | + |
| 105 | +## Roadmap |
| 106 | + |
| 107 | +- More providers: YouTube, file, and Vimeo |
| 108 | +- AirPlay support |
| 109 | +- Chapters |
| 110 | +- Documented headless primitives for fully custom chrome |
| 111 | + |
| 112 | +## License |
| 113 | + |
| 114 | +MIT |
0 commit comments