146 lines
3.8 KiB
JavaScript
146 lines
3.8 KiB
JavaScript
JSMpeg.AudioOutput.WebAudio = (function() { "use strict";
|
|
|
|
var WebAudioOut = function(options) {
|
|
this.context = WebAudioOut.CachedContext =
|
|
WebAudioOut.CachedContext ||
|
|
new (window.AudioContext || window.webkitAudioContext)();
|
|
|
|
this.gain = this.context.createGain();
|
|
this.destination = this.gain;
|
|
|
|
// Keep track of the number of connections to this AudioContext, so we
|
|
// can safely close() it when we're the only one connected to it.
|
|
this.gain.connect(this.context.destination);
|
|
this.context._connections = (this.context._connections || 0) + 1;
|
|
|
|
this.startTime = 0;
|
|
this.buffer = null;
|
|
this.wallclockStartTime = 0;
|
|
this.volume = 1;
|
|
this.enabled = true;
|
|
|
|
this.unlocked = !WebAudioOut.NeedsUnlocking();
|
|
|
|
Object.defineProperty(this, 'enqueuedTime', {get: this.getEnqueuedTime});
|
|
};
|
|
|
|
WebAudioOut.prototype.destroy = function() {
|
|
this.gain.disconnect();
|
|
this.context._connections--;
|
|
|
|
if (this.context._connections === 0) {
|
|
this.context.close();
|
|
WebAudioOut.CachedContext = null;
|
|
}
|
|
};
|
|
|
|
WebAudioOut.prototype.play = function(sampleRate, left, right) {
|
|
if (!this.enabled) {
|
|
return;
|
|
}
|
|
|
|
// If the context is not unlocked yet, we simply advance the start time
|
|
// to "fake" actually playing audio. This will keep the video in sync.
|
|
if (!this.unlocked) {
|
|
var ts = JSMpeg.Now()
|
|
if (this.wallclockStartTime < ts) {
|
|
this.wallclockStartTime = ts;
|
|
}
|
|
this.wallclockStartTime += left.length / sampleRate;
|
|
return;
|
|
}
|
|
|
|
|
|
this.gain.gain.value = this.volume;
|
|
|
|
var buffer = this.context.createBuffer(2, left.length, sampleRate);
|
|
buffer.getChannelData(0).set(left);
|
|
buffer.getChannelData(1).set(right);
|
|
|
|
var source = this.context.createBufferSource();
|
|
source.buffer = buffer;
|
|
source.connect(this.destination);
|
|
|
|
var now = this.context.currentTime;
|
|
var duration = buffer.duration;
|
|
if (this.startTime < now) {
|
|
this.startTime = now;
|
|
this.wallclockStartTime = JSMpeg.Now();
|
|
}
|
|
|
|
source.start(this.startTime);
|
|
this.startTime += duration;
|
|
this.wallclockStartTime += duration;
|
|
};
|
|
|
|
WebAudioOut.prototype.stop = function() {
|
|
// Meh; there seems to be no simple way to get a list of currently
|
|
// active source nodes from the Audio Context, and maintaining this
|
|
// list ourselfs would be a pain, so we just set the gain to 0
|
|
// to cut off all enqueued audio instantly.
|
|
this.gain.gain.value = 0;
|
|
};
|
|
|
|
WebAudioOut.prototype.getEnqueuedTime = function() {
|
|
// The AudioContext.currentTime is only updated every so often, so if we
|
|
// want to get exact timing, we need to rely on the system time.
|
|
return Math.max(this.wallclockStartTime - JSMpeg.Now(), 0)
|
|
};
|
|
|
|
WebAudioOut.prototype.resetEnqueuedTime = function() {
|
|
this.startTime = this.context.currentTime;
|
|
this.wallclockStartTime = JSMpeg.Now();
|
|
};
|
|
|
|
WebAudioOut.prototype.unlock = function(callback) {
|
|
if (this.unlocked) {
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
return;
|
|
}
|
|
|
|
this.unlockCallback = callback;
|
|
|
|
// Create empty buffer and play it
|
|
var buffer = this.context.createBuffer(1, 1, 22050);
|
|
var source = this.context.createBufferSource();
|
|
source.buffer = buffer;
|
|
source.connect(this.destination);
|
|
source.start(0);
|
|
|
|
setTimeout(this.checkIfUnlocked.bind(this, source, 0), 0);
|
|
};
|
|
|
|
WebAudioOut.prototype.checkIfUnlocked = function(source, attempt) {
|
|
if (
|
|
source.playbackState === source.PLAYING_STATE ||
|
|
source.playbackState === source.FINISHED_STATE
|
|
) {
|
|
this.unlocked = true;
|
|
if (this.unlockCallback) {
|
|
this.unlockCallback();
|
|
this.unlockCallback = null;
|
|
}
|
|
}
|
|
else if (attempt < 10) {
|
|
// Jeez, what a shit show. Thanks iOS!
|
|
setTimeout(this.checkIfUnlocked.bind(this, source, attempt+1), 100);
|
|
}
|
|
};
|
|
|
|
WebAudioOut.NeedsUnlocking = function() {
|
|
return /iPhone|iPad|iPod/i.test(navigator.userAgent);
|
|
};
|
|
|
|
WebAudioOut.IsSupported = function() {
|
|
return (window.AudioContext || window.webkitAudioContext);
|
|
};
|
|
|
|
WebAudioOut.CachedContext = null;
|
|
|
|
return WebAudioOut;
|
|
|
|
})();
|
|
|