We've been using the CreateJS suite for a while now, but have just realised that our audio is not working on IOS9. Unfortunately we only have one IOS9 test device, running IOS9.2.4, but are getting mixed results with audio playback.
The rough process I'm using is;
Preload all assets (scripts/images/audio) via PreloadJS
Construct an initial splash screen in EaselJS, including a start button
Continue with the main content
It would be advantageous to be able to preload all audio before presenting that splash screen. The splash screen was added initially to allow audio to play on mobile Safari, with an empty sound played on click. This does of course work for IOS7/8, but not for 9.
I've created this test case on codepen as an attempt to track down the issue and try some options.
Codepen sample
HTML
<p id="status">Hello World</p>
<canvas id="canvas" width="200" height="200"></canvas>
JS
var canvas, stage, rect;
function init() {
canvas = document.getElementById('canvas');
canvas.style.background = "rgb(10,10,30)";
stage = new createjs.Stage("canvas");
createjs.Touch.enable(stage);
rect = new createjs.Shape();
rect.graphics.f("#f00").dr(50,75,100, 50);
rect.on("mousedown", handleStart, null, true);
rect.on("touchend", handleStart, null, true);
//rect.on("click", handleStart, null, true);
stage.addChild(rect);
stage.update();
$('#status').text("Touch to Start");
createjs.Sound.initializeDefaultPlugins();
//createjs.Sound.alternateExtensions = ["ogg"];
createjs.Sound.registerSound("https://www.freesound.org/data/previews/66/66136_606715-lq.mp3", "ding1");
}
function handleStart(event) {
createjs.WebAudioPlugin.playEmptySound();
$('#status').text("Touch to Play");
rect.graphics._fill.style = '#0f0';
rect.removeAllEventListeners();
rect.on('click', handlePlay);
stage.update();
}
function handlePlay(){
createjs.Sound.play("ding1");
$('#status').text("Playing");
}
init();
Apologies for the lack of ogg version, was struggling to get test files to load x-domain.
With this, audio partially works for us on IOS9. If we leave clicking the red rectangle (figure start button) and leave it ~20 seconds, then click the green button no audio plays. If we click it immediately, audio plays fine.
I have been reviewing this bug/thread and attempting to follow Grant's suggestions. I gather SoundJS v0.6.2 now automatically attempts to play the empty sound appropriate when plugins are initialized, however moving the initializeDefaultPlugins and registerSounds calls into the handleStart function appears to make no difference. If I'm understanding the issue correctly, calling the WebAudioPlugin.playEmptySound method should be sufficient?
Have also been looking at the event binding, trying mousedown/touchend instead of click, but the result is the same with the 20 second wait. Event also appears to fire twice, although I could probably dig deeper into that if I could get it to work correctly.
I'm aware of the Mobile Safe Approach article aimed at this issue, but the need for a namespace at this level would mean a substantial rewrite of our existing content. Could someone perhaps advise if it is completely necessary to take this approach? I'm under the impression it should be feasible by correctly playing some empty audio within that initial handler.
Can't actually get the main project to this point, but if I can get a working example perhaps I'll be a step closer.
Any thoughts would be appreciated!
The Mobile-safe tutorial is not really relevant anymore since the updates in 0.6.2. It will likely be updated or removed in the near future.
You should never need to initializeDefaultPlugins(), unless you want to act on the result (ie, check the activePlugin before doing something). This method fires automatically the first time you try and register a sound.
The playEmptySound is also no longer necessary. SoundJS will listen for the first document mousedown/click, and automatically do this in the background.
touch events are not directly propagated from the EaselJS stage, but are instead turned into mouse events (touchstart=mousedown, touchmove=mousemove, touchend=pressup/click)
Based on this, you should be able to play sound once anywhere in the document has been clicked, regardless of whether you listen for it or not.
That is not to say that there isn't a bug, just that the steps you are taking shouldn't be necessary. I did some testing, and it appears to work in iOS9. Here is a simplified fiddle.
https://jsfiddle.net/lannymcnie/b4k19fwc/ (link updated Dec 3, 2018)
Related
Thank you for looking at my question. Fiddle is HERE.
I am trying to implement an audio player for many songs on a page. This is using JQueryUI Slider and HTML5 Audio, with one Audio element and multiple sliders.
The problems right now are:
The slider does not animate with the audio.
The slider does not seek to the audio.
previously, when both of the above WERE working, once you chose a spot in the song, the slider would no longer animate.
I have created function to rebindSlider() when a new song is clicked. Inside this function, two things happen: a) the new slider is created, with slide and stop listeners defined, and b) the new slider is bound to the timeupdate event on the audio element for the new song. I feel like this should be all that I need, but the slider does not bind, and an undefined error can be seen when you try to drag the slider.
Using a single slider, and a single audio element, I have gotten this 90% the way there; as soon as I introduced multiple divs for the sliders though, problems started occurring.
Here is the code for rebindSlider:
function rebindSlider(sliderDiv) {
var createSeek = function() {
sliderDiv.slider({
value: 0,
step: 1,
orientation: "horizontal",
range: "min",
max: audioPlayer.duration,
animate: true,
slide: function() {
manualSeek = true;
},
stop: function(e, ui) {
manualSeek = false;
audioPlayer.currentTime = ui.value;
}
});
};
createSeek();
$(audioPlayer).bind('timeupdate', function() {
if (!manualSeek) {
sliderDiv.slider('value', audioPlayer.currentTime);
}
});
}
Further description is below.
There is a list of songs included on the page. Each song has a containing div, within which is an <a> that contains meta data (absent in the fiddle), as well as a div designated for the audio seek slider. There is a single audio element on the page, which has its source re-loaded as you click through the songs.
When a song is clicked, I would like to destroy the sliders bound to the playing audio (if necessary), and bind the new slider for the clicked song to the audio player.
The closest I have gotten was to have the slider 1) begin animating when the song plays 2) dragging of the slider moved to a different position in the song. Once the slider had moved though, it no longer animated. After a refactor, the slider no longer works, and though I could go to a previous commit to get the working code, the refactor was so drastic that I would prefer to present the current, non-working code, as it better represents what I would like to end up with.
Reasoning, and additional information.
I am making a web app, for which I have a concept for an audio player that I would rather build myself than modify anything that I have come across. That being said, if you know of something that I can implement, I would love suggestions.
The idea sounds simple enough, and most of it is done, but there is a very crucial part of it that I am having trouble with, which is setting up the slider to animate with, and seek to the desired spot in the audio track, and to be able to rebind a new slider to the audio when a song is clicked.
There are a few changes needed to get the slider working correctly. I'll cover them each, in order that they apply.
Invalid Slider Max Value
The first issue is that the slider's max value is set to the audioPlayer.duration, which would normally be all well and good, except that the HTML5 audio player loads the audio assets asynchronously. What this means is that even though you might have loaded the audio prior to setting the slider, the audio asset may not be loaded yet, and the audioPlayer.duration may be invalid (likely NaN).
Simply remove the max key (and value) from the slider initialization, and this will work. Et viola! The slider moves!
One caveat: the slider defaults to a max value of 100 units, and the duration of the audio is a little over 25 seconds, so the song finishes playing when the slider is 1/4th of the way along. We can set the max key to 25 (seconds), but that's a little inelegant, and wont work if we change the audio to use a different source.
Set the Slider Max Value
Capturing the audioPlayer.duration must be done be handling an event, once the asynchronous load has completed. Javascript provides such an event: onloadedmetadata. Let's install an event handler, and make this work:
audioPlayer.onloadedmetadata = function() {
$(".slider").slider("option", { max: Math.floor(audioPlayer.duration) });
};
Now, what this does is set the slider's max to the audioPlayer.duration once the audio asset has loaded. Actually, this is currently setting the max value for ALL of the sliders, but that shouldn't be a problem, since they're all hidden. If you have a very large number of songs, there might be a bit of a delay, and you may want to find the specific slider to update.
Smooth Sliding
Now, you may notice after these changes that the slider is somewhat jumpy. It pauses for a second, then jumps, then pauses, etc. This can be fixed very easily, by changing the step key to 0.1 in the slider initialization, like so:
sliderDiv.slider({
value: 0,
step: 0.1,
orientation: "horizontal",
The time updates occur every 50 to 250 milliseconds, but the slider could only move in 1-second increments. Now, with 1/10th of a second increments, the slider will move more smoothly. You can decrease that slightly if you like, but don't make the number too small; 0.01 is the practical lower bound.
Update the End-of-Play State
When the song has finished, the "Play" button is left in play state, even though there's no longer anything playing. There's an event for that, as well: onended. We'll use that event to update the UI, so that the user doesn't get confused:
audioPlayer.onended = function() {
playButton.removeClass('fa-pause-circle-o');
playButton.addClass('fa-play-circle-o');
};
Now, when the song finishes, the Play button will go back to the "play" state, and the user will know that clicking on it will play the song.
Refactor Play Button State
Since there are now 3 places that update the state of the Play button, and this involves duplication, we can refactor the state handling. This function will definitively set the state of the Play button:
function setUIState() {
if (audioPlayer.paused || audioPlayer.ended) {
playButton.removeClass('fa-pause-circle-o');
playButton.addClass('fa-play-circle-o');
} else {
playButton.removeClass('fa-play-circle-o');
playButton.addClass('fa-pause-circle-o');
}
}
Now, calling it from the other functions, you end up with the onended event handler:
audioPlayer.onended = function() {
setUIState();
};
The changeUI function:
function changeUI(selectedSong) {
setUIState();
var sliderDiv = selectedSong.parent().find('.slider');
rebindSlider(sliderDiv);//Bind the desired slider
$('.slider').hide(); //Hide all sliders
sliderDiv.show();//Show only the desired slider
}
And, the Play button click handler:
$(".playButton").click(function() {
if (audioLoaded === true) {
if (!audioPlayer.paused) {
audioPlayer.pause();
} else {
audioPlayer.play();
}
setUIState();
} else {
alert("Please click a song");
}
return false;
});
This makes the UI state management for the button simple to deal with, and keeps the state from creeping out through the app. You can already see how the refactored code is noticeable clearer and will be easier to maintain. Plus, as a bonus, we got to use the cool audioPlayer.ended attribute, which you don't see used much.
But Wait, There's More!
With the changes above, you're well on your way to having a very functional audio player. But that's certainly not the end of the line for feature functionality. There's always room to grow!
I've create a jsFiddle that includes all of these changes, as well as a few other mods to the original code. You can find the most recent version at: https://jsfiddle.net/mgaskill/mp087adp/. Additional features are likely to keep popping up, but the jsFiddle already includes these additional features:
Volume control (slider)
Time display
Dynamically generated HTML for audio controls (keeps the HTML simpler)
I'm in the frustrating situation of having an intermittent error. When functional, PHP specifies a video and 3 pictures to be displayed. The video begins playing 800ms after the page loads, and the pictures are displayed immediately upon the end of the video, at which point a user presses a key (corresponding to the location of the picture) and the keypress triggers a function that measures reaction time, stores which picture they selected, and loads the next page, whereupon all of this happens again with a new video and new pictures.
That all works just fine... sometimes.
At other times, I get a blank screen where I was expecting to see the video. Sometimes it happens right away, sometimes after as many as 15 or 20 successful loads, or anywhere in between. I'm running Chrome 27.0.1453.93, and tracking errors using Chrome's built-in javascript console. Even when the video fails to load, there are no javascript errors (with DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">).
To pinpoint where things are breaking down, I've added a bunch of event listeners and used anonymous functions to pass some output to the js console log file to see which stages of the video event are or aren't happening. Here, at least, is a reliable pattern: every time the video fails to load, loadstart has happened, but durationchange has not. I just can't figure out what the problem is that prevents it from continuing!
I look forward to the responses here, whether they're answers or even just other people corroborating the experience so I don't feel so crazy! I'll include the two most relevant javascript functions here, but I'm not sure how much good they'll do.
<script>
function playVid(newSource){
console.log("playVid worked");
setTimeout(function(){//tells it to wait for some amount of time (below) before doing the next part
console.log("setTimeout was called");
document.getElementById("StimClip").innerHTML="<video id='video1'> <source src='" + newSource + "' type='video/mp4'>Something went wrong. Sorry 'bout that!</video>"; //replace blank.jpg with the appropriate video
console.log("the inner html was set");
myAddListener();
console.log("myAddListener was called");
var myVid=document.getElementById("StimClip"); //create a new variable to identify the video
console.log("the myVid variable was created");
video1.play(); //play the video
} //
,800); //finishes the setTimeout call by specifying how many msec to wait before executing.
}
function myAddListener(){
console.log("myAddListener worked");
var myVideo = document.getElementsByTagName('video')[0];
myVideo.addEventListener('waiting', function(){console.log("I'm WAITING...!")}, false);
myVideo.addEventListener('suspend', function(){console.log("Nice suspenders.")}, false);
myVideo.addEventListener('abort', function(){console.log("video was aborted")}, false);
myVideo.addEventListener('error', function(){console.log("there was an error")}, false);
myVideo.addEventListener('loadstart', function(){console.log("load start worked")}, false);
myVideo.addEventListener('durationchange', function(){console.log("the duration changed")}, false);
myVideo.addEventListener('loadedmetadata', function(){console.log("metadata was loaded")}, false);
myVideo.addEventListener('loadeddata', function(){console.log("data was loaded")}, false);
myVideo.addEventListener('progress', function(){console.log("progress...? ")}, false);
myVideo.addEventListener('canplay', function(){console.log("video can play")}, false);
myVideo.addEventListener('canplaythrough', function(){console.log("can play through worked")}, false);
myVideo.addEventListener('playing', function(){console.log("the video started playing")}, false);
myVideo.addEventListener('ended', ShowPics, false);
}
</script>
Oh geez. I'm glad no one else wasted their time trying to answer this. In case you were wondering, the thing that would cause a video to stop loading between loadstart and durationchange is if that video doesn't exist! (I had a typo in the filename of one of my videos, and because the order of videos is randomized I couldn't ever tell that it was always failing when it was trying to load the same one.)
Fixed.
Actually this the first time I'm gonna use jplayer plugin, so all I need is to play a sound clip (2 sec) when I click on a div, and I don't know how to use jplayer!
I also need to load the sound clip file in cache as well as the page is loading, so when I click that div the sound clip plays immediately without loading it when clicking
btw, is it possible to do that without using jplayer, jquery maybe?!
If you want to stick with jPlayer, try setting warningAlerts and errorAlerts to true.
$(id).jPlayer( { warningAlerts: true, errorAlerts: true } );
This may solve your problem.
But as long as you only need sound (no video), SoundManager 2 might be a better fit for you. Its really robust and provides extensive debugging output.
soundManager.url = './swf/'; // directory where SM2 .SWFs live
var mySound;
soundManager.onready(function() {
// SM2 has loaded - now you can create and play sounds!
mySound = soundManager.createSound({
id: 'someSound',
url: '/path/to/some.mp3'
});
});
//....
//if(xhr-stuff)
if(mySound)
mySound.play();
Use soundmanager2.js for testing and soundmanager2-nodebug-jsmin.js for production. If you have further questions, don't hesistate to ask.
-snip-
scratch that... there's an api
http://www.jplayer.org/latest/developer-guide/#jPlayer-play
Use
$("#jpId").jPlayer("play");
I've got this in the <head>:
<script>
function log(event){
var Url = "./log.php?session=<?php echo session_id(); ?>&event=" + event;
xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", Url, true );
xmlHttp.send( null );
}
</script>
And this in the <body>:
<video id="video" src="./video/LarryVideo.mp4"
controls="controls"
poster="./img/video_poster.jpg"
onabort="log('onabort');"
oncanplay="log('oncanplay');"
oncanplaythrough="log('oncanplaythrough');"
ondurationchange="log('ondurationchange');"
onemptied="log('onemptied');"
onended="log('onended');"
onerror="log('onerror');"
onloadeddata="log('onloadeddata');"
onloadedmetadata="log('onloadedmetadata');"
onloadstart="log('onloadstart');"
onpause="log('onpause');"
onplay="log('onplay');"
onplaying="log('onplaying');"
onprogress="log('onprogress');"
onratechange="log('onratechange');"
onreadystatechange="log('onreadystatechange');"
onseeked="log('onseeked');"
onseeking="log('onseeking');"
onstalled="log('onstalled');"
onsuspend="log('onsuspend');"
ontimeupdate="log('ontimeupdate');"
onvolumechange="log('onvolumechange');"
onwaiting="log('onwaiting');">
<script>
QT_WriteOBJECT('./video/LarryVideo.mp4',
'380px', '285px', // width & height
'', // required version of the ActiveX control, we're OK with the default value
'scale', 'tofit', // scale to fit element size exactly so resizing works
'emb#id', 'video_embed', // ID for embed tag only
'obj#id', 'video_obj'); // ID for object tag only
</script>
</video>
My normal Safari creates nice log-file entries as expected. Mobile Safari from iPad however doesn't do anything at all.
What could be wrong with this?
I have not been able to get a hold of readystate on an ipad either, but you can get other events that more-or-less let you infer the readystate.
var audio = new Audio("someSource.mp3");
audio.play();
/* you may need to use .load() depending on how the event was initiated */
audio.addEventListener("canplay", handleCanPlay, false);
audio.addEventListener("durationchange", handleDurationChange, false);
But let's be clear, the problem is Apple pretty much telling the whole world they're using the internet wrong. Granted, everyone hates sites that start playing music the second they load, but then Apple goes nuts and kills ANY/ALL buffering of audio/video that isn't explicitly initiated by a user gesture because Apple, apparently, thinks their users can't hit "back" if a site bothers them; fanboys agree too. This basically leaves the rest of us to hack up our applications if we dare try and manage any kind of sound effects. I know this isn't the place to rant, but building ANY soft of interesting/interactive experience in HTML5 on the iPad is one facepalm after another -- be it the 5mb cache limit that simply crashes the browser if a page has "too" many (according to Apple) images, or the difficulty to preload any sort of media to enhance UI. Seriously, outside of Wordpress blogs and RSS readers, mobile Safari's implementation of HTML5 is pretty worthless. And so the dream of HTML5 "build once, play anywhere" value proposition is dead and we go back to developing native apps. At least this gives us good job security! /rant
I've got a page with links to MP3s, when the link is clicked I use javascript to show a small Flash player (NiftyPlayer) under the link. When a different link is clicked, the old player is hidden and the new player is revealed.
The player auto-starts when the element is shown, and auto-stops when hidden - in Firefox.
In IE it will only auto-start and NOT auto-stop. This is what I would like to solve.
This is an example HTML with link and player
Misunderstood What You Said
<div id="player662431" class="playerhide"><embed src="http://www.example.com/shop/flash/player.swf?file=/mp3/Beat The Radar - Misunderstood What You Said.mp3&as=1" quality="high" bgcolor="#000000" width="161" height="13" name="niftyPlayer662431" align="" type="application/x-shockwave-flash" swLiveConnect="true" pluginspage="http://www.macromedia.com/go/getflashplayer"></embed>
Here is the javascript (I've got jQuery installed to let me hide all the open players on this page apart from the new one)
function toggle_visibility(id) {
$('.playerhide').hide();
var e = document.getElementById(id);
e.style.display = 'block';
}
I think what I need to do is start the player manually with javascript (rather than using the autostart as=1 function in the URL string)
There is some javascript that comes with NiftyPlayer to allow this EG
niftyplayer('niftyPlayer1').play()
there is also a stop method.
I need some help with javascript - how do I add this call to play into my toggle_visibility function (it has the same unique ID number added to the name of the player as the ID of the div that's being shown, but I don't know how to pull this ID number out of one thing and put it in another)
I also would like to be able to do
niftyplayer('niftyPlayer1').stop()
to stop the audio of the previously running player. Is it possible to store the current ID number somewhere and call it back when needed?
Thanks for the help, i'm a PHP programmer who needs some support with Javascript - I know what I want to achieve, just don't know the commands to do it!
Thanks
If you assigned each niftyplayer object a classname, f.x. ".players", then you could loop through each player, like this:
function toggle_visibility(id) {
$(".players").each(function(){
playerId = $(this).attr('id');
if(niftyplayer(playerId).getState() == 'playing') {
//Stop the currently playing player
niftyplayer(playerId).stop();
//Hide the div that was playing
$("#" + playerId).hide();
}
});
//Start the new player
niftyplayer(id).play();
$("#" + id).show();
}
So what this actually does, is it loops through all the players on the website. It checks if the status of each player is equal to "playing", if it is, then it stops it and hides the div tags. Then it starts the new player and shows that div tag.
I think this does it. Try it out.
I have a much better solution after I noticed a very nasty bug / 'feature' when using Internet Explorer in conjunction.
I had noticed that in IE the pages were taking a very long time to load when I had a lot of hidden Nifty Players, I looked closer using Fiddler and found that each instance of NiftyPlayer was preloading the MP3 in full, rather than loading on demand as with Firefox and Chrome etc.
This meant that a page with 100 items (each item having up to 4 MP3s) took several minutes to load at times with obvious data transfer implications.
My solution which is rather simpler (but maybe clunkier) than Indyber's is to just use
function toggle_visibility(id,mp3location) {
// hide all players
$(".playerarea").html('');
// show clicked player
$('#' + id).html('<embed src=\"http://www.xxx.com/shop/flash/player.swf?file=http://www.xxx.com/mp3/' + decodeURIComponent(mp3location) + '.mp3&as=1\" quality=high bgcolor=#000000 WMODE=transparent width=\"161\" height=\"13\" align=\"\" type=\"application/x-shockwave-flash\" swLiveConnect=\"true\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\" class=\"playerNew\">');
}
which works fine with IE, and also solves the problem of not being able to stop the players from playing in IE