Watch any web video stream in mpv, with no ads, popups or browser.
unplayer https://example.com/watch/some-live-eventPaste a link and unplayer finds the real video stream inside the page's player, then plays it in mpv. The website never loads, so its ads, popups, popunders, fake "close" buttons and trackers never run. A clean video window opens and your terminal is free again.
Platforms: Linux and macOS (both tested in CI). Windows via WSL2.
One command. It's a single bash script, no build step:
mkdir -p ~/.local/bin && curl -fsSL https://github.com/JonTelep/unplayer/releases/latest/download/unplayer -o ~/.local/bin/unplayer && chmod +x ~/.local/bin/unplayerIt needs curl and mpv. yt-dlp is optional, for YouTube, Twitch and friends.
sudo pacman -S mpv yt-dlp # Arch / Omarchy
sudo apt install mpv yt-dlp # Debian / Ubuntu
sudo dnf install mpv yt-dlp # Fedora
brew install mpv yt-dlp # macOSMake sure ~/.local/bin is on your PATH. Most distros include it by default.
To remove it, delete ~/.local/bin/unplayer.
Lots of live-stream and video sites wrap a perfectly normal video stream in a page full of ad scripts. They open popunder tabs, hijack your clicks and slow your browser to a crawl. Ad blockers end up chasing them forever.
unplayer skips the whole page. It reads the page as plain text (no JavaScript ever runs), pulls out the stream URL and hands it to mpv. You get the video and nothing else, usually in better quality and with smoother playback than the site's own player.
- 🚫 No ads, popups or trackers. The site's code never executes.
- 🔍 Finds the stream for you. It looks in the page, embedded
<iframe>players and mirror buttons. - ✅ Skips dead mirrors. It tests each stream by downloading a real chunk of video and plays the first one that works.
- 🎞️ Best quality automatically. It picks the highest resolution the stream offers.
- 📺 YouTube, Twitch and 1,800+ other sites. If a page hides its stream, unplayer hands off to yt-dlp, which skips the site's player and its ads.
- 🧈 Smooth playback. Read-ahead buffering absorbs network hiccups.
unplayer <url> # play the first working stream on the page
unplayer <url> 2 # play the 2nd stream found (try this if one stutters)
unplayer -f <url> # keep mpv attached to the terminal to see its output
unplayer --help # all options
unplayer --versionYou can paste the page you're watching on, or the player/embed URL directly.
mpv runs in the background, so your terminal comes straight back. Close the window
(or press q) to stop. mpv's output goes to ~/.local/state/unplayer.log. Check it
if a window closes unexpectedly.
| Key / mouse | Action |
|---|---|
Space / click |
Pause / play |
Mouse wheel, ↑ ↓ |
Volume |
m |
Mute |
f / double-click |
Fullscreen |
← → |
Seek back / forward |
q |
Quit |
mpv's 1–8 picture keys (brightness, contrast…) are disabled, so a stray keypress
can't turn the video black.
Run unplayer as a website on your own server, so you and anyone you share the password with
can watch from any device, including phones and TVs. Paste a page URL and you land on a
plain video player: fullscreen, volume, nothing else. Each attempt is logged on /stats.
All you need is a machine with Docker and the Compose plugin: a VPS, a home server or your desktop.
git clone https://github.com/JonTelep/unplayer.git && cd unplayer
cp .env.example .envEdit .env:
| Setting | What it does |
|---|---|
PASSWORD |
Phrase that locks every page. Visitors type it once per browser (remembered for a year). Set one on anything reachable from the internet. Empty = open to anyone. Generate one with openssl rand -base64 24. |
DOMAIN |
Hostname for HTTPS, e.g. streams.example.com. Only used by make deploy. |
MAX_STREAMS |
Max streams relayed at once (default 5). Each one is an ffmpeg process plus its bandwidth. |
.env is gitignored, so your password stays out of git.
make deploy # docker compose up -d --build: the app + Caddy with automatic Let's Encrypt HTTPS- Add a DNS A record for
DOMAINpointing at the server's IP. On Cloudflare, set it to DNS only (grey cloud). Cloudflare's proxy isn't meant for relaying video, and it blocks Caddy from getting its certificate. - Open ports 80 and 443 in the server's firewall (
sudo ufw allow 80,443/tcp, plus your host's panel firewall if it has one). - Open
https://<DOMAIN>, enter your password and paste a link.
make local # the app only, no Caddy: http://localhost:8080It only listens on localhost. To reach it from your phone over your LAN or
Tailscale, add a compose.override.yml (gitignored) with that
machine's IP and run make local again:
services:
app:
ports: ["100.x.y.z:8080:8080"] # your Tailscale or LAN IPThen open http://100.x.y.z:8080 on the phone.
- Update:
git pull && make deploy(ormake local). Rebuilding also pulls the latest yt-dlp, which sites like YouTube need often. - Change the password: edit
.env, thenmake deployagain. Everyone has to re-enter it. - Logs:
docker compose logs -f app. Health check:/healthz. - Stop:
make clean(keeps the stats and TLS volumes;docker compose down -vwipes them). - Stats live in SQLite on the
datavolume and survive rebuilds.
- The server finds the stream with
unplayer --print. ffmpeg then relays it as HLS (fMP4) that browsers play directly: iPhone/Safari natively, everything else through hls.js. The video isn't re-encoded. The audio is re-encoded to plain AAC so it plays cleanly in every browser. - Live sources often freeze for 10–20 seconds. The player waits until 30 seconds are buffered before it starts, and that cushion absorbs the freezes. Expect to be a bit behind live and a short "Buffering…" wait when a stream opens.
- If the browser blocks autoplay (Brave does by default), tap ▶ Tap to play.
- A stream stops 5 minutes after its last viewer leaves. Everyone watching the same link shares one relay.
- The page shows each step while it works (finding the stream, checking mirrors, buffering). Next mirror → switches to the next stream on the page. If the source dies, the player says so, and ↻ Restart starts the stream again.
- Watch links are
/w?url=<page>, so you can bookmark them, refresh them or send them to someone, and they always restart the stream. Recent links are listed on the home page.
- Desktop: drag the ▶ unplayer link on the home page to your bookmarks bar. Click it on any video page.
- Android: install the site as an app (browser menu → Add to Home screen). unplayer then appears in the share menu.
- iPhone: make a Shortcut that shows in the share sheet with URL Encode (Shortcut Input) →
Open URLs
https://<DOMAIN>/w?url=<Encoded URL>. - Pasting a link into the box starts it right away; you don't need to press Watch.
- The password protects the pages and the "start a stream" form. Stream files (
/s/<id>/…) stay open so a TV can play them over AirPlay. Their URLs are random and stop working when the stream ends. - Anyone with the password can make your server fetch public web pages and relay video. The
server refuses private and local addresses, so it can't be pointed at your own network.
Bandwidth is on you, so keep
MAX_STREAMSsensible for your plan.
- Fetch the page with
curl. This is just text, so no scripts run. - Find the players: the page itself, its
<iframe>embeds and any mirror buttons. - Pull the stream URL (an HLS
.m3u8playlist) out of each player. - Pick the best quality and check it's alive by downloading one segment and
confirming it's real video. Many hosts disguise video as
.txtfiles, so it checks the bytes, not the label. - Play it in mpv with the headers the stream expects.
- Nothing found? Hand the link to mpv + yt-dlp instead.
| Problem | Fix |
|---|---|
No working stream |
The stream may not be live yet, or every mirror is down. Try again closer to start time. |
| Stutters or freezes | Try another mirror: unplayer <url> 2, 3, … |
429 Too Many Requests |
Close any browser tab playing the same stream. It counts against the same limit. |
| Window opens then closes | Check ~/.local/state/unplayer.log, or rerun with -f to watch mpv's output live. |
| YouTube link fails | Update yt-dlp (sudo pacman -Syu yt-dlp or your distro's equivalent). YouTube changes often. |
- Works when the player exposes its stream URL in the page. Players that build the URL in heavily obfuscated JavaScript won't work unless yt-dlp supports the site.
- DRM-protected streams (Netflix, Disney+, etc.) are not supported and never will be.
- If the source stream itself is down, no player can fix that.
git clone https://github.com/JonTelep/unplayer.git
cd unplayer
make install # symlinks ./unplayer into ~/.local/bin, so git pull updates it
make test # offline test against a local fake player (needs python3)The whole tool is one bash script: unplayer. Issues and PRs are welcome.
To release: bump VERSION= in unplayer, commit, then run make release. That tags
vX.Y.Z, pushes and publishes a GitHub release with the script attached.
unplayer is a media player helper. It doesn't host, index or link to any content. Use it only for streams you have the right to watch, and respect the terms of the sites you visit.