Wavecast Radio

Source Adapters

The source parameter controls how the player resolves its audio URL. When omitted, the source type is auto-detected from the src URL.

Local (default)

Plays the URL as-is. Use for direct audio file links or local Hugo resources.

{{< podcast-player src="https://example.com/audio.mp3" >}}

AzuraCast

Fetches the AzuraCast nowplaying API to discover the stream URL and enriches the player with current song metadata.

Requires the data-azuracast-api-url attribute:

{{< podcast-player
  src=""
  source="azuracast"
  data-azuracast-api-url="https://radio.example.org/api/live/nowplaying/station-slug"
>}}

If src is provided alongside source="azuracast", it’s used directly without fetching the API.

Auto-detection: triggered when the URL contains azuracast or .stream..

iVoox

Fetches the iVoox episode page and extracts the audio URL from:

  1. og:audio meta property
  2. data-audio-url attribute
  3. <audio><source> element

On fetch failure, falls back to the src URL as-is.

{{< podcast-player
  src="https://www.ivoox.com/episode-title_12345_1.html"
  source="ivoox"
>}}

Auto-detection: triggered when the URL contains ivoox.com.

JavaScript Events API

The component emits custom events that bubble through the DOM. Listen on any <podcast-player> or <podcast-footer> element:

document.querySelector("podcast-player")
  .addEventListener("player-state", (e) => {
    console.log(e.detail);
    // { paused, src, currentTime, duration }
  });

Event Reference

EventFired onDetail payload
player-stateplay, pause, seek{ paused, src, currentTime, duration }
podcast-playplay (compat){ src, title, url }
podcast-pausepause{ src }
podcast-closefooter close button{ src }
podcast-seekseek on any player{ src, currentTime }

sessionStorage Persistence

State is saved per-source in sessionStorage using the key format:

podcastPlayerState:<src>

This means multiple players on the same page use separate keys and don’t interfere with each other.

Cross-Page Behaviour

  • Navigating away pauses inline audio (saves position)
  • Returning to a page does not autoplay: the player restores in paused state
  • The footer player persists across all pages without interruption

Keyboard Shortcuts

KeyAction
SpaceToggle play/pause
Left ArrowSkip back 15 seconds
Right ArrowSkip forward 15 seconds
MToggle mute

Works on both inline and footer players when the player is focused.

Media Session API

Wavecast integrates with the Media Session API, which means:

  • Play/pause controls appear on OS lock screens and notification centres
  • Track title and cover art are displayed in system media controls
  • Hardware media keys (headphones, keyboard) control playback

This is automatic: no configuration needed.

Troubleshooting

Symptom: Click play on an inline player, the footer bar appears. Click a nav link — the new page loads, the footer is gone, and the audio stops.

Root cause: No navigation framework is loaded. The data-turbo-permanent / data-turbolinks-permanent / hx-preserve attributes on <podcast-footer> are inert HTML attributes with no effect on their own — they are read by htmx / Turbo / Turbolinks JS. If none of those libraries is loaded, the browser does a full page reload and the footer DOM element is destroyed and recreated with no src.

Diagnose: In the browser console on any page, type:

[window.htmx, window.Turbo, window.Turbolinks].filter(Boolean)
// should print exactly one entry

If it prints an empty array, no framework is loaded. Fix: Load one — see Homepage Setup → Framework Attributes.

Symptom: Volume / mute / playback rate changes on the inline player do not update the footer (or vice versa).

Root cause: Some themes (especially ones that bundle a DOMContentLoaded init script) re-bind event handlers on every full page load, but not on htmx/Turbo swaps. If your theme has this pattern, hook the theme’s re-init function to htmx:afterSwap / turbo:render / turbolinks:render.

Fix (Blowfish example): add to your extend-head.html:

if (window.htmx) {
  document.addEventListener("htmx:afterSwap", () => {
    // re-run your theme's page-load init here
  });
}