worker.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  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', (actor, props) => {
  81. if (actor) {
  82. actor = actor.id
  83. }
  84. if (props.parent) {
  85. props.parent = props.parent.id
  86. }
  87. if (props.links) {
  88. props.links = props.links.map((it) => it.id)
  89. }
  90. return [actor, props]
  91. })
  92. registerEventProxy(id, channel, 'remove')
  93. pushProp(id, channel, 'parent', (it) => it ? it.id : it)
  94. pushProp(id, channel, 'links', (it) => it.map((it) => it.id))
  95. let props = [
  96. 'position', 'name', 'description'
  97. ]
  98. for (let prop of props) {
  99. pushProp(id, channel, prop)
  100. }
  101. for (let child of channel.children) {
  102. setupChannel(id, child)
  103. }
  104. return channel.id
  105. }
  106. function setupUser (id, user) {
  107. id = Object.assign({}, id, { user: user.id })
  108. registerEventProxy(id, user, 'update', (actor, props) => {
  109. if (actor) {
  110. actor = actor.id
  111. }
  112. if (props.channel != null) {
  113. props.channel = props.channel.id
  114. }
  115. return [actor, props]
  116. })
  117. registerEventProxy(id, user, 'voice', (stream) => {
  118. let voiceId = nextVoiceId++
  119. let target
  120. // We want to do as little on the UI thread as possible, so do resampling here as well
  121. var resampler = new Resampler({
  122. unsafe: true,
  123. type: Resampler.Type.ZERO_ORDER_HOLD,
  124. ratio: sampleRate / 48000
  125. })
  126. // Pipe stream into resampler
  127. stream.on('data', (data) => {
  128. // store target so we can pass it on after resampling
  129. target = data.target
  130. resampler.write(Buffer.from(data.pcm.buffer))
  131. }).on('end', () => {
  132. resampler.end()
  133. })
  134. // Pipe resampler into output stream on UI thread
  135. resampler.on('data', (data) => {
  136. data = toArrayBuffer(data) // postMessage can't transfer node's Buffer
  137. postMessage({
  138. voiceId: voiceId,
  139. target: target,
  140. buffer: data
  141. }, [data])
  142. }).on('end', () => {
  143. postMessage({
  144. voiceId: voiceId
  145. })
  146. })
  147. return [voiceId]
  148. })
  149. registerEventProxy(id, user, 'remove')
  150. pushProp(id, user, 'channel', (it) => it ? it.id : it)
  151. let props = [
  152. 'uniqueId', 'username', 'mute', 'deaf', 'suppress', 'selfMute', 'selfDeaf',
  153. 'texture', 'textureHash', 'comment'
  154. ]
  155. for (let prop of props) {
  156. pushProp(id, user, prop)
  157. }
  158. return user.id
  159. }
  160. function setupClient (id, client) {
  161. id = { client: id }
  162. registerEventProxy(id, client, 'error')
  163. registerEventProxy(id, client, 'newChannel', (it) => [setupChannel(id, it)])
  164. registerEventProxy(id, client, 'newUser', (it) => [setupUser(id, it)])
  165. registerEventProxy(id, client, 'message', (sender, message, users, channels, trees) => {
  166. return [
  167. sender.id,
  168. message,
  169. users.map((it) => it.id),
  170. channels.map((it) => it.id),
  171. trees.map((it) => it.id)
  172. ]
  173. })
  174. client.on('dataPing', () => {
  175. pushProp(id, client, 'dataStats')
  176. })
  177. setupChannel(id, client.root)
  178. for (let user of client.users) {
  179. setupUser(id, user)
  180. }
  181. pushProp(id, client, 'root', (it) => it.id)
  182. pushProp(id, client, 'self', (it) => it.id)
  183. pushProp(id, client, 'welcomeMessage')
  184. pushProp(id, client, 'serverVersion')
  185. pushProp(id, client, 'maxBandwidth')
  186. }
  187. function onMessage (data) {
  188. let { reqId, method, payload } = data
  189. if (method === '_init') {
  190. sampleRate = data.sampleRate
  191. } else if (method === '_connect') {
  192. payload.args.codecs = CodecsBrowser
  193. mumbleConnect(payload.host, payload.args).then((client) => {
  194. let id = nextClientId++
  195. clients[id] = client
  196. setupClient(id, client)
  197. return id
  198. }).done((id) => {
  199. resolve(reqId, id)
  200. }, (err) => {
  201. reject(reqId, err)
  202. })
  203. } else if (data.clientId != null) {
  204. let client = clients[data.clientId]
  205. let target
  206. if (data.userId != null) {
  207. target = client.getUserById(data.userId)
  208. if (method === 'setChannel') {
  209. payload = [client.getChannelById(payload)]
  210. }
  211. } else if (data.channelId != null) {
  212. target = client.getChannelById(data.channelId)
  213. } else {
  214. target = client
  215. if (method === 'createVoiceStream') {
  216. let voiceId = payload.shift()
  217. let samplesPerPacket = payload.shift()
  218. let stream = target.createVoiceStream.apply(target, payload)
  219. setupOutboundVoice(voiceId, samplesPerPacket, stream)
  220. return
  221. }
  222. if (method === 'disconnect') {
  223. delete clients[data.clientId]
  224. }
  225. }
  226. target[method].apply(target, payload)
  227. } else if (data.voiceId != null) {
  228. let stream = voiceStreams[data.voiceId]
  229. let buffer = data.chunk
  230. if (buffer) {
  231. stream.write(Buffer.from(buffer))
  232. } else {
  233. delete voiceStreams[data.voiceId]
  234. stream.end()
  235. }
  236. }
  237. }
  238. self.addEventListener('message', (ev) => {
  239. try {
  240. onMessage(ev.data)
  241. } catch (ex) {
  242. console.error('exception during message event', ev.data, ex)
  243. }
  244. })
  245. export default null