| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- 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;
-
- })();
-
|