refactor audio startup so it will autostart on firefox, if allowed
This commit is contained in:
parent
6aa25760c5
commit
9e41d49d46
@ -10,9 +10,16 @@ function AudioEngine(maxBufferLength, audioReporter) {
|
|||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.audioContext = new ctx();
|
|
||||||
this.allowed = this.audioContext.state === 'running';
|
this.onStartCallbacks = [];
|
||||||
|
|
||||||
this.started = false;
|
this.started = false;
|
||||||
|
this.audioContext = new ctx();
|
||||||
|
var me = this;
|
||||||
|
this.audioContext.onstatechange = function() {
|
||||||
|
if (me.audioContext.state !== 'running') return;
|
||||||
|
me._start();
|
||||||
|
}
|
||||||
|
|
||||||
this.audioCodec = new ImaAdpcmCodec();
|
this.audioCodec = new ImaAdpcmCodec();
|
||||||
this.compression = 'none';
|
this.compression = 'none';
|
||||||
@ -24,112 +31,130 @@ function AudioEngine(maxBufferLength, audioReporter) {
|
|||||||
this.maxBufferSize = maxBufferLength * this.getSampleRate();
|
this.maxBufferSize = maxBufferLength * this.getSampleRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioEngine.prototype.start = function(callback) {
|
AudioEngine.prototype.resume = function(){
|
||||||
|
this.audioContext.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioEngine.prototype._start = function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
if (me.resamplingFactor === 0) return; //if failed to find a valid resampling factor...
|
|
||||||
|
// if failed to find a valid resampling factor...
|
||||||
|
if (me.resamplingFactor === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// been started before?
|
||||||
if (me.started) {
|
if (me.started) {
|
||||||
if (callback) callback(false);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
me.audioContext.resume().then(function(){
|
// are we allowed to play audio?
|
||||||
me.allowed = me.audioContext.state === 'running';
|
if (!me.isAllowed()) {
|
||||||
if (!me.allowed) {
|
return;
|
||||||
if (callback) callback(false);
|
}
|
||||||
return;
|
me.started = true;
|
||||||
}
|
|
||||||
me.started = true;
|
|
||||||
|
|
||||||
me.gainNode = me.audioContext.createGain();
|
me.gainNode = me.audioContext.createGain();
|
||||||
me.gainNode.connect(me.audioContext.destination);
|
me.gainNode.connect(me.audioContext.destination);
|
||||||
|
|
||||||
if (useAudioWorklets && me.audioContext.audioWorklet) {
|
if (useAudioWorklets && me.audioContext.audioWorklet) {
|
||||||
me.audioContext.audioWorklet.addModule('static/lib/AudioProcessor.js').then(function(){
|
me.audioContext.audioWorklet.addModule('static/lib/AudioProcessor.js').then(function(){
|
||||||
me.audioNode = new AudioWorkletNode(me.audioContext, 'openwebrx-audio-processor', {
|
me.audioNode = new AudioWorkletNode(me.audioContext, 'openwebrx-audio-processor', {
|
||||||
numberOfInputs: 0,
|
numberOfInputs: 0,
|
||||||
numberOfOutputs: 1,
|
numberOfOutputs: 1,
|
||||||
outputChannelCount: [1],
|
outputChannelCount: [1],
|
||||||
processorOptions: {
|
processorOptions: {
|
||||||
maxBufferSize: me.maxBufferSize
|
maxBufferSize: me.maxBufferSize
|
||||||
}
|
}
|
||||||
});
|
|
||||||
me.audioNode.connect(me.gainNode);
|
|
||||||
me.audioNode.port.addEventListener('message', function(m){
|
|
||||||
var json = JSON.parse(m.data);
|
|
||||||
if (typeof(json.buffersize) !== 'undefined') {
|
|
||||||
me.audioReporter({
|
|
||||||
buffersize: json.buffersize
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (typeof(json.samplesProcessed) !== 'undefined') {
|
|
||||||
me.audioSamples.add(json.samplesProcessed);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
me.audioNode.port.start();
|
|
||||||
if (callback) callback(true, 'AudioWorklet');
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
me.audioBuffers = [];
|
|
||||||
|
|
||||||
if (!AudioBuffer.prototype.copyToChannel) { //Chrome 36 does not have it, Firefox does
|
|
||||||
AudioBuffer.prototype.copyToChannel = function (input, channel) //input is Float32Array
|
|
||||||
{
|
|
||||||
var cd = this.getChannelData(channel);
|
|
||||||
for (var i = 0; i < input.length; i++) cd[i] = input[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bufferSize;
|
|
||||||
if (me.audioContext.sampleRate < 44100 * 2)
|
|
||||||
bufferSize = 4096;
|
|
||||||
else if (me.audioContext.sampleRate >= 44100 * 2 && me.audioContext.sampleRate < 44100 * 4)
|
|
||||||
bufferSize = 4096 * 2;
|
|
||||||
else if (me.audioContext.sampleRate > 44100 * 4)
|
|
||||||
bufferSize = 4096 * 4;
|
|
||||||
|
|
||||||
|
|
||||||
function audio_onprocess(e) {
|
|
||||||
var total = 0;
|
|
||||||
var out = new Float32Array(bufferSize);
|
|
||||||
while (me.audioBuffers.length) {
|
|
||||||
var b = me.audioBuffers.shift();
|
|
||||||
// not enough space to fit all data, so splice and put back in the queue
|
|
||||||
if (total + b.length > bufferSize) {
|
|
||||||
var spaceLeft = bufferSize - total;
|
|
||||||
var tokeep = b.subarray(0, spaceLeft);
|
|
||||||
out.set(tokeep, total);
|
|
||||||
var tobuffer = b.subarray(spaceLeft, b.length);
|
|
||||||
me.audioBuffers.unshift(tobuffer);
|
|
||||||
total += spaceLeft;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
out.set(b, total);
|
|
||||||
total += b.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
e.outputBuffer.copyToChannel(out, 0);
|
|
||||||
me.audioSamples.add(total);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//on Chrome v36, createJavaScriptNode has been replaced by createScriptProcessor
|
|
||||||
var method = 'createScriptProcessor';
|
|
||||||
if (me.audioContext.createJavaScriptNode) {
|
|
||||||
method = 'createJavaScriptNode';
|
|
||||||
}
|
|
||||||
me.audioNode = me.audioContext[method](bufferSize, 0, 1);
|
|
||||||
me.audioNode.onaudioprocess = audio_onprocess;
|
|
||||||
me.audioNode.connect(me.gainNode);
|
me.audioNode.connect(me.gainNode);
|
||||||
if (callback) callback(true, 'ScriptProcessorNode');
|
me.audioNode.port.addEventListener('message', function(m){
|
||||||
|
var json = JSON.parse(m.data);
|
||||||
|
if (typeof(json.buffersize) !== 'undefined') {
|
||||||
|
me.audioReporter({
|
||||||
|
buffersize: json.buffersize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (typeof(json.samplesProcessed) !== 'undefined') {
|
||||||
|
me.audioSamples.add(json.samplesProcessed);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
me.audioNode.port.start();
|
||||||
|
me.workletType = 'AudioWorklet';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
me.audioBuffers = [];
|
||||||
|
|
||||||
|
if (!AudioBuffer.prototype.copyToChannel) { //Chrome 36 does not have it, Firefox does
|
||||||
|
AudioBuffer.prototype.copyToChannel = function (input, channel) //input is Float32Array
|
||||||
|
{
|
||||||
|
var cd = this.getChannelData(channel);
|
||||||
|
for (var i = 0; i < input.length; i++) cd[i] = input[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setInterval(me.reportStats.bind(me), 1000);
|
var bufferSize;
|
||||||
});
|
if (me.audioContext.sampleRate < 44100 * 2)
|
||||||
|
bufferSize = 4096;
|
||||||
|
else if (me.audioContext.sampleRate >= 44100 * 2 && me.audioContext.sampleRate < 44100 * 4)
|
||||||
|
bufferSize = 4096 * 2;
|
||||||
|
else if (me.audioContext.sampleRate > 44100 * 4)
|
||||||
|
bufferSize = 4096 * 4;
|
||||||
|
|
||||||
|
|
||||||
|
function audio_onprocess(e) {
|
||||||
|
var total = 0;
|
||||||
|
var out = new Float32Array(bufferSize);
|
||||||
|
while (me.audioBuffers.length) {
|
||||||
|
var b = me.audioBuffers.shift();
|
||||||
|
// not enough space to fit all data, so splice and put back in the queue
|
||||||
|
if (total + b.length > bufferSize) {
|
||||||
|
var spaceLeft = bufferSize - total;
|
||||||
|
var tokeep = b.subarray(0, spaceLeft);
|
||||||
|
out.set(tokeep, total);
|
||||||
|
var tobuffer = b.subarray(spaceLeft, b.length);
|
||||||
|
me.audioBuffers.unshift(tobuffer);
|
||||||
|
total += spaceLeft;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
out.set(b, total);
|
||||||
|
total += b.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.outputBuffer.copyToChannel(out, 0);
|
||||||
|
me.audioSamples.add(total);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//on Chrome v36, createJavaScriptNode has been replaced by createScriptProcessor
|
||||||
|
var method = 'createScriptProcessor';
|
||||||
|
if (me.audioContext.createJavaScriptNode) {
|
||||||
|
method = 'createJavaScriptNode';
|
||||||
|
}
|
||||||
|
me.audioNode = me.audioContext[method](bufferSize, 0, 1);
|
||||||
|
me.audioNode.onaudioprocess = audio_onprocess;
|
||||||
|
me.audioNode.connect(me.gainNode);
|
||||||
|
me.workletType = 'ScriptProcessorNode';
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(me.reportStats.bind(me), 1000);
|
||||||
|
|
||||||
|
var callbacks = this.onStartCallbacks;
|
||||||
|
this.onStartCallbacks = false;
|
||||||
|
callbacks.forEach(function(c) { c(me.workletType); });
|
||||||
|
};
|
||||||
|
|
||||||
|
AudioEngine.prototype.onStart = function(callback) {
|
||||||
|
if (this.onStartCallbacks) {
|
||||||
|
this.onStartCallbacks.push(callback);
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
AudioEngine.prototype.isAllowed = function() {
|
AudioEngine.prototype.isAllowed = function() {
|
||||||
return this.allowed;
|
return this.audioContext.state === 'running';
|
||||||
};
|
};
|
||||||
|
|
||||||
AudioEngine.prototype.reportStats = function() {
|
AudioEngine.prototype.reportStats = function() {
|
||||||
|
@ -1099,9 +1099,11 @@ var mute = false;
|
|||||||
// Optimalise these if audio lags or is choppy:
|
// Optimalise these if audio lags or is choppy:
|
||||||
var audio_buffer_maximal_length_sec = 1; //actual number of samples are calculated from sample rate
|
var audio_buffer_maximal_length_sec = 1; //actual number of samples are calculated from sample rate
|
||||||
|
|
||||||
function onAudioStart(success, apiType){
|
function onAudioStart(apiType){
|
||||||
divlog('Web Audio API succesfully initialized, using ' + apiType + ' API, sample rate: ' + audioEngine.getSampleRate() + " Hz");
|
divlog('Web Audio API succesfully initialized, using ' + apiType + ' API, sample rate: ' + audioEngine.getSampleRate() + " Hz");
|
||||||
|
|
||||||
|
hideOverlay();
|
||||||
|
|
||||||
// canvas_container is set after waterfall_init() has been called. we cannot initialize before.
|
// canvas_container is set after waterfall_init() has been called. we cannot initialize before.
|
||||||
//if (canvas_container) synchronize_demodulator_init();
|
//if (canvas_container) synchronize_demodulator_init();
|
||||||
|
|
||||||
@ -1320,11 +1322,12 @@ var audioEngine;
|
|||||||
function openwebrx_init() {
|
function openwebrx_init() {
|
||||||
audioEngine = new AudioEngine(audio_buffer_maximal_length_sec, audioReporter);
|
audioEngine = new AudioEngine(audio_buffer_maximal_length_sec, audioReporter);
|
||||||
$overlay = $('#openwebrx-autoplay-overlay');
|
$overlay = $('#openwebrx-autoplay-overlay');
|
||||||
$overlay.on('click', playButtonClick);
|
$overlay.on('click', function(){
|
||||||
|
audioEngine.resume();
|
||||||
|
});
|
||||||
|
audioEngine.onStart(onAudioStart);
|
||||||
if (!audioEngine.isAllowed()) {
|
if (!audioEngine.isAllowed()) {
|
||||||
$overlay.show();
|
$overlay.show();
|
||||||
} else {
|
|
||||||
audioEngine.start(onAudioStart);
|
|
||||||
}
|
}
|
||||||
fft_codec = new ImaAdpcmCodec();
|
fft_codec = new ImaAdpcmCodec();
|
||||||
initProgressBars();
|
initProgressBars();
|
||||||
@ -1370,9 +1373,7 @@ function update_dmr_timeslot_filtering() {
|
|||||||
$('#openwebrx-panel-receiver').demodulatorPanel().getDemodulator().setDmrFilter(filter);
|
$('#openwebrx-panel-receiver').demodulatorPanel().getDemodulator().setDmrFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
function playButtonClick() {
|
function hideOverlay() {
|
||||||
//On iOS, we can only start audio from a click or touch event.
|
|
||||||
audioEngine.start(onAudioStart);
|
|
||||||
var $overlay = $('#openwebrx-autoplay-overlay');
|
var $overlay = $('#openwebrx-autoplay-overlay');
|
||||||
$overlay.css('opacity', 0);
|
$overlay.css('opacity', 0);
|
||||||
$overlay.on('transitionend', function() {
|
$overlay.on('transitionend', function() {
|
||||||
|
Loading…
Reference in New Issue
Block a user