Play multiple mp3 files with phonegap for android - javascript

I'm trying to write an Android app with PhoneGap that contains multiple HTML files. In every HTML I would like to include an mp3 file that the user can play and stop. This has worked so far. The problem that I encounter is when trying to put multiple mp3 files in one file.
The thing that I would like to achieve is to put the general audio player javescript in one file and to tell in the HTML which file should be played. Is this possible and how? Thanks!
My code for one of the html files looks like this:
<script type="text/javascript" charset="utf-8" src="phonegap.js"></script>
<script type="text/javascript" charset="utf-8">
//-------------------------------------------------------------------------
// Audio player
//-------------------------------------------------------------------------
var media1 = null;
var media1Timer = null;
var audioSrc = null;
/**
* Play audio
*/
function playAudio(url) {
console.log("playAudio()");
console.log(" -- media="+media1);
//var src = "/android_asset/www/audio/01.mp3";
var src = "/android_asset/www/audio/01.mp3";
if (url) {
src = url;
}
// Stop playing if src is different from currently playing source
if (src != audioSrc) {
if (media1 != null) {
stopAudio();
media1 = null;
}
}
if (media1 == null) {
media1 = new Media(src,
function() {
console.log("playAudio():Audio Success");
},
function(err) {
console.log("playAudio():Audio Error: "+err);
setAudioStatus("Error: " + err);
},
function(status) {
console.log("playAudio():Audio Status: "+status);
setAudioStatus(Media.MEDIA_MSG[status]);
// If stopped, then stop getting current position
if (Media.MEDIA_STOPPED == status) {
clearInterval(media1Timer);
media1Timer = null;
}
});
}
audioSrc = src;
// Play audio
media1.play();
if (media1Timer == null) {
media1Timer = setInterval(
function() {
media1.getCurrentPosition(
function(position) {
console.log("Pos="+position);
if (position > -1) {
setAudioPosition((position/1000)+" sec");
}
},
function(e) {
console.log("Error getting pos="+e);
setAudioPosition("Error: "+e);
}
);
},
1000
);
}
// Get duration
var counter = 0;
var timerDur = setInterval(
function() {
counter = counter + 100;
if (counter > 2000) {
clearInterval(timerDur);
}
var dur = media1.getDuration();
if (dur > 0) {
clearInterval(timerDur);
document.getElementById('audio_duration').innerHTML = (dur/1000) + " sec";
}
}, 100);
}
/**
* Pause audio playback
*/
function pauseAudio() {
console.log("pauseAudio()");
if (media1) {
media1.pause();
}
}
/**
* Stop audio
*/
function stopAudio() {
console.log("stopAudio()");
if (media1) {
media1.stop();
}
clearInterval(media1Timer);
media1Timer = null;
}
/**
* Set audio status
*/
var setAudioStatus = function(status) {
document.getElementById('audio_status').innerHTML = status;
};
/**
* Set audio position
*/
var setAudioPosition = function(position) {
document.getElementById('audio_position').innerHTML = position;
};
</script>
</head>
<body>
<!-- Audio -->
<div id="info">
<h2>Audio</h2>
</div>
Play
Pause
Stop
</body>

You might consider using a page with a frame on it. Place your common javascript in the of the main page and then call back to parent.playAudio(src) to kick off the player from each sub-page as it's loaded into the frame.
Another possible direction is use of jqMobile. By default, jqMobile loads pages using Ajax calls..consequently, your javascript can be loaded by the very first page only...and all subsequent page loads via ajax do not totally replace the DOM...leaving your javascript in tact. I have developed a small app using phonegap and jqMobile with fairly good success.

Related

Unable to play canvas streaming in Enablex. What will be canvas Selector here?

I am trying to add a whiteboard in my video chat app for which i am using Enablex's canvas streaming api.
https://developer.enablex.io/video-api/client-api/web-toolkit/advance-features/#start-canvas-streaming
But it is'nt working. Does canvasSelector should be something or just "CanvasElementId"
var CanvasOpt = {
"canvasSelector": "CanvasElementID",
"fps": 23
};
room.startCanvas(CanvasOpt, function(arg) {
if(arg.result == 0) { // Success
}
else { // Error arg.result == 1154 (Frame rate not supported)
}
});
// Participant receives notification, finds stream ID 102 and plays
room.addEventListener("canvas-started", function (event) {
var canvasStream = room.remoteStreams.get(102);
if(canvasStream.stream !== undefined) {
canvasStream.play("PlayerDiv");
}
});

Fluent-ffmpeg: merging video and audio = wrong frames

I'm trying to merge a video (mp4) without audio stream with an audio file (mp3). I'm developing under nodewebkit a video software which means that I have to use ogg files, so when the user upload a video or a audio file it converts it in ogg whatever its format. Then when the user want to export its video I’m exporting frames from a canvas to PNG images. Once this is done I’m creating a video from the frames with a 30 fps with this following code:
var videoMaker = function () {
console.log('videoMaker');
var deffered = Q.defer();
if (!FS.existsSync($rootScope.project.path + '/video')) {
filestorageService.createFolder($rootScope.project.path + '/video');
}
audioMaker().then(function () {
var commandVideo = new Ffmpeg({
source: $rootScope.project.path + '/frames/%d.png'
});
commandVideo.setFfmpegPath(ffmpegPath);
commandVideo.addOptions(['-c:v libx264', '-r 30']).withFpsInput(30).format('mp4').on('error', function (err) {
console.log('video', err);
}).on('end', function () {
console.log('video win');
deffered.resolve();
}).save($rootScope.project.path + '/video/rendu.mp4');
});
return deffered.promise;
};
Then i'm reconverting the audio wich has been uploaded by the user to mp3:
var audioMaker = function () {
console.log('audioMaker');
var deffered = Q.defer();
if ($rootScope.project.settings.music.path !== '') {
FS.writeFileSync($rootScope.project.path + '/music/finalMusic.mp3', null);
var commandAudio = new Ffmpeg({
source: $rootScope.project.settings.music.path
});
commandAudio.setFfmpegPath(ffmpegPath);
if ($rootScope.project.settings.music.fadeIn) {
commandAudio.audioFilters('afade=t=in:ss=0:d=0.5');
}
console.log($rootScope.project.settings.music.fadeOut, $rootScope.project.settings.music.fadeIn);
if ($rootScope.project.settings.music.fadeOut) {
var time = sceneService.getTotalDuration() - 0.5;
commandAudio.audioFilters('afade=t=out:st=' + time + ':d=0.5');
}
commandAudio.toFormat('mp3').on('end', function () {
console.log('audio win');
deffered.resolve();
}).on('error', function (err) {
console.log('audio', err);
}).save($rootScope.project.path + '/music/finalMusic.mp3');
} else {
deffered.resolve();
}
return deffered.promise;
};
Until there everything is alright those files work well but when i do this:
var command = new Ffmpeg({
source: $rootScope.project.path + '/video/rendu.mp4'
});
command.setFfmpegPath(ffmpegPath);
console.log($rootScope.project.settings.music.path !== '');
if ($rootScope.project.settings.music.path !== '') {
command.addInput($rootScope.project.path + '/music/finalMusic.mp3');
command.addOptions(['-c:v copy', '-c:a copy']);
if ($rootScope.project.settings.music.duration > sceneService.getTotalDuration()) {
command.addOptions(['-shortest']);
}
command.on('error', function (err) {
console.log(err);
}).on('end', function () {
console.log("win");
//filestorageService.rmFolder($rootScope.project.path + '/frames');
}).save($rootScope.project.path + '/video/rendu.mp4');
} else {
filestorageService.rmFolder($rootScope.project.path + '/frames');
}
And my final file has the music and the right duration but the frames aren't right, any ideas?
I finally find out how to fix this, the final video wasn't good because i was merging the video and the audio to the same video file which means that i was getting the data of the video via outsream while i was writing on the file. So in my program i created a temp folder which contains the video and the audio then i merge them to a new video file which is in an other final folder to finally delete the temp folder.

Cordova - Control several sounds on Android

I manage to play several sounds on Android (Cordova 4.1) but now I would like to dynamically change the volume of each video.
My code for playing sounds is the following:
function onDeviceReady() {
document.querySelector("#play").addEventListener("touchend", playMP3(), false);
function playMP3() {
var mp3_1 = getMediaURL("audio/sound1.mp3");
var mp3_2 = getMediaURL("audio/sound2.mp3");
media1 = new Media(mp3_1, null, mediaError);
media2 = new Media(mp3_2, null, mediaError);
media1.play();
media2.play();
}
function getMediaURL(s) {
if(device.platform.toLowerCase() === "android") return "/android_asset/www/" + s;
return s;
}
function mediaError(e) {
alert('Media Error');
alert(JSON.stringify(e));
}
}
Then I would like to control volume with something like media1.volume = 0.5; from outside the onDeviceReady function but I can't make it work...
Any idea of what I'm missing?
Finally, the whole package to play and control sounds with Cordova 4.1 and Android 4.4 wich worked for me. To set the volume call the function with something like onclick=setVolume(media1, "0.5")
Hope it can help !
var media1 = null;
var media2 = null;
//set volume of one sound
function setVolume(media, volume) {
if (media) {
media.setVolume(volume);
}
}
//play the 2 sounds at the same time
function playMP3() {
var mp3_1 = getMediaURL("audio/media1.mp3");
var mp3_2 = getMediaURL("audio/media2.mp3");
media1 = new Media(mp3gauche, null, mediaError);
media2 = new Media(mp3droite, null, mediaError);
media1.play();
media2.play();
}
//for android devices (need the Device Cordova plugin)
function getMediaURL(s) {
if(device.platform.toLowerCase() === "android") return "/android_asset/www/" + s;
return s;
}
//help to get log when errors occur
function mediaError(e) {
alert('Media Error');
alert(JSON.stringify(e));
}
//launch the 2 sounds when the device is ready
function onDeviceReady() {
document.querySelector("#playMp3").addEventListener("touchend", playMP3(), false);
}

how to jump to the next video when current video file is not available?

I am trying to play list of video files stored in a folder one by one but if any of the file is missing or deleted the program will stop but i want it jump to the next video and continue its execution is there any way to catch this error and jump to the next one?
function advanceVideo()
{
video_count++;
if (video_count > num_files) video_count = 1;
videoPlayer.setAttribute("src","video/video"+video_count+".ogg");
video.load();
video.play();
}
I am using the above function for accessing the video file in the folder.
Make use of the various events the <video /> element offers
// function to load the next video (without .play())
function advanceVideo() {
video_count = (video_count + 1) % num_files;
this.setAttribute("src", "video/video" + video_count + ".ogg");
this.load();
}
// if the current video has reached the end, load the next
videoPlayer.addEventListener("ended", advanceVideo);
// if the browser fetched enough data to play the video start playing
videoPlayer.addEventListener("canplay", function() {
this.play();
});
// if an error occured load the next video
videoPlayer.addEventListener("error", advanceVideo);
Update
This should do what you've requested in your comment
var num_files = 2,
playedVideos = 0;
// load next video
function advanceVideo() {
// current video id or -1 if not found
var curId = parseInt((/video(\d+)\.ogg$/.exec(this.src) || [, -1])[1], 10);
// reset if all videos have been played
if (playedVideos == num_files) {
playedVideos = 0;
curId = -1;
}
this.src = "video/video" + (curId + 1) + ".ogg";
this.load();
}
// if the current video has reached the end, load the next
videoPlayer.addEventListener("ended", function() {
playedVideos++;
advanceVideo();
});
// if the browser fetched enough data to play the video start playing
videoPlayer.addEventListener("canplay", function() {
this.play();
});
// if an error occured load the next video
videoPlayer.addEventListener("error", advanceVideo);
If there are less videos to play than num_files indicates, this will end in an "infinite loop" as it will try all videos until curId reaches the value Infinity!
Try using try-catch block:
function advanceVideo()
{
video_count++;
if (video_count > num_files) video_count = 1;
videoPlayer.setAttribute("src","video/video"+video_count+".ogg");
try {
video.load();
video.play();
} catch (e) { }
}

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