From 4d65080a860624e04e06b02c4cc8fae290ae4dcd Mon Sep 17 00:00:00 2001 From: thelamer Date: Sun, 12 Feb 2023 16:13:31 -0800 Subject: [PATCH] add basic audio support with some bugs --- index.js | 31 ++++++++++++++++++++++++++++++- package.json | 3 ++- public/css/kclient.css | 4 ++++ public/css/speaker.svg | 5 +++++ public/index.html | 5 ++++- public/js/kclient.js | 33 +++++++++++++++++++++++++++++++++ public/js/pcm-player.min.js | 1 + 7 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 public/css/speaker.svg create mode 100644 public/js/pcm-player.min.js diff --git a/index.js b/index.js index 62c7b4e..3082dd6 100644 --- a/index.js +++ b/index.js @@ -17,7 +17,8 @@ var bodyParser = require('body-parser'); var baseRouter = express.Router(); var fsw = require('fs').promises; var fs = require('fs'); - +var PulseAudio = require('pulseaudio2'); +var pulse = new PulseAudio(); //// Server Paths Main //// app.engine('html', require('ejs').renderFile); @@ -130,6 +131,34 @@ io.on('connection', async function (socket) { socket.on('createfolder', createFolder); }); +//// PCM Audio Wrapper //// +aio = socketIO(http, {path: SUBFOLDER + 'audio/socket.io'}); +aio.on('connection', async function (socket) { + var record; + let id = socket.id; + + async function open() { + if (record) record.end(); + record = pulse.createRecordStream({ + channels: 2, + rate: 48000, + format: 'S16LE', + }); + record.on('connection', function(){ + record.on('data', function(chunk) { + aio.sockets.to(id).emit('audio', chunk); + }); + }); + } + async function close() { + if (record) record.end(); + } + + + // Incoming socket requests + socket.on('open', open); + socket.on('close', close); +}); // Spin up application on 6900 app.use(SUBFOLDER, baseRouter); diff --git a/package.json b/package.json index cac04fe..cb9b067 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { "name": "kclient", - "version": "0.1.0", + "version": "0.2.0", "description": "Kclient is a wrapper for KasmVNC to add functionality to a containerized environment", "main": "index.js", "dependencies": { "ejs": "^3.1.8", "express": "^4.18.2", + "pulseaudio2": "^0.5.5", "socket.io": "^4.6.0" }, "devDependencies": {}, diff --git a/public/css/kclient.css b/public/css/kclient.css index d564244..be69ee3 100644 --- a/public/css/kclient.css +++ b/public/css/kclient.css @@ -69,6 +69,10 @@ filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(82deg) brightness(105%) contrast(105%); } +.icons-selected { + background: rgba(0, 0, 0, 0.3); +} + .icons:hover { background: rgba(0, 0, 0, 0.3); } diff --git a/public/css/speaker.svg b/public/css/speaker.svg new file mode 100644 index 0000000..c355f43 --- /dev/null +++ b/public/css/speaker.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/index.html b/public/index.html index 81120e4..127cbd8 100644 --- a/public/index.html +++ b/public/index.html @@ -9,13 +9,16 @@ + +
- + +
diff --git a/public/js/kclient.js b/public/js/kclient.js index 330ecd3..b027f32 100644 --- a/public/js/kclient.js +++ b/public/js/kclient.js @@ -58,3 +58,36 @@ function fullscreen() { } } } + +// Websocket comms for audio +var host = window.location.hostname; +var port = window.location.port; +var protocol = window.location.protocol; +var path = window.location.pathname; +var socket = io(protocol + '//' + host + ':' + port, { path: path + 'audio/socket.io'}); +var player = {}; + +function audio() { + if (('audioCtx' in player) && (player.audioCtx)) { + player.destroy(); + socket.emit('close', ''); + $('#audioButton').removeClass("icons-selected"); + return; + } + socket.emit('open', ''); + player = new PCMPlayer({ + encoding: '16bitInt', + channels: 2, + sampleRate: 48000, + flushingTime: 10 + }); + $('#audioButton').addClass("icons-selected"); +} + +function processAudio(data) { + let buf = new Uint16Array(data); + player.feed(buf); + player.volume(1) ; +} + +socket.on('audio', processAudio); diff --git a/public/js/pcm-player.min.js b/public/js/pcm-player.min.js new file mode 100644 index 0000000..42c3362 --- /dev/null +++ b/public/js/pcm-player.min.js @@ -0,0 +1 @@ +function PCMPlayer(t){this.init(t)}PCMPlayer.prototype.init=function(t){this.option=Object.assign({},{encoding:"16bitInt",channels:1,sampleRate:8e3,flushingTime:1e3},t),this.samples=new Float32Array,this.flush=this.flush.bind(this),this.interval=setInterval(this.flush,this.option.flushingTime),this.maxValue=this.getMaxValue(),this.typedArray=this.getTypedArray(),this.createContext()},PCMPlayer.prototype.getMaxValue=function(){var t={"8bitInt":128,"16bitInt":32768,"32bitInt":2147483648,"32bitFloat":1};return t[this.option.encoding]?t[this.option.encoding]:t["16bitInt"]},PCMPlayer.prototype.getTypedArray=function(){var t={"8bitInt":Int8Array,"16bitInt":Int16Array,"32bitInt":Int32Array,"32bitFloat":Float32Array};return t[this.option.encoding]?t[this.option.encoding]:t["16bitInt"]},PCMPlayer.prototype.createContext=function(){this.audioCtx=new(window.AudioContext||window.webkitAudioContext),this.gainNode=this.audioCtx.createGain(),this.gainNode.gain.value=1,this.gainNode.connect(this.audioCtx.destination),this.startTime=this.audioCtx.currentTime},PCMPlayer.prototype.isTypedArray=function(t){return t.byteLength&&t.buffer&&t.buffer.constructor==ArrayBuffer},PCMPlayer.prototype.feed=function(t){if(this.isTypedArray(t)){t=this.getFormatedValue(t);var e=new Float32Array(this.samples.length+t.length);e.set(this.samples,0),e.set(t,this.samples.length),this.samples=e}},PCMPlayer.prototype.getFormatedValue=function(t){t=new this.typedArray(t.buffer);var e,i=new Float32Array(t.length);for(e=0;e