Skip to content

Web Player Example

The webPlayer feature renders an inline pill-shaped control for narrating an article: paragraph-level highlight, click-to-jump navigation, 15-step speed cycle (0.5×–3×), and a sticky mini-player when the main controls scroll out of view. It mounts on every top-level element matching the configured selector, and exposes rv.webPlayer.mount(selectorOrElement, overrides?) for content added after rv.init().

<script src="https://cdn.responsivevoice.org/sdk/latest/responsivevoice.js"></script>
<script>
const rv = window.responsiveVoice;
rv.init({
features: {
webPlayer: {
enabled: true,
selector: 'article',
paragraphSelector: 'p, h2, li',
position: 'before',
theme: 'responsivevoice',
},
},
});
</script>

The feature ships in the CDN bundle — no extra imports.

When selector matches more than one top-level element, an independent player is mounted on each. Single-active-narrator coordination is implicit: starting playback on one resets every other player to idle.

Nested matches (e.g. <article> inside <article>) are filtered automatically — only the outermost match mounts. Publishers who specifically want nested mounts write a more specific selector.

For SPAs and lazy-loaded sections, call rv.webPlayer?.mount(selectorOrElement, overrides?) after the element is in the DOM:

fetch('/article/123')
.then((r) => r.text())
.then((html) => {
document.getElementById('dynamic-area').innerHTML = html;
const handle = rv.webPlayer?.mount('#dynamic-area', { theme: 'neutral' });
// later, before removing the element from the DOM:
// handle?.unmount();
});

Overrides are leaf-merged over the init config — { controls: { brand: false } } keeps the rest of the init defaults intact.

Any element carrying data-rv-skip is excluded from narration:

<article>
<p>Read this paragraph.</p>
<div class="pullquote" data-rv-skip>This block is skipped.</div>
<p>Continue reading from here.</p>
</article>