VideoTogether is a private movie sharing project designed for two people in the same LAN.
The host imports a local video file, processes it into HLS with ffprobe and ffmpeg, creates a private room link, and shares that link with a second viewer. The viewer does not need the desktop app and can join directly from a browser.
The current primary workflow is: run locally, share quickly, and keep playback synchronized for two people.
Implemented today:
- Local video import
- Media probing with
ffprobe - HLS transcoding and segmentation with
ffmpeg - Subtitle import and conversion for
.srt,.vtt, and.ass - Private room creation and shareable links
- Browser-based room playback
- Two-person synchronized
play / pause / seek - Room-level subtitle selection sync
- Participant presence and reconnect recovery
- Tauri desktop host dashboard
- One-command host startup flow
apps/
desktop/ Tauri desktop host dashboard
server/ Fastify + Socket.IO + SQLite + FFmpeg backend
web/ Browser playback and room UI
packages/
shared-types/
shared-schemas/
shared-utils/
storage/
media/ uploaded source videos
hls/ generated HLS manifests and segments
subtitles/ converted subtitle files
db/ SQLite database
temp/ temporary processing directory
infra/
docker-compose.yml
scripts/ startup, shutdown, and packaging scripts
You can install VideoTogether directly on Windows:
- Download the latest Windows installer (
.exe) from the Releases page. - Run the installer and follow the instructions.
Configure your ip inLaunch the app and configure your.env.exampleLAN IP- Start watching videos with your friends!
Open the terminal of your choice, say, powershell. Input the command:
> ipconfigYou'll get your ipv4 address in the result that looks something like this:
Ethernet adapter Ethernet:
Connection-specific DNS Suffix . :
IPv6 Address. . . . . . . . . . . : xxxx:xxxx:xxxx:xxxx::xxxx
Link-local IPv6 Address . . . . . : fe80::xxxx:xxxx:xxxx:xxxx%xx
IPv4 Address. . . . . . . . . . . : 10.x.x.x
Subnet Mask . . . . . . . . . . . : 255.255.240.0
Default Gateway . . . . . . . . . : fe80::xxxx:xxxx:xxxx:xxxx%xx
10.x.x.x
You need to copy the IPv4 address, and paste it into enter it into the .env.exampleLAN IP text box:
...
LAN_IP=10.x.x.x
...And after that, you're done!
As is stated later, zerotier is required to share the movie with someone not in the same physical LAN. In this case, you'll need to :
- Download zerotier
- Join the same virtual LAN with whom you'd like to share the movie with
- Set the
LAN_IPin.env.exampleas the ip you assigned yourself in the zerotier panel.
Once you've successfully configured zerotier, you should see something like this after running ipconfig:
Ethernet adapter ZeroTier One [xxxxxxxxxxxxxxxx]:
Connection-specific DNS Suffix . :
Link-local IPv6 Address . . . . . : fe80::xxxx:xxxx:xxxx:xxxx%xx
IPv4 Address. . . . . . . . . . . : 10.x.x.x
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 25.x.x.x
The IPv4 in this result should match the ip you assigned yourself in zerotier.
You need these on the host machine:
- Node.js 22+
- npm 11+
ffmpegffprobe- Rust / Tauri Windows build prerequisites
- required for desktop development or packaging
- Docker Desktop
- only required when
USE_DOCKER=true
- only required when
Start from .env.example.
Important variables:
USE_DOCKER- controls whether the host startup script uses Docker for the server
- default:
false
HOST- server bind address
- use
0.0.0.0if guests on your LAN or ZeroTier need to connect - default:
0.0.0.0
PORT- local server port used by the production-like host flow
- default:
3000
PUBLIC_PROTOCOL- protocol used when deriving public share URLs
- default:
http
PUBLIC_HOST- hostname used when deriving public share URLs
- default:
localhost
WEB_DEV_PORT- Vite development server port used by
npm run dev - default:
5173
- Vite development server port used by
LAN_IP- fixed IPv4 used by the desktop app when generating LAN room URLs
- set this to the exact IPv4 you want to expose, such as your ZeroTier IPv4
FFMPEG_PATH- path to the
ffmpegexecutable
- path to the
FFPROBE_PATH- path to the
ffprobeexecutable
- path to the
Current .env.example defaults assume:
- development web URL:
http://localhost:5173 - production-like local host flow:
http://localhost:3000 - Docker disabled by default
- LAN URL generation uses only the explicit
LAN_IPvalue
From the repository root:
npm installnpm run host:startThis command will:
- build the production
webassets - build the production
serverassets - start the local production server
- launch the Tauri desktop host dashboard
Default behavior:
- Docker is not used
- share links point to
http://localhost:3000 - LAN room URLs are only generated from
LAN_IP
To stop the host flow:
npm run host:stopThis stops the background local server process tree started by host:start.
If you only want the local server and do not want to launch the desktop app:
npm run host:start -- -SkipDesktopRecommended host flow inside the desktop app:
- Select a local video file, or reuse an existing media item from the library.
- Wait for processing to complete.
- Optionally upload subtitle files.
- Choose the room's initial subtitle.
- Set the host display name and room expiration.
- Create the room.
- Copy the share link and send it to the second viewer.
The desktop app also provides:
- processing status
- media metadata
- room status
- participant presence
- subtitle updates
- room closing
- unused media deletion
- local URL and LAN URL copy actions
If you want the desktop app to generate LAN URLs, set LAN_IP to the exact IPv4 you want to use, for example your ZeroTier IPv4. The app does not auto-detect LAN IPs.
If the host can open the room locally but a guest cannot, first check that HOST=0.0.0.0 and then send the guest the LAN room URL instead of a localhost URL.
The guest does not need the desktop app.
They only need to:
- Open the room link in a browser.
- Enter a display name.
- Join the room.
- Watch the synchronized stream.
If you want host:start to run the server through Docker, set:
USE_DOCKER=trueThen run the same command:
npm run host:startIn this mode the script will:
- use
infra/docker-compose.yml - run the server inside a container
- mount the local
storage/*directories - still run the desktop app on the host machine
If you want to work with separate dev processes for web, server, and desktop:
npm run dev:server
npm run dev:web
npm run dev:desktopDefault development endpoints:
- API:
http://localhost:3000 - Web:
http://localhost:5173 - Desktop dev shell:
npm run dev:desktop
npm run host:start- builds the production web and server bundles, starts the local host server, and launches the Tauri desktop dashboard
npm run host:stop- stops the background server process tree started by
host:start
- stops the background server process tree started by
npm run host:start -- -SkipDesktop- starts the local host server only, without launching the desktop app
npm run package:desktop- builds the Tauri desktop installer bundles
npm run dev:server- runs the Fastify server in development watch mode
npm run dev:web- runs the Vite web app in development mode
npm run dev:desktop- runs the Tauri desktop app in development mode
npm run build:host- builds production assets for
apps/webandapps/server
- builds production assets for
npm run lint- runs ESLint across all workspaces that define a lint script
npm run typecheck- runs TypeScript type checking across all workspaces without emitting build artifacts
Health and diagnostics:
GET /healthGET /api/system/status
Room endpoints:
POST /api/roomsGET /api/rooms/:tokenPOST /api/rooms/:token/joinPOST /api/rooms/:token/subtitlePOST /api/rooms/:token/close
Media endpoints:
GET /api/mediaPOST /api/media/importGET /api/media/:idDELETE /api/media/:idPOST /api/media/:id/processPOST /api/media/:id/subtitlesGET /api/media/:id/subtitles
Static playback assets:
GET /media/:mediaId/*GET /subtitles/:subtitleId.vttGET /GET /room/:token
To share video with people not within the same physical LAN, you can use projects like zerotier
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
This project uses FFmpeg which is licensed under the GNU Lesser General Public License (LGPL) version 2.1 or later.
