HTML5 audio on android chrome, not playing unless an alert() is called - javascript

I've searched other questions and it seems like this is a problematic area. Unfortunately no one else seemed to have the same issue as me.
I'm essentially trying to trigger an mp3 when a button is clicked. I'm having no problem with desktop browsers but when I try on android I get nothing. I added an alert() just to check that the onclick function was firing and to my surprise after dismissing it the audio plays.
I'm aware that user interaction is required in order to trigger audio but I thought an onclick event would suffice. Is there another gotcha that I am missing?
I've included two snippets to display the difference.
This version works on desktop but not android
let btn = document.querySelector('button')
let audio = new Audio('https://archive.org/download/BirdCall/chrysococcyx-basalis.mp3')
const handleClick = (file) => {
if (file.paused) {
file.play();
} else {
file.currentTime = 0;
}
}
btn.onclick = () => handleClick(audio)
<h1>Working On Desktop</h1>
<button>Bird Call</button>
This version works on android
let btn = document.querySelector('button')
let audio = new Audio('https://archive.org/download/BirdCall/chrysococcyx-basalis.mp3')
const handleClick = (file) => {
alert('Audio will play.')
if (file.paused) {
file.play();
} else {
file.currentTime = 0;
}
}
btn.onclick = () => handleClick(audio)
<h1>Working On Android Chrome</h1>
<button>Bird Call</button>
Any input is greatly appreciated.

That's a strange problem. I tried it on an old android phone and couldn't reproduce the issue (the audio plays without any need for an alert box). I can't see any obvious explanation, but perhaps chrome is waiting for user input before loading the audio file, your device is failing to start loading it quickly enough, then it's hitting a timeout. The alert box might just introduce enough delay. It's a long shot, but you could try adding an extra interaction for the user to load the audio first, like this:
let load = document.querySelector("#load");
let play = document.querySelector("#play");
// different audio file used in this snippet, because archive.org is
// blocked by my ISP ... damn internet censorship
let audio = new Audio("https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3");
play.onclick = () => { audio.play(); }
load.onclick = () => { audio.load(); }
audio.oncanplay = () => {
load.disabled = true;
play.disabled = false;
};
<button id="play" disabled>Play</button>
<button id="load">Load</button>

Related

In the Safari browser, the script restarts, but does not play video on the event

Got bumped with a problem, maybe someone can help me with it.
There is a script that should restart and play a video on an event ( play video.play(); ). On other browsers like Chrome etc. it works, except Safari.
On Safari it just resets the video to 0, but doesn't play it.
Seems like video.play() doesn't work on that browser.
Autoplay and mured are on.
Has anyone had this problem?
Here is the website https://luna-wealth-3b31bac5f1f8e980696881bf9d5.webflow.io/client-online-access
<script>
window.addEventListener('DOMContentLoaded', function () {
const video = document.getElementById("573dfe8f-6592-57e6-c48c-8e93fbdae2e3-video");
const trigger = document.getElementById("trigger");
const observerVideo = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
if (video.currentTime === 0) {
video.muted = true;
video.play();
}
}
}, { threshold: 1 });
const observerTrigger = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
if (video.currentTime !== 0) {
video.currentTime = 0
}
}
}, { threshold: 0 });
observerTrigger.observe(trigger);
observerVideo.observe(video);
})
</script>
The problem was solved this way.
We removed the 100% width parameter from the video and set the container that contains the video to flex justify-center. And the script started playing the video on Safari correctly.
At some resolutions the video stretched beyond its real resolution and Safari did not want to replay it in the event...
Of course it was strange, but it worked =)

detect html audio start download

I would like to know when the audio tag starts downloading so that I can throw up an overlay. Then remove it when it stops, but this does not work in chrome
audio.oncanplay = () => {
loading = false;
};
audio.onloadstart = () => {
loading = true;
};
loading will be true as soon as the page loads.
Is there an event for started downloading, and finished downloading? I need both not just the finished.
I figured it out. You want to use the waiting event. As seen in my fiddle.
let audio = document.getElementById('ad')
let message = document.getElementById('mes')
audio.oncanplay = () => {
message.append ( "done\n")
};
audio.onwaiting = () => {
message.append ( "load\n")
};
message.append ("start\n")
<audio id='ad' controls src='https://html.com/wp-content/uploads/flamingos.mp3' preload="none"></audio>
<h1 id="mes"></h1>
The waiting will fire whenever the audio tag is loading, and canplay will fire whenever the audio tag is again capable of playing.
https://jsfiddle.net/2pdn1k5f/1/

How to play sound in javascript?

I create websocket server in python to handle notification event. Now, i can receive notification, the problem is i can't play sound because new autoplay policy changed, if i play sound using javascript it give me domexception. Any suggestion please ?
As i know, playing sound is simple in html-javascript. like this example: https://stackoverflow.com/a/18628124/7514010
but it depend to your browsers and how you load and play, so issues is:
Some of browsers wait till user click something, then let you play it (Find a way for it)
In some case browsers never let you play till the address use SSL (means the HTTPS behind your url)
The loading be late so the playing be late / or even not start.
So i usually do this:
HTML
<audio id="notifysound" src="notify.mp3" autobuffer preload="auto" style="visibility:hidden;width:0px;height:0px;z-index:-1;"></audio>
JAVASCRIPT (Generally)
var theSound = document.getElementById("notifysound");
theSound.play();
And the most safe if i want sure it be played when i notify is :
JAVASCRIPT (In your case)
function notifyme(theTitle,theBody) {
theTitle=theTitle || 'Title';
theBody=theBody || "Hi. \nIt is notification!";
var theSound = document.getElementById("notifysound");
if ("Notification" in window && Notification) {
if (window.Notification.permission !== "granted") {
window.Notification.requestPermission().then((result) => {
if (result != 'denied') {
return notifyme(theTitle,theBody);
} else {
theSound.play();
}
});
} else {
theSound.play();
try {
var notification = new Notification(theTitle, {
icon: 'icon.png',
body: theBody
});
notification.onclick = function () {
window.focus();
};
}
catch(err) {
return;
}
}
} else {
theSound.play();
}
}
(and just hope it be played. because even possible to volume or some customization make it failed.)
to bypass new autoplay policy :
create a button that can play the sound, hide it and trigger the sound with :
var event = new Event('click');
playBtn.dispatchEvent(event);
EDIT
assuming you have :
let audioData = 'data:audio/wav;base64,..ᴅᴀᴛᴀ...'; // or the src path
you can use this function to trigger whenever you want without appending or create element to the DOM:
function playSound() {
let audioEl = document.createElement('audio');
audioEl.src = audioData;
let audioBtn = document.createElement('button');
audioBtn.addEventListener('click', () => audioEl.play(), false);
let event = new Event('click');
audioBtn.dispatchEvent(event);
}
usage :
just playSound()
EDIT 2
I re test my code and it does'nt work hum ... weird

safari ios audio- refusing to play with error

The javascript error is: Unhandled Promise Rejection: NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.
My setup works across other browsers, desktop and mobile.
The way it works is:
have a flag first_audio_played = false;
add a touch event listener that plays some audio, and sets first_audio_played = true; (then removes the touch listener)
all subsequent audio checks if(first_audio_played) some_other_audio.play();
this way, only the first audio played requires direct user input. after that, all audio is free to be triggered by in-game events, timing, etc...
this appears to be the "rule" for audio across most browsers. is the iOS "rule" that every audio needs to be triggered by user input? or is there some other step I'm missing?
For my javascript game, sounds stopped working on iOS recently. They all have readyState=4, but only the sound I played on tap works, the others won't play. Maybe you could play all the sounds on the first tap. But the solution I found that works for now for me is to load all the sounds from ajax arraybuffers and use decodeAudioData(). Then once you've played 1 sound from user tap (on not the body), they all play whenever.
Here is my working code where the second way of doing it is on bottom. When I tap to play sound2, sound1 starts working also.
<html>
<body>
<div id=all style='font-size:160%;background:#DDD' onclick="log('clicked');playSound(myAudio)">
Sound1 should be playing every couple seconds.
<br />Tap here to play sound1.
</div>
<div id=debug style='font-size:120%;' onclick="playSound(myAudio2)">
Tap here to play the sound2.
</div>
<script>
var url = "http://curtastic.com/drum.wav"
var url2 = "http://curtastic.com/gold.wav"
var myAudio, myAudio2
if(0)
{
var playSound = function(sound)
{
log("playSound() readyState="+sound.readyState)
log("gold readyState="+myAudio2.readyState)
sound.play()
}
var loadSound = function(url, callback)
{
var audio = new Audio(url)
audio.addEventListener('canplaythrough', function()
{
log('canplaythrough');
if(callback)
callback()
}, false)
audio.load()
if(audio.readyState > 3)
{
log('audio.readyState > 3');
if(callback)
callback()
}
return audio
}
myAudio = loadSound(url, startInterval)
myAudio2 = loadSound(url2)
}
else
{
var playSound = function(sound)
{
log("playSound()")
var source = audioContext.createBufferSource()
if(source)
{
source.buffer = sound
if(!source.start)
source.start = source.noteOn
if(source.start)
{
var gain = audioContext.createGain()
source.connect(gain)
gain.connect(audioContext.destination)
source.start()
}
}
}
var loadSound = function(url, callback)
{
log("start loading sound "+url)
var ajax = new XMLHttpRequest()
ajax.open("GET", url, true)
ajax.responseType = "arraybuffer"
ajax.onload = function()
{
audioContext.decodeAudioData(
ajax.response,
function(buffer)
{
log("loaded sound "+url)
log(buffer)
callback(buffer)
},
function(error)
{
log(error)
}
)
}
ajax.send()
}
var AudioContext = window.AudioContext || window.webkitAudioContext
var audioContext = new AudioContext()
loadSound(url, function(r) {myAudio = r; startInterval()})
loadSound(url2, function(r) {myAudio2 = r})
}
function startInterval()
{
log("startInterval()")
setInterval(function()
{
playSound(myAudio)
}, 2000)
}
function log(m)
{
console.log(m)
debug.innerHTML += m+"<br />"
}
</script>
</body>
</html>
You can use either [WKWebViewConfiguration setMediaTypesRequiringUserActionForPlayback:WKAudiovisualMediaTypeNone] or [UIWebView setMediaPlaybackRequiresUserAction:NO] depending on your WebView class (or Swift equivalent).

audio.play() html tag doesn't play in chrome

I am facing an issue while playing audio in chrome when audio.src is not called preceeding to play call. However, firefox plays it alright. Can someone pls suggest? Below is the fiddle link -
http://jsfiddle.net/vn215r2d/1/
One can also find the code over here -
<html>
<head>
<script language="javascript">
var audioo = document.createElement("audio");
function newCall() {
audioo.src = "";
audioo.src = "http://xx.xx.xx.xx:8181/api/captcha/sound";
audioo.play();
}
function playAgain() {
audioo.play(); // doesn't work in chrome :(
}
</script>
</head>
<body>
<button type="button" onClick="newCall()">New Captcha Call</button>
<br>
<button type="button" onClick="playAgain()">Replay Captcha</button>
</body>
</html>
Update
Oh waw, apparently is comes down to the audio.currentTime not being writeable until you rewrite the src. Heres what you can do and it does work:
function newCall() {
audioo.src = "http://xx.xx.xx.xx:8181/api/captcha/sound";
// make all audio attributes writeable again by resetting the src
audioo.src = audio.src;
}
The answer took a bit of googling, but I got it from here: https://stackoverflow.com/a/14850353
Original
I tested on Chrome with resetting the audio position and it worked. Its safest to pause first, but it works either way.
var audioo = document.createElement("audio");
// Lets add an event listener to play when we are ready to start playing
audioo.addEventListener("canplaythrough", function(){
audioo.play();
}, false);
function newCall() {
audioo.src = "";
audioo.src = "http://xx.xx.xx.xx:8181/api/captcha/sound";
}
function playAgain() {
// Let's check if we are ready enough to play
if(audioo.readyState === 4){
audioo.pause(); // first pause
audioo.currentTime = 0; // then reset
audioo.play(); // then play
} else {
// else inform us that we are not ready to play yet.
alert("Audio not ready yet.");
}
}
Here are two fun resources that can help:
MDN:
MediaElement (usefull properties and functions)
MDN:
Media Events (for event listeners)

Categories