I'm making a game made of bare JavaScript code and I want to manipulate audio in JavaScript.
Specifically, I want to let the page start playing music when some div element is clicked and when using some function in the JavaScript code.
I know audio tag of HTML5 has a limitation that a music file associated with Audio element cannot be played without user clicking the play button of the audio element, so I cannot do like this:
const audio = document.createElement("audio");
audio.src = "url of source";
audio.play();
//Uncaught (in promise) DOMException: play() failed because
//the user didn't interact with the document first.
//https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
Chrome's autoplay policies are simple:
Muted autoplay is always allowed.
Autoplay with sound is allowed if:
User has interacted with the domain (click, tap, etc.).
On desktop, the user's Media Engagement Index threshold has been crossed, meaning the user has previously played video with sound.
The user has added the site to their home screen on mobile or installed the PWA on desktop.
Top frames can delegate autoplay permission to their iframes to allow autoplay with sound.
But I want to manage audio more freely in order to make a good UI of the game.
To achieve my goal, I think I need to introduce another library or API into the JavaScript code or still use audio tag with some tricky tips.
I'm thinking one of the choices could be Web Audio API.
Could you tell me some advice?
Progress
I tried code like the following and it worked when I clicked a div element with click event handler:
<div id="my-div">play</div>
<audio id="myAudio">
<source src="url of source" type="audio/mpeg">
</audio>
const myAudio = document.getElementById("myAudio");
const btn = document.getElementById("my-div");
btn.addEventListener("click",() => {
myAudio.play();
});
However, when I wrote like the following, it didn't work with the same error even if it didn't seem I broke the autoplay policy:
document.getElementById('my-div').addEventListener('click', () => {
const audio = new Audio('url of source');
audio.play();
});
//Uncaught (in promise) DOMException: play() failed because
//the user didn't interact with the document first.
//https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
The main difference between two codes above is whether I wrote the url of the audio source file in the audio tag or wrote it in the source tag inside the audio tag. I'm guessing this makes some difference but I don't get what exactly is the problem.
You're correct. You can only be sure that the playback will not be blocked by the autoplay policy if the playback is started in response to a user gesture. A button with a click handler is the classic example but the click handler could be attached to a div as well. Let's say your div has an id called my-div. In that case the following should work.
document.getElementById('my-div').addEventListener('click', () => {
const audio = new Audio('url of source');
audio.play();
});
Using the Web Audio API will not change the autoplay policy. Usually the same rules apply no matter which browser API is used.
Related
I'm getting this error when trying to play audio within componentDidMount.
'Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first.'
componentDidMount() {
document.getElementById("backgroundMusic").play();
}
<audio id="backgroundMusic">
<source src={url} type="audio/mpeg" />
Your browser does not support the audio element.
</audio>
It works fine with an onclick event. Is there any way to autoPlay audio in React? I thought I could trigger the audio to start by using onMouseOver, but ideally the music would just start without any user interaction.
Autoplay Policy Changes no longer allow autoplay without user interaction first.
Muted autoplay is always allowed.
Autoplay with sound is allowed if:
User has interacted with the domain (click, tap, etc.).
On desktop, the user's Media Engagement Index threshold has been crossed, meaning the user has previously played video with sound.
The user has added the site to their home screen on mobile or installed the PWA on desktop.
Top frames can delegate autoplay permission to their iframes to allow autoplay with sound.
The only way to bypass this would be your mouse movement implementation
The error message you've got is pretty much self-explaining. Most of the modern browsers prevent audio/video autoplay before user interaction with the page. They do so to avoid certain undesired effects for the user (for instance, a user might have maximum audio volume set & auto-playing loud audio might surprise/scare her).
There are certain hacks you can try out, but none is really guaranteed to work cross-browser.
I fixed this by catching the error and play the sound after the first click. In your code, that would be something like this:
componentDidMount() {
document.getElementById("backgroundMusic").play().catch((error) => {
document.addEventListener('click', () => {
document.getElementById("backgroundMusic").play()
}, { once: true } )
}
<audio id="backgroundMusic">
<source src={url} type="audio/mpeg" />
Your browser does not support the audio element.
</audio>
This way, you're compliant with the Autoplay Policy Changes mentioned by Halmond and you stay away from hacky solutions referenced by Igor.
I am developing a web system to monitor some values from a database, and I need to play some sound alert when a range of values is received. I've tried a lot of internet samples, but anyone works. The error returned is "uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first". The last try was with the code bellow:
<audio id="myAudio" muted="muted">
<source src="./resources/sound/Alarm.mp3" type="audio/mp3">
</audio>
<script>
alert(){
let x = document.getElementById("myAudio");
if(this.percentIntegral[0]>=70 && this.percentIntegral[0]<=80){
//alert("play");
x.play();
}
}
</script>
Thank you for asking this question, I am also suffered from this issue, You can not autoplay audio because the browser needed some interaction with a user after music will autoplay. It's the security purpose of the browser.
The user didn't interact with the document first. It only works when the user interacts with a browser.
You also can not jquery click into the browser. when the user scrolls up down or clicks to any button then after audio will work.
window.onload = function() {
document.getElementById("up").play();
alert("k");
}
<audio id="up" src="./res/up.mp3" loop="loop"></audio>
hi I've just began studying html&js, and the code above worked fine on IE, but only the alert happened on chrome. Is this normal?
This is because chrome throws an exception :
DOMException: The element has no supported sources.
"code": 9,
"name": "NotSupportedError",
"message": "The element has no supported sources."
Chrome has changed it's policy because blocking roughly half of unwanted media autoplays
READ : https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
Reference :
The Autoplay Policy launched in M66 Stable for audio and video
elements and is effectively blocking roughly half of unwanted media
autoplays in Chrome. For the Web Audio API, the autoplay policy will
launch in M70. This affects web games, some WebRTC applications, and
other web pages using audio features. Developers will need to update
their code to take advantage of the policy. More details can be found
in the Web Audio API section below.
Chrome's autoplay policies are simple:
Muted autoplay is always allowed.
Autoplay with sound is allowed if:
User has interacted with the domain (click, tap, etc.).
On desktop,the user's Media Engagement Index threshold has been crossed,
meaningthe user has previously play video with sound.
On mobile, the user has added the site to his or her home screen.
Top frames
can delegate autoplay permission to their iframes to allow autoplay with
sound.
I have edited your code to display the error
window.onload = function() {
const playPromise = document.getElementById("up").play();
// In browsers that don’t yet support this functionality,
// playPromise won’t be defined.
if (playPromise !== undefined) {
playPromise.then(function() {
// Automatic playback started!
}).catch(function(error) {
console.log(error);
// Automatic playback failed.
// Show a UI element to let the user manually start playback.
});
}
alert("k");
}
<audio id="up" src="http://hcmaslov.d-real.sci-nnov.ru/public/mp3/Queen/Queen%20'Back%20Chat'.mp3" loop="loop"></audio>
This is a result of Chrome's autoplay policy, which restricts sites from playing audio before the user has interacted with the page.
In summary autoplay in Chrome is Not allowed by default since april 2018. This to protect the user for undesired sounds. More info.
But, if the user interacts with the page (by making a click for example), then yes it is allowed, so you can change the onload code to a click. Like this:
window.addEventListener("click", function(event) {
document.getElementById("up").play();
alert("k");
});
Click on the page, and walla.
Hope it helps.
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.
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.
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
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.
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.
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