playback for electron

This commit is contained in:
Alexandre Storelli
2018-11-12 21:49:11 +01:00
parent 6b5ab72756
commit ecf506fd91
3 changed files with 192 additions and 2 deletions
+103
View File
@@ -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;
+12
View File
@@ -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
View File
@@ -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() {