diff --git a/Dockerfile b/Dockerfile index 3a8c111..53b2109 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,11 +2,17 @@ FROM alpine:latest LABEL maintainer="Don " -RUN apk add --no-cache sudo git xfce4 faenza-icon-theme bash python3 tigervnc xfce4-terminal\ +RUN apk add --no-cache sudo git xfce4 faenza-icon-theme bash python3 tigervnc xfce4-terminal firefox cmake wget \ + pulseaudio xfce4-pulseaudio-plugin pavucontrol pulseaudio-alsa alsa-plugins-pulse alsa-lib-dev nodejs npm \ && adduser -h /home/alpine -s /bin/bash -S -D alpine && echo -e "alpine\nalpine" | passwd alpine \ && echo 'alpine ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ + && npm install audify \ + && npm install ws \ && git clone https://github.com/novnc/noVNC /opt/noVNC \ - && git clone https://github.com/novnc/websockify /opt/noVNC/utils/websockify + && git clone https://github.com/novnc/websockify /opt/noVNC/utils/websockify \ + && wget https://raw.githubusercontent.com/novaspirit/Alpine_xfce4_noVNC/dev/script.js -O /opt/noVNC/script.js \ + && wget https://raw.githubusercontent.com/novaspirit/Alpine_xfce4_noVNC/dev/audify.js -O /opt/noVNC/audify.js \ + && wget https://raw.githubusercontent.com/novaspirit/Alpine_xfce4_noVNC/dev/vnc.html -O /opt/noVNC/vnc.html \ USER alpine WORKDIR /home/alpine @@ -15,6 +21,13 @@ RUN mkdir -p /home/alpine/.vnc \ && echo -e "#!/bin/bash\nstartxfce4 &" > /home/alpine/.vnc/xstartup \ && echo -e "alpine\nalpine\nn\n" | vncpasswd -COPY entry.sh /entry.sh -CMD [ "/bin/bash", "/entry.sh" ] +RUN echo '\n\ +#!/bin/bash\n\ +/usr/bin/vncserver :99 2>&1 | sed "s/^/[Xtigervnc ] /" & \n\ +/usr/bin/pulseaudio -d & \n\ +/usr/bin/node /opt/noVNC/audify.js 2>&1 | sed "s/^/[audify ] /" & \n\ +/opt/noVNC/utils/novnc_proxy --vnc localhost:5999 2>&1 | sed "s/^/[noVNC ] /"\ +>/entry.sh + +ENTRYPOINT [ "/bin/bash", "/entry.sh" ] diff --git a/audify.js b/audify.js new file mode 100644 index 0000000..7d421db --- /dev/null +++ b/audify.js @@ -0,0 +1,47 @@ +//Audify audio : https://almoghamdani.github.io/audify/index.html +const WebSocket = require('ws') + +var wss = new WebSocket.Server({ + port: 56780 +}); + +console.log('Server ready...') +wss.on('connection', function connection(ws) { + console.log('Socket connected. sending data...') +}) + +const { + RtAudio, + RtAudioFormat, +} = require("audify") + +// Init RtAudio instance using default sound API +const rtAudio = new RtAudio() +rtAudio.outputVolume = 0 + +// Open the input/output stream +rtAudio.openStream({ + deviceId: rtAudio.getDefaultOutputDevice(), // Output device id (Get all devices using `getDevices`) + nChannels: 2, // Number of channels + firstChannel: 0 // First channel index on device (default = 0). + }, { + deviceId: rtAudio.getDefaultInputDevice(), // Input device id (Get all devices using `getDevices`) + nChannels: 2, // Number of channels + firstChannel: 0 // First channel index on device (default = 0). + }, + RtAudioFormat.RTAUDIO_SINT16, // PCM Format - Signed 16-bit integer + 48000, // Sampling rate is 44.1kHz + 480, // Frame size is 1920 (40ms) + "MyStream", // The name of the stream (used for JACK Api) + pcm => { + wss.clients.forEach(function each(client) { + if (client.readyState === WebSocket.OPEN) { + client.send(pcm) + } + }) + rtAudio.write(pcm) + } // Input callback function, write every input pcm data to the output buffer +) + +// Start the stream +rtAudio.start() diff --git a/entry.sh b/entry.sh deleted file mode 100644 index bb0724a..0000000 --- a/entry.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -/usr/bin/vncserver :99 & -/opt/noVNC/utils/novnc_proxy --vnc 127.0.0.1:5999 diff --git a/pcm-player.js b/pcm-player.js new file mode 100644 index 0000000..9da3f81 --- /dev/null +++ b/pcm-player.js @@ -0,0 +1,131 @@ +function PCMPlayer(option) { + this.init(option); +} + +PCMPlayer.prototype.init = function(option) { + var defaults = { + encoding: '16bitInt', + channels: 1, + sampleRate: 8000, + flushingTime: 1000 + }; + this.option = Object.assign({}, defaults, option); + 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 encodings = { + '8bitInt': 128, + '16bitInt': 32768, + '32bitInt': 2147483648, + '32bitFloat': 1 + } + + return encodings[this.option.encoding] ? encodings[this.option.encoding] : encodings['16bitInt']; +}; + +PCMPlayer.prototype.getTypedArray = function () { + var typedArrays = { + '8bitInt': Int8Array, + '16bitInt': Int16Array, + '32bitInt': Int32Array, + '32bitFloat': Float32Array + } + + return typedArrays[this.option.encoding] ? typedArrays[this.option.encoding] : typedArrays['16bitInt']; +}; + +PCMPlayer.prototype.createContext = function() { + this.audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + + // context needs to be resumed on iOS and Safari (or it will stay in "suspended" state) + this.audioCtx.resume(); + this.audioCtx.onstatechange = () => console.log(this.audioCtx.state); // if you want to see "Running" state in console and be happy about it + + 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(data) { + return (data.byteLength && data.buffer && data.buffer.constructor == ArrayBuffer); +}; + +PCMPlayer.prototype.feed = function(data) { + if (!this.isTypedArray(data)) return; + data = this.getFormatedValue(data); + var tmp = new Float32Array(this.samples.length + data.length); + tmp.set(this.samples, 0); + tmp.set(data, this.samples.length); + this.samples = tmp; +}; + +PCMPlayer.prototype.getFormatedValue = function(data) { + var data = new this.typedArray(data.buffer), + float32 = new Float32Array(data.length), + i; + + for (i = 0; i < data.length; i++) { + float32[i] = data[i] / this.maxValue; + } + return float32; +}; + +PCMPlayer.prototype.volume = function(volume) { + this.gainNode.gain.value = volume; +}; + +PCMPlayer.prototype.destroy = function() { + if (this.interval) { + clearInterval(this.interval); + } + this.samples = null; + this.audioCtx.close(); + this.audioCtx = null; +}; + +PCMPlayer.prototype.flush = function() { + if (!this.samples.length) return; + var bufferSource = this.audioCtx.createBufferSource(), + length = this.samples.length / this.option.channels, + audioBuffer = this.audioCtx.createBuffer(this.option.channels, length, this.option.sampleRate), + audioData, + channel, + offset, + i, + decrement; + + for (channel = 0; channel < this.option.channels; channel++) { + audioData = audioBuffer.getChannelData(channel); + offset = channel; + decrement = 50; + for (i = 0; i < length; i++) { + audioData[i] = this.samples[offset]; + /* fadein */ + if (i < 50) { + audioData[i] = (audioData[i] * i) / 50; + } + /* fadeout*/ + if (i >= (length - 51)) { + audioData[i] = (audioData[i] * decrement--) / 50; + } + offset += this.option.channels; + } + } + + if (this.startTime < this.audioCtx.currentTime) { + this.startTime = this.audioCtx.currentTime; + } + console.log('start vs current '+this.startTime+' vs '+this.audioCtx.currentTime+' duration: '+audioBuffer.duration); + bufferSource.buffer = audioBuffer; + bufferSource.connect(this.gainNode); + bufferSource.start(this.startTime); + this.startTime += audioBuffer.duration; + this.samples = new Float32Array(); +}; diff --git a/script.js b/script.js new file mode 100644 index 0000000..9980be2 --- /dev/null +++ b/script.js @@ -0,0 +1,19 @@ +//PCM Player : https://github.com/samirkumardas/pcm-player/blob/master/example/server/server.js + +window.onload = function () { + var socketURL = 'ws://' + window.location.hostname + ':8080' + var player = new PCMPlayer({ + encoding: '16bitInt', + channels: 2, + sampleRate: 48000, + flushingTime: 100 + }) + + var ws = new WebSocket(socketURL) + ws.binaryType = 'arraybuffer' + ws.addEventListener('message', function (event) { + var data = new Uint16Array(event.data) + player.feed(data) + player.volume(1) + }) +} diff --git a/vnc.html b/vnc.html new file mode 100644 index 0000000..9bc0f18 --- /dev/null +++ b/vnc.html @@ -0,0 +1,351 @@ + + + + + + noVNC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
noVNC encountered an error:
+
+
+
+
+ + +
+ +
+
+ +
+ +

no
VNC

+ +
+ + + + + +
+ +
+ + + +
+
+ + + + + + +
+
+ + + +
+
+
+ Power +
+ + + +
+
+ + + +
+
+
+ Clipboard +
+

+ Edit clipboard content in the textarea below. +

+ +
+
+ + + + + + +
+
+
+ Settings +
+
    +
  • + +
  • +
  • + +
  • +

  • +
  • + +
  • +
  • + + +
  • +

  • +
  • +
    Advanced
    +
      +
    • + + +
    • +
    • + + +
    • +

    • +
    • + + +
    • +
    • +
      WebSocket
      +
        +
      • + +
      • +
      • + + +
      • +
      • + + +
      • +
      • + + +
      • +
      +
    • +

    • +
    • + +
    • +
    • + + +
    • +

    • +
    • + +
    • +

    • + +
    • + +
    • +
    +
  • +

  • +
  • + Version: + +
  • +
+
+
+ + + + +
+
+ +
+ +
+
+
+
+ + +
+ + +
+
+ +
+ Connect +
+
+
+ + +
+
+
+ Server identity +
+
+ The server has provided the following identifying information: +
+
+ Fingerprint: + +
+
+ Please verify that the information is correct and press + "Approve". Otherwise press "Reject". +
+
+ + +
+
+
+ + +
+
+
+ Credentials +
+
+ + +
+
+ + +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+ + +
+ + +
+ + + + +