@dxos/plugin-thread 0.8.1-staging.9eaf14f → 0.8.2-main.10c050d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (407) hide show
  1. package/dist/lib/browser/ChannelContainer-XFNGSLHL.mjs +15 -0
  2. package/dist/lib/browser/ChatContainer-JWRI73OG.mjs +14 -0
  3. package/dist/lib/browser/app-graph-builder-K3CFBN7V.mjs +171 -0
  4. package/dist/lib/browser/app-graph-builder-K3CFBN7V.mjs.map +7 -0
  5. package/dist/lib/browser/call-manager-I4IKVCAK.mjs +23 -0
  6. package/dist/lib/browser/call-manager-I4IKVCAK.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-QDJ3Y6WF.mjs → chunk-5P5BH5GE.mjs} +57 -35
  8. package/dist/lib/browser/chunk-5P5BH5GE.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-5PGLGDIP.mjs +983 -0
  10. package/dist/lib/browser/chunk-5PGLGDIP.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-G3TS64FP.mjs +351 -0
  12. package/dist/lib/browser/chunk-G3TS64FP.mjs.map +7 -0
  13. package/dist/lib/browser/{chunk-WBJLUUOK.mjs → chunk-IIUTBDVB.mjs} +4 -4
  14. package/dist/lib/browser/chunk-IIUTBDVB.mjs.map +7 -0
  15. package/dist/lib/browser/chunk-IYY42OE2.mjs +545 -0
  16. package/dist/lib/browser/chunk-IYY42OE2.mjs.map +7 -0
  17. package/dist/lib/browser/chunk-JKQUJOTX.mjs +31 -0
  18. package/dist/lib/browser/chunk-JKQUJOTX.mjs.map +7 -0
  19. package/dist/lib/browser/chunk-N2V6MV6B.mjs +21 -0
  20. package/dist/lib/browser/chunk-N2V6MV6B.mjs.map +7 -0
  21. package/dist/lib/browser/{chunk-EP6MRLY7.mjs → chunk-R5MDLUPC.mjs} +5 -3
  22. package/dist/lib/browser/chunk-R5MDLUPC.mjs.map +7 -0
  23. package/dist/lib/browser/chunk-RJGJXXWK.mjs +1708 -0
  24. package/dist/lib/browser/chunk-RJGJXXWK.mjs.map +7 -0
  25. package/dist/lib/browser/chunk-WDOQXTI2.mjs +126 -0
  26. package/dist/lib/browser/chunk-WDOQXTI2.mjs.map +7 -0
  27. package/dist/lib/browser/index.mjs +107 -41
  28. package/dist/lib/browser/index.mjs.map +4 -4
  29. package/dist/lib/browser/{intent-resolver-QAY4FLLM.mjs → intent-resolver-YUYZCTLF.mjs} +102 -74
  30. package/dist/lib/browser/intent-resolver-YUYZCTLF.mjs.map +7 -0
  31. package/dist/lib/browser/markdown-4EC3OCF2.mjs +169 -0
  32. package/dist/lib/browser/markdown-4EC3OCF2.mjs.map +7 -0
  33. package/dist/lib/browser/meta.json +1 -1
  34. package/dist/lib/browser/react-root-YYDCVHGL.mjs +27 -0
  35. package/dist/lib/browser/react-root-YYDCVHGL.mjs.map +7 -0
  36. package/dist/lib/browser/react-surface-MEYBFPBC.mjs +108 -0
  37. package/dist/lib/browser/react-surface-MEYBFPBC.mjs.map +7 -0
  38. package/dist/lib/browser/{settings-4FEC7EAX.mjs → settings-KVBQQSO5.mjs} +5 -5
  39. package/dist/lib/browser/settings-KVBQQSO5.mjs.map +7 -0
  40. package/dist/lib/browser/{state-PNMSLKAH.mjs → state-RKL5MSDP.mjs} +7 -7
  41. package/dist/lib/browser/state-RKL5MSDP.mjs.map +7 -0
  42. package/dist/lib/browser/types/index.mjs +16 -0
  43. package/dist/lib/browser/types/index.mjs.map +7 -0
  44. package/dist/lib/node/ChannelContainer-F24RHJPX.cjs +36 -0
  45. package/dist/lib/node/ChannelContainer-F24RHJPX.cjs.map +7 -0
  46. package/dist/lib/node/ChatContainer-EE3FPSYO.cjs +35 -0
  47. package/dist/lib/node/ChatContainer-EE3FPSYO.cjs.map +7 -0
  48. package/dist/lib/node/app-graph-builder-IRE3GYKE.cjs +179 -0
  49. package/dist/lib/node/app-graph-builder-IRE3GYKE.cjs.map +7 -0
  50. package/dist/lib/node/call-manager-FZ4OMTB6.cjs +37 -0
  51. package/dist/lib/node/call-manager-FZ4OMTB6.cjs.map +7 -0
  52. package/dist/lib/node/chunk-4B3PPXA6.cjs +371 -0
  53. package/dist/lib/node/chunk-4B3PPXA6.cjs.map +7 -0
  54. package/dist/lib/node/{chunk-NQ5DTPOY.cjs → chunk-4YSNVPWU.cjs} +10 -8
  55. package/dist/lib/node/chunk-4YSNVPWU.cjs.map +7 -0
  56. package/dist/lib/node/chunk-6B3M6XRE.cjs +984 -0
  57. package/dist/lib/node/chunk-6B3M6XRE.cjs.map +7 -0
  58. package/dist/lib/node/chunk-AUIU6JGD.cjs +549 -0
  59. package/dist/lib/node/chunk-AUIU6JGD.cjs.map +7 -0
  60. package/dist/lib/node/chunk-ECIVLX56.cjs +1731 -0
  61. package/dist/lib/node/chunk-ECIVLX56.cjs.map +7 -0
  62. package/dist/lib/node/{chunk-S2A6BXEA.cjs → chunk-EYAUPLHT.cjs} +16 -10
  63. package/dist/lib/node/chunk-EYAUPLHT.cjs.map +7 -0
  64. package/dist/lib/node/{chunk-AKINTFBH.cjs → chunk-FUXJAVFZ.cjs} +50 -29
  65. package/dist/lib/node/chunk-FUXJAVFZ.cjs.map +7 -0
  66. package/dist/lib/node/{chunk-ONLWOUYZ.cjs → chunk-HLMCEE5K.cjs} +7 -7
  67. package/dist/lib/node/{chunk-ONLWOUYZ.cjs.map → chunk-HLMCEE5K.cjs.map} +2 -2
  68. package/dist/lib/node/chunk-LR5NENBE.cjs +54 -0
  69. package/dist/lib/node/chunk-LR5NENBE.cjs.map +7 -0
  70. package/dist/lib/node/chunk-TKHJVC2C.cjs +147 -0
  71. package/dist/lib/node/chunk-TKHJVC2C.cjs.map +7 -0
  72. package/dist/lib/node/index.cjs +142 -80
  73. package/dist/lib/node/index.cjs.map +4 -4
  74. package/dist/lib/node/{intent-resolver-S6FE5OR7.cjs → intent-resolver-MZ3HADNY.cjs} +108 -82
  75. package/dist/lib/node/intent-resolver-MZ3HADNY.cjs.map +7 -0
  76. package/dist/lib/node/markdown-NJDPXIKD.cjs +178 -0
  77. package/dist/lib/node/markdown-NJDPXIKD.cjs.map +7 -0
  78. package/dist/lib/node/meta.json +1 -1
  79. package/dist/lib/node/react-root-HQDBRGD3.cjs +51 -0
  80. package/dist/lib/node/react-root-HQDBRGD3.cjs.map +7 -0
  81. package/dist/lib/node/react-surface-KY5KFKVQ.cjs +122 -0
  82. package/dist/lib/node/react-surface-KY5KFKVQ.cjs.map +7 -0
  83. package/dist/lib/node/{settings-IXCPE55J.cjs → settings-H45P5YO2.cjs} +9 -9
  84. package/dist/lib/node/settings-H45P5YO2.cjs.map +7 -0
  85. package/dist/lib/node/{state-AT73KPGZ.cjs → state-IY5XX3H2.cjs} +10 -10
  86. package/dist/lib/node/state-IY5XX3H2.cjs.map +7 -0
  87. package/dist/lib/node/{types.cjs → types/index.cjs} +12 -6
  88. package/dist/lib/node/types/index.cjs.map +7 -0
  89. package/dist/lib/node-esm/ChannelContainer-AOXWXM53.mjs +16 -0
  90. package/dist/lib/node-esm/ChannelContainer-AOXWXM53.mjs.map +7 -0
  91. package/dist/lib/node-esm/ChatContainer-UJYPQMLV.mjs +15 -0
  92. package/dist/lib/node-esm/ChatContainer-UJYPQMLV.mjs.map +7 -0
  93. package/dist/lib/node-esm/app-graph-builder-I5VMPLE4.mjs +172 -0
  94. package/dist/lib/node-esm/app-graph-builder-I5VMPLE4.mjs.map +7 -0
  95. package/dist/lib/node-esm/call-manager-B3D3RK3R.mjs +24 -0
  96. package/dist/lib/node-esm/call-manager-B3D3RK3R.mjs.map +7 -0
  97. package/dist/lib/node-esm/chunk-CSO73XLB.mjs +546 -0
  98. package/dist/lib/node-esm/chunk-CSO73XLB.mjs.map +7 -0
  99. package/dist/lib/node-esm/{chunk-42F2DTMM.mjs → chunk-ESQC6SYE.mjs} +4 -4
  100. package/dist/lib/node-esm/chunk-ESQC6SYE.mjs.map +7 -0
  101. package/dist/lib/node-esm/chunk-J323VQ4P.mjs +127 -0
  102. package/dist/lib/node-esm/chunk-J323VQ4P.mjs.map +7 -0
  103. package/dist/lib/node-esm/chunk-JJLYSLLU.mjs +23 -0
  104. package/dist/lib/node-esm/chunk-JJLYSLLU.mjs.map +7 -0
  105. package/dist/lib/node-esm/{chunk-WLY2HMDB.mjs → chunk-K4KAUOKT.mjs} +5 -3
  106. package/dist/lib/node-esm/chunk-K4KAUOKT.mjs.map +7 -0
  107. package/dist/lib/node-esm/{chunk-J56FORYF.mjs → chunk-O3V45YJP.mjs} +57 -35
  108. package/dist/lib/node-esm/chunk-O3V45YJP.mjs.map +7 -0
  109. package/dist/lib/node-esm/chunk-PGRP3O4O.mjs +33 -0
  110. package/dist/lib/node-esm/chunk-PGRP3O4O.mjs.map +7 -0
  111. package/dist/lib/node-esm/chunk-POKXRTII.mjs +352 -0
  112. package/dist/lib/node-esm/chunk-POKXRTII.mjs.map +7 -0
  113. package/dist/lib/node-esm/chunk-WBNC7KMA.mjs +1710 -0
  114. package/dist/lib/node-esm/chunk-WBNC7KMA.mjs.map +7 -0
  115. package/dist/lib/node-esm/chunk-X6AUQDTE.mjs +984 -0
  116. package/dist/lib/node-esm/chunk-X6AUQDTE.mjs.map +7 -0
  117. package/dist/lib/node-esm/index.mjs +107 -41
  118. package/dist/lib/node-esm/index.mjs.map +4 -4
  119. package/dist/lib/node-esm/{intent-resolver-OURFRSSF.mjs → intent-resolver-CU22OPAH.mjs} +102 -74
  120. package/dist/lib/node-esm/intent-resolver-CU22OPAH.mjs.map +7 -0
  121. package/dist/lib/node-esm/markdown-HRGGO7IA.mjs +170 -0
  122. package/dist/lib/node-esm/markdown-HRGGO7IA.mjs.map +7 -0
  123. package/dist/lib/node-esm/meta.json +1 -1
  124. package/dist/lib/node-esm/react-root-MKN5SK3G.mjs +28 -0
  125. package/dist/lib/node-esm/react-root-MKN5SK3G.mjs.map +7 -0
  126. package/dist/lib/node-esm/react-surface-J2C7CVX7.mjs +109 -0
  127. package/dist/lib/node-esm/react-surface-J2C7CVX7.mjs.map +7 -0
  128. package/dist/lib/node-esm/{settings-ZUJQ4LWI.mjs → settings-IJGUOU3F.mjs} +5 -5
  129. package/dist/lib/node-esm/settings-IJGUOU3F.mjs.map +7 -0
  130. package/dist/lib/node-esm/{state-SSDX5O67.mjs → state-6AUTNROZ.mjs} +7 -7
  131. package/dist/lib/node-esm/state-6AUTNROZ.mjs.map +7 -0
  132. package/dist/lib/node-esm/types/index.mjs +17 -0
  133. package/dist/lib/node-esm/types/index.mjs.map +7 -0
  134. package/dist/types/src/ThreadPlugin.d.ts.map +1 -1
  135. package/dist/types/src/calls/call-manager.d.ts +77 -0
  136. package/dist/types/src/calls/call-manager.d.ts.map +1 -0
  137. package/dist/types/src/calls/call-swarm-synchronizer.d.ts +66 -0
  138. package/dist/types/src/calls/call-swarm-synchronizer.d.ts.map +1 -0
  139. package/dist/types/src/calls/index.d.ts +7 -0
  140. package/dist/types/src/calls/index.d.ts.map +1 -0
  141. package/dist/types/src/calls/media-manager.d.ts +60 -0
  142. package/dist/types/src/calls/media-manager.d.ts.map +1 -0
  143. package/dist/types/src/calls/speaking-monitor.d.ts +24 -0
  144. package/dist/types/src/calls/speaking-monitor.d.ts.map +1 -0
  145. package/dist/types/src/calls/types.d.ts +45 -0
  146. package/dist/types/src/calls/types.d.ts.map +1 -0
  147. package/dist/types/src/calls/util/audio.d.ts +6 -0
  148. package/dist/types/src/calls/util/audio.d.ts.map +1 -0
  149. package/dist/types/src/calls/util/calls-service.d.ts +72 -0
  150. package/dist/types/src/calls/util/calls-service.d.ts.map +1 -0
  151. package/dist/types/src/calls/util/history-cache.d.ts +11 -0
  152. package/dist/types/src/calls/util/history-cache.d.ts.map +1 -0
  153. package/dist/types/src/calls/util/index.d.ts +5 -0
  154. package/dist/types/src/calls/util/index.d.ts.map +1 -0
  155. package/dist/types/src/calls/util/media.d.ts +6 -0
  156. package/dist/types/src/calls/util/media.d.ts.map +1 -0
  157. package/dist/types/src/calls/util/stub-media-streams.d.ts +18 -0
  158. package/dist/types/src/calls/util/stub-media-streams.d.ts.map +1 -0
  159. package/dist/types/src/calls/util/task-scheduling.d.ts +17 -0
  160. package/dist/types/src/calls/util/task-scheduling.d.ts.map +1 -0
  161. package/dist/types/src/capabilities/app-graph-builder.d.ts +2 -179
  162. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  163. package/dist/types/src/capabilities/call-manager.d.ts +5 -0
  164. package/dist/types/src/capabilities/call-manager.d.ts.map +1 -0
  165. package/dist/types/src/capabilities/capabilities.d.ts +13 -1
  166. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  167. package/dist/types/src/capabilities/index.d.ts +8 -179
  168. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  169. package/dist/types/src/capabilities/intent-resolver.d.ts +2 -2
  170. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  171. package/dist/types/src/capabilities/markdown.d.ts +2 -2
  172. package/dist/types/src/capabilities/markdown.d.ts.map +1 -1
  173. package/dist/types/src/capabilities/react-root.d.ts +7 -0
  174. package/dist/types/src/capabilities/react-root.d.ts.map +1 -0
  175. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  176. package/dist/types/src/components/Call/Call.d.ts +17 -0
  177. package/dist/types/src/components/Call/Call.d.ts.map +1 -0
  178. package/dist/types/src/components/Call/Toolbar.d.ts +16 -0
  179. package/dist/types/src/components/Call/Toolbar.d.ts.map +1 -0
  180. package/dist/types/src/components/Call/index.d.ts +3 -0
  181. package/dist/types/src/components/Call/index.d.ts.map +1 -0
  182. package/dist/types/src/components/CallDebugPanel.d.ts +8 -0
  183. package/dist/types/src/components/CallDebugPanel.d.ts.map +1 -0
  184. package/dist/types/src/components/CallSidebar.d.ts +4 -0
  185. package/dist/types/src/components/CallSidebar.d.ts.map +1 -0
  186. package/dist/types/src/components/ChannelContainer.d.ts +14 -0
  187. package/dist/types/src/components/ChannelContainer.d.ts.map +1 -0
  188. package/dist/types/src/components/ChannelContainer.stories.d.ts +10 -0
  189. package/dist/types/src/components/ChannelContainer.stories.d.ts.map +1 -0
  190. package/dist/types/src/components/Chat.stories.d.ts.map +1 -1
  191. package/dist/types/src/components/ChatContainer.d.ts +9 -7
  192. package/dist/types/src/components/ChatContainer.d.ts.map +1 -1
  193. package/dist/types/src/components/CommentContainer.d.ts +11 -2
  194. package/dist/types/src/components/CommentContainer.d.ts.map +1 -1
  195. package/dist/types/src/components/Comments.stories.d.ts +1 -1
  196. package/dist/types/src/components/Comments.stories.d.ts.map +1 -1
  197. package/dist/types/src/components/CommentsContainer.d.ts +5 -9
  198. package/dist/types/src/components/CommentsContainer.d.ts.map +1 -1
  199. package/dist/types/src/components/Lobby/Lobby.d.ts +15 -0
  200. package/dist/types/src/components/Lobby/Lobby.d.ts.map +1 -0
  201. package/dist/types/src/components/Lobby/index.d.ts +2 -0
  202. package/dist/types/src/components/Lobby/index.d.ts.map +1 -0
  203. package/dist/types/src/components/Media/AudioStream.d.ts +6 -0
  204. package/dist/types/src/components/Media/AudioStream.d.ts.map +1 -0
  205. package/dist/types/src/components/Media/VideoObject.d.ts +15 -0
  206. package/dist/types/src/components/Media/VideoObject.d.ts.map +1 -0
  207. package/dist/types/src/components/Media/VideoObject.stories.d.ts +8 -0
  208. package/dist/types/src/components/Media/VideoObject.stories.d.ts.map +1 -0
  209. package/dist/types/src/components/Media/index.d.ts +3 -0
  210. package/dist/types/src/components/Media/index.d.ts.map +1 -0
  211. package/dist/types/src/components/MessageContainer.d.ts +5 -4
  212. package/dist/types/src/components/MessageContainer.d.ts.map +1 -1
  213. package/dist/types/src/components/Participant/Participant.d.ts +6 -0
  214. package/dist/types/src/components/Participant/Participant.d.ts.map +1 -0
  215. package/dist/types/src/components/Participant/ParticipantGrid.d.ts +11 -0
  216. package/dist/types/src/components/Participant/ParticipantGrid.d.ts.map +1 -0
  217. package/dist/types/src/components/Participant/index.d.ts +3 -0
  218. package/dist/types/src/components/Participant/index.d.ts.map +1 -0
  219. package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.d.ts +12 -0
  220. package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.d.ts.map +1 -0
  221. package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.stories.d.ts +10 -0
  222. package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.stories.d.ts.map +1 -0
  223. package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.d.ts +32 -0
  224. package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.d.ts.map +1 -0
  225. package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.stories.d.ts +23 -0
  226. package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.stories.d.ts.map +1 -0
  227. package/dist/types/src/components/ResponsiveGrid/ResponsiveGridItem.d.ts +21 -0
  228. package/dist/types/src/components/ResponsiveGrid/ResponsiveGridItem.d.ts.map +1 -0
  229. package/dist/types/src/components/ResponsiveGrid/index.d.ts +4 -0
  230. package/dist/types/src/components/ResponsiveGrid/index.d.ts.map +1 -0
  231. package/dist/types/src/components/ThreadComplementary.d.ts.map +1 -1
  232. package/dist/types/src/components/ThreadSettings.d.ts.map +1 -1
  233. package/dist/types/src/components/index.d.ts +5 -1
  234. package/dist/types/src/components/index.d.ts.map +1 -1
  235. package/dist/types/src/components/testing.d.ts +101 -54
  236. package/dist/types/src/components/testing.d.ts.map +1 -1
  237. package/dist/types/src/extensions/threads.d.ts.map +1 -1
  238. package/dist/types/src/hooks/analytics.d.ts +3 -3
  239. package/dist/types/src/hooks/analytics.d.ts.map +1 -1
  240. package/dist/types/src/hooks/index.d.ts +2 -1
  241. package/dist/types/src/hooks/index.d.ts.map +1 -1
  242. package/dist/types/src/hooks/useDebugMode.d.ts +2 -0
  243. package/dist/types/src/hooks/useDebugMode.d.ts.map +1 -0
  244. package/dist/types/src/hooks/useStatus.d.ts.map +1 -1
  245. package/dist/types/src/index.d.ts +1 -1
  246. package/dist/types/src/index.d.ts.map +1 -1
  247. package/dist/types/src/meta.d.ts.map +1 -1
  248. package/dist/types/src/peers.stories.d.ts +11 -0
  249. package/dist/types/src/peers.stories.d.ts.map +1 -0
  250. package/dist/types/src/testing/index.d.ts +2 -0
  251. package/dist/types/src/testing/index.d.ts.map +1 -0
  252. package/dist/types/src/testing/stories.d.ts +2 -0
  253. package/dist/types/src/testing/stories.d.ts.map +1 -0
  254. package/dist/types/src/translations.d.ts +35 -56
  255. package/dist/types/src/translations.d.ts.map +1 -1
  256. package/dist/types/src/types/index.d.ts +3 -0
  257. package/dist/types/src/types/index.d.ts.map +1 -0
  258. package/dist/types/src/types/schema.d.ts +317 -0
  259. package/dist/types/src/types/schema.d.ts.map +1 -0
  260. package/dist/types/src/types/types.d.ts +1015 -0
  261. package/dist/types/src/types/types.d.ts.map +1 -0
  262. package/dist/types/src/util.d.ts +2 -0
  263. package/dist/types/src/util.d.ts.map +1 -1
  264. package/dist/types/tsconfig.tsbuildinfo +1 -1
  265. package/package.json +60 -40
  266. package/src/ThreadPlugin.tsx +33 -14
  267. package/src/calls/call-manager.ts +242 -0
  268. package/src/calls/call-swarm-synchronizer.ts +350 -0
  269. package/src/calls/index.ts +10 -0
  270. package/src/calls/media-manager.ts +342 -0
  271. package/src/calls/speaking-monitor.ts +85 -0
  272. package/src/calls/types.ts +70 -0
  273. package/src/calls/util/audio.ts +58 -0
  274. package/src/calls/util/calls-service.ts +527 -0
  275. package/src/calls/util/history-cache.ts +27 -0
  276. package/src/calls/util/index.ts +8 -0
  277. package/src/calls/util/media.ts +52 -0
  278. package/src/calls/util/stub-media-streams.ts +73 -0
  279. package/src/calls/util/task-scheduling.ts +92 -0
  280. package/src/capabilities/app-graph-builder.ts +171 -25
  281. package/src/capabilities/call-manager.ts +19 -0
  282. package/src/capabilities/capabilities.ts +15 -1
  283. package/src/capabilities/index.ts +2 -0
  284. package/src/capabilities/intent-resolver.ts +77 -56
  285. package/src/capabilities/markdown.ts +54 -4
  286. package/src/capabilities/react-root.tsx +17 -0
  287. package/src/capabilities/react-surface.tsx +52 -27
  288. package/src/capabilities/settings.ts +2 -2
  289. package/src/capabilities/state.ts +3 -3
  290. package/src/components/Call/Call.tsx +88 -0
  291. package/src/components/Call/Toolbar.tsx +182 -0
  292. package/src/components/Call/index.ts +6 -0
  293. package/src/components/CallDebugPanel.tsx +115 -0
  294. package/src/components/CallSidebar.tsx +17 -0
  295. package/src/components/ChannelContainer.stories.tsx +58 -0
  296. package/src/components/ChannelContainer.tsx +203 -0
  297. package/src/components/Chat.stories.tsx +11 -12
  298. package/src/components/ChatContainer.tsx +37 -25
  299. package/src/components/CommentContainer.tsx +56 -61
  300. package/src/components/Comments.stories.tsx +20 -16
  301. package/src/components/CommentsContainer.tsx +20 -36
  302. package/src/components/Lobby/Lobby.tsx +119 -0
  303. package/src/components/Lobby/index.ts +5 -0
  304. package/src/components/Media/AudioStream.tsx +67 -0
  305. package/src/components/Media/VideoObject.stories.tsx +52 -0
  306. package/src/components/Media/VideoObject.tsx +58 -0
  307. package/src/components/Media/index.ts +6 -0
  308. package/src/components/MessageContainer.tsx +31 -50
  309. package/src/components/Participant/Participant.tsx +57 -0
  310. package/src/components/Participant/ParticipantGrid.tsx +88 -0
  311. package/src/components/Participant/index.ts +6 -0
  312. package/src/components/ResponsiveGrid/ResponsiveContainer.stories.tsx +54 -0
  313. package/src/components/ResponsiveGrid/ResponsiveContainer.tsx +31 -0
  314. package/src/components/ResponsiveGrid/ResponsiveGrid.stories.tsx +156 -0
  315. package/src/components/ResponsiveGrid/ResponsiveGrid.tsx +341 -0
  316. package/src/components/ResponsiveGrid/ResponsiveGridItem.tsx +131 -0
  317. package/src/components/ResponsiveGrid/index.ts +7 -0
  318. package/src/components/ThreadComplementary.tsx +37 -42
  319. package/src/components/index.ts +4 -0
  320. package/src/components/testing.ts +6 -5
  321. package/src/extensions/threads.ts +42 -24
  322. package/src/hooks/analytics.ts +4 -4
  323. package/src/hooks/index.ts +2 -1
  324. package/src/hooks/useDebugMode.ts +25 -0
  325. package/src/index.ts +1 -1
  326. package/src/meta.ts +4 -3
  327. package/src/peers.stories.tsx +189 -0
  328. package/src/testing/index.ts +5 -0
  329. package/src/testing/stories.ts +50 -0
  330. package/src/translations.ts +46 -5
  331. package/src/types/index.ts +6 -0
  332. package/src/types/schema.ts +29 -0
  333. package/src/types/types.ts +110 -0
  334. package/src/typings/peermetrics-webrtc-stats.d.ts +30 -0
  335. package/src/util.ts +12 -0
  336. package/dist/lib/browser/ChatContainer-IOOAQW6S.mjs +0 -138
  337. package/dist/lib/browser/ChatContainer-IOOAQW6S.mjs.map +0 -7
  338. package/dist/lib/browser/app-graph-builder-6W4UKJDI.mjs +0 -41
  339. package/dist/lib/browser/app-graph-builder-6W4UKJDI.mjs.map +0 -7
  340. package/dist/lib/browser/chunk-EP6MRLY7.mjs.map +0 -7
  341. package/dist/lib/browser/chunk-QDJ3Y6WF.mjs.map +0 -7
  342. package/dist/lib/browser/chunk-S5ZR4FYW.mjs +0 -17
  343. package/dist/lib/browser/chunk-S5ZR4FYW.mjs.map +0 -7
  344. package/dist/lib/browser/chunk-TFVPUPOO.mjs +0 -207
  345. package/dist/lib/browser/chunk-TFVPUPOO.mjs.map +0 -7
  346. package/dist/lib/browser/chunk-WBJLUUOK.mjs.map +0 -7
  347. package/dist/lib/browser/chunk-WOG72DWE.mjs +0 -93
  348. package/dist/lib/browser/chunk-WOG72DWE.mjs.map +0 -7
  349. package/dist/lib/browser/intent-resolver-QAY4FLLM.mjs.map +0 -7
  350. package/dist/lib/browser/markdown-7KXBXCUS.mjs +0 -111
  351. package/dist/lib/browser/markdown-7KXBXCUS.mjs.map +0 -7
  352. package/dist/lib/browser/react-surface-MT3OWULR.mjs +0 -437
  353. package/dist/lib/browser/react-surface-MT3OWULR.mjs.map +0 -7
  354. package/dist/lib/browser/settings-4FEC7EAX.mjs.map +0 -7
  355. package/dist/lib/browser/state-PNMSLKAH.mjs.map +0 -7
  356. package/dist/lib/browser/types.mjs +0 -10
  357. package/dist/lib/node/ChatContainer-4FNDRF3G.cjs +0 -163
  358. package/dist/lib/node/ChatContainer-4FNDRF3G.cjs.map +0 -7
  359. package/dist/lib/node/app-graph-builder-HZNWT4QU.cjs +0 -57
  360. package/dist/lib/node/app-graph-builder-HZNWT4QU.cjs.map +0 -7
  361. package/dist/lib/node/chunk-46BSFPBV.cjs +0 -113
  362. package/dist/lib/node/chunk-46BSFPBV.cjs.map +0 -7
  363. package/dist/lib/node/chunk-AKINTFBH.cjs.map +0 -7
  364. package/dist/lib/node/chunk-E7Z3BKRQ.cjs +0 -232
  365. package/dist/lib/node/chunk-E7Z3BKRQ.cjs.map +0 -7
  366. package/dist/lib/node/chunk-NQ5DTPOY.cjs.map +0 -7
  367. package/dist/lib/node/chunk-S2A6BXEA.cjs.map +0 -7
  368. package/dist/lib/node/intent-resolver-S6FE5OR7.cjs.map +0 -7
  369. package/dist/lib/node/markdown-L2WMCQKM.cjs +0 -121
  370. package/dist/lib/node/markdown-L2WMCQKM.cjs.map +0 -7
  371. package/dist/lib/node/react-surface-Z45BYECE.cjs +0 -439
  372. package/dist/lib/node/react-surface-Z45BYECE.cjs.map +0 -7
  373. package/dist/lib/node/settings-IXCPE55J.cjs.map +0 -7
  374. package/dist/lib/node/state-AT73KPGZ.cjs.map +0 -7
  375. package/dist/lib/node/types.cjs.map +0 -7
  376. package/dist/lib/node-esm/ChatContainer-NYLCL6QA.mjs +0 -139
  377. package/dist/lib/node-esm/ChatContainer-NYLCL6QA.mjs.map +0 -7
  378. package/dist/lib/node-esm/app-graph-builder-M3KK5QJH.mjs +0 -42
  379. package/dist/lib/node-esm/app-graph-builder-M3KK5QJH.mjs.map +0 -7
  380. package/dist/lib/node-esm/chunk-42F2DTMM.mjs.map +0 -7
  381. package/dist/lib/node-esm/chunk-ADTVMESP.mjs +0 -208
  382. package/dist/lib/node-esm/chunk-ADTVMESP.mjs.map +0 -7
  383. package/dist/lib/node-esm/chunk-E446MGLP.mjs +0 -94
  384. package/dist/lib/node-esm/chunk-E446MGLP.mjs.map +0 -7
  385. package/dist/lib/node-esm/chunk-J56FORYF.mjs.map +0 -7
  386. package/dist/lib/node-esm/chunk-WLY2HMDB.mjs.map +0 -7
  387. package/dist/lib/node-esm/chunk-WSE23GSG.mjs +0 -19
  388. package/dist/lib/node-esm/chunk-WSE23GSG.mjs.map +0 -7
  389. package/dist/lib/node-esm/intent-resolver-OURFRSSF.mjs.map +0 -7
  390. package/dist/lib/node-esm/markdown-ZKU23UVD.mjs +0 -112
  391. package/dist/lib/node-esm/markdown-ZKU23UVD.mjs.map +0 -7
  392. package/dist/lib/node-esm/react-surface-KIJ767JK.mjs +0 -438
  393. package/dist/lib/node-esm/react-surface-KIJ767JK.mjs.map +0 -7
  394. package/dist/lib/node-esm/settings-ZUJQ4LWI.mjs.map +0 -7
  395. package/dist/lib/node-esm/state-SSDX5O67.mjs.map +0 -7
  396. package/dist/lib/node-esm/types.mjs +0 -11
  397. package/dist/types/src/components/types.d.ts +0 -28
  398. package/dist/types/src/components/types.d.ts.map +0 -1
  399. package/dist/types/src/events.d.ts +0 -4
  400. package/dist/types/src/events.d.ts.map +0 -1
  401. package/dist/types/src/types.d.ts +0 -125
  402. package/dist/types/src/types.d.ts.map +0 -1
  403. package/src/components/types.ts +0 -32
  404. package/src/events.ts +0 -11
  405. package/src/types.ts +0 -100
  406. /package/dist/lib/browser/{types.mjs.map → ChannelContainer-XFNGSLHL.mjs.map} +0 -0
  407. /package/dist/lib/{node-esm/types.mjs.map → browser/ChatContainer-JWRI73OG.mjs.map} +0 -0
@@ -0,0 +1,350 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import md5Hex from 'md5-hex';
6
+
7
+ import { DeferredTask, Event, scheduleTaskInterval, synchronized } from '@dxos/async';
8
+ import { type Identity } from '@dxos/client/halo';
9
+ import { Context, Resource } from '@dxos/context';
10
+ import { generateName } from '@dxos/display-name';
11
+ import { invariant } from '@dxos/invariant';
12
+ import { PublicKey } from '@dxos/keys';
13
+ import { log } from '@dxos/log';
14
+ import { buf } from '@dxos/protocols/buf';
15
+ import { ActivitySchema } from '@dxos/protocols/buf/dxos/edge/calls_pb';
16
+ import { type Device, type NetworkService } from '@dxos/protocols/proto/dxos/client/services';
17
+ import { ConnectionState, type SwarmResponse } from '@dxos/protocols/proto/dxos/edge/messenger';
18
+ import { isNonNullable } from '@dxos/util';
19
+
20
+ import { codec, type ActivityState, type UserState } from './types';
21
+
22
+ export type CallState = {
23
+ /**
24
+ * Room id.
25
+ */
26
+ roomId?: string;
27
+
28
+ /**
29
+ * All users states in the swarm.
30
+ */
31
+ users?: UserState[];
32
+
33
+ /**
34
+ * Self state inside swarm.
35
+ */
36
+ self?: UserState;
37
+
38
+ /**
39
+ * Swarm synchronized CRDT transcription. Last write wins.
40
+ */
41
+ activities?: Record<string, ActivityState>;
42
+
43
+ raisedHand?: boolean;
44
+ speaking?: boolean;
45
+ joined?: boolean;
46
+
47
+ /**
48
+ * Tracks pushed by self to calls service.
49
+ */
50
+ tracks?: UserState['tracks'];
51
+ };
52
+
53
+ export type CallSwarmSynchronizerParams = { networkService: NetworkService };
54
+
55
+ /**
56
+ * Period for peer to reconnect to the call swarm gracefully if connection was lost abruptly.
57
+ */
58
+ const DISCONNECTED_ABRUPT_TIMEOUT = 10_000; // [ms]
59
+
60
+ /**
61
+ * Sends and receives state to/from Swarm network.
62
+ */
63
+ export class CallSwarmSynchronizer extends Resource {
64
+ public readonly stateUpdated = new Event<CallState>();
65
+
66
+ private readonly _state: CallState = { activities: {} };
67
+ private readonly _networkService: NetworkService;
68
+ private _lastSwarmEvent?: SwarmResponse = undefined;
69
+ private _reconcileSwarmStateTask?: DeferredTask = undefined;
70
+
71
+ private _identityKey?: string = undefined;
72
+ private _deviceKey?: string = undefined;
73
+ private _displayName?: string = undefined;
74
+
75
+ private _swarmCtx?: Context = undefined;
76
+
77
+ private _sendStateTask?: DeferredTask = undefined;
78
+
79
+ constructor({ networkService }: CallSwarmSynchronizerParams) {
80
+ super();
81
+ this._networkService = networkService;
82
+ }
83
+
84
+ /**
85
+ * @internal
86
+ */
87
+ _getState(): CallState {
88
+ return this._state;
89
+ }
90
+
91
+ setRaisedHand(raisedHand: boolean): void {
92
+ if (this._state.raisedHand === raisedHand) {
93
+ return;
94
+ }
95
+
96
+ this._state.raisedHand = raisedHand;
97
+ this._notifyAndSchedule();
98
+ }
99
+
100
+ setSpeaking(speaking: boolean): void {
101
+ if (this._state.speaking === speaking) {
102
+ return;
103
+ }
104
+
105
+ this._state.speaking = speaking;
106
+ this._notifyAndSchedule();
107
+ }
108
+
109
+ setJoined(joined: boolean): void {
110
+ if (this._state.joined === joined) {
111
+ return;
112
+ }
113
+
114
+ this._state.joined = joined;
115
+ this._notifyAndSchedule();
116
+ }
117
+
118
+ setTracks(tracks: UserState['tracks']): void {
119
+ this._state.tracks = { ...this._state.tracks, ...tracks };
120
+ this._notifyAndSchedule();
121
+ }
122
+
123
+ setActivity(key: string, payload: ActivityState['payload']): void {
124
+ const lamportTimestamp = LamportTimestampCrdt.increment(
125
+ this._state.activities?.[key]?.lamportTimestamp ?? { id: this._deviceKey },
126
+ );
127
+ this._state.activities![key] = { lamportTimestamp, payload };
128
+
129
+ this._notifyAndSchedule();
130
+ }
131
+
132
+ /**
133
+ * @internal
134
+ */
135
+ _setIdentity(identity: Identity): void {
136
+ this._identityKey = identity.identityKey.toHex();
137
+ this._displayName = identity.profile?.displayName ?? generateName(identity.identityKey.toHex());
138
+ }
139
+
140
+ /**
141
+ * @internal
142
+ */
143
+ _setDevice(device: Device): void {
144
+ this._deviceKey = device.deviceKey.toHex();
145
+ }
146
+
147
+ /**
148
+ * @internal
149
+ */
150
+ _setRoomId(roomId?: string): void {
151
+ if (this._swarmCtx) {
152
+ log.verbose('joined to a different room', { roomId });
153
+ return;
154
+ }
155
+
156
+ this._state.roomId = roomId;
157
+ }
158
+
159
+ protected override async _open(): Promise<void> {
160
+ this._sendStateTask = new DeferredTask(this._ctx, async () => {
161
+ await this._sendState();
162
+ });
163
+
164
+ this._reconcileSwarmStateTask = new DeferredTask(this._ctx, async () => {
165
+ await this._reconcileSwarmState();
166
+ });
167
+ }
168
+
169
+ protected override async _close(): Promise<void> {
170
+ this._sendStateTask = undefined;
171
+ this._reconcileSwarmStateTask = undefined;
172
+ }
173
+
174
+ @synchronized
175
+ async join(): Promise<void> {
176
+ invariant(this._state.roomId);
177
+ invariant(!this._swarmCtx, 'Already joined');
178
+
179
+ this._swarmCtx = new Context();
180
+ const topic = getTopic(this._state.roomId);
181
+ log('joining swarm', { topic, peer: { identityKey: this._identityKey, peerKey: this._deviceKey } });
182
+ const stream = this._networkService.subscribeSwarmState({ topic });
183
+ stream.subscribe((event) => this._processSwarmEvent(event));
184
+
185
+ const cleanup = () => {
186
+ void stream.close();
187
+ if (topic && this._identityKey && this._deviceKey) {
188
+ log('leaving swarm', { topic, peer: { identityKey: this._identityKey, peerKey: this._deviceKey } });
189
+ void this._networkService
190
+ .leaveSwarm({
191
+ topic,
192
+ peer: { identityKey: this._identityKey, peerKey: this._deviceKey },
193
+ })
194
+ .catch((err) => log.catch(err));
195
+ }
196
+ window.removeEventListener('beforeunload', cleanup);
197
+ };
198
+ window.addEventListener('beforeunload', cleanup);
199
+ this._swarmCtx.onDispose(cleanup);
200
+
201
+ this._notifyAndSchedule();
202
+ }
203
+
204
+ @synchronized
205
+ async leave(): Promise<void> {
206
+ if (!this._swarmCtx) {
207
+ return;
208
+ }
209
+
210
+ await this._swarmCtx.dispose();
211
+ this._swarmCtx = undefined;
212
+
213
+ this._state.users = undefined;
214
+ this._state.self = undefined;
215
+ }
216
+
217
+ async querySwarm(roomId: string) {
218
+ const topic = getTopic(roomId);
219
+ const swarm = await this._networkService.querySwarm({ topic });
220
+ return swarm.peers ?? [];
221
+ }
222
+
223
+ /**
224
+ * Notify and schedule send state task.
225
+ */
226
+ private _notifyAndSchedule(): void {
227
+ this.stateUpdated.emit(this._state);
228
+ if (this._state.joined) {
229
+ this._sendStateTask?.schedule();
230
+ }
231
+ }
232
+
233
+ private async _sendState(): Promise<void> {
234
+ if (!this._state.roomId || !this._identityKey || !this._deviceKey || !this._state.joined) {
235
+ return;
236
+ }
237
+
238
+ const state: UserState = {
239
+ ...this._state,
240
+ id: this._deviceKey,
241
+ name: this._displayName,
242
+ activities: this._state.activities
243
+ ? Object.fromEntries(
244
+ Object.entries(this._state.activities).map(([key, activity]) => [
245
+ key,
246
+ buf.create(ActivitySchema, activity),
247
+ ]),
248
+ )
249
+ : {},
250
+ };
251
+
252
+ await this._networkService.joinSwarm({
253
+ topic: getTopic(this._state.roomId),
254
+ peer: {
255
+ identityKey: this._identityKey,
256
+ peerKey: this._deviceKey,
257
+ state: codec.encode(state),
258
+ },
259
+ });
260
+ }
261
+
262
+ private _processSwarmEvent(swarmEvent: SwarmResponse): void {
263
+ if (!this._state.joined) {
264
+ return;
265
+ }
266
+
267
+ this._lastSwarmEvent = swarmEvent;
268
+ this._reconcileSwarmStateTask!.schedule();
269
+ }
270
+
271
+ private async _reconcileSwarmState(): Promise<void> {
272
+ const swarmEvent = this._lastSwarmEvent;
273
+ invariant(swarmEvent);
274
+ // We include inactive peers that were disconnected abruptly and have not yet reconnected to not drop them from call.
275
+ // Websocket connection could be unstable and we include graceful period for peers to reconnect.
276
+ const peers = [
277
+ ...(swarmEvent.peers ?? []),
278
+ ...(swarmEvent.inactivePeers ?? []).filter(
279
+ ({ disconnected, connectionState }) =>
280
+ connectionState === ConnectionState.DISCONNECTED_ABRUPT &&
281
+ disconnected &&
282
+ disconnected > Date.now() - DISCONNECTED_ABRUPT_TIMEOUT,
283
+ ),
284
+ ].sort((peer1, peer2) => peer1.peerKey.localeCompare(peer2.peerKey));
285
+ const users = peers
286
+ .map((peer) => {
287
+ try {
288
+ return codec.decode(peer.state!);
289
+ } catch (error) {
290
+ log.error('error decoding peer state', { error, peer });
291
+ return undefined;
292
+ }
293
+ })
294
+ .filter(isNonNullable);
295
+ this._state.users = users;
296
+ this._state.self = users.find((user) => user.id === this._deviceKey);
297
+ log('reconciling swarm state', { users });
298
+ if (users.length === 0) {
299
+ return;
300
+ }
301
+
302
+ const activityKeys = new Set<string>(users.flatMap((user) => Object.keys(user.activities)));
303
+ [...activityKeys].forEach((key) => {
304
+ const activities = users.map((user) => user.activities?.[key]).filter(isNonNullable);
305
+ const lastActivity = LamportTimestampCrdt.getLastState(activities);
306
+ if (
307
+ lastActivity &&
308
+ lastActivity.lamportTimestamp!.version! > (this._state.activities![key]?.lamportTimestamp?.version ?? 0)
309
+ ) {
310
+ this._state.activities![key] = lastActivity;
311
+ }
312
+ });
313
+ this.stateUpdated.emit(this._state);
314
+
315
+ // Schedule next reconcile if there are inactive peer to drop them after timeout.
316
+ if (peers.some((peer) => peer.connectionState !== ConnectionState.CONNECTED)) {
317
+ scheduleTaskInterval(
318
+ this._ctx,
319
+ async () => this._reconcileSwarmStateTask!.schedule(),
320
+ Math.min(DISCONNECTED_ABRUPT_TIMEOUT / 2, 1000),
321
+ );
322
+ }
323
+ }
324
+ }
325
+
326
+ type LamportTimestamp = ActivityState['lamportTimestamp'];
327
+
328
+ // TODO(mykola): Factor out.
329
+ class LamportTimestampCrdt {
330
+ static getLastState<T extends { lamportTimestamp?: LamportTimestamp }>(states: T[]): T | undefined {
331
+ const maxTimestamp = Math.max(...states.map((state) => state.lamportTimestamp?.version ?? 0));
332
+ const sortedStates = states
333
+ .filter(
334
+ (state) =>
335
+ state.lamportTimestamp && state.lamportTimestamp.version === maxTimestamp && state.lamportTimestamp.id,
336
+ )
337
+ .sort((a, b) => a.lamportTimestamp!.id!.localeCompare(b.lamportTimestamp!.id!));
338
+ return sortedStates[0];
339
+ }
340
+
341
+ static increment(timestamp: LamportTimestamp): LamportTimestamp {
342
+ return {
343
+ ...(timestamp ?? {}),
344
+ version: (timestamp?.version ?? 0) + 1,
345
+ };
346
+ }
347
+ }
348
+
349
+ // TODO(mykola): Delete once we refactor swarm to accept string as topic.
350
+ const getTopic = (roomId: string): PublicKey => PublicKey.fromHex(md5Hex(roomId));
@@ -0,0 +1,10 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './call-manager';
6
+ export * from './call-swarm-synchronizer';
7
+ export * from './media-manager';
8
+ export * from './speaking-monitor';
9
+ export * from './types';
10
+ export * from './util';
@@ -0,0 +1,342 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { DeferredTask, Event, sleep, synchronized } from '@dxos/async';
6
+ import { cancelWithContext, type Context, Resource } from '@dxos/context';
7
+ import { log } from '@dxos/log';
8
+
9
+ import { SpeakingMonitor } from './speaking-monitor';
10
+ import { TrackNameCodec, type EncodedTrackName, type TrackObject } from './types';
11
+ import {
12
+ type CallsServiceConfig,
13
+ CallsServicePeer,
14
+ createBlackCanvasStreamTrack,
15
+ createInaudibleAudioStreamTrack,
16
+ getScreenshare,
17
+ getUserMediaTrack,
18
+ } from './util';
19
+
20
+ export type MediaState = {
21
+ audioDeviceId?: string;
22
+ audioEnabled?: boolean;
23
+ audioTrack?: MediaStreamTrack;
24
+
25
+ videoDeviceId?: string;
26
+ videoEnabled?: boolean;
27
+ videoTrack?: MediaStreamTrack;
28
+ videoStream?: MediaStream;
29
+
30
+ screenshareEnabled?: boolean;
31
+ screenshareTrack?: MediaStreamTrack;
32
+ screenshareVideoStream?: MediaStream;
33
+
34
+ pushedVideoTrack?: TrackObject;
35
+ pushedAudioTrack?: TrackObject;
36
+ pushedScreenshareTrack?: TrackObject;
37
+ peer?: CallsServicePeer;
38
+
39
+ pulledAudioTracks: Record<EncodedTrackName, { track: MediaStreamTrack; ctx: Context }>;
40
+ /**
41
+ * Includes video feeds and screenshare feeds of other participants.
42
+ */
43
+ pulledVideoStreams: Record<EncodedTrackName, { stream: MediaStream; ctx: Context }>;
44
+ };
45
+
46
+ // TOOD(burdon): Hard coded.
47
+ const VIDEO_WIDTH = 1280;
48
+ const VIDEO_HEIGHT = 720;
49
+ const MAX_WEB_CAM_FRAMERATE = 24;
50
+ const MAX_WEB_CAM_BITRATE = 120_0000;
51
+ const RETRY_INTERVAL = 500;
52
+
53
+ export type MediaManagerParams = {
54
+ serviceConfig: CallsServiceConfig;
55
+ };
56
+
57
+ export class MediaManager extends Resource {
58
+ public readonly stateUpdated = new Event<MediaState>();
59
+ private readonly _state: MediaState = { pulledVideoStreams: {}, pulledAudioTracks: {} };
60
+ private _speakingMonitor?: SpeakingMonitor = undefined;
61
+
62
+ private _trackToReconcile: EncodedTrackName[] = [];
63
+ private _blackCanvasStreamTrack?: MediaStreamTrack = undefined;
64
+ private _inaudibleAudioStreamTrack?: MediaStreamTrack = undefined;
65
+ private _pushTracksTask?: DeferredTask = undefined;
66
+ private _pullTracksTask?: DeferredTask = undefined;
67
+
68
+ get isSpeaking() {
69
+ return this._speakingMonitor?.isSpeaking;
70
+ }
71
+
72
+ /**
73
+ * @internal
74
+ */
75
+ _getState(): MediaState {
76
+ return this._state;
77
+ }
78
+
79
+ protected override async _open(): Promise<void> {
80
+ this._blackCanvasStreamTrack = await createBlackCanvasStreamTrack({
81
+ ctx: this._ctx,
82
+ width: VIDEO_WIDTH,
83
+ height: VIDEO_HEIGHT,
84
+ });
85
+ this._state.videoTrack = this._blackCanvasStreamTrack;
86
+ this._state.videoStream = new MediaStream();
87
+ this._state.videoStream.addTrack(this._state.videoTrack);
88
+
89
+ this._inaudibleAudioStreamTrack = await createInaudibleAudioStreamTrack({ ctx: this._ctx });
90
+ this._state.audioTrack = this._inaudibleAudioStreamTrack;
91
+
92
+ this._pushTracksTask = new DeferredTask(this._ctx, async () => {
93
+ await this._pushTracks();
94
+ });
95
+ this._pullTracksTask = new DeferredTask(this._ctx, async () => {
96
+ await this._reconcilePulledMedia();
97
+ });
98
+ }
99
+
100
+ protected override async _close(): Promise<void> {
101
+ void this._speakingMonitor?.close();
102
+ this._state.videoTrack && this._state.videoStream?.removeTrack(this._state.videoTrack);
103
+ this._state.audioTrack?.stop();
104
+ this._state.videoTrack?.stop();
105
+ this._state.screenshareTrack?.stop();
106
+ this._pushTracksTask = undefined;
107
+ this._pullTracksTask = undefined;
108
+ }
109
+
110
+ @synchronized
111
+ async join(serviceConfig: CallsServiceConfig): Promise<void> {
112
+ this._state.peer = new CallsServicePeer(serviceConfig);
113
+ await this._state.peer!.open();
114
+ this._pushTracksTask!.schedule();
115
+ }
116
+
117
+ @synchronized
118
+ async leave(): Promise<void> {
119
+ await Promise.all(Object.values(this._state.pulledAudioTracks).map(({ ctx }) => ctx.dispose()));
120
+ await Promise.all(Object.values(this._state.pulledVideoStreams).map(({ ctx }) => ctx.dispose()));
121
+ this._trackToReconcile = [];
122
+ await this._state.peer?.close();
123
+ this._state.peer = undefined;
124
+ this._state.pushedVideoTrack = undefined;
125
+ this._state.pushedAudioTrack = undefined;
126
+ this._state.pushedScreenshareTrack = undefined;
127
+ this._state.pulledAudioTracks = {};
128
+ this._state.pulledVideoStreams = {};
129
+ }
130
+
131
+ async turnVideoOn(): Promise<void> {
132
+ this._state.videoStream!.removeTrack(this._state.videoTrack!);
133
+ this._state.videoTrack = await getUserMediaTrack('videoinput', { width: VIDEO_WIDTH, height: VIDEO_HEIGHT });
134
+ this._state.videoStream!.addTrack(this._state.videoTrack);
135
+ this._state.videoEnabled = true;
136
+ this.stateUpdated.emit(this._state);
137
+ this._pushTracksTask!.schedule();
138
+ }
139
+
140
+ async turnVideoOff(): Promise<void> {
141
+ if (this._state.videoTrack !== this._blackCanvasStreamTrack) {
142
+ this._state.videoStream!.removeTrack(this._state.videoTrack!);
143
+ this._state.videoTrack?.stop();
144
+ this._state.videoTrack = this._blackCanvasStreamTrack;
145
+ this._state.videoStream!.addTrack(this._state.videoTrack!);
146
+ }
147
+
148
+ this._state.videoEnabled = false;
149
+ this.stateUpdated.emit(this._state);
150
+ this._pushTracksTask!.schedule();
151
+ }
152
+
153
+ // TODO(mykola): Change to `setAudioEnabled(enabled: boolean)`.
154
+ async turnAudioOn(): Promise<void> {
155
+ void this._speakingMonitor?.close();
156
+ this._state.audioEnabled = true;
157
+ this._state.audioTrack = await getUserMediaTrack('audioinput', {
158
+ echoCancellation: true,
159
+ noiseSuppression: true,
160
+ autoGainControl: true,
161
+ sampleRate: 16_000,
162
+ });
163
+ this.stateUpdated.emit(this._state);
164
+ this._pushTracksTask!.schedule();
165
+ this._speakingMonitor = new SpeakingMonitor(this._state.audioTrack!);
166
+ this._speakingMonitor.speakingChanged.on(this._ctx, () => this.stateUpdated.emit(this._state));
167
+ void this._speakingMonitor.open();
168
+ }
169
+
170
+ async turnAudioOff(): Promise<void> {
171
+ void this._speakingMonitor?.close();
172
+ this._speakingMonitor = undefined;
173
+ if (this._state.audioTrack !== this._inaudibleAudioStreamTrack) {
174
+ this._state.audioTrack?.stop();
175
+ this._state.audioTrack = this._inaudibleAudioStreamTrack;
176
+ }
177
+ this._state.audioEnabled = false;
178
+ this.stateUpdated.emit(this._state);
179
+ this._pushTracksTask!.schedule();
180
+ }
181
+
182
+ async turnScreenshareOn(): Promise<void> {
183
+ const ms = await getScreenshare({ contentHint: 'text' });
184
+ this._state.screenshareVideoStream = ms;
185
+ this._state.screenshareTrack = ms.getVideoTracks()[0];
186
+ this._state.screenshareEnabled = true;
187
+ this.stateUpdated.emit(this._state);
188
+ this._pushTracksTask!.schedule();
189
+ }
190
+
191
+ async turnScreenshareOff(): Promise<void> {
192
+ this._state.screenshareEnabled = false;
193
+ this._state.screenshareTrack?.stop();
194
+ this._state.screenshareTrack = undefined;
195
+ this.stateUpdated.emit(this._state);
196
+ this._pushTracksTask!.schedule();
197
+ }
198
+
199
+ /**
200
+ * @internal
201
+ */
202
+ _schedulePullTracks(tracks?: EncodedTrackName[]): void {
203
+ if (
204
+ !tracks ||
205
+ (this._trackToReconcile.every((name) => tracks.includes(name)) && this._trackToReconcile.length === tracks.length)
206
+ ) {
207
+ return;
208
+ }
209
+
210
+ this._trackToReconcile = tracks;
211
+ this._pullTracksTask!.schedule();
212
+ }
213
+
214
+ private async _reconcilePulledMedia(): Promise<void> {
215
+ log('reconciling tracks');
216
+ // TODO(mykola): Currently cloudflare fails if you try to pull track immediately after pushing it.
217
+ // Add retry logic, remove sleep.
218
+ // Wait for cloudflare to process the track.
219
+ await cancelWithContext(this._ctx, sleep(800));
220
+ const trackNames = this._trackToReconcile;
221
+
222
+ const tracksToPull = trackNames.filter(
223
+ (name) => !this._state.pulledAudioTracks[name] && !this._state.pulledVideoStreams[name],
224
+ );
225
+
226
+ const audioTracksToClose = Object.entries(this._state.pulledAudioTracks).filter(
227
+ ([key]) => !trackNames.some((name) => name === key),
228
+ );
229
+
230
+ const videoStreamsToClose = Object.entries(this._state.pulledVideoStreams).filter(
231
+ ([key]) => !trackNames.some((name) => name === key),
232
+ );
233
+
234
+ log('reconciling tracks', {
235
+ trackNames,
236
+ tracksToPull,
237
+ videoStreamsToClose,
238
+ audioTracksToClose,
239
+ currentAudioTracks: Object.keys(this._state.pulledAudioTracks),
240
+ currentVideoStreams: Object.keys(this._state.pulledVideoStreams),
241
+ });
242
+
243
+ // Pull new tracks.
244
+ await Promise.all(tracksToPull.map((name) => this._pullTrack(name)));
245
+
246
+ // Close old tracks.
247
+ await Promise.all([...audioTracksToClose, ...videoStreamsToClose].map(([_, { ctx }]) => ctx.dispose()));
248
+
249
+ log('reconciled tracks', {
250
+ currentAudioTracks: Object.keys(this._state.pulledAudioTracks),
251
+ currentVideoStreams: Object.keys(this._state.pulledVideoStreams),
252
+ });
253
+ this.stateUpdated.emit(this._state);
254
+ }
255
+
256
+ private async _pushTracks(): Promise<void> {
257
+ if (!this._state.peer?.isOpen) {
258
+ return;
259
+ }
260
+
261
+ let updated = false;
262
+ const [pushedVideoTrack, pushedAudioTrack, pushedScreenshareTrack] = await Promise.all([
263
+ this._pushTrack(this._state.videoTrack, this._state.pushedVideoTrack, [
264
+ { maxFramerate: MAX_WEB_CAM_FRAMERATE, maxBitrate: MAX_WEB_CAM_BITRATE },
265
+ ]),
266
+ this._pushTrack(this._state.audioTrack, this._state.pushedAudioTrack, [{ networkPriority: 'high' }]),
267
+ this._pushTrack(this._state.screenshareTrack, this._state.pushedScreenshareTrack),
268
+ ]);
269
+
270
+ if (pushedVideoTrack !== this._state.pushedVideoTrack) {
271
+ this._state.pushedVideoTrack = pushedVideoTrack;
272
+ updated = true;
273
+ }
274
+ if (pushedAudioTrack !== this._state.pushedAudioTrack) {
275
+ this._state.pushedAudioTrack = pushedAudioTrack;
276
+ updated = true;
277
+ }
278
+ if (pushedScreenshareTrack !== this._state.pushedScreenshareTrack) {
279
+ this._state.pushedScreenshareTrack = pushedScreenshareTrack;
280
+ updated = true;
281
+ }
282
+ if (!updated) {
283
+ return;
284
+ }
285
+
286
+ this.stateUpdated.emit(this._state);
287
+ }
288
+
289
+ private async _pullTrack(name: EncodedTrackName): Promise<void> {
290
+ const ctx = this._ctx.derive();
291
+ try {
292
+ const trackData = TrackNameCodec.decode(name);
293
+ const track = await this._state.peer!.pullTrack({ trackData, ctx });
294
+ if (track?.readyState === 'ended') {
295
+ throw new Error('Pulled track ended immediately');
296
+ }
297
+
298
+ switch (track?.kind) {
299
+ case 'audio': {
300
+ this._state.pulledAudioTracks[name] = { track, ctx };
301
+ ctx.onDispose(() => delete this._state.pulledAudioTracks[name]);
302
+ break;
303
+ }
304
+ case 'video': {
305
+ const mediaStream = new MediaStream();
306
+ mediaStream.addTrack(track);
307
+ this._state.pulledVideoStreams[name] = { stream: mediaStream, ctx };
308
+ ctx.onDispose(() => {
309
+ mediaStream.removeTrack(track);
310
+ track.stop();
311
+ delete this._state.pulledVideoStreams[name];
312
+ });
313
+ break;
314
+ }
315
+ default:
316
+ throw new Error(`Invalid track kind: ${track?.kind}`);
317
+ }
318
+ } catch (err) {
319
+ log.warn('failed to pull track', { err, name });
320
+ void ctx.dispose();
321
+ await cancelWithContext(this._ctx, sleep(RETRY_INTERVAL));
322
+ log.warn('retrying pull track', { name });
323
+ this._pullTracksTask!.schedule();
324
+ }
325
+ }
326
+
327
+ private async _pushTrack(
328
+ track?: MediaStreamTrack,
329
+ previousTrack?: TrackObject,
330
+ encodings?: RTCRtpEncodingParameters[],
331
+ ): Promise<TrackObject | undefined> {
332
+ if (!track && !previousTrack) {
333
+ return;
334
+ }
335
+
336
+ return this._state.peer!.pushTrack({
337
+ track: track ?? null,
338
+ previousTrack,
339
+ encodings,
340
+ });
341
+ }
342
+ }