Autoplaying back-to-back videos on the web using a Media Pool

No one likes it when you go to a web page and it automatically starts playing a video loudly. Browsers have changed their auto-play policies to let JavaScript auto-play a video only if it's inline and muted. (See posts for Safari and Chrome).

Meme saying: One does not simply autoplay videos, and I will find you and I will pause you

Problem: Playing a video queue

This change in policy is great, but it may restrict some use cases. Say, you have created a queue of videos (or audios). You'd want the next one to play as soon as the first one finishes. Instagram/Snapchat like stories is a good example for this.

Normally one would implement this by listening to your video's progress and as soon as it ends, call play on the next video. But, if the video is not muted, it will not play according to the browser's media policy.

Unmuted videos can be played if they were triggered by a user action. But, when playing one video after another, asking the user to tap to play again doesn't feel like a great user experience.

Reusing the media element

Once a media element (<video> or <audio>) has been triggered by a user action, it gets cleared by the browser for future use. i.e. Later on, some script can play the video unmuted.

With that in mind, the very basic solution is to create a single media element, and re-use it for every video. This, however, may not be a great user experience. Loading the next video may take time, so transitioning from one video to another will not be a smooth one.

Media Pool

In an ideal world where resources and browser limitations do not exist, one could load every video in memory. But it's not good practice, and some browsers (like iOS Safari) limit the number of media elements in memory.

So we create a Media Pool. This is similar to thread pools in multi-threaded languages where you allocate a thread to a task and when the task is done, the thread is released to be used by another task.

Applying this model to media elements, we can keep a few videos in memory for a smooth experience, and release them back to the pool when they are done playing. As new videos get queued up to play, they request a media element from the pool. The pool will provide an unused or a recycled media element.

In the following diagram, we have a pool of seven media elements . We keep the last video in memory, and preload the next one. So there are only 3 allocated media elements at a time. As the videos leave this three-video-window, the media element is released back into the pool.

Diagram showing 3 allocated media elements in a media pool

When video-3 finishes, video-4 starts playing. video-2 is outside the window so it releases the media element back into the queue. video5 gets a new element allocated from the queue.

Diagram showing 3 allocated media elements in a media pool

Blessing the Media

A media pool sounds like a great way to reuse video elements but we have not addressed the original problem - playing unmuted videos automatically. I discovered that Google's AMP project had the same problem as this, and they use a term called blessing a video element.

When the user takes their first action to unmute a video or to start an unmuted video; in the same event handler, the media-pool will unmute all the videos in the pool (allocated, unallocated, and released ones). It will then mute the ones that are not active right away. By doing this, all videos have been cleared by the browser. If we use AMP's terminology, these videos have now been blessed.

How to bless a video

As one video finishes, the script can now call play on a blessed video element to start playing it even when it's unmuted.

There are a couple of things to keep in mind when implementing a blessing based media pool. Firstly, one cannot invoke operations like mute, unmute, play on a media element if it doesn't have any media loaded. That's why the unallocated media-elements should be loaded with an empty video file. Secondly, in some browsers operations of media elements can be interrupted when invoked in a certain way, for example calling load after calling play can reject the play promise. The AMP project creates a queue of operations per media element. I decided to do the same in my case.

Media Pool implementation

I have created a small framework independent media-pool implementation that you can use if you like, or feel free to look at the code to create a new one. AMP's version of media pool is available here but it may be a bit AMP specific.

I have further optimized my media-pool implementation so it lazy-releases a media-element; which means if the video is active again it can reuse its old media-element even though it was released. This prevents re-attaching to DOM and re-loading videos. In the case where the released media-element has been allocated to some other video player, the video player will get a new one from the pool.