jsmpeg组件包

This commit is contained in:
龚皓
2024-10-18 11:04:03 +08:00
parent 8a31a31d93
commit 2c17b8f9d8
30 changed files with 8128 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
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;
})();