Stop Audio When Leaving Pages in Hotwire Native

Your user starts a meditation. They tap back. The audio keeps playing. They navigate to settings. Still playing. They close the app. Still playing. This is what happens when you embed audio in Hotwire Native without thinking it through.

I hit this in both PrayAI and Fitivity. PrayAI has guided meditations. Fitivity has workout audio cues. Both needed audio that stops when users navigate away. Here's what actually works.

Why This Happens


When you play audio from a web view—an HTML audio element or JavaScript-based player—the audio lives in the web context. Hotwire Native manages view controller lifecycle, but it doesn't reach into your web view to pause media. Navigate away and that audio element keeps doing its thing.

The web view might even get cached. Your audio keeps playing from a view controller that isn't visible. Users hear phantom audio and have no idea where it's coming from.

Option 1: Stimulus Controller


If you want the quickest fix with zero native code, use Stimulus. Listen for the disconnect callback and pause your audio:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["audio"]

  disconnect() {
    this.audioTarget.pause()
    this.audioTarget.currentTime = 0
  }
}

Hook it up to your audio element:

<div data-controller="audio-player">
  <audio data-audio-player-target="audio" src="meditation.mp3"></audio>
  <button data-action="click->audio-player#play">Play</button>
</div>

When Turbo removes the element from the DOM—on navigation—Stimulus fires disconnect. Audio stops. This approach requires only Rails changes. No App Store update needed.

Option 2: Override viewWillDisappear


If you're already subclassing Hotwire Native's view controller, you can handle this in Swift:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    webView.evaluateJavaScript("document.querySelectorAll('audio, video').forEach(el => el.pause())")
}

This pauses all media elements when the view disappears. Blunt but effective. You could also call a specific JavaScript function if you need more control.

Downside: you're coupling your native code to your web implementation. Change your audio player markup and you might break this.

Option 3: Native Audio Player


For anything beyond simple sound effects, build a native audio player. This is more work upfront but gives you everything users expect from audio apps:

  • Background playback
  • Lock screen controls
  • Control Center integration
  • Proper audio session handling
  • Bluetooth and CarPlay support

Use a Bridge Component to communicate between your web view and native player. Your Rails view sends a message with the audio URL. Your native component handles playback. Navigation events are irrelevant because the audio lives outside the web view entirely.

// Bridge component receives message from web
func handle(message: InboundMessage) {
    guard let audioURL = message.data["url"] as? String else { return }
    AudioManager.shared.play(url: audioURL)
}

Your web view becomes a remote control. The audio engine is native. Users get the experience they expect from Spotify or Apple Podcasts.

Which Should You Use?


Stimulus controller if you need a quick fix, audio is secondary to your app, and you can't ship a native update right now. Deploy it today from your Rails app.

viewWillDisappear if you're already customizing view controllers and want consistency across all audio without touching your web code.

Native player if audio is core to your app. Meditations, podcasts, music, workout audio—anything users will play for more than a few seconds. The UX difference is significant. Background playback alone is worth it.

For PrayAI, I went native. Users play 20-minute meditations. They want lock screen controls. They want to keep listening when they check a text message. A web-based player can't deliver that.

For quick audio cues in Fitivity—"Start your set!"—the Stimulus approach works fine. Short clips, always played in the foreground, no need for background support.

Start Simple


If you're fighting phantom audio right now, add the Stimulus controller. It takes five minutes and ships without an app update. Then evaluate whether native audio makes sense for your use case.

Most apps don't need a full native audio stack. But if yours does, don't fight the platform. Build the bridge component, use AVFoundation, and give users the experience they expect.