Lynx/Modules/Video/Usage
@sigx/lynx-video · Stable

Using Video#

Drop a native video player into your layout tree with a single component. It draws decoded frames inside its own box, plays declaratively from props, and reports back through typed events.

@sigx/lynx-video exposes one component, VideoPlayer, a typed sigx-lynx wrapper over the native <video-player> element. On iOS it drives an AVPlayer inside an AVPlayerLayer; on Android it drives an androidx.media3 ExoPlayer inside a PlayerView. Prefer the component over the raw intrinsic — it maps friendly camelCase props onto the native attribute surface for you.

Run sigx prebuild once after adding the dependency. Prebuild auto-discovers the package, registers the <video-player> UI element on both platforms, and pulls in the media3 Gradle deps on Android. The element is unavailable until prebuild has run.

Basic usage#

Give the player a src and a size, then let autoplay start it when the asset is ready. Because the component body returns a render function, signals and props read inside it stay reactive.

TSX
import { VideoPlayer } from '@sigx/lynx-video';

function ClipScreen() {
  return () => (
    <VideoPlayer
      src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
      autoplay
      resizeMode="contain"
      style={{ width: '100%', aspectRatio: 16 / 9 }}
    />
  );
}

The player must be given a size — set width/height, or an aspectRatio, via style. It draws within its layout box like any other view. resizeMode controls how the frame fits: contain letterboxes, cover fills and crops, stretch distorts to fill.

Controlling play and pause declaratively#

There is no imperative API in v1 — no seek or getStatus. You drive everything through props. The playing prop is a declarative play/pause toggle backed by a signal; when both playing and autoplay are set, playing wins.

TSX
import { component, signal } from '@sigx/lynx';
import { VideoPlayer } from '@sigx/lynx-video';

const Player = component(() => {
  const playing = signal(false);

  return () => (
    <view style={{ width: '100%' }}>
      <VideoPlayer
        src="file:///clips/intro.mp4"
        playing={playing()}
        loop
        muted
        style={{ width: '100%', aspectRatio: 16 / 9 }}
      />
      <text bindtap={() => playing.set(!playing())}>
        {playing() ? 'Pause' : 'Play'}
      </text>
    </view>
  );
});

loop restarts the clip natively at end-of-clip (iOS seeks to zero, Android uses REPEAT_MODE_ONE), so onEnd does not fire while looping. muted mutes audio independently of volume — it sets the effective volume to 0 without losing the stored value.

Reading duration and tracking progress#

Listen for onLoad to learn an asset's duration and natural dimensions once metadata resolves. For live progress, onTimeUpdate broadcasts the current position roughly four times a second.

TSX
import { component, signal } from '@sigx/lynx';
import { VideoPlayer } from '@sigx/lynx-video';

const Progress = component(() => {
  const durationMs = signal(0);
  const positionMs = signal(0);

  return () => (
    <view>
      <VideoPlayer
        src="https://example.com/talk.mp4"
        autoplay
        onLoad={(e) => durationMs.set(e.detail.durationMs)}
        onTimeUpdate={(e) => positionMs.set(e.detail.positionMs)}
        style={{ width: '100%', aspectRatio: 16 / 9 }}
      />
      <text>{Math.round((positionMs() / (durationMs() || 1)) * 100)}%</text>
    </view>
  );
});

onTimeUpdate fires every 250ms and crosses the native bridge on each call, so use it sparingly. For per-frame animation, read the duration once via onLoad and animate locally rather than reacting to every tick. On Android the 250ms timer only runs while playback is active. Note that durationMs is 0 for live or indeterminate-duration streams.

Handling errors and a poster#

Show a poster before the first frame and surface failures through onError. An error fires on a non-recoverable load or playback failure — and immediately for an invalid src URL on iOS.

TSX
import { component, signal } from '@sigx/lynx';
import { VideoPlayer } from '@sigx/lynx-video';

const SafePlayer = component(() => {
  const error = signal<string | null>(null);

  return () => (
    <view>
      {error() && <text>{error()}</text>}
      <VideoPlayer
        src="https://example.com/maybe-broken.mp4"
        poster="https://example.com/cover.jpg"
        controls
        onError={(e) => error.set(e.detail.message)}
        style={{ width: '100%', aspectRatio: 16 / 9 }}
      />
    </view>
  );
});

Setting src reloads the player; clearing it (empty or undefined) stops playback and releases the asset, so a re-render that drops src will not keep playing in the background. src accepts https://, http://, file:// URIs, and absolute paths starting with / (treated as a local file).

Native setup notes#

  • iOS ATS — playing an http:// (non-HTTPS) URL requires an NSAppTransportSecurity exception in your app's Info.plist. The package does not relax ATS itself.
  • Controlscontrols shows the platform default overlay. Android uses PlayerView's built-in transport controls. iOS v1 renders a minimal tap-to-play/pause overlay rather than AVPlayerViewController; for full transport UI on iOS, build your own overlay driven by the playing prop.
  • Poster on Android — the poster is a no-op stub in v1. Render an <image> sibling and toggle it off on onLoad instead.
  • media3 version — Android pins media3 1.4.1. If your app already depends on a different media3 version, align it via Gradle resolution to avoid duplicate-class errors.
  • Audio session (iOS) — for audio-bearing video the component is intended to set AVAudioSession to .playback; apps that also use @sigx/lynx-audio share a ref-counted session.

Notes#

  • Declarative only in v1: no seek / getStatus. Drive playback through the playing and src props.
  • playing overrides autoplay when both are set.
  • volume is clamped to 0..1; muted is independent of volume.
  • onEnd does not fire while loop is true.
  • onTimeUpdate fires ~4x/sec across the bridge — avoid using it to drive per-frame animation.

See also#