OpenTogetherTube synchronises video playback across a room with no accounts
Watch videos with your friends. The spiritual successor to TogetherTube, preserving the spirit of it's simple to use interface, while improving it's look, feel, and reliability.
At a glance
- What is it?
- OpenTogetherTube is an AGPL licensed watch party server, a spiritual successor to TogetherTube, that keeps players in sync in real time across YouTube, Vimeo, direct MP4s, HLS and DASH streams, with vote modes, chat and SponsorBlock. It suits a group that wants a self hosted watch party, and it is a Node.js and Rust application that needs Redis and Postgres behind it.
- Who is it for?
- OpenTogetherTube fits a group that wants a watch party service it hosts itself, with no account registration for viewers and a room per event that can be shared by URL. It does not fit a team that wants a managed service, because the deployment is a Node.js server plus a Rust balancer in front of Redis and Postgres, and it does not fit anyone who needs the transport-level guarantee that a commercial video call provides, since synchronisation is best effort by nature.
- Can I use it commercially?
- Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A watch party with a room URL and no signup
OpenTogetherTube describes itself as the easy way to watch videos with your friends, and the project description frames it as the spiritual successor to TogetherTube, preserving the spirit of its simple to use interface while improving the look, feel and reliability. A live instance runs at opentogethertube.com.
The feature list explains the shape of the product. Real-time video synchronization with no account registration required, bookmarkable rooms with custom URLs for easy sharing, text chat, and SponsorBlock integration.
The absence of accounts is the design decision that matters most. A watch party is a temporary gathering, and making people create an account to watch a video with colleagues is friction for no benefit. Instead, a room exists at a URL you share, and people join it. The room is the unit of both content and access, and the permissions feature operates on rooms.
The rest of the list covers the mechanics of a shared session. Vote mode lets the group vote on what to watch next, and vote to skip lets them skip the current video. DJ mode is described as good for D&D background music, which tells you the model: one person drives, or the room votes, and the others follow.
There are multiple UI themes, and the project topics name Vue for the client, Node.js for the server, and video synchronization as the subject.
The source list is the hard part of watch party software
The list of playable sources is where a project like this spends its real effort, because every source is a different extraction mechanism with its own rules about embedding, embedding time and cross-origin playback.
OpenTogetherTube plays videos from YouTube, from Vimeo, from .mp4 files served over HTTP, from HLS VOD streams, from DASH VOD streams, and from custom media manifests, with a documentation page for the custom media format. The README also links a directory of the individual service implementations under server/services, with the note that it plays from those and some others.
That list matters more than it first appears. A watch party where the host can only drop in a YouTube link is a different product from one where the host can drop in a direct MP4 on their own server, a Twitch-style HLS stream, or a DASH manifest from a media company. The custom media manifest route is the escape hatch for anything not built in, and it is the reason a self hosted instance can be pointed at internal video.
The two VOD streaming protocols are the technically hardest of the set, since HLS and DASH both describe a video as a set of segments and playlists that have to be fetched and assembled, and a synchronised player has to be able to seek within that assembled timeline.
SponsorBlock integration is the other notable item. It skips the sponsor segments that exist in uploaded video, which is a shared annoyance rather than a personal one, and the fact that the skip is coordinated across a room is the interesting part.
A Node.js server and a Rust balancer, sharing types
The repository is a polyglot project, and the layout explains why.
The package.json declares a Yarn 4 workspace with four members: common, server, client and packages/*. The server is the Node.js side, the client is the Vue front end, and common holds the code both sides share. The root package is private and licensed AGPL-3.0-or-later, and the engines field asks for node 22 or newer up to 27 with yarn 4.
Then there is a Cargo.toml with a Rust workspace, members under crates/*, and a set of crates with revealing names: ott-common, ott-balancer and ott-balancer-protocol, plus a harness and harness_macros crate. The dependency list is a Rust web stack: rocket and rocket_ws for HTTP and WebSockets, tokio and tokio-tungstenite, reqwest with rustls-tls, hickory-resolver for DNS, figment for configuration from toml and env, prometheus for metrics, and criterion for benchmarking.
So the architecture is a Node.js application server with a Rust balancer in front of it, connected by a protocol crate that both sides share. typeshare appears in the dependency list, which is a tool for generating TypeScript types from Rust structs, and that is how the protocol types stay in step between the two languages.
The balancer's presence is the interesting design choice. Running one balancer for many application instances, backed by Redis for shared state, is how you get a horizontally scalable room server: any instance can accept a request for any room because the room state lives outside the instance. The docker-compose file confirms it, with a redis service and a postgres service alongside the app, and the server pointing at both through REDIS_HOST and POSTGRES_HOST.
What the compose file says about the deployment
The docker-compose file is the most concrete description of the runtime, and it is short.
version: "3.7"
services:
opentogethertube:
image: dyc3/opentogethertube:latest
container_name: opentogethertube
environment:
- PORT=8080
- REDIS_HOST=redis_db
- REDIS_PORT=6379
- FFPROBE_PATH=/usr/bin/ffprobeThree services, then. The application itself listens on 8080 and publishes that port, and it reaches Redis and Postgres over the compose network using the service names redis_db and postgres_db, with the depends_on listing both.
Redis is the shared state store, and it has a healthcheck of redis-cli ping with a named volume for its data. Postgres is pinned to postgres:15-bullseye, with a healthcheck of pg_isready, and its own volume.
The FFPROBE_PATH environment variable is the one that catches people out. The server shells out to ffprobe to probe media, so the binary has to exist in the image or on the host at the configured path. The volume mount of ./env onto /app/env is how the configuration directory gets into the container.
The image is published to Docker Hub as dyc3/opentogethertube, and the repository has a docker/ directory and a deploy/ directory, plus a docker-compose.yml at the root, so there is more than one way to run it.
The scripts describe the two ways to develop
The root package.json scripts are a map of the project's workflow, and they show the two-mode development setup.
"start": "yarn workspace ott-server start",
"serve": "yarn workspace ott-client serve",
"dev": "NODE_ENV=development concurrently \"yarn run api-server\" \"PORT=8080 yarn run serve\"",
"db:migrate": "yarn workspace ott-server run sequelize db:migrate",
"test:e2e": "playwright test",The dev script runs the API server and the client serve task at the same time with concurrently, with the client on port 8080, and there is a separate dev-windows variant that uses the Windows environment variable syntax, which tells you the project is developed on both platforms. The start:e2e script does the same pair for end-to-end tests, in development mode.
The database is handled through sequelize, with db:migrate and db:migrate:undo scripts, so the schema is managed by migrations in the server workspace rather than by hand.
Testing is layered. The test script runs the test task in every workspace, test:e2e runs Playwright, test:e2e:ui opens the Playwright UI mode, and test:component runs the client component tests. There is a playwright.config.ts at the repository root and a codecov.yml, so coverage is reported from CI.
Linting is a mix of tools: biome check for the bulk, prettier for .vue files, and a per-workspace lint step, with a separate lint-ci variant that runs the same checks without writing, which is what the CI workflow runs. There is also a typos.toml for a spell checker and a biome.jsonc at the root.
Patches, a devcontainer and the usual project furniture
A few root files tell you how the project expects to be worked on.
The postinstall script runs patch-package, and there is a patches/ directory, so the build applies patches to dependencies after install. That is a common pattern for a project that needs a small change in a dependency and prefers to carry the diff in the repository rather than wait for an upstream release.
The tooling set is broad. There is a Cargo.lock and a yarn.lock, so both the Rust and JavaScript halves are locked. There is a.devcontainer/ directory and a.gitpod.yml, so there is a ready made development environment for both VS Code and Gitpod, and a.agents/ directory alongside CLAUDE.md and AGENTS.md, which are the agent instruction files. There is a skills-lock.json, a scripts/ directory, a packages/ directory, and crates/ for the Rust crates, with a common/ directory at the root that is one of the Yarn workspaces.
The other root files are the ordinary ones for a project of this size: a.eslintrc.json alongside the biome configuration, a.prettierrc, a postcss.config.js for the client styles, an env/ directory for configuration, a prometheus.yml for the metrics endpoint, an announce.sh script, and CONTRIBUTING.md, SECURITY.md and CODE_OF_CONDUCT.md.
The most recent releases are v0.15.0 on 2026-06-04 and v0.14.1 and v0.14.0 from April 2024, and the last push was on 2026-10-22.
Where the project is the wrong tool
The honest limits are about what synchronisation over the internet can promise, and about the licensing.
A watch party server is not a video conferencing product. It distributes a video pointer and keeps players roughly in step, which means a viewer on a slow connection sees the same moment a few seconds late, and a viewer whose browser throttles a background tab falls out of step and needs to catch up. The advertised benefit is real, but the guarantee is best effort by construction, and a group that needs to discuss something at exactly the same instant is better served by a call.
The other limit is the AGPL-3.0-or-later licence, which is the strongest copyleft the project could have chosen. For a private family or group deployment that is irrelevant. For anyone embedding the server in a product, or running it as a service for others, the obligations attach to the whole work, and that conversation has to happen with a lawyer rather than with a licence file.
The source list is the third limit. If your video source is not in the list and does not fit the custom media manifest format, the room cannot play it, and no amount of server configuration changes that.
What the project does offer is the thing TogetherTube offered, without the service going away, with a room URL instead of an account, and with the media pipeline open for a self hoster to extend.
Editorial conclusion
OpenTogetherTube fits a group that wants a watch party service it hosts itself, with no account registration for viewers and a room per event that can be shared by URL. It does not fit a team that wants a managed service, because the deployment is a Node.js server plus a Rust balancer in front of Redis and Postgres, and it does not fit anyone who needs the transport-level guarantee that a commercial video call provides, since synchronisation is best effort by nature. Before deploying, read docs/how-to-deploy.md, copy the environment file and change the database credentials before the first start, and check the FFPROBE_PATH setting, because the server shells out to ffprobe for media probing.
Frequently asked questions
What is OpenTogetherTube and do viewers need an account?
It is an open source watch party server, the spiritual successor to TogetherTube, with real-time video synchronization and no account registration required. Rooms are bookmarkable with custom URLs, so a room is shared by link and text chat and voting are built in.
Which video sources can OpenTogetherTube play?
YouTube, Vimeo, .mp4 files served over HTTP, HLS VOD streams, DASH VOD streams and custom media manifests, with the individual service implementations under server/services.
What does OpenTogetherTube need to run?
The docker-compose file runs three services: the application on port 8080, a Redis instance for shared state and a Postgres 15 instance for the database. The application also needs ffprobe available at the path set by FFPROBE_PATH, and the schema is applied with the db:migrate script, which runs sequelize migrations.
What is the Rust part of OpenTogetherTube for?
The Cargo workspace holds ott-common, ott-balancer and ott-balancer-protocol crates alongside a harness, and the dependencies include rocket, rocket_ws, tokio-tungstenite and reqwest, so a Rust balancer sits in front of the Node.js application server, with typeshare keeping the protocol types in step across the two languages.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/dyc3-opentogethertube)Was this useful?