Initial commit
This commit is contained in:
commit
88a0749cd4
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
**/*
|
||||||
|
!.gitignore
|
||||||
|
|
||||||
|
!docker-compose.yaml
|
||||||
|
!novnc-audio-build
|
||||||
|
!novnc-audio-build/**
|
||||||
|
!musescore-psock-build
|
||||||
|
!musescore-psock-build/*
|
46
docker-compose.yaml
Normal file
46
docker-compose.yaml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
version: "3.0"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pulse_socket:
|
||||||
|
|
||||||
|
services:
|
||||||
|
#DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 docker-compose build musescore
|
||||||
|
musescore:
|
||||||
|
image: thomasloven/musescore-psock
|
||||||
|
build: ./musescore-psock-build/
|
||||||
|
depends_on:
|
||||||
|
- novnc
|
||||||
|
volumes:
|
||||||
|
- pulse_socket:/tmp/psock
|
||||||
|
- ./mscore:/root/Documents/MuseScore4
|
||||||
|
environment:
|
||||||
|
DISPLAY: novnc:0.0
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- /bin/sleep 3 && mscore
|
||||||
|
|
||||||
|
novnc:
|
||||||
|
image: thomasloven/novnc-audio
|
||||||
|
build: ./novnc-audio-build/
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
default:
|
||||||
|
volumes:
|
||||||
|
- pulse_socket:/tmp/psock
|
||||||
|
environment:
|
||||||
|
RUN_XTERM: "yes"
|
||||||
|
RUN_FLUXBOX: "yes"
|
||||||
|
DISPLAY_WIDTH: 1920
|
||||||
|
DISPLAY_HEIGHT: 1080
|
||||||
|
labels:
|
||||||
|
traefik.enable: true
|
||||||
|
traefik.docker.network: web
|
||||||
|
traefik.http.routers.musescore.rule: Host(`musescore.${BASE_DOMAIN}`)
|
||||||
|
traefik.http.routers.musescore.tls.certResolver: le
|
||||||
|
traefik.http.routers.musescore.middlewares: auth@file
|
||||||
|
|
27
musescore-psock-build/Dockerfile
Normal file
27
musescore-psock-build/Dockerfile
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# syntax=docker/dockerfile:1.4
|
||||||
|
FROM archlinux
|
||||||
|
|
||||||
|
|
||||||
|
RUN pacman -Sy \
|
||||||
|
&& pacman --noconfirm -S xterm alsa-utils alsa-plugins libpulse
|
||||||
|
ENV PULSE_SERVER unix:/tmp/psock/pulseaudio.socket
|
||||||
|
COPY <<EOF /root/.asoundrc
|
||||||
|
pcm.!default {
|
||||||
|
type pulse
|
||||||
|
fallback "sysdefault"
|
||||||
|
hint {
|
||||||
|
show on
|
||||||
|
description "Default ALSA output (currently PulseAudio Sound Server)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctl.!default {
|
||||||
|
type pulse
|
||||||
|
fallback "sysdefault"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
RUN pacman -Sy\
|
||||||
|
&& pacman --noconfirm -S community/musescore
|
||||||
|
|
||||||
|
CMD mscore
|
50
novnc-audio-build/Dockerfile
Normal file
50
novnc-audio-build/Dockerfile
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Inspired by https://github.com/vexingcodes/dwarf-fortress-docker
|
||||||
|
|
||||||
|
|
||||||
|
FROM theasp/novnc:latest
|
||||||
|
|
||||||
|
RUN apt-get update --allow-releaseinfo-change \
|
||||||
|
&& DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \
|
||||||
|
gstreamer1.0-plugins-good \
|
||||||
|
gstreamer1.0-pulseaudio \
|
||||||
|
gstreamer1.0-tools \
|
||||||
|
\
|
||||||
|
libglu1-mesa \
|
||||||
|
libgtk2.0-0 \
|
||||||
|
libncursesw5 \
|
||||||
|
libopenal1 \
|
||||||
|
libsdl-image1.2 \
|
||||||
|
libsdl-ttf2.0-0 \
|
||||||
|
libsdl1.2debian \
|
||||||
|
\
|
||||||
|
libsndfile1 \
|
||||||
|
pulseaudio \
|
||||||
|
ucspi-tcp \
|
||||||
|
dbus-x11 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY conf.d /app/conf.d2
|
||||||
|
RUN mv /app/conf.d2/* /app/conf.d
|
||||||
|
|
||||||
|
COPY tokenfile /app/tokenfile
|
||||||
|
|
||||||
|
COPY default.pa client.conf /etc/pulse/
|
||||||
|
COPY webaudio.js /usr/share/novnc/core/
|
||||||
|
RUN ln -s /usr/share/novnc/vnc_lite.html /usr/share/novnc/index.html \
|
||||||
|
&& sed -i 's/display:flex/display:none/' /usr/share/novnc/app/styles/lite.css \
|
||||||
|
&& sed -i "/import RFB/a \
|
||||||
|
import WebAudio from './core/webaudio.js'" \
|
||||||
|
/usr/share/novnc/vnc_lite.html \
|
||||||
|
&& sed -i "/function connected(e)/a \
|
||||||
|
var wa = new WebAudio(null); \
|
||||||
|
document.getElementsByTagName('canvas')[0].addEventListener('keydown', e => { wa.start(); });" \
|
||||||
|
/usr/share/novnc/vnc_lite.html \
|
||||||
|
&& sed -i "s/'path', 'websockify');/'path', 'websockify?token=vnc');/" \
|
||||||
|
/usr/share/novnc/vnc_lite.html
|
||||||
|
|
||||||
|
RUN groupadd pa \
|
||||||
|
&& useradd --create-home --gid pa pa \
|
||||||
|
&& mkdir -p /tmp/psock \
|
||||||
|
&& chown -R pa:pa /tmp/psock
|
||||||
|
|
||||||
|
VOLUME /tmp/psock
|
1
novnc-audio-build/client.conf
Normal file
1
novnc-audio-build/client.conf
Normal file
@ -0,0 +1 @@
|
|||||||
|
default-server=unix:/tmp/psock/pulseaudio.socket
|
5
novnc-audio-build/conf.d/audiostream.conf
Normal file
5
novnc-audio-build/conf.d/audiostream.conf
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[program:audiostream]
|
||||||
|
command=tcpserver localhost 5901 gst-launch-1.0 -q pulsesrc server=/tmp/psock/pulseaudio.socket !audio/x-raw, channels=2, rate=24000 ! cutter ! opusenc ! webmmux ! fdsink fd=1
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/var/log/audiostream.log
|
||||||
|
redirect_stderr=true
|
5
novnc-audio-build/conf.d/pulseaudio.conf
Normal file
5
novnc-audio-build/conf.d/pulseaudio.conf
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[program:pulseaudio]
|
||||||
|
command=su --command="/usr/bin/pulseaudio --disallow-module-loading -vvvv --disallow-exit --exit-idle-time=-1" pa
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/var/log/pulseaudio.log
|
||||||
|
redirect_stderr=true
|
5
novnc-audio-build/conf.d/websockify.conf
Normal file
5
novnc-audio-build/conf.d/websockify.conf
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[program:websockify]
|
||||||
|
command=websockify --web /usr/share/novnc 8080 --token-plugin=TokenFile --token-source=/app/tokenfile
|
||||||
|
autorestart=true
|
||||||
|
stdout_logfile=/var/log/websockify.log
|
||||||
|
redirect_stderr=true
|
3
novnc-audio-build/default.pa
Normal file
3
novnc-audio-build/default.pa
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/pulseaudio -nF
|
||||||
|
load-module module-native-protocol-unix socket=/tmp/psock/pulseaudio.socket auth-anonymous=1
|
||||||
|
load-module module-always-sink
|
2
novnc-audio-build/tokenfile
Normal file
2
novnc-audio-build/tokenfile
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
vnc: localhost:5900
|
||||||
|
pulse: localhost:5901
|
129
novnc-audio-build/webaudio.js
Normal file
129
novnc-audio-build/webaudio.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
export default class WebAudio {
|
||||||
|
constructor(url=null) {
|
||||||
|
if (url !== null)
|
||||||
|
this.url = url
|
||||||
|
else {
|
||||||
|
if (window.location.protocol === "https:")
|
||||||
|
this.url = "wss"
|
||||||
|
else
|
||||||
|
this.url = "ws"
|
||||||
|
this.url += "://" + window.location.hostname + "/websockify?token=pulse"
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connected = false;
|
||||||
|
|
||||||
|
//constants for audio behavoir
|
||||||
|
this.maximumAudioLag = 1.5; //amount of seconds we can potentially be behind the server audio stream
|
||||||
|
this.syncLagInterval = 5000; //check every x milliseconds if we are behind the server audio stream
|
||||||
|
this.updateBufferEvery = 20; //add recieved data to the player buffer every x milliseconds
|
||||||
|
this.reduceBufferInterval = 500; //trim the output audio stream buffer every x milliseconds so we don't overflow
|
||||||
|
this.maximumSecondsOfBuffering = 1; //maximum amount of data to store in the play buffer
|
||||||
|
this.connectionCheckInterval = 500; //check the connection every x milliseconds
|
||||||
|
|
||||||
|
//register all our background timers. these need to be created only once - and will run independent of the object's streams/properties
|
||||||
|
setInterval(() => this.updateQueue(), this.updateBufferEvery);
|
||||||
|
setInterval(() => this.syncInterval(), this.syncLagInterval);
|
||||||
|
setInterval(() => this.reduceBuffer(), this.reduceBufferInterval);
|
||||||
|
setInterval(() => this.tryLastPacket(), this.connectionCheckInterval);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//registers all the event handlers for when this stream is closed - or when data arrives.
|
||||||
|
registerHandlers() {
|
||||||
|
this.mediaSource.addEventListener('sourceended', e => this.socketDisconnected(e))
|
||||||
|
this.mediaSource.addEventListener('sourceclose', e => this.socketDisconnected(e))
|
||||||
|
this.mediaSource.addEventListener('error', e => this.socketDisconnected(e))
|
||||||
|
this.buffer.addEventListener('error', e => this.socketDisconnected(e))
|
||||||
|
this.buffer.addEventListener('abort', e => this.socketDisconnected(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
//starts the web audio stream. only call this method on button click.
|
||||||
|
start() {
|
||||||
|
if (!!this.connected) return;
|
||||||
|
if (!!this.audio) this.audio.remove();
|
||||||
|
this.queue = null;
|
||||||
|
|
||||||
|
this.mediaSource = new MediaSource()
|
||||||
|
this.mediaSource.addEventListener('sourceopen', e => this.onSourceOpen())
|
||||||
|
//first we need a media source - and an audio object that contains it.
|
||||||
|
this.audio = document.createElement('audio');
|
||||||
|
this.audio.src = window.URL.createObjectURL(this.mediaSource);
|
||||||
|
|
||||||
|
//start our stream - we can only do this on user input
|
||||||
|
this.audio.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
wsConnect() {
|
||||||
|
if (!!this.socket) this.socket.close();
|
||||||
|
|
||||||
|
this.socket = new WebSocket(this.url, ['binary', 'base64'])
|
||||||
|
this.socket.binaryType = 'arraybuffer'
|
||||||
|
this.socket.addEventListener('message', e => this.websocketDataArrived(e), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//this is called when the media source contains data
|
||||||
|
onSourceOpen(e) {
|
||||||
|
this.buffer = this.mediaSource.addSourceBuffer('audio/webm; codecs="opus"')
|
||||||
|
this.registerHandlers();
|
||||||
|
this.wsConnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
//whenever data arrives in our websocket this is called.
|
||||||
|
websocketDataArrived(e) {
|
||||||
|
this.lastPacket = Date.now();
|
||||||
|
this.connected = true;
|
||||||
|
this.queue = this.queue == null ? e.data : this.concat(this.queue, e.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
//whenever a disconnect happens this is called.
|
||||||
|
socketDisconnected(e) {
|
||||||
|
console.log(e);
|
||||||
|
this.connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tryLastPacket() {
|
||||||
|
if (this.lastPacket == null) return;
|
||||||
|
if ((Date.now() - this.lastPacket) > 1000) {
|
||||||
|
this.socketDisconnected('timeout');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//this updates the buffer with the data from our queue
|
||||||
|
updateQueue() {
|
||||||
|
if (!(!!this.queue && !!this.buffer && !this.buffer.updating)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.buffer.appendBuffer(this.queue);
|
||||||
|
this.queue = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//reduces the stream buffer to the minimal size that we need for streaming
|
||||||
|
reduceBuffer() {
|
||||||
|
if (!(this.buffer && !this.buffer.updating && !!this.audio && !!this.audio.currentTime && this.audio.currentTime > 1)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.buffer.remove(0, this.audio.currentTime - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//synchronizes the current time of the stream with the server
|
||||||
|
syncInterval() {
|
||||||
|
if (!(this.audio && this.audio.currentTime && this.audio.currentTime > 1 && this.buffer && this.buffer.buffered && this.buffer.buffered.length > 1)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentTime = this.audio.currentTime;
|
||||||
|
var targetTime = this.buffer.buffered.end(this.buffer.buffered.length - 1);
|
||||||
|
|
||||||
|
if (targetTime > (currentTime + this.maximumAudioLag)) this.audio.fastSeek(targetTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
//joins two data arrays - helper function
|
||||||
|
concat(buffer1, buffer2) {
|
||||||
|
var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
|
||||||
|
tmp.set(new Uint8Array(buffer1), 0);
|
||||||
|
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
|
||||||
|
return tmp.buffer;
|
||||||
|
};
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user