worker-client.js 9.5 KB

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