Watch videos together, perfectly in sync. No accounts, no hassle — just create a room, share the code, and enjoy.
- This was generated by Claude Opus4.6 and modified by myself
- my girlfriend was getting mad that we had speakers issues so I made this.
- Promising results nad custom CI/CD github actions pipeline for fun for going from problem to solution within 8 hours
- Synchronized Playback — Play, pause, and seek sync across all viewers in real time via WebSocket
- Multiple Video Sources — Load from a URL (
.mp4,.webm,.m3u8), upload a file, or set the video later - Screen Sharing — Share your screen or an application window with viewers via WebRTC peer-to-peer streaming (host only)
- Stream Quality Settings — Cogwheel menu to adjust video bitrate (500 kbps – 6 Mbps) and audio bitrate (64 – 510 kbps) during screen share
- Room System — Create rooms with a 6-character code, share with friends, no login required
- Live Chat — Built-in chat sidebar with nickname support
- Custom Controls — Full video player with progress bar, volume, playback speed, and fullscreen
- Help Tooltips — Hover over
?icons next to settings for contextual help with animated, backdrop-blurred tooltips - Keyboard Shortcuts — Space/K (play/pause), F (fullscreen), M (mute), Arrow keys (±10s seek)
- Responsive Design — Works on desktop and mobile
SnuggleStream/
├── app/
│ ├── __init__.py
│ ├── auth.py # Google OAuth2 authentication
│ ├── config.py # App settings and paths
│ ├── main.py # FastAPI app factory
│ ├── moderation.py # Chat moderation
│ ├── rooms.py # Room manager (in-memory state)
│ ├── security.py # Security middleware
│ ├── transcode.py # HLS transcoding (FFmpeg)
│ └── routers/
│ ├── __init__.py
│ ├── api.py # REST API endpoints
│ ├── auth.py # Auth routes
│ ├── pages.py # HTML page routes
│ └── ws.py # WebSocket sync + WebRTC signalling
├── static/
│ ├── css/
│ │ └── style.css # All styles (dark theme)
│ └── js/
│ ├── home.js # Home page logic
│ └── room.js # Room page + video sync + screen share
├── templates/
│ ├── index.html # Home page (create/join)
│ └── room.html # Room page (player + chat)
├── media/ # Uploaded videos (auto-created, gitignored)
├── requirements.txt
├── run.py # Dev entry point
├── .env.example
└── .gitignore
- Python 3.10+
# Clone or navigate to the project
cd SnuggleStream
# Create virtual environment
python -m venv venv
# Activate (Windows)
venv\Scripts\activate
# Activate (Linux/Mac)
source venv/bin/activate
# Install dependencies
pip install -r requirements.txtpython run.pyThe app starts at http://localhost:8000.
pip install -r requirements.txt
# Run with uvicorn directly
uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 1
# Or behind a reverse proxy (recommended):
# Use Nginx/Caddy to proxy http + websocket to port 8000Important: Use
--workers 1because rooms are stored in-memory. Multiple workers would create separate room stores. For scaling, add Redis-backed room storage.
server {
listen 80;
server_name your-domain.com;
client_max_body_size 2G;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400;
}
}| Variable | Default | Description |
|---|---|---|
HOST |
0.0.0.0 |
Bind address |
PORT |
8000 |
Server port |
ROOM_EXPIRY_HOURS |
24 |
Hours before empty rooms expire |
- Create a Room — Pick a name, optionally add a video URL or upload a file
- Share the Code — A 6-character room code is generated (e.g.,
ABC123) - Watch Together — Anyone who joins sees the same video at the same position
- Sync Events — When anyone plays, pauses, or seeks, a WebSocket message is broadcast to all other viewers
- Chat — Send messages in the sidebar chat
The host can share their screen or a specific application window directly with all viewers using WebRTC peer-to-peer connections.
- Select Screen Share as the source when creating a room, or switch to the Screen Share tab inside a room
- Audio is captured with high-quality settings (48 kHz stereo, no noise suppression)
- Use the cogwheel icon (⚙) in the player controls to adjust video and audio bitrate on the fly
- Viewers receive the stream automatically — no extra setup needed
- Screen share stops when the host clicks Stop or closes the browser share prompt
| Method | Path | Description |
|---|---|---|
GET |
/ |
Home page |
GET |
/room/{code} |
Room page |
POST |
/api/rooms |
Create a room |
GET |
/api/rooms |
List active rooms |
GET |
/api/rooms/{code} |
Get room info |
POST |
/api/upload |
Upload a video file |
WS |
/ws/{room_code} |
WebSocket sync connection |
MIT
A GitHub Actions workflow is included at .github/workflows/deploy.yml. Every push to main will SSH into your VPS, pull the latest code, install dependencies, and restart the service.
- A VPS running the app at
/opt/snugglestreamwith thesnugglestreamsystemd service - Git initialised on the VPS with your GitHub repo as the
originremote
SSH into your server and run:
cd /opt/snugglestream
git init
git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO.git
git fetch origin main
git reset --hard origin/mainOn your local machine:
ssh-keygen -t ed25519 -f ~/.ssh/deploy_key -N ""Add the public key to your VPS:
ssh-copy-id -i ~/.ssh/deploy_key.pub root@YOUR_SERVEROr manually:
cat ~/.ssh/deploy_key.pub | ssh root@YOUR_SERVER "cat >> ~/.ssh/authorized_keys"- Go to your repo → Settings → Secrets and variables → Actions
- Click New repository secret
- Name:
VPS_SSH_KEY - Value: paste the contents of
~/.ssh/deploy_key(the private key, including theBEGINandENDlines)
Open .github/workflows/deploy.yml and set your server's hostname and SSH user if they differ from the defaults.
Push to main and the workflow will:
1.#SSH into your server
3. git fetch + git reset --hard to the latest commit
4. pip install -r requirements.txt
5. systemctl restart snugglestream
GNU Affero General Public License v3
e
