The problem
You are building an installation: three projectors across a wall, a row of screens in a museum, a display that has to change at the exact moment a light comes on. Each screen shows a different video, and they only work together if they stay in step for hours, every day.
Starting the same file on several machines at “the same time” doesn’t hold up. Clocks drift, decoders stall, a machine reboots, and after a while one screen is visibly behind. Streaming the video from one central computer avoids the drift but puts all your media on the network.
Separate players
Each host plays its file on its own clock. They start roughly together and slowly drift apart, and a restarted host never finds its place again.
With Syncplayer
Each host still decodes its own local file, but against one shared clock and one authoritative position from the Master. Late or restarted players join in step.
This has been measured, not just watched: four Raspberry Pis (one Master, three Slaves) played a looping three-cue program for over 17 hours. Across the analysed 17.16 hours the worst pairwise separation was 4.4 ms, the 99th percentile stayed under 0.4 ms, and no corrective resynchronization was ever needed. The soak report has the full numbers.
How it works
One host is the Master. It plays the program from its own clock, exports that clock on the network with GStreamer, and sends the current position several times a second. Every other host is a Slave: it locks onto the Master’s clock, plays its own rendition of the same cue, and corrects itself if it drifts. Video files never leave the machines they’re on.
- Describe the showWrite one TOML program: an ordered list of cues, and which local file each player shows for each cue.
- Validate on each host
syncplayer validateprobes every file, checks durations and prints the program's hash. - Start Master and SlavesSlaves connect, check they have the same program, calibrate the clock and start in step.
Built for unattended installations
No media on the network
Only clock, control and state messages cross the LAN. Each player can use a rendition suited to its own decoder, as long as the cues match in length.
Joins without interrupting
A Slave that starts late or reconnects gets a future release time and joins the running show without disturbing the Master or the others.
Keeps playing through outages
If the Master or the network disappears, Slaves free-run on their last clock estimate and reconnect in the background.
Corrects drift gently
Small sustained drift can be corrected by changing playback speed by at most 2%. Only large errors trigger a seek, and a frame watchdog exits a wedged player so its supervisor can restart it.
Timed events on each host
A cue can run a local command or send a UDP message at a set time, for example to dim the lights 12.5 seconds into the main video.
You can see how well it syncs
An optional console pauses, seeks and loops the whole installation, shows an on-screen overlay and reports each player's timing error. A CSV log and an analysis script give you the full picture.
What it supports today
Syncplayer’s MVP is feature-complete and the project calls itself a v1.0 release candidate. The remaining work is qualification and packaging rather than new features.
- Platforms: x86-64 Linux and 64-bit Raspberry Pi OS, built natively from source with Rust and GStreamer 1.20 or newer.
- Proven so far: the four-Raspberry-Pi topology described above, with hardware H.264 decoding.
- Still open for v1.0: one unexplained crash at the start of the soak test, deliberate network-loss and recovery testing, qualification on Ubuntu PCs and of the fullscreen and display paths, and a supported way to install and supervise it (packaging, a systemd unit).
- Deliberately not in v1: automatic Master election, media transfer between hosts, multicast, a GUI or web UI, and live program reloads.
Everything that’s done and still to do is tracked in the release checklist. All hosts must run the same Syncplayer build.
Get started
The console has no authentication. It is off by
default. When you enable it, bind it to 127.0.0.1 unless
you trust everyone on the installation network.
1. Build it on each host
On Ubuntu, Debian or Raspberry Pi OS (64-bit):
git clone https://github.com/arturormk/syncplayer.git
cd syncplayer
# Offers to install missing packages and Rust, then builds
./quickstart.sh
The script prints the path of the finished syncplayer executable; the
commands below assume it is on your PATH. It builds natively; there is
no cross-compilation.
2. Prepare the media
Give every video the same size, frame rate and audio layout. One script targets PCs and one targets the Raspberry Pi’s hardware decoder:
./tools/prepare-video-for-pc.sh -o media/main-pc.mp4 main.mov
./tools/prepare-video-for-rpi.sh -o media/main-rpi.mp4 main.mov
3. Write and validate the program
Put the same show.toml on every host, adjusting only the paths each
host plays:
version = 1
[program]
id = "simple-show"
loop = true
[[program.cue]]
id = "intro"
[[program.cue]]
id = "main"
[players.default.intro]
video = "media/intro.mp4"
[players.default.main]
video = "media/main.mp4"
syncplayer validate show.toml
4. Start the Master, then the Slaves
# On the Master
syncplayer master show.toml --player-id master \
--console-bind 127.0.0.1:2323
# On each Slave, using the Master's LAN address
syncplayer slave show.toml --player-id left-wall \
--master <master-ip>:46000
Allow TCP port 46000 and UDP port 46001 to the Master, and each Slave’s
UDP state port, through the host firewalls. Then type
nc 127.0.0.1 2323 on the Master and try status and diagnostics.
Ctrl-C stops a player cleanly.
Learn more
- README: every command-line option, the console commands and the synchronization behaviour in detail.
- Media preparation: formats, options and keeping per-host renditions in step.
- Example programs: aliases, per-player files, external audio and timed events.
- Design blueprint and architecture decisions: how and why it works the way it does.
- Issues: bug reports and questions.
Syncplayer is dual-licensed under MIT or Apache-2.0, at your option.