Sticky mini audio player dock
A fixed bottom audio dock that plays any link marked with data-audio and keeps playing while the visitor browses the page.
WordPressUtilitiesaudioplayerpodcastaccessibility
Live preview
Code
<?php
/**
* Sticky audio dock.
*
* @package Acme_Theme
*/
defined( 'ABSPATH' ) || exit;
function acme_audio_dock_assets(): void {
$uri = get_template_directory_uri() . '/assets/';
wp_enqueue_style( 'acme-dock', $uri . 'audio-dock.css', array(), '1.0.0' );
wp_enqueue_script( 'acme-dock', $uri . 'audio-dock.js', array(), '1.0.0', array( 'in_footer' => true, 'strategy' => 'defer' ) );
}
add_action( 'wp_enqueue_scripts', 'acme_audio_dock_assets' );
function acme_audio_dock_markup(): void {
?>
<div class="acme-dock" role="region" aria-label="<?php esc_attr_e( 'Audio player', 'acme-theme' ); ?>" hidden>
<button type="button" class="acme-dock__play" aria-label="<?php esc_attr_e( 'Play or pause', 'acme-theme' ); ?>"></button>
<span class="acme-dock__title"></span>
<input type="range" class="acme-dock__seek" min="0" max="100" value="0" aria-label="<?php esc_attr_e( 'Seek', 'acme-theme' ); ?>">
<audio preload="none"></audio>
</div>
<?php
}
add_action( 'wp_footer', 'acme_audio_dock_markup' );
// Helper for templates: a link the dock script picks up.
function acme_audio_link( string $url, string $title ): string {
return sprintf( '<a href="%1$s" data-audio="%1$s" data-title="%2$s">%3$s</a>', esc_url( $url ), esc_attr( $title ), esc_html( $title ) );
}
(() => {
const dock = document.querySelector('.acme-dock');
if (!dock) return;
const audio = dock.querySelector('audio');
const play = dock.querySelector('.acme-dock__play');
const seek = dock.querySelector('.acme-dock__seek');
document.addEventListener('click', (e) => {
const link = e.target.closest('[data-audio]');
if (!link) return;
e.preventDefault();
audio.src = link.dataset.audio;
dock.querySelector('.acme-dock__title').textContent = link.dataset.title || '';
dock.hidden = false;
audio.play();
});
play.addEventListener('click', () => (audio.paused ? audio.play() : audio.pause()));
audio.addEventListener('play', () => play.classList.add('is-playing'));
audio.addEventListener('pause', () => play.classList.remove('is-playing'));
audio.addEventListener('timeupdate', () => {
if (audio.duration) seek.value = (audio.currentTime / audio.duration) * 100;
});
seek.addEventListener('input', () => {
if (audio.duration) audio.currentTime = (seek.value / 100) * audio.duration;
});
})();
.acme-dock { position: fixed; inset: auto 0 0 0; z-index: 50; display: flex; gap: 1rem; align-items: center; padding: .6rem 1rem; background: #111; color: #fff; }
.acme-dock[hidden] { display: none; }
.acme-dock__title { flex: 0 1 14rem; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.acme-dock__seek { flex: 1; }
.acme-dock__play { width: 2.5rem; height: 2.5rem; border: 0; border-radius: 50%; background: #fff; cursor: pointer; }
.acme-dock__play::before { content: "\25B6"; color: #111; }
.acme-dock__play.is-playing::before { content: "\275A\275A"; letter-spacing: -2px; }
How it works
What it solves
Listening to an episode should not stop when the visitor clicks around. A small docked player gives one persistent control strip, so any episode link on the page can play without a new tab or a heavyweight plugin.
How it works
Links carrying data-audio are intercepted through a single delegated click listener, so lists loaded later work too. The script loads the URL into one audio element inside a fixed footer region, shows the dock, and mirrors state onto a play button and a range slider that doubles as the seek bar. The markup is printed in wp_footer, and acme_audio_link() outputs links with escaped attributes.
Without JavaScript, the link is still a normal download link.
Adapting it
Add playback speed by setting audio.playbackRate from a select, and remember the last position per URL with a try/catch around localStorage. For full page navigation continuity, pair it with a fragment-swapping router; on a classic multi-page site playback pauses at each load. Style the dock to match the theme.
Pitfalls
- Browsers block autoplay unless it follows a user gesture; the click handler satisfies that.
- Keep
preload="none"to avoid downloading files nobody plays. - Give the slider a text alternative; time values read better with
aria-valuetext. - Remember the dock covers the bottom of the page; add matching bottom padding.
How to use it
Add the three files and require the PHP from functions.php.
Output episode links with echo acme_audio_link( $mp3_url, get_the_title() );.
Add padding-bottom: 4rem to body while the dock is visible.
Licence: GPL-2.0-or-later. This snippet comes from WordPress code and is GPL-2.0-or-later.