Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Programmatically play video with sound on Safari and Mobile Chrome

With the release of OSX High-Sierra*, one of the new features in Safari is that videos on websites will not auto play anymore and scripts can't start it either, just like on iOS. As a user, I like the feature, but as a developer it puts a problem before me: I have an in-browser HTML5 game that contains video. The videos do not get automatically played anymore unless the user changes their settings. This messes up the game flow.

My question is, can I somehow use the players' interaction with the game as a trigger for the video to start playing automatically, even if said activity is not directly linked to the video element?

I cannot use jQuery or other frameworks, because of a restraint that my employer has put on our development. The one exception is pixi.js which - among all other animations - we are also using to play our videos inside a pixi container.

*The same restriction also applies on Mobile Chrome.

like image 768
Geert Avatar asked Sep 29 '17 06:09

Geert


2 Answers

Yes, you can bind on event that are not directly ones triggered on the video element:

btn.onclick = e => vid.play();
<button id="btn">play</button><br>
<video id="vid" src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4"></video>

So you can replace this button with any other splash screen requesting an user click, and you'll be granted access to play the video.

But to keep this ability, you must call at least once the video's play method inside the event handler itself.

Not working:

btn.onclick = e => {
  // won't work, we're not in the event handler anymore
  setTimeout(()=> vid.play().catch(console.error), 5000);
  }
<button id="btn">play</button><br>
<video id="vid" src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4"></video>

Proper fix:

btn.onclick = e => {
  vid.play().then(()=>vid.pause()); // grants full access to the video
  setTimeout(()=> vid.play().catch(console.error), 5000);
  }
<button id="btn">play</button><br>
<video id="vid" src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4"></video>

Ps: here is the list of trusted events as defined by the specs, I'm not sure if Safari limits itself to these, nor if it includes all of these.


Important note regarding Chrome and preparing multiple MediaElements

Chrome has a long-standing bug caused by the maximum simultaneous requests per host which does affect MediaElement playing in the page, limiting their number to 6.

This means that you can not use the method above to prepare more than 6 different MediaElements in your page.

At least two workarounds exist though:

  • It seems that once a MediaElement has been marked as user-approved, it will keep this state, even though you change its src. So you could prepare a maximum of MediaElements and then change their src when needed.
  • The Web Audio API, while also concerned by this user-gesture requirement can play any number of audio sources once allowed. So, thanks to the decodeAudioData() method, one could load all their audio resources as AudioBuffers, and even audio resources from videos medias, which images stream could just be displayed in a muted <video> element in parallel of the AudioBuffer.
like image 110
Kaiido Avatar answered Nov 06 '22 12:11

Kaiido


In my case i was combining transparent video (with audio) with GSAP animation. The solution from Kaiido works perfectly!

First, on user interaction, start and pause the video:

videoPlayer.play().then(() => videoPlayer.pause());

After that you can play it whenever you want. Like this:

const tl = gsap.timeline();
tl.from('.element', {scale: 0, duration: 5);
tl.add(() => videoPlayer.play());

Video will play after the scale animation :).

Tested in Chrome, Safari on iPhone

like image 1
Remco Avatar answered Nov 06 '22 12:11

Remco