Getting rid of lag time when playing sound with jQuery - javascript

I am trying to play sounds with a .keydown() event in jQuery. I would like the sounds to play quickly, but there seems to be a lag time when I perform the keydown event faster than about 3 times per second.
Here is a jsFiddle of my sample code: http://jsfiddle.net/pfrater/FRudg/3/
I am using the audio html tags for the sound and playing:
<audio controls id="sound" preload="auto">
<source src="http://www.wavlist.com/soundfx/011/duck-baby.wav" type="audio/wav"/>
</audio>
<audio controls id="sound2" preload="auto">
<source src="http://rezound.sourceforge.net/examples/chirp.wav" type="audio/wav"/>
</audio>
<audio controls id="sound3" preload="auto">
<source src="http://www.all-birds.com/Sound/downychirp.wav" type="audio/wav"/>
</audio>
and here's my jQuery:
$(document).ready( function() {
var playing;
$(document).bind("keydown", function(key) {
playing = undefined;
switch(parseInt(key.which, 10)) {
case 65:
playing = $("#sound").get(0);
break;
case 83:
playing = $("#sound2").get(0);
break;
case 68:
playing = $("#sound3").get(0);
break;
};
if (playing) {
playing.play();
}
}).on("keyup", function() {
if(playing){
playing.pause();
playing.currentTime=50;
playing = undefined;
}
});
});
Does anyone know of a way to get rid of this lag? Also, the actual files that I'll be playing are mpegs. The ones above are just an example.
Thanks for any help,
Paul

You won't be able to do this with the audio element. This is because the cost setting up and filling the buffers will take too much time.
The good news though is that you can do it using the Web Audio API instead.
I made you an example based on this code from HTML5 rocks (which you should check out for more details) and your original fiddle.
Currently this API is supported in Chrome, Firefox, Safari and Opera will be able to use this:
Fiddle demo
window.AudioContext = window.AudioContext || window.webkitAudioContext;
/// custom buffer loader
/// see http://www.html5rocks.com/en/tutorials/webaudio/intro/
function BufferLoader(context, urlList, callback) {
this.context = context;
this.urlList = urlList;
this.onload = callback;
this.bufferList = new Array();
this.loadCount = 0;
}
BufferLoader.prototype.loadBuffer = function (url, index) {
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.responseType = "arraybuffer";
var loader = this;
request.onload = function () {
// Asynchronously decode the audio file data in request.response
loader.context.decodeAudioData(
request.response,
function (buffer) {
if (!buffer) {
alert('error decoding file data: ' + url);
return;
}
loader.bufferList[index] = buffer;
if (++loader.loadCount == loader.urlList.length)
loader.onload(loader.bufferList);
},
function (error) {
console.error('decodeAudioData error', error);
});
}
request.onerror = function (e) {
alert('BufferLoader: XHR error');
}
request.send();
}
BufferLoader.prototype.load = function () {
for (var i = 0; i < this.urlList.length; ++i)
this.loadBuffer(this.urlList[i], i);
}
The main code:
/// setup audio context and start loading samples
var actx = new AudioContext(),
blst,
bLoader = new BufferLoader(
actx, [
'duck-baby.wav', 'chirp.wav', 'downychirp.wav'],
done),
isReady = false;
/// start loading the samples
bLoader.load();
function done(bl) {
blst = bl; /// buffer list
isReady = true; /// enable keys
$('#status').html('Ready!'); /// update statusw
}
/// this sets up chain so we can play audio
function play(i) {
var src = actx.createBufferSource(); /// prepare sample
src.buffer = blst[i]; /// set buffer from loader
src.connect(actx.destination); /// connect to speakers
src.start(0); /// play sample now
}
/// check keys
$(window).bind("keydown", function (key) {
if (!isReady) return;
switch (parseInt(key.which, 10)) {
case 65:
play(0);
return;
case 83:
play(1);
return;
case 68:
play(2);
return;
}
})
NOTE: When using external samples you must make sure they can be used cross-origin or else loading will fail (I used my DropBox to enable the samples to be loaded with fiddle).

Related

Javascript : Autoplay audio onended function not working

I'm trying to build a web app that fetches a sound of a bird from an API, plays it and gets a new bird once the old one has stopped playing. As of now it works in Safari & Firefox, but the script stops in Chrome.
function init() {
console.log('init');
var container = document.getElementById("container");
container.innerHTML = ("Machines & Birds");
fetchBirdAPI();
}
function fetchBirdAPI() {
fetch(url)
.then(function(response) {
return response.json();
}).then(function(response) {
console.log(response)
getAudio(response);
});
}
function getAudio(response) {
if (response.numRecordings !== 0) {
var birdSrc = (response.recordings[0].file);
var audio = document.createElement('AUDIO');
audio.src = birdSrc;
audio.addEventListener('loadedmetadata', function() {
console.log(audio.duration);
});
audio.play();
audio.onended = function() {
console.log('Audio Ended');
init();
}
}
else {
init();
}
}
So basically the audio.onended doesn't work. Also tried to initiate the audio with audio = new Audio() without any luck.
Really flabbergasted here, so any input would be appreciated.

Javascript to stop playing sound when another starts

So, I'm a complete amateur when it comes to coding, but I still like to fiddle around with it.
I'm currently working on a html/jS/PHP based soundboard and I can't figure out how to stop sound from playing when pressing a button to play another one.
<script type="text/javascript" charset="utf-8">
$(function() {
$("audio").removeAttr("controls").each(function(i, audioElement) {
var audio = $(this);
var that = this; //closure to keep reference to current audio tag
$("#doc").append($('<button>'+audio.attr("title")+'</button>').click(function() {
that.play();
}));
});
});
</script>
I hope someone understands that. Thanks in advance.
There is also a PHP code to fetch the audio file automatically from the folder to the front end, probably unnecessary info for this problem.
This is not very difficult to do if you use HTML5 which introduced the HTMLAudioElement.
Here is the minimal code for what you are trying to do:
// Let's create a soundboard module ("sb")
var sb = {
song: null,
init: function () {
sb.song = new Audio();
sb.listeners();
},
listeners: function () {
$("button").click(sb.play);
},
play: function (e) {
sb.song.src = e.target.value;
sb.song.play();
}
};
$(document).ready(sb.init);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Audio</title>
</head>
<body>
<button value="https://www.gnu.org/music/FreeSWSong.ogg">Song #1</button>
<button value="https://www.gnu.org/music/free-software-song-herzog.ogg">Song #2</button>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</body>
</html>
You may also consider libraries like howler.js to help you in the development process.
What you could do, is before start playing a new audio pause all available audio on the page. Something like this.
var audioOne = document.querySelector('#audio-1');
var audioTwo = document.querySelector('#audio-2');
var allAudios = document.querySelectorAll('audio');
function stopAllAudio(){
allAudios.forEach(function(audio){
audio.pause();
});
}
document.querySelector('#play-1').addEventListener('click', function(){
stopAllAudio();
audioOne.play();
})
document.querySelector('#play-2').addEventListener('click', function(){
stopAllAudio();
audioTwo.play();
})
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<audio id="audio-1"
src="http://developer.mozilla.org/#api/deki/files/2926/=AudioTest_(1).ogg">
</audio>
<audio id="audio-2"
src="http://www.w3schools.com/html/horse.mp3">
</audio>
<button id="play-1">
play audio 1
</button>
<button id="play-2">
play audio 2
</button>
</body>
</html>
Instead of adding audio using <audio> tag you could use HTMLAudioElement.
You can stop and reset an audio element by pausing it and setting its current time to 0. You would need to do this whenever a button is clicked. Example:
// Available sounds:
const sounds = {
"Bottle": "http://freewavesamples.com/files/Bottle.wav",
"Bamboo": "http://freewavesamples.com/files/Bamboo.wav"
}
// Load audio elements:
let audios = {};
for (let [title, url] of Object.entries(sounds)) {
audios[title] = new Audio(url);
}
// Create board buttons:
let board = document.getElementById("board");
for (let title of Object.keys(audios)) {
let button = document.createElement("button");
button.textContent = title;
button.dataset["audio"] = title;
board.appendChild(button);
}
// Handle board button clicks:
board.addEventListener("click", function(event) {
let audio = audios[event.target.dataset["audio"]];
if (audio) {
// Pause and reset all audio elements:
for (let audio of Object.values(audios)) {
audio.pause();
audio.currentTime = 0;
}
// Play this audio element:
audio.play();
}
});
<div id="board"></div>
In case you want to leverage the full power of the Web Audio API, you would probably start building your soundboard similar to this:
// Load buffer from 'url' calling 'cb' on complete:
function loadBuffer(url, cb) {
var request = new XMLHttpRequest();
request.open('GET', url);
request.responseType = 'arraybuffer';
request.onload = () => context.decodeAudioData(request.response, cb);
request.send();
}
// Available sounds:
const sounds = {
"Bottle": "url/to/bottle.wav",
"Bamboo": "url/to/bamboo.wav"
};
let audioCtx = new (AudioContext || webkitAudioContext)(),
board = document.getElementById("soundboard"),
buffers = {},
source;
// Load buffers:
for (let [title, url] of Object.entries(sounds)) {
loadBuffer(url, buffer => buffers[title] = buffer);
}
// Create board buttons:
for (let title of Object.keys(sounds)) {
let button = document.createElement("button");
button.textContent = title;
button.dataset["buffer"] = title;
board.appendChild(button);
}
// Handle board button clicks:
board.addEventListener("click", function(event) {
let buffer = buffers[event.target.dataset["buffer"]];
if (buffer) {
if (source) source.stop();
source = audioCtx.createBufferSource();
source.buffer = buffer;
source.connect(audioCtx.destination);
source.start();
}
});
<div id="soundboard"></div>
Please note that the sound URLs given above must either be on the same domain or available under the same origin policy (see CORS headers).
Below code may help others:
var audioMap = new Map();
var rappers = document.querySelectorAll('.rapper');
rappers.forEach(function(rapper){
audioMap.set(rapper, new Audio());
rapper.addEventListener('click', function(){
var audio = new Audio($(this).data('audio'));
audio.play();
audioMap.set(this, audio);
var current = audioMap.get(this);
// console.log('get', current);
audioMap.forEach(function(audio){
if( audio != current ){
audio.pause();
audio.currentTime = 0;
}
});
});
});
For the ones that don't use the audioTag in HTML5 and want to use only vanilla js this may help ya
var songplaying = null; //This is where we will store the current playing song
function playsong(song_name){
//settings
var playing = null;
if(songplaying==null){ //If a song isn't stored that means that it's not playing and vice versa
playing = false;
}else{
playing = true;
}
var dir = "./songs/"+song_name+".mp3"; //Here we get the directory of the song
var song = new Audio(dir); //Making a new audio element
if(!playing){ //This runs when no song is playing
song.play();
songplaying=song; //We save the current playing song
}else{ //This runs when another song is already playing
var song_alredyplaying = songplaying; //We get the audio element (song)
song_alredyplaying.pause(); //Pause the song
song.play(); //Then we play the new song
songplaying=song; //Save the current playing song
}
}
//Example
var playbtn = document.querySelector(".play");
playbtn.addEventListener('click', function(){
playsong("the song name");
};

Ajax request to pre-download video(s)

I have been trying to download a video (which will ultimately be from my own resource) and while the video is downloading I want to show a loading message.
Once it is fully downloaded I want to hide the loading icon and have the video ready to be played.
I am having trouble getting the video file and setting it to the video attribute in the HTML. Here is the code I am using...
JS:
var root = 'https://crossorigin.me/http://techslides.com/demos/sample-videos/small.mp4';
function loadVideo() {
$('.loading').show();
$.ajax({
url: root,
method: 'GET'
}).then(function(data) {
$('.loading').hide();
console.log("done");
$('video').attr('src', data);
$("video")[0].load();
//console.log(data);
});
}
HTML:
<div class="loading animated bounce infinite">
Loading
</div>
<video width="320" height="240" controls>
</video>
You don't need an ajax request for this. You could simply set the source of the video element, and then listen to loadeddata. There are 4 different readyStates you can check for. HAVE_ENOUGH_DATA (readyState 4) stands for:
Enough data is available—and the download rate is high enough—that the media can be played through to the end without interruption.
An implementation for this could look like this:
function loadVideo(videoURL, $player) {
$player = $videoObj || $('video');
$('.loading').show();
$player.attr('src', videoURL);
var player = $player[0];
player.addEventListener('loadeddata', function() {
if(player.readyState == 4) {
// or whatever you want to do
// in case the video has enough data
$('.loading').hide();
}
});
}
In case you really need the "complete" state (the 100%), you could use the progress event to determine how much of the video has been buffered, in case the buffer is 100%, the video should be completely loaded.
function loadVideoCompleted(videoURL, $player) {
$player = $videoObj || $('video');
$('.loading').show();
$player.attr('src', videoURL);
var player = $player[0];
player.addEventListener('loadeddata', function() {
var percentageBuffered = 0;
if (player.buffered.length > 0
&& player.buffered.end && player.duration) {
percentageBuffered = player.buffered.end(0) / player.duration;
} else if (player.bytesTotal != undefined &&
player.bytesTotal > 0 && player.bufferedBytes != undefined) {
percentageBuffered = player.bufferedBytes / player.bytesTotal;
}
if (percentageBuffered == 1) {
// or whatever you want to do in case
// 100% of the video has been buffered
$('.loading').hide();
}
});
}

Autoplay within HTML

I am new to Javascript... like super new :X and i am writing my very first script to try and automate video episodes of my favorite anime because the videos on the site don't have a autoplay feature after you click the link. I read about Video and Var = 'My video' to try and figure out how to tell the script, when page loads video .autoplay = true...but i just get to dead ends. here is where i am at:
var urlsToLoad = [
'http://Site1/Ep1',
'http://Site1/Ep2',
'http://Site1/Ep3',
'http://Site1/Ep4'
];
if (document.readyState == "complete") {
FireTimer ();
}
window.addEventListener ("hashchange", FireTimer, false);
function FireTimer () {
setTimeout (GotoNextURL, 5000); // 5000 == 5 seconds
}
function GotoNextURL () {
var numUrls = urlsToLoad.length;
var urlIdx = urlsToLoad.indexOf (location.href);
urlIdx++;
if (urlIdx >= numUrls) urlIdx = 0;
Once the first video link starts 'Site1/EP1', i have this going:
function myFunction() {
var vid = document.getElementById("925664"); <--Ctrl+F 'Videoid' from Source
vid.autoplay = true;
vid.load();
}
but it just stays died like 'click play to start' ..
I can really use help here PLZZZ and thank you.
Something like this:
<!-- HTML Page -->
<video id="925664"></video>
// Javascript
function myFunction() {
var vid = document.getElementById("925664"); // <--Ctrl+F 'Videoid' from Source
vid.src = "path/to/file.mp4";
//vid.autoplay = true; // not really anything that's useful
vid.load();
vid.play();
}
Warning: Most mobile devices don't allow media files to play via code and require human interaction (click/touch) in order to start playback. However, on desktops this isn't a problem.
Try using the play() method instead.
function myFunction() {
var vid = document.getElementById("925664");
vid.play();
}

HTML5 Audio tag on Safari has a delay

I'm trying to accomplish a simple doodle-like behaviour, where a mp3/ogg sound rings on click, using the html tag. It is supposed to work under Firefox, Safari and Safari iPad is very desireable.
I've tried many approaches and have come down to this:
HTML
<span id="play-blue-note" class="play blue" ></span>
<span id="play-green-note" class="play green" ></span>
<audio id="blue-note" style="display:none" controls preload="auto" autobuffer>
<source src="blue.mp3" />
<source src="blue.ogg" />
<!-- now include flash fall back -->
</audio>
<audio id="green-note" style="display:none" controls preload="auto" autobuffer>
<source src="green.mp3" />
<source src="green.ogg" />
</audio>
JS
function addSource(elem, path) {
$('<source>').attr('src', path).appendTo(elem);
}
$(document).ready(function() {
$('body').delegate('.play', 'click touchstart', function() {
var clicked = $(this).attr('id').split('-')[1];
$('#' + clicked + '-note').get(0).play();
});
});
This seems to work great under Firefox but Safari seems to have a delay whenever you click, even when you click several times and the audio file has loaded. On Safari on iPad it behaves almost unpredictably.
Also, Safari's performance seems to improve when I test locally, I'm guessing Safari is downloading the file each time. Is this possible? How can I avoid this?
Thanks!
On desktop Safari, adding AudioContext fixes the issue:
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();
I found out by accident, so I have no idea why it works, but this removed the delay on my app.
I just answered another iOS/<audio> question a few minutes ago. Seems to apply here as well:
Preloading <audio> and <video> on iOS devices is disabled to save bandwidth.
In Safari on iOS (for all devices, including iPad), where the user may
be on a cellular network and be charged per data unit, preload and
autoplay are disabled. No data is loaded until the user initiates it.
Source: Safari Developer Library
The problem with Safari is that it puts a request every time for the audio file being played. You can try creating an HTML5 cache manifest. Unfortunately my experience has been that you can only add to the cache one audio file at a time. A workaround might be to merge all your audio files sequentially into a single audio file, and start playing at a specific position depending on the sound needed. You can create an interval to track the current play position and pause it once it has reached a certain time stamp.
Read more about creating an HTML5 cache manifest here:
http://www.html5rocks.com/en/tutorials/appcache/beginner/
http://www.whatwg.org/specs/web-apps/current-work/multipage/offline.html
Hope it helps!
HTML5 Audio Delay on Safari iOS (<audio> Element vs AudioContext)
Yes, Safari iOS has an audio delay when using the native <audio> Element ...however this can be overcome by using AudioContext.
My code snippet is based on what I learnt from https://lowlag.alienbill.com/
Please test the functionality on your own iOS device (I tested in iOS 12)
https://fiddle.jshell.net/eLya8fxb/51/show/
Snippet from JS Fiddle
https://jsfiddle.net/eLya8fxb/51/
// Requires jQuery
// Adding:
// Strip down lowLag.js so it only supports audioContext (So no IE11 support (only Edge))
// Add "loop" monkey patch needed for looping audio (my primary usage)
// Add single audio channel - to avoid overlapping audio playback
// Original source: https://lowlag.alienbill.com/lowLag.js
if (!window.console) console = {
log: function() {}
};
var lowLag = new function() {
this.someVariable = undefined;
this.showNeedInit = function() {
lowLag.msg("lowLag: you must call lowLag.init() first!");
}
this.load = this.showNeedInit;
this.play = this.showNeedInit;
this.pause = this.showNeedInit;
this.stop = this.showNeedInit;
this.switch = this.showNeedInit;
this.change = this.showNeedInit;
this.audioContext = undefined;
this.audioContextPendingRequest = {};
this.audioBuffers = {};
this.audioBufferSources = {};
this.currentTag = undefined;
this.currentPlayingTag = undefined;
this.init = function() {
this.msg("init audioContext");
this.load = this.loadSoundAudioContext;
this.play = this.playSoundAudioContext;
this.pause = this.pauseSoundAudioContext;
this.stop = this.stopSoundAudioContext;
this.switch = this.switchSoundAudioContext;
this.change = this.changeSoundAudioContext;
if (!this.audioContext) {
this.audioContext = new(window.AudioContext || window.webkitAudioContext)();
}
}
//we'll use the tag they hand us, or else the url as the tag if it's a single tag,
//or the first url
this.getTagFromURL = function(url, tag) {
if (tag != undefined) return tag;
return lowLag.getSingleURL(url);
}
this.getSingleURL = function(urls) {
if (typeof(urls) == "string") return urls;
return urls[0];
}
//coerce to be an array
this.getURLArray = function(urls) {
if (typeof(urls) == "string") return [urls];
return urls;
}
this.loadSoundAudioContext = function(urls, tag) {
var url = lowLag.getSingleURL(urls);
tag = lowLag.getTagFromURL(urls, tag);
lowLag.msg('webkit/chrome audio loading ' + url + ' as tag ' + tag);
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
// Decode asynchronously
request.onload = function() {
// if you want "successLoadAudioFile" to only be called one time, you could try just using Promises (the newer return value for decodeAudioData)
// Ref: https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/decodeAudioData
//Older callback syntax:
//baseAudioContext.decodeAudioData(ArrayBuffer, successCallback, errorCallback);
//Newer promise-based syntax:
//Promise<decodedData> baseAudioContext.decodeAudioData(ArrayBuffer);
// ... however you might want to use a pollfil for browsers that support Promises, but does not yet support decodeAudioData returning a Promise.
// Ref: https://github.com/mohayonao/promise-decode-audio-data
// Ref: https://caniuse.com/#search=Promise
// var retVal = lowLag.audioContext.decodeAudioData(request.response);
// Note: "successLoadAudioFile" is called twice. Once for legacy syntax (success callback), and once for newer syntax (Promise)
var retVal = lowLag.audioContext.decodeAudioData(request.response, successLoadAudioFile, errorLoadAudioFile);
//Newer versions of audioContext return a promise, which could throw a DOMException
if (retVal && typeof retVal.then == 'function') {
retVal.then(successLoadAudioFile).catch(function(e) {
errorLoadAudioFile(e);
urls.shift(); //remove the first url from the array
if (urls.length > 0) {
lowLag.loadSoundAudioContext(urls, tag); //try the next url
}
});
}
};
request.send();
function successLoadAudioFile(buffer) {
lowLag.audioBuffers[tag] = buffer;
if (lowLag.audioContextPendingRequest[tag]) { //a request might have come in, try playing it now
lowLag.playSoundAudioContext(tag);
}
}
function errorLoadAudioFile(e) {
lowLag.msg("Error loading webkit/chrome audio: " + e);
}
}
this.playSoundAudioContext = function(tag) {
var context = lowLag.audioContext;
// if some audio is currently active and hasn't been switched, or you are explicitly asking to play audio that is already active... then see if it needs to be unpaused
// ... if you've switch audio, or are explicitly asking to play new audio (that is not the currently active audio) then skip trying to unpause the audio
if ((lowLag.currentPlayingTag && lowLag.currentTag && lowLag.currentPlayingTag === lowLag.currentTag) || (tag && lowLag.currentPlayingTag && lowLag.currentPlayingTag === tag)) {
// find currently paused audio (suspended) and unpause it (resume)
if (context !== undefined) {
// ref: https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/suspend
if (context.state === 'suspended') {
context.resume().then(function() {
lowLag.msg("playSoundAudioContext resume " + lowLag.currentPlayingTag);
return;
}).catch(function(e) {
lowLag.msg("playSoundAudioContext resume error for " + lowLag.currentPlayingTag + ". Error: " + e);
});
return;
}
}
}
if (tag === undefined) {
tag = lowLag.currentTag;
}
if (lowLag.currentPlayingTag && lowLag.currentPlayingTag === tag) {
// ignore request to play same sound a second time - it's already playing
lowLag.msg("playSoundAudioContext already playing " + tag);
return;
} else {
lowLag.msg("playSoundAudioContext " + tag);
}
var buffer = lowLag.audioBuffers[tag];
if (buffer === undefined) { //possibly not loaded; put in a request to play onload
lowLag.audioContextPendingRequest[tag] = true;
lowLag.msg("playSoundAudioContext pending request " + tag);
return;
}
// need to create a new AudioBufferSourceNode every time...
// you can't call start() on an AudioBufferSourceNode more than once. They're one-time-use only.
var source;
source = context.createBufferSource(); // creates a sound source
source.buffer = buffer; // tell the source which sound to play
source.connect(context.destination); // connect the source to the context's destination (the speakers)
source.loop = true;
lowLag.audioBufferSources[tag] = source;
// find current playing audio and stop it
var sourceOld = lowLag.currentPlayingTag ? lowLag.audioBufferSources[lowLag.currentPlayingTag] : undefined;
if (sourceOld !== undefined) {
if (typeof(sourceOld.noteOff) == "function") {
sourceOld.noteOff(0);
} else {
sourceOld.stop();
}
lowLag.msg("playSoundAudioContext stopped " + lowLag.currentPlayingTag);
lowLag.audioBufferSources[lowLag.currentPlayingTag] = undefined;
lowLag.currentPlayingTag = undefined;
}
// play the new source audio
if (typeof(source.noteOn) == "function") {
source.noteOn(0);
} else {
source.start();
}
lowLag.currentTag = tag;
lowLag.currentPlayingTag = tag;
if (context.state === 'running') {
lowLag.msg("playSoundAudioContext started " + tag);
} else if (context.state === 'suspended') {
/// if the audio context is in a suspended state then unpause (resume)
context.resume().then(function() {
lowLag.msg("playSoundAudioContext started and then resumed " + tag);
}).catch(function(e) {
lowLag.msg("playSoundAudioContext started and then had a resuming error for " + tag + ". Error: " + e);
});
} else if (context.state === 'closed') {
// ignore request to pause sound - it's already closed
lowLag.msg("playSoundAudioContext failed to start, context closed for " + tag);
} else {
lowLag.msg("playSoundAudioContext unknown AudioContext.state for " + tag + ". State: " + context.state);
}
}
this.pauseSoundAudioContext = function() {
// not passing in a "tag" parameter because we are playing all audio in one channel
var tag = lowLag.currentPlayingTag;
var context = lowLag.audioContext;
if (tag === undefined) {
// ignore request to pause sound as nothing is currently playing
lowLag.msg("pauseSoundAudioContext nothing to pause");
return;
}
// find currently playing (running) audio and pause it (suspend)
if (context !== undefined) {
// ref: https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/suspend
if (context.state === 'running') {
lowLag.msg("pauseSoundAudioContext " + tag);
context.suspend().then(function() {
lowLag.msg("pauseSoundAudioContext suspended " + tag);
}).catch(function(e) {
lowLag.msg("pauseSoundAudioContext suspend error for " + tag + ". Error: " + e);
});
} else if (context.state === 'suspended') {
// ignore request to pause sound - it's already suspended
lowLag.msg("pauseSoundAudioContext already suspended " + tag);
} else if (context.state === 'closed') {
// ignore request to pause sound - it's already closed
lowLag.msg("pauseSoundAudioContext already closed " + tag);
} else {
lowLag.msg("pauseSoundAudioContext unknown AudioContext.state for " + tag + ". State: " + context.state);
}
}
}
this.stopSoundAudioContext = function() {
// not passing in a "tag" parameter because we are playing all audio in one channel
var tag = lowLag.currentPlayingTag;
if (tag === undefined) {
// ignore request to stop sound as nothing is currently playing
lowLag.msg("stopSoundAudioContext nothing to stop");
return;
} else {
lowLag.msg("stopSoundAudioContext " + tag);
}
// find current playing audio and stop it
var source = lowLag.audioBufferSources[tag];
if (source !== undefined) {
if (typeof(source.noteOff) == "function") {
source.noteOff(0);
} else {
source.stop();
}
lowLag.msg("stopSoundAudioContext stopped " + tag);
lowLag.audioBufferSources[tag] = undefined;
lowLag.currentPlayingTag = undefined;
}
}
this.switchSoundAudioContext = function(autoplay) {
lowLag.msg("switchSoundAudioContext " + (autoplay ? 'and autoplay' : 'and do not autoplay'));
if (lowLag.currentTag && lowLag.currentTag == 'audio1') {
lowLag.currentTag = 'audio2';
} else {
lowLag.currentTag = 'audio1';
}
if (autoplay) {
lowLag.playSoundAudioContext();
}
}
this.changeSoundAudioContext = function(tag, autoplay) {
lowLag.msg("changeSoundAudioContext to tag " + tag + " " + (autoplay ? 'and autoplay' : 'and do not autoplay'));
if(tag === undefined) {
lowLag.msg("changeSoundAudioContext tag is undefined");
return;
}
lowLag.currentTag = tag;
if (autoplay) {
lowLag.playSoundAudioContext();
}
}
this.msg = function(m) {
m = "-- lowLag " + m;
console.log(m);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
<script>
// AudioContext
$(document).ready(function() {
lowLag.init();
lowLag.load(['https://coubsecure-s.akamaihd.net/get/b86/p/coub/simple/cw_looped_audio/f0dab49f867/083bf409a75db824122cf/med_1550250381_med.mp3'], 'audio1');
lowLag.load(['https://coubsecure-s.akamaihd.net/get/b173/p/coub/simple/cw_looped_audio/0d5adfff2ee/80432a356484068bb0e15/med_1550254045_med.mp3'], 'audio2');
// starts with audio1
lowLag.changeSoundAudioContext('audio1', false);
});
// ----------------
// Audio Element
$(document).ready(function() {
var $audioElement = $('#audioElement');
var audioEl = $audioElement[0];
var audioSources = {
"audio1": "https://coubsecure-s.akamaihd.net/get/b86/p/coub/simple/cw_looped_audio/f0dab49f867/083bf409a75db824122cf/med_1550250381_med.mp3",
"audio2": "https://coubsecure-s.akamaihd.net/get/b173/p/coub/simple/cw_looped_audio/0d5adfff2ee/80432a356484068bb0e15/med_1550254045_med.mp3"
};
playAudioElement = function() {
audioEl.play();
}
pauseAudioElement = function() {
audioEl.pause();
}
stopAudioElement = function() {
audioEl.pause();
audioEl.currentTime = 0;
}
switchAudioElement = function(autoplay) {
var source = $audioElement.attr('data-source');
if (source && source == 'audio1') {
$audioElement.attr('src', audioSources.audio2);
$audioElement.attr('data-source', 'audio2');
} else {
$audioElement.attr('src', audioSources.audio1);
$audioElement.attr('data-source', 'audio1');
}
if (autoplay) {
audioEl.play();
}
}
changeAudioElement = function(tag, autoplay) {
var source = $audioElement.attr('data-source');
if(tag === undefined || audioSources[tag] === undefined) {
return;
}
$audioElement.attr('src', audioSources[tag]);
$audioElement.attr('data-source', tag);
if (autoplay) {
audioEl.play();
}
}
changeAudioElement('audio1', false); // starts with audio1
});
</script>
<h1>
AudioContext (api)
</h1>
<button onClick="lowLag.play();">Play</button>
<button onClick="lowLag.pause();">Pause</button>
<button onClick="lowLag.stop();">Stop</button>
<button onClick="lowLag.switch(true);">Swtich</button>
<button onClick="lowLag.change('audio1', true);">Play 1</button>
<button onClick="lowLag.change('audio2', true);">Play 2</button>
<hr>
<h1>
Audio Element (api)
</h1>
<audio id="audioElement" controls loop preload="auto" src="">
</audio>
<br>
<button onClick="playAudioElement();">Play</button>
<button onClick="pauseAudioElement();">Pause</button>
<button onClick="stopAudioElement();">Stop</button>
<button onClick="switchAudioElement(true);">Switch</button>
<button onClick="changeAudioElement('audio1', true);">Play 1</button>
<button onClick="changeAudioElement('audio2', true);">Play 2</button>
Apple decided (to save money on celluar) to not pre-load <audio> and <video> HTML elements.
From the Safari Developer Library:
In Safari on iOS (for all devices, including iPad), where the user may
be on a cellular network and be charged per data unit, preload and
autoplay are disabled. No data is loaded until the user initiates it.
This means the JavaScript play() and load() methods are also inactive
until the user initiates playback, unless the play() or load() method
is triggered by user action. In other words, a user-initiated Play
button works, but an onLoad="play()" event does not.
This plays the movie: <input type="button" value="Play" onClick="document.myMovie.play()">
This does nothing on iOS: <body onLoad="document.myMovie.play()">
I don't think you can bypass this restriction, but you might be able to.
Remember: Google is your best friend.
Update: After some experimenting, I found a way to play the <audio> with JavaScript:
var vid = document.createElement("iframe");
vid.setAttribute('src', "http://yoursite.com/yourvideooraudio.mp4"); // replace with actual source
vid.setAttribute('width', '1px');
vid.setAttribute('height', '1px');
vid.setAttribute('scrolling', 'no');
vid.style.border = "0px";
document.body.appendChild(vid);
Note: I only tried with <audio>.
Update 2: jsFiddle here. Seems to work.
Unfortunately, the only way to make it work properly in Safari we need to use WebAudio API, or third-party libs to handle this. Check the source code here (it's not minified)
https://drums-set-js.herokuapp.com/index.html
https://drums-set-js.herokuapp.com/app.js
Same issue. I tried to preload it via different ways. Finally I wrapped animation logic to "playing" callback. So this logic should work only if file loaded and playing started, but as a result I see that animation logic already started, and audio playing with around 2 seconds delay.
It's braking my mind, how it can has delay if audio already called "playing" callback?
Audio Context resolved my issue.
The simplest example I found here
https://developer.mozilla.org/en-US/docs/Web/API/Body/arrayBuffer
getData - preparing your audio file;
then you can play it with source.start(0);
This link missed how to get audioCtx you can copy it here
let audioCtx = new (window.AudioContext || window.webkitAudioContext)();
your audio files are loaded once then cached.. playing the sounds repeatedly, even after page refresh, did not cause further HTTP requests in Safari..
i just had a look at one of your sounds in an audio editor - there was a small amount of silence at the beginning of the file.. this will manifest as latency..
is the Web Audio API a viable option for you?
I am having this same issue. What is odd is that I am preloading the file. But with WiFi it plays fine, but on phone data, there is a long delay before starting. I thought that had something to do with load speeds, but I do not start playing my scene until all images and the audio file are loaded. Any suggestions would be great. (I know this isn't an answer but I thought it better that making a dup post).
I would simply create <audio autoplay /> dom element on click, this works in all major browsers - no need to handle events and trigger play manually
if you want to respond to audio status change manually - I would suggest to listen for play event instead of loadeddata - it's behavior is more consistent in different browsers
If you have a small/short audio file that doesn't require a lot of audio clarity, you can convert the audio file to base64 encoding.
This way the audio file will be text based and doesn't have latency related to downloading the audio file, since iOS downloads the audio pretty much when it's played.
On one hand, it's nice what iOS does to prevent abuse. On the other hand, it's annoying when it gets in the way of legitimate usage.
Here's a base64 encoder for audio files.

Categories

Resources