Web Player Customization
Tune every webPlayer option in real time. Each toggle re-mounts the player via rv.webPlayer.mount() with leaf-merged overrides, and the matching JSON config snippet updates alongside the player so you can copy-paste the result into your own setup.
What you can tweak
Section titled “What you can tweak”- Theme —
Neutral,ResponsiveVoice, or custom tokens (9-token color picker covering bg, fg, muted, accent, accentSoft, hover, border, track, fill). - Controls — toggle progress, time, skip, speed, brand individually. Play / pause is always shown.
- Navigation — paragraph highlight and click-to-jump independently.
- Position — main-pill placement: keyword (
before/after/inline) relative to the container, or{ target, at }for a custom mount target. - Layout — main-pill width (
shrink/fill) and outer display (block/inline). - Mini-Player — visibility, viewport corner (or CSS-offset object), and entrance/exit animation.
- Sanitize — keep scripts, styles, controls, and media out of narration (on by default), plus extra
excludeselectors of your own. - Spacing — the
--rv-player-marginCSS custom property, controlling the player's surrounding margin.
How re-mount works
Section titled “How re-mount works”The example panel keeps a single mount handle and replaces it on every change:
let handle = null;
function remount() { handle?.unmount(); handle = rv.webPlayer?.mount('#sample', buildConfig());}buildConfig() reads the panel state and returns a WebPlayerMountOverrides object that's leaf-merged over the init config. The same shape works in rv.init({ features: { webPlayer: ... } }) for static sites.
Custom mount target
Section titled “Custom mount target”The position field accepts either a keyword ('before', 'after', 'inline') relative to selector, or an object that mounts the player into any element on the page. This is useful when the article element is constrained by your layout (sidebar, fixed slot, CMS-driven composition):
rv.webPlayer?.mount('#article', { position: { target: '#player-slot', at: 'inside' },});at accepts 'inside' (first child of the target, the default), 'before' (sibling before), or 'after' (sibling after). target is required — the keyword form covers the article-relative case. If target doesn't match anything in the DOM at mount time, the player falls back to the keyword 'before' and logs a warning.
Mini-player position
Section titled “Mini-player position”The floating mini-player surfaces when the main player scrolls out of view. Its viewport corner is configurable via miniPlayer.position:
rv.webPlayer?.mount('#article', { miniPlayer: { enabled: true, position: 'bottom-right' },});Accepted values:
- A corner keyword:
'top-left','top-right','bottom-left','bottom-right'. Default'bottom-left'. - A CSS-offset object:
{ top, right, bottom, left }, each a CSS length string. At least one side is required; opposing sides (top+bottom,left+right) are rejected.
rv.webPlayer?.mount('#article', { miniPlayer: { enabled: true, position: { top: '80px', right: '20px' } },});The boolean shorthand is the natural form for the on/off case — miniPlayer: true enables the mini-player at the default corner, miniPlayer: false disables it.
Mini-player animation
Section titled “Mini-player animation”miniPlayer.animation controls how the mini-player enters and leaves as you scroll:
rv.webPlayer?.mount('#article', { miniPlayer: { enabled: true, animation: 'fade' },});Accepted values:
'slide'— fades in and slides from the docked corner. Default.'fade'— fades in and out, no movement.'pop'— fades in with a brief scale-up.'none'— appears and disappears instantly.
The slide direction follows where the mini-player sits: anything in the lower half of the viewport rises into place, anything in the upper half drops in — so corner keywords and custom offsets both enter from the nearest edge. Motion is skipped automatically when the browser requests reduced motion, so every preset falls back to an instant swap for those readers.
Choosing a voice per player
Section titled “Choosing a voice per player”By default every web player narrates with the website's default voice (the voice profile from /v2/config). The web-player config exposes four flat playback fields — voice, rate, pitch, volume — that mirror the arguments of core.speak(text, voice, params). Each is independently optional and overrides the website default for one player:
rv.webPlayer?.mount('#article', { voice: 'US English Male', rate: 0.9,});voice accepts the full VoiceSelector grammar. In JS, write whichever form is most natural — strings for named voices, real RegExp literals for patterns, plain objects for structured queries. The schema normalizes a RegExp to its JSON-clean { regex, flags } form on parse, so the same selector works identically in JS, server config, and every SDK language:
// 1. Exact voice namerv.webPlayer?.mount('#en-article', { voice: 'UK English Female',});
// 2. Structured query — pick a Portuguese female voice from any providerrv.webPlayer?.mount('#pt-article', { voice: { lang: 'pt', gender: 'female' },});
// 3. Regex pattern — first voice whose name matchesrv.webPlayer?.mount('#multi-article', { voice: /English.*Male/i,});The shape parallels speak() directly:
// Direct speak() call:rv.speak('Hello', /UK English/, { rate: 0.9, volume: 0.8 });// ^text ^voice ^speech params
// Equivalent web-player config — same fields, same names:rv.webPlayer?.mount('#article', { voice: /UK English/, rate: 0.9, volume: 0.8,});Setting a default for every player
Section titled “Setting a default for every player”The same fields are valid in the website config (webPlayer.voice / rate / pitch / volume), in which case every auto-discovered article starts with those values instead of the website-wide default:
rv.init({ features: { webPlayer: { enabled: true, voice: 'US English Male', rate: 0.95, }, },});Per-mount overrides leaf-merge over this config, so a player that mounts with { rate: 1.2 } keeps the 'US English Male' voice and only changes the rate.
What inherits, what overrides
Section titled “What inherits, what overrides”Each playback field is independently optional. Any field you omit falls through to the website default voice profile (the profile's name becomes the string-form voice selector when none is set):
| Field | When omitted |
|---|---|
voice | Inherits the website default voice's name as a selector |
pitch | Inherits the website default pitch |
rate | Inherits the website default rate |
volume | Inherits the website default volume |
Next steps
Section titled “Next steps”- Web Player Example — the basic integration without the customization panel.
- Voice Selection — the full
VoiceSelectorgrammar used by thevoicefield.