# How Stremio Web Handles Video Playback: Architecture and Implementation

> Discover how Stremio Web handles video playback using the useVideo React hook and the @stremio/stremio-video library. Learn about its architecture and implementation.

- Repository: [Stremio/stremio-web](https://github.com/Stremio/stremio-web)
- Tags: architecture
- Published: 2026-05-23

---

**Stremio Web delegates video playback to a React hook called `useVideo` that wraps the `@stremio/stremio-video` library, while the `Player` route orchestrates UI layers, shortcuts, and Core model synchronization.**

Stremio Web's playback system is built on a modular React architecture that separates video rendering, state management, and Core integration. The implementation relies on three core components working together: the **`<Video/>` component** for UI entry points, the **`useVideo` hook** for low-level player control, and the **`Player` route** for coordinating the complete playback experience. This architecture ensures smooth streaming while maintaining clean separation between the presentation layer and the underlying `@stremio/stremio-video` library.

## Core Architecture of Stremio Web Video Playback

The playback stack divides responsibilities across distinct layers, each handling specific aspects of the video lifecycle.

### The Video Component (UI Entry Point)

Located at [`src/components/Video/Video.js`](https://github.com/Stremio/stremio-web/blob/main/src/components/Video/Video.js), the **`<Video/>` component** serves as the primary interface for video discovery and selection. It renders clickable thumbnails with progress indicators and context menus rather than the actual video element itself.

Key features include:

- **Deep link navigation** – The `videoButtonOnClick` handler (lines 63-70) navigates to `deepLinks.player` or `deepLinks.metaDetailsStreams` by setting `window.location`, routing users to the dedicated player page.
- **Interactive menus** – Right-click or long-press triggers `popupLabelOnMouseUp` and `popupLabelOnLongPress` (lines 21-38) to display options like "Mark watched" or "Mark rest."
- **Progress visualization** – The `renderLabel` function (lines 90-110) draws progress bars and watch flags based on viewing history.

The component imports the actual video renderer from `@stremio/stremio-video`, delegating all playback commands to the `useVideo` hook.

### The useVideo Hook (Low-Level Player Wrapper)

The **`useVideo` hook** in [`src/routes/Player/useVideo.js`](https://github.com/Stremio/stremio-web/blob/main/src/routes/Player/useVideo.js) creates a singleton bridge between React and the `@stremio/stremio-video` library. It exposes a stateful API while proxying commands to the underlying video instance.

**State management** operates through:
- A `state` object tracking `paused`, `time`, `volume`, `audioTracks`, `subtitleTracks`, `hdrInfo`, and `fullscreen` status
- A `dispatch` function forwarding commands like `load`, `unload`, and `setProp` to the video instance
- An **`EventEmitter`** (`events`) broadcasting changes to UI layers like the `ControlBar` and error overlays

Critical implementation details from [`src/routes/Player/useVideo.js`](https://github.com/Stremio/stremio-web/blob/main/src/routes/Player/useVideo.js):

- **Instance creation** – Lines 7-14 construct `new Video()` from `@stremio/stremio-video` inside a `useEffect` and attach event listeners.
- **Property synchronization** – The `onPropChanged` callback (lines 90-95) listens for `"propChanged"` events from the video implementation and updates React state accordingly.
- **Stream loading** – The `load` method (lines 59-65) sends `commandName: 'load'` with the stream manifest to initialize playback.

### The Player Route (Orchestration Layer)

[`src/routes/Player/Player.js`](https://github.com/Stremio/stremio-web/blob/main/src/routes/Player/Player.js) functions as the top-level playback page triggered by deep links (e.g., `/player?stream=...`). It coordinates multiple subsystems:

- **Core integration** – Uses `usePlayer` (from [`src/routes/Player/usePlayer.js`](https://github.com/Stremio/stremio-web/blob/main/src/routes/Player/usePlayer.js)) to build a Core `Player` model from URL parameters and dispatch actions like `Load`, `Seek`, and `Ended`.
- **Video initialization** – Calls `video.load()` when the Core reports a ready stream (lines 96-115), passing the stream manifest and autoplay preferences.
- **UI layer management** – Conditionally renders the buffering indicator, error overlays, control bars, and side drawers based on component state (`menuOpen`, `overlayHidden`).
- **Input handling** – Registers keyboard shortcuts and gamepad actions via `onShortcut` (lines 71-125) for play/pause, seeking, and volume control.
- **Chromecast support** – Monitors Chromecast state and routes playback to external devices when active.
- **Navigation logic** – The `handleNextVideoNavigation` function (lines 39-61) decides whether to advance to the next episode or return to browsing when playback ends.

The component connects video state to the Core model through callbacks like `videoParamsChanged`, `streamStateChanged`, `timeChanged`, `pausedChanged`, and `ended`.

## Playback Flow from Click to Stream

Understanding Stremio Web video playback requires tracing the complete data flow:

1. **User interaction** – Clicking a video thumbnail triggers `<Video/>`'s `videoButtonOnClick` handler.
2. **Route transition** – Navigation changes `window.location` to a player URL containing stream parameters.
3. **Model initialization** – `usePlayer` (lines 92-150) decodes the URL, creates a Core `Player` model, and fetches stream metadata.
4. **Video preparation** – The `Player` component detects a ready stream and invokes `video.load({ stream, autoplay: true })`.
5. **Element creation** – `useVideo` forwards the load command to `@stremio/stremio-video`, which instantiates an HTML5 `<video>` element.
6. **UI rendering** – Control bars and navigation read from `video.state` to display current time, buffering status, and track options.
7. **State synchronization** – User interactions (pause, seek, track change) call `useVideo` setters, which emit `"propChanged"` events. These propagate to Core via `streamStateChanged` and `pausedChanged` callbacks.
8. **End handling** – The `"ended"` event triggers `onEnded` in `Player`, notifies Core via the `ended()` action, and executes navigation to the next video or previous page.

## Implementation Examples for Custom Components

### Programmatically Starting Playback

```javascript
import React from 'react';
import usePlayer from 'stremio-web/src/routes/Player/usePlayer';
import useVideo from 'stremio-web/src/routes/Player/useVideo';

function PlayButton({ streamId }) {
  const [player] = usePlayer({ stream: streamId });
  const video = useVideo();

  const startPlayback = () => {
    video.load({
      stream: player.stream?.content,
      autoplay: true
    });
  };

  return <button onClick={startPlayback}>Play Now</button>;
}

```

### Monitoring Playback State Changes

```javascript
import { useEffect } from 'react';
import useVideo from 'stremio-web/src/routes/Player/useVideo';

function PlaybackMonitor() {
  const { events, state } = useVideo();

  useEffect(() => {
    const handleEnded = () => console.log('Playback completed');
    const handleError = (err) => console.error('Playback failed:', err);

    events.on('ended', handleEnded);
    events.on('error', handleError);

    return () => {
      events.off('ended', handleEnded);
      events.off('error', handleError);
    };
  }, [events]);

  return <div>Current time: {state.time ?? 0}s</div>;
}

```

### Changing Audio Tracks Dynamically

```javascript
function AudioTrackSelector() {
  const { state, setAudioTrack } = useVideo();

  return (
    <select 
      value={state.selectedAudioTrackId} 
      onChange={(e) => setAudioTrack(e.target.value)}
    >
      {state.audioTracks?.map(track => (
        <option key={track.id} value={track.id}>
          {track.name}
        </option>
      ))}
    </select>
  );
}

```

## Summary

- **Stremio Web video playback** relies on a three-tier architecture: the `<Video/>` component for discovery UI, the `useVideo` hook for player control, and the `Player` route for orchestration.
- The **`useVideo` hook** in [`src/routes/Player/useVideo.js`](https://github.com/Stremio/stremio-web/blob/main/src/routes/Player/useVideo.js) creates a singleton wrapper around `@stremio/stremio-video`, exposing methods like `load()`, `setPaused()`, and `setAudioTrack()` while broadcasting events via `EventEmitter`.
- **Core model synchronization** happens through `usePlayer` ([`src/routes/Player/usePlayer.js`](https://github.com/Stremio/stremio-web/blob/main/src/routes/Player/usePlayer.js)), which translates between React state and the Stremio Core's `Player` model.
- **Deep link navigation** drives the entire flow, with [`src/components/Video/Video.js`](https://github.com/Stremio/stremio-web/blob/main/src/components/Video/Video.js) initiating navigation and [`src/routes/Player/Player.js`](https://github.com/Stremio/stremio-web/blob/main/src/routes/Player/Player.js) handling the resulting stream initialization.
- All UI components read from the centralized `video.state` object, ensuring consistent display of playback progress, buffering status, and track information.

## Frequently Asked Questions

### What video library powers Stremio Web's playback?

Stremio Web uses the **`@stremio/stremio-video`** package as its underlying video engine. The React codebase does not manipulate HTML5 video elements directly; instead, the `useVideo` hook creates an abstraction layer that communicates with this library via a command-and-event pattern (sending commands like `load` and receiving `propChanged` events).

### How does Stremio Web synchronize video state with the Core model?

Synchronization occurs through bidirectional callbacks defined in `usePlayer` ([`src/routes/Player/usePlayer.js`](https://github.com/Stremio/stremio-web/blob/main/src/routes/Player/usePlayer.js)). When users interact with the player, UI methods like `setAudioTrack` trigger `streamStateChanged` callbacks that update the Core. Conversely, when the Core updates stream metadata or playback progress, `usePlayer` maps these to React state updates (lines 92-150), transforming data types like release strings into JavaScript `Date` objects where necessary.

### Can developers programmatically control video playback in Stremio Web?

Yes. Any component can import the `useVideo` hook from [`src/routes/Player/useVideo.js`](https://github.com/Stremio/stremio-web/blob/main/src/routes/Player/useVideo.js) to access the playback API. The hook returns methods including `load()`, `unload()`, `setPaused()`, `setVolume()`, `seek()`, `setAudioTrack()`, and `setSubtitlesTrack()`. These methods dispatch commands to the underlying video instance and automatically synchronize state through the `EventEmitter` pattern.

### How does Stremio Web handle navigation when a video ends?

The `Player` component ([`src/routes/Player/Player.js`](https://github.com/Stremio/stremio-web/blob/main/src/routes/Player/Player.js)) listens for the `"ended"` event from `useVideo`. Upon receiving this event, it executes `handleNextVideoNavigation` (lines 39-61), which checks for `deepLinks.player` or `deepLinks.metaDetailsStreams` to determine the next destination. If a subsequent episode exists, it navigates there; otherwise, it returns the user to the previous browsing history or meta-details page.