worker.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import { Transform } from 'stream'
  2. import mumbleConnect from 'mumble-client-websocket'
  3. import toArrayBuffer from 'to-arraybuffer'
  4. import chunker from 'stream-chunker'
  5. import Resampler from 'libsamplerate.js'
  6. import CodecsBrowser from 'mumble-client-codecs-browser'
  7. // Polyfill nested webworkers for https://bugs.chromium.org/p/chromium/issues/detail?id=31666
  8. import 'subworkers'
  9. let sampleRate
  10. let nextClientId = 1
  11. let nextVoiceId = 1
  12. let voiceStreams = []
  13. let clients = []
  14. function postMessage (msg, transfer) {
  15. try {
  16. self.postMessage(msg, transfer)
  17. } catch (err) {
  18. console.error('Failed to postMessage', msg)
  19. throw err
  20. }
  21. }
  22. function resolve (reqId, value, transfer) {
  23. postMessage({
  24. reqId: reqId,
  25. result: value
  26. }, transfer)
  27. }
  28. function reject (reqId, value, transfer) {
  29. console.error(value)
  30. let jsonValue = JSON.parse(JSON.stringify(value))
  31. if (value.$type) {
  32. jsonValue.$type = { name: value.$type.name }
  33. }
  34. postMessage({
  35. reqId: reqId,
  36. error: jsonValue
  37. }, transfer)
  38. }
  39. function registerEventProxy (id, obj, event, transform) {
  40. obj.on(event, function (_) {
  41. postMessage({
  42. clientId: id.client,
  43. channelId: id.channel,
  44. userId: id.user,
  45. event: event,
  46. value: transform ? transform.apply(null, arguments) : Array.from(arguments)
  47. })
  48. })
  49. }
  50. function pushProp (id, obj, prop, transform) {
  51. let value = obj[prop]
  52. postMessage({
  53. clientId: id.client,
  54. channelId: id.channel,
  55. userId: id.user,
  56. prop: prop,
  57. value: transform ? transform(value) : value
  58. })
  59. }
  60. function setupOutboundVoice (voiceId, samplesPerPacket, stream) {
  61. let resampler = new Resampler({
  62. unsafe: true,
  63. type: Resampler.Type.SINC_FASTEST,
  64. ratio: 48000 / sampleRate
  65. })
  66. let buffer2Float32Array = new Transform({
  67. transform (data, _, callback) {
  68. callback(null, new Float32Array(data.buffer, data.byteOffset, data.byteLength / 4))
  69. },
  70. readableObjectMode: true
  71. })
  72. resampler
  73. .pipe(chunker(4 * samplesPerPacket))
  74. .pipe(buffer2Float32Array)
  75. .pipe(stream)
  76. voiceStreams[voiceId] = resampler
  77. }
  78. function setupChannel (id, channel) {
  79. id = Object.assign({}, id, { channel: channel.id })
  80. registerEventProxy(id, channel, 'update', (props) => {
  81. if (props.parent) {
  82. props.parent = props.parent.id
  83. }
  84. if (props.links) {
  85. props.links = props.links.map((it) => it.id)
  86. }
  87. return [props]
  88. })
  89. registerEventProxy(id, channel, 'remove')
  90. pushProp(id, channel, 'parent', (it) => it ? it.id : it)
  91. pushProp(id, channel, 'links', (it) => it.map((it) => it.id))
  92. let props = [
  93. 'position', 'name', 'description'
  94. ]
  95. for (let prop of props) {
  96. pushProp(id, channel, prop)
  97. }
  98. for (let child of channel.children) {
  99. setupChannel(id, child)
  100. }
  101. return channel.id
  102. }
  103. function setupUser (id, user) {
  104. id = Object.assign({}, id, { user: user.id })
  105. registerEventProxy(id, user, 'update', (actor, props) => {
  106. if (actor) {
  107. actor = actor.id
  108. }
  109. if (props.channel != null) {
  110. props.channel = props.channel.id
  111. }
  112. return [actor, props]
  113. })
  114. registerEventProxy(id, user, 'voice', (stream) => {
  115. let voiceId = nextVoiceId++
  116. let target
  117. // We want to do as little on the UI thread as possible, so do resampling here as well
  118. var resampler = new Resampler({
  119. unsafe: true,
  120. type: Resampler.Type.ZERO_ORDER_HOLD,
  121. ratio: sampleRate / 48000
  122. })
  123. // Pipe stream into resampler
  124. stream.on('data', (data) => {
  125. // store target so we can pass it on after resampling
  126. target = data.target
  127. resampler.write(Buffer.from(data.pcm.buffer))
  128. }).on('end', () => {
  129. resampler.end()
  130. })
  131. // Pipe resampler into output stream on UI thread
  132. resampler.on('data', (data) => {
  133. data = toArrayBuffer(data) // postMessage can't transfer node's Buffer
  134. postMessage({
  135. voiceId: voiceId,
  136. target: target,
  137. buffer: data
  138. }, [data])
  139. }).on('end', () => {
  140. postMessage({
  141. voiceId: voiceId
  142. })
  143. })
  144. return [voiceId, stream.target]
  145. })
  146. registerEventProxy(id, user, 'remove')
  147. pushProp(id, user, 'channel', (it) => it ? it.id : it)
  148. let props = [
  149. 'uniqueId', 'username', 'mute', 'deaf', 'suppress', 'selfMute', 'selfDeaf',
  150. 'texture', 'textureHash', 'comment'
  151. ]
  152. for (let prop of props) {
  153. pushProp(id, user, prop)
  154. }
  155. return user.id
  156. }
  157. function setupClient (id, client) {
  158. id = { client: id }
  159. registerEventProxy(id, client, 'error')
  160. registerEventProxy(id, client, 'denied', it => [it])
  161. registerEventProxy(id, client, 'newChannel', (it) => [setupChannel(id, it)])
  162. registerEventProxy(id, client, 'newUser', (it) => [setupUser(id, it)])
  163. registerEventProxy(id, client, 'message', (sender, message, users, channels, trees) => {
  164. return [
  165. sender.id,
  166. message,
  167. users.map((it) => it.id),
  168. channels.map((it) => it.id),
  169. trees.map((it) => it.id)
  170. ]
  171. })
  172. client.on('dataPing', () => {
  173. pushProp(id, client, 'dataStats')
  174. })
  175. setupChannel(id, client.root)
  176. for (let user of client.users) {
  177. setupUser(id, user)
  178. }
  179. pushProp(id, client, 'root', (it) => it.id)
  180. pushProp(id, client, 'self', (it) => it.id)
  181. pushProp(id, client, 'welcomeMessage')
  182. pushProp(id, client, 'serverVersion')
  183. pushProp(id, client, 'maxBandwidth')
  184. }
  185. function onMessage (data) {
  186. let { reqId, method, payload } = data
  187. if (method === '_init') {
  188. sampleRate = data.sampleRate
  189. } else if (method === '_connect') {
  190. payload.args.codecs = CodecsBrowser
  191. mumbleConnect(payload.host, payload.args).then((client) => {
  192. let id = nextClientId++
  193. clients[id] = client
  194. setupClient(id, client)
  195. return id
  196. }).done((id) => {
  197. resolve(reqId, id)
  198. }, (err) => {
  199. reject(reqId, err)
  200. })
  201. } else if (data.clientId != null) {
  202. let client = clients[data.clientId]
  203. let target
  204. if (data.userId != null) {
  205. target = client.getUserById(data.userId)
  206. if (method === 'setChannel') {
  207. payload = [client.getChannelById(payload)]
  208. }
  209. } else if (data.channelId != null) {
  210. target = client.getChannelById(data.channelId)
  211. } else {
  212. target = client
  213. if (method === 'createVoiceStream') {
  214. let voiceId = payload.shift()
  215. let samplesPerPacket = payload.shift()
  216. let stream = target.createVoiceStream.apply(target, payload)
  217. setupOutboundVoice(voiceId, samplesPerPacket, stream)
  218. return
  219. }
  220. if (method === 'disconnect') {
  221. delete clients[data.clientId]
  222. }
  223. }
  224. target[method].apply(target, payload)
  225. } else if (data.voiceId != null) {
  226. let stream = voiceStreams[data.voiceId]
  227. let buffer = data.chunk
  228. if (buffer) {
  229. stream.write(Buffer.from(buffer))
  230. } else {
  231. delete voiceStreams[data.voiceId]
  232. stream.end()
  233. }
  234. }
  235. }
  236. self.addEventListener('message', (ev) => {
  237. try {
  238. onMessage(ev.data)
  239. } catch (ex) {
  240. console.error('exception during message event', ev.data, ex)
  241. }
  242. })
  243. export default null