Keep Audio or Video Playing Across Pages

4 min read

Normally, clicking a link in WordPress reloads the page and your audio stops. AjaxPress keeps the player alive so a stream, podcast or video never cuts out while visitors browse.

Prerequisite: the player must render on every page

If the player sits inside the content of a single page, it cannot persist — the content area is exactly what gets replaced during navigation. Put it in a footer, header, or a template that outputs site-wide. Most radio plugins offer a sticky or floating mode that does this.

Two routes

SituationRouteLicence
You can edit the player’s HTMLAdd a marker attributeFree
A theme or plugin renders itMedia Players CSS selectorPro

Route A — marker attributes (free)

Add any one of these to the element that wraps your player. All four behave identically.

<div id="radio-bar" data-ajaxpress-persist>…</div>

<div id="radio-bar" class="ajaxpress-persist">…</div>

<div id="persistent-radio">…</div>

<audio data-persist src="https://stream.example.com/live"></audio>

Mark the outermost wrapper, not the <audio> tag inside it. Play buttons, volume sliders and artwork have to travel with the player or they stop working.

Route B — Media Players selector (Pro)

Go to Settings → AjaxPress → Advanced → Media Players and enter a CSS selector:

#mediastream-player, .sonaar-sticky-player

Any CSS selector works. Verify it matches exactly one element before saving:

document.querySelectorAll('#your-selector').length  // want: 1

Give the player a stable identity

AjaxPress must recognise your player as the same player on every page. It resolves identity in this order:

#SourceReliability
1data-persist-keyBest — you control it
2The element’s idGood, if identical on every page
3src of an inner iframe/audio/videoBreaks if the URL varies
4The element’s own srcSame caveat
5Fingerprint of the opening HTMLLast resort, fragile
<div data-ajaxpress-persist data-persist-key="live-radio">…</div>

Why it matters. If your stream URL carries a cache-busting parameter such as ?t=1736301234, that URL differs on every page. AjaxPress sees a new player each time, keeps the old one and adds the new one — resulting in two streams playing at once. A fixed key prevents this.

The player is moved — plan for it

To survive navigation, the player is physically moved out of the page into a container appended to <body>:

<div id="ajaxpress-persist" style="position: fixed; z-index: 1000000;">
  <div data-ajaxpress-lifted data-persist-key="live-radio">
    <!-- your player, live element, listeners intact -->
  </div>
</div>
  • It leaves its old parent. CSS written as .site-footer .radio-player { … } stops applying.
  • It must position itself. The container is position: fixed with no offsets, so the player lands at its natural position — usually the bottom of the document.

Symptom: “I added the selector and the widget just moves up the page.” That is this behaviour. Fix it with self-contained positioning:

#radio-bar {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  z-index: 10;
}

How it works internally

  1. On each navigation, AjaxPress matches the built-in markers plus anything in Media Players.
  2. The first match is moved — not copied or rebuilt — into #ajaxpress-persist. Because the live element moves, its event listeners, playback position and any embedded player iframe survive.
  3. On later pages, that page’s own copy is removed; the one already playing is the real one.
  4. If a page has no player, the playing one is removed. Continuity follows your content.

Rebuilding from HTML would drop every event listener, so a JavaScript-driven widget would render correctly and do nothing on click. Moving the live element avoids that.

Never carried into wp-admin. Moving between the front end and the dashboard is always a full page load.

Known issues (2.4.2)

Lifted player is invisible or unclickable. Inline visibility: hidden; opacity: 0; pointer-events: none can end up on it. Workaround:

#ajaxpress-persist [data-ajaxpress-lifted] {
  visibility: visible !important;
  opacity: 1 !important;
  pointer-events: auto !important;
}

ajaxpress:ready does not fire on excluded pages. Players that initialise on that event will not start on pages added to Exclude Links. No clean workaround yet.

Player appears only after a second click. Reported with Mediastream. A data-persist-key resolves it in some cases.

Checklist

  • Player renders on every page, not just one
  • The outermost wrapper is marked, not the <audio> tag
  • data-persist-key set, or an id identical on every page
  • Player positions itself, independent of its old parent
  • Selector matches exactly one element
  • Tested across 5+ pages including Back, and a page with no player