playback for electron
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
const { config, getRadio } = require('../handlers/config');
|
||||
const cp = require("child_process");
|
||||
const { log } = require("abr-log")("listen-electron");
|
||||
|
||||
let transcoder, listenTimer;
|
||||
|
||||
// play audio locally
|
||||
function play(radio, delay, playToken, onData) {
|
||||
try {
|
||||
log.info("play " + radio + " at delay " + delay);
|
||||
|
||||
const radioObj = getRadio(...radio.split("_"));
|
||||
if (!radioObj) {
|
||||
return "radio not found";
|
||||
}
|
||||
|
||||
//let skipPcmBytes = Math.max(config.user.streamInitialBuffer - 0.5) * 44100 * 2 * 2; // 44100 Hz, stereo, 16 bit.
|
||||
|
||||
var initialBuffer = radioObj.liveStatus.audioCache.readLast(+delay+config.user.streamInitialBuffer,config.user.streamInitialBuffer);
|
||||
//log.debug("listen: readCursor set to " + radioObj.liveStatus.audioCache.readCursor);
|
||||
|
||||
if (!initialBuffer) {
|
||||
log.error("/listen/" + radio + "/" + delay + ": initialBuffer not available");
|
||||
return "buffer not available";
|
||||
}
|
||||
|
||||
stop(); // shut down previous trancoder
|
||||
|
||||
transcoder.stdout.on("data", function(data) {
|
||||
onData(playToken, data);
|
||||
});
|
||||
|
||||
log.info("listen: send initial buffer of " + initialBuffer.length + " bytes");
|
||||
|
||||
setImmediate(function() {
|
||||
transcoder.stdin.write(initialBuffer);
|
||||
});
|
||||
|
||||
var sendMore = function() {
|
||||
/*if (listenRequestDate !== state.requestDate) {
|
||||
log.warn("request canceled because another one has been initiated");
|
||||
return stop();
|
||||
}*/
|
||||
var radioObj = getRadio(...radio.split("_"));
|
||||
if (!radioObj) {
|
||||
log.error("/listen/" + radio + "/" + delay + ": radio not available");
|
||||
return stop();
|
||||
}
|
||||
var audioCache = radioObj.liveStatus.audioCache;
|
||||
if (!audioCache) {
|
||||
log.error("/listen/" + radio + "/" + delay + ": audioCache not available");
|
||||
return stop();
|
||||
}
|
||||
//var prevReadCursor = audioCache.readCursor;
|
||||
transcoder.stdin.write(audioCache.readAmountAfterCursor(config.user.streamGranularity));
|
||||
//onData(audioCache.readAmountAfterCursor(config.user.streamGranularity));
|
||||
//log.debug("listen: readCursor=" + audioCache.readCursor);
|
||||
}
|
||||
|
||||
listenTimer = setInterval(sendMore, 1000*config.user.streamGranularity);
|
||||
|
||||
} catch(e) {
|
||||
log.error("play error=" + e);
|
||||
log.error(e.stack);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function newTranscoder() {
|
||||
log.debug("new transcoder");
|
||||
transcoder = cp.spawn('ffmpeg', [
|
||||
'-i', 'pipe:0',
|
||||
'-acodec', 'pcm_s16le',
|
||||
'-ar', 44100,
|
||||
'-ac', 2,
|
||||
'-f', 'wav',
|
||||
'-v', 'fatal',
|
||||
'pipe:1'
|
||||
], { stdio: ['pipe', 'pipe', process.stderr] });
|
||||
/*log.debug("stdin hWM: " + transcoder.stdin._writableState.highWaterMark);
|
||||
transcoder.stdin._writableState.highWaterMark = 1024;
|
||||
log.debug("stdin hWM: " + transcoder.stdin._writableState.highWaterMark);
|
||||
log.debug("stdout hWM: " + transcoder.stdout._readableState.highWaterMark);
|
||||
transcoder.stdout._readableState.highWaterMark = 1024;
|
||||
log.debug("stdout hWM: " + transcoder.stdout._readableState.highWaterMark);*/
|
||||
}
|
||||
|
||||
newTranscoder();
|
||||
|
||||
function stop() {
|
||||
log.debug("stop");
|
||||
if (listenTimer) {
|
||||
clearInterval(listenTimer);
|
||||
if (transcoder && transcoder.stdin) {
|
||||
transcoder.stdin.end();
|
||||
transcoder.kill();
|
||||
newTranscoder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.play = play;
|
||||
exports.stop = stop;
|
||||
@@ -13,6 +13,18 @@
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<script type="text/javascript">
|
||||
const isElectron = navigator.userAgent.toLowerCase().indexOf(' electron/') > -1; // in a Electron environment (https://github.com/electron/electron/issues/2288)
|
||||
const isCordovaApp = document.URL.indexOf('http://') === -1 && document.URL.indexOf('https://') === -1 && !isElectron;
|
||||
|
||||
if (isElectron) {
|
||||
console.log("detected Electron environment");
|
||||
//require("../../index.js"); // start main process for stream analysis
|
||||
//require("adblockradio-buffer-server");
|
||||
navigator.abrserver = require("electron").remote.require("../adblockradio-buffer");
|
||||
navigator.abrlisten = require("electron").remote.require("../adblockradio-buffer/api/listen-electron");
|
||||
}
|
||||
</script>
|
||||
<div id="root" style="display: flex;"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+77
-2
@@ -2,11 +2,85 @@
|
||||
/* global Media */
|
||||
/* global Android */
|
||||
|
||||
var isCordovaApp = document.URL.indexOf('http://') === -1 && document.URL.indexOf('https://') === -1;
|
||||
const isElectron = navigator.userAgent.toLowerCase().indexOf(' electron/') > -1;
|
||||
const isCordovaApp = document.URL.indexOf('http://') === -1 && document.URL.indexOf('https://') === -1 && !isElectron;
|
||||
|
||||
var audioElement, play, stop, setVolume;
|
||||
|
||||
if (isCordovaApp) {
|
||||
if (isElectron) {
|
||||
console.log("listen: detected Electron env");
|
||||
|
||||
let audioCtx, gainNode;
|
||||
let source;
|
||||
let startTime; //, startPlayback;
|
||||
|
||||
function newContext() {
|
||||
console.log("new context");
|
||||
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
gainNode = audioCtx.createGain();
|
||||
gainNode.gain.value = 0.5;
|
||||
gainNode.connect(audioCtx.destination);
|
||||
}
|
||||
|
||||
newContext();
|
||||
|
||||
play = function(url, callback) {
|
||||
if (startTime) {
|
||||
stop();
|
||||
return setTimeout(function() {
|
||||
play(url, callback);
|
||||
}, 50);
|
||||
}
|
||||
startTime = +new Date();
|
||||
let nextStartTime = null; //audioCtx.currentTime;
|
||||
//audioElement = document.createElement('audio');
|
||||
const spl = decodeURIComponent(url).split('?')[0].split("/"); // assuming following URL format: "listen/" + encodeURIComponent(radio) + "/" + (delay/1000)
|
||||
navigator.abrlisten.play(spl[1], spl[2], startTime, function(receivedStartTime, PCMAudioChunk) {
|
||||
if (receivedStartTime !== startTime) {
|
||||
console.log('received obsolete PCM chunk');
|
||||
return;
|
||||
}
|
||||
if (!nextStartTime) {
|
||||
console.log('start playback');
|
||||
nextStartTime = audioCtx.currentTime;
|
||||
}
|
||||
//if (!startPlayback) startPlayback = new Date();
|
||||
const PCMAudioChunk2 = Int8Array.from(PCMAudioChunk); //);
|
||||
const frames = PCMAudioChunk2.byteLength / 4;
|
||||
//console.log(frames / 44100 + " s => cursor = " + nextStartTime + " buffer=" + (nextStartTime - ((+new Date() - startPlayback)/1000) + " s"));
|
||||
const arrayBuffer = audioCtx.createBuffer(2, frames, 44100);
|
||||
|
||||
const nowBufferingL = arrayBuffer.getChannelData(0);
|
||||
const nowBufferingR = arrayBuffer.getChannelData(1);
|
||||
for (var i = 0; i < frames; i++) {
|
||||
nowBufferingL[i] = (PCMAudioChunk2[4*i] + 256 * PCMAudioChunk2[4*i + 1]) / 32768;
|
||||
nowBufferingR[i] = (PCMAudioChunk2[4*i + 2] + 256 * PCMAudioChunk2[4*i + 3]) / 32768;
|
||||
}
|
||||
|
||||
source = audioCtx.createBufferSource();
|
||||
source.buffer = arrayBuffer;
|
||||
source.connect(gainNode);
|
||||
source.start(nextStartTime);
|
||||
nextStartTime += frames / 44100;
|
||||
});
|
||||
}
|
||||
|
||||
stop = function() {
|
||||
console.log("playback stop");
|
||||
navigator.abrlisten.stop();
|
||||
//source.stop(audioCtx.currentTime);//disconnect(gainNode);
|
||||
audioCtx.close();
|
||||
startTime = null;
|
||||
newContext();
|
||||
//setVolume(0);
|
||||
}
|
||||
|
||||
setVolume = function(vol) {
|
||||
console.log("set volume = " + vol);
|
||||
gainNode.gain.value = vol;
|
||||
}
|
||||
|
||||
} else if (isCordovaApp) {
|
||||
play = function(url, callback) {
|
||||
if (audioElement && audioElement.stop) audioElement.stop();
|
||||
audioElement = new Media(url, function() {
|
||||
@@ -17,6 +91,7 @@ if (isCordovaApp) {
|
||||
console.log("stream status=" + status);
|
||||
});
|
||||
audioElement.play();
|
||||
if (callback) setImmediate(callback);
|
||||
}
|
||||
|
||||
stop = function() {
|
||||
|
||||
Reference in New Issue
Block a user