GitHub Release License: MIT WebRTC Signaling: PeerJS Media Server: None PRs Welcome
Private, peer-to-peer virtual screening room and watch party platform in your browser.
Stream local video files, direct URLs, screen shares, and synchronized YouTube embeds in near-synchronous lock-step.
No application database. No central media relay server. Media streams directly between peers over encrypted WebRTC.
MovieNight β P2P Watch Party & Virtual Screening Room
Architecture β’ Capabilities β’ Sync Engine β’ Connectivity & NAT β’ Security & Threat Model β’ Limitations β’ Quickstart
Note
Project Status: MovieNight is an open-source, production-oriented prototype and experimental P2P screening application. It is designed and optimized for small private groups (tested 2β6 participants). An optional lightweight Node.js server component is provided strictly for CORS-restricted media proxying; no media or chat messages are stored on any server.
- π‘οΈ No Media Server Architecture: Video and audio frames are decoded in the browser and transmitted directly to peers using WebRTC DTLS/SRTP encryption. No video files or stream chunks are stored on or relayed through application servers.
- β‘ Signaling via PeerJS: Ephemeral room discovery, ICE candidate exchange, and SDP handshakes are coordinated via PeerJS cloud brokers (or self-hosted PeerServer). Once connected, the signaling server is bypassed for all media and room state.
- π₯ Optimized for Small Groups (2β6 Peers): Designed for private co-watching among friends without the infrastructure cost or complexity of an SFU (Selective Forwarding Unit) or MCU.
- π₯ Triple-Mode Playback Pipeline:
- Local Files: Browser hardware decodes
.mp4,.webm,.mov, or.mkvfiles and broadcasts media tracks viaHTMLMediaElement.captureStream(). - Direct URLs: HTML5
<video>playback with built-in HTTP Range proxy support to handle third-party servers that restrict CORS headers. - YouTube Embeds: Official YouTube IFrame Player API integration with bidirectional play, pause, and seek synchronization over WebRTC DataChannels.
- Local Files: Browser hardware decodes
- π 1080p WebRTC Negotiation Hints: Injects RFC 4566 SDP bandwidth targets (
b=AS:12000,b=TIAS:12000000,x-google-start-bitrate=6000) and encoder directives (degradationPreference = "maintain-resolution",contentHint = "detail") to optimize the browser pipeline for sharp text and 1080p video rather than default low-bitrate webcam tuning. - β±οΈ Continuous Drift Correction: Presenter clock heartbeats and latency compensation maintain near-synchronous playback with an automatic drift-correction threshold ($< 0.8$s).
- πΏ Interactive Cinema Stage: Responsive 16:9 cinema theater, webcam grid, live emoji reactions, Backrow chat, and procedural SVG avatars.
| Dimension | MovieNight Specification | Technical Implementation |
|---|---|---|
| Network Topology | Full Mesh (P2P) | Direct peer-to-peer WebRTC connections between all participants |
| Signaling Dependency | PeerJS Broker / Cloud | Initial SDP Offer/Answer handshake and ICE candidate exchange |
| Recommended Group Size | 2β6 participants | Mesh upload bandwidth scales linearly ( |
| Media Server Storage | None (0 MB stored) | Audio/video decoded locally; exists solely in peer browser memory |
| Application Database | None | Ephemeral room state; rooms dissolve when the last participant departs |
| Target Video Quality | Up to 1080p @ 30β60 fps | SDP bandwidth hints (b=AS:12000); bounded by client CPU and upload bandwidth |
| Target Audio Quality | Up to 256 kbps Stereo | RFC 7587 Stereo Opus negotiation (stereo=1; maxaveragebitrate=256000) |
| Sync Accuracy Target | Near-sync (drift $< 0.8$s) | 1000ms periodic heartbeat + network transit offset compensation |
| Transport Separation | SRTP (Media) + SCTP (Control) | Dedicated media tracks for video/audio; DataChannels for chat and sync |
| Access Control | Knock & Approval Admission | 6-character room codes (ABC-123) with host-approved entry modal |
| CORS Media Proxy | Optional Node.js Service | HTTP Range forwarder (/proxy-video) for external media URLs lacking CORS |
MovieNight strictly separates media streaming from control signaling:
- Media Plane (SRTP): Real-time audio and video tracks flow directly between peer browsers over encrypted DTLS-SRTP.
- Control Plane (SCTP DataChannel): Room events, chat messages, emoji reactions, and playback heartbeats flow across bidirectional WebRTC DataChannels.
+-----------------------------+
| PeerJS Cloud / Broker |
| (Signaling & ICE Discovery)|
+--------------+--------------+
|
Room Connection Request | SDP Offer / Answer & ICE
(Knock & Approval) | (Target 12 Mbps SDP Hints)
v
+-------------------------------------------------------------+
| |
v v
+--------+-------------+ +---------+-----------+
| PRESENTER / HOST | | PARTICIPANT 1 |
| | WebRTC P2P Mesh | |
| - video element |====================================>| - remote <video> |
| - captureStream() | SRTP: Video & Opus Audio | - Drift Corrector |
| - Clock Authority |------------------------------------>| - Chat & Reactions |
+--------+-------------+ SCTP: DataChannel Sync +---------+-----------+
| ^
| WebRTC P2P Mesh |
+=============================================================+
|
v
+--------+-------------+
| PARTICIPANT 2 |
| |
| - Webcams & Mics |
| - Backrow Chat |
+----------------------+
WebRTC was originally designed for low-bandwidth video calling, defaulting to conservative bitrates (300 kbps) and dropping resolution under CPU or network load. MovieNight optimizes this pipeline:
[Local Video / Web URL / YouTube]
β
βΌ
[Native HTML5 Video Element]
β
βββΊ video.captureStream() βββΊ track.contentHint = "detail"
β
βββΊ Native Decoded Stereo Audio (48 kHz)
β
βΌ
[PeerConnection setLocalDescription]
β
βββΊ SDP Munge: b=AS:12000 / b=TIAS:12000000 (12 Mbps ceiling hint)
βββΊ SDP Munge: x-google-start-bitrate=6000 (Instant HD fast ramp)
βββΊ SDP Munge: stereo=1; sprop-stereo=1; maxaveragebitrate=256000
β
βΌ
[Sender degradationPreference: "maintain-resolution"]
β
βΌ
[Encrypted SRTP Mesh Delivery (Target 1080p @ 30-60 fps)]
Note: SDP attributes serve as negotiation hints. Final delivered bitrate and frame rate adapt dynamically via WebRTC's congestion control algorithms based on available peer-to-peer network capacity and client hardware limits.
MovieNight achieves near-synchronized playback across different network connections without a central media server using an authoritative client clock with transit compensation.
PRESENTER PARTICIPANT
β β
β βββ 1. Emits Heartbeat (T = 1.0s) ββββββββββββββββββββββββΊ β
β { currentTime: 142.5, paused: false, timestamp: t0 } β
β β
β β βββ 2. Computes Transit Latency:
β β Ξt = (Date.now() - t0) / 1000
β β
β β βββ 3. Computes Target Position:
β β targetTime = currentTime + Ξt
β β
β β βββ 4. Evaluates Drift:
β β drift = |localTime - targetTime|
β β β’ If drift > 0.8s: seekTo(targetTime)
β β β’ If drift β€ 0.8s: ignore (prevent stutter)
β β
β βββ 5. Host Override (Pause / Seek / Play) βββββββββββββββ β
-
Heartbeat Beacon: The active presenter broadcasts a sync payload every 1000ms over the DataChannel:
$${ \text{currentTime}, \text{duration}, \text{paused}, \text{timestamp: Date.now()} }$$ -
Transit Latency Estimation: The participant calculates one-way transit delay:
$$\Delta t = \frac{\text{Date.now()} - \text{timestamp}}{1000}$$ -
Expected Position Calculation:
$$\text{targetTime} = \text{currentTime} + (\text{paused} ? 0 : \Delta t)$$ -
Drift Evaluation:
$$\text{drift} = |\text{localPlaybackTime} - \text{targetTime}|$$ -
Threshold breached (
$\text{drift} > 0.8\text{s}$ ): The participant smoothly seeks totargetTime. -
Within tolerance (
$\text{drift} \le 0.8\text{s}$ ): Normal playback continues uninterrupted, avoiding micro-stutters.
-
Threshold breached (
-
Late-Joiner Recovery: When a new viewer joins mid-screening, they receive the room's current state and apply
$\Delta t$ to jump immediately to the current playback position. - Host Override Authority: If the room host is not the presenter, host playback actions (play, pause, seek) broadcast high-priority control packets that override local client states.
MovieNight relies on WebRTC's Interactive Connectivity Establishment (ICE) protocol:
- STUN Discovery: Uses public Google STUN servers (
stun:stun.l.google.com:19302) to discover external public IP addresses and UDP port mappings. - NAT Traversal Capability: Successfully establishes direct peer-to-peer connections across Full-Cone NAT, Address-Restricted NAT, and Port-Restricted NAT configurations (standard for most home broadband and consumer Wi-Fi networks).
- Symmetric NAT & Enterprise Firewalls: When two connecting peers are both behind symmetric NATs (common in corporate, university, or strict cellular networks), direct UDP socket pairs cannot be negotiated using STUN alone. In such environments, a TURN relay server (RFC 5766) is required. Users deploying in enterprise environments can configure custom TURN credentials in the PeerJS connection options.
| Feature | Chrome / Edge (v90+) | Firefox (v95+) | Safari macOS (v15+) | Mobile Safari / Chrome |
|---|---|---|---|---|
| Local File Streaming | β Full Support | β Full Support | β Restricted (File picker/upload limitations) | |
| Direct URL Streaming | β Full Support | β Full Support | β Full Support | |
| YouTube Embed Sync | β Full Support | β Full Support | β Full Support | |
| Screen / Tab Sharing | β Full Support | β Full Support | β OS Restricted | |
| Webcam & Mic Mesh | β Full Support | β Full Support | β Full Support | |
| SDP Bandwidth Munging | β Full Support |
- In-Transit Encryption: All peer-to-peer audio, video, and DataChannel payloads are encrypted end-to-end using browser-enforced DTLS (Datagram Transport Layer Security) and SRTP (Secure Real-time Transport Protocol).
- Ephemeral In-Memory Operation: MovieNight operates with zero database persistence. Room codes, participant lists, and chat messages exist solely in the browser memory of active participants and vanish when the room closes.
- Admission Control: Private screening rooms require the host to explicitly approve each participant via a Knock-and-Approval modal.
| Threat Vector | Risk Level | Mitigation Strategy |
|---|---|---|
| Room Code Guessing / Brute Force | Medium | Rooms use random 6-character alphanumeric codes. Even if guessed, the host must manually admit the participant via the admission modal. |
| Malicious External Video URLs | Low/Medium | Video URLs are loaded into standard HTML5 <video> elements or sandboxed YouTube <iframe> elements. No user-supplied scripts are evaluated. |
| Signaling Broker Metadata | Low | The public PeerJS signaling server observes connection metadata (IP addresses, peer IDs) during handshake. For total network autonomy, self-host PeerServer. |
| CORS Proxy Abuse | Low/Medium | The Node.js /proxy-video endpoint validates URL protocols (http:, https:), enforces a maximum of 5 redirects, and only proxies binary media streams. It is not an open general-purpose proxy. |
Warning
Proxy Scope Notice: The included Node.js /proxy-video proxy forwards HTTP Range requests with CORS headers to enable video capture. It is a streaming forwarder, not an antivirus, deep-packet-inspection, or content-sanitization firewall. Do not paste untrusted URLs from unknown sources.
-
P2P Mesh Bandwidth Scaling: Because MovieNight uses a peer-to-peer mesh rather than an SFU relay, the presenter's computer must upload separate video streams to every viewer (
$N-1$ outbound streams). A 1080p stream at 6 Mbps with 4 viewers requires$\approx 24$ Mbps upload bandwidth. For this reason, MovieNight is designed for small groups (2β6 users). -
Codec Compatibility: Local file playback relies on native browser decoder support. Standard
.mp4(H.264 / AAC) and.webm(VP8/VP9 / Opus) work seamlessly across all platforms. Proprietary formats such as HEVC/H.265 or Dolby DTS audio may not decode in browsers lacking hardware licenses. - Autoplay Policies: Modern browsers prohibit media from playing with sound automatically without prior user interaction. Remote YouTube embeds and direct streams start muted by default; viewers must click once to enable audio.
- Mobile Background Throttling: Mobile operating systems (iOS and Android) pause WebRTC video processing and camera tracks when the browser tab is sent to the background.
- Node.js 18+ (or any static HTTP server)
- Modern web browser with WebRTC support
git clone https://github.com/sagegallant/movienight.git
cd movienight
npm installnpm start
The server will start at http://localhost:3000.
- Open
http://localhost:3000in your browser. - Enter your display name, choose an avatar, and click START A NEW SCREENING.
- Share the room code (
ABC-123) or direct link (http://localhost:3000/?room=ABC123) with a friend. - When your friend joins, click Admit on the host modal.
- Click STREAM A VIDEO to drop a local movie or paste a direct media URL!
MovieNight can run purely client-side on GitHub Pages. The repository includes an automated GitHub Actions deployment workflow (.github/workflows/deploy.yml):
- Fork this repository.
- Navigate to Settings > Pages.
- Under Build and deployment > Source, select GitHub Actions.
- Push to
mainto trigger the deployment.
Note: GitHub Pages deployment operates without the Node.js CORS proxy. Local video files, synchronized YouTube embeds, and CORS-enabled CDN links work seamlessly.
To enable streaming of external video URLs whose origin servers block CORS:
# Production setup with PM2 npm install -g pm2 pm2 start server.js --name "movienight" pm2 startup pm2 save
| Key | Action |
|---|---|
| Space / K | Toggle Play / Pause |
| F | Toggle Fullscreen Cinema Mode |
| M | Toggle Video Mute / Unmute |
| C | Toggle Backrow Chat Sidebar |
| β | Seek forward 10 seconds |
| β | Seek backward 10 seconds |
| Esc | Close modals / Exit fullscreen |
- Core: Vanilla ECMAScript (ES6+), HTML5 Semantic Markup, Modern CSS3
- Networking: WebRTC (Real-Time Communication) & PeerJS
- Media Engine: HTML5 Media CaptureStream API, Web Audio API, RFC 4566 SDP Munging
- Player Sync: YouTube IFrame Player API with bidirectional DataChannel message synchronization
- CORS Proxy: Node.js
http/httpsstreaming pipelines with HTTP Range request forwarding - Icons & Visuals: Font Awesome 6, Procedural generative SVG avatars
MovieNight adheres to Semantic Versioning. See the complete version history in CHANGELOG.md.
Contributions are welcome! Whether filing bug reports, improving documentation, or optimizing the WebRTC sync engine:
- Fork the Project.
- Create your Feature Branch (
git checkout -b feature/AmazingFeature). - Commit your Changes (
git commit -m 'feat: Add AmazingFeature'). - Push to the Branch (
git push origin feature/AmazingFeature). - Open a Pull Request using our PR Template.
Distributed under the MIT License. See LICENSE for details.