Re-add UDPTunnel fallback to WebRTC version

This commit is contained in:
Jonas Herzig 2020-11-25 16:58:07 +01:00
commit 506a799592
9 changed files with 138 additions and 101 deletions

View file

@ -32,6 +32,7 @@ window.mumbleWebConfig = {
'token': '',
'username': '',
'password': '',
'webrtc': 'auto', // whether to enable (true), disable (false) or auto-detect ('auto') WebRTC support
'joinDialog': false, // replace whole dialog with single "Join Conference" button
'matrix': false, // enable Matrix Widget support (mostly auto-detected; implies 'joinDialog')
'avatarurl': '', // download and set the user's Mumble avatar to the image at this URL

View file

@ -5,6 +5,7 @@ import ByteBuffer from 'bytebuffer'
import MumbleClient from 'mumble-client'
import WorkerBasedMumbleConnector from './worker-client'
import BufferQueueNode from 'web-audio-buffer-queue'
import mumbleConnect from 'mumble-client-websocket'
import audioContext from 'audio-context'
import ko from 'knockout'
import _dompurify from 'dompurify'
@ -118,6 +119,9 @@ function ConnectDialog () {
self.hide = self.visible.bind(self.visible, false)
self.connect = function () {
self.hide()
if (ui.detectWebRTC) {
ui.webrtc = true
}
ui.connect(self.username(), self.address(), self.port(), self.tokens(), self.password(), self.channelName())
}
@ -336,7 +340,10 @@ class GlobalBindings {
constructor (config) {
this.config = config
this.settings = new Settings(config.settings)
this.connector = new WorkerBasedMumbleConnector()
this.detectWebRTC = true
this.webrtc = true
this.fallbackConnector = new WorkerBasedMumbleConnector()
this.webrtcConnector = { connect: mumbleConnect }
this.client = null
this.userContextMenu = new ContextMenu()
this.channelContextMenu = new ContextMenu()
@ -449,12 +456,27 @@ class GlobalBindings {
// Note: This call needs to be delayed until the user has interacted with
// the page in some way (which at this point they have), see: https://goo.gl/7K7WLu
this.connector.setSampleRate(audioContext().sampleRate)
let ctx = audioContext()
this.fallbackConnector.setSampleRate(ctx.sampleRate)
if (!this._delayedMicNode) {
this._micNode = ctx.createMediaStreamSource(this._micStream)
this._delayNode = ctx.createDelay()
this._delayNode.delayTime.value = 0.15
this._delayedMicNode = ctx.createMediaStreamDestination()
}
// TODO: token
this.connector.connect(`wss://${host}:${port}`, {
(this.webrtc ? this.webrtcConnector : this.fallbackConnector).connect(`wss://${host}:${port}`, {
username: username,
password: password,
webrtc: this.webrtc ? {
enabled: true,
required: true,
mic: this._delayedMicNode.stream,
audioContext: ctx
} : {
enabled: false,
},
tokens: tokens
}).done(client => {
log(translate('logentry.connected'))
@ -535,6 +557,10 @@ class GlobalBindings {
this.connectErrorDialog.type(err.type)
this.connectErrorDialog.reason(err.reason)
this.connectErrorDialog.show()
} else if (err === 'server_does_not_support_webrtc' && this.detectWebRTC && this.webrtc) {
log(translate('logentry.connection_fallback_mode'))
this.webrtc = false
this.connect(username, host, port, tokens, password, channelName)
} else {
log(translate('logentry.connection_error'), err)
}
@ -686,24 +712,32 @@ class GlobalBindings {
}
}).on('voice', stream => {
console.log(`User ${user.username} started takling`)
var userNode = new BufferQueueNode({
audioContext: audioContext()
})
userNode.connect(audioContext().destination)
let userNode
if (!this.webrtc) {
userNode = new BufferQueueNode({
audioContext: audioContext()
})
userNode.connect(audioContext().destination)
}
if (stream.target === 'normal') {
ui.talking('on')
} else if (stream.target === 'shout') {
ui.talking('shout')
} else if (stream.target === 'whisper') {
ui.talking('whisper')
}
stream.on('data', data => {
if (data.target === 'normal') {
ui.talking('on')
} else if (data.target === 'shout') {
ui.talking('shout')
} else if (data.target === 'whisper') {
ui.talking('whisper')
if (this.webrtc) {
// mumble-client is in WebRTC mode, no pcm data should arrive this way
} else {
userNode.write(data.buffer)
}
userNode.write(data.buffer)
}).on('end', () => {
console.log(`User ${user.username} stopped takling`)
ui.talking('off')
userNode.end()
if (!this.webrtc) {
userNode.end()
}
})
})
}
@ -825,6 +859,15 @@ class GlobalBindings {
voiceHandler.setMute(true)
}
this._micNode.disconnect()
this._delayNode.disconnect()
if (mode === 'vad') {
this._micNode.connect(this._delayNode)
this._delayNode.connect(this._delayedMicNode)
} else {
this._micNode.connect(this._delayedMicNode)
}
this.client.setAudioQuality(
this.settings.audioBitrate,
this.settings.samplesPerPacket
@ -1055,6 +1098,12 @@ function initializeUI () {
if (queryParams.password) {
ui.connectDialog.password(queryParams.password)
}
if (queryParams.webrtc !== 'auto') {
ui.detectWebRTC = false
if (queryParams.webrtc == 'false') {
ui.webrtc = false
}
}
if (queryParams.channelName) {
ui.connectDialog.channelName(queryParams.channelName)
}
@ -1251,23 +1300,26 @@ function translateEverything() {
async function main() {
await localizationInitialize(navigator.language);
translateEverything();
initializeUI();
initVoice(data => {
if (testVoiceHandler) {
testVoiceHandler.write(data)
}
if (!ui.client) {
if (voiceHandler) {
voiceHandler.end()
try {
const userMedia = await initVoice(data => {
if (testVoiceHandler) {
testVoiceHandler.write(data)
}
voiceHandler = null
} else if (voiceHandler) {
voiceHandler.write(data)
}
}, err => {
log(translate('logentry.mic_init_error'), err)
})
if (!ui.client) {
if (voiceHandler) {
voiceHandler.end()
}
voiceHandler = null
} else if (voiceHandler) {
voiceHandler.write(data)
}
})
ui._micStream = userMedia
} catch (err) {
window.alert('Failed to initialize user media\nRefresh page to retry.\n' + err)
return
}
initializeUI();
}
window.onload = main

View file

@ -1,10 +1,10 @@
import { Writable } from 'stream'
import MicrophoneStream from 'microphone-stream'
import audioContext from 'audio-context'
import getUserMedia from 'getusermedia'
import keyboardjs from 'keyboardjs'
import vad from 'voice-activity-detection'
import DropStream from 'drop-stream'
import { WorkerBasedMumbleClient } from './worker-client'
class VoiceHandler extends Writable {
constructor (client, settings) {
@ -33,8 +33,12 @@ class VoiceHandler extends Writable {
return this._outbound
}
// Note: the samplesPerPacket argument is handled in worker.js and not passed on
this._outbound = this._client.createVoiceStream(this._settings.samplesPerPacket)
if (this._client instanceof WorkerBasedMumbleClient) {
// Note: the samplesPerPacket argument is handled in worker.js and not passed on
this._outbound = this._client.createVoiceStream(this._settings.samplesPerPacket)
} else {
this._outbound = this._client.createVoiceStream()
}
this.emit('started_talking')
}
@ -160,16 +164,13 @@ export class VADVoiceHandler extends VoiceHandler {
var theUserMedia = null
export function initVoice (onData, onUserMediaError) {
getUserMedia({ audio: true }, (err, userMedia) => {
if (err) {
onUserMediaError(err)
} else {
theUserMedia = userMedia
var micStream = new MicrophoneStream(userMedia, { objectMode: true, bufferSize: 1024 })
micStream.on('data', data => {
onData(Buffer.from(data.getChannelData(0).buffer))
})
}
export function initVoice (onData) {
return window.navigator.mediaDevices.getUserMedia({ audio: true }).then((userMedia) => {
theUserMedia = userMedia
var micStream = new MicrophoneStream(userMedia, { objectMode: true, bufferSize: 1024 })
micStream.on('data', data => {
onData(Buffer.from(data.getChannelData(0).buffer))
})
return userMedia
})
}

View file

@ -125,7 +125,7 @@ class WorkerBasedMumbleConnector {
}
}
class WorkerBasedMumbleClient extends EventEmitter {
export class WorkerBasedMumbleClient extends EventEmitter {
constructor (connector, clientId) {
super()
this._connector = connector
@ -342,11 +342,12 @@ class WorkerBasedMumbleUser extends EventEmitter {
props
]
} else if (name === 'voice') {
let [id] = args
let [id, target] = args
let stream = new PassThrough({
objectMode: true
})
this._connector._voiceStreams[id] = stream
stream.target = target
args = [stream]
} else if (name === 'remove') {
delete this._client._users[this._id]

View file

@ -164,7 +164,7 @@ import 'subworkers'
})
})
return [voiceId]
return [voiceId, stream.target]
})
registerEventProxy(id, user, 'remove')