Naar inhoud

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

  1. 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.
  2. 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.
  3. 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.
  4. 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 size7.3 KB bundled (3.2 KB gzip)
Runtime dependencies0
Server calls during playback0 — commands go over your own Wi-Fi
Account requiredNone — 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.