Unofficial Nuvio Web client.
2.3K
Unofficial implementation of the Nuvio API in a web UI (since there is none, yet).
Sign in with the Nuvio account you already use on mobile and you get the same profiles, addons, library and watch progress in a browser : plus a player that streams in the tab, casts to a TV, or hands the link off to a native app.
Nuvio Web hosts no media. All catalogs, metadata, streams and subtitles come from addons (the Stremio addon protocol) which you install and are responsible for. The app is a shell around whatever those addons return; it does not endorse, index, or verify any addon or its content. Use only addons you have the right to use in your jurisdiction. Not affiliated with or endorsed by Nuvio.
⌘K / Ctrl-K from anywhere to jump to a screen or
start a search.hls.js for .m3u8, the element's own src
for everything else, with audio-track switching on either.magnet: for a P2P source, and copy-to-clipboard
on desktop.<video>, and playback that decodes no frames (unsupported HEVC/AV1) or no
audio (Dolby Digital / DTS / Atmos) raises a dismissible banner instead of a
black screen with no explanation.BroadcastChannel.You need a Nuvio account (you can create one from the app's sign-up screen) and at least one addon. Addons installed on your account — mobile or web : show up everywhere.
The docker image is available
at Docker Hub, for
linux/amd64 and linux/arm64.
docker run -p 3000:3000 -e ORIGIN=http://localhost:3000 \
orochibraru/nuvio-web:latest
services:
nuvio:
image: orochibraru/nuvio-web:latest
restart: unless-stopped
ports:
- 3000:3000
environment:
# The URL you actually browse to : see Configuration below.
ORIGIN: http://localhost:3000
healthcheck:
interval: 30s
retries: 3
start_period: 5s
test: ["CMD", "/app/dist/healthcheck"]
timeout: 30s
Then open http://localhost:3000, sign in, pick a profile, and add an addon from Settings → Addons if your account has none yet.
There is no database and no volume : every piece of state : account, profiles,
addons, library, settings : lives on your Nuvio account. The container serves
the app on port 3000, and /app/dist/healthcheck is a self-contained binary
suitable for HEALTHCHECK and for orchestrator probes.
One environment variable matters: ORIGIN, the URL you actually browse to.
| Variable | Default | When you need it |
|---|---|---|
ORIGIN | (unset) | Always, unless the proxy headers below cover it |
PROTOCOL_HEADER | assumes https | Behind a reverse proxy |
HOST_HEADER | the Host header | Behind a proxy that rewrites it |
PORT | 3000 | To listen on another port |
Without ORIGIN the server reconstructs its own origin from the request's
Host header and assumes https://. Browse to a plain-HTTP address and
that guess disagrees with the browser's Origin header, so SvelteKit's
cross-site check rejects every write the app makes with
403 Cross-site remote requests are forbidden. Only non-GET requests are
checked, so the app still renders and reads fine : but nothing saves. Settings
snap back, library toggles revert, progress never sticks. Set ORIGIN to
exactly what's in the address bar (scheme, host and port, no trailing slash) and
it goes away.
Behind a reverse proxy, either set ORIGIN to the public URL or let the proxy's
headers speak for it:
docker run -p 3000:3000 \
-e PROTOCOL_HEADER=x-forwarded-proto \
-e HOST_HEADER=x-forwarded-host \
orochibraru/nuvio-web:latest
Serving over HTTPS on the default port needs none of this : the assumed
https:// already matches.
Running it on the public internet is on you: put it behind HTTPS and whatever access control you would give any other self-hosted app.
In the player:
| Key | Action |
|---|---|
Space / K | Play / pause |
← / J and → / L | Seek 10s back / forward |
↑ / ↓ | Volume |
M | Mute |
F | Fullscreen |
C | Cycle subtitle track |
I | Info overlay |
N | Next episode |
E | Episode list |
Esc | Close the open panel |
Anywhere in the app, ⌘K / Ctrl-K opens the command palette.
experimental.async), Tailwind 4 and
shadcn-svelte components.+page.server.ts returns promises rather than awaiting
them, so navigation completes on the page shell and each row fills in behind
its own skeleton. Everything a page needs for its URL is fetched by the load —
addon fan-out included : instead of costing an extra round trip after
hydration.Promise.all-ed (bounded
concurrency, a per-request timeout, and a failed addon degrading to an empty
row) so one slow addon can't hold a page hostage. Outbound addon fetches go
through an SSRF guard that refuses private and link-local address ranges.svelte-smol into a
self-contained server binary, so the runtime layer is debian:slim plus that
binary : no Bun, no node_modules.See contributing.md.
bun install
bun run dev # dev server on :5173
bun run check # svelte-check + tsc
bun run lint # biome + tailwind class lint
bun run test:unit # vitest
bun run test:e2e # playwright (needs a test account, see .env.example)
Tracked in TODO.md
AGPL-3.0-or-later. If you run a modified version as a network service, you must offer its source to your users.
Content type
Image
Digest
sha256:874cad855…
Size
103.9 MB
Last updated
about 17 hours ago
docker pull orochibraru/nuvio-web