Getting started
By the end of this page, one machine runs the playlog-api server holding your watch history as an append-only event store.
One mpv device records playback into it, and a second device resumes exactly where the first one stopped.
The pieces speak one HTTP API and nothing else, so they mix freely: a NixOS-managed server with standalone mpv clients, or the reverse, or all-standalone.
Two install paths run through the steps below, and you pick one per machine.
The nix path adds playlog as a flake input and uses the NixOS module services.playlog on the server plus the Home Manager module services.playlog-mpv on each mpv device.
The standalone path builds the two Rust binaries with cargo, drops the Lua plugin into any mpv setup, and runs the example systemd user units for the sync agent.
Run the server
Section titled “Run the server”The server is a single binary with an SQLite database and no other moving parts.
Authentication is on by default: every /v1 request needs a bearer token, while /healthz stays open for liveness probes.
A fresh server therefore accepts no one until you issue the first token, which is the next step after it is running.
The nix path
Section titled “The nix path”Add the flake input once; both modules and the packages come from it.
# in your flake's inputsplaylog.url = "github:Joaqim/playlog";Then enable the service on the server machine, inside your NixOS configuration.
{ outputs = { self, nixpkgs, playlog }: { nixosConfigurations.media = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ playlog.nixosModules.server { services.playlog = { enable = true; bind = "0.0.0.0:7600"; }; } ]; }; };}The default bind is 127.0.0.1:7600, which only the server machine itself can reach; setting it to your LAN or VPN address (0.0.0.0 binds all interfaces) is what lets devices connect.
The module manages no firewall or interfaces, so opening the port to the right network is yours to arrange.
Events land in /var/lib/playlog/events.db by default; the stateDir and dbPath options move it.
The service starts protected with auth on and no tokens issued, which is the intended starting posture.
The standalone path
Section titled “The standalone path”Any Linux host with a Rust toolchain builds both binaries; only playlog-api is needed on the server.
git clone https://github.com/Joaqim/playlogcd playlogcargo build --releaseRun the server on the address your devices can reach.
target/release/playlog-api --bind 0.0.0.0:7600The full flag surface is small, and flags win over the environment:
--bind ADDR:PORTorPLAYLOG_BIND— listen address, default127.0.0.1:7600.--db PATHorPLAYLOG_DB— SQLite file, default~/.local/state/playlog/events.db; the parent directory is created at startup.--auth on|offorPLAYLOG_AUTH— bearer enforcement, defaulton.
Issue the first token
Section titled “Issue the first token”With auth on, /v1 answers 401 to everyone until a token exists, so the first order of business after boot is issuing one.
The token is printed exactly once, prefixed plt_, and stored only as a hash — keep it somewhere safe when it appears.
Standalone, run the same binary with the same --db the server uses; with the defaults, no flag is needed.
target/release/playlog-api token add --label living-roomOn NixOS, run it as root against the live database; the server can keep running while you do.
nix build github:Joaqim/playlog#playlog-apisudo ./result/bin/playlog-api token add --db /var/lib/playlog/events.db --label living-roomA token’s label doubles as its device identity: events sent with it are stored under that device_id, so issue one token per device and name the labels accordingly.
token list shows labels and issue times, and token revoke --label NAME retires one.
If the server is reachable only over a network you already trust end to end — a VPN whose every peer is yours — you can drop tokens entirely: --auth off standalone, or services.playlog.auth.enable = false; on NixOS.
With auth off the network is the whole trust boundary, so keep it on for anything reachable beyond such a network.
Connect an mpv device
Section titled “Connect an mpv device”Playlog’s mpv client is a Lua script; it appends events to a local spool file and never touches the network.
A sync agent ships the spool to the server and pulls the shared history into a local resume cache, which the script reads the next time a file loads.
The two paths below configure the same pieces.
The standalone path
Section titled “The standalone path”Install the script into any mpv setup.
mkdir -p ~/.config/mpv/scripts ~/.config/mpv/script-optscp client/playlog.lua ~/.config/mpv/scripts/Give the device a stable identity.
device_id=living-roomThat is the whole client configuration; the spool and resume cache default to ~/.local/state/playlog/, the same directory the sync agent reads and writes.
Put the credential and server address in a private environment file.
# ~/.config/playlog/token (chmod 600)PLAYLOG_SYNC_TOKEN=plt_...PLAYLOG_SYNC_URL=http://media.lan:7600Install the agent and the example user units shipped in the repository.
sudo install target/release/playlog-sync /usr/local/bin/cp sync/systemd/playlog-sync.* ~/.config/systemd/user/In ~/.config/systemd/user/playlog-sync.service, uncomment the EnvironmentFile=%h/.config/playlog/token line, and adjust ExecStart= if you installed the binary somewhere other than /usr/local/bin.
Then start the units.
systemctl --user daemon-reloadsystemctl --user enable --now playlog-sync.timer playlog-sync.pathThe path unit ships the spool within seconds of each appended event, and the timer sweeps once a minute as the fallback.
Play a video and events appear in ~/.local/state/playlog/spool.jsonl, then leave for the server on the next sync run.
The home-manager path
Section titled “The home-manager path”With the playlog input already added, bring the client module into a Home Manager configuration on the device.
homeConfigurations.living-room = home-manager.lib.homeManagerConfiguration { pkgs = nixpkgs.legacyPackages.x86_64-linux; modules = [ playlog.homeManagerModules.client { services.playlog-mpv = { enable = true; deviceId = "living-room"; sync.url = "http://media.lan:7600"; sync.tokenFile = "/home/you/.config/playlog/token"; }; } ];};The token file carries the same PLAYLOG_SYNC_TOKEN=plt_... line, mode 600, and is attached to the sync units for you.
There is deliberately no option taking the bare token string: a literal would land world-readably in the nix store, which is also why the path above is absolute rather than a flake-relative ./token.
The module installs the script, writes the script-opts from your deviceId, manages the spool, resume cache, and timer/path units, and enables programs.mpv unless you already manage it.
Add a second device
Section titled “Add a second device”Repeat the device steps on another machine with a different deviceId and its own token — token add --label bedroom on the server, then the matching id and token file on the device.
Both devices now feed one shared history, and resume positions derive from the newest event per item across all of them.
Nothing else changes; the labels keep the two devices distinguishable in the stored data.
Verify it works
Section titled “Verify it works”From anywhere that can reach the server, check liveness first.
curl http://media.lan:7600/healthzA 200 means the server and its database are up.
Then watch events arrive, using the token of the first device.
curl -H "Authorization: Bearer plt_..." "http://media.lan:7600/v1/events?after=0"Play something for at least half a minute on the first device, give the sync a moment, and the pull returns the session’s events.
For the cross-device payoff, open the same item on the second device: playback seeks to the freshest saved position and an on-screen line names the device and age it resumed from.
Resume only stages for positions past 30 seconds and under 95 percent of the duration, and only seeks when the saved position leads the live one by more than 10 seconds, so barely-started and nearly-finished items stay untouched.
Where to go next
Section titled “Where to go next”- Firefox add-on - track YouTube and Twitch in the browser, on desktop and Android
- Reading paths - curated sequences through the documentation based on your goals
- HTTP API - endpoints, authentication, and the event schema