I just started a Rails 5 project, and on the homepage I'm supposed to have a video banner, muted, in loop autoplay. I'm using Turbolinks 5 and I don't know if I can keep it.
When I load the home page for the first time, everything works fine. But when I go to any other page, the sound of the home page video starts to play. And If I go back to the home, the sound start again, over the sound previously started.
And so I have multiple instance of the sound of the muted video playing at the same time.
Fun at first, nightmare after 5 seconds.
I've seen this issue: https://github.com/turbolinks/turbolinks/issues/177 , which is still open and have no practical solution in the comments.
Apparently it's not new, this post https://www.ruby-forum.com/topic/6803782 seems to talk about the same problem in 2015. The guy talks about disabling Turbolinks for the link that lead to that page. I can't do it, since my problem is on the home page.
This is due to Turbolinks caching a copy of the homepage DOM. Even though the video is not visible it still exists in the cache and so will play. (However, I'm not sure why it's not muted—this could be a browser bug :/)
I think Turbolinks could handle this by default, but in the meantime, here are a couple of options:
To prevent Turbolinks from caching a copy of the homepage, you could disable caching for that page. For example, you could include the following in your layout's <head>
:
<% if @turbolinks_cache_control %>
<meta name="turbolinks-cache-control" content="<%= @turbolinks_cache_control %>">
<% end %>
Then in your homepage view:
<% @turbolinks_cache_control = 'no-cache' %>
The downside is that you miss out on the performance benefits when the visitor revisits the homepage.
autoplay
elementsAlternatively, you could track autoplay
elements, remove the autoplay
attribute before the page is cached, then re-add it before it's rendered. Similar to Persisting Elements Across Page Loads, this solution requires that autoplay
elements have a unique ID.
Include the following in your application JavaScript:
;(function () {
var each = Array.prototype.forEach
var autoplayIds = []
document.addEventListener('turbolinks:before-cache', function () {
var autoplayElements = document.querySelectorAll('[autoplay]')
each.call(autoplayElements, function (element) {
if (!element.id) throw 'autoplay elements need an ID attribute'
autoplayIds.push(element.id)
element.removeAttribute('autoplay')
})
})
document.addEventListener('turbolinks:before-render', function (event) {
autoplayIds = autoplayIds.reduce(function (ids, id) {
var autoplay = event.data.newBody.querySelector('#' + id)
if (autoplay) autoplay.setAttribute('autoplay', true)
else ids.push(id)
return ids
}, [])
})
})()
This script gets all the autoplay
elements before the page is cached, stores each ID in autoplayIds
, then removes the autoplay
attribute. When a page is about to be rendered, it iterates over the stored IDs, and checks if the new body contains an element with a matching ID. If it does, then it re-adds the autoplay
attribute, otherwise it pushes the ID to the new autoplayIds
array. This ensures that autoplayIds
only includes IDs that have not been re-rendered.