Google Chrome, audio, setTimeout, replace audio src, restarts mid-audio - javascript

This is only an issue in Chrome and runs fine in the other browsers.
I click a speaker which plays a welcome message, if the welcome message is not finished, I keep checking until it is finished, then I change the source to another message.
If I then click the speaker again to hear the new message, this message starts and after a few seconds stops and starts again, then stops (my estimate is that it stops when the duration of that message is passed)
In my html:
<audio id="shop_audio" preload="auto" type="audio/mpeg" src="/audios/helper/shop/en/welcome.mp3"></audio>
<p id="shop_speaker">
<i class="fa fa-volume-up"></i>
</p>
In my javascript:
Doc ready:
$("#shop_speaker").click(function(){
document.getElementById("shop_audio").play();
});
Then:
$(window).load(function(){
sl.utils.t_shopInstructions = setTimeout(sl.shop.audio.setAudio, 5000, 0);
});
And:
sl.shop.audio={
setAudio: function(which){
clearTimeout(sl.utils.t_shopInstructions);
var audio = document.getElementById("shop_audio");
if(audio.paused==true){
sl.shop.audio.newSrc(which);
}else{
sl.utils.t_shopAudioPlaying = setInterval(sl.shop.audio.waitForPaused, 500, which);
}
},
waitForPaused: function(which){
if(document.getElementById("shop_audio").paused==true){
clearInterval(sl.utils.t_shopAudioPlaying);
sl.shop.audio.newSrc(which);
}
},
newSrc: function(which){
document.getElementById("shop_audio").src = '/audios/helper/shop/en/help_instruction_'+which+'.mp3';
}
}
And a variant that also works everywhere, except chrome:
(I thought that setInterval was causing trouble, and/or its clearing)
sl.shop.audio={
setAudio: function(which){
clearTimeout(sl.utils.t_shopInstructions);
var audio = document.getElementById("shop_audio");
if(audio.paused==true){
sl.shop.audio.newSrc(which);
}else{
(function looping(){
clearTimeout(sl.utils.t_shopAudioPlaying);
if(audio.paused==false){
sl.utils.t_shopAudioPlaying=setTimeout(looping, 500);
}else{
sl.shop.audio.newSrc(which);
}
})()
}
},
newSrc: function(which){
document.getElementById("shop_audio").src = '/audios/helper/shop/en/help_instruction_'+which+'.mp3';
}
}
Placing the clearTimeout() in different places makes a difference, so I pursued that, but the result is not consistent.
I have now removed all the clearTimeouts - they make no difference whatsoever. The problem stays exactly the same.
Thanks for your suggestions!

I click a speaker which plays a welcome message, if the welcome
message is not finished, I keep checking until it is finished, then I
change the source to another message.
You can substitute ended event for setTimeout. Since you are using jQuery use .one() instead of .click().
var audio = $("#shop_audio")[0];
var speaker = $("#shop_speaker");
function resetAudio() {
audio.play();
audio.onended = function() {
this.onended = null;
this.src = "/path/to/a/different/src";
speaker.one("click", resetAudio);
}
}
speaker.one("click", resetAudio);

Related

AudioContext not working on Safari 15 running state but currentTime not progressing

I cannot seem to get the AudioContext in Safari 15 to function properly. When you initiate it, it is in a running state, but the AudioContext.currentTime never ticks up and nothing plays. All the previous advice covers old versions of Safari where you needed to call resume inside a click handler to get it out of a suspended state, but as you can see below it is running and calling resume does not make a difference.This is the most basic example I could come up with below:
https://codepen.io/thelamer123/pen/vYeLXOm
<html>
<body>
<button onclick="play()">Test Audio</button>
<div id="output"></div>
<script>
async function play() {
var ac = new window.AudioContext();
ac.resume();
var response = await fetch('https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3');
var buffer = await response.arrayBuffer();
ac.decodeAudioData(buffer, onDecoded);
function onDecoded(buffer){
var bs = ac.createBufferSource();
bs.buffer = buffer;
bs.loop = true;
bs.connect(ac.destination);
bs.start(0);
}
var logloop = setInterval(() => {
document.getElementById('output').innerHTML = '';
document.getElementById('output').innerHTML = 'AudioContext State: ' + ac.state + '<br>Current Time: ' + ac.currentTime;
}, 100);
}
</script>
</body>
</html>
So this was a couple things, this example seems to function hit or miss depending on your OS or platform.
The advice I would give anyone running into this is to wrap your logic in a button click, do not try to use event capture. Initiate the Audio context at a top level on page load then when the button is pressed do this:
await audiocontext.resume();
console.log(audiocontext);
For some reason to have compatibility you need to not only be inside an async clicked on function but also console log the audio context after resuming it. With Safari 15.1 it seems it can be in a state where it is "running" but not really if you do not do these two things.

The video loop is multipling itself more than one time every time it ends

This question is using Aframe .
I have the videosphere on loop manually with addEventListener on ("end").
Code works fine until I look at the console. It gives me strange logs.
myvideo = document.querySelector("#videoSphere");
var aEntranceScene1 = document.querySelectorAll("#playButton1");
for (var i = 0; i < aEntranceScene1.length; i++) {
aEntranceScene1[i].addEventListener("click", function () {
Scene1();
});
};
function Scene1(){
myvideo.setAttribute("src", "video/AEntranceScene1.mp4");
myvideo.currentTime = 0;
myvideo.play();
console.log("playing AEntranceScene1");
Hotspots();
myvideo.addEventListener("ended", function () {
console.log("AEntranceScene1 ended");
Scene1();
});
};
function Hotspots() {
sceneIndex = 1;
document.querySelector("#DButton").emit("disappear");
setTimeout(function () {
if (sceneIndex == 1 && !myvideo.paused) {
document.querySelector("#DButton").emit("move-start");
}
}, 3000);
};
When the video plays first time. It displays the log "playing
AEntranceScene1 and when it ended, log displays "AEntranceScene1 ended" 1 time as expected.this (Attached the photo0) played 1 time
But then when the video play again. It displays the log "playing
AEntranceScene1" several times, not only add one. When it ended, log also displays several times of "AEntranceScene1 ended" this (Attached the photo1) played 2 time
This would get worst as more loop completed.This (Attached the photo2) played 3 times only
[
Can you explain to me whats happening here? and any fix to my code?
Thank you
I have found the answer myself.
Its caused by putting autoplay in the <a-assets>,
once i deleted autoplay, it works fine

How to force a media to be loaded at run time, without playing it now?

I am making a web app that can be open for a long time. I don't want to load audio at load time (when the HTML gets downloaded and parsed) to make the first load as fast as possible and to spare precious resources for mobile users. Audio is disabled by default.
Putting the audio in CSS or using preload is not appropriate here because I don't want to load it at load time with the rest.
I am searching for the ideal method to load audio at run time, (after a checkbox has been checked, this can be after 20 minutes after opening the app) given a list of audio elements.
The list is already in a variable allSounds. I have the following audio in a webpage (there are more):
<audio preload="none">
<source src="sound1.mp3">
</audio>
I want to keep the same HTML because after second visit I can easily change it to (this works fine with my server-side HTML generation)
<audio preload="auto">
<source src="sound1.mp3">
</audio>
and it works.
Once the option is turned on, I want to load the sounds, but not play them immediately. I know that .play() loads the sounds. But I want to avoid the delay between pressing a button and the associated feedback sound.
It is better to not play sound than delayed (in my app).
I made this event handler to load sounds (it works) but in the chrome console, it says that download was cancelled, and then restarted I don't know exactly what I am doing wrong.
Is this is the correct way to force load sounds? What are the other ways? If possible without changing the HTML.
let loadSounds = function () {
allSounds.forEach(function (sound) {
sound.preload = "auto";
sound.load();
});
loadSounds = function () {}; // Execute once
};
here is playSound function, but not very important for the questions
const playSound = function (sound) {
// PS
/* only plays ready sound to avoid delays between fetching*/
if (!soundEnabled)) {
return;
}
if (sound.readyState < sound.HAVE_ENOUGH_DATA) {
return;
}
sound.play();
};
Side question: Should there be a preload="full" in the HTML spec?
See also:
Preload mp3 file in queue to avoid any delay in playing the next file in queue
how we can Play Audio with text highlight word by word in angularjs
To cache the audio will need to Base64 encode your MP3 files, and start the Base64 encoded MP3 file with data:audio/mpeg;base64,
Then you can pre-load/cache the file with css using something like:
body::after {
content:url(myfile.mp3);
display:none;
}
I think I would just use the preloading functionality without involving audio tag at all...
Example:
var link = document.createElement('link')
link.rel = 'preload'
link.href = 'sound1.mp3'
link.as = 'audio'
link.onload = function() {
// Done loading the mp3
}
document.head.appendChild(link)
I'm quite sure that I've found a solution for you. As far as I'm concerned, your sounds are additional functionality, and are not required for everybody. In that case I would propose to load the sounds using pure javascript, after user has clicked unmute button.
A simple sketch of solution is:
window.addEventListener('load', function(event) {
var audioloaded = false;
var audioobject = null;
// Load audio on first click
document.getElementById('unmute').addEventListener('click', function(event) {
if (!audioloaded) { // Load audio on first click
audioobject = document.createElement("audio");
audioobject.preload = "auto"; // Load now!!!
var source = document.createElement("source");
source.src = "sound1.mp3"; // Append src
audioobject.appendChild(source);
audioobject.load(); // Just for sure, old browsers fallback
audioloaded = true; // Globally remember that audio is loaded
}
// Other mute / unmute stuff here that you already got... ;)
});
// Play sound on click
document.getElementById('playsound').addEventListener('click', function(event) {
audioobject.play();
});
});
Of course, button should have id="unmute", and for simplicity, body id="body" and play sound button id="playsound. You can modify that of course to suit your needs. After that, when someone will click unmute, audio object will be generated and dynamically loaded.
I didn't try this solution so there may be some little mistakes (I hope not!). But I hope this will get you an idea (sketch) how this can be acomplished using pure javascript.
Don't be afraid that this is pure javascript, without html. This is additional functionality, and javascript is the best way to implement it.
You can use a Blob URL representation of the file
let urls = [
"https://upload.wikimedia.org/wikipedia/commons/b/be/Hidden_Tribe_-_Didgeridoo_1_Live.ogg"
, "https://upload.wikimedia.org/wikipedia/commons/6/6e/Micronesia_National_Anthem.ogg"
];
let audioNodes, mediaBlobs, blobUrls;
const request = url => fetch(url).then(response => response.blob())
.catch(err => {throw err});
const handleResponse = response => {
mediaBlobs = response;
blobUrls = mediaBlobs.map(blob => URL.createObjectURL(blob));
audioNodes = blobUrls.map(blobURL => new Audio(blobURL));
}
const handleMediaSelection = () => {
const select = document.createElement("select");
document.body.appendChild(select);
const label = new Option("Select audio to play");
select.appendChild(label);
select.onchange = () => {
audioNodes.forEach(audio => {
audio.pause();
audio.currentTime = 0;
});
audioNodes[select.value].play();
}
select.onclick = () => {
const media = audioNodes.find(audio => audio.currentTime > 0);
if (media) {
media.pause();
media.currentTime = 0;
select.selectedIndex = 0;
}
}
mediaBlobs.forEach((blob, index) => {
let option = new Option(
new URL(urls[index]).pathname.split("/").pop()
, index
);
option.onclick = () => console.log()
select.appendChild(option);
})
}
const handleError = err => {
console.error(err);
}
Promise.all(urls.map(request))
.then(handleResponse)
.then(handleMediaSelection)
.catch(handleError);

Can't make nativescript-videoplayer and nativescript-audioplayer work alongside each other?

So i'm currently building an app using the multiplatform framework of nativescript. I've got the two plugins in question to work by themselves, but when both of them runs at the same time, and i wish to pause both tracks (video and audio with separate track files), only one of them stops, while the other continues. Why is that?
To start off, my nativescript audioplayer code is:
var videoViewModel = new Observable();
videoViewModel.audioPlayer = new TNSPlayer();
let audioParams = {
audioFile: selectedVideoEntity.audioPath_,
loop: true,
completeCallback: function(){},
errorCallback: function(error){console.log("failed with: ", error)}};
let audioPlayerCreatedCallback = () => {
videoViewModel.audioPlayer.getAudioTrackDuration().then((duration) => {
// iOS: duration is in seconds
// Android: duration is in milliseconds
console.log(`song duration:`, duration);
});
}
//todo: change to something more meaningfull (new RegExp('^' + "https\:\/\/|http\:\/\/", 'i')).test(selectedVideoEntity.audioPath)
if(selectedVideoEntity.audioPath.startsWith("http")){
console.log("getting from url");
videoViewModel.audioPlayer.playFromUrl(audioParams).then(audioPlayerCreatedCallback);
}
else{
videoViewModel.audioPlayer.playFromFile(audioParams).then(audioPlayerCreatedCallback);
}
When this is setup, I move on to making the videoplayer. This is implemented in xml.
<Page xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:VideoPlayer="nativescript-videoplayer"
loaded="loaded" class="page" navigatingFrom="onNavigatingFrom" actionBarHidden="true">
<StackLayout id="layout">
<VideoPlayer:Video id="videoPlayer" controls="true" loop="false"
finished="{{ videoFinished }} "
src="{{ videoPlayerViewModel.videoPath }}" observeCurrentTime="true" muted="true" />
<VideoPlayer:Video id="rewardVideoPlayer" controls="true" loop="false"
finished="{{ rewardVideoFinished }} "/>
</StackLayout>
</Page>
And then I grab the object from the associated page file
let videoPlayer = page.getViewById("videoPlayer");
Expect the video to have it's source and all set at this point.
Now is where the issues begin. The thing i want is to pause the video and the audio at the same time, so my approach is to pause the audio, whenever i call the cycleVideoPlayers pause function and afterwords, like this:
videoViewModel.audioPlayer.pause();
videoViewModel.videoPlayer.pause();
At this point only one of them stops (mostly the audio) and the video continues on. Any ideas why?
Links to the plugins:
https://github.com/bradmartin/nativescript-audio
https://github.com/bradmartin/nativescript-videoplayer
TO CLARIFY THE SITUATION
This only pauses one of the tracks and resumes the paused track, without having any effect
videoViewModel.audioPlayer.pause();
videoViewModel.videoPlayer.pause();
// Wait a few seconds
videoViewModel.audioPlayer.resume();
videoViewModel.videoPlayer.play();
This pauses the video and resumes flawlessly
videoViewModel.videoPlayer.pause();
// Wait a few seconds
videoViewModel.videoPlayer.play();
Same goes for audio
videoViewModel.audioPlayer.pause();
// Wait a few seconds
videoViewModel.audioPlayer.resume();
The explicit code that tries to pause and resume is given here. This code is in an exports.loaded function, in one of my nativescript page .js files.
cycleVideoPlayer.on(gestures.GestureTypes.swipe, function(eventdata) {
console.log("Swipe detected, with direction: " + eventdata.direction);
if (!paused) {
(function() {
vm.audioPlayer.pause();
vm.cycleVideoPlayerViewModel.pause(); //This doesnt work alongside audiopause.. why?
paused = true;
console.log("Paused");
});
} else {
(function() {
vm.audioPlayer.resume();
vm.cycleVideoPlayerViewModel.play();
paused = false;
console.log("Resumed");
});
}
});
If anybody have an idea or need clarification on the matter, please comment :)

Dynamic sounds not playing after two plays

I have a little html5 application where you can play a sound by clicking a button.
I have a function that adds an <audio> tag to a <div> with an id "playing." The sound removes itself when it is done.
function sound(track){
$("#playing").append("<audio src=\"" + track + "\" autoplay onended=\"$(this).remove()\"></audio>");
}
For the button I have:
<button onclick="sound('sounds/tada.mp3')">Tada</button>
When I click the button, an <audio> briefly appears in the element inspector and disappears when it is finished, just the way I want it, but after triggering it two times, it just stops working in Chrome, at least. There are no errors in the console either.
What is going on?
Get rid of the onclick/onend in your HTML and reference the button in your js:
HTML
<button id='tada' sound_url='sounds/tada.mp3'>Tada</button>
And the JS
var sound = function(track){
$("#playing").append("<audio id='played_audio' src='\" + track + \"' autoplay='true'></audio>");
}
$('#tada').on('click', function () {
var sound_url = $(this).attr('sound_url');
sound(sound_url);
});
$('#playing').on('end', 'played_audio', function() {
$(this).remove();
});
Okay, lets see..
var audioURL = "http://soundbible.com/mp3/Canadian Geese-SoundBible.com-56609871.mp3";
var audioEl = null;
function removeAudio() {
if (audioEl && audioEl.parentNode)
audioEl.parentNode.removeChild(audioEl);
}
function sound() {
removeAudio();
audioEl = document.createElement("audio");
audioEl.src = audioURL;
audioEl.controls = true;
audioEl.addEventListener("ended", removeAudio); // <-- Note it's ended, not end!
document.getElementById("playing").appendChild(audioEl);
audioEl.play();
}
document.getElementById("tada").addEventListener("click", sound);
<div id="playing">
</div>
<button id="tada">Tada</button>
I'm not seeing any problems with this script.
Decide audioURL, set audioEl to null as it will be used later
When the element with ID "tada" is clicked, run our sound function.
Remove the audio.
Create the audio element.
When the audio is finished, remove the audio.
Append the audio to the element with ID "playing".
Play the audio.
One thing to note is that I use the ended event, not the end event.
(This answer is here because Andrew really wants us to answer it.)

Categories