I'm trying to to write a javascript app that use the [SoundManager 2][1] api and aim to run in
all desktop and mobile browsers. On the iPad platform, Soundmanager is using the HTML5 audio api since there is on flash support. Now, when I'm trying to play two audio files back to back, both loaded in response to a click event, a [HTML5::stalled][2] event is occasionally raised. How do I set an event handler to catch the stalled event?
Since sound objects in my app are created on the fly and I don't know how to access directly to tags that are created by SoundManager, I tried to use a delegate to handle the stalled event:
document.delegate('audio', 'stalled', function (event) {...});
It doesn't work. the event did not raised in respond to stalled. (I had an alert in my handler).
Also tried to use [Sound::onsuspend()][3] to listen for stalled, but onsuspend pops out
on the end of sound::play(). How can we distinguish between stalled and other events that may raise the audio::suspend? Is there any other way to access the tags that SoundManager must create in order to play HTML audio?
I solved it with the following solution. This is not documented and found by reverse engineering.
It is all about accessing the html audio object, which is availalbe under _a.
currentSound = soundManager.createSound({..});
currentSound._a.addEventListener('stalled', function() {
if (!self.currentSound) return;
var audio = this;
audio.load();
audio.play();
});
The body of the method is based on this post about html5 stalled callback in safari
I can suggest a different "fix" I use with an html5 using platform (samsung smart TV):
var mySound = soundManager.createSound({..});
mySound.load();
setTimeout(function() {
if (mySound.readyState == 1) {
// this object is probably stalled
}
}, 1500);
This works since in html5, unlike flash, the 'readystate' property jumps from '0' to '3' almost instantanously, skipping '1'. ('cause if the track started buffering it's playable...).
Hope this works for you as well.
Related
I am attempting to rebuild a game that works on itch to be compatible on most major devices and browsers. I have a problem where on iOS (and possibly other mobile devices) a call to play audio tags from click and touch events has quite a significant delay.
I have read about several potential causes, including the 300ms delay, preventDefault for the second click event, stopPropagation for potential parent clicks, different audio formats causing lag in decompression, etc. Nothing seems to work.
Initially my intent was to keep everything in vanilla js without outside libraries to force myself to really learn what's going on under the hood. However, I have been having some success with some outside libraries for other problems, so I tried fastclick.js for this problem. That didn't work for me either. So, if someone knows how to address this issue without a library that would be great, but after looking at the fastclick code, that may be beyond my level of comprehension.
A current build can be found at www.teachersteve.net/assett_loading_with_ian/assett_loading_with_ian.html
Some explanation of my thought process:
I removed anything that is actually game related to try and isolate the problem. I put all the assetts directly in the html to simplify the loading process and wait to start the js after the DOM loaded
document.addEventListener("DOMContentLoaded", doSomething);
I am currently only calling one audio tag to play as I read somewhere that calling multiple tags can overload the decompression process and cause a delay. That seems to not be the issue.
I have 3 different file formats so far for compatibility attempts... I did read that LEI16 (a wav format) might be best because it eliminates compression, although I haven't tried it yet.
This is the rest of the doSomething() function
function doSomething() {
document.body.style.opacity = 1;
document.addEventListener("click", playAudio);
document.addEventListener('click', preventInputDefault);
document.addEventListener('ontouchend', preventInputDefault);
console.log("assetts loaded");
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
function playAudio() {
// backgroundMusic.play();
letterAudio.play();
// correctAnswerAudio.play();
// letterAudio.play();
// correctAnswerAudio.play();
}
function preventInputDefault(evt) {
evt.preventDefault();
console.log("hello preventInputDefault");
evt.stopPropagation();
console.log("hello stopPropagation");
}
}
Thanks!
I know that there is a limitation in iOS Safari where the audio is not playing until user triggers an interaction. So I have placed the code inside a touchstart event. But unfortunately, I have tried almost every combination, and I couldn't get it to play on iOS Safari.
Here are the things I have tried:
putting the audio load outside the touchstart callback
try adding a gain node
use 0.01 as the start time
and none of the above works in iOS Safari, but they can all play in desktop Chrome and Safari. Here is the link to the gist, you can see the versions where I made the changes (P.S. the click event is used for testing on desktop)
https://gist.github.com/angelathewebdev/32e0fbd817410db5dea1
Sounds play only when currentTime starts to run, but scheduling sounds exactly at currentTime doesn't seem to work. They need to be a little bit into the future (ex: 10ms). You can use the following createAudioContext function to wait until the context is ready to make noise. User action doesn't seem to be required on iPhone, but no such success on iPad just yet.
function createAudioContext(callback, errback) {
var ac = new webkitAudioContext();
ac.createGainNode(); // .. and discard it. This gets
// the clock running at some point.
var count = 0;
function wait() {
if (ac.currentTime === 0) {
// Not ready yet.
++count;
if (count > 600) {
errback('timeout');
} else {
setTimeout(wait, 100);
}
} else {
// Ready. Pass on the valid audio context.
callback(ac);
}
}
wait();
}
Subsequently, when playing a note, don't call .noteOn(ac.currentTime), but do .noteOn(ac.currentTime + 0.01) instead.
I've scoured the web, upgraded the player, rewritten it 5 times, and now completing my 5th day of failing, and still cannot accomplish what the folks at Longtail tell me will work. (Don't get me wrong, I love 'em there, but this has me ready to jump off a bridge).
I'm simply trying to load a video that will play with Flash or iOS, and upon loading it, immediately go to a specific point in the video useing the .seek() method. Longtail tells me to use the onBeforePlay() function because iOS apparently doesn't respect the start value of the playlist. This code works like smoke with Flash, but ignores the seek in iOS.
Can ANYone assist me with this - it has become the most expensive script I've ever worked on and I have made zero progress at all. :( :( :( Also, I removed all the console functions and tried that, but with the same result.
Full code/player can be seen at http://www.tempurl.us/jw6e.html. You can see that with Flash, the video starts at 60 seconds, but on iOS, it starts at 0.
jwp = jwplayer('jwp').setup({
title: 'Single File Player', width: '720', height:'240', autostart: 'false', listbar: {position: "right",size: 400},
sources:[
{ file: 'http://media3.scctv.net/insight/mp4:nursing_4_clips_400.mp4/playlist.m3u8'},
{ file: 'rtmp://fms.scctv.net/insight/nursing_4_clips_400.mp4'}
]
}
);
jwp.onReady(function() {
// Create a playlist item of the video to play
var newItem = [
{ title: 'Title4 ACUTE_ABDO_PAIN_400',
image: 'playlistitem.png',
sources:[
{ file: 'http://media3.scctv.net/insight/mp4:ACUTE_ABDO_PAIN_400.mp4/playlist.m3u8'},
{ file: 'rtmp://fms.scctv.net/insight/ACUTE_ABDO_PAIN_400.mp4'}
]
}
];
jwp.load(newItem);
});
jwp.onBeforePlay(function() {
// This Works on PC/Mac with Flash, but does nothing on iPad/iPhone
jwp.seek(60);
});
Simply to close the question, the bottom line on this problem was that iOS will not allow autostart - period. Knowing that, all the expected events that were not behaving as expected made sense. Once the user initiates the stream with Play, everything works as expected. In our case, this is still a problem because we want to start later in the stream, but knowing that made dealing with it more manageable.
If the problem is iOS will not allow autostart - period. Knowing that,
all the expected events that were not behaving as expected made sense.
Once the user initiates the stream with Play, everything works as
expected
then you can have a play button only for tablet and ios device and on Clicking the play button,
call jwplayer().play(), this could be a work around for your problem, and after you have invoked jwplayer.play, which is only possible with the touch event, after play is triggeredother events will work.
otherwise even if you try jwplayer().play() onReady(), or autostart nothing will work because of iOs will not allow autostart as you said
I've solved this problem on iOS using onBeforePlay with seek() and play(). This work on desktop flash and IOS. Doesn't work on Android using the parameter androidhls:true
jwplayer().onBeforePlay(function() { jwplayer().seek(60); });
jwplayer().play();
As Ethan JWPlayer mentioned in comment use onPlay event. To prevent "loop buffering" as you said just use flag variable:
var isFirstStart = true,
seekValue = 60;
jwplayer().onPlay(function(){
//exit if it's no first playback start
if( !isFirstStart ) {
return;
}
jwplayer().seek(seekValue);
isFirstStart = false;
});
so I'm working on an application that uses webview to display data. At the moment i'm trying to get data from, and send data to the webview. It seems that getting data from the webview works fine, but sending data back to the webview forms the problem.
I use fireEvents and Eventlisteners to communicat. It looks somethhing like this:
webview : index.html
// declared at the beginning of the html file
Ti.App.addEventListener('sendToWebview', function(data) {
alert('alert in webview');
});
// fires when button is pushed
function onClick(){
Ti.App.fireEvent('sendToTi', { "someDataToTi" });
}
app.js
Ti.App.addEventListener('sendToTi', function(data) {
alert('alert in Ti');
Ti.App.fireEvent('sendToWebview', { "someDataToWebview" });
});
What works is the sendToTi event. here i always get the alert. What doesn't seem to work all the time is the sendToWebview event. The weird thing is that is sometimes seem to work, other times not and even when I go back to the code that worked, it seems to not work anymore.
What am I doing wrong? is there a way to make it work?
Your 'sendToTi' is correct. But you can't send events to the WebView in that way.
To execute JavaScript (which is sending events) in your WebView you can use
webview.evalJS('someJSFunction(with, parameters, for, instance);');
webview.evalJS('alert("Hello World!");');
There is no need of EventListeners (especially no app-wide event listeners).
I have a literal JS object that create and plays my playlist.
In the HTML page I have a list with all my tracks.
When I click on some track all works fine but when I click in one track and wait the end of track, the next track not play.
this is the part of my code:
playSound : function(track){
console.log("Play this song");
DG_PLAYER.TrackPlaying = track;
soundManager.destroySound('MySoundID');
DG_PLAYER.Sound = soundManager.createSound({
id:'MySoundID',
url:track.sound,
autoLoad: true,
autoPlay: true,
onload: function() { console.log('sound loaded!', this); },
onfinish: function(){
console.log('end song');
var nextSong = DG_PLAYER.getNextSong();
DG_PLAYER.playSound(nextSong);
},
onfailure : function(){console.log('some error')},
whileloading : function(){console.log('i m loading');},
whileplaying : function(){console.log('i m playing');}
});
},
If I change the line on onfinish event like this :
setTimeOut(function(){DG_PLAYER.playSound(nextSong);},2000) it works fine.
Some one can help me ?
One other things, when the first song finish and call Play for next Song the track is not loaded (no message back from onload event).
Thanks, and sorry for my bad English.
A.
OK ,
I try to add "flashVersion: 9" on setup and this fix the problem but on IE 8 I have un action script message now.
From SoundManager2's Revision History:
Flash Player 11.6.602.171, released by Adobe on 02/26/2013, introduced an issue with SM2's default Flash 8 (flashVersion: 8) API-based JS/Flash interaction, where SM2 methods called from callbacks such as onfinish() would not work. This primarily broke methods used for playing sounds in sequence, serially loading a series of sounds and so on. (See discussion for more.)
Note that this does not affect cases where soundManager.setup({ flashVersion: 9}) is being used; however, SM2 does use flashVersion: 8 by default.
Specifically, Flash-initiated events (such as a sound finishing) make Flash -> JS calls to the SM2 API, which subsequently call user-specified event handlers. If the user-specified SM2 onfinish() handler immediately calls a SM2 method like play() that makes a JS -> Flash call, this call either silently fails or is blocked. Other JS + Flash libraries that use similar callback patterns may also be affected, if their SWF is built targeting the Flash 8 API.
Suspecting a timing or recursion/stack issue, it was found that introducing a setTimeout(callback, 0) to user-specified SM2 callbacks like onfinish() restored sequential/playlist functionality.
Flash Player 11.6.602.180, relased by Adobe on 3/12/2013, exhibits the same behaviour. To avoid additional hacks, SM2 applies this to all Flash 8-based API callbacks regardless of what version of Flash Player is installed. No regressions are anticipated as a result of this change.
Alternately, this issue can be avoided by using soundManager.setup({ flashVersion: 9 }) as the Flash 9-based API does not appear to have this problem.