@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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"player.js","names":[],"sources":["../src/context.ts","../src/base.ts","../src/containers.ts","../src/progress.ts","../src/icons.ts","../src/play-toggle-button.ts","../src/sound-control.ts","../src/time.ts","../src/loop-button.ts","../src/fullscreen-button.ts","../src/default-controls.ts","../src/signal.ts","../src/smoove-player.ts","../src/play-button.ts"],"sourcesContent":["import { createContext } from \"@lit/context\";\nimport type { PlayerApi } from \"./player-api.js\";\n\n/**\n * Context token carrying the {@link PlayerApi}. `<smoove-player>` is the provider;\n * descendant controls may consume it with `@lit/context`'s `ContextConsumer`.\n * In light DOM the underlying `context-request` event bubbles through ordinary\n * DOM ancestors, so no shadow boundary is needed. Controls in this package use\n * the simpler {@link getPlayerApi} (`closest`) lookup, which also works when a\n * control is used without a context consumer.\n */\nexport const playerContext = createContext<PlayerApi>(Symbol(\"smoove-player\"));\n\n/** Resolve the nearest ancestor `<smoove-player>` as a {@link PlayerApi}. */\nexport function getPlayerApi(el: Element): PlayerApi | null {\n return el.closest(\"smoove-player\") as unknown as PlayerApi | null;\n}\n","import { LitElement } from \"lit\";\nimport { getPlayerApi } from \"./context.js\";\nimport type { PlayerApi } from \"./player-api.js\";\nimport type { ReadonlySignal } from \"./signal.js\";\n\n/**\n * Base class for **leaf** controls (play button, time, volume, …). Renders its\n * own markup into light DOM (it has no user children, so rendering into `this`\n * is safe and lets users style with plain selectors). Resolves the ancestor\n * `<smoove-player>` on connect and offers {@link watch} to re-render when a player\n * signal changes.\n */\nexport abstract class SmooveControl extends LitElement {\n /** The resolved player, or `null` if used outside a `<smoove-player>`. */\n protected api: PlayerApi | null = null;\n private _unsubs: Array<() => void> = [];\n\n // Light DOM — no shadow root, so all styling is overridable with plain CSS.\n protected override createRenderRoot(): HTMLElement {\n return this;\n }\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.api = getPlayerApi(this);\n if (this.api) this.bind(this.api);\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n for (const u of this._unsubs) u();\n this._unsubs = [];\n this.api = null;\n }\n\n /** Subscribe to a player signal and re-render this control on every change. */\n protected watch<T>(sig: ReadonlySignal<T>): void {\n this._unsubs.push(sig.subscribe(() => this.requestUpdate()));\n }\n\n /**\n * Declare reactive subscriptions here using {@link watch}. Called once on\n * connect with the resolved {@link PlayerApi}.\n */\n protected bind(_api: PlayerApi): void {}\n}\n\n/**\n * Base class for **layout containers** (overlay, controls, rows, spacer). These\n * wrap user-authored children, so they must never let a framework re-render\n * wipe that content — they are bare custom elements that only exist for\n * semantics + CSS targeting. All styling lives in the opt-in stylesheet.\n */\nexport class SmooveContainer extends HTMLElement {}\n","import { SmooveContainer } from \"./base.js\";\n\n/**\n * Layout containers. Each is a bare custom element that preserves its\n * user-authored children and is styled entirely by the opt-in stylesheet.\n *\n * - `<smoove-player-overlay>` — centered overlay layer above the video.\n * - `<smoove-player-controls>` — the control bar (one or more rows).\n * - `<smoove-player-controls-row>` — a flex row of controls.\n * - `<smoove-player-space grow>` — a spacer; `grow` makes it consume free space.\n */\nexport class SmoovePlayerOverlay extends SmooveContainer {}\nexport class SmoovePlayerControls extends SmooveContainer {}\nexport class SmoovePlayerControlsRow extends SmooveContainer {}\nexport class SmoovePlayerSpace extends SmooveContainer {}\n\nconst REGISTRY: Array<[string, CustomElementConstructor]> = [\n [\"smoove-player-overlay\", SmoovePlayerOverlay],\n [\"smoove-player-controls\", SmoovePlayerControls],\n [\"smoove-player-controls-row\", SmoovePlayerControlsRow],\n [\"smoove-player-space\", SmoovePlayerSpace],\n];\n\nfor (const [tag, ctor] of REGISTRY) {\n if (!customElements.get(tag)) customElements.define(tag, ctor);\n}\n","import { html, type TemplateResult } from \"lit\";\nimport { SmooveControl } from \"./base.js\";\nimport type { PlayerApi } from \"./player-api.js\";\n\nconst clamp01 = (x: number): number => Math.max(0, Math.min(1, x));\n\n/**\n * Draggable seek bar. Mirrors the demo studio's scrubber UX: grabbing pauses\n * playback and resumes on release, dragging seeks live.\n */\nexport class SmoovePlayerProgress extends SmooveControl {\n private _dragging = false;\n private _wasPlaying = false;\n\n protected override bind(api: PlayerApi): void {\n this.watch(api.state.frame);\n this.watch(api.state.duration);\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this._teardownDrag();\n }\n\n private _pct(): number {\n const total = this.api?.state.duration.get() ?? 0;\n const frame = this.api?.state.frame.get() ?? 0;\n return total > 1 ? clamp01(frame / (total - 1)) : 0;\n }\n\n private _posFromEvent(e: PointerEvent): number {\n const track = this.querySelector(\".smoove-player__track\") ?? this;\n const r = track.getBoundingClientRect();\n return r.width > 0 ? clamp01((e.clientX - r.left) / r.width) : 0;\n }\n\n private _seek(p: number): void {\n const total = this.api?.state.duration.get() ?? 0;\n if (total > 1) this.api?.seekTo(Math.round(p * (total - 1)));\n }\n\n private _onMove = (e: PointerEvent): void => {\n if (this._dragging) this._seek(this._posFromEvent(e));\n };\n\n private _onUp = (): void => {\n if (!this._dragging) return;\n this._dragging = false;\n this._teardownDrag();\n if (this._wasPlaying) this.api?.play();\n };\n\n private _teardownDrag(): void {\n window.removeEventListener(\"pointermove\", this._onMove);\n window.removeEventListener(\"pointerup\", this._onUp);\n }\n\n private _onDown(e: PointerEvent): void {\n if (!this.api) return;\n e.preventDefault();\n this._dragging = true;\n this._wasPlaying = this.api.isPlaying();\n this.api.pause();\n this._seek(this._posFromEvent(e));\n window.addEventListener(\"pointermove\", this._onMove);\n window.addEventListener(\"pointerup\", this._onUp);\n }\n\n protected override render(): TemplateResult {\n const pct = this._pct() * 100;\n return html`<div\n class=\"smoove-player__progress${this._dragging ? \" is-dragging\" : \"\"}\"\n @pointerdown=${(e: PointerEvent) => this._onDown(e)}\n >\n <div class=\"smoove-player__track\">\n <div class=\"smoove-player__fill\" style=${`width:${pct}%`}></div>\n <div class=\"smoove-player__knob\" style=${`left:${pct}%`}></div>\n </div>\n </div>`;\n }\n}\n\nif (!customElements.get(\"smoove-player-progress\")) {\n customElements.define(\"smoove-player-progress\", SmoovePlayerProgress);\n}\n","import { svg, type TemplateResult } from \"lit\";\n\n/**\n * Inline-SVG icon set, ported from the demo studio's icon sheet. Each entry is\n * the inner markup of an 18×18 viewBox; {@link icon} wraps it in an `<svg>`.\n * No icon dependency — controls render `${icon(\"play\")}` directly.\n */\nconst PATHS: Record<string, TemplateResult> = {\n play: svg`<path d=\"M6 4.5l9 5.5-9 5.5z\" fill=\"currentColor\" stroke=\"none\" />`,\n pause: svg`<g fill=\"currentColor\" stroke=\"none\">\n <rect x=\"5\" y=\"4\" width=\"3.2\" height=\"12\" rx=\"1\" />\n <rect x=\"11.8\" y=\"4\" width=\"3.2\" height=\"12\" rx=\"1\" />\n </g>`,\n prev: svg`<g fill=\"currentColor\" stroke=\"none\">\n <path d=\"M14 5v10l-7-5z\" />\n <rect x=\"4.5\" y=\"5\" width=\"2.2\" height=\"10\" rx=\"1\" />\n </g>`,\n next: svg`<g fill=\"currentColor\" stroke=\"none\">\n <path d=\"M6 5v10l7-5z\" />\n <rect x=\"13.3\" y=\"5\" width=\"2.2\" height=\"10\" rx=\"1\" />\n </g>`,\n volume: svg`<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M4 8v4h2.5L11 15.5v-11L6.5 8z\" fill=\"currentColor\" stroke=\"none\" />\n <path d=\"M13.2 7.2a3.6 3.6 0 010 5.6M15 5.4a6 6 0 010 9.2\" />\n </g>`,\n mute: svg`<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M4 8v4h2.5L11 15.5v-11L6.5 8z\" fill=\"currentColor\" stroke=\"none\" />\n <path d=\"M13.5 8l3 4M16.5 8l-3 4\" />\n </g>`,\n loop: svg`<g fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M5 7h7a3 3 0 013 3M15 13H8a3 3 0 01-3-3\" />\n <path d=\"M13 5l2 2-2 2M7 15l-2-2 2-2\" />\n </g>`,\n fullscreen: svg`<path d=\"M4 7V4h3M16 7V4h-3M4 13v3h3M16 13v3h-3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />`,\n fullscreenExit: svg`<path d=\"M7 4v3H4M13 4v3h3M7 16v-3H4M13 16v-3h3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />`,\n};\n\n/** Render a named icon as an `<svg>` of the given pixel size. */\nexport function icon(name: keyof typeof PATHS | string, size = 18): TemplateResult {\n return svg`<svg\n width=${size}\n height=${size}\n viewBox=\"0 0 18 18\"\n aria-hidden=\"true\"\n style=\"display:block;flex:0 0 auto\"\n >${PATHS[name] ?? null}</svg>`;\n}\n","import { html, type TemplateResult } from \"lit\";\nimport { SmooveControl } from \"./base.js\";\nimport { icon } from \"./icons.js\";\nimport type { PlayerApi } from \"./player-api.js\";\n\n/** A play/pause toggle button reflecting and driving playback state. */\nexport class SmoovePlayerPlayToggleButton extends SmooveControl {\n protected override bind(api: PlayerApi): void {\n this.watch(api.state.playing);\n }\n\n protected override render(): TemplateResult {\n const playing = this.api?.state.playing.get() ?? false;\n const label = playing ? \"Pause\" : \"Play\";\n return html`<button\n type=\"button\"\n class=\"smoove-player__btn\"\n aria-label=${label}\n title=${label}\n @click=${() => this.api?.toggle()}\n >${icon(playing ? \"pause\" : \"play\")}</button>`;\n }\n}\n\nif (!customElements.get(\"smoove-player-play-toggle-button\")) {\n customElements.define(\"smoove-player-play-toggle-button\", SmoovePlayerPlayToggleButton);\n}\n","import { html, type TemplateResult } from \"lit\";\nimport { SmooveControl } from \"./base.js\";\nimport { icon } from \"./icons.js\";\nimport type { PlayerApi } from \"./player-api.js\";\n\n/**\n * Mute toggle + volume slider. With the `collapsed` attribute the slider is\n * hidden until the control is hovered/focused (styled by the stylesheet).\n */\nexport class SmoovePlayerSoundControl extends SmooveControl {\n static override properties = {\n collapsed: { type: Boolean, reflect: true },\n };\n declare collapsed?: boolean;\n\n protected override bind(api: PlayerApi): void {\n this.watch(api.state.volume);\n this.watch(api.state.muted);\n }\n\n private _onInput(e: Event): void {\n const v = Number((e.target as HTMLInputElement).value);\n this.api?.setVolume(v);\n this.api?.setMuted(v === 0);\n }\n\n protected override render(): TemplateResult {\n const muted = this.api?.state.muted.get() ?? false;\n const volume = this.api?.state.volume.get() ?? 1;\n const off = muted || volume === 0;\n const label = off ? \"Unmute\" : \"Mute\";\n return html`<div class=\"smoove-player__sound\">\n <button\n type=\"button\"\n class=\"smoove-player__btn\"\n aria-label=${label}\n title=${label}\n @click=${() => this.api?.toggleMute()}\n >${icon(off ? \"mute\" : \"volume\")}</button>\n <input\n class=\"smoove-player__volume\"\n type=\"range\"\n min=\"0\"\n max=\"1\"\n step=\"0.01\"\n aria-label=\"Volume\"\n .value=${String(off ? 0 : volume)}\n @input=${(e: Event) => this._onInput(e)}\n />\n </div>`;\n }\n}\n\nif (!customElements.get(\"smoove-player-sound-control\")) {\n customElements.define(\"smoove-player-sound-control\", SmoovePlayerSoundControl);\n}\n","import { html, type TemplateResult } from \"lit\";\nimport { SmooveControl } from \"./base.js\";\nimport type { PlayerApi } from \"./player-api.js\";\n\nconst fmt = (seconds: number): string => {\n const t = Math.max(0, seconds);\n const m = Math.floor(t / 60);\n const s = Math.floor(t % 60);\n return `${m}:${s.toString().padStart(2, \"0\")}`;\n};\n\n/** Current time / total duration readout (`m:ss / m:ss`). */\nexport class SmoovePlayerTime extends SmooveControl {\n protected override bind(api: PlayerApi): void {\n this.watch(api.state.frame);\n this.watch(api.state.duration);\n }\n\n protected override render(): TemplateResult {\n const fps = this.api?.fps ?? 0;\n const frame = this.api?.state.frame.get() ?? 0;\n const total = this.api?.state.duration.get() ?? 0;\n const cur = fps > 0 ? frame / fps : 0;\n const dur = fps > 0 ? total / fps : 0;\n return html`<span class=\"smoove-player__time\"\n ><span class=\"smoove-player__time-cur\">${fmt(cur)}</span\n ><span class=\"smoove-player__time-sep\">/</span\n ><span class=\"smoove-player__time-dur\">${fmt(dur)}</span></span\n >`;\n }\n}\n\nif (!customElements.get(\"smoove-player-time\")) {\n customElements.define(\"smoove-player-time\", SmoovePlayerTime);\n}\n","import { html, type PropertyValues, type TemplateResult } from \"lit\";\nimport { SmooveControl } from \"./base.js\";\nimport { icon } from \"./icons.js\";\nimport type { PlayerApi } from \"./player-api.js\";\n\n/** Toggles loop playback. Reflects an `on` attribute when looping. */\nexport class SmoovePlayerLoopButton extends SmooveControl {\n static override properties = { on: { type: Boolean, reflect: true } };\n declare on?: boolean;\n\n protected override bind(api: PlayerApi): void {\n this.watch(api.state.loop);\n }\n\n protected override willUpdate(_changed: PropertyValues): void {\n this.on = this.api?.state.loop.get() ?? false;\n }\n\n protected override render(): TemplateResult {\n return html`<button\n type=\"button\"\n class=\"smoove-player__btn\"\n aria-label=\"Loop\"\n title=\"Loop\"\n aria-pressed=${this.api?.state.loop.get() ? \"true\" : \"false\"}\n @click=${() => this.api?.toggleLoop()}\n >${icon(\"loop\")}</button>`;\n }\n}\n\nif (!customElements.get(\"smoove-player-loop-button\")) {\n customElements.define(\"smoove-player-loop-button\", SmoovePlayerLoopButton);\n}\n","import { html, type TemplateResult } from \"lit\";\nimport { SmooveControl } from \"./base.js\";\nimport { icon } from \"./icons.js\";\nimport type { PlayerApi } from \"./player-api.js\";\n\n/** Toggles the player in and out of fullscreen. */\nexport class SmoovePlayerFullscreenButton extends SmooveControl {\n protected override bind(api: PlayerApi): void {\n this.watch(api.state.fullscreen);\n }\n\n protected override render(): TemplateResult {\n const fs = this.api?.state.fullscreen.get() ?? false;\n const label = fs ? \"Exit fullscreen\" : \"Fullscreen\";\n return html`<button\n type=\"button\"\n class=\"smoove-player__btn\"\n aria-label=${label}\n title=${label}\n @click=${() => this.api?.toggleFullscreen()}\n >${icon(fs ? \"fullscreenExit\" : \"fullscreen\")}</button>`;\n }\n}\n\nif (!customElements.get(\"smoove-player-fullscreen-button\")) {\n customElements.define(\"smoove-player-fullscreen-button\", SmoovePlayerFullscreenButton);\n}\n","// Importing the control modules ensures their custom elements are defined\n// before the default bar instantiates them.\nimport \"./containers.js\";\nimport \"./progress.js\";\nimport \"./play-toggle-button.js\";\nimport \"./sound-control.js\";\nimport \"./time.js\";\nimport \"./loop-button.js\";\nimport \"./fullscreen-button.js\";\n\n/**\n * Build the default control bar used when `<smoove-player controls>` has no\n * user-supplied `<smoove-player-controls>`. It is composed from the same public\n * sub-components, so it inherits the player context and the opt-in styling.\n * Tagged `data-smoove-default` so the player can swap it out if the user later\n * provides their own controls.\n */\nexport function createDefaultControls(): HTMLElement {\n const controls = document.createElement(\"smoove-player-controls\");\n controls.setAttribute(\"data-smoove-default\", \"\");\n\n const progressRow = document.createElement(\"smoove-player-controls-row\");\n progressRow.appendChild(document.createElement(\"smoove-player-progress\"));\n\n const row = document.createElement(\"smoove-player-controls-row\");\n row.appendChild(document.createElement(\"smoove-player-play-toggle-button\"));\n\n const sound = document.createElement(\"smoove-player-sound-control\");\n sound.setAttribute(\"collapsed\", \"\");\n row.appendChild(sound);\n\n row.appendChild(document.createElement(\"smoove-player-time\"));\n\n const space = document.createElement(\"smoove-player-space\");\n space.setAttribute(\"grow\", \"\");\n row.appendChild(space);\n\n row.appendChild(document.createElement(\"smoove-player-loop-button\"));\n row.appendChild(document.createElement(\"smoove-player-fullscreen-button\"));\n\n controls.appendChild(progressRow);\n controls.appendChild(row);\n return controls;\n}\n","import type { ReadonlySignal } from \"@smoove/core\";\n\nexport type { ReadonlySignal };\n\nexport type Signal<T> = ReadonlySignal<T> & {\n set(value: T): void;\n};\n\n/**\n * Minimal reactive value, shape-compatible with core's `ReadonlySignal`.\n * The player owns its own stable signals (frame, playing, fullscreen, scale,\n * …) so descendant components can subscribe once and keep working even as the\n * underlying `Composition` is swapped in or out.\n */\nexport function createSignal<T>(initial: T): Signal<T> {\n let value = initial;\n const listeners = new Set<(value: T) => void>();\n return {\n get: () => value,\n set(next: T) {\n if (Object.is(next, value)) return;\n value = next;\n for (const fn of listeners) fn(value);\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n","import type { Composition } from \"@smoove/core\";\nimport { createDefaultControls } from \"./default-controls.js\";\nimport type { PlayerApi, PlayerState } from \"./player-api.js\";\nimport { createSignal } from \"./signal.js\";\n\nconst TIMEUPDATE_INTERVAL_MS = 250;\nconst now = (): number => (typeof performance?.now === \"function\" ? performance.now() : Date.now());\n\n/**\n * Duck-typed `Composition` check — avoids a runtime dependency on `core`\n * (it stays a type-only import). A Composition is a `Konva.Stage`\n * (`setContainer`) that also exposes the engine's `refresh()`.\n */\nfunction isComposition(v: unknown): v is Composition {\n return (\n typeof v === \"object\" &&\n v !== null &&\n typeof (v as { setContainer?: unknown }).setContainer === \"function\" &&\n typeof (v as { refresh?: unknown }).refresh === \"function\"\n );\n}\n\n/**\n * Resolve a remote module's default export to a live {@link Composition},\n * unwrapping factories (sync/async) and `{ default }` nesting along the way.\n */\nasync function resolveComposition(input: unknown): Promise<Composition> {\n let value: unknown = input;\n for (let i = 0; i < 5; i++) {\n value = await value;\n if (isComposition(value)) return value;\n if (typeof value === \"function\") {\n value = (value as () => unknown)();\n continue;\n }\n if (typeof value === \"object\" && value !== null && \"default\" in value) {\n value = (value as { default: unknown }).default;\n continue;\n }\n break;\n }\n throw new Error(\n \"[smoove] remote src did not resolve to a Composition — expected a default export of a Composition or a factory returning one\",\n );\n}\n\n/**\n * `<smoove-player>` — the host element. Wraps a {@link Composition} and plays it\n * like an HTML5 `<video>`: letterbox-scales the stage to its box, supports\n * fullscreen and keyboard control, auto-renders a default control bar, and\n * exposes a Remotion-style imperative + event API.\n *\n * It is a plain custom element (not a `LitElement`) so it never re-renders over\n * its user-authored children (overlay / controls). Chrome is injected\n * imperatively; descendant controls read state through {@link PlayerApi}.\n *\n * `composition` is a property (an object), e.g. `el.composition = comp`.\n */\nexport class SmoovePlayer extends HTMLElement implements PlayerApi {\n static get observedAttributes(): string[] {\n return [\"loop\", \"controls\", \"muted\", \"volume\", \"playbackrate\", \"src\"];\n }\n\n // --- stable, player-owned reactive state -----------------------------------\n private readonly _frame = createSignal(0);\n private readonly _playing = createSignal(false);\n private readonly _duration = createSignal(1);\n private readonly _loop = createSignal(false);\n private readonly _rate = createSignal(1);\n private readonly _volume = createSignal(1);\n private readonly _muted = createSignal(false);\n private readonly _fullscreen = createSignal(false);\n private readonly _scale = createSignal(1);\n\n readonly state: PlayerState = {\n frame: this._frame,\n playing: this._playing,\n duration: this._duration,\n loop: this._loop,\n rate: this._rate,\n volume: this._volume,\n muted: this._muted,\n fullscreen: this._fullscreen,\n scale: this._scale,\n };\n\n private _comp: Composition | null = null;\n private _unsubs: Array<() => void> = [];\n // Monotonic token guarding async `src` loads: a stale import resolving late\n // must not clobber a composition assigned by a newer `src` (or imperatively).\n private _loadSeq = 0;\n private _prevPlaying = false;\n private _lastTimeupdate = 0;\n\n private _stage: HTMLDivElement | null = null;\n private _scaleEl: HTMLDivElement | null = null;\n private _canvasEl: HTMLDivElement | null = null;\n private _resizeObserver: ResizeObserver | null = null;\n private _mutationObserver: MutationObserver | null = null;\n // Last pixelRatio pushed onto the composition's layer canvases (0 = none yet).\n private _appliedPixelRatio = 0;\n\n // --- public reactive accessors (PlayerApi) ---------------------------------\n get composition(): Composition | null {\n return this._comp;\n }\n set composition(c: Composition | null) {\n // An explicit assignment wins over any in-flight `src` load — invalidate it.\n this._loadSeq++;\n if (c === this._comp) return;\n // Halt the outgoing composition so it stops ticking + playing audio in the\n // background — it's a long-lived instance the consumer may reuse, so we\n // reset it to a clean stopped state (frame 0) rather than leave it running.\n this._comp?.stop();\n this._unbind();\n this._appliedPixelRatio = 0;\n this._comp = c ?? null;\n if (this.isConnected && this._comp) this._mount();\n }\n\n get fps(): number {\n return this._comp?.fps ?? 0;\n }\n\n // --- convenience attribute-backed properties -------------------------------\n get loop(): boolean {\n return this.hasAttribute(\"loop\");\n }\n set loop(v: boolean) {\n this.toggleAttribute(\"loop\", v);\n }\n get controls(): boolean {\n return this.hasAttribute(\"controls\");\n }\n set controls(v: boolean) {\n this.toggleAttribute(\"controls\", v);\n }\n get autoPlay(): boolean {\n return this.hasAttribute(\"autoplay\");\n }\n set autoPlay(v: boolean) {\n this.toggleAttribute(\"autoplay\", v);\n }\n get clickToPlay(): boolean {\n return !this.hasAttribute(\"no-click-to-play\");\n }\n get spaceKey(): boolean {\n return !this.hasAttribute(\"no-space-key\");\n }\n get keyboard(): boolean {\n return !this.hasAttribute(\"no-keyboard\");\n }\n get doubleClickFullscreen(): boolean {\n return this.hasAttribute(\"double-click-fullscreen\");\n }\n get initialFrame(): number {\n const a = this.getAttribute(\"initialframe\");\n return a == null ? 0 : Number(a);\n }\n get src(): string | null {\n return this.getAttribute(\"src\");\n }\n set src(v: string | null) {\n if (v == null) this.removeAttribute(\"src\");\n else this.setAttribute(\"src\", v);\n }\n // --- lifecycle -------------------------------------------------------------\n connectedCallback(): void {\n if (!this.hasAttribute(\"tabindex\")) this.setAttribute(\"tabindex\", \"0\");\n this._ensureChrome();\n\n this._resizeObserver ??= new ResizeObserver(() => this._layout());\n this._resizeObserver.observe(this);\n\n this._mutationObserver ??= new MutationObserver(() => this._reconcileControls());\n this._mutationObserver.observe(this, { childList: true });\n\n document.addEventListener(\"fullscreenchange\", this._onFullscreenChange);\n this.addEventListener(\"keydown\", this._onKeyDown);\n\n if (this._comp) this._mount();\n // A composition assigned imperatively before connect wins; otherwise honor\n // a declarative `src`.\n else if (this.src) this._loadFromSrc(this.src);\n this._reconcileControls();\n }\n\n disconnectedCallback(): void {\n this._unbind();\n this._resizeObserver?.disconnect();\n this._mutationObserver?.disconnect();\n document.removeEventListener(\"fullscreenchange\", this._onFullscreenChange);\n this.removeEventListener(\"keydown\", this._onKeyDown);\n this._stage?.removeEventListener(\"click\", this._onStageClick);\n this._stage?.removeEventListener(\"dblclick\", this._onStageDblClick);\n this._appliedPixelRatio = 0;\n\n // A removed player must not keep working in the background. We never destroy\n // the composition: `src` usually resolves to a cached ES-module singleton, so\n // destroying it would leave a reconnect (or a second player with the same\n // `src`) reusing a dead husk. Instead rewind it to the start (stop) and\n // release its media sources (suspend) so it stops both ticking and\n // downloading/decoding video/audio. A reconnect re-acquires via `_mount`.\n this._comp?.stop();\n this._comp?.suspend();\n\n // Drop the injected chrome so a reconnect rebuilds a fresh stage/canvas.\n this._stage?.remove();\n this._stage = null;\n this._scaleEl = null;\n this._canvasEl = null;\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n // `src` is handled before the early-return below: it loads a composition\n // rather than configuring an existing one, so it must run even when none is\n // mounted yet.\n if (name === \"src\") {\n if (this.isConnected && value) this._loadFromSrc(value);\n return;\n }\n const comp = this._comp;\n if (!comp) return;\n switch (name) {\n case \"loop\":\n comp.setLoop(this.loop);\n break;\n case \"muted\":\n comp.mixer.setMuted(this.hasAttribute(\"muted\"));\n break;\n case \"volume\":\n if (this.hasAttribute(\"volume\")) comp.mixer.setVolume(Number(this.getAttribute(\"volume\")));\n break;\n case \"playbackrate\":\n if (this.hasAttribute(\"playbackrate\"))\n comp.setPlaybackRate(Number(this.getAttribute(\"playbackrate\")));\n break;\n case \"controls\":\n this._reconcileControls();\n break;\n }\n }\n\n // --- chrome / mounting -----------------------------------------------------\n private _ensureChrome(): void {\n if (this._stage) return;\n // Essential structural styles are applied inline so the player works with\n // zero CSS imported (headless). The opt-in stylesheet only adds cosmetics\n // (control bar, colors, overlay). The host needs a positioning context for\n // the absolutely-positioned stage; only set it if the page hasn't.\n if (getComputedStyle(this).position === \"static\") this.style.position = \"relative\";\n const stage = document.createElement(\"div\");\n stage.className = \"smoove-player__stage\";\n stage.style.cssText = \"position:absolute;inset:0;overflow:hidden\";\n const scale = document.createElement(\"div\");\n scale.className = \"smoove-player__scale\";\n scale.style.cssText = \"position:absolute;top:0;left:0;transform-origin:top left\";\n const canvas = document.createElement(\"div\");\n canvas.className = \"smoove-player__canvas\";\n canvas.style.cssText = \"width:100%;height:100%\";\n scale.appendChild(canvas);\n stage.appendChild(scale);\n this.insertBefore(stage, this.firstChild);\n this._stage = stage;\n this._scaleEl = scale;\n this._canvasEl = canvas;\n stage.addEventListener(\"click\", this._onStageClick);\n stage.addEventListener(\"dblclick\", this._onStageDblClick);\n }\n\n private _mount(): void {\n const comp = this._comp;\n if (!comp) return;\n this._ensureChrome();\n if (!this._canvasEl) return;\n\n this._canvasEl.replaceChildren();\n comp.setContainer(this._canvasEl);\n\n // Re-acquire media sources released while the player was disconnected\n // (no-op on a first mount). Mirrors the `stop()` + `suspend()` on disconnect.\n comp.resume();\n\n // apply element config onto the composition\n if (this.hasAttribute(\"loop\")) comp.setLoop(true);\n if (this.hasAttribute(\"muted\")) comp.mixer.setMuted(true);\n if (this.hasAttribute(\"volume\")) comp.mixer.setVolume(Number(this.getAttribute(\"volume\")));\n if (this.hasAttribute(\"playbackrate\"))\n comp.setPlaybackRate(Number(this.getAttribute(\"playbackrate\")));\n\n // seed stable signals from the live composition\n this._frame.set(comp.frame.get());\n this._playing.set(comp.isPlaying.get());\n this._duration.set(comp.durationInFrames.get());\n this._loop.set(comp.loop.get());\n this._rate.set(comp.playbackRate.get());\n this._volume.set(comp.mixer.volume.get());\n this._muted.set(comp.mixer.muted.get());\n this._prevPlaying = comp.isPlaying.get();\n\n this._bind(comp);\n\n // Load at the start (or an explicit `initialframe`) — a reused composition\n // instance may carry a playhead from a previous mount.\n comp.setFrame(this.initialFrame);\n comp.refresh();\n this._layout();\n\n if (this.autoPlay) {\n try {\n this.play();\n } catch (error) {\n this._emit(\"error\", { error });\n }\n }\n }\n\n private _bind(comp: Composition): void {\n this._unsubs.push(\n comp.frame.subscribe((frame) => {\n this._frame.set(frame);\n this._emit(\"frameupdate\", { frame });\n const t = now();\n const last = comp.durationInFrames.get() - 1;\n if (t - this._lastTimeupdate >= TIMEUPDATE_INTERVAL_MS || frame >= last || frame <= 0) {\n this._lastTimeupdate = t;\n const fps = comp.fps;\n const total = comp.durationInFrames.get();\n this._emit(\"timeupdate\", {\n frame,\n time: fps > 0 ? frame / fps : 0,\n durationInFrames: total,\n durationInSeconds: fps > 0 ? total / fps : 0,\n });\n }\n }),\n comp.isPlaying.subscribe((playing) => {\n this._playing.set(playing);\n const frame = comp.frame.get();\n if (playing && !this._prevPlaying) {\n this._emit(\"play\", { frame });\n } else if (!playing && this._prevPlaying) {\n const last = comp.durationInFrames.get() - 1;\n const rate = comp.playbackRate.get();\n const ended =\n !comp.loop.get() && ((rate > 0 && frame >= last) || (rate < 0 && frame <= 0));\n this._emit(ended ? \"ended\" : \"pause\", { frame });\n }\n this._prevPlaying = playing;\n }),\n comp.durationInFrames.subscribe((d) => this._duration.set(d)),\n comp.loop.subscribe((l) => this._loop.set(l)),\n comp.playbackRate.subscribe((rate) => {\n this._rate.set(rate);\n this._emit(\"ratechange\", { playbackRate: rate });\n }),\n comp.mixer.volume.subscribe((volume) => {\n this._volume.set(volume);\n this._emit(\"volumechange\", { volume });\n }),\n comp.mixer.muted.subscribe((muted) => {\n this._muted.set(muted);\n this._emit(\"mutechange\", { muted });\n }),\n );\n }\n\n private _unbind(): void {\n for (const u of this._unsubs) u();\n this._unsubs = [];\n }\n\n // --- remote loading --------------------------------------------------------\n /**\n * Dynamically import a remote ESM module and mount its default export. The\n * default export may be a {@link Composition}, a factory returning one (sync\n * or async), or a factory resolving to `{ default: Composition }`.\n *\n * Loads are race-guarded with {@link _loadSeq}: a stale import resolving after\n * a newer `src` — or an imperative `composition =` — is discarded.\n */\n private async _loadFromSrc(rawSrc: string): Promise<void> {\n const seq = ++this._loadSeq;\n let url: string;\n try {\n // Resolve against the document base so consumer-relative URLs behave like\n // `<video src>` (a bare dynamic import resolves relative to this module).\n url = new URL(rawSrc, document.baseURI).href;\n } catch (error) {\n this._emit(\"error\", { error });\n return;\n }\n this.toggleAttribute(\"loading\", true);\n this._emit(\"loadstart\", { src: url });\n try {\n const mod = (await import(/* @vite-ignore */ url)) as { default?: unknown };\n const comp = await resolveComposition(\"default\" in mod ? mod.default : mod);\n if (seq !== this._loadSeq) return; // superseded by a newer load/assignment\n this.toggleAttribute(\"loading\", false);\n this.composition = comp;\n this._emit(\"loaded\", { src: url, composition: comp });\n } catch (error) {\n if (seq !== this._loadSeq) return;\n this.toggleAttribute(\"loading\", false);\n this._emit(\"error\", { error });\n }\n }\n\n // --- layout / fullscreen ---------------------------------------------------\n private _layout(): void {\n const comp = this._comp;\n if (!comp || !this._scaleEl) return;\n const boxW = this.clientWidth;\n const boxH = this.clientHeight;\n const compW = comp.width() || 1;\n const compH = comp.height() || 1;\n if (boxW <= 0 || boxH <= 0) return;\n const scale = Math.min(boxW / compW, boxH / compH);\n const offX = (boxW - compW * scale) / 2;\n const offY = (boxH - compH * scale) / 2;\n this._scaleEl.style.width = `${compW}px`;\n this._scaleEl.style.height = `${compH}px`;\n this._scaleEl.style.transform = `translate(${offX}px, ${offY}px) scale(${scale})`;\n if (scale !== this._scale.get()) {\n this._scale.set(scale);\n this._emit(\"scalechange\", { scale });\n }\n this._applyRenderScale(scale);\n }\n\n /** Author-facing cap on the render pixel ratio. Defaults to the device ratio. */\n private get _maxPixelRatio(): number {\n const dpr = typeof globalThis !== \"undefined\" ? globalThis.devicePixelRatio || 1 : 1;\n const attr = this.getAttribute(\"max-pixel-ratio\");\n const parsed = attr == null ? Number.NaN : Number(attr);\n return Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, dpr) : dpr;\n }\n\n /**\n * Match the composition's backing canvas resolution to how large it's actually\n * displayed, instead of always rendering at authored resolution × devicePixelRatio.\n *\n * A 1600×900 composition letterboxed into a 375px-wide phone was drawing a\n * 3200×1800 (5.76 MP) canvas every frame and letting CSS scale it down — ~18×\n * overdraw. Here the effective pixel ratio is `displayScale × dpr` (capped at\n * the device ratio, floored so it never goes pathologically blurry), so the\n * backing store tracks on-screen pixels. This is the single biggest win for\n * mobile frame rate; it also keeps oversized compositions from over-rendering.\n */\n private _applyRenderScale(scale: number): void {\n const comp = this._comp;\n if (!comp || !Number.isFinite(scale) || scale <= 0) return;\n const dpr = typeof globalThis !== \"undefined\" ? globalThis.devicePixelRatio || 1 : 1;\n const target = Math.max(0.5, Math.min(scale * dpr, this._maxPixelRatio));\n // Skip churn on sub-pixel resize deltas.\n if (Math.abs(target - this._appliedPixelRatio) < 0.01) return;\n this._appliedPixelRatio = target;\n for (const layer of comp.getLayers()) {\n layer.getCanvas().setPixelRatio(target);\n }\n // Repaint the current frame at the new resolution (setPixelRatio cleared it).\n comp.refresh();\n }\n\n private _onFullscreenChange = (): void => {\n const fs = document.fullscreenElement === this;\n this._fullscreen.set(fs);\n this.toggleAttribute(\"fullscreen\", fs);\n this._emit(\"fullscreenchange\", { isFullscreen: fs });\n this._layout();\n };\n\n // --- interaction -----------------------------------------------------------\n private _onStageClick = (): void => {\n if (this.clickToPlay) this.toggle();\n };\n\n private _onStageDblClick = (): void => {\n if (this.doubleClickFullscreen) this.toggleFullscreen();\n };\n\n private _onKeyDown = (e: KeyboardEvent): void => {\n if (!this.keyboard) return;\n const target = e.target as HTMLElement | null;\n if (target && /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName)) return;\n switch (e.key) {\n case \" \":\n if (this.spaceKey) {\n e.preventDefault();\n this.toggle();\n }\n break;\n case \"ArrowLeft\":\n e.preventDefault();\n this.stepBy(-1);\n break;\n case \"ArrowRight\":\n e.preventDefault();\n this.stepBy(1);\n break;\n case \"f\":\n case \"F\":\n this.toggleFullscreen();\n break;\n case \"l\":\n case \"L\":\n this.toggleLoop();\n break;\n }\n };\n\n private _reconcileControls(): void {\n if (!this.isConnected) return;\n const hasUserControls = Array.from(this.children).some(\n (c) => c.tagName === \"SMOOVE-PLAYER-CONTROLS\" && !c.hasAttribute(\"data-smoove-default\"),\n );\n const existingDefault = this.querySelector(\n \":scope > smoove-player-controls[data-smoove-default]\",\n );\n if (this.controls && !hasUserControls) {\n if (!existingDefault) this.appendChild(createDefaultControls());\n } else if (existingDefault) {\n existingDefault.remove();\n }\n }\n\n private _emit(type: string, detail: unknown): void {\n this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));\n }\n\n // --- imperative API (PlayerApi) --------------------------------------------\n play(): void {\n const comp = this._comp;\n if (!comp) return;\n // Reverse playback from the start would immediately stop — jump to the end.\n if (comp.playbackRate.get() < 0 && comp.frame.get() <= 0) {\n comp.setFrame(comp.durationInFrames.get() - 1);\n }\n try {\n comp.play();\n } catch (error) {\n this._emit(\"error\", { error });\n }\n }\n\n pause(): void {\n this._comp?.pause();\n }\n\n toggle(): void {\n if (this.isPlaying()) this.pause();\n else this.play();\n }\n\n stop(): void {\n this._comp?.stop();\n }\n\n seekTo(frame: number): void {\n const comp = this._comp;\n if (!comp) return;\n comp.setFrame(frame);\n this._emit(\"seeked\", { frame: comp.frame.get() });\n }\n\n stepBy(delta: number): void {\n const comp = this._comp;\n if (!comp) return;\n this.seekTo(comp.frame.get() + delta);\n }\n\n setProps(props: Record<string, unknown>): void {\n this._comp?.setProps(props);\n }\n\n getCurrentFrame(): number {\n return this._comp?.frame.get() ?? 0;\n }\n\n isPlaying(): boolean {\n return this._comp?.isPlaying.get() ?? false;\n }\n\n setVolume(volume: number): void {\n this._comp?.mixer.setVolume(volume);\n }\n\n getVolume(): number {\n return this._comp?.mixer.volume.get() ?? this._volume.get();\n }\n\n mute(): void {\n this._comp?.mixer.setMuted(true);\n }\n\n unmute(): void {\n this._comp?.mixer.setMuted(false);\n }\n\n setMuted(muted: boolean): void {\n this._comp?.mixer.setMuted(muted);\n }\n\n toggleMute(): void {\n this._comp?.mixer.setMuted(!this.isMuted());\n }\n\n isMuted(): boolean {\n return this._comp?.mixer.muted.get() ?? this._muted.get();\n }\n\n setLoop(loop: boolean): void {\n this.loop = loop;\n this._comp?.setLoop(loop);\n }\n\n toggleLoop(): void {\n this.setLoop(!this.isLooping());\n }\n\n isLooping(): boolean {\n return this._comp?.loop.get() ?? this._loop.get();\n }\n\n setPlaybackRate(rate: number): void {\n this._comp?.setPlaybackRate(rate);\n }\n\n getPlaybackRate(): number {\n return this._comp?.playbackRate.get() ?? this._rate.get();\n }\n\n override requestFullscreen(options?: FullscreenOptions): Promise<void> {\n return HTMLElement.prototype.requestFullscreen.call(this, options);\n }\n\n exitFullscreen(): Promise<void> {\n return this.isFullscreen() ? document.exitFullscreen() : Promise.resolve();\n }\n\n toggleFullscreen(): void {\n if (this.isFullscreen()) void this.exitFullscreen();\n else void this.requestFullscreen();\n }\n\n isFullscreen(): boolean {\n return document.fullscreenElement === this;\n }\n\n getScale(): number {\n return this._scale.get();\n }\n}\n\nif (!customElements.get(\"smoove-player\")) {\n customElements.define(\"smoove-player\", SmoovePlayer);\n}\n","import { html, type PropertyValues, type TemplateResult } from \"lit\";\nimport { SmooveControl } from \"./base.js\";\nimport { icon } from \"./icons.js\";\nimport type { PlayerApi } from \"./player-api.js\";\n\nconst SIZES: Record<string, number> = { small: 28, medium: 44, large: 72 };\n\n/**\n * A large, centered play affordance (for use inside `<smoove-player-overlay>`).\n * `size` is `small | medium | large`. Reflects a `playing` attribute so the\n * stylesheet can fade it out during playback.\n */\nexport class SmoovePlayerPlayButton extends SmooveControl {\n static override properties = {\n size: { type: String, reflect: true },\n playing: { type: Boolean, reflect: true },\n };\n declare size?: \"small\" | \"medium\" | \"large\";\n declare playing?: boolean;\n\n protected override bind(api: PlayerApi): void {\n this.watch(api.state.playing);\n }\n\n protected override willUpdate(_changed: PropertyValues): void {\n this.playing = this.api?.state.playing.get() ?? false;\n }\n\n protected override render(): TemplateResult {\n const playing = this.api?.state.playing.get() ?? false;\n const size = SIZES[this.size ?? \"medium\"] ?? SIZES.medium;\n const label = playing ? \"Pause\" : \"Play\";\n return html`<button\n type=\"button\"\n class=\"smoove-player__overlay-play\"\n aria-label=${label}\n title=${label}\n @click=${() => this.api?.toggle()}\n >${icon(playing ? \"pause\" : \"play\", size)}</button>`;\n }\n}\n\nif (!customElements.get(\"smoove-player-play-button\")) {\n customElements.define(\"smoove-player-play-button\", SmoovePlayerPlayButton);\n}\n"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { TemplateResult } from 'lit';
2
+ import { SmooveControl } from './base.js';
3
+ import { PlayerApi } from './player-api.js';
4
+ /**
5
+ * Draggable seek bar. Mirrors the demo studio's scrubber UX: grabbing pauses
6
+ * playback and resumes on release, dragging seeks live.
7
+ */
8
+ export declare class SmoovePlayerProgress extends SmooveControl {
9
+ private _dragging;
10
+ private _wasPlaying;
11
+ protected bind(api: PlayerApi): void;
12
+ disconnectedCallback(): void;
13
+ private _pct;
14
+ private _posFromEvent;
15
+ private _seek;
16
+ private _onMove;
17
+ private _onUp;
18
+ private _teardownDrag;
19
+ private _onDown;
20
+ protected render(): TemplateResult;
21
+ }
@@ -0,0 +1,12 @@
1
+ import { ReadonlySignal } from '@smoove/core';
2
+ export type { ReadonlySignal };
3
+ export type Signal<T> = ReadonlySignal<T> & {
4
+ set(value: T): void;
5
+ };
6
+ /**
7
+ * Minimal reactive value, shape-compatible with core's `ReadonlySignal`.
8
+ * The player owns its own stable signals (frame, playing, fullscreen, scale,
9
+ * …) so descendant components can subscribe once and keep working even as the
10
+ * underlying `Composition` is swapped in or out.
11
+ */
12
+ export declare function createSignal<T>(initial: T): Signal<T>;
@@ -0,0 +1,117 @@
1
+ import { Composition } from '@smoove/core';
2
+ import { PlayerApi, PlayerState } from './player-api.js';
3
+ /**
4
+ * `<smoove-player>` — the host element. Wraps a {@link Composition} and plays it
5
+ * like an HTML5 `<video>`: letterbox-scales the stage to its box, supports
6
+ * fullscreen and keyboard control, auto-renders a default control bar, and
7
+ * exposes a Remotion-style imperative + event API.
8
+ *
9
+ * It is a plain custom element (not a `LitElement`) so it never re-renders over
10
+ * its user-authored children (overlay / controls). Chrome is injected
11
+ * imperatively; descendant controls read state through {@link PlayerApi}.
12
+ *
13
+ * `composition` is a property (an object), e.g. `el.composition = comp`.
14
+ */
15
+ export declare class SmoovePlayer extends HTMLElement implements PlayerApi {
16
+ static get observedAttributes(): string[];
17
+ private readonly _frame;
18
+ private readonly _playing;
19
+ private readonly _duration;
20
+ private readonly _loop;
21
+ private readonly _rate;
22
+ private readonly _volume;
23
+ private readonly _muted;
24
+ private readonly _fullscreen;
25
+ private readonly _scale;
26
+ readonly state: PlayerState;
27
+ private _comp;
28
+ private _unsubs;
29
+ private _loadSeq;
30
+ private _prevPlaying;
31
+ private _lastTimeupdate;
32
+ private _stage;
33
+ private _scaleEl;
34
+ private _canvasEl;
35
+ private _resizeObserver;
36
+ private _mutationObserver;
37
+ private _appliedPixelRatio;
38
+ get composition(): Composition | null;
39
+ set composition(c: Composition | null);
40
+ get fps(): number;
41
+ get loop(): boolean;
42
+ set loop(v: boolean);
43
+ get controls(): boolean;
44
+ set controls(v: boolean);
45
+ get autoPlay(): boolean;
46
+ set autoPlay(v: boolean);
47
+ get clickToPlay(): boolean;
48
+ get spaceKey(): boolean;
49
+ get keyboard(): boolean;
50
+ get doubleClickFullscreen(): boolean;
51
+ get initialFrame(): number;
52
+ get src(): string | null;
53
+ set src(v: string | null);
54
+ connectedCallback(): void;
55
+ disconnectedCallback(): void;
56
+ attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
57
+ private _ensureChrome;
58
+ private _mount;
59
+ private _bind;
60
+ private _unbind;
61
+ /**
62
+ * Dynamically import a remote ESM module and mount its default export. The
63
+ * default export may be a {@link Composition}, a factory returning one (sync
64
+ * or async), or a factory resolving to `{ default: Composition }`.
65
+ *
66
+ * Loads are race-guarded with {@link _loadSeq}: a stale import resolving after
67
+ * a newer `src` — or an imperative `composition =` — is discarded.
68
+ */
69
+ private _loadFromSrc;
70
+ private _layout;
71
+ /** Author-facing cap on the render pixel ratio. Defaults to the device ratio. */
72
+ private get _maxPixelRatio();
73
+ /**
74
+ * Match the composition's backing canvas resolution to how large it's actually
75
+ * displayed, instead of always rendering at authored resolution × devicePixelRatio.
76
+ *
77
+ * A 1600×900 composition letterboxed into a 375px-wide phone was drawing a
78
+ * 3200×1800 (5.76 MP) canvas every frame and letting CSS scale it down — ~18×
79
+ * overdraw. Here the effective pixel ratio is `displayScale × dpr` (capped at
80
+ * the device ratio, floored so it never goes pathologically blurry), so the
81
+ * backing store tracks on-screen pixels. This is the single biggest win for
82
+ * mobile frame rate; it also keeps oversized compositions from over-rendering.
83
+ */
84
+ private _applyRenderScale;
85
+ private _onFullscreenChange;
86
+ private _onStageClick;
87
+ private _onStageDblClick;
88
+ private _onKeyDown;
89
+ private _reconcileControls;
90
+ private _emit;
91
+ play(): void;
92
+ pause(): void;
93
+ toggle(): void;
94
+ stop(): void;
95
+ seekTo(frame: number): void;
96
+ stepBy(delta: number): void;
97
+ setProps(props: Record<string, unknown>): void;
98
+ getCurrentFrame(): number;
99
+ isPlaying(): boolean;
100
+ setVolume(volume: number): void;
101
+ getVolume(): number;
102
+ mute(): void;
103
+ unmute(): void;
104
+ setMuted(muted: boolean): void;
105
+ toggleMute(): void;
106
+ isMuted(): boolean;
107
+ setLoop(loop: boolean): void;
108
+ toggleLoop(): void;
109
+ isLooping(): boolean;
110
+ setPlaybackRate(rate: number): void;
111
+ getPlaybackRate(): number;
112
+ requestFullscreen(options?: FullscreenOptions): Promise<void>;
113
+ exitFullscreen(): Promise<void>;
114
+ toggleFullscreen(): void;
115
+ isFullscreen(): boolean;
116
+ getScale(): number;
117
+ }
@@ -0,0 +1,19 @@
1
+ import { TemplateResult } from 'lit';
2
+ import { SmooveControl } from './base.js';
3
+ import { PlayerApi } from './player-api.js';
4
+ /**
5
+ * Mute toggle + volume slider. With the `collapsed` attribute the slider is
6
+ * hidden until the control is hovered/focused (styled by the stylesheet).
7
+ */
8
+ export declare class SmoovePlayerSoundControl extends SmooveControl {
9
+ static properties: {
10
+ collapsed: {
11
+ type: BooleanConstructor;
12
+ reflect: boolean;
13
+ };
14
+ };
15
+ collapsed?: boolean;
16
+ protected bind(api: PlayerApi): void;
17
+ private _onInput;
18
+ protected render(): TemplateResult;
19
+ }
@@ -0,0 +1,26 @@
1
+ import { default as Konva } from 'konva';
2
+ /**
3
+ * Standalone (`<script>` tag) entry. Bundles everything and exposes the
4
+ * authoring vocabulary on the global object so a page with no bundler can
5
+ * build a Composition and hand it to `<smoove-player>`:
6
+ *
7
+ * ```html
8
+ * <link rel="stylesheet" href="player.css" />
9
+ * <script src="player.global.js"></script>
10
+ * <smoove-player controls></smoove-player>
11
+ * <script>
12
+ * const { Composition, Sequence, Rect } = window.Smoove;
13
+ * const comp = new Composition({ width: 640, height: 360, fps: 30, durationInFrames: 90 });
14
+ * // ...build comp...
15
+ * document.querySelector("smoove-player").composition = comp;
16
+ * </script>
17
+ * ```
18
+ */
19
+ import * as Smoove from "@smoove/core";
20
+ declare global {
21
+ interface Window {
22
+ Smoove: typeof Smoove;
23
+ Konva: typeof Konva;
24
+ }
25
+ }
26
+ export * from './index.js';
package/dist/time.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { TemplateResult } from 'lit';
2
+ import { SmooveControl } from './base.js';
3
+ import { PlayerApi } from './player-api.js';
4
+ /** Current time / total duration readout (`m:ss / m:ss`). */
5
+ export declare class SmoovePlayerTime extends SmooveControl {
6
+ protected bind(api: PlayerApi): void;
7
+ protected render(): TemplateResult;
8
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@smoove/player",
3
+ "version": "0.1.1",
4
+ "description": "Lit web components that play a smoove Composition like an HTML5 <video>.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/smoove-dev/smoove.git",
9
+ "directory": "packages/player"
10
+ },
11
+ "homepage": "https://smoove.dev",
12
+ "bugs": "https://github.com/smoove-dev/smoove/issues",
13
+ "type": "module",
14
+ "main": "./dist/player.js",
15
+ "module": "./dist/player.js",
16
+ "types": "./dist/index.d.ts",
17
+ "unpkg": "./dist/player.global.js",
18
+ "jsdelivr": "./dist/player.global.js",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/player.js"
23
+ },
24
+ "./standalone": "./dist/player.global.js",
25
+ "./styles.css": "./dist/player.css"
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "dependencies": {
31
+ "@lit/context": "^1.1.3",
32
+ "lit": "^3.2.1"
33
+ },
34
+ "peerDependencies": {
35
+ "konva": ">=10",
36
+ "@smoove/core": "^0.1.1"
37
+ },
38
+ "devDependencies": {
39
+ "konva": "^10.3.0",
40
+ "vite": "^8.0.0",
41
+ "vite-plugin-dts": "^5.0.2",
42
+ "@smoove/core": "0.1.1"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "scripts": {
48
+ "build": "vite build && vite build --mode standalone",
49
+ "dev": "vite build --watch",
50
+ "typecheck": "tsc --noEmit",
51
+ "clean": "rm -rf dist *.tsbuildinfo"
52
+ }
53
+ }