before anything ! i have checked all answers in similar questions and the answers wasn't usefull and old .
i have tried to use webrtc on firefox but it did not work ! and no error was givin !
this code work on all browsers but firefox
this is my code
let configuration = {
"iceServers":[{
'url':'stun:stun.l.google.com:19302'
}]
};
io.on("signaling_message",(data)=>{
//displayMessage(data.message);
if(!rtcPeerConn){
startSignaling();
}
if(data.type!="user_here"){
let message = JSON.parse(data.message);
if(message.sdp){
rtcPeerConn.setRemoteDescription(new RTCSessionDescription(message.sdp),()=>{
if(rtcPeerConn.remoteDescription.type=="offer"){
rtcPeerConn.createAnswer(sendLocalDesc,logerror)
}
})
}else{
rtcPeerConn.addIceCandidate(new RTCIceCandidate(message.candidate));
}
}
})
function startSignaling(){
displayMessage("start signaling...");
rtcPeerConn = new RTCPeerConnection(configuration);
//send ice candidate to other peer
rtcPeerConn.onicecandidate = function(evt){
if(evt.candidate){
io.emit("signal",{"type":"ice candidate","message":JSON.stringify({'candidate':evt.candidate}),room:signal_room})
displayMessage("completed that ice candidate");
}
}
rtcPeerConn.onnegotiationneeded = function(){
displayMessage("on negotiationnneded");
rtcPeerConn.createOffer(sendLocalDesc,logerror);
}
rtcPeerConn.onaddstream = (evt,err)=>{
displayMessage("creating the other stream");
if(err){
displayMessage(err)
}
success2(evt.stream);
}
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
navigator.mediaDevices.getUserMedia({video:true,audio:true}).then(stream=>{
success(stream);
rtcPeerConn.addStream(stream);
}).catch(err=>{
logerror(err);
});
}
function sendLocalDesc(desc){
rtcPeerConn.setLocalDescription(desc,()=>{
displayMessage("set local description");
io.emit("signal",{type:"SDP",message:JSON.stringify({'sdp':rtcPeerConn.localDescription}),room:signal_room})
},logerror);
}
function success(stream){
if ("srcObject" in video) {
video.srcObject = stream;
} else {
// Avoid using this in new browsers, as it is going away.
video.src = window.URL.createObjectURL(stream);
}
video.play();
}
function success2(stream){
if ("srcObject" in video2) {
video2.srcObject = stream;
} else {
// Avoid using this in new browsers, as it is going away.
video2.src = window.URL.createObjectURL(stream);
}
video2.play();
}
is there anything wrong with my code?
another question , can i use google stun iceservers on production ? are they reliable?
EDIT
the firefox stuck at this script
rtcPeerConn.onicecandidate = function(evt){
if(evt.candidate){
io.emit("signal",{"type":"ice candidate","message":JSON.stringify({'candidate':evt.candidate}),room:signal_room})
displayMessage("completed that ice candidate");
}
}
it keeps displaying this message (completed that ice candidate) like a loop
Have you applied webrtc-adapter?
And google stun server is not proper on production.
Because if your clients are located behind NAT or firewall, P2P connection is impossible.
If P2P Connection failed in WebRTC, you have to ready TURN server for relay.
for example, you can use coTURN
Good evening Stack Overflow!
I really need help for a project of mine where I'm using sip.js and a VoIP to make real calls to a phone number.
The Goal
I want to allow the user to record the audio and microphone and save the data on a server (in base64 encoding or as a file). So I after the conversation can hear the conversation again and use it for what ever my purpose (employee training) was.
The Problem
I can't get the sound of the person speaking, which comes through and -HTML tag (working with the sip.js plugin). As of now I haven't found any way to successfully save the sound streaming through this audio tag.
What I've done so far
I've successfully figured out how to record the audio of the microphone using a plugin called AudioRecorder which allows me to record the audio through the microphone and saving it. I slightly changed the code so it got saved encoded as base64. This all work as expected, though I only get the audio of my own voice, and not the person I'm talking with.
Because I succeed to record the audio of my own voice I looked into the AudioRecorder plugin and tried to reverse the plugin to record from a audio tag. I found the "createMediaStreamSource" function inside AudioRecorder which I wanted to work with the -tag which did not work (as I suspected, because the -tag in it self isn't a stream (of which i understand).
The Code
I'm basically using the sip.js plugin to establish a call to a phone number by using below code (just using an example, matching my code, because my raw code contains some added values which doesn't need to be showed here):
// Create a user agent called bob, connect, and register to receive invitations.
var userAgent = new SIP.UA({
uri: 'bob#example.com',
wsServers: ['wss://sip-ws.example.com'],
register: true
});
var options = { media: { constraints: { audio: true, video: false }, render: { remote: document.getElementById("audio") } } };
Then i use the build in invite function to call a phonenumber, which does the rest. Audio and microphone is now up and running.
userAgent.invite("+4512345678", options);
I can now talk with my new best friend Bob. But I can't record other than my own sound as of now.
Whats Next?
I would really like some help to understand how I can record the sound of "Bob" and store it, preferred in the same file as my own voice. If I have to record two separately files and play them synced, I won't mind, but else if preferred.
I know this might just be a call for help without showing anything real code of what I've tried to do it myself, but I have to admit I just fiddled with the code for hours without any good results and now I'm screaming for help.
Thank your all in advance and sorry for the bad grammar and (mis)use of language.
Okay, so I after finally found a solution to my problem, which I though i wanted to share here.
What I did to solve the problem was to add ONE simple line of code to the "normal" recording script of a microphone. The script to record mic audio is:
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var audioGlobalContext = new AudioContext();
var audioOutputAnalyser
var inputPoint = null,
audioRecorder = null;
var recording = false;
// Controls the start and stop of recording
function toggleRecording( e ) {
if (recording == true) {
recording = false;
audioRecorder.stop();
audioRecorder.getBuffers( gotBuffers );
console.log("Stop recording");
} else {
if (!audioRecorder)
return;
recording = true;
audioRecorder.clear();
audioRecorder.record();
console.log("Start recording");
}
}
function gotBuffers(buffers) {
audioRecorder.exportWAV(doneEncoding);
}
function doneEncoding(blob) {
document.getElementById("outputAudio").pause();
Recorder.setupDownload(blob);
}
function gotAudioMicrophoneStream(stream) {
var source = audioGlobalContext.createMediaStreamSource(stream);
source.connect(inputPoint);
}
function initAudio() {
if (!navigator.getUserMedia)
navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
if (!navigator.cancelAnimationFrame)
navigator.cancelAnimationFrame = navigator.webkitCancelAnimationFrame || navigator.mozCancelAnimationFrame;
if (!navigator.requestAnimationFrame)
navigator.requestAnimationFrame = navigator.webkitRequestAnimationFrame || navigator.mozRequestAnimationFrame;
inputPoint = audioGlobalContext.createGain();
navigator.getUserMedia({
"audio": {
"mandatory": {
"googEchoCancellation": "true",
"googAutoGainControl": "false",
"googNoiseSuppression": "true",
"googHighpassFilter": "false"
},
"optional": []
},
}, gotAudioMicrophoneStream, function(e) {
alert('Error recording microphone');
console.log(e);
});
var analyserNode = audioGlobalContext.createAnalyser();
analyserNode.fftSize = 2048;
inputPoint.connect(analyserNode);
var zeroGain = audioGlobalContext.createGain();
zeroGain.gain.value = 0.0;
inputPoint.connect(zeroGain);
zeroGain.connect(audioGlobalContext.destination);
audioRecorder = new Recorder(inputPoint);
}
window.addEventListener('load', initAudio );
The function I was looking for to convert the Audio-tag sound into an Audio Source was createMediaElementSource() so what I did was adding this function:
function gotAudioOutputStream() {
var source = audioGlobalContext.createMediaElementSource(document.getElementById("outputAudio"));
source.connect(inputPoint);
source.connect(audioGlobalContext.destination);
}
And in the initAudio() function just after navigator.getUserMedia added a call to the function. To the finished code (with HTML) would look like this
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var audioGlobalContext = new AudioContext();
var audioOutputAnalyser
var inputPoint = null,
audioRecorder = null;
var recording = false;
// Controls the start and stop of recording
function toggleRecording( e ) {
if (recording == true) {
recording = false;
audioRecorder.stop();
audioRecorder.getBuffers( gotBuffers );
console.log("Stop recording");
} else {
if (!audioRecorder)
return;
recording = true;
audioRecorder.clear();
audioRecorder.record();
console.log("Start recording");
}
}
function gotBuffers(buffers) {
audioRecorder.exportWAV(doneEncoding);
}
function doneEncoding(blob) {
document.getElementById("outputAudio").pause();
Recorder.setupDownload(blob);
}
function gotAudioMicrophoneStream(stream) {
var source = audioGlobalContext.createMediaStreamSource(stream);
source.connect(inputPoint);
}
function gotAudioOutputStream() {
var source = audioGlobalContext.createMediaElementSource(document.getElementById("outputAudio"));
source.connect(inputPoint);
source.connect(audioGlobalContext.destination);
}
function initAudio() {
if (!navigator.getUserMedia)
navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
if (!navigator.cancelAnimationFrame)
navigator.cancelAnimationFrame = navigator.webkitCancelAnimationFrame || navigator.mozCancelAnimationFrame;
if (!navigator.requestAnimationFrame)
navigator.requestAnimationFrame = navigator.webkitRequestAnimationFrame || navigator.mozRequestAnimationFrame;
inputPoint = audioGlobalContext.createGain();
navigator.getUserMedia({
"audio": {
"mandatory": {
"googEchoCancellation": "true",
"googAutoGainControl": "false",
"googNoiseSuppression": "true",
"googHighpassFilter": "false"
},
"optional": []
},
}, gotAudioMicrophoneStream, function(e) {
alert('Error recording microphone');
console.log(e);
});
gotAudioOutputStream();
var analyserNode = audioGlobalContext.createAnalyser();
analyserNode.fftSize = 2048;
inputPoint.connect(analyserNode);
var zeroGain = audioGlobalContext.createGain();
zeroGain.gain.value = 0.0;
inputPoint.connect(zeroGain);
zeroGain.connect(audioGlobalContext.destination);
audioRecorder = new Recorder(inputPoint);
}
window.addEventListener('load', initAudio );
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Audio Recorder</title>
<script src="assets/js/AudioRecorder/js/recorderjs/recorder.js"></script>
<script src="assets/js/AudioRecorder/js/main.js"></script>
</head>
<body>
<audio id="outputAudio" autoplay="true" src="test.mp3" type="audio/mpeg"></audio>
<audio id="playBack"></audio>
<div id="controls">
<img id="record" src="assets/js/AudioRecorder/img/mic128.png" onclick="toggleRecording(this);">
</div>
</body>
</html>
This records your voice and the sound coming from the audio element tag. Simple. Hope everyone out there who had the same problem as me to "rewind" your head around Audio API will find this helpful.
This code snippets shown above require Recorder.js to work.
I implemented a recorder in JavaScript using recorder.js
Basically, I am creating an app for Mozilla OS. When I install the app, the recorder works fine for the first time. When I try for the second time, it's not working. When I opened the wav file in Audacity, It's showing completely blank.
I am sharing my recorder. I kept the createDownloadLink() empty because it is something which I can't share.
Any clues on why the recorder is working fine for the first time well and not second time onwards? Any hacks to overcome this problem?
function startUserMedia(stream) {
var input = audio_context.createMediaStreamSource(stream);
__log('Media stream created.');
// Uncomment if you want the audio to feedback directly
//input.connect(audio_context.destination);
//__log('Input connected to audio context destination.');
recorder = new Recorder(input);
__log('Recorder initialised.');
}
function startRecording(button) {
recorder && recorder.clear();
recorder && recorder.record();
button.disabled = true;
button.nextElementSibling.disabled = false;
__log('Recording...');
}
function stopRecording(button) {
recorder && recorder.stop();
button.disabled = true;
button.previousElementSibling.disabled = false;
__log('Stopped recording.');
// create WAV download link using audio data blob
createDownloadLink();
recorder.clear();
recorder && recorder.clear();
}
function createDownloadLink() {
recorder && recorder.exportWAV(function(blob) {
}
}
window.onload = function init() {
try {
request = new XMLHttpRequest();
request.mozSystem = true;
// webkit shim
window.AudioContext = window.AudioContext || window.webkitAudioContext;
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia|| navigator.mozGetUserMedia;
window.URL = window.URL || window.webkitURL;
audio_context = new AudioContext;
__log('Audio context set up.');
__log('navigator.getUserMedia ' + (navigator.getUserMedia ? 'available.' : 'not present!'));
} catch (e) {
alert('No web audio support in this browser!');
}
navigator.getUserMedia({
audio: true
}, startUserMedia, function(e) {
__log('No live audio input: ' + e);
});
};
You may be hitting this bug: https://bugzilla.mozilla.org/show_bug.cgi?id=934512 The workaround is to place a reference to the result of createMediaStreamSource somewhere that will never be garbage collected, such as:
window.savedReferenceWorkaroundFor934512 = input;
If that isn't the cause, then I would look into creating a new audioContext for each start/stop cycle (and initiating a new getUserMedia call at each new start).
It's hard to tell without looking at all of your code, but you might also have a race condition between saving the blob and clearing the recorder. One fix would be to call recorder.clear() only after the blob is made.
EDIT : I just created a new Meteor Project and it worked :D wow.But it still doesnt work on my core project..looks like i have different settings.
In my Meteor.js project i have 4 .mp3-files located in public/sounds/xyz.mp3.
I load these .mp3 with :
let soundRequest = new XMLHttpRequest();
soundRequest.open('GET', this._soundPath, true);
soundRequest.responseType = 'arraybuffer';
let $this = this;
soundRequest.onload = function () {
Core.getAudioContext().decodeAudioData(soundRequest.response, function (buffer) {
$this.source.buffer = buffer;
$this.source.loop = true;
$this.source.connect($this.panner);
});
};
soundRequest.send();
This WORKS on google Chrome, but when i build the app via meteor run android-device, i get the following error message : DOMException: Unable to decode audio data
I wonder if this is a bug because loading .png or .jpg works just fine in the mobile version. I have not installed any packages beside meteor add crosswalk but deinstalling this doesnt help either.
You shouldn't need to do a http request to get a local resource. You can just refer to a local url. On the Android device the path is different. See this code:
function getSound(file) {
var sound = "/sounds/"+file;
if (Meteor.isCordova) {
var s;
if (device.platform.toLowerCase() === "android") {
sfile = cordova.file.applicationDirectory.replace('file://', '') + 'www/application/app' + sound;
}
else {
sfile = cordova.file.applicationDirectory.replace('file://', '') + sound;
}
var s = new Media(
sfile,
function (success) {
console.log("Got sound "+file+" ok ("+sfile+")");
s.play();
},
function (err) {
console.log("Get sound "+file+" ("+sfile+") failed: "+err);
}
);
} else {
var a = new Audio(sound);
a.play();
}
}
On a device it loads the sound file asynchronously and then plays it. In the browser it just loads and plays it synchronously.
This web API is not supported on android device but works on chrome browser of android
Check browser specification in this link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/decodeAudioData
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.