Development Setup
Prerequisites
- Docker and Docker Compose
- Just — command runner (
cargo install justor other methods) - .NET 9.0 SDK (for plugin development)
- Rust 1.83+ (for server development)
- Node.js 20+ (optional, for JS tooling)
- mold (recommended, for faster Rust linking)
Quick Start
# Clone the repository
git clone https://github.com/mhbxyz/OpenWatchParty.git
cd OpenWatchParty
# Configure git hooks (required once after clone)
just setup
# Start development environment
just up
This will:
- Start Jellyfin on
http://localhost:8096 - Build and mount the plugin
- Start the Rust session server on
http://localhost:3000 - Auto-inject the client script into Jellyfin’s
index.html
First-Time Setup
1. Jellyfin Configuration
After running just up:
- Open
http://localhost:8096 - Complete the Jellyfin setup wizard
- Add a media library (can use sample media)
- Create a user account
2. Plugin Configuration (Optional)
- Go to Dashboard > Plugins > OpenWatchParty
- Configure JWT Secret if testing authentication
- Save and restart Jellyfin
3. Verify Installation
- Play any video
- Look for the Watch Party button in the header
- Click to open the panel
Project Structure
OpenWatchParty/
├── src/
│ ├── clients/
│ │ └── jellyfin-web/ # JavaScript client modules
│ │ ├── plugin.js # Loader/entry point
│ │ ├── state.js # State management
│ │ ├── utils/ # Utility functions
│ │ │ ├── log.js, media.js, misc.js
│ │ │ ├── time.js, video.js
│ │ ├── ui/ # User interface
│ │ │ ├── cards.js, home.js, indicators.js
│ │ │ ├── render.js, styles.js, toasts.js
│ │ ├── playback/ # Video sync
│ │ │ ├── bind.js, play.js, sync.js
│ │ ├── chat/ # Text chat
│ │ │ ├── input.js, messages.js
│ │ ├── ws/ # WebSocket
│ │ │ ├── auth.js, connection.js, send.js
│ │ │ └── handlers/
│ │ │ ├── clock.js, playback.js
│ │ │ ├── room.js, sync.js
│ │ └── app/ # Initialization
│ │ ├── cleanup.js, lifecycle.js
│ │
│ ├── plugins/
│ │ └── jellyfin/
│ │ └── OpenWatchParty/ # C# Jellyfin plugin
│ │ ├── Plugin.cs
│ │ ├── Controllers/
│ │ ├── Configuration/
│ │ └── Web/ # Bundled JS (copied from clients/)
│ │
│ └── server/ # Rust WebSocket server
│ ├── src/
│ │ ├── main.rs
│ │ ├── types.rs
│ │ ├── routes.rs
│ │ ├── tasks.rs
│ │ ├── messaging.rs
│ │ ├── auth.rs
│ │ ├── utils.rs
│ │ ├── ws/
│ │ │ ├── mod.rs, connection.rs, dispatch.rs
│ │ │ ├── constants.rs, validation.rs, pending_play.rs
│ │ │ └── handlers/
│ │ │ ├── auth.rs, chat.rs, create.rs
│ │ │ ├── join.rs, misc.rs, playback.rs
│ │ └── room/
│ │ ├── mod.rs, leave.rs, close.rs
│ └── Cargo.toml
│
├── .githooks/
│ └── pre-commit # cargo fmt check on staged .rs files
│
├── infra/
│ ├── docker/ # Docker configuration
│ │ ├── server.Dockerfile
│ │ ├── dev/
│ │ │ ├── docker-compose.yml # Dev environment
│ │ │ ├── config/ # Jellyfin runtime config (gitignored)
│ │ │ └── scripts/
│ │ │ └── jellyfin-entrypoint.sh
│ │ └── prod/
│ │ └── docker-compose.yml # Prod / release builds
│ └── just/ # Just modules
│ ├── common.just # Shared variables
│ ├── build.just
│ ├── test.just
│ ├── lint.just
│ ├── logs.just
│ ├── clean.just
│ └── shell.just
│
├── docs/ # Documentation
│
├── justfile # Build automation
├── CLAUDE.md # AI assistant context
└── README.md # Project overview
Commands
Run just for a full list (with submodules). Key commands:
Development
| Command | Description |
|———|————-|
| just up | Start full development environment |
| just down | Stop all services |
| just dev | Start stack and follow logs |
| just restart | Restart all services |
| just watch | Watch JS files and auto-restart on change |
| just status | Show service status with health checks |
Build (just build ...)
| Command | Description |
|———|————-|
| just build | Build the Jellyfin plugin (default) |
| just build plugin | Build the Jellyfin plugin |
| just build server | Build the session server locally (Rust) |
| just build image | Rebuild session server Docker image |
| just build all | Build everything (plugin + server image) |
| just rebuild | Clean + rebuild + restart everything |
| just release | Build release artifacts (zip) |
Testing (just test ...)
| Command | Description |
|———|————-|
| just test | Run all tests (Rust + .NET) |
| just test server | Run Rust server tests |
| just test plugin | Run .NET plugin tests |
Linting (just lint ...)
| Command | Description |
|———|————-|
| just lint | Run all linters (Rust + JS) |
| just lint server | Lint Rust code (clippy) |
| just lint client | Lint JavaScript (eslint) |
| just fmt | Format all code |
| just check | Run cargo check (fast compile check) |
Logs (just logs ...)
| Command | Description |
|———|————-|
| just logs | Follow logs from all services |
| just logs server | Follow session server logs |
| just logs jellyfin | Follow Jellyfin logs |
Clean (just clean ...)
| Command | Description |
|———|————-|
| just clean | Clean all build artifacts |
| just clean plugin | Clean plugin build artifacts |
| just clean server | Clean server build artifacts |
| just clean docker | Remove Docker images and volumes |
| just reset | Full reset (containers + artifacts) |
Shell (just shell ...)
| Command | Description |
|———|————-|
| just shell server | Open shell in session server container |
| just shell jellyfin | Open shell in Jellyfin container |
Quick aliases: u=up, d=down, s=status
Development Workflow
JavaScript Client
- Edit files in
src/clients/jellyfin-web/ - Rebuild and restart:
just rebuild - Hard refresh browser (Ctrl+F5)
Tip: Use just watch to automatically restart Jellyfin when JS files change.
Rust Session Server
- Edit files in
src/server/src/ - Rebuild everything:
just rebuild
C# Plugin
- Edit files in
src/plugins/jellyfin/OpenWatchParty/ - Build and restart:
just rebuild
Hot Reload
JavaScript
Use just watch for automatic reload on JS file changes. Otherwise:
- Run
just rebuild - Hard refresh browser (Ctrl+F5)
Rust
The session server needs restart after changes:
just rebuild
For faster iteration, run locally:
cd src/server
cargo watch -x run
C# Plugin
Requires rebuilding and restarting Jellyfin:
just rebuild
Debugging
JavaScript (Browser)
- Open Developer Tools (F12)
- Go to Console tab
- Filter by “OWP”
- Set breakpoints in Sources tab
Useful console commands:
// View current state
console.log(OWP.state);
// Check WebSocket connection
console.log(OWP.state.ws?.readyState);
// View rooms
console.log(OWP.state.rooms);
Rust (Server)
Enable debug logging:
# docker-compose.yml
environment:
- LOG_LEVEL=debug
Or use RUST_LOG:
RUST_LOG=debug cargo run
C# (Plugin)
Check Jellyfin logs:
docker logs jellyfin-dev
Or enable debug logging in Jellyfin settings.
Testing Changes
Manual Testing
- Open Jellyfin in two browser windows
- Play the same video in both
- Create a room in one window
- Join from the other window
- Test sync functionality
Sync Testing
Things to test:
- Room creation
- Room joining
- Play/pause sync
- Seek sync
- Drift correction (watch for 5+ minutes)
- Disconnect/reconnect
- Host leaving
Common Development Issues
Script Not Updating
- Clear browser cache (Ctrl+Shift+Delete)
- Hard refresh (Ctrl+F5)
- Check ETag is changing:
curl -I http://localhost:8096/OpenWatchParty/ClientScript
Tip: Use just watch to automatically restart Jellyfin when JS files change, avoiding stale cache issues.
For other issues (plugin not loading, WebSocket connection problems, build errors), see the Troubleshooting Guide.
Build Optimization (Rust)
The Rust server has optimized build configuration for faster development cycles.
Docker Build Modes
The Dockerfile supports a BUILD_MODE argument:
| Mode | Usage | Optimization |
|---|---|---|
dev |
Local development (docker-compose.yml) |
Fast builds, debug symbols |
release |
CI/CD and production | Full optimization, smaller binary |
Development builds use BUILD_MODE=dev by default. CI releases use BUILD_MODE=release.
Mold Linker (Recommended)
Install the mold linker for 5-10x faster linking:
# Arch Linux / Manjaro
sudo pacman -S mold
# Ubuntu/Debian
sudo apt install mold
# macOS (via Homebrew)
brew install mold
The project’s .cargo/config.toml automatically uses mold when available.
Cargo Configuration
Located in src/server/.cargo/config.toml:
[build]
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
[profile.dev]
incremental = true
opt-level = 0
[profile.dev.package."*"]
opt-level = 2 # Optimize dependencies (they rarely change)
Tokio Features
The server uses minimal tokio features to reduce compile times:
# Only what's needed (instead of "full")
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time", "signal"] }
Build Times
| Build Type | Without Optimization | With Optimization |
|---|---|---|
| Clean build | ~4-5 min | ~2-3 min |
| Incremental rebuild | ~15-20s | ~2-3s |
IDE Setup
VS Code
Recommended extensions:
- rust-analyzer - Rust support
- C# Dev Kit - C# support
- ESLint - JavaScript linting
- Docker - Docker support
.vscode/settings.json:
{
"rust-analyzer.cargo.buildScripts.enable": true,
"editor.formatOnSave": true
}
JetBrains
- RustRover for Rust
- Rider for C#
Environment Variables
For local development, create .env file:
# .env
JWT_SECRET=dev-secret-at-least-32-characters-long
ALLOWED_ORIGINS=http://localhost:8096
LOG_LEVEL=debug
Next Steps
- Contributing - How to contribute
- Testing - Running tests
- Architecture - System design