The engine needs no React binding: import it and call it. What React changes is who stops a sound, because a component can vanish mid-gesture.
The built package was not found. Run npm run build (or ./serve.sh, which builds too) and reload.
React could not load from the CDN, so this page cannot render. The other three examples need no network.
Drag the fader and, still holding it, press a view button. Done naively, the drone outlives its component and hums until reload. Here the effect's cleanup stops it, and the view change calls Anti.silence() on the way out.
// useDrone.js import { useEffect, useRef } from 'react' import Anti from 'anti-tapes' export function useDrone(id) { const held = useRef(false) // The whole reason this is a hook. A component can // unmount mid-gesture (a route change, a filtered // list, a modal closing over it) and the pointerup // that would have ended the drag never arrives at // anything that still exists. useEffect(() => () => { if (held.current) Anti.drone.stop(id) }, [id]) return { start(v) { held.current = true; Anti.drone.start(id, v) }, move(v) { if (held.current) Anti.drone.move(id, v) }, stop() { if (!held.current) return held.current = false; Anti.drone.stop(id) } } }
The engine is a module with its own state, so it answers most integration questions. Two are yours.
Drone and progress runs are keyed by an id you choose, so starting one twice moves the existing run instead of stacking a voice. A double-invoked effect is harmless.
// The route-change contract. One call, and nothing // that was held by the view being left is still // ringing over the view being entered. useEffect(() => { return () => Anti.silence() }, [route]) // Importing on the server is silent, not fatal: the // engine arms itself on the first user gesture and // no-ops before there is one, so there is nothing to // guard and no dynamic import to arrange. import Anti from 'anti-tapes'