Videos intermittently fail to load when there are many in a single post

Issue Summary

Hi there, I’ve been setting up a new Ghost blog using Ghost Pro, and with my first post ran into what I think is an issue with video loading that someone possibly may want to look at. I’ve already had some responsive help from the Ghost support team trying to get a better idea about what might be occurring here but sharing here now in case it’s helpful for others too.

I believe when many videos are embedded in a single post, all the videos are loaded at once and some devices fail to decode all those video streams at once resulting in silent errors and videos which can’t be played.

I recently wrote this article which contains 8 auto playing and looping visual UI demonstrations in it. A few of my friends had told me that none or some of the videos were impossible to load for them and I started looking into it, discovering that this was mainly a problem on mobile devices for some reason — falling back to just a thumbnail that can’t be played manually because the play controls aren’t displayed for autoplay looping videos. No amount of refreshes, cache clearing, trying different networks, disabling ad blockers etc etc would improve the situation.

After some back and forth basically what I found was that if I called .play() on a failing video element it would result in a forever pending promise (and not play), but if I called .load() on it it would load the video and start playing it. This made me think maybe something was quirky about the way different browsers decided to handle autoplay videos (because the rules are a little arbitrary), but then after looking a little harder I discovered by calling .error on them that actually all the videos failing to play had an this error which was silently occurring:

This suggests the videos are being downloaded, but aren’t being decoded by the device — which would make the fact this happens more often on mobile or lower powered devices make a whole lot more sense, a case that seems to be backed up in this stackoverflow where the top answer mentions:

  1. There are insufficient resources to decode the video because several video buffers (even if they’re not encrypted videos) have been used up already.

It also makes more sense that .play() doesn’t work but .load() does because .load() would be trigging a whole new load and decode of the video, whereas .play() would just try to play the video which hasn’t been decoded yet (I think).

So the obvious (but sad) answer for me in the short term is probably “have less videos on your page” (I’m wondering if making some of them not autoplay might help or whether it would just be the same either way).

But I raise this here because I suspect this might actually be an issue in the wild going relatively unnoticed if people don’t use too many videos. I have a couple of thoughts and ideas:

  1. Should these errors be getting handled by cards/js/video.js so that in the event where something prevents the video from being decoded or any other error, then some UI can be displayed for the user to ‘retry’ it or manually trigger a load (or some other kind of retry handler). It seems like there are likely other errors that can happen here too judging by the possible MediaError codes.
  2. It also seems like there could be other reasons for autoplaying videos in particular that they might not start playing, like the user’s own browser preferences about autoplaying videos — in that case because all the player UI controls are hidden I think this could mean that they can’t start the video?
  3. I wonder if the lazy loader which is already in place for images etc could be extended to also handle video loading — I suspect this would make this issue largely go away but also it seems good overall for people’s data usage to not be loading every video on the page as soon as the page loads.

Happy to raise this over on Github if we think it seems sound, just thought I’d raise it here first in case I’m totally off my rocker!


Steps to Reproduce

Set up a post with a lot of videos set to automatically play and loop. I used 8 fairly high resolution ones but I assume this should be easier to replicate by trying to ask the browser to load as many videos simultaneously as possible.

Test on a lower powered device like an android phone and look for videos that don’t get loaded. It has been frustratingly intermittent to reproduce at times, but is reliably reproduced for me in Chrome on a OnePlus 7T debugging via desktop Chrome’s USB remote android debugging.

Inspect the video element and store the element as a variable, then run .error on it to check for MediaError codes.

Try running .load() on that element now — it should load and start playing.


Setup information

Ghost Version
5.82.9+moya

Node.js Version
None — using Ghost pro

How did you install Ghost?

Provide details of your host & operating system

Database type

Browser & OS version
Chrome Android 124.0.6367.179

Relevant log / error output
See above detail

A little bit more detail in a video here showing the thumbnail-only problem on my blog and the fix for that after calling .load() on one of them:
https://github.com/TryGhost/Ghost/assets/616368/6b11e5c0-2b8b-495b-b968-e2a9c3119806

Update on this, I converted all the videos in the original post into HTML blocks instead by copying the element DOM output back in.

This makes the editing experience less than ideal but I was then able to experiment using script injection to add a small lazy loader:

document.addEventListener("DOMContentLoaded", function() {
  var lazyVideos = [].slice.call(document.querySelectorAll("video[data-lazy]"));

  if ("IntersectionObserver" in window) {
    var lazyVideoObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(video) {
        if (video.isIntersecting) {
          video.target.src = video.target.dataset.src;
          video.target.load();
          video.target.removeAttribute("data-lazy");
          lazyVideoObserver.unobserve(video.target);
        }
      });
    });

    lazyVideos.forEach(function(lazyVideo) {
      lazyVideoObserver.observe(lazyVideo);
    });
  }
});

Which I can confirm has smoothed out the loading performance on mobile quite considerably — I’m not experiencing any decoding errors anymore during a normal read through / scroll.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.