Astro, Svelte, Vue, plain JavaScript and Expo DOM components, powered by one framework-free player.
One player across frameworks
Import seamplayer/element to register <seam-player>. Simple options are attributes; arrays, objects and handlers are properties. React is optional for this entry. CSS injects automatically after registration.
import "seamplayer/element";
const player = document.querySelector("seam-player")!;
player.options = { src: "/film.mp4", title: "Film", chapters: [{ start: 0, title: "Intro" }] };
player.play();Astro
No React integration or client hydration directive is required. Import the element in a browser script. For a poster before JavaScript loads, render the core poster HTML and import the stylesheet on the server.
---
import { renderPoster } from "seamplayer/core";
import "seamplayer/styles.css";
const options = { src: "/film.mp4", poster: "/poster.jpg", title: "Film" };
---
<seam-player class="sp" src={options.src} poster={options.poster} title={options.title}>
<Fragment set:html={renderPoster(options)} />
</seam-player>
<script>
import "seamplayer/element";
</script>
Svelte and Vue
The Svelte 5 example registers only in the browser and assigns chapters as a property. Vue can use the same element; configure isCustomElement for seam-player in the Vue compiler.
<script lang="ts">
import { onMount } from "svelte";
import type { SeamPlayerElement } from "seamplayer/element";
import type { SeamChapter } from "seamplayer/core";
import "seamplayer/styles.css";
let { src, poster, title = "Film", chapters = [] }: {
src: string; poster?: string; title?: string; chapters?: SeamChapter[];
} = $props();
let player = $state<SeamPlayerElement>();
onMount(() => { void import("seamplayer/element"); });
$effect(() => { if (player) player.chapters = chapters; });
</script>
<seam-player class="sp" bind:this={player} {src} {poster} {title} />
Plain JavaScript
The core entry exposes createSeamPlayer, renderPoster and the shared option types. Call destroy when removing the host to release media, timers and listeners.
import { createSeamPlayer } from "seamplayer/core";
const player = createSeamPlayer(document.querySelector<HTMLElement>("#player")!, { src: "/film.mp4" });
player.update({ src: "/next.mp4" });
player.destroy();Expo and React Native
Copy this file into your app so Expo processes the use dom directive. It runs the web player in a webview on native. Props must be serializable; native actions must be top-level async functions. Expo SDK 55 and earlier require react-native-webview; SDK 56+ includes the default DOM webview. Fullscreen, picture-in-picture, clipboard and downloads depend on the platform. Native lock-screen media controls are not implemented.
"use dom";
import type { DOMProps } from "expo/dom";
import { SeamPlayer } from "seamplayer/react";
// Keep props serializable. Native actions must be top-level async functions.
export default function SeamPlayerDOM({ src, poster, title, onTimeUpdate }: {
src: string;
poster?: string;
title?: string;
dom?: DOMProps;
onTimeUpdate?: (time: number) => Promise<void>;
}) {
return <SeamPlayer src={src} poster={poster} title={title}
onTimeUpdate={(time) => { void onTimeUpdate?.(time); }} />;
}