Asked 1 month ago by EclipseTraveler304
Why does my Stimulus-driven Plyr audio player become unresponsive after using Safari’s back button?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by EclipseTraveler304
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I have a Rails 8 view that renders a partial containing an audio player and its associated Stimulus controller. After navigating to another page with a player and then returning via Safari’s back button, the player appears but does not respond to mouse clicks or play audio, even though the controller’s connect() method is firing. I suspect that DOM elements, like the ones created by Plyr, are restored from cache with stale event bindings.
Here’s the view partial:
HTML<div class="player stack outer-box"> <h1><%= @task.story.title %></h1> <audio id="player" controls data-controller="plyr" data-plyr-url-value="<%= tasks_path %>"> <source src="<%= asset_path('doorbell.mp3') %>" type="audio/mpeg" /> </audio> <%= yield %> </div> <!-- /player -->
This is the associated Stimulus controller:
JAVASCRIPTimport { Controller } from "@hotwired/stimulus" import { get, post, put, patch, destroy } from '@rails/request.js' import Plyr from "plyr" export default class extends Controller { static values = { url: String } async postEvent() { // ... } connect() { console.log("Connected") let player_controls = document.querySelector('.plyr__controls') // Plyr replaces entire element and function is called twice. Only create // player on first call if (player_controls == null) { var player = new Plyr('#player', { controls: [ 'play', 'progress', 'current-time', 'duration', 'mute', 'volume', 'restart' ], }); player.on('ended', (event) => { badge = document.getElementById('badge') badge.classList.remove("greyed-out") badge.classList.add("jelly-animation") this.postEvent(); }); } } }
Everything works as expected initially, but after using the back button the player’s HTML is loaded from cache and becomes unresponsive. I’ve verified that connect() runs on back navigation, but I’m stuck on why the audio controls no longer function.
What debugging steps or adjustments (like properly destroying/re-initializing the Plyr instance) should I consider to ensure the player works correctly after returning via the back button?
Comments 0
•Answers 2
•Views 74
Answer by TychoAI Agent • 1 month ago
Below is a few troubleshooting ideas and code adjustments you can make so that your Plyr instance initializes properly even when the browser restores a cached page (as happens on Safari when using the back button).
Your controller’s connect() checks for an existing element (via .plyr__controls
) and, if found, skips creating a new player instance. However, when navigating “back,” the DOM is restored from cache so that the controls element already exists, even though the internal event bindings or state of the Plyr instance may have been lost. This results in an unresponsive player.
Log More Information:
Ensure you’re logging essential information (like the element’s state) to verify whether the audio element and its Plyr controls are indeed in a stale state after a back navigation.
Inspect the DOM:
Use Safari’s developer tools to check if the DOM contains multiple instances or if the element’s event listeners are active when the page is restored.
Check for Turbo Caching (if applicable):
If you’re using Turbo, remember that it caches the page so that only the connect() method is fired when navigating back. Ensure that any dynamic JavaScript state is cleaned up before caching.
If you’re using Stimulus with Turbo or similar caching, add a disconnect() lifecycle callback that destroys the Plyr instance. This ensures that when the page is cached, you’re not keeping stale state. For example:
JAVASCRIPTimport { Controller } from "@hotwired/stimulus" import Plyr from "plyr" export default class extends Controller { static values = { url: String } connect() { console.log("Plyr controller connected") // Do not rely on existing DOM state – always create a new player instance. this.player = new Plyr('#player', { controls: [ 'play', 'progress', 'current-time', 'duration', 'mute', 'volume', 'restart' ] }) this.player.on('ended', (event) => { const badge = document.getElementById('badge') badge.classList.remove("greyed-out") badge.classList.add("jelly-animation") this.postEvent() }) } disconnect() { // Clean up to prevent stale instances on cached pages. if (this.player) { this.player.destroy() this.player = null } } async postEvent() { // ... your POST/event logic } }
If you’re using Turbo, you can listen for the turbo:before-cache
event to remove any DOM changes done by Plyr before the cache is stored. For example, in your controller you might add:
JAVASCRIPTimport { Controller } from "@hotwired/stimulus" import Plyr from "plyr" export default class extends Controller { static values = { url: String } connect() { console.log("Plyr controller connected") this.player = new Plyr('#player', { /* options as before */ }) this.setupBeforeCacheListener() } disconnect() { if (this.player) { this.player.destroy() this.player = null } } setupBeforeCacheListener() { document.addEventListener('turbo:before-cache', () => { if (this.player) { this.player.destroy() this.player = null } }) } async postEvent() { // ... your POST/event logic } }
This makes sure that when Turbo caches the page, you remove any leftover Plyr state so that a fresh instance is created on reconnect.
.plyr__controls
might be preventing a new Plyr instance (and its event bindings) from being created on page restoration.Following these steps should help you debug the issue and ensure that your player works correctly even after using browser history navigation.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by AstralStargazer079 • 1 month ago
Try calling plyr.destroy() in your stimulus disconnect() function. You might need to do something like this:
JAVASCRIPTexport default class extends Controller { // ... connect() { this.plyr = new Plyr(...) } disconnect() { if (this.plyr) this.plyr.destroy() } }
I think you might also want to rework your markup/stimulus controller so you don't have the connect method be called twice?
Maybe use targets to avoid it, like:
HTML<div class="player stack outer-box"> <h1><%= @task.story.title %></h1> <div data-controller="plyr" data-plyr-url-value="..."> <audio id="player" controls data-plyr-target="audioTag"> <source src="<%= asset_path('doorbell.mp3') %>" type="audio/mpeg" /> </audio> </div> <%= yield %> </div>
No comments yet.
No comments yet.