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.

Toy track
To try it on a toy, start with your key in the playground.

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

OptionMeaning
mediaThe <video> or <audio> to follow (anything with currentTime and paused works). Without it, the track plays on its own clock
startAtMsWithout 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)
motorDual-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 });
OptionMeaning
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
axisWhich axis of a multi-axis script to read. Default: the vibration axis V0 if there is one
gainMultiplies 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() or playTrack() replaces it, and toy.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 maxStrength apply as for every command (see Safety).
  • Keep a Stop button on screen while a track plays.

Tips

  • Start with offsetMs: 0 and 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.minStrength in the SDK reference).
  • Live or generated content doesn't need a track: send vibrate() with a short duration as things happen.

Last updated 18 Oct 2026