Function: useEqualizer()
function useEqualizer(player: Player | undefined, options?: UseEqualizerOptions): Equalizer;
Defined in: packages/player/src/hooks/useEqualizer.ts:416
A ten-band equaliser as one hook: the curve, the presets, the persistence,
and the one af write that puts it on the signal.
Before this, an EQ screen meant composing four exports and owning the state
between them — EQUALIZER_PRESET_LIST for the chips,
equalizerPresetChain to compile, defineEqualizerPreset for a user curve,
setAudioFilters to apply, and a useState per slider. That is the library
making the app do its arithmetic. This is the same machinery with the
bookkeeping done:
const eq = useEqualizer(player)
return (
<>
{eq.presets.map((p) => (
<Chip key={p.id} label={p.name} active={p.id === eq.preset?.id}
onPress={() => eq.applyPreset(p)} />
))}
{eq.bands.map((band, index) => (
<Slider key={band.frequency} value={band.gainDb}
minimumValue={eq.gainRangeDb.min}
maximumValue={eq.gainRangeDb.max}
onValueChange={(db) => eq.setBandGain(index, db)} />
))}
</>
)
Parameters
| Parameter | Type | Description |
|---|---|---|
player | Player | undefined | The player, or undefined before it has been created. The hook holds its state either way, so a screen can render (and be edited) before the core exists; the chain is written as soon as one appears. |
options | UseEqualizerOptions | See UseEqualizerOptions. |
Returns
See Equalizer.
Remarks
There is no onBandChange/onPresetChange event, on purpose. The
returned object is the notification: every mutator re-renders the
component that holds the hook, and the value is a fresh immutable snapshot.
An event carrying the same fact would be a second source of truth for
consumers to fall out of sync with, and a subscription for the common case
(one EQ screen) to pay for. Several components can call the hook
independently — they will each have their own curve, which is the honest
consequence of not keeping app state in the library; hoist it if you want
one.
What is written, and when — drag it, it is built for that. Nothing is written unless the compiled chain would actually differ, and how it is written depends on what changed:
- Only gains changed (the slider case): each moved band is pushed into
the running filter with
Player.setAudioFilterParam— mpv'saf-command, which updates the biquad's coefficients and leaves its state alone. No chain rebuild, no blocked JS thread (the command is async), no click. The curve is compilededitableprecisely so this path is available: ten labelled bands whose shape never depends on their values. - The graph changed (EQ toggled,
chainoptions changed, or the curve left/returned to flat): onePlayer.setEqualizerFilters, which rebuilds the entries that differ. That is the expensive path, and it is now reached once per gesture at most. - COMMIT_DELAY_MS after the last in-place change: one
setEqualizerFiltersthat makes mpv'safproperty agree with the running chain again, so the curve survives the next track, device switch or normalization toggle. Until it lands,player.getAudioFilters()still shows the pre-drag string — that read-back is mpv's property, and the whole point of the fast path is not to write it.
An af-command that mpv refuses (nothing playing, an older engine, a filter
that will not take the parameter at runtime) switches this hook to
commit-only for the rest of the player's life: the curve then lands once the
gesture settles rather than following the finger. That is the honest
degradation — the alternative, rewriting the chain per frame, is what makes
playback stutter, and it is never the right answer.
One timer, and only for the commit above. Nothing else here ticks; every update is caused by a call you made or by the persisted record arriving. The pending commit is flushed on unmount.
Unmounting does not clear the chain. af is a global mpv option that
survives track changes, and an EQ screen closing is not a reason to stop
equalising. Call setEnabled(false) — or player.clearAudioFilters() — to
take it off.
Ownership is scoped to the entries it wrote. This hook owns
Player.setEqualizerFilters — the labelled @rnmedia_eq_… half of the
chain — and nothing else. player.setAudioFilters([...]) keeps working
while an EQ screen is mounted, and keeps working after a slider drag: the
two halves are composed (equaliser → your chain → loudness normalization),
not overwritten. UseEqualizerOptions.extraFilters was the workaround
for the old wholesale behaviour and is deprecated.