How Stremio Web Handles Video Playback: Architecture and Implementation

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, 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 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:

  • 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 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) 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

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

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

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 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), 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 initiating navigation and 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). 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 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) 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →