Class: VisualizerController
Defined in: packages/player/src/visualizer-controller.ts:72
The lazy, reference-counted owner of one player's audio visualizer.
Laziness is the whole contract
Nothing exists until the first VisualizerController.subscribe: mpv's
tap is disarmed, its ring is unallocated, and there is no sampler thread, no
FFT table and no window. The last unsubscribe releases all of it and disarms
mpv again, after which the audio thread's tap path is a single atomic load per
device chunk. This is why subscribe — not a manual start/stop pair — is
the primitive: a lifetime that is derived from the listener set cannot be
leaked by forgetting to call stop(). There is deliberately no free-standing
start(): it could only ever mean "hold the tap open with nobody looking",
which is the one state this design exists to make unrepresentable.
Native parameters are the union, decode parameters are per subscriber
fftSize, fps and waveform configure the one shared native sampler, so
they are resolved as the union across live subscribers (largest transform,
fastest rate, waveform if anyone wants it). Everything else — band count, dB
window, tilt, auto-gain, smoothing — is pure maths applied per subscriber, so
two components can paint the same audio with different ballistics without
fighting over the engine.
Constructors
Constructor
new VisualizerController(client: MpvClient | undefined): VisualizerController;
Defined in: packages/player/src/visualizer-controller.ts:86
Parameters
| Parameter | Type | Description |
|---|---|---|
client | MpvClient | undefined | The player's mpv binding, or undefined when the player was built without one (never in production; the tests do it). |
Returns
VisualizerController
Accessors
active
Get Signature
get active(): boolean;
Defined in: packages/player/src/visualizer-controller.ts:116
Whether a native capture is running right now.
Returns
boolean
capabilities
Get Signature
get capabilities(): VisualizerCapabilities;
Defined in: packages/player/src/visualizer-controller.ts:99
What this build can actually do. Probed once, then cached.
The probe is a plain property read through the generic binding — if
pcm-tap exists, this libmpv carries the rn-media source patch and the
visualizer works; if it does not, mpv answers "no such property" and the
feature reports itself unavailable. One code path, both platforms, no
Platform.OS. Reading this allocates nothing.
Returns
Methods
destroy()
destroy(): void;
Defined in: packages/player/src/visualizer-controller.ts:211
Release everything. Called by Player.destroy(); idempotent.
Drops every subscription first so the native teardown happens exactly once, then stops the sampler.
Returns
void
handleCapture()
handleCapture(capture: VisualizerCapture): void;
Defined in: packages/player/src/visualizer-controller.ts:192
Deliver a native capture to every subscriber.
Wired to the native listener by Player; not part of the public API surface, but harmless to call with a synthetic capture in tests.
Parameters
| Parameter | Type | Description |
|---|---|---|
capture | VisualizerCapture | The raw capture as the native binding delivered it. |
Returns
void
subscribe()
subscribe(listener: VisualizerListener, options?: VisualizerOptions): VisualizerUnsubscribe;
Defined in: packages/player/src/visualizer-controller.ts:132
Start delivering frames to listener.
Parameters
| Parameter | Type | Description |
|---|---|---|
listener | VisualizerListener | Called with one decoded VisualizerFrame per capture, at up to options.fps. |
options? | VisualizerOptions | Per-subscriber tuning; see VisualizerOptions. |
Returns
A function that removes this subscription — and, when it was the last one, disarms the native tap completely.
Throws
PlayerErrorException with code unsupported when the
linked libmpv has no PCM tap, or disposed after the owning player was
destroyed. It never fails silently.