123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- import MumbleClient from 'mumble-client'
- import Promise from 'promise'
- import EventEmitter from 'events'
- import { Writable, PassThrough } from 'stream'
- import toArrayBuffer from 'to-arraybuffer'
- import ByteBuffer from 'bytebuffer'
- import Worker from './worker'
- /**
- * Creates proxy MumbleClients to a real ones running on a web worker.
- * Only stuff which we need in mumble-web is proxied, i.e. this is not a generic solution.
- */
- class WorkerBasedMumbleConnector {
- constructor () {
- this._worker = new Worker()
- this._worker.addEventListener('message', this._onMessage.bind(this))
- this._reqId = 1
- this._requests = {}
- this._clients = {}
- this._nextVoiceId = 1
- this._voiceStreams = {}
- }
- setSampleRate (sampleRate) {
- this._postMessage({
- method: '_init',
- sampleRate: sampleRate
- })
- }
- _postMessage (msg, transfer) {
- try {
- this._worker.postMessage(msg, transfer)
- } catch (err) {
- console.error('Failed to postMessage', msg)
- throw err
- }
- }
- _call (id, method, payload, transfer) {
- let reqId = this._reqId++
- console.debug(method, id, payload)
- this._postMessage({
- clientId: id.client,
- channelId: id.channel,
- userId: id.user,
- method: method,
- reqId: reqId,
- payload: payload
- }, transfer)
- return reqId
- }
- _query (id, method, payload, transfer) {
- let reqId = this._call(id, method, payload, transfer)
- return new Promise((resolve, reject) => {
- this._requests[reqId] = [resolve, reject]
- })
- }
- _addCall (proxy, name, id) {
- let self = this
- proxy[name] = function () {
- self._call(id, name, Array.from(arguments))
- }
- }
- connect (host, args) {
- return this._query({}, '_connect', { host: host, args: args })
- .then(id => this._client(id))
- }
- _client (id) {
- let client = this._clients[id]
- if (!client) {
- client = new WorkerBasedMumbleClient(this, id)
- this._clients[id] = client
- }
- return client
- }
- _onMessage (ev) {
- let data = ev.data
- if (data.reqId != null) {
- console.debug(data)
- let { reqId, result, error } = data
- let [ resolve, reject ] = this._requests[reqId]
- delete this._requests[reqId]
- if (result) {
- resolve(result)
- } else {
- reject(error)
- }
- } else if (data.clientId != null) {
- console.debug(data)
- let client = this._client(data.clientId)
- let target
- if (data.userId != null) {
- target = client._user(data.userId)
- } else if (data.channelId != null) {
- target = client._channel(data.channelId)
- } else {
- target = client
- }
- if (data.event) {
- target._dispatchEvent(data.event, data.value)
- } else if (data.prop) {
- target._setProp(data.prop, data.value)
- }
- } else if (data.voiceId != null) {
- let stream = this._voiceStreams[data.voiceId]
- let buffer = data.buffer
- if (buffer) {
- stream.write({
- target: data.target,
- buffer: Buffer.from(buffer)
- })
- } else {
- delete this._voiceStreams[data.voiceId]
- stream.end()
- }
- }
- }
- }
- class WorkerBasedMumbleClient extends EventEmitter {
- constructor (connector, clientId) {
- super()
- this._connector = connector
- this._id = clientId
- this._users = {}
- this._channels = {}
- let id = { client: clientId }
- connector._addCall(this, 'setSelfDeaf', id)
- connector._addCall(this, 'setSelfMute', id)
- connector._addCall(this, 'setSelfTexture', id)
- connector._addCall(this, 'setAudioQuality', id)
- connector._addCall(this, 'disconnect', id)
- let _disconnect = this.disconnect
- this.disconnect = () => {
- _disconnect.apply(this)
- delete connector._clients[id]
- }
- connector._addCall(this, 'createVoiceStream', id)
- let _createVoiceStream = this.createVoiceStream
- this.createVoiceStream = function () {
- let voiceId = connector._nextVoiceId++
- let args = Array.from(arguments)
- args.unshift(voiceId)
- _createVoiceStream.apply(this, args)
- return new Writable({
- write (chunk, encoding, callback) {
- chunk = toArrayBuffer(chunk)
- connector._postMessage({
- voiceId: voiceId,
- chunk: chunk
- })
- callback()
- },
- final (callback) {
- connector._postMessage({
- voiceId: voiceId
- })
- callback()
- }
- })
- }
- // Dummy client used for bandwidth calculations
- this._dummyClient = new MumbleClient({ username: 'dummy' })
- let defineDummyMethod = (name) => {
- this[name] = function () {
- return this._dummyClient[name].apply(this._dummyClient, arguments)
- }
- }
- defineDummyMethod('getMaxBitrate')
- defineDummyMethod('getActualBitrate')
- let _setAudioQuality = this.setAudioQuality
- this.setAudioQuality = function () {
- this._dummyClient.setAudioQuality.apply(this._dummyClient, arguments)
- _setAudioQuality.apply(this, arguments)
- }
- }
- _user (id) {
- let user = this._users[id]
- if (!user) {
- user = new WorkerBasedMumbleUser(this._connector, this, id)
- this._users[id] = user
- }
- return user
- }
- _channel (id) {
- let channel = this._channels[id]
- if (!channel) {
- channel = new WorkerBasedMumbleChannel(this._connector, this, id)
- this._channels[id] = channel
- }
- return channel
- }
- _dispatchEvent (name, args) {
- if (name === 'newChannel') {
- args[0] = this._channel(args[0])
- } else if (name === 'newUser') {
- args[0] = this._user(args[0])
- } else if (name === 'message') {
- args[0] = this._user(args[0])
- args[2] = args[2].map((id) => this._user(id))
- args[3] = args[3].map((id) => this._channel(id))
- args[4] = args[4].map((id) => this._channel(id))
- }
- args.unshift(name)
- this.emit.apply(this, args)
- }
- _setProp (name, value) {
- if (name === 'root') {
- name = '_rootId'
- }
- if (name === 'self') {
- name = '_selfId'
- }
- if (name === 'maxBandwidth') {
- this._dummyClient.maxBandwidth = value
- }
- this[name] = value
- }
- get root () {
- return this._channel(this._rootId)
- }
- get channels () {
- return Object.values(this._channels)
- }
- get users () {
- return Object.values(this._users)
- }
- get self () {
- return this._user(this._selfId)
- }
- }
- class WorkerBasedMumbleChannel extends EventEmitter {
- constructor (connector, client, channelId) {
- super()
- this._connector = connector
- this._client = client
- this._id = channelId
- let id = { client: client._id, channel: channelId }
- connector._addCall(this, 'sendMessage', id)
- }
- _dispatchEvent (name, args) {
- if (name === 'update') {
- let [props] = args
- Object.entries(props).forEach((entry) => {
- this._setProp(entry[0], entry[1])
- })
- if (props.parent != null) {
- props.parent = this.parent
- }
- if (props.links != null) {
- props.links = this.links
- }
- args = [
- props
- ]
- } else if (name === 'remove') {
- delete this._client._channels[this._id]
- }
- args.unshift(name)
- this.emit.apply(this, args)
- }
- _setProp (name, value) {
- if (name === 'parent') {
- name = '_parentId'
- }
- if (name === 'links') {
- value = value.map((id) => this._client._channel(id))
- }
- this[name] = value
- }
- get parent () {
- if (this._parentId != null) {
- return this._client._channel(this._parentId)
- }
- }
- get children () {
- return Object.values(this._client._channels).filter((it) => it.parent === this)
- }
- }
- class WorkerBasedMumbleUser extends EventEmitter {
- constructor (connector, client, userId) {
- super()
- this._connector = connector
- this._client = client
- this._id = userId
- let id = { client: client._id, user: userId }
- connector._addCall(this, 'requestTexture', id)
- connector._addCall(this, 'clearTexture', id)
- connector._addCall(this, 'setMute', id)
- connector._addCall(this, 'setDeaf', id)
- connector._addCall(this, 'sendMessage', id)
- this.setChannel = (channel) => {
- connector._call(id, 'setChannel', channel._id)
- }
- }
- _dispatchEvent (name, args) {
- if (name === 'update') {
- let [actor, props] = args
- Object.entries(props).forEach((entry) => {
- this._setProp(entry[0], entry[1])
- })
- if (props.channel != null) {
- props.channel = this.channel
- }
- if (props.texture != null) {
- props.texture = this.texture
- }
- args = [
- this._client._user(actor),
- props
- ]
- } else if (name === 'voice') {
- let [id] = args
- let stream = new PassThrough({
- objectMode: true
- })
- this._connector._voiceStreams[id] = stream
- args = [stream]
- } else if (name === 'remove') {
- delete this._client._users[this._id]
- }
- args.unshift(name)
- this.emit.apply(this, args)
- }
- _setProp (name, value) {
- if (name === 'channel') {
- name = '_channelId'
- }
- if (name === 'texture') {
- if (value) {
- let buf = ByteBuffer.wrap(value.buffer)
- buf.offset = value.offset
- buf.limit = value.limit
- value = buf
- }
- }
- this[name] = value
- }
- get channel () {
- return this._client._channels[this._channelId]
- }
- }
- export default WorkerBasedMumbleConnector
|