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
videoButtonOnClickhandler (lines 63-70) navigates todeepLinks.playerordeepLinks.metaDetailsStreamsby settingwindow.location, routing users to the dedicated player page. - Interactive menus – Right-click or long-press triggers
popupLabelOnMouseUpandpopupLabelOnLongPress(lines 21-38) to display options like "Mark watched" or "Mark rest." - Progress visualization – The
renderLabelfunction (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
stateobject trackingpaused,time,volume,audioTracks,subtitleTracks,hdrInfo, andfullscreenstatus - A
dispatchfunction forwarding commands likeload,unload, andsetPropto the video instance - An
EventEmitter(events) broadcasting changes to UI layers like theControlBarand error overlays
Critical implementation details from src/routes/Player/useVideo.js:
- Instance creation – Lines 7-14 construct
new Video()from@stremio/stremio-videoinside auseEffectand attach event listeners. - Property synchronization – The
onPropChangedcallback (lines 90-95) listens for"propChanged"events from the video implementation and updates React state accordingly. - Stream loading – The
loadmethod (lines 59-65) sendscommandName: '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(fromsrc/routes/Player/usePlayer.js) to build a CorePlayermodel from URL parameters and dispatch actions likeLoad,Seek, andEnded. - 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
handleNextVideoNavigationfunction (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:
- User interaction – Clicking a video thumbnail triggers
<Video/>'svideoButtonOnClickhandler. - Route transition – Navigation changes
window.locationto a player URL containing stream parameters. - Model initialization –
usePlayer(lines 92-150) decodes the URL, creates a CorePlayermodel, and fetches stream metadata. - Video preparation – The
Playercomponent detects a ready stream and invokesvideo.load({ stream, autoplay: true }). - Element creation –
useVideoforwards the load command to@stremio/stremio-video, which instantiates an HTML5<video>element. - UI rendering – Control bars and navigation read from
video.stateto display current time, buffering status, and track options. - State synchronization – User interactions (pause, seek, track change) call
useVideosetters, which emit"propChanged"events. These propagate to Core viastreamStateChangedandpausedChangedcallbacks. - End handling – The
"ended"event triggersonEndedinPlayer, notifies Core via theended()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, theuseVideohook for player control, and thePlayerroute for orchestration. - The
useVideohook insrc/routes/Player/useVideo.jscreates a singleton wrapper around@stremio/stremio-video, exposing methods likeload(),setPaused(), andsetAudioTrack()while broadcasting events viaEventEmitter. - Core model synchronization happens through
usePlayer(src/routes/Player/usePlayer.js), which translates between React state and the Stremio Core'sPlayermodel. - Deep link navigation drives the entire flow, with
src/components/Video/Video.jsinitiating navigation andsrc/routes/Player/Player.jshandling the resulting stream initialization. - All UI components read from the centralized
video.stateobject, 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →