Stream any video to your Tesla browser via MJPEG.
No <video> element inside.
Supports YouTube, Twitch, X/Twitter, Pluto TV, IPTV lists, and more.
Tesla browser
│ GET /watch?url=https://youtube.com/watch?v=xxx
▼
[nginx] (optional TLS + DDNS domain)
│
▼
[streamer container]
yt-dlp stdout
│ pipe
ffmpeg stdin → JPEG frames → multipart/x-mixed-replace
│
└──► Tesla sees a fast-updating <img> — not a <video>
scp -r opencarstream/ user@yourserver:~/
ssh user@yourserver
cd opencarstreamdocker compose up -d --buildIf Docker Hub is flaky in your region, override the Python base image source:
PYTHON_IMAGE=mirror.gcr.io/library/python:3.12-slim docker compose up -d --buildhttp://localhost:33333/health
http://localhost:33333/
Navigate to your server's status page and use the Stream tab, or go directly to:
http://YOUR_SERVER_IP:33333/
or directly using the url
http://YOUR_SERVER_IP:33333/watch?url=https://www.youtube.com/watch?v=VIDEO_ID
The Channel Feed tab lets you browse recent uploads from any YouTube channel and click to stream them directly.
Enter @channelhandle or a full channel URL in the feed tab and click Load Feed.
Run sync_subscriptions.py once on your home machine to generate a
subscriptions.json file. The streamer reads this static file — no cookies
or YouTube access needed inside the container.
Make sure you are logged in to YouTube in your browser, then run:
# Chrome
uv run sync_subscriptions.py --browser chrome
# Firefox
uv run sync_subscriptions.py --browser firefox
# Other supported browsers: chromium, brave, edge, opera, safari
uv run sync_subscriptions.py --browser braveyt-dlp reads your browser's cookie store directly — no export or extension needed. The script fetches only the channel list (not videos) and finishes in a few seconds. You will see output like:
Fetching subscriptions from YouTube…
✔ 112 channels written to subscriptions.json
Re-run this command whenever you follow or unfollow channels.
docker-compose.yml already has the volume configured:
volumes:
- ./subscriptions.json:/subscriptions.json:roAfter generating the file, restart the container to pick it up:
docker compose restart streamerOpen the Channel Feed tab. A My Subscriptions panel will appear automatically. Click Load Subscriptions, then click any channel to browse its recent uploads and stream a video.
Note: if you prefer to export cookies manually instead of using
--browser, install the Get cookies.txt LOCALLY Chrome extension, export while on youtube.com, then run:uv run sync_subscriptions.py --cookies /path/to/cookies.txt
- Sign up for a free DDNS provider (no-ip.com, duckdns.org, dynu.com)
- Install their update client on your server or router
- Port-forward TCP 33333 (or 443 if using nginx TLS) on your router to the server
- Open Tesla browser to:
http://yourname.ddns.net:33333/
# Install cloudflared
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \
| sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] \
https://pkg.cloudflare.com/cloudflared any main" \
| sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install cloudflared
# Authenticate and create tunnel
cloudflared tunnel login
cloudflared tunnel create opencarstream
cloudflared tunnel route dns opencarstream stream.yourdomain.com
# Run (or add to systemd)
cloudflared tunnel run --url http://localhost:33333 opencarstreamTesla URL becomes: https://stream.yourdomain.com/
- Uncomment the
nginxservice indocker-compose.yml - Edit
nginx.conf→ set your domain inserver_name - Get a free TLS cert:
sudo apt install certbot
sudo certbot certonly --standalone -d stream.yourdomain.com
sudo cp /etc/letsencrypt/live/stream.yourdomain.com/fullchain.pem certs/cert.pem
sudo cp /etc/letsencrypt/live/stream.yourdomain.com/privkey.pem certs/key.pemdocker compose up -d
| Variable | Default | Description |
|---|---|---|
PORT |
8080 | HTTP port inside container |
MJPEG_FPS |
24 | Frames/sec sent to client |
FFMPEG_QUALITY |
3 | JPEG quality: 1=best, 31=smallest |
STREAM_WIDTH |
1920 | Output width (px) |
STREAM_HEIGHT |
1080 | Output height (px) |
MAX_STREAMS |
3 | Max parallel video streams |
AUDIO_DELAY_MS |
0 | ms to delay video start after audio |
LOCAL_MEDIA_DIR |
/media/videos | Local Media folder path inside container |
IPTV_LISTS_DIR |
/iptv_lists | IPTV list folder (.m3u/.m3u8) inside container |
SUBSCRIPTIONS_FILE |
/subscriptions.json | Path to subscriptions JSON inside container |
Override in docker-compose.yml under environment:.
The Local Media tab reads files from inside the container, not directly from your host filesystem. Use a bind mount:
environment:
LOCAL_MEDIA_DIR: /media/videos
volumes:
- ./local-media:/media/videos:roThen put your videos on the host in ./local-media (next to docker-compose.yml),
or change the left side to any host path, for example:
volumes:
- /home/username/oocal-media:/media/videos:roAfter changing mounts/env:
docker compose up -dYou can define multiple mounted folders in docker-compose.yml and the container
will show them as subfolders in Local Media directly.
environment:
LOCAL_MEDIA_DIR: /media/videos
volumes:
- ./local-media:/media/videos:ro
- /home/santi/videos:/media/videos/home-videos:ro
- /mnt/nas/media:/media/videos/nas-media:roThen just run:
docker compose up -d --buildNotes:
- Symlinks are followed by the scanner (
followlinks=True). - This avoids compose override generation and keeps everything in one YAML.
- You only declare each extra directory once (in
volumes).
The IPTV tab loads playlist files from a folder mounted in the container.
environment:
IPTV_LISTS_DIR: /iptv_lists
volumes:
- ./iptv_lists:/iptv_lists:roPut your playlist files in ./iptv_lists next to docker-compose.yml, for example:
./iptv_lists/sports.m3u
./iptv_lists/news.m3u8
Then run:
docker compose up -dOpen the IPTV tab, pick a list by name, and all streams from that playlist will appear automatically.
Any URL that yt-dlp supports works in the Stream tab or /watch?url=:
| Source | Example URL |
|---|---|
| YouTube | https://www.youtube.com/watch?v=VIDEO_ID |
| Twitch live | https://www.twitch.tv/channelname |
| Twitch VOD | https://www.twitch.tv/videos/VOD_ID |
| X / Twitter | https://x.com/user/status/TWEET_ID |
| Pluto TV | Built-in channel list in the Pluto TV tab (US, no account) |
| IPTV list | .m3u / .m3u8 files in the IPTV tab (iptv_lists/) |
For Twitch and YouTube VODs, use the Twitch and YouTube tabs respectively to browse and pick a stream. For X/Twitter, paste the tweet URL directly in the Stream tab.
| Endpoint | Description |
|---|---|
GET / |
Status dashboard (Stream / YouTube / Twitch / Pluto TV / IPTV / Info tabs) |
GET /watch?url=… |
Watch page (MJPEG video + audio) |
GET /feed?channel=URL&limit=N |
JSON list of recent uploads for a channel/user |
GET /iptv_lists |
JSON playlist files discovered in IPTV_LISTS_DIR |
GET /iptv_streams?list=NAME |
JSON stream entries parsed from a playlist |
GET /subscriptions |
JSON channel list from subscriptions.json |
GET /health |
{"ok":true} — for uptime monitors |
GET /status |
JSON list of active streams |
OpenCarStream is a third-party, unofficial project. Tesla is a trademark of Tesla, Inc. OpenCarStream is unofficial and not affiliated with or endorsed by Tesla. YouTube is a trademark of Google LLC, Twitch is a trademark of Twitch Interactive, Inc., and X/Twitter is a trademark of X Corp.; OpenCarStream is not affiliated with or endorsed by any of them.
- Bookmark it in Tesla: Save
http://yourserver/as a bookmark and use the UI - Lower bandwidth: Set
MJPEG_FPS=15andFFMPEG_QUALITY=6in docker-compose.yml - Update yt-dlp (YouTube changes frequently):
docker compose build --no-cache && docker compose up -d
| Symptom | Fix |
|---|---|
| Black screen in Tesla | Make sure URL ends in /watch?url=... not just / |
502 Pipeline error |
yt-dlp may need updating: rebuild image |
DeadlineExceeded while pulling base image |
Retry with a mirror: PYTHON_IMAGE=mirror.gcr.io/library/python:3.12-slim docker compose build --no-cache |
| Stream stutters on LAN | Lower MJPEG_FPS to 12–15 |
| Can't reach from internet | Check port forwarding / firewall; try curl http://yourserver:33333/health from outside |
| Container exits immediately | docker compose logs streamer to see the error |
| Subscriptions panel not visible | subscriptions.json not mounted — check volume in docker-compose.yml |
‼ yt-dlp failed in sync script |
Make sure you are logged in to YouTube in the browser you specified |