Search code examples
ruby-on-railsvideohtml5-videoturbolinks

Turbolinks and autoplay HTML5 video


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.


Solution

  • 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:

    Solution 1: Disable Caching on the Homepage

    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.

    Solution 2: Track autoplay elements

    Alternatively, 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.