worker.js 7.6 KB

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