Media Source Extensions Not Working - javascript

I am trying to use the MediaSource API to append separate WebM videos to a single source.
I found a Github project that was attempting the same thing, where a playlist of WebMs is loaded, and each one is appended as a SourceBuffer. But it was last committed a year ago, and thus out-of-sync with the current spec. So I forked it and updated to the latest API properties/methods, plus some restructuring. Much of the existing code was taken directly from the spec’s examples and Eric Bidelman’s test page.
However, I can not get it to work as expected. I am testing in two browsers, both on Mac OS X 10.9.2: Chrome 35 stable (latest at the time of this writing), and Firefox 30 beta with the flag media.mediasource.enabled set to true in about:config (this feature will not be introduced until FF 25, and current stable is 24).
Here are the problems I’m running into.
Both browsers
I want the video to be, in the end, one long video composed of the 11 WebMs (00.webm, 01.webm, …, 10.webm). Right now, each browser only plays 1 segment of the video.
Chrome
Wildly inconsistent behavior. Seems impossible to reproduce any of these bugs reliably.
Sometimes the video is blank, or has a tall black bar in the middle of it, and is unplayable.
Sometimes the video will load and pause on the first frame of 01.webm.
Sometimes, the video will play a couple of frames of the 02.webm and pause, having only loaded the first three segments.
The Play button is initially grayed out.
Pressing the grayed out Play button produces wildly inconsistent behaviors. Sometimes, it loads a black, unplayable video. Other times, it will play the first segment, then, when you get to the end, it stops, and when you press Play/Pause again, it will load the next segment. Even then, it will sometimes skip over segments and gets stuck on 04.webm. Regardless, it never plays the final segment, even though the console will report going through all of the buffers.
It is honestly different every time. I can’t list them all here.
Known caveats: Chrome does not currently implement sourceBuffer.mode, though I do not know what effect this might have.
Firefox
Only plays 00.webm. Total running time is 0:08, the length of that video.
Video seeking does not work. (This may be expected behavior, as there is nothing actually happening in the onSeeking event handler.)
Video can not be restarted once finished.
My initial theory was that this had to do with mediaSource.sourceBuffers[0].timestampOffset = duration and duration = mediaSource.duration. But I can’t seem to get anything back from mediaSource.duration except for NaN, even though I’m appending new segments.
Completely lost here. Guidance very much appreciated.
EDIT: I uncommented the duration parts of the code, and ran mse_webm_remuxer from Aaron Colwell's Media Source Extension Tools (thanks Adam Hart for the tips) on all of the videos. Voila, no more unpredictable glitches in Chrome! But alas, it still pauses once a media segment ends, and even when you press play, it sometimes gets stuck on one frame.
In Firefox Beta, it doesn’t play past the first segment, responding with:
TypeError: Value being assigned to SourceBuffer.timestampOffset is not a finite floating-point value.
Logging the value of duration returns NaN (but only in FF).

The main problem is with the video files. If you open chrome://media-internals/ you can see error Media segment did not begin with keyframe. Using properly formatted videos, like the one from Eric Bidelman's example (I hope he doesn't get mad that I keep linking directly to that video, but it's the only example video I've found that works), your code does work with the following change in appendNextMediaSegment():
duration = mediaSource.duration;
mediaSource.sourceBuffers[0].timestampOffset = duration;
mediaSource.sourceBuffers[0].appendBuffer(mediaSegment);
You can try Aaron Colwell's Media Source Extension Tools to try to get your videos working, but I've had limited success.
It also seems a little weird that you're looking at the onProgress event before appending segments, but I guess that could work if you only want to append if the video is actually playing. It could make the seekbar act odd since the video length is unknown, but that can be a problem in any case.

I agree with the opinion Adam Hart said. With a webm file, I tried to implement an example like http://html5-demos.appspot.com/static/media-source.html and then made a conclusion that its problem caused the source file I used.
If you have an arrow left, how about trying to use "samplemuxer" introduced at https://developer.mozilla.org/en-US/docs/Web/HTML/DASH_Adaptive_Streaming_for_HTML_5_Video.
In my opinion, samplemuxer is one of encoders like FFMPEG.
I found that the converted file works with mediaSource API. If you will also see it works, please let me know.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>MediaSource API Demo</title>
</head>
<body>
<h3>Appending .webm video chunks using the Media Source API</h3>
<section>
<video controls autoplay width="320" height="240"></video>
<pre id="log"></pre>
</section>
<script>
//ORIGINAL CODE http://html5-demos.appspot.com/static/media-source.html
var FILE = 'IU_output2.webm';
var NUM_CHUNKS = 5;
var video = document.querySelector('video');
var mediaSource = new MediaSource();
video.src = window.URL.createObjectURL(mediaSource);
function callback(e) {
var sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vorbis,vp8"');
logger.log('mediaSource readyState: ' + this.readyState);
GET(FILE, function(uInt8Array) {
var file = new Blob([uInt8Array], {type: 'video/webm'});
var chunkSize = Math.ceil(file.size / NUM_CHUNKS);
logger.log('num chunks:' + NUM_CHUNKS);
logger.log('chunkSize:' + chunkSize + ', totalSize:' + file.size);
// Slice the video into NUM_CHUNKS and append each to the media element.
var i = 0;
(function readChunk_(i) {
var reader = new FileReader();
// Reads aren't guaranteed to finish in the same order they're started in,
// so we need to read + append the next chunk after the previous reader
// is done (onload is fired).
reader.onload = function(e) {
try {
sourceBuffer.appendBuffer(new Uint8Array(e.target.result));
logger.log('appending chunk:' + i);
}catch(e){
console.log(e);
}
if (i == NUM_CHUNKS - 1) {
if(!sourceBuffer.updating)
mediaSource.endOfStream();
} else {
if (video.paused) {
video.play(); // Start playing after 1st chunk is appended.
}
sourceBuffer.addEventListener('updateend', function(e){
if( i < NUM_CHUNKS - 1 )
readChunk_(++i);
});
} //end if
};
var startByte = chunkSize * i;
var chunk = file.slice(startByte, startByte + chunkSize);
reader.readAsArrayBuffer(chunk);
})(i); // Start the recursive call by self calling.
});
}
mediaSource.addEventListener('sourceopen', callback, false);
// mediaSource.addEventListener('webkitsourceopen', callback, false);
//
// mediaSource.addEventListener('webkitsourceended', function(e) {
// logger.log('mediaSource readyState: ' + this.readyState);
// }, false);
function GET(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.send();
xhr.onload = function(e) {
if (xhr.status != 200) {
alert("Unexpected status code " + xhr.status + " for " + url);
return false;
}
callback(new Uint8Array(xhr.response));
};
}
</script>
<script>
function Logger(id) {
this.el = document.getElementById('log');
}
Logger.prototype.log = function(msg) {
var fragment = document.createDocumentFragment();
fragment.appendChild(document.createTextNode(msg));
fragment.appendChild(document.createElement('br'));
this.el.appendChild(fragment);
};
Logger.prototype.clear = function() {
this.el.textContent = '';
};
var logger = new Logger('log');
</script>
</body>
</html>
another test code
<!DOCTYPE html>
<html>
<head>
<title>MediaSource API Demo</title>
</head>
<body>
<h3>Appending .webm video chunks using the Media Source API</h3>
<section>
<video controls autoplay width="320" height="240"></video>
<pre id="log"></pre>
</section>
<script>
//ORIGINAL CODE http://html5-demos.appspot.com/static/media-source.html
var FILE = 'IU_output2.webm';
// var FILE = 'test_movie_output.webm';
var NUM_CHUNKS = 10;
var video = document.querySelector('video');
var mediaSource = new MediaSource();
video.src = window.URL.createObjectURL(mediaSource);
function callback(e) {
var sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vorbis,vp8"');
logger.log('mediaSource readyState: ' + this.readyState);
GET(FILE, function(uInt8Array) {
logger.log('byteLength:' + uInt8Array.byteLength );
sourceBuffer.appendBuffer(uInt8Array);
});
}
mediaSource.addEventListener('sourceopen', callback, false);
// mediaSource.addEventListener('webkitsourceopen', callback, false);
//
// mediaSource.addEventListener('webkitsourceended', function(e) {
// logger.log('mediaSource readyState: ' + this.readyState);
// }, false);
function GET(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.send();
xhr.onload = function(e) {
if (xhr.status != 200) {
alert("Unexpected status code " + xhr.status + " for " + url);
return false;
}
callback(new Uint8Array(xhr.response));
};
}
</script>
<script>
function Logger(id) {
this.el = document.getElementById('log');
}
Logger.prototype.log = function(msg) {
var fragment = document.createDocumentFragment();
fragment.appendChild(document.createTextNode(msg));
fragment.appendChild(document.createElement('br'));
this.el.appendChild(fragment);
};
Logger.prototype.clear = function() {
this.el.textContent = '';
};
var logger = new Logger('log');
</script>
</body>
</html>
Thanks.

Related

Web API MediaRecorder: How do I force record at 60fps?

Summary
The API I speak about
The context
The problem: Actual result + Expected result
What I already have tried
Clues to help you to help me
Minimal and Testable Example: Prerequisites and Steps to follow + Sources
The API I speak about
https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder
The context
I have a video element that I load & play several times with different src. Between two different videos, a transition occures and each video appears with a fade in. When a video is playing, I draw it in a canvas with the transition and the fade in.
Everything I draw in the canvas is recorded using the Web API MediaRecorder. Then I download the WEBM file corresponding to this recording.
The problem
Actual result
The resulting WEBM is jerky. When I drew in the canvas, the drawing was fluid. But the WEBM resulting from the recording of the canvas drawing is jerky.
Expected result
The WEBM resulting from the recording of the canvas drawing must be as fluid as the canvas drawing itself. If it's impossible to have exactly this result, then: the WEBM must be approximately as fluid as the canvas drawing itself in a way that we quite can't say it's jerky.
What I have already tried
First, I've tried to set a time slice of 1ms, of 16ms (corresponding to 60fps), of 100ms, then 1000, then 10000, etc. to the method start of the Media Recorder, but it didn't work.
Second, I've tried to call requestData every 16ms (in a simple JS timeoutlistener executed every 16ms), but it didn't work.
Clues to help you to help me
Perhaps I'm wrong but it would be possible that a material limit exist on my computer (I have a HP Spectre x360 that doesn't have graphic card, but just a little graphics chip), or a logical limit on my Chromium browser (I have Version 81.0.4044.138 on my Ubuntu 16.04 LTS).
If you can confirm this it would solve this question. If you can explain how to deal with this problem it would be very good (alternative solution to the Web API Media Recorder, or something else).
Minimal and Testable Example
Prerequisites and Steps to follow
Have at least 2 WEBM videos (which will be the input videos, remember: we want to output a new video that contains the canvas drawing, which itself contains these two input videos plus transitions and colors effects etc.)
Have a HTTP server and a file called "index.html" that you will open with Chromium v.81 e.g.. Copy/paste the following sources inside ; click on the "Start" button (you won't need to click on the "Stop" button) ; it will draw both videos on the canvas, with the transitions & colors effects, it will record the canvas drawing, and a "Download the final output video" will appear, allowing you to download the output video. You will see that it's jerky.
In the following sources, copy/paste the paths of your videos in the JS array called videos.
Sources
<html>
<head>
<title>Creating Final Video</title>
</head>
<body>
<button id="start">Start</button>
<button id="stop_recording">Stop recording</button>
<video id="video" width="320" height="240" controls>
<source type="video/mp4">
</video>
<canvas id="canvas" width=3200 height=1608></canvas>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
var videos = []; // Populated by Selenium
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var video = document.querySelector("video");
startRecording();
$('#start').click(function() {
showSomeMedia(0, 0);
});
function showSomeMedia(videos_counter) {
resetVideo();
if(videos_counter == videos.length) {
$('#stop_recording').click();
return;
} else {
setVideoSrc(videos_counter);
setVideoListener();
videos_counter++;
}
video.addEventListener('ended', () => {
showSomeMedia(videos_counter);
}, false);
}
function resetVideo() {
var clone = video.cloneNode(true);
video.remove();
video = clone;
}
function setVideoSrc(videos_counter) {
video.setAttribute("src", videos[videos_counter]);
video.load();
video.play();
}
function setVideoListener() {
var alpha = 0.1;
video.addEventListener('playing', () => {
function step() {
if(alpha < 1) {
ctx.globalAlpha = alpha;
}
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
requestAnimationFrame(step)
if(alpha < 1) {
alpha += 0.00001;
}
}
requestAnimationFrame(step);
}, false)
}
function startRecording() {
const chunks = [];
const stream = canvas.captureStream();
const rec = new MediaRecorder(stream);
rec.ondataavailable = e => chunks.push(e.data);
$('#stop_recording').click(function() {
rec.stop();
});
rec.onstop = e => exportVid(new Blob(chunks, {type: 'video/webm'}));
window.setTimeout(function() {
rec.requestData();
}, 1);
rec.start();
}
function exportVid(blob) {
const vid = document.createElement('video');
vid.src = URL.createObjectURL(blob);
vid.controls = true;
document.body.appendChild(vid);
const a = document.createElement('a');
a.download = 'my_final_output_video.webm';
a.href = vid.src;
a.textContent = 'Download the final output video';
document.body.appendChild(a);
}
</script>
</body>
</html>
The problem is solved by using my gaming laptop (RoG), wich has a good graphic card, able to record at higher frequency than my Spectre x360 development laptop.

Firefox stops calling AJAX functions

I have a database with points (1300 points), I plot them on a map. For most of these points, I want to plot an image in a popup, if the image exists.
So with AJAX I check if the path exists, if it does I plot the image, if not I do nothing.
Here is the code :
function doesFileExist(urlToFile) {
var value;
var xhr = new XMLHttpRequest();
xhr.open('HEAD', urlToFile,false); //requete synchrone : nécessaire pour l'affichage d'image mais long
xhr.onreadystatechange = function() {
if (this.readyState == this.DONE) {
if (this.status == "404") {
value = false;
} else {
value = true;
}
};
}
xhr.send();
return value;
}
The main problem is that Firefox is plotting only few points most of the time. No problem with IE and Chrome.
I was wondering if I ask for too much ressources but it does not even struggle to plot the points in that case. It just plots the first ones and it stops loading. (so it is either long and complete, either fast and uncomplete).
Does someone know how to avoid to plot only part of the points ?
A solution could be to use a more efficient way than my AJAX function, if someone knows one, because it takes time to load because of this path checking (about 17 seconds with Chrome, 15s with Internet Explorer, 60s with Firefox).
Here is website, you will be able to see by yourself.
My javascript call to the function (the div 'fiche' corresponds to the popup)
var fiche ="<div class='fiche'>";
ouvrage = L.marker([latitude,longitude],{icon: unvisibleSmallIcon});
var directory = "./photos/"+identite['Cours d\'eau'];
var chemin_photo = directory+"/"+ identite['Identification'] +".JPG";
if (doesFileExist(chemin_photo)){
fiche += "<img class='img_logo' src='"+chemin_photo+"' alt='' width='80%' heigth='auto'> <br>";}
ouvrage.bindPopup(fiche);
EDIT
Thanks to comment, I changed my code to :
<img class='img_logo' onerror='this.remove()' src='"+chemin_photo+"' alt='' width='80%' heigth='auto'> <br>"
However the remove function is not understood by Internet Explorer.
Then I tried to replace by another image, but image.onerror = "" does not work with Chrome.
<img class='img_logo' onerror='imgError(this)' src='"+chemin_photo+"' alt='' width='80%' heigth='auto'> <br>"
function imgError(image) {
image.onerror = "";
image.src = "./photos/no-image.png";
return true;
}
But Chrome says that "imgError is not defined at HTMLImageElement.onerror "
Using async false is a terrible practice as it completely blocks everything in page
Why not just use an error handler on images and remove when error occurs
<img src="..." onerror="this.remove()">

ajax undefined after a thousand iterations

When creating an animation I try to use javascript for additional effects, namely snow being piled up and falling off the edges of the foreground. I figured that the javascript could do the "calculations" on a <canvas> that had the image, then send the "snow" to a php script that would create the png images. This is all placed on my local "server" because that is the only one that can write files.
<html>
<head>
<title>Making Snow</title>
</head>
<body bgcolor="black">
<canvas id="canvas" width="1920px" height="1080px"></canvas>
</body>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script>
var canvas;
var ctx;
var frame=-530;
var simg = new Image()
var dimg = new Image()
onload = function()
{
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
simg.src = "prodB/C/DGB.0530.png"
}
simg.onload = function()
{
var ipo=3;
// Initialize all pixels on the screen/page
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(simg,0,0);
document.title = "Making Snow " + (frame+530) + " / 7000";
snowdraw();
}
dimg.onerror = function()
{
dimg.src = "../imagewriting/snow" + zeroFill(frame+530,4) + ".png";
}
dimg.onload = function()
{
frame++;
if(frame<530)
simg.src = "prodB/C/DGB.0530.png"
else if(frame>4400)
simg.src = "prodB/C/DGB.4400.png"
else
simg.src = "prodB/C/DGB." + zeroFill(frame,4) + ".png"
}
var snowdraw = function()
{
var temp;
var canvasData = "";
// console.log(screen);
// Animation
// Choose a random pixel at the top of the screen
if(frame<7000)
{
for(ny=canvas.height-2; ny>=0; ny--)
{ // Iterate through all the pixels starting from the bottom right corner.
for(nx=canvas.width-2; nx>=0; nx--)
{
canvasData=canvasData+"1";
}
}
$.ajax({
method: "POST",
url: "makesnow.php",
data:{ imgData: canvasData, frame: (frame+530) }
})
.done(function( msg ) {
dimg.src = "../imagewriting/snow" + zeroFill(frame+530,4) + ".png";
});
}
else
{
document.title = "COMPLETE";
}
}
// http://stackoverflow.com/questions/1267283/how-can-i-create-a-zerofilled-value-using-javascript
// by Peter Bailey http://stackoverflow.com/users/8815
function zeroFill( number, width )
{
width -= number.toString().length;
if ( width > 0 )
{
return new Array( width + (/\./.test( number ) ? 2 : 1) ).join( '0' ) + number;
}
return number + ""; // always return a string
}
</script>
</html>
However, on the 1640th frame (or more precisely frame=1110) ajax is suddenly undefined. The image snow1640.png is created, but the browser tells me ajax is not defined and won't advance passed Making Snow 1640 / 7000. Because of the small random nature for each "snow flake" I can't just pick it up from where I left off, as the snow would jump from one frame to the next. Though I did try that at one point and ajax still stopped after that frame.
I first ran it on the local machine running Firefox (http://127.0.0.1/...) then moved onto another machine on the network which is more powerful running Chrome and both died on that same frame. Thought it might be a memory or file limit so I moved the complete frames out of there. Still nothing.
EDIT: Code now snippit of just the problem.
Also, console.log for data and ajax.responseText seem to be generally empty, both during successful "renders" and when it starts iterating ajax is not defined (every other line empty, every other error).
EDIT: New development! Turns out that the error ReferenceError: ajax is not defined anyways gets called between frames (and the 404 when asking dimg if image was created).
EDIT: By typing in console.log($.ajax) after onload and commenting out simg.src I got function ajax().
Error persists, this time I expanded it to reveal the following:
success http://127.0.0.1/ag/makesnowBC.html 197:7
j http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js 2:27131
fireWith http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js 2:27949
x http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js 4:22242
b http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js 4:26298
EDIT: Changed the code to now use Synchronous ajax. This time there are no error messages what so ever, but it still stops at 1640.

Broken Youtube thumbnails do not fire error callback

Will an image error callback fire if an image is 404, but the host returns an image anyway?
I am trying to determine on the client whether a Youtube thumbnail is valid before submitting the URL to the server. Normally you can generate a thumbnail URL without querying their API with the format http://img.youtube.com/vi/**ID**/maxresdefault.jpg
Some videos do not have high-res thumbnails, for example, this one:
http://img.youtube.com/vi/ty62YzGryU4/maxresdefault.jpg
However, a lower quality thumbnail always exists:
http://img.youtube.com/vi/ty62YzGryU4/default.jpg
Ideally I would be able to detect whether the thumbnail did load via this code snippet, which would call "done" when it loaded a valid thumbnail:
var id = "ty62YzGryU4"
var tries = 0
var thumb = "http://img.youtube.com/vi/" + id + "/maxresdefault.jpg"
var img = new Image ()
img.onload = function(){ console.log('ok'); done(id, thumb) }
img.onerror = function(){
switch (tries++){
case 0:
img.src = thumb = "http://img.youtube.com/vi/" + id + "/hqdefault.jpg"
break;
case 1:
img.src = thumb = "http://img.youtube.com/vi/" + id + "/default.jpg"
break;
case 2:
done(id, thumb)
break;
}
}
img.src = thumb
if (img.complete) img.onload()
However this is not the case -- while I see a 404 error in the console, neither the onload nor the onerror callbacks fire, and thus done is never called.
If I set img.crossOrigin = "Anonymous" the onerror callback fires... for every thumbnail, because of the accursed Cross-Origin Resource Sharing policy.
I have also tried crafting an XMLHttpRequest, but to no avail:
xmlhttp = new XMLHttpRequest()
xmlhttp.onreadystatechange = function() {
console.log(xmlhttp.readyState)
console.log(xmlhttp.status)
};
xmlhttp.open('GET', url, true);
xmlhttp.send(null);
Whether I set X-Requested-With: XMLHttpRequest or not, the readyState goes from 1 to 4 but status is always zero!
Is there any way to see if this particular image gave a 404 without using the API?
YouTube thumbnails will not fire the onerror() function because YouTube sends another base64 gif image which shows the empty video icon which has the resolution of 120X90. (this is the key to the solution)
Note that you should load the image tag with the maxresdefault image and give it or (them) a certain class, e.g. "youtube-thumb".
For info: maxresdefault 1080, sddefault 720, hqdefault 480, mqdefault 360, default 240.
According to limited experiments, 0.jpg is 480 or the best low-res image available for a video.
$(function()
{
$('.youtube-thumb').each(function(ix, it){
if($(it)[0].naturalHeight <= 90 )
{
var path = $(it).attr('src');
var altpath = path.replace('maxresdefault.jpg','0.jpg');
$(it).attr('src', altpath);
}
});
});
I know this question is a bit older but I had the same problem today and I want to give you my solution which tries all of the YouTube thumbnails from best to worst.
My "ranking" of the different options is:
maxresdefault
mqdefault -> only one besides 1) that doesn't have black borders
sddefault
hqdefault
default -> works 100%
This is my code.
function youtube_check(e) {
var thumbnail = ["maxresdefault", "mqdefault", "sddefault", "hqdefault", "default"];
var url = e.attr("src");
if (e[0].naturalWidth === 120 && e[0].naturalHeight === 90) {
for (var i = 0, len = thumbnail.length - 1; i < len; i++) {
if (url.indexOf(thumbnail[i]) > 0) {
e.attr("src", url.replace(thumbnail[i], thumbnail[i + 1]));
break;
}
}
}
}
<img onload="youtube_check($(this))" src="https://i3.ytimg.com/vi/---ID---/maxresdefault.jpg">

Is Web Audio API source.start(0) supported on Safari (it works just fine on Chrome)?

I'm trying to implement Web Audio API. The code works on Chrome 29.0.1547.76 but not on Safari 6.0.5 (8536.30.1). The key is whether I use noteOn(0) or start(0).
I want to use start() so that I can play part of a sound:
asource.start(0, 2, 1);
works fine in Chrome (plays immediately, starts at the 2 s mark, plays for 1 s) but results in
TypeError: 'undefined' is not a function (evaluating 'asource.start(0, 2, 1)')
on Safari. Replacing that one line with
asource.noteOn(0);
works. [Well, I need to call noteOff(0) instead of stop(0).] I get the same error with start(0). So, I'm assuming that Safari does not implement start(0)? But if so, why do some of the examples at HTML5 Rocks that use start(0) work?
For reference, here's the complete web page. I've tried many different sounds/formats; all result in the same error.
<!DOCTYPE html>
<html lang=en>
<head>
<meta charset="utf-8">
<title>Web Audio API Issue</title>
</head>
<body>
<p>Example working on Chrome but not Safari when using start()</p>
<button id="Load" onclick="init()" >Load</button>
<button id="Play" onclick="playSound()" disabled>Play</button>
<button id="Stop" onclick="stopSound()" disabled>Stop</button>
<script>
var web_audio_context;
var abuffer;
var asource;
function init() {
contextClass = (window.AudioContext ||
window.webkitAudioContext ||
window.mozAudioContext ||
window.msAudioContext);
web_audio_context = new contextClass();
var theURL = './digits.mp3';
var xhr = new XMLHttpRequest();
xhr.open('GET', theURL, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
finishedLoading(this.response);
};
xhr.send();
}
function finishedLoading(arrayBuffer) {
web_audio_context.decodeAudioData(arrayBuffer, function(buffer) {
abuffer = buffer;
document.getElementById('Load').disabled = true;
document.getElementById('Play').disabled = false;
document.getElementById('Stop').disabled = false;
}, function(e) {
console.log('Error decoding file', e);
});
}
function playSound() {
asource = web_audio_context.createBufferSource();
asource.buffer = abuffer;
asource.connect(web_audio_context.destination);
asource.start(0, 2, 1);
}
function stopSound() {
asource.stop(0);
}
</script>
</body>
</html>
In newer Web Audio API versions the method noteOn is renamed to start. Safari still uses the older version while Chrome uses a more current one.
Just try:
asource.start ? asource.start(0, 2, 1) : asource.noteOn(0, 2, 1);
Try adding Web Audio API monkey patch library by Chris Wilson to your project.
It might help.

Categories

Resources