Turbolinks: Video keeps playing in pages

Created on 10 Sep 2016  Â·  13Comments  Â·  Source: turbolinks/turbolinks

I have HTML5 video in one of the pages with autoplay enabled. That video keeps playing from beginning when I browse other pages after visiting the page with the video.

To reproduce the issue, just add this code in one of the .erb file:

<video controls autoplay>
    <source src="http://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4">
    Your browser does not support the video tag.
</video>

Then navigate to that page and then to other pages. Tested in Chrome, Firefox and Safari in Mac.

bug

Most helpful comment

Here's how I managed it.

document.addEventListener("turbolinks:request-end", function() {
  if ($('video').length > 0) {
    $('video').remove()
  }
});

All 13 comments

hi all, i meet this issue too. Any update for this?

It's funny this bug :smile:

:D i just paused video

This is sort of less a bug than fundamentally how TL5 works by design.

Unlike TL3's "partial replacement", TL5 _removes all elements in the body from the DOM_, no matter what. If an element has a data-turbolinks-permanent attribute, it is still removed from the old page's body and moved into the new one, which is what's causing the behavior you're seeing.

Unfortunately, I think probably the only way this will be fixed is with some kind of dom-diffing plugin so that elements which persist between pages are not removed and re-inserted.

A solution mentioned in #157 which may work here is to create an HTMLVideoElement without an associated tag.

Any idea how to actually solve this?

Here's how I managed it.

document.addEventListener("turbolinks:request-end", function() {
  if ($('video').length > 0) {
    $('video').remove()
  }
});

I'm also encountering this issue.

The only workaround I've found is to opt out of Turbolinks cache completely.

<meta name="turbolinks-cache-control" content="no-cache">

This is what I am using to get around the issue.

Dynamically add the video element on load then remove before it gets cached. In this snippet, content_player_div is the parent element of the video element.

<script>
  $(document).one("turbolinks:before-cache", function(event) {
    $("video[id=content_player_video]").remove();
  });
  $(document).one("turbolinks:load", function(event) {
    if( $("#content_player_video").length == 0 ) {
      $("#content_player_div").append(
        $("<video controls autoplay/>")
          .attr("id", "content_player_video")
          .attr("oncontextmenu", "return false;")
          .attr("controlList", "nodownload")
          .append($("<source/>")
            .attr("src", "<%= source_url %>")
            .attr("type", "video/mp4")
        )
      )
    }
  }); 
</script>

As has been hinted at, this is due to Turbolinks caching. Even though the autoplay-video is not visible, it is stored in the cache, and therefore will still play automatically.

I think Turbolinks could handle this by default, but in the meantime, and as an alternative to disabling the cache, or re-constructing the video element manually, you may find the following script useful. Similar to Persisting Elements Across Page Loads, this solution requires that autoplay elements have a unique ID…

;(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.

(This solution was also posted on my StackOverflow answer.)

Hope that helps.

Tried your solution @domchristie , however i'm getting errors. My set up is two different videos each on different pages. Copied and pasted your code. Each video has a unique id.

Uncaught TypeError: Cannot read property 'push' of undefined

Could be a problem with your reducer... haven't had a chance to play. Tested on latest Chrome and Safari... same problem.

@jaybloke Thanks for testing this out. I noticed that the reducer wasn't returning anything. The script above has now been fixed. Thanks again

Works like a charm @domchristie !

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kstratis picture kstratis  Â·  13Comments

gregblass picture gregblass  Â·  17Comments

sblackstone picture sblackstone  Â·  38Comments

tadiou picture tadiou  Â·  42Comments

kochka picture kochka  Â·  14Comments