worker-client.js 9.4 KB

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