issues playing soundclips with HTML5 Audio on iPhone - iphone

I have a few mp3 soundclips to play at certain events on my HTML5 app. There's no tag, the script creates Audio objects at the beginning (one for each mp3) and loads the files. When I have to play a sound, I simply call play() on one of these objects.
This works fine in a Chrome desktop, but is very inconsistent in my iPod touch, eventually some sounds stop playing and I even get error alerts.
Here's a small script I set up to see the problem, it's hosted at soundtest.staticloud.com including the 3 audio files so you can check it out on an iPhone/whatever.
var snd = [];
window.onload = function() {
for(var i = 0; i < 3; i++) {
snd[i] = new Audio("snd" + i + ".mp3");
snd[i].load();
}
}
function sound(n) {
snd[n-1].play();
}
Am I doing something wrong?

I had a ton of audio problems in iDevices...what you have to do is blank out the last played clip before starting the next. Also, I just had a single audio object, and would change the source, so my solution looked more like this:
<html>
<body>
<audio></audio>
etc
</body>
<script type="text/javascript">
var sounds=[];
window.onload = function() {
for(var i = 0; i < 3; i++) {
sounds[i] = "snd" + i + ".mp3";
}
}
function sound(n){
snd=document.getElementsByTagName("audio")[0];
snd.pause(); //-----------------PAUSE THE LAST (WHICH MAY BE LONG DONE)
snd.src=''; //-----------------BLANK SRC
snd.src=sounds[n-1]; //---------REPLACE WITH NEW SOUND
snd.load();
snd.play();
}
</script>
</html>

Related

inline html5 video on iphone

I want to play an HTML5 video on the iPhone but whenever I try to, the iPhone automatically pops out in fullscreen when the video '.play()' is called. How do I play the video inline without the iPhone changing the UI of it like these:
http://www.easy-bits.com/iphone-inline-video-autostart
http://www.takeyourdose.com/en (When you click "Start the 360 experience")
Edit: Here's my code:
<!DOCTYPE html>
<html lang="en">
<head>
<title>iPhone Test</title>
<meta charset="utf-8">
</head>
<body>
<button onclick="document.getElementById('vid').play()">Start</button>
<video id="vid">
<source src="/videos/tutorial.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
</body>
</html>
I'm working on a solution to this until Apple allows the "webkit-playsinline" to actually play inline.
I started a library here: https://github.com/newshorts/InlineVideo
It's very rough, but the basic gist is that you "seek" through the video instead of playing it outright. So instead of calling:
video.play()
You instead set a loop using request animation frame or setInterval, then set the:
video.currentTime = __FRAME_RATE__
So the whole thing might look like in your html:
<video controls width="300">
<source src="http://www.w3schools.com/html/mov_bbb.mp4">
</video>
<canvas></canvas>
<button>Play</button>
and your js (make sure to include jquery)
var video = $('video')[0];
var canvas = $('canvas')[0];
var ctx = canvas.getContext('2d');
var lastTime = Date.now();
var animationFrame;
var framesPerSecond = 25;
function loop() {
var time = Date.now();
var elapsed = (time - lastTime) / 1000;
// render
if(elapsed >= ((1000/framesPerSecond)/1000)) {
video.currentTime = video.currentTime + elapsed;
$(canvas).width(video.videoWidth);
$(canvas).height(video.videoHeight);
ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
lastTime = time;
}
// if we are at the end of the video stop
var currentTime = (Math.round(parseFloat(video.currentTime)*10000)/10000);
var duration = (Math.round(parseFloat(video.duration)*10000)/10000);
if(currentTime >= duration) {
console.log('currentTime: ' + currentTime + ' duration: ' + video.duration);
return;
}
animationFrame = requestAnimationFrame(loop);
}
$('button').on('click', function() {
video.load();
loop();
});
http://codepen.io/newshorts/pen/yNxNKR
The real driver for Apple changing this will be the recent release of webGL for ios devices enabled by default. Basically there are going to be a whole bunch of people looking to use video textures. technically right now, that can't be done.
On IOS10 / Safari 10 you can now add the playsinline property to the HTML5 Video element, and it will just play inline.
If you create an audio element and a video element, you can play the audio via user interaction and then seek the video, rendering it to a canvas. This is something quick that I came up with (tested on iPhone iOS 9)
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
var audio = document.createElement('audio');
var video = document.createElement('video');
function onFrame(){
ctx.drawImage(video,0,0,426,240);
video.currentTime = audio.currentTime;
requestAnimationFrame(onFrame);
}
function playVideo(){
var i = 0;
function ready(){
i++;
if(i == 2){
audio.play();
onFrame();
}
}
video.addEventListener('canplaythrough',ready);
audio.addEventListener('canplaythrough',ready);
audio.src = video.src = "http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_10mb.mp4";
audio.load();
video.load();
}
CodePen
Test Page
Apologies for writing this as an answer instead of a comment on the main thread, but I apparently do not have enough reputation points to comment!
Anyways, I am also looking to do exactly the same thing as the OP.
I noticed that there is a particular library, krpano, coupled with the krpano videoplayer plugin that allows for video to be played on iPhone INLINE! Some demos of this in action can be found here: http://krpano.com/video/
While I would prefer a simple 2D video example over these crazy panorama videos, this is the closest I have found while scouring the web. From what I can tell, they use a normal element not attached to the document:
var v = document.querySelector('video');
// remove from document
v.parentNode.removeChild(v);
// touch anywhere to play
document.ontouchstart = function () {
v.play();
}
Video element before it's removed:
<video playsinline webkit-playsinline preload="auto" crossorigin="anonymous" src="http://www.mediactiv.com/video/Milano.mp4" loop style="transform: translateZ(0px);"></video>
But that alone doesn't seem to be enough: when the video is played, it still goes fullscreen.
How do they manage to prevent the video from going fullscreen?
EDIT: After looking at both examples it looked like they both were leveraging the canvas element to render the video, so I went ahead and whipped up a demo showing off video rendering thru the canvas element. While the demo works great, it fails to deliver on iPhone (even tho the video element is completely removed from the DOM!) -- the video still jumps to full screen. I'm thinking the next step would be to apply these same principles to a WebGL canvas (that's what the krpano examples are doing), but in the meantime maybe this demo will spark some ideas in others...
http://jakesiemer.com/projects/video/index.htm

How to get dynamic element HTML use Addon SDK with Timers?

I want to scrape a page, the HTML content of this page auto change in a time frame. So i want to use pageMod and Timers of Addon Sdk to get the element innerHtml which change often.
Here are my scripts :
In main.js :
var tag = "container1";
var data = require("sdk/self").data;
var pageMod = require("sdk/page-mod");
var timer = require("sdk/timers");
var i = 0;
function scrapeData()
{
i = i + 1;
console.log("Begin pageMod : " + i);
pageMod.PageMod({
include: "*.test.com",
contentScriptFile: data.url("element-getter.js"),
contentScriptWhen: 'ready',
onAttach: function(worker) {
worker.port.emit("getElements", tag);
worker.port.on("gotElement", function(elementContent) {
console.log(elementContent);
});
}
});
console.log("End pageMod : " + i);
}
timer.setInterval(scrapeData, 10000);
And in data/element-getter.js :
self.port.on("getElements", function(tag) {
var elements = document.getElementById(tag);
self.port.emit("gotElement", elements.innerHTML);
});
After install this Firefox Add-on, when timers is running, it can only get the innerHtml one time, and the other time, it only display Begin pageMod and End pageMode in console log. Please help.
What you're currently doing is attaching the same page mod multiple times.
What you should do is move the timer inside the content script:
data/element-getter.js:
function scrapeData() {
var elements = document.getElementById(tag);
self.port.emit("gotElement", elements.innerHTML);
}
setInterval(scrapeData, 10000);
If you really want to keep the timer in the main page, then you need to maintain an array of worker instances, and loop through this array to emit your custom event. See this answer for more details.
(PS. Depending on your use case, the sdk/frame/hidden-frame module might be of interest.)

Soundcloud HTML5 widget continuous play

I'm trying to get the SoundCloud HTML5 player widget to automatically start and seek to a specific track and position but no matter what I try it doesn't work.
I'm using the API code below:
<iframe width="100%" height="450" scrolling="no" id="soundcloud-player" frameborder="no" src="https://w.soundcloud.com/player/?url=http%3A%2F%2Fapi.soundcloud.com%2Fplaylists%2F3058825&color=00be53&auto_play=false&show_artwork=true"></iframe>
<script type="text/javascript" src="http://w.soundcloud.com/player/api.js"></script>
<script type="text/javascript">
(function(){
var widgetIframe = document.getElementById('soundcloud-player'),
widget = SC.Widget(widgetIframe);
widget.bind(SC.Widget.Events.READY, function() {
widget.play();
widget.seekTo('5000');
});
widget.bind(SC.Widget.Events.PLAY, function() {
// get information about currently playing sound
widget.getCurrentSound(function(currentSound) {
console.log('sound ' + currentSound.title + 'began to play');
});
}); }());
What I'm basically trying to accomplish is have the player automatically seek to the same spot when the user switches between pages on the site. I plan on reading from a cookie, the position and track and then using the method above. Any help would be greatly appreciated!
The problem is most probably related to the sound not being fully loaded at the moment when you are trying to call seekTo. You can easily verify this by adding the following bit to your code:
// …
widget.bind(SC.Widget.Events.READY, function() {
widget.play();
// Note setTimeout here!
// This will now work since the needed part of the sound
// will have loaded after the timeout
setTimeout(function () {
widget.seekTo('5000');
}, 1000);
});
// …
But since you don't really want to have arbitrary timeout in your code, it's a good idea to attach event handler to progress event:
widget.bind(SC.Widget.Events.LOAD_PROGRESS, function onLoadProgress (e) {
if (e.loadedProgress && e.loadedProgress === 1) {
widget.seekTo(15000); // seek to previous location
widget.unbind(SC.Widget.Events.LOAD_PROGRESS);
}
});
Here's a working version of this code http://jsbin.com/ebeboj/2/edit
Also, in case you have very long tracks, you could also retrieve duration from the sound (via getCurrentSound), check at what point in range from 0 to 1 the track has stopped playing and only wait for that value (since loadedProgress === 1 might take a while), something like:
widget.getCurrentSound(function(currentSound) {
// currrentSound.duration is 269896 for the first track of your playlist
relativePreviousPlay = previousPlay / currentSound.duration; // ~0.204
});
widget.bind(SC.Widget.Events.LOAD_PROGRESS, function onLoadProgress (e) {
if (e.loadedProgress && e.loadedProgress > relativePreviousPlay) {
widget.seekTo(previousPlay); // seek to previous location
widget.unbind(SC.Widget.Events.LOAD_PROGRESS);
}
});
Check out working example for the last bit of code here http://jsbin.com/ebeboj/4/edit
Sidenote: I'd recommend using localStorage over cookies for storing previous position of playback, because cookies will travel back and forth from client to server slowing down your website, and you likely don't need the information on the sever side.

iPhone, iPad script rule out

Is it possible to rule out a script load only when viewing site on Apple devices? For example I don't want this file to be loaded on iPhone/iPad:
<script type="text/javascript" src="js/jquery.skinned-select.js"></script>
Will be grateful for any help.
There may be an easier way, but try the following:
<script type="text/javascript">
if ('ontouchstart' in document.documentElement) // check for touch screen
{ }
else {
// run code for mouse device
var s = document.createElement("script");
s.setAttribute("type", "text/javascript");
s.setAttribute("src", "js/jquery.skinned-select.js");
var nodes = document.getElementsByTagName("*");
var node = nodes[nodes.length - 1].parentNode;
node.appendChild(s);
};
</script>
Hope it helps.

Triggering shouldStartLoadWithRequest with multiple window.location.href calls

Im trying to pass multiple things from a webpage inside a UIWebView back to my iPhone app via the shouldStartLoadWithRequest method of the UIWebView.
Basically my webpage calls window.location.href = "command://foo=bar" and i am able to intercept that in my app no problem. Now if i create a loop and do multiple window.location.href calls at once, then shouldStartLoadWithRequest only appears to get called on once and the call it gets is the very last firing of window.location.href at the end of the loop.
The same thing happens with the webview for Android, only the last window.location.href gets processed.
iFrame = document.createElement("IFRAME");
iFrame.setAttribute("src", "command://foo=bar");
document.body.appendChild(iFrame);
iFrame.parentNode.removeChild(iFrame);
iFrame = null;
So this creates an iframe, sets its source to a command im trying to pass to the app, then as soon as its appended to the body shouldStartLoadWithRequest gets called, then we remove the iframe from the body, and set it to null to free up the memory.
I also tested this on an Android webview using shouldOverrideUrlLoading and it also worked properly!
I struck this problem also and here is my solution that works for me.
All my JavaScript functions use this function __js2oc(msg) to pass data
and events to Objective-C via shouldStartLoadWithRequest:
P.S. replace "command:" with your "appname:" trigger you use.
/* iPhone JS2Objective-C bridge interface */
var __js2oc_wait = 300; // min delay between calls in milliseconds
var __prev_t = 0;
function __js2oc(m) {
// It's a VERY NARROW Bridge so traffic must be throttled
var __now = new Date();
var __curr_t = __now.getTime();
var __diff_t = __curr_t - __prev_t;
if (__diff_t > __js2oc_wait) {
__prev_t = __curr_t;
window.location.href = "command:" + m;
} else {
__prev_t = __curr_t + __js2oc_wait - __diff_t;
setTimeout( function() {
window.location.href = "command:" + m;
}, (__js2oc_wait - __diff_t));
}
}
No, iframe's url changing won't trigger shouldOverrideUrlLoading, at least no in Android 2.2.