@smoove/player 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 shemi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @smoove/player
2
+
3
+ A framework-agnostic web component that plays a
4
+ [smoove](https://smoove.dev) `Composition` like an HTML5 `<video>`.
5
+
6
+ `<smoove-player>` letterbox-scales the stage to its box and adds fullscreen,
7
+ click and keyboard control, an imperative API, and a set of DOM events. Use
8
+ it from plain HTML, React, Vue, Svelte, or anything that can put an element
9
+ on a page.
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ pnpm add konva @smoove/core @smoove/player
15
+ ```
16
+
17
+ `konva` and `@smoove/core` are peer dependencies (`lit` and `@lit/context`
18
+ are bundled). Import the package once to register the elements, then opt
19
+ into the default styling:
20
+
21
+ ```ts
22
+ import "@smoove/player"; // registers <smoove-player> and the controls
23
+ import "@smoove/player/styles.css"; // opt-in default styling
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ Hand the player a composition in one of two ways.
29
+
30
+ **Assign it imperatively** — set the `composition` property to a live
31
+ `Composition`; the player owns the canvas and mounts the stage itself:
32
+
33
+ ```html
34
+ <smoove-player controls loop style="width: 640px; aspect-ratio: 16/9"></smoove-player>
35
+ ```
36
+
37
+ ```ts
38
+ document.querySelector("smoove-player").composition = comp;
39
+ ```
40
+
41
+ **Point `src` at a module** — like `<video src>`, the player `import()`s
42
+ the URL and resolves its default export (a `Composition`, or a sync/async
43
+ factory returning one):
44
+
45
+ ```html
46
+ <smoove-player src="https://cdn.example/orbit.js" controls loop></smoove-player>
47
+ ```
48
+
49
+ ## No bundler? Use the CDN build
50
+
51
+ A self-contained build bundles Konva and the core authoring API, registers
52
+ every element, and exposes `window.Smoove`:
53
+
54
+ ```html
55
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@smoove/player/dist/player.global.js"></script>
56
+ ```
57
+
58
+ The same file is published on unpkg and through the
59
+ `@smoove/player/standalone` export.
60
+
61
+ ## Docs
62
+
63
+ Full documentation lives at [smoove.dev](https://smoove.dev).
64
+
65
+ ## License
66
+
67
+ MIT
package/dist/base.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { LitElement } from 'lit';
2
+ import { PlayerApi } from './player-api.js';
3
+ import { ReadonlySignal } from './signal.js';
4
+ /**
5
+ * Base class for **leaf** controls (play button, time, volume, …). Renders its
6
+ * own markup into light DOM (it has no user children, so rendering into `this`
7
+ * is safe and lets users style with plain selectors). Resolves the ancestor
8
+ * `<smoove-player>` on connect and offers {@link watch} to re-render when a player
9
+ * signal changes.
10
+ */
11
+ export declare abstract class SmooveControl extends LitElement {
12
+ /** The resolved player, or `null` if used outside a `<smoove-player>`. */
13
+ protected api: PlayerApi | null;
14
+ private _unsubs;
15
+ protected createRenderRoot(): HTMLElement;
16
+ connectedCallback(): void;
17
+ disconnectedCallback(): void;
18
+ /** Subscribe to a player signal and re-render this control on every change. */
19
+ protected watch<T>(sig: ReadonlySignal<T>): void;
20
+ /**
21
+ * Declare reactive subscriptions here using {@link watch}. Called once on
22
+ * connect with the resolved {@link PlayerApi}.
23
+ */
24
+ protected bind(_api: PlayerApi): void;
25
+ }
26
+ /**
27
+ * Base class for **layout containers** (overlay, controls, rows, spacer). These
28
+ * wrap user-authored children, so they must never let a framework re-render
29
+ * wipe that content — they are bare custom elements that only exist for
30
+ * semantics + CSS targeting. All styling lives in the opt-in stylesheet.
31
+ */
32
+ export declare class SmooveContainer extends HTMLElement {
33
+ }
@@ -0,0 +1,18 @@
1
+ import { SmooveContainer } from './base.js';
2
+ /**
3
+ * Layout containers. Each is a bare custom element that preserves its
4
+ * user-authored children and is styled entirely by the opt-in stylesheet.
5
+ *
6
+ * - `<smoove-player-overlay>` — centered overlay layer above the video.
7
+ * - `<smoove-player-controls>` — the control bar (one or more rows).
8
+ * - `<smoove-player-controls-row>` — a flex row of controls.
9
+ * - `<smoove-player-space grow>` — a spacer; `grow` makes it consume free space.
10
+ */
11
+ export declare class SmoovePlayerOverlay extends SmooveContainer {
12
+ }
13
+ export declare class SmoovePlayerControls extends SmooveContainer {
14
+ }
15
+ export declare class SmoovePlayerControlsRow extends SmooveContainer {
16
+ }
17
+ export declare class SmoovePlayerSpace extends SmooveContainer {
18
+ }
@@ -0,0 +1,14 @@
1
+ import { PlayerApi } from './player-api.js';
2
+ /**
3
+ * Context token carrying the {@link PlayerApi}. `<smoove-player>` is the provider;
4
+ * descendant controls may consume it with `@lit/context`'s `ContextConsumer`.
5
+ * In light DOM the underlying `context-request` event bubbles through ordinary
6
+ * DOM ancestors, so no shadow boundary is needed. Controls in this package use
7
+ * the simpler {@link getPlayerApi} (`closest`) lookup, which also works when a
8
+ * control is used without a context consumer.
9
+ */
10
+ export declare const playerContext: {
11
+ __context__: PlayerApi;
12
+ };
13
+ /** Resolve the nearest ancestor `<smoove-player>` as a {@link PlayerApi}. */
14
+ export declare function getPlayerApi(el: Element): PlayerApi | null;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Build the default control bar used when `<smoove-player controls>` has no
3
+ * user-supplied `<smoove-player-controls>`. It is composed from the same public
4
+ * sub-components, so it inherits the player context and the opt-in styling.
5
+ * Tagged `data-smoove-default` so the player can swap it out if the user later
6
+ * provides their own controls.
7
+ */
8
+ export declare function createDefaultControls(): HTMLElement;
@@ -0,0 +1,75 @@
1
+ import { Composition } from '@smoove/core';
2
+ /**
3
+ * Detail payloads for the DOM `CustomEvent`s dispatched by `<smoove-player>`.
4
+ * All events bubble and are composed. Subscribe with
5
+ * `player.addEventListener("frameupdate", (e) => e.detail.frame)`.
6
+ */
7
+ /** `frameupdate` — fires on every frame change (playback + seeking). */
8
+ export interface FrameUpdateDetail {
9
+ frame: number;
10
+ }
11
+ /** `timeupdate` — throttled (~4/s) periodic update during playback. */
12
+ export interface TimeUpdateDetail {
13
+ frame: number;
14
+ time: number;
15
+ durationInFrames: number;
16
+ durationInSeconds: number;
17
+ }
18
+ /** `seeked` — an explicit seek (seek bar, `seekTo`, arrow keys). */
19
+ export interface SeekedDetail {
20
+ frame: number;
21
+ }
22
+ /** `play` / `pause` / `ended`. */
23
+ export interface PlayPauseDetail {
24
+ frame: number;
25
+ }
26
+ /** `ratechange` — playback speed changed. */
27
+ export interface RateChangeDetail {
28
+ playbackRate: number;
29
+ }
30
+ /** `volumechange` — volume changed. */
31
+ export interface VolumeChangeDetail {
32
+ volume: number;
33
+ }
34
+ /** `mutechange` — mute state toggled. */
35
+ export interface MuteChangeDetail {
36
+ muted: boolean;
37
+ }
38
+ /** `fullscreenchange` — entered/exited fullscreen. */
39
+ export interface FullscreenChangeDetail {
40
+ isFullscreen: boolean;
41
+ }
42
+ /** `scalechange` — the letterbox scale factor changed. */
43
+ export interface ScaleChangeDetail {
44
+ scale: number;
45
+ }
46
+ /** `error` — an exception thrown while mounting, starting playback, or loading `src`. */
47
+ export interface PlayerErrorDetail {
48
+ error: unknown;
49
+ }
50
+ /** `loadstart` — a remote `src` load began. */
51
+ export interface LoadStartDetail {
52
+ src: string;
53
+ }
54
+ /** `loaded` — a remote `src` resolved and its composition was mounted. */
55
+ export interface LoadedDetail {
56
+ src: string;
57
+ composition: Composition;
58
+ }
59
+ /** Maps each event name to its `CustomEvent` detail type. */
60
+ export interface SmoovePlayerEventMap {
61
+ play: CustomEvent<PlayPauseDetail>;
62
+ pause: CustomEvent<PlayPauseDetail>;
63
+ ended: CustomEvent<PlayPauseDetail>;
64
+ seeked: CustomEvent<SeekedDetail>;
65
+ frameupdate: CustomEvent<FrameUpdateDetail>;
66
+ timeupdate: CustomEvent<TimeUpdateDetail>;
67
+ ratechange: CustomEvent<RateChangeDetail>;
68
+ volumechange: CustomEvent<VolumeChangeDetail>;
69
+ mutechange: CustomEvent<MuteChangeDetail>;
70
+ fullscreenchange: CustomEvent<FullscreenChangeDetail>;
71
+ scalechange: CustomEvent<ScaleChangeDetail>;
72
+ error: CustomEvent<PlayerErrorDetail>;
73
+ loadstart: CustomEvent<LoadStartDetail>;
74
+ loaded: CustomEvent<LoadedDetail>;
75
+ }
@@ -0,0 +1,8 @@
1
+ import { TemplateResult } from 'lit';
2
+ import { SmooveControl } from './base.js';
3
+ import { PlayerApi } from './player-api.js';
4
+ /** Toggles the player in and out of fullscreen. */
5
+ export declare class SmoovePlayerFullscreenButton extends SmooveControl {
6
+ protected bind(api: PlayerApi): void;
7
+ protected render(): TemplateResult;
8
+ }
@@ -0,0 +1,10 @@
1
+ import { TemplateResult } from 'lit';
2
+ /**
3
+ * Inline-SVG icon set, ported from the demo studio's icon sheet. Each entry is
4
+ * the inner markup of an 18×18 viewBox; {@link icon} wraps it in an `<svg>`.
5
+ * No icon dependency — controls render `${icon("play")}` directly.
6
+ */
7
+ declare const PATHS: Record<string, TemplateResult>;
8
+ /** Render a named icon as an `<svg>` of the given pixel size. */
9
+ export declare function icon(name: keyof typeof PATHS | string, size?: number): TemplateResult;
10
+ export {};
@@ -0,0 +1,13 @@
1
+ export { SmoovePlayerControls, SmoovePlayerControlsRow, SmoovePlayerOverlay, SmoovePlayerSpace, } from './containers.js';
2
+ export { getPlayerApi, playerContext } from './context.js';
3
+ export { createDefaultControls } from './default-controls.js';
4
+ export type { FrameUpdateDetail, FullscreenChangeDetail, LoadedDetail, LoadStartDetail, MuteChangeDetail, PlayerErrorDetail, PlayPauseDetail, RateChangeDetail, ScaleChangeDetail, SeekedDetail, SmoovePlayerEventMap, TimeUpdateDetail, VolumeChangeDetail, } from './events.js';
5
+ export { SmoovePlayerFullscreenButton } from './fullscreen-button.js';
6
+ export { SmoovePlayerLoopButton } from './loop-button.js';
7
+ export { SmoovePlayerPlayButton } from './play-button.js';
8
+ export { SmoovePlayerPlayToggleButton } from './play-toggle-button.js';
9
+ export type { PlayerApi, PlayerState } from './player-api.js';
10
+ export { SmoovePlayerProgress } from './progress.js';
11
+ export { SmoovePlayer } from './smoove-player.js';
12
+ export { SmoovePlayerSoundControl } from './sound-control.js';
13
+ export { SmoovePlayerTime } from './time.js';
@@ -0,0 +1,16 @@
1
+ import { PropertyValues, TemplateResult } from 'lit';
2
+ import { SmooveControl } from './base.js';
3
+ import { PlayerApi } from './player-api.js';
4
+ /** Toggles loop playback. Reflects an `on` attribute when looping. */
5
+ export declare class SmoovePlayerLoopButton extends SmooveControl {
6
+ static properties: {
7
+ on: {
8
+ type: BooleanConstructor;
9
+ reflect: boolean;
10
+ };
11
+ };
12
+ on?: boolean;
13
+ protected bind(api: PlayerApi): void;
14
+ protected willUpdate(_changed: PropertyValues): void;
15
+ protected render(): TemplateResult;
16
+ }
@@ -0,0 +1 @@
1
+ export declare function createRequire(_url: string | URL): (id: string) => unknown;
@@ -0,0 +1,25 @@
1
+ import { PropertyValues, TemplateResult } from 'lit';
2
+ import { SmooveControl } from './base.js';
3
+ import { PlayerApi } from './player-api.js';
4
+ /**
5
+ * A large, centered play affordance (for use inside `<smoove-player-overlay>`).
6
+ * `size` is `small | medium | large`. Reflects a `playing` attribute so the
7
+ * stylesheet can fade it out during playback.
8
+ */
9
+ export declare class SmoovePlayerPlayButton extends SmooveControl {
10
+ static properties: {
11
+ size: {
12
+ type: StringConstructor;
13
+ reflect: boolean;
14
+ };
15
+ playing: {
16
+ type: BooleanConstructor;
17
+ reflect: boolean;
18
+ };
19
+ };
20
+ size?: "small" | "medium" | "large";
21
+ playing?: boolean;
22
+ protected bind(api: PlayerApi): void;
23
+ protected willUpdate(_changed: PropertyValues): void;
24
+ protected render(): TemplateResult;
25
+ }
@@ -0,0 +1,8 @@
1
+ import { TemplateResult } from 'lit';
2
+ import { SmooveControl } from './base.js';
3
+ import { PlayerApi } from './player-api.js';
4
+ /** A play/pause toggle button reflecting and driving playback state. */
5
+ export declare class SmoovePlayerPlayToggleButton extends SmooveControl {
6
+ protected bind(api: PlayerApi): void;
7
+ protected render(): TemplateResult;
8
+ }
@@ -0,0 +1,63 @@
1
+ import { Composition } from '@smoove/core';
2
+ import { ReadonlySignal } from './signal.js';
3
+ /**
4
+ * The reactive state a `<smoove-player>` exposes to its descendant control
5
+ * components. These are **stable** signals owned by the player — they survive
6
+ * the `Composition` being swapped, so a control subscribes once on connect.
7
+ * Composition-derived values mirror the live comp; `fullscreen` and `scale`
8
+ * are player-owned (not part of core).
9
+ */
10
+ export interface PlayerState {
11
+ readonly frame: ReadonlySignal<number>;
12
+ readonly playing: ReadonlySignal<boolean>;
13
+ readonly duration: ReadonlySignal<number>;
14
+ readonly loop: ReadonlySignal<boolean>;
15
+ readonly rate: ReadonlySignal<number>;
16
+ readonly volume: ReadonlySignal<number>;
17
+ readonly muted: ReadonlySignal<boolean>;
18
+ readonly fullscreen: ReadonlySignal<boolean>;
19
+ readonly scale: ReadonlySignal<number>;
20
+ }
21
+ /**
22
+ * The control surface a `<smoove-player>` provides to its descendants and to
23
+ * imperative consumers. `<smoove-player>` itself implements this interface, so
24
+ * `el.closest("smoove-player")` (or the `playerContext`) yields a `PlayerApi`.
25
+ * Method names mirror Remotion's `PlayerRef`.
26
+ */
27
+ export interface PlayerApi {
28
+ /** The mounted composition, or `null` before one is assigned. */
29
+ readonly composition: Composition | null;
30
+ /** The composition's frame rate (0 until a composition is mounted). */
31
+ readonly fps: number;
32
+ /** Stable reactive state for descendant controls to subscribe to. */
33
+ readonly state: PlayerState;
34
+ play(): void;
35
+ pause(): void;
36
+ toggle(): void;
37
+ stop(): void;
38
+ seekTo(frame: number): void;
39
+ /** Step the playhead by `delta` frames (negative steps backward). */
40
+ stepBy(delta: number): void;
41
+ getCurrentFrame(): number;
42
+ isPlaying(): boolean;
43
+ /** Push new props onto the mounted composition; it re-renders the current
44
+ frame automatically. No-op before a composition is assigned. */
45
+ setProps(props: Record<string, unknown>): void;
46
+ setVolume(volume: number): void;
47
+ getVolume(): number;
48
+ mute(): void;
49
+ unmute(): void;
50
+ setMuted(muted: boolean): void;
51
+ toggleMute(): void;
52
+ isMuted(): boolean;
53
+ setLoop(loop: boolean): void;
54
+ toggleLoop(): void;
55
+ isLooping(): boolean;
56
+ setPlaybackRate(rate: number): void;
57
+ getPlaybackRate(): number;
58
+ requestFullscreen(): Promise<void>;
59
+ exitFullscreen(): Promise<void>;
60
+ toggleFullscreen(): void;
61
+ isFullscreen(): boolean;
62
+ getScale(): number;
63
+ }
@@ -0,0 +1,2 @@
1
+ smoove-player{-webkit-user-select:none;user-select:none;background:#000;outline:none;width:100%;display:block;position:relative;overflow:hidden}:is(smoove-player:fullscreen,smoove-player[fullscreen]){width:100vw;height:100vh}.smoove-player__stage{position:absolute;inset:0;overflow:hidden}.smoove-player__scale{transform-origin:0 0;position:absolute;top:0;left:0}.smoove-player__canvas{width:100%;height:100%}.smoove-player__canvas canvas{display:block}smoove-player-overlay{z-index:1;pointer-events:none;place-items:center;display:grid;position:absolute;inset:0}smoove-player-overlay>*{pointer-events:auto}.smoove-player__overlay-play{color:#fff;cursor:pointer;background:#00000073;border:none;border-radius:999px;place-items:center;padding:16px;transition:opacity .2s,background .12s,transform .12s;display:grid}.smoove-player__overlay-play:hover{background:#0000009e;transform:scale(1.05)}smoove-player-play-button{transition:opacity .2s}smoove-player-play-button[playing]{opacity:0;pointer-events:none}smoove-player-controls{z-index:2;background:linear-gradient(#0000,#000000a6);flex-direction:column;gap:2px;padding:4px 10px 8px;font-family:inherit;display:flex;position:absolute;bottom:0;left:0;right:0}smoove-player-controls-row{align-items:center;gap:4px;display:flex}smoove-player-space{display:block}smoove-player-space[grow]{flex:1}.smoove-player__btn{color:#fff;cursor:pointer;background:0 0;border:none;border-radius:8px;place-items:center;width:34px;height:34px;transition:background .12s,color .12s;display:grid}.smoove-player__btn:hover{background:#ffffff29}smoove-player-loop-button[on] .smoove-player__btn{color:#15cda8}.smoove-player__sound{align-items:center;display:flex}.smoove-player__volume{appearance:none;background:#ffffff47;border-radius:99px;outline:none;width:74px;height:4px;margin:0 4px;transition:width .2s,opacity .2s,margin .2s}.smoove-player__volume::-webkit-slider-thumb{-webkit-appearance:none;cursor:pointer;background:#fff;border-radius:50%;width:12px;height:12px}.smoove-player__volume::-moz-range-thumb{cursor:pointer;background:#fff;border:none;border-radius:50%;width:12px;height:12px}smoove-player-sound-control[collapsed] .smoove-player__volume{opacity:0;width:0;margin:0}smoove-player-sound-control[collapsed]:hover .smoove-player__volume,smoove-player-sound-control[collapsed]:focus-within .smoove-player__volume{opacity:1;width:74px;margin:0 4px}.smoove-player__time{color:#fff;white-space:nowrap;align-items:center;gap:3px;padding:0 6px;font:12.5px/1 JetBrains Mono,ui-monospace,monospace;display:inline-flex}.smoove-player__time-sep,.smoove-player__time-dur{opacity:.7}smoove-player-progress{width:100%;display:block}.smoove-player__progress{cursor:pointer;width:100%;padding:6px 0}.smoove-player__track{background:#ffffff42;border-radius:2px;height:4px;transition:height .12s;position:relative}.smoove-player__progress:hover .smoove-player__track,.smoove-player__progress.is-dragging .smoove-player__track{height:6px}.smoove-player__fill{background:linear-gradient(90deg,#ff5640,#15cda8);border-radius:2px;position:absolute;top:0;bottom:0;left:0}.smoove-player__knob{background:#fff;border-radius:50%;width:13px;height:13px;transition:transform .12s;position:absolute;top:50%;transform:translate(-50%,-50%)scale(0);box-shadow:0 1px 4px #00000080}.smoove-player__progress:hover .smoove-player__knob,.smoove-player__progress.is-dragging .smoove-player__knob{transform:translate(-50%,-50%)scale(1)}
2
+ /*$vite$:1*/