Sync to video and audio
A toy track is a soundtrack for the toy: strength over time. Hand it a <video> or <audio> element and the toy follows the player: play, pause, seek, buffering and speed changes included. It's how Velvet Stories keeps toys in time with its videos.
Play a track
const track = {
points: [
{ at: 0, strength: 0 }, // at: ms, strength: 0–100
{ at: 2000, strength: 40 },
{ at: 5000, strength: 90 },
{ at: 8000, strength: 0 },
],
};
const video = document.querySelector('video');
const playback = toy.playTrack(track, { media: video });
await playback.done; // the track ended, or something stopped it
Strength glides in a straight line from point to point. Before the first point and after the end, the toy is off.
playTrack() doesn't start the video: it follows it. While the player is paused, seeking or buffering, the toy rests. Small timing jitter is smoothed out, and a jump of more than about ¾ s (a seek) snaps to the new spot with a short ramp in.
Options
| Option | Meaning |
|---|---|
media | The <video> or <audio> to follow (anything with currentTime and paused works). Without it, the track plays on its own clock |
startAtMs | Without media: where to start. Default 0 |
offsetMs | −5000–5000. Positive plays the toy earlier, to make up for Bluetooth or audio delay. Change it while playing with playback.setOffset(ms) |
motor | Dual-motor toys: 1, 2 or 'all' (default) |
playback.positionMs is the track time last played. playback.stop() stops the toy.
Funscript
Funscript is the community format for scripted toy content, and thousands of scripts already exist for videos. Turn one into a track with one call:
import { fromFunscript } from '@velvetrituals/toys';
const script = await fetch('/scripts/scene.funscript').then((r) => r.text());
const track = fromFunscript(script);
toy.playTrack(track, { media: video });
| Option | Meaning |
|---|---|
mode | 'position' (default): the script's position is the strength, right for vibration scripts (.vib.funscript). 'speed': stroke speed is the strength, better for stroker scripts |
axis | Which axis of a multi-axis script to read. Default: the vibration axis V0 if there is one |
gain | Multiplies every strength (0–2, default 1) |
toFunscript(track) goes the other way: save the result as .vib.funscript and community players can play your tracks.
Safety
- A track is one motor command: the next
vibrate(),preset(),pattern()orplayTrack()replaces it, andtoy.stop()ends it. - Tracks can run up to 4 hours. Instead of the usual 60-second cap they have a watchdog: if the SDK stops getting samples for a second (a frozen tab, a crashed player), the toy stops.
- Hidden-page rules and your key's
maxStrengthapply as for every command (see Safety). - Keep a Stop button on screen while a track plays.
Tips
- Start with
offsetMs: 0and adjust by ear. Most setups land between 0 and 300. - Aurora runs at 50 % or more, so a track's gentle parts play at the bottom of that range (see
toy.info.minStrengthin the SDK reference). - Live or generated content doesn't need a track: send
vibrate()with a short duration as things happen.