# @hyperframes/player Embeddable web component for playing HyperFrames compositions. Zero dependencies, works with any framework. ## Install ```bash npm install @hyperframes/player ``` Or load directly via CDN: ```html ``` If you need a classic ` ``` ## Usage ```html ``` The player loads the composition in a sandboxed iframe, auto-detects its dimensions and duration, and scales it responsively to fit the container. ### With a framework ```typescript import "@hyperframes/player"; // The custom element is now registered — use it in your markup // React: // Vue: ``` ### Poster image Show a static image before playback starts: ```html ``` ## Attributes | Attribute | Type | Default | Description | | ---------------------- | ------------------------------- | ------------- | --------------------------------------------------------------------------- | | `src` | string | — | URL to the composition HTML file | | `audio-src` | string | — | Audio URL for parent-frame playback (mobile) | | `width` | number | 1920 | Composition width in pixels (aspect ratio) | | `height` | number | 1080 | Composition height in pixels (aspect ratio) | | `controls` | boolean | false | Show play/pause, scrubber, and time display | | `muted` | boolean | false | Mute audio playback | | `audio-locked` | boolean | false | Force-mute and hide the volume controls so the viewer cannot turn sound on | | `poster` | string | — | Image URL shown before playback starts | | `playback-rate` | number | 1 | Speed multiplier (0.5 = half, 2 = double) | | `autoplay` | boolean | false | Start playing when ready | | `loop` | boolean | false | Restart when the composition ends | | `shader-capture-scale` | number | — | Shader transition snapshot scale forwarded to browser previews (`0.25`-`1`) | | `shader-loading` | `composition \| player \| none` | `composition` | Controls shader transition prep loading UI ownership | ### Shader transition previews When a composition uses `@hyperframes/shader-transitions`, the player can own preview-only shader capture settings: ```html ``` `shader-loading="player"` shows the player-owned transition-prep overlay from shader progress messages. `composition` leaves direct composition fallback behavior alone, and `none` suppresses the loader. ### Audio lock (host-mandated silent playback) `audio-locked` forces `muted` on and hides the volume controls, with no UI path for the viewer to turn sound back on. Use it when embedding in a chat host (Claude.ai, ChatGPT, etc.) where audio must stay off regardless of viewer intent. Setting `muted` directly is _not_ enough — viewers can flip it back via the controls bar. Removing `audio-locked` only unhides the controls; it does **not** auto-unmute. Callers manage `muted` explicitly after unlocking. **Host-environment fallback.** Some host renderers — notably the Claude desktop Electron client — strip unknown custom-element attributes before they reach the DOM, defeating the attribute. As a safety net, the player also self-imposes the lock when it detects such an environment via `navigator.userAgent`, so audio stays muted even if the attribute never arrives. The public `audioLocked` property still reflects only the attribute, so external consumers (e.g. host widgets that mirror state) are not affected by the fallback. ### Mobile audio Mobile browsers block `audio.play()` inside iframes when the user gesture happened in the parent frame (the [User Activation spec](https://html.spec.whatwg.org/multipage/interaction.html#tracking-user-activation) does not propagate activation across frame boundaries via `postMessage`). The player handles this automatically for same-origin iframes (the default — `sandbox` includes `allow-same-origin`): 1. When the composition is ready, the player extracts all timed media (`audio[data-start]`, `video[data-start]`) from the iframe DOM and creates parent-frame copies. 2. The iframe originals are disabled (`src` and `data-start` removed) so the runtime doesn't try to play them. 3. When `play()` is called (from a user gesture), parent media `.play()` runs synchronously in the gesture call stack, satisfying mobile autoplay policy. 4. Both parent media and the GSAP timeline start simultaneously and free-run — no active sync needed since both are real-time systems. No changes are required by consumers — this works out of the box. The optional `audio-src` attribute can be used to start preloading a primary audio track before the iframe loads (useful on slow connections), but is not required for mobile playback. ## JavaScript API ```js const player = document.querySelector("hyperframes-player"); // Playback player.play(); player.pause(); player.seek(2.5); // jump to 2.5 seconds // Properties player.currentTime; // number (read/write) player.duration; // number (read-only) player.paused; // boolean (read-only) player.ready; // boolean (read-only) player.playbackRate; // number (read/write) player.muted; // boolean (read/write) player.audioLocked; // boolean (read/write) — force-mute + hide volume controls player.loop; // boolean (read/write) player.shaderCaptureScale; // number (read/write) player.shaderLoading; // "composition" | "player" | "none" (read/write) // Inner iframe access (for advanced consumers — see "Advanced: iframe access" below) player.iframeElement; // HTMLIFrameElement (read-only) ``` ## Advanced: iframe access The composition runs inside a sandboxed `