worker-client.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. import MumbleClient from 'mumble-client'
  2. import Promise from 'promise'
  3. import EventEmitter from 'events'
  4. import { Writable, PassThrough } from 'stream'
  5. import toArrayBuffer from 'to-arraybuffer'
  6. import ByteBuffer from 'bytebuffer'
  7. import webworkify from 'webworkify'
  8. import worker from './worker'
  9. /**
  10. * Creates proxy MumbleClients to a real ones running on a web worker.
  11. * Only stuff which we need in mumble-web is proxied, i.e. this is not a generic solution.
  12. */
  13. class WorkerBasedMumbleConnector {
  14. constructor (sampleRate) {
  15. this._worker = webworkify(worker)
  16. this._worker.addEventListener('message', this._onMessage.bind(this))
  17. this._reqId = 1
  18. this._requests = {}
  19. this._clients = {}
  20. this._nextVoiceId = 1
  21. this._voiceStreams = {}
  22. this._postMessage({
  23. method: '_init',
  24. sampleRate: sampleRate
  25. })
  26. }
  27. _postMessage (msg, transfer) {
  28. try {
  29. this._worker.postMessage(msg, transfer)
  30. } catch (err) {
  31. console.error('Failed to postMessage', msg)
  32. throw err
  33. }
  34. }
  35. _call (id, method, payload, transfer) {
  36. let reqId = this._reqId++
  37. console.debug(method, id, payload)
  38. this._postMessage({
  39. clientId: id.client,
  40. channelId: id.channel,
  41. userId: id.user,
  42. method: method,
  43. reqId: reqId,
  44. payload: payload
  45. }, transfer)
  46. return reqId
  47. }
  48. _query (id, method, payload, transfer) {
  49. let reqId = this._call(id, method, payload, transfer)
  50. return new Promise((resolve, reject) => {
  51. this._requests[reqId] = [resolve, reject]
  52. })
  53. }
  54. _addCall (proxy, name, id) {
  55. let self = this
  56. proxy[name] = function () {
  57. self._call(id, name, Array.from(arguments))
  58. }
  59. }
  60. connect (host, args) {
  61. return this._query({}, '_connect', { host: host, args: args })
  62. .then(id => this._client(id))
  63. }
  64. _client (id) {
  65. let client = this._clients[id]
  66. if (!client) {
  67. client = new WorkerBasedMumbleClient(this, id)
  68. this._clients[id] = client
  69. }
  70. return client
  71. }
  72. _onMessage (ev) {
  73. let data = ev.data
  74. if (data.reqId != null) {
  75. console.debug(data)
  76. let { reqId, result, error } = data
  77. let [ resolve, reject ] = this._requests[reqId]
  78. delete this._requests[reqId]
  79. if (result) {
  80. resolve(result)
  81. } else {
  82. reject(error)
  83. }
  84. } else if (data.clientId != null) {
  85. console.debug(data)
  86. let client = this._client(data.clientId)
  87. let target
  88. if (data.userId != null) {
  89. target = client._user(data.userId)
  90. } else if (data.channelId != null) {
  91. target = client._channel(data.channelId)
  92. } else {
  93. target = client
  94. }
  95. if (data.event) {
  96. target._dispatchEvent(data.event, data.value)
  97. } else if (data.prop) {
  98. target._setProp(data.prop, data.value)
  99. }
  100. } else if (data.voiceId != null) {
  101. let stream = this._voiceStreams[data.voiceId]
  102. let buffer = data.buffer
  103. if (buffer) {
  104. stream.write({
  105. target: data.target,
  106. buffer: Buffer.from(buffer)
  107. })
  108. } else {
  109. delete this._voiceStreams[data.voiceId]
  110. stream.end()
  111. }
  112. }
  113. }
  114. }
  115. class WorkerBasedMumbleClient extends EventEmitter {
  116. constructor (connector, clientId) {
  117. super()
  118. this._connector = connector
  119. this._id = clientId
  120. this._users = {}
  121. this._channels = {}
  122. let id = { client: clientId }
  123. connector._addCall(this, 'setSelfDeaf', id)
  124. connector._addCall(this, 'setSelfMute', id)
  125. connector._addCall(this, 'setSelfTexture', id)
  126. connector._addCall(this, 'setAudioQuality', id)
  127. connector._addCall(this, 'disconnect', id)
  128. let _disconnect = this.disconnect
  129. this.disconnect = () => {
  130. _disconnect.apply(this)
  131. delete connector._clients[id]
  132. }
  133. connector._addCall(this, 'createVoiceStream', id)
  134. let _createVoiceStream = this.createVoiceStream
  135. this.createVoiceStream = function () {
  136. let voiceId = connector._nextVoiceId++
  137. let args = Array.from(arguments)
  138. args.unshift(voiceId)
  139. _createVoiceStream.apply(this, args)
  140. return new Writable({
  141. write (chunk, encoding, callback) {
  142. chunk = toArrayBuffer(chunk)
  143. connector._postMessage({
  144. voiceId: voiceId,
  145. chunk: chunk
  146. })
  147. callback()
  148. },
  149. final (callback) {
  150. connector._postMessage({
  151. voiceId: voiceId
  152. })
  153. callback()
  154. }
  155. })
  156. }
  157. // Dummy client used for bandwidth calculations
  158. this._dummyClient = new MumbleClient({ username: 'dummy' })
  159. let defineDummyMethod = (name) => {
  160. this[name] = function () {
  161. return this._dummyClient[name].apply(this._dummyClient, arguments)
  162. }
  163. }
  164. defineDummyMethod('getMaxBitrate')
  165. defineDummyMethod('getActualBitrate')
  166. }
  167. _user (id) {
  168. let user = this._users[id]
  169. if (!user) {
  170. user = new WorkerBasedMumbleUser(this._connector, this, id)
  171. this._users[id] = user
  172. }
  173. return user
  174. }
  175. _channel (id) {
  176. let channel = this._channels[id]
  177. if (!channel) {
  178. channel = new WorkerBasedMumbleChannel(this._connector, this, id)
  179. this._channels[id] = channel
  180. }
  181. return channel
  182. }
  183. _dispatchEvent (name, args) {
  184. if (name === 'newChannel') {
  185. args[0] = this._channel(args[0])
  186. } else if (name === 'newUser') {
  187. args[0] = this._user(args[0])
  188. } else if (name === 'message') {
  189. args[0] = this._user(args[0])
  190. args[2] = args[2].map((id) => this._user(id))
  191. args[3] = args[3].map((id) => this._channel(id))
  192. args[4] = args[4].map((id) => this._channel(id))
  193. }
  194. args.unshift(name)
  195. this.emit.apply(this, args)
  196. }
  197. _setProp (name, value) {
  198. if (name === 'root') {
  199. name = '_rootId'
  200. }
  201. if (name === 'self') {
  202. name = '_selfId'
  203. }
  204. if (name === 'maxBandwidth') {
  205. this._dummyClient.maxBandwidth = value
  206. }
  207. this[name] = value
  208. }
  209. get root () {
  210. return this._channel(this._rootId)
  211. }
  212. get channels () {
  213. return Object.values(this._channels)
  214. }
  215. get users () {
  216. return Object.values(this._users)
  217. }
  218. get self () {
  219. return this._user(this._selfId)
  220. }
  221. }
  222. class WorkerBasedMumbleChannel extends EventEmitter {
  223. constructor (connector, client, channelId) {
  224. super()
  225. this._connector = connector
  226. this._client = client
  227. this._id = channelId
  228. let id = { client: client._id, channel: channelId }
  229. connector._addCall(this, 'sendMessage', id)
  230. }
  231. _dispatchEvent (name, args) {
  232. if (name === 'update') {
  233. let [actor, props] = args
  234. Object.entries(props).forEach((entry) => {
  235. this._setProp(entry[0], entry[1])
  236. })
  237. if (props.parent != null) {
  238. props.parent = this.parent
  239. }
  240. if (props.links != null) {
  241. props.links = this.links
  242. }
  243. args = [
  244. this._client._user(actor),
  245. props
  246. ]
  247. } else if (name === 'remove') {
  248. delete this._client._channels[this._id]
  249. }
  250. args.unshift(name)
  251. this.emit.apply(this, args)
  252. }
  253. _setProp (name, value) {
  254. if (name === 'parent') {
  255. name = '_parentId'
  256. }
  257. if (name === 'links') {
  258. value = value.map((id) => this._client._channel(id))
  259. }
  260. this[name] = value
  261. }
  262. get parent () {
  263. if (this._parentId != null) {
  264. return this._client._channel(this._parentId)
  265. }
  266. }
  267. get children () {
  268. return Object.values(this._client._channels).filter((it) => it.parent === this)
  269. }
  270. }
  271. class WorkerBasedMumbleUser extends EventEmitter {
  272. constructor (connector, client, userId) {
  273. super()
  274. this._connector = connector
  275. this._client = client
  276. this._id = userId
  277. let id = { client: client._id, user: userId }
  278. connector._addCall(this, 'requestTexture', id)
  279. connector._addCall(this, 'clearTexture', id)
  280. connector._addCall(this, 'setMute', id)
  281. connector._addCall(this, 'setDeaf', id)
  282. connector._addCall(this, 'sendMessage', id)
  283. this.setChannel = (channel) => {
  284. connector._call(id, 'setChannel', channel._id)
  285. }
  286. }
  287. _dispatchEvent (name, args) {
  288. if (name === 'update') {
  289. let [actor, props] = args
  290. Object.entries(props).forEach((entry) => {
  291. this._setProp(entry[0], entry[1])
  292. })
  293. if (props.channel != null) {
  294. props.channel = this.channel
  295. }
  296. if (props.texture != null) {
  297. props.texture = this.texture
  298. }
  299. args = [
  300. this._client._user(actor),
  301. props
  302. ]
  303. } else if (name === 'voice') {
  304. let [id] = args
  305. let stream = new PassThrough({
  306. objectMode: true
  307. })
  308. this._connector._voiceStreams[id] = stream
  309. args = [stream]
  310. } else if (name === 'remove') {
  311. delete this._client._users[this._id]
  312. }
  313. args.unshift(name)
  314. this.emit.apply(this, args)
  315. }
  316. _setProp (name, value) {
  317. if (name === 'channel') {
  318. name = '_channelId'
  319. }
  320. if (name === 'texture') {
  321. if (value) {
  322. let buf = ByteBuffer.wrap(value.buffer)
  323. buf.offset = value.offset
  324. buf.limit = value.limit
  325. value = buf
  326. }
  327. }
  328. this[name] = value
  329. }
  330. get channel () {
  331. return this._client.channels[this._channelId]
  332. }
  333. }
  334. export default WorkerBasedMumbleConnector