Complete mp3 web worker

master
William Toohey 10 years ago
parent 2b8a98e7a3
commit 0ac94c6347
  1. 154
      js/SoundManager.js
  2. 47
      js/mp3/mp3-worker.js

@ -54,7 +54,6 @@ function SoundManager(core) {
this.maxBinLin = 0; this.maxBinLin = 0;
// For concatenating our files // For concatenating our files
this.leftToLoad = 0;
this.tmpBuffer = null; this.tmpBuffer = null;
this.tmpBuild = null; this.tmpBuild = null;
this.onLoadCallback = null; this.onLoadCallback = null;
@ -76,11 +75,7 @@ function SoundManager(core) {
} }
this.mp3Worker = new Worker(core.settings.defaults.mp3WorkerPath + 'mp3-worker.js'); this.mp3Worker = new Worker(core.settings.defaults.mp3WorkerPath + 'mp3-worker.js');
this.mp3Worker.addEventListener('message', function(e) { this.mp3Worker.addEventListener('message', this.workerFinished.bind(this), false);
console.log('Worker said: ', e.data);
}, false);
this.mp3Worker.postMessage("Hello world");
window.addEventListener('touchend', function() { window.addEventListener('touchend', function() {
// create empty buffer // create empty buffer
@ -201,76 +196,97 @@ SoundManager.prototype.loadBuffer = function(song, callback) {
if(callback) { if(callback) {
this.onLoadCallback = callback; this.onLoadCallback = callback;
} }
if(song.sound.byteLength == 0) {
// Someone went forward then immediately back then forward again
// Either way, the sound is still loading. It'll come back when it's ready
return;
}
var transferrables = [song.sound];
if(song.buildup) { if(song.buildup) {
this.loadAudioFile(song, true); transferrables.push(song.buildup);
} }
this.loadAudioFile(song, false); this.mp3Worker.postMessage(song, transferrables);
}; };
SoundManager.prototype.loadAudioFile = function(song, isBuild) { SoundManager.prototype.workerFinished = function(event) {
var asset = AV.Asset.fromBuffer(isBuild ? song.buildup : song.sound); var result = event.data;
asset.on("error", function(err) {
console.log(err);
});
asset.decodeToBuffer(function(buffer) {
console.log(asset.format);
console.log(buffer.length);
var channels = asset.format.channelsPerFrame;
var samples = buffer.length/channels;
var audioBuf = this.context.createBuffer(channels, samples, asset.format.sampleRate);
var audioChans = [];
for(var i = 0; i < channels; i++) {
audioChans.push(audioBuf.getChannelData(i));
}
for(var i = 0; i < buffer.length; i++) {
audioChans[i % channels][Math.round(i/channels)] = buffer[i];
}
(this.getAudioCallback(song, isBuild))(audioBuf);
}.bind(this));
};
/* decodeAudioData nukes our original MP3 array, but we want to keep it around // restore our old ArrayBuffers TODO race
for memory saving purposes, so we must duplicate it locally here */ var song = this.restoreBuffers(result.song);
SoundManager.prototype.getAudioCallback = function(song, isBuild) {
var current = isBuild ? song.buildup : song.sound; // Something else started loading after we started
var copy = current.slice(0); if(this.song != song) {
return function(buffer) { console.log("Song changed before we could play it, user is impatient!");
// before the race condition check or we might lose data return;
if(isBuild) { }
song.buildup = copy;
} else { if(song.buildup) {
song.sound = copy; this.tmpBuild = this.trimMP3(this.audioBufFromRaw(result.build), song.forceTrim, song.noTrim);
} }
// race condition prevention this.tmpBuffer = this.trimMP3(this.audioBufFromRaw(result.loop), song.forceTrim, song.noTrim);
if(this.song != song) { this.onSongLoad(song);
return; }
}
if(isBuild) { // We pass our ArrayBuffers away, so we need to put them back
this.tmpBuild = this.trimMP3(buffer, song.forceTrim, song.noTrim); // We must iterate all the songs in case the player has moved on in the meantime
} else { SoundManager.prototype.restoreBuffers = function(newSong) {
this.tmpBuffer = this.trimMP3(buffer, song.forceTrim, song.noTrim); var songs = this.core.resourceManager.allSongs;
} for(var i = 0; i < songs.length; i++) {
this.onSongLoad(song); var oldSong = songs[i];
}.bind(this); var same = true;
}; for(var attr in oldSong) {
if(oldSong.hasOwnProperty(attr) && attr != "buildup" && attr != "sound") {
var oldV = oldSong[attr];
var newV = newSong[attr];
if(oldV != newV) {
// Equality checks break for NaN, and isNaN coerces args to Number, which we don't want
if(!( (oldV != oldV) && (newV != newV) )) {
same = false;
break;
}
}
}
}
if(same) {
oldSong.sound = newSong.sound;
oldSong.buildup = newSong.buildup;
return oldSong;
}
}
console.log("Oh no! Original song has been lost!");
return null;
}
// Converts interleaved PCM to Web Audio API friendly format
SoundManager.prototype.audioBufFromRaw = function(sound) {
var buffer = sound.array;
var channels = sound.channels;
var samples = buffer.length/channels;
var audioBuf = this.context.createBuffer(channels, samples, sound.sampleRate);
var audioChans = [];
for(var i = 0; i < channels; i++) {
audioChans.push(audioBuf.getChannelData(i));
}
for(var i = 0; i < buffer.length; i++) {
audioChans[i % channels][Math.round(i/channels)] = buffer[i];
}
return audioBuf;
}
SoundManager.prototype.onSongLoad = function(song) { SoundManager.prototype.onSongLoad = function(song) {
// if this fails, we need to wait for the other part to load if(song.buildup) {
if(this.tmpBuffer && (!song.buildup || this.tmpBuild)) { this.buffer = this.concatenateAudioBuffers(this.tmpBuild, this.tmpBuffer);
if(song.buildup) { this.loopStart = this.tmpBuild.duration;
this.buffer = this.concatenateAudioBuffers(this.tmpBuild, this.tmpBuffer); } else {
this.loopStart = this.tmpBuild.duration; this.buffer = this.tmpBuffer;
} else { this.loopStart = 0;
this.buffer = this.tmpBuffer; }
this.loopStart = 0; this.loopLength = this.buffer.duration - this.loopStart;
} // free dat memory
this.loopLength = this.buffer.duration - this.loopStart; this.tmpBuild = this.tmpBuffer = null;
// free dat memory if(this.onLoadCallback) {
this.tmpBuild = this.tmpBuffer = null; this.onLoadCallback();
if(this.onLoadCallback) { this.onLoadCallback = null;
this.onLoadCallback();
this.onLoadCallback = null;
}
} }
}; };

@ -1,5 +1,50 @@
importScripts('aurora.js', 'mp3.js'); importScripts('aurora.js', 'mp3.js');
var decodeBuffer = function(source, callback) {
var asset = AV.Asset.fromBuffer(source);
asset.on("error", function(err) {
console.log(err);
});
asset.decodeToBuffer(function(buffer) {
var result = {array: buffer,
sampleRate: asset.format.sampleRate,
channels: asset.format.channelsPerFrame}
callback(result);
});
}
var finish = function(result, transferrables) {
transferrables.push(result.loop.array.buffer);
if(result.song.buildup) {
transferrables.push(result.build.array.buffer);
transferrables.push(result.song.buildup);
}
self.postMessage(result, transferrables);
}
self.addEventListener('message', function(e) { self.addEventListener('message', function(e) {
self.postMessage(e.data); var song = e.data;
var result = {song: song, build: null, loop: null};
var transferrables = [result.song.sound];
if(song.buildup) {
decodeBuffer(song.buildup, function(sound) {
result.build = sound;
// Song is finished too
if(result.loop) {
finish(result, transferrables);
}
});
}
decodeBuffer(song.sound, function(sound) {
result.loop = sound;
// Either there was no build, or it's already loaded
if(!song.buildup || (song.buildup && result.build)) {
finish(result, transferrables);
}
});
}, false); }, false);
Loading…
Cancel
Save