Local-only toy sync: rhythm prediction in 7 KB of browser JavaScript
Every live toy-sync we could find shares two flaws: it lags behind the scene, and it phones home. We built one that does neither. It runs entirely in the viewer’s browser, drives a Lovense toy over the viewer’s own LAN, and makes zero server calls during playback. This page explains how.
The problem: live sync is always late
Scripted sync (hand-authored patterns per scene) feels good because the script knows the future — the motor can leave early. But scripts don’t exist for a large, changing catalogue. Live sync reacts instead of anticipating: analysis → network → Bluetooth → motor adds up to 150–300 ms, and cloud-routed sync adds an internet round-trip on top. The result is the familiar “slight delay depending on your internet speed”.
Four ideas that fix it
- Own the audio pipeline. A Web Audio DelayNode holds what the viewer hears back by 100 ms (inaudible for lip-sync; ITU-R BT.1359 tolerates ~125 ms). The analyser taps the signal before the delay, so commands are already in flight when the sound reaches the ear — most of the Bluetooth latency cancels out.
- Watch the picture too. The engine reads motion energy from a tiny downscaled frame (mean luma delta, 10 Hz). MSE-backed video doesn’t taint a canvas, so this is possible precisely because we own the player. Sound and motion each get their own normaliser; the fusion is a max — action on either channel is action. A muted viewer still gets sync.
- Predict the rhythm. Autocorrelation over a four-second envelope finds the period (0.3–2 s) of a rhythmic scene; after three stable measurements the engine is locked and the next second is a phase-fold of the last cycles — a repetition, not an extrapolation. Locked predictions are rendered through a small library of designed pulse shapes and sent ahead as pattern blocks that the toy executes on its own clock. Network jitter stops mattering.
- Commands that expire. Every command is short-lived and continuously refreshed. Close the tab, lose Wi-Fi, scroll away — the toy falls silent within a second or two on its own. “Stuck buzzing” is structurally impossible, and five consecutive failed sends flip the on-screen meter to zero instead of animating against a dead bridge.
The numbers
| Engine size | 7.3 KB bundled (3.2 KB gzip) |
| Runtime dependencies | 0 |
| Server calls during playback | 0 — commands go over your own Wi-Fi |
| Account required | None — the vendor app is only the Bluetooth bridge |
| CPU cost | ≈78 ns per 10 Hz tick; 7.5 µs per prediction cycle |
| Worst-case stop after failure | ≤1 s reactive, ≤2 s pattern mode |
The privacy model
Pairing uses the vendor’s QR / Game-Mode code, so the vendor app acts purely as a Bluetooth bridge — no account on our side, and we never learn that a toy was paired. After pairing, commands travel directly over the viewer’s LAN. What someone watches never leaves their device; that’s consistent with the rest of the site, where recommendations run client-side and the server never sees taste data.
Honest limits
- It follows sound and motion — it is not frame-accurate scripted sync, and we don’t claim otherwise.
- Bluetooth dropouts between the vendor app and the toy are the vendor’s domain; we can only detect a dead bridge honestly, not prevent it.
- Today it speaks the Lovense Standard API; the engine is vendor-neutral behind a small output adapter, so other devices are an adapter away.
- Validated end-to-end on the mechanism; skin-level feel is being field-tested.
The engine runs in production on kinoloom.com — an adult streaming search engine (18+, currently EU-only; this technical page is global). Pair a toy and add ?debug=1 to any video URL to watch the rhythm lock happen live.