first draft audio support
This commit is contained in:
parent
9142f8f0f7
commit
e9b1325a3c
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* noVNC: HTML5 VNC client
|
||||||
|
* Copyright (C) 2020 The noVNC Authors
|
||||||
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
|
*
|
||||||
|
* See README.md for usage and integration instructions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class AudioBuffer {
|
||||||
|
constructor(codec) {
|
||||||
|
// instantiate a media source and audio buffer/queue
|
||||||
|
this._mediaSource = new MediaSource();
|
||||||
|
this._audioBuffer = null;
|
||||||
|
this._audioQ = [];
|
||||||
|
|
||||||
|
// create a hidden audio element
|
||||||
|
this._audio = document.createElement('audio');
|
||||||
|
this._audio.src = window.URL.createObjectURL(mediaSource);
|
||||||
|
|
||||||
|
// when data is queued, start playing
|
||||||
|
this._mediaSource.addEventListener('sourceopen', function(e) {
|
||||||
|
this._audio.play();
|
||||||
|
this._audioBuffer = mediaSource.addSourceBuffer(codec)
|
||||||
|
this._audioBuffer.addEventListener('update', function() {
|
||||||
|
if (this._audioQ.length > 0 && !this._audioBuffer.updating) {
|
||||||
|
this._audioBuffer.appendBuffer(this._audioQ.shift())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
queueAudio(data) {
|
||||||
|
if (this._audioBuffer !== null) {
|
||||||
|
if (this._audioBuffer.updating || this._audioQ.length > 0) {
|
||||||
|
this._audioQ.push(data)
|
||||||
|
} else {
|
||||||
|
this._audioBuffer.appendBuffer(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {} // intentionally left empty as no cleanup seems necessary
|
||||||
|
}
|
||||||
83
core/rfb.js
83
core/rfb.js
|
|
@ -499,6 +499,15 @@ export default class RFB extends EventTargetMixin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enableAudio(sampleFormat, channels, frequency) {
|
||||||
|
RFB.messages.SetQEMUExtendedAudioFormat(this._sock, sampleFormat, channels, frequency)
|
||||||
|
RFB.messages.ToggleQEMUExtendedAudio(this._sock, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
disableAudio() {
|
||||||
|
RFB.messages.ToggleQEMUExtendedAudio(this._sock, false)
|
||||||
|
}
|
||||||
|
|
||||||
// ===== PRIVATE METHODS =====
|
// ===== PRIVATE METHODS =====
|
||||||
|
|
||||||
_connect() {
|
_connect() {
|
||||||
|
|
@ -2039,6 +2048,25 @@ export default class RFB extends EventTargetMixin {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handleQEMUExtAudioMsg() {
|
||||||
|
if (this._sock.rQwait("QEMU extended audio message", 3, 1)) { return false; }
|
||||||
|
|
||||||
|
this._sock.rQshift8(); // for now there is only a single submessage type 1
|
||||||
|
const operation = this._sock.rQshift16();
|
||||||
|
|
||||||
|
if (operation === 1) { // stream is starting
|
||||||
|
this._audioBuffer = new AudioBuffer('audio/webm; codecs="opus"') // TODO: This is obviously not the right value to use here
|
||||||
|
} else if (operation === 0) { // stream is stopping
|
||||||
|
this._audioBuffer.close()
|
||||||
|
} else { // stream data
|
||||||
|
const length = this._sock.rQshift32();
|
||||||
|
const data = this._sock.rQshiftBytes(length);
|
||||||
|
this._audioBuffer.queueAudio(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
_handleXvpMsg() {
|
_handleXvpMsg() {
|
||||||
if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
|
if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
|
||||||
this._sock.rQskipBytes(1); // Padding
|
this._sock.rQskipBytes(1); // Padding
|
||||||
|
|
@ -2112,6 +2140,9 @@ export default class RFB extends EventTargetMixin {
|
||||||
|
|
||||||
case 250: // XVP
|
case 250: // XVP
|
||||||
return this._handleXvpMsg();
|
return this._handleXvpMsg();
|
||||||
|
|
||||||
|
case 255: // Qemu extended audio message
|
||||||
|
return this._handleQEMUExtAudioMsg();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
this._fail("Unexpected server message (type " + msgType + ")");
|
this._fail("Unexpected server message (type " + msgType + ")");
|
||||||
|
|
@ -2549,6 +2580,16 @@ export default class RFB extends EventTargetMixin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Audio sample formats
|
||||||
|
RFB.sampleFormats = {
|
||||||
|
U8: 0,
|
||||||
|
S8: 1,
|
||||||
|
U16: 2,
|
||||||
|
S16: 3,
|
||||||
|
U32: 4,
|
||||||
|
S32: 5
|
||||||
|
}
|
||||||
|
|
||||||
// Class Methods
|
// Class Methods
|
||||||
RFB.messages = {
|
RFB.messages = {
|
||||||
keyEvent(sock, keysym, down) {
|
keyEvent(sock, keysym, down) {
|
||||||
|
|
@ -2570,6 +2611,48 @@ RFB.messages = {
|
||||||
sock.flush();
|
sock.flush();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ToggleQEMUExtendedAudio(sock, enabled) {
|
||||||
|
const buff = sock._sQ;
|
||||||
|
const offset = sock._sQlen;
|
||||||
|
|
||||||
|
buff[offset] = 255; // msg-type
|
||||||
|
buff[offset + 1] = 1; // sub msg-type
|
||||||
|
|
||||||
|
buff[offset + 2] = 0; // operation
|
||||||
|
if (enabled) {
|
||||||
|
buff[offset + 3] = 0;
|
||||||
|
} else {
|
||||||
|
buff[offset + 3] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sock._sQlen += 4;
|
||||||
|
sock.flush();
|
||||||
|
},
|
||||||
|
|
||||||
|
SetQEMUExtendedAudioFormat(sock, sampleFormat, channels, frequency) {
|
||||||
|
const buff = sock._sQ;
|
||||||
|
const offset = sock._sQlen;
|
||||||
|
|
||||||
|
buff[offset] = 255; // msg type
|
||||||
|
buff[offset + 1] = 1; // sub msg-type
|
||||||
|
|
||||||
|
buff[offset + 2] = 0; // operation
|
||||||
|
buff[offset + 3] = 2;
|
||||||
|
|
||||||
|
buff[offset + 4] = sampleFormat;
|
||||||
|
buff[offset + 5] = channels;
|
||||||
|
|
||||||
|
const freq = toUnsigned32bit(frequency);
|
||||||
|
|
||||||
|
buff[offset + 6] = freq >> 24;
|
||||||
|
buff[offset + 7] = freq >> 16;
|
||||||
|
buff[offset + 8] = freq >> 8;
|
||||||
|
buff[offset + 9] = freq;
|
||||||
|
|
||||||
|
sock._sQlen += 10;
|
||||||
|
sock.flush()
|
||||||
|
},
|
||||||
|
|
||||||
QEMUExtendedKeyEvent(sock, keysym, down, keycode) {
|
QEMUExtendedKeyEvent(sock, keysym, down, keycode) {
|
||||||
function getRFBkeycode(xtScanCode) {
|
function getRFBkeycode(xtScanCode) {
|
||||||
const upperByte = (keycode >> 8);
|
const upperByte = (keycode >> 8);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue