@syra.fm/sdk 0.4.0 → 0.5.0

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 (307) hide show
  1. package/lib/commonjs/client.js +268 -0
  2. package/lib/commonjs/client.js.map +1 -0
  3. package/lib/commonjs/errors.js +22 -0
  4. package/lib/commonjs/errors.js.map +1 -0
  5. package/lib/commonjs/index.js +63 -0
  6. package/lib/commonjs/index.js.map +1 -0
  7. package/lib/commonjs/index.native.js +28 -0
  8. package/lib/commonjs/index.native.js.map +1 -0
  9. package/lib/commonjs/live/assets/icons/spaces-icon.js +42 -0
  10. package/lib/commonjs/live/assets/icons/spaces-icon.js.map +1 -0
  11. package/lib/commonjs/live/assets/icons/syra-icon.js +43 -0
  12. package/lib/commonjs/live/assets/icons/syra-icon.js.map +1 -0
  13. package/lib/commonjs/live/components/AnimatedPulse.js +38 -0
  14. package/lib/commonjs/live/components/AnimatedPulse.js.map +1 -0
  15. package/lib/commonjs/live/components/AnimatedSpeakerRing.js +90 -0
  16. package/lib/commonjs/live/components/AnimatedSpeakerRing.js.map +1 -0
  17. package/lib/commonjs/live/components/CreateRoomSheet.js +688 -0
  18. package/lib/commonjs/live/components/CreateRoomSheet.js.map +1 -0
  19. package/lib/commonjs/live/components/InsightsPanel.js +333 -0
  20. package/lib/commonjs/live/components/InsightsPanel.js.map +1 -0
  21. package/lib/commonjs/live/components/LiveRoomSheet.js +1195 -0
  22. package/lib/commonjs/live/components/LiveRoomSheet.js.map +1 -0
  23. package/lib/commonjs/live/components/MiniRoomBar.js +161 -0
  24. package/lib/commonjs/live/components/MiniRoomBar.js.map +1 -0
  25. package/lib/commonjs/live/components/PanelHeader.js +57 -0
  26. package/lib/commonjs/live/components/PanelHeader.js.map +1 -0
  27. package/lib/commonjs/live/components/PodcastStreamPicker.js +1190 -0
  28. package/lib/commonjs/live/components/PodcastStreamPicker.js.map +1 -0
  29. package/lib/commonjs/live/components/RecordingCard.js +192 -0
  30. package/lib/commonjs/live/components/RecordingCard.js.map +1 -0
  31. package/lib/commonjs/live/components/RecordingsPanel.js +304 -0
  32. package/lib/commonjs/live/components/RecordingsPanel.js.map +1 -0
  33. package/lib/commonjs/live/components/RoomCard.js +553 -0
  34. package/lib/commonjs/live/components/RoomCard.js.map +1 -0
  35. package/lib/commonjs/live/components/StreamConfigModal.js +656 -0
  36. package/lib/commonjs/live/components/StreamConfigModal.js.map +1 -0
  37. package/lib/commonjs/live/components/StreamConfigPanel.js +822 -0
  38. package/lib/commonjs/live/components/StreamConfigPanel.js.map +1 -0
  39. package/lib/commonjs/live/context/LiveConfigContext.js +46 -0
  40. package/lib/commonjs/live/context/LiveConfigContext.js.map +1 -0
  41. package/lib/commonjs/live/context/LiveRoomContext.js +195 -0
  42. package/lib/commonjs/live/context/LiveRoomContext.js.map +1 -0
  43. package/lib/commonjs/live/hooks/useActiveSpeakers.js +35 -0
  44. package/lib/commonjs/live/hooks/useActiveSpeakers.js.map +1 -0
  45. package/lib/commonjs/live/hooks/useRoomAudio.js +129 -0
  46. package/lib/commonjs/live/hooks/useRoomAudio.js.map +1 -0
  47. package/lib/commonjs/live/hooks/useRoomConnection.js +177 -0
  48. package/lib/commonjs/live/hooks/useRoomConnection.js.map +1 -0
  49. package/lib/commonjs/live/hooks/useRoomManager.js +37 -0
  50. package/lib/commonjs/live/hooks/useRoomManager.js.map +1 -0
  51. package/lib/commonjs/live/hooks/useRoomUsers.js +52 -0
  52. package/lib/commonjs/live/hooks/useRoomUsers.js.map +1 -0
  53. package/lib/commonjs/live/index.js +310 -0
  54. package/lib/commonjs/live/index.js.map +1 -0
  55. package/lib/commonjs/live/services/livekitService.js +20 -0
  56. package/lib/commonjs/live/services/livekitService.js.map +1 -0
  57. package/lib/commonjs/live/services/spaceSocketService.js +181 -0
  58. package/lib/commonjs/live/services/spaceSocketService.js.map +1 -0
  59. package/lib/commonjs/live/services/spacesService.js +642 -0
  60. package/lib/commonjs/live/services/spacesService.js.map +1 -0
  61. package/lib/commonjs/live/types.js +6 -0
  62. package/lib/commonjs/live/types.js.map +1 -0
  63. package/lib/commonjs/live/validation.js +238 -0
  64. package/lib/commonjs/live/validation.js.map +1 -0
  65. package/lib/commonjs/package.json +1 -0
  66. package/{dist/cjs → lib/commonjs}/schema.js +55 -47
  67. package/lib/commonjs/schema.js.map +1 -0
  68. package/lib/module/client.js +264 -0
  69. package/lib/module/client.js.map +1 -0
  70. package/lib/module/errors.js +17 -0
  71. package/lib/module/errors.js.map +1 -0
  72. package/lib/module/index.js +6 -0
  73. package/lib/module/index.js.map +1 -0
  74. package/lib/module/index.native.js +5 -0
  75. package/lib/module/index.native.js.map +1 -0
  76. package/lib/module/live/assets/icons/spaces-icon.js +34 -0
  77. package/lib/module/live/assets/icons/spaces-icon.js.map +1 -0
  78. package/lib/module/live/assets/icons/syra-icon.js +36 -0
  79. package/lib/module/live/assets/icons/syra-icon.js.map +1 -0
  80. package/lib/module/live/components/AnimatedPulse.js +33 -0
  81. package/lib/module/live/components/AnimatedPulse.js.map +1 -0
  82. package/lib/module/live/components/AnimatedSpeakerRing.js +85 -0
  83. package/lib/module/live/components/AnimatedSpeakerRing.js.map +1 -0
  84. package/lib/module/live/components/CreateRoomSheet.js +682 -0
  85. package/lib/module/live/components/CreateRoomSheet.js.map +1 -0
  86. package/lib/module/live/components/InsightsPanel.js +327 -0
  87. package/lib/module/live/components/InsightsPanel.js.map +1 -0
  88. package/lib/module/live/components/LiveRoomSheet.js +1189 -0
  89. package/lib/module/live/components/LiveRoomSheet.js.map +1 -0
  90. package/lib/module/live/components/MiniRoomBar.js +154 -0
  91. package/lib/module/live/components/MiniRoomBar.js.map +1 -0
  92. package/lib/module/live/components/PanelHeader.js +52 -0
  93. package/lib/module/live/components/PanelHeader.js.map +1 -0
  94. package/lib/module/live/components/PodcastStreamPicker.js +1183 -0
  95. package/lib/module/live/components/PodcastStreamPicker.js.map +1 -0
  96. package/lib/module/live/components/RecordingCard.js +187 -0
  97. package/lib/module/live/components/RecordingCard.js.map +1 -0
  98. package/lib/module/live/components/RecordingsPanel.js +298 -0
  99. package/lib/module/live/components/RecordingsPanel.js.map +1 -0
  100. package/lib/module/live/components/RoomCard.js +548 -0
  101. package/lib/module/live/components/RoomCard.js.map +1 -0
  102. package/lib/module/live/components/StreamConfigModal.js +650 -0
  103. package/lib/module/live/components/StreamConfigModal.js.map +1 -0
  104. package/lib/module/live/components/StreamConfigPanel.js +816 -0
  105. package/lib/module/live/components/StreamConfigPanel.js.map +1 -0
  106. package/lib/module/live/context/LiveConfigContext.js +40 -0
  107. package/lib/module/live/context/LiveConfigContext.js.map +1 -0
  108. package/lib/module/live/context/LiveRoomContext.js +189 -0
  109. package/lib/module/live/context/LiveRoomContext.js.map +1 -0
  110. package/lib/module/live/hooks/useActiveSpeakers.js +31 -0
  111. package/lib/module/live/hooks/useActiveSpeakers.js.map +1 -0
  112. package/lib/module/live/hooks/useRoomAudio.js +125 -0
  113. package/lib/module/live/hooks/useRoomAudio.js.map +1 -0
  114. package/lib/module/live/hooks/useRoomConnection.js +173 -0
  115. package/lib/module/live/hooks/useRoomConnection.js.map +1 -0
  116. package/lib/module/live/hooks/useRoomManager.js +32 -0
  117. package/lib/module/live/hooks/useRoomManager.js.map +1 -0
  118. package/lib/module/live/hooks/useRoomUsers.js +46 -0
  119. package/lib/module/live/hooks/useRoomUsers.js.map +1 -0
  120. package/lib/module/live/index.js +38 -0
  121. package/lib/module/live/index.js.map +1 -0
  122. package/lib/module/live/services/livekitService.js +16 -0
  123. package/lib/module/live/services/livekitService.js.map +1 -0
  124. package/lib/module/live/services/spaceSocketService.js +176 -0
  125. package/lib/module/live/services/spaceSocketService.js.map +1 -0
  126. package/lib/module/live/services/spacesService.js +639 -0
  127. package/lib/module/live/services/spacesService.js.map +1 -0
  128. package/lib/module/live/types.js +4 -0
  129. package/lib/module/live/types.js.map +1 -0
  130. package/lib/module/live/validation.js +229 -0
  131. package/lib/module/live/validation.js.map +1 -0
  132. package/lib/module/package.json +1 -0
  133. package/{dist/esm → lib/module}/schema.js +47 -39
  134. package/lib/module/schema.js.map +1 -0
  135. package/{dist/types → lib/typescript/commonjs}/client.d.ts +1 -0
  136. package/lib/typescript/commonjs/client.d.ts.map +1 -0
  137. package/{dist/types → lib/typescript/commonjs}/errors.d.ts +1 -0
  138. package/lib/typescript/commonjs/errors.d.ts.map +1 -0
  139. package/{dist/types → lib/typescript/commonjs}/index.d.ts +1 -0
  140. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  141. package/lib/typescript/commonjs/index.native.d.ts +3 -0
  142. package/lib/typescript/commonjs/index.native.d.ts.map +1 -0
  143. package/lib/typescript/commonjs/live/assets/icons/spaces-icon.d.ts +13 -0
  144. package/lib/typescript/commonjs/live/assets/icons/spaces-icon.d.ts.map +1 -0
  145. package/lib/typescript/commonjs/live/assets/icons/syra-icon.d.ts +13 -0
  146. package/lib/typescript/commonjs/live/assets/icons/syra-icon.d.ts.map +1 -0
  147. package/lib/typescript/commonjs/live/components/AnimatedPulse.d.ts +8 -0
  148. package/lib/typescript/commonjs/live/components/AnimatedPulse.d.ts.map +1 -0
  149. package/lib/typescript/commonjs/live/components/AnimatedSpeakerRing.d.ts +10 -0
  150. package/lib/typescript/commonjs/live/components/AnimatedSpeakerRing.d.ts.map +1 -0
  151. package/lib/typescript/commonjs/live/components/CreateRoomSheet.d.ts +26 -0
  152. package/lib/typescript/commonjs/live/components/CreateRoomSheet.d.ts.map +1 -0
  153. package/lib/typescript/commonjs/live/components/InsightsPanel.d.ts +11 -0
  154. package/lib/typescript/commonjs/live/components/InsightsPanel.d.ts.map +1 -0
  155. package/lib/typescript/commonjs/live/components/LiveRoomSheet.d.ts +11 -0
  156. package/lib/typescript/commonjs/live/components/LiveRoomSheet.d.ts.map +1 -0
  157. package/lib/typescript/commonjs/live/components/MiniRoomBar.d.ts +15 -0
  158. package/lib/typescript/commonjs/live/components/MiniRoomBar.d.ts.map +1 -0
  159. package/lib/typescript/commonjs/live/components/PanelHeader.d.ts +10 -0
  160. package/lib/typescript/commonjs/live/components/PanelHeader.d.ts.map +1 -0
  161. package/lib/typescript/commonjs/live/components/PodcastStreamPicker.d.ts +54 -0
  162. package/lib/typescript/commonjs/live/components/PodcastStreamPicker.d.ts.map +1 -0
  163. package/lib/typescript/commonjs/live/components/RecordingCard.d.ts +11 -0
  164. package/lib/typescript/commonjs/live/components/RecordingCard.d.ts.map +1 -0
  165. package/lib/typescript/commonjs/live/components/RecordingsPanel.d.ts +12 -0
  166. package/lib/typescript/commonjs/live/components/RecordingsPanel.d.ts.map +1 -0
  167. package/lib/typescript/commonjs/live/components/RoomCard.d.ts +38 -0
  168. package/lib/typescript/commonjs/live/components/RoomCard.d.ts.map +1 -0
  169. package/lib/typescript/commonjs/live/components/StreamConfigModal.d.ts +11 -0
  170. package/lib/typescript/commonjs/live/components/StreamConfigModal.d.ts.map +1 -0
  171. package/lib/typescript/commonjs/live/components/StreamConfigPanel.d.ts +13 -0
  172. package/lib/typescript/commonjs/live/components/StreamConfigPanel.d.ts.map +1 -0
  173. package/lib/typescript/commonjs/live/context/LiveConfigContext.d.ts +78 -0
  174. package/lib/typescript/commonjs/live/context/LiveConfigContext.d.ts.map +1 -0
  175. package/lib/typescript/commonjs/live/context/LiveRoomContext.d.ts +12 -0
  176. package/lib/typescript/commonjs/live/context/LiveRoomContext.d.ts.map +1 -0
  177. package/lib/typescript/commonjs/live/hooks/useActiveSpeakers.d.ts +3 -0
  178. package/lib/typescript/commonjs/live/hooks/useActiveSpeakers.d.ts.map +1 -0
  179. package/lib/typescript/commonjs/live/hooks/useRoomAudio.d.ts +16 -0
  180. package/lib/typescript/commonjs/live/hooks/useRoomAudio.d.ts.map +1 -0
  181. package/lib/typescript/commonjs/live/hooks/useRoomConnection.d.ts +29 -0
  182. package/lib/typescript/commonjs/live/hooks/useRoomConnection.d.ts.map +1 -0
  183. package/lib/typescript/commonjs/live/hooks/useRoomManager.d.ts +11 -0
  184. package/lib/typescript/commonjs/live/hooks/useRoomManager.d.ts.map +1 -0
  185. package/lib/typescript/commonjs/live/hooks/useRoomUsers.d.ts +5 -0
  186. package/lib/typescript/commonjs/live/hooks/useRoomUsers.d.ts.map +1 -0
  187. package/lib/typescript/commonjs/live/index.d.ts +24 -0
  188. package/lib/typescript/commonjs/live/index.d.ts.map +1 -0
  189. package/lib/typescript/commonjs/live/services/livekitService.d.ts +7 -0
  190. package/lib/typescript/commonjs/live/services/livekitService.d.ts.map +1 -0
  191. package/lib/typescript/commonjs/live/services/spaceSocketService.d.ts +69 -0
  192. package/lib/typescript/commonjs/live/services/spaceSocketService.d.ts.map +1 -0
  193. package/lib/typescript/commonjs/live/services/spacesService.d.ts +181 -0
  194. package/lib/typescript/commonjs/live/services/spacesService.d.ts.map +1 -0
  195. package/lib/typescript/commonjs/live/types.d.ts +100 -0
  196. package/lib/typescript/commonjs/live/types.d.ts.map +1 -0
  197. package/lib/typescript/commonjs/live/validation.d.ts +658 -0
  198. package/lib/typescript/commonjs/live/validation.d.ts.map +1 -0
  199. package/lib/typescript/commonjs/package.json +1 -0
  200. package/{dist/types → lib/typescript/commonjs}/schema.d.ts +1 -0
  201. package/lib/typescript/commonjs/schema.d.ts.map +1 -0
  202. package/lib/typescript/module/client.d.ts +141 -0
  203. package/lib/typescript/module/client.d.ts.map +1 -0
  204. package/lib/typescript/module/errors.d.ts +9 -0
  205. package/lib/typescript/module/errors.d.ts.map +1 -0
  206. package/lib/typescript/module/index.d.ts +6 -0
  207. package/lib/typescript/module/index.d.ts.map +1 -0
  208. package/lib/typescript/module/index.native.d.ts +3 -0
  209. package/lib/typescript/module/index.native.d.ts.map +1 -0
  210. package/lib/typescript/module/live/assets/icons/spaces-icon.d.ts +13 -0
  211. package/lib/typescript/module/live/assets/icons/spaces-icon.d.ts.map +1 -0
  212. package/lib/typescript/module/live/assets/icons/syra-icon.d.ts +13 -0
  213. package/lib/typescript/module/live/assets/icons/syra-icon.d.ts.map +1 -0
  214. package/lib/typescript/module/live/components/AnimatedPulse.d.ts +8 -0
  215. package/lib/typescript/module/live/components/AnimatedPulse.d.ts.map +1 -0
  216. package/lib/typescript/module/live/components/AnimatedSpeakerRing.d.ts +10 -0
  217. package/lib/typescript/module/live/components/AnimatedSpeakerRing.d.ts.map +1 -0
  218. package/lib/typescript/module/live/components/CreateRoomSheet.d.ts +26 -0
  219. package/lib/typescript/module/live/components/CreateRoomSheet.d.ts.map +1 -0
  220. package/lib/typescript/module/live/components/InsightsPanel.d.ts +11 -0
  221. package/lib/typescript/module/live/components/InsightsPanel.d.ts.map +1 -0
  222. package/lib/typescript/module/live/components/LiveRoomSheet.d.ts +11 -0
  223. package/lib/typescript/module/live/components/LiveRoomSheet.d.ts.map +1 -0
  224. package/lib/typescript/module/live/components/MiniRoomBar.d.ts +15 -0
  225. package/lib/typescript/module/live/components/MiniRoomBar.d.ts.map +1 -0
  226. package/lib/typescript/module/live/components/PanelHeader.d.ts +10 -0
  227. package/lib/typescript/module/live/components/PanelHeader.d.ts.map +1 -0
  228. package/lib/typescript/module/live/components/PodcastStreamPicker.d.ts +54 -0
  229. package/lib/typescript/module/live/components/PodcastStreamPicker.d.ts.map +1 -0
  230. package/lib/typescript/module/live/components/RecordingCard.d.ts +11 -0
  231. package/lib/typescript/module/live/components/RecordingCard.d.ts.map +1 -0
  232. package/lib/typescript/module/live/components/RecordingsPanel.d.ts +12 -0
  233. package/lib/typescript/module/live/components/RecordingsPanel.d.ts.map +1 -0
  234. package/lib/typescript/module/live/components/RoomCard.d.ts +38 -0
  235. package/lib/typescript/module/live/components/RoomCard.d.ts.map +1 -0
  236. package/lib/typescript/module/live/components/StreamConfigModal.d.ts +11 -0
  237. package/lib/typescript/module/live/components/StreamConfigModal.d.ts.map +1 -0
  238. package/lib/typescript/module/live/components/StreamConfigPanel.d.ts +13 -0
  239. package/lib/typescript/module/live/components/StreamConfigPanel.d.ts.map +1 -0
  240. package/lib/typescript/module/live/context/LiveConfigContext.d.ts +78 -0
  241. package/lib/typescript/module/live/context/LiveConfigContext.d.ts.map +1 -0
  242. package/lib/typescript/module/live/context/LiveRoomContext.d.ts +12 -0
  243. package/lib/typescript/module/live/context/LiveRoomContext.d.ts.map +1 -0
  244. package/lib/typescript/module/live/hooks/useActiveSpeakers.d.ts +3 -0
  245. package/lib/typescript/module/live/hooks/useActiveSpeakers.d.ts.map +1 -0
  246. package/lib/typescript/module/live/hooks/useRoomAudio.d.ts +16 -0
  247. package/lib/typescript/module/live/hooks/useRoomAudio.d.ts.map +1 -0
  248. package/lib/typescript/module/live/hooks/useRoomConnection.d.ts +29 -0
  249. package/lib/typescript/module/live/hooks/useRoomConnection.d.ts.map +1 -0
  250. package/lib/typescript/module/live/hooks/useRoomManager.d.ts +11 -0
  251. package/lib/typescript/module/live/hooks/useRoomManager.d.ts.map +1 -0
  252. package/lib/typescript/module/live/hooks/useRoomUsers.d.ts +5 -0
  253. package/lib/typescript/module/live/hooks/useRoomUsers.d.ts.map +1 -0
  254. package/lib/typescript/module/live/index.d.ts +24 -0
  255. package/lib/typescript/module/live/index.d.ts.map +1 -0
  256. package/lib/typescript/module/live/services/livekitService.d.ts +7 -0
  257. package/lib/typescript/module/live/services/livekitService.d.ts.map +1 -0
  258. package/lib/typescript/module/live/services/spaceSocketService.d.ts +69 -0
  259. package/lib/typescript/module/live/services/spaceSocketService.d.ts.map +1 -0
  260. package/lib/typescript/module/live/services/spacesService.d.ts +181 -0
  261. package/lib/typescript/module/live/services/spacesService.d.ts.map +1 -0
  262. package/lib/typescript/module/live/types.d.ts +100 -0
  263. package/lib/typescript/module/live/types.d.ts.map +1 -0
  264. package/lib/typescript/module/live/validation.d.ts +658 -0
  265. package/lib/typescript/module/live/validation.d.ts.map +1 -0
  266. package/lib/typescript/module/package.json +1 -0
  267. package/lib/typescript/module/schema.d.ts +1072 -0
  268. package/lib/typescript/module/schema.d.ts.map +1 -0
  269. package/package.json +133 -21
  270. package/src/index.native.ts +2 -0
  271. package/src/live/assets/icons/spaces-icon.tsx +15 -0
  272. package/src/live/assets/icons/syra-icon.tsx +13 -0
  273. package/src/live/assets/sounds/intro.mp3 +0 -0
  274. package/src/live/components/AnimatedPulse.tsx +52 -0
  275. package/src/live/components/AnimatedSpeakerRing.tsx +101 -0
  276. package/src/live/components/CreateRoomSheet.tsx +654 -0
  277. package/src/live/components/InsightsPanel.tsx +202 -0
  278. package/src/live/components/LiveRoomSheet.tsx +969 -0
  279. package/src/live/components/MiniRoomBar.tsx +166 -0
  280. package/src/live/components/PanelHeader.tsx +36 -0
  281. package/src/live/components/PodcastStreamPicker.tsx +1155 -0
  282. package/src/live/components/RecordingCard.tsx +154 -0
  283. package/src/live/components/RecordingsPanel.tsx +251 -0
  284. package/src/live/components/RoomCard.tsx +494 -0
  285. package/src/live/components/StreamConfigModal.tsx +600 -0
  286. package/src/live/components/StreamConfigPanel.tsx +729 -0
  287. package/src/live/context/LiveConfigContext.tsx +96 -0
  288. package/src/live/context/LiveRoomContext.tsx +235 -0
  289. package/src/live/hooks/useActiveSpeakers.ts +39 -0
  290. package/src/live/hooks/useRoomAudio.ts +103 -0
  291. package/src/live/hooks/useRoomConnection.ts +162 -0
  292. package/src/live/hooks/useRoomManager.ts +17 -0
  293. package/src/live/hooks/useRoomUsers.ts +52 -0
  294. package/src/live/index.ts +92 -0
  295. package/src/live/services/livekitService.ts +11 -0
  296. package/src/live/services/spaceSocketService.ts +136 -0
  297. package/src/live/services/spacesService.ts +692 -0
  298. package/src/live/types.ts +119 -0
  299. package/src/live/validation.ts +279 -0
  300. package/dist/cjs/client.js +0 -244
  301. package/dist/cjs/errors.js +0 -16
  302. package/dist/cjs/index.js +0 -15
  303. package/dist/cjs/package.json +0 -3
  304. package/dist/esm/client.js +0 -240
  305. package/dist/esm/errors.js +0 -12
  306. package/dist/esm/index.js +0 -3
  307. package/dist/esm/package.json +0 -3
@@ -0,0 +1,96 @@
1
+ import React, { createContext, useContext, useMemo } from 'react';
2
+ import type { ViewStyle } from 'react-native';
3
+ import type { LiveTheme, UserEntity, HttpClient } from '../types';
4
+ import { createRoomsService, type RoomsServiceInstance } from '../services/spacesService';
5
+ import { RoomSocketService } from '../services/spaceSocketService';
6
+ import { createGetRoomToken, type GetRoomTokenFn } from '../services/livekitService';
7
+
8
+ /**
9
+ * The viewer's pinned Syra podcast (resolved from their profile media), surfaced
10
+ * by {@link LiveConfig.getPinnedPodcast}. `title`/`artworkUrl` are optional —
11
+ * the id is all the picker needs to drill into the episode list.
12
+ */
13
+ export interface PinnedPodcast {
14
+ syraPodcastId: string;
15
+ title?: string;
16
+ artworkUrl?: string;
17
+ }
18
+
19
+ export interface LiveConfig {
20
+ httpClient: HttpClient;
21
+ socketUrl: string;
22
+ useTheme: () => LiveTheme;
23
+ /**
24
+ * Translate a UI string key to the active locale. Optional: hosts WITH an i18n
25
+ * layer (the Mention frontend) inject their real translator so the shared
26
+ * live-room UI localizes; hosts WITHOUT one (the standalone live-rooms app) omit it
27
+ * and the shared components fall back to their bundled English source copy.
28
+ * Keys are looked up flat (e.g. `agora.podcastStream.disclaimer`).
29
+ */
30
+ t?: (key: string, options?: Record<string, string | number>) => string;
31
+ /**
32
+ * Resolve the viewer's pinned Syra podcast from their profile media, or `null`
33
+ * when they have none / it can't be read. Optional: hosts that expose profile
34
+ * media wire it so the podcast picker can offer a one-tap "stream my pinned
35
+ * podcast" quick-start row. The picker renders nothing when this is absent or
36
+ * resolves to `null`.
37
+ */
38
+ getPinnedPodcast?: () => Promise<PinnedPodcast | null>;
39
+ useUserById: (id: string | undefined) => UserEntity | undefined;
40
+ ensureUserById: (
41
+ id: string,
42
+ loader: (id: string) => Promise<UserEntity | null | undefined>
43
+ ) => Promise<UserEntity | undefined>;
44
+ getCachedFileDownloadUrl: (oxyServices: unknown, fileId: string, variant?: string) => Promise<string>;
45
+ getCachedFileDownloadUrlSync: (oxyServices: unknown, fileId: string, variant?: string) => string;
46
+ AvatarComponent: React.ComponentType<{ size: number; source?: string; shape?: string; style?: ViewStyle }>;
47
+ toast: {
48
+ (message: string, options?: Record<string, unknown>): void;
49
+ success: (message: string) => void;
50
+ error: (message: string) => void;
51
+ };
52
+ introSound?: number;
53
+ isDesktop?: boolean;
54
+ useIsDesktop?: () => boolean;
55
+ onRoomChanged?: (roomId: string) => void;
56
+ /**
57
+ * Optional NativeWind className applied to the floating live-room dock and its
58
+ * backdrop. Hosts whose web shell uses a DOCUMENT-scroll model (html/body/#root
59
+ * = height:auto + overflow:visible, the window is the scroller) must pass
60
+ * `"web:fixed"` so the dock pins to the VIEWPORT bottom instead of resolving
61
+ * its `position: absolute` against the tall document and sinking to the page
62
+ * bottom. Omitted on hosts with the default fixed-viewport web model (and on
63
+ * native, where `web:fixed` is a no-op and the dock pins via `position:
64
+ * absolute` against the screen-filling root).
65
+ */
66
+ dockClassName?: string;
67
+ }
68
+
69
+ export interface LiveConfigInternal extends LiveConfig {
70
+ roomsService: RoomsServiceInstance;
71
+ roomSocketService: RoomSocketService;
72
+ getRoomToken: GetRoomTokenFn;
73
+ }
74
+
75
+ const LiveConfigContext = createContext<LiveConfigInternal | null>(null);
76
+
77
+ export function useLiveConfig(): LiveConfigInternal {
78
+ const config = useContext(LiveConfigContext);
79
+ if (!config) throw new Error('useLiveConfig must be used within a LiveConfigProvider');
80
+ return config;
81
+ }
82
+
83
+ export function LiveConfigProvider({ config, children }: { config: LiveConfig; children: React.ReactNode }) {
84
+ const fullConfig = useMemo<LiveConfigInternal>(() => {
85
+ const roomsService = createRoomsService(config.httpClient);
86
+ const roomSocketService = new RoomSocketService(config.socketUrl);
87
+ const getRoomToken = createGetRoomToken(config.httpClient);
88
+ return { ...config, roomsService, roomSocketService, getRoomToken };
89
+ }, [config]);
90
+
91
+ return (
92
+ <LiveConfigContext.Provider value={fullConfig}>
93
+ {children}
94
+ </LiveConfigContext.Provider>
95
+ );
96
+ }
@@ -0,0 +1,235 @@
1
+ import React, { createContext, useContext, useState, useCallback, useMemo } from 'react';
2
+ import { StyleSheet, Pressable, useWindowDimensions, Platform, type ViewStyle } from 'react-native';
3
+ import Animated, {
4
+ useSharedValue,
5
+ useAnimatedStyle,
6
+ withSpring,
7
+ withTiming,
8
+ interpolate,
9
+ Extrapolation,
10
+ runOnJS,
11
+ } from 'react-native-reanimated';
12
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
13
+ import { BlurView } from 'expo-blur';
14
+
15
+ import { useLiveConfig } from './LiveConfigContext';
16
+ import { LiveRoomSheet } from '../components/LiveRoomSheet';
17
+ import { MINI_BAR_HEIGHT } from '../components/MiniRoomBar';
18
+
19
+ interface LiveRoomContextProps {
20
+ activeRoomId: string | null;
21
+ joinLiveRoom: (roomId: string) => void;
22
+ leaveLiveRoom: () => void;
23
+ }
24
+
25
+ const LiveRoomContext = createContext<LiveRoomContextProps>({
26
+ activeRoomId: null,
27
+ joinLiveRoom: () => {},
28
+ leaveLiveRoom: () => {},
29
+ });
30
+
31
+ export function useLiveRoom() {
32
+ return useContext(LiveRoomContext);
33
+ }
34
+
35
+ const SPRING_CONFIG = { damping: 28, stiffness: 220, overshootClamping: true };
36
+ // Bottom bar: bottom 12 + height 56 + gap 8
37
+ const BOTTOM_BAR_OFFSET = 76;
38
+ const defaultUseIsDesktop = () => false;
39
+
40
+ export function LiveRoomProvider({ children }: { children: React.ReactNode }) {
41
+ const config = useLiveConfig();
42
+ const theme = config.useTheme();
43
+ // NativeWind class the host supplies to pin the dock to the viewport on web
44
+ // (e.g. `"web:fixed"` for the document-scroll shell). Undefined on hosts with
45
+ // the default fixed-viewport model and on native (no-op there).
46
+ const dockClassName = config.dockClassName;
47
+ // Inline `position`, resolved per-platform/host so it never fights the
48
+ // className on web:
49
+ // - NATIVE (always): `absolute` — pins against the screen-filling root
50
+ // (`web:fixed` is a no-op on native).
51
+ // - WEB + host className present (e.g. Mention's document-scroll shell):
52
+ // OMIT the inline position so the supplied `web:fixed` class owns it
53
+ // (NativeWind lets an inline `position` win over a className, which would
54
+ // re-sink the dock to the document bottom).
55
+ // - WEB + no host className (e.g. the live-rooms app, fixed-viewport model with
56
+ // no NativeWind): `absolute` — pins to the viewport there, preserving the
57
+ // prior behavior.
58
+ const dockPosition = useMemo<ViewStyle>(
59
+ () => (Platform.OS === 'web' && dockClassName ? {} : { position: 'absolute' }),
60
+ [dockClassName],
61
+ );
62
+ const insets = useSafeAreaInsets();
63
+ const { height: screenHeight } = useWindowDimensions();
64
+
65
+ const useIsDesktopHook = config.useIsDesktop ?? defaultUseIsDesktop;
66
+ const isDesktop = useIsDesktopHook();
67
+
68
+ const [activeRoomId, setActiveRoomId] = useState<string | null>(null);
69
+ const [isExpanded, setIsExpanded] = useState(false);
70
+
71
+ const hasBottomBar = !isDesktop;
72
+ const bottomBarOffset = hasBottomBar ? BOTTOM_BAR_OFFSET : 12;
73
+ const collapsedHeight = MINI_BAR_HEIGHT;
74
+ const expandedMaxHeight = screenHeight * 0.85 - bottomBarOffset;
75
+
76
+ const progress = useSharedValue(0);
77
+
78
+ const sheetAnimStyle = useAnimatedStyle(() => {
79
+ const h = interpolate(
80
+ progress.value,
81
+ [0, 1, 2],
82
+ [0, collapsedHeight, expandedMaxHeight],
83
+ Extrapolation.CLAMP,
84
+ );
85
+ const inset = 16;
86
+ const topRadius = interpolate(
87
+ progress.value,
88
+ [1, 2],
89
+ [collapsedHeight / 2, 16],
90
+ Extrapolation.CLAMP,
91
+ );
92
+ const bottomRadius = interpolate(
93
+ progress.value,
94
+ [1, 2],
95
+ [collapsedHeight / 2, 16],
96
+ Extrapolation.CLAMP,
97
+ );
98
+ return {
99
+ height: h,
100
+ left: inset,
101
+ right: inset,
102
+ borderTopLeftRadius: topRadius,
103
+ borderTopRightRadius: topRadius,
104
+ borderBottomLeftRadius: bottomRadius,
105
+ borderBottomRightRadius: bottomRadius,
106
+ };
107
+ });
108
+
109
+ const backdropAnimStyle = useAnimatedStyle(() => ({
110
+ opacity: interpolate(progress.value, [1, 2], [0, 1], Extrapolation.CLAMP),
111
+ }));
112
+
113
+ const joinLiveRoom = useCallback((roomId: string) => {
114
+ setActiveRoomId(roomId);
115
+ setIsExpanded(true);
116
+ progress.value = withSpring(2, SPRING_CONFIG);
117
+ }, [progress]);
118
+
119
+ const clearRoom = useCallback(() => {
120
+ setActiveRoomId(null);
121
+ setIsExpanded(false);
122
+ }, []);
123
+
124
+ const leaveLiveRoom = useCallback(() => {
125
+ setIsExpanded(false);
126
+ progress.value = withTiming(0, { duration: 250 }, (finished) => {
127
+ if (finished) runOnJS(clearRoom)();
128
+ });
129
+ }, [progress, clearRoom]);
130
+
131
+ const handleCollapse = useCallback(() => {
132
+ setIsExpanded(false);
133
+ progress.value = withSpring(1, SPRING_CONFIG);
134
+ }, [progress]);
135
+
136
+ const handleExpand = useCallback(() => {
137
+ setIsExpanded(true);
138
+ progress.value = withSpring(2, SPRING_CONFIG);
139
+ }, [progress]);
140
+
141
+ const contextValue = useMemo(
142
+ () => ({ activeRoomId, joinLiveRoom, leaveLiveRoom }),
143
+ [activeRoomId, joinLiveRoom, leaveLiveRoom]
144
+ );
145
+
146
+ return (
147
+ <LiveRoomContext.Provider value={contextValue}>
148
+ {children}
149
+
150
+ {activeRoomId && (
151
+ <Animated.View
152
+ className={dockClassName}
153
+ style={[styles.backdrop, dockPosition, backdropAnimStyle]}
154
+ pointerEvents={isExpanded ? 'auto' : 'none'}
155
+ >
156
+ <Pressable style={StyleSheet.absoluteFill} onPress={handleCollapse} />
157
+ </Animated.View>
158
+ )}
159
+
160
+ {activeRoomId && (
161
+ <Animated.View
162
+ className={dockClassName}
163
+ style={[
164
+ styles.sheet,
165
+ dockPosition,
166
+ {
167
+ bottom: bottomBarOffset,
168
+ borderColor: theme.colors.border,
169
+ ...(Platform.OS === 'web' ? {
170
+ backgroundColor: `${theme.colors.card}CC`,
171
+ backdropFilter: 'blur(20px)',
172
+ WebkitBackdropFilter: 'blur(20px)',
173
+ boxShadow: `0 2px 16px ${theme.colors.shadow}`,
174
+ } : {
175
+ shadowColor: '#000',
176
+ shadowOffset: { width: 0, height: 4 },
177
+ shadowOpacity: 0.15,
178
+ shadowRadius: 12,
179
+ elevation: 8,
180
+ }),
181
+ },
182
+ sheetAnimStyle,
183
+ ]}
184
+ >
185
+ {Platform.OS === 'web' ? (
186
+ <LiveRoomSheet
187
+ roomId={activeRoomId}
188
+ isExpanded={isExpanded}
189
+ onCollapse={handleCollapse}
190
+ onExpand={handleExpand}
191
+ onLeave={leaveLiveRoom}
192
+ />
193
+ ) : (
194
+ <BlurView
195
+ intensity={80}
196
+ tint={theme.isDark ? 'dark' : 'light'}
197
+ experimentalBlurMethod="dimezisBlurView"
198
+ style={{ flex: 1 }}
199
+ >
200
+ <LiveRoomSheet
201
+ roomId={activeRoomId}
202
+ isExpanded={isExpanded}
203
+ onCollapse={handleCollapse}
204
+ onExpand={handleExpand}
205
+ onLeave={leaveLiveRoom}
206
+ />
207
+ </BlurView>
208
+ )}
209
+ </Animated.View>
210
+ )}
211
+ </LiveRoomContext.Provider>
212
+ );
213
+ }
214
+
215
+ // `position` is intentionally NOT set in these StyleSheet entries — it is
216
+ // supplied at runtime via `dockPosition` (inline) and/or the host's
217
+ // `dockClassName` (web), so an inline `position` here can never override the
218
+ // `web:fixed` class on the document-scroll host.
219
+ const styles = StyleSheet.create({
220
+ backdrop: {
221
+ top: 0,
222
+ left: 0,
223
+ right: 0,
224
+ bottom: 0,
225
+ backgroundColor: 'rgba(0, 0, 0, 0.3)',
226
+ zIndex: 999,
227
+ },
228
+ sheet: {
229
+ maxWidth: 500,
230
+ alignSelf: 'center',
231
+ zIndex: 1000,
232
+ overflow: 'hidden',
233
+ borderWidth: 1,
234
+ },
235
+ });
@@ -0,0 +1,39 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { Room, RoomEvent, Participant } from 'livekit-client';
3
+
4
+ export function useActiveSpeakers(room: Room | null): Set<string> {
5
+ const [activeSpeakerIds, setActiveSpeakerIds] = useState<Set<string>>(
6
+ () => new Set(),
7
+ );
8
+ const prevSetRef = useRef<Set<string>>(new Set());
9
+
10
+ useEffect(() => {
11
+ if (!room) {
12
+ if (prevSetRef.current.size > 0) {
13
+ prevSetRef.current = new Set();
14
+ setActiveSpeakerIds(new Set());
15
+ }
16
+ return;
17
+ }
18
+
19
+ const handleActiveSpeakersChanged = (speakers: Participant[]) => {
20
+ const newIds = new Set(speakers.map((s) => s.identity));
21
+ const prev = prevSetRef.current;
22
+ if (
23
+ newIds.size !== prev.size ||
24
+ [...newIds].some((id) => !prev.has(id))
25
+ ) {
26
+ prevSetRef.current = newIds;
27
+ setActiveSpeakerIds(newIds);
28
+ }
29
+ };
30
+
31
+ room.on(RoomEvent.ActiveSpeakersChanged, handleActiveSpeakersChanged);
32
+
33
+ return () => {
34
+ room.off(RoomEvent.ActiveSpeakersChanged, handleActiveSpeakersChanged);
35
+ };
36
+ }, [room]);
37
+
38
+ return activeSpeakerIds;
39
+ }
@@ -0,0 +1,103 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { Platform } from 'react-native';
3
+ import { Room, RoomEvent, Track, ConnectionState } from 'livekit-client';
4
+ import { setAudioModeAsync } from 'expo-audio';
5
+ import { useLiveConfig } from '../context/LiveConfigContext';
6
+
7
+ let AudioSession: { startAudioSession: () => void; stopAudioSession: () => void } | null = null;
8
+ if (Platform.OS !== 'web') {
9
+ try { AudioSession = require('@livekit/react-native').AudioSession; } catch {}
10
+ }
11
+
12
+ interface UseRoomAudioOptions {
13
+ roomId: string;
14
+ isSpeaker: boolean;
15
+ isMuted: boolean;
16
+ isConnected: boolean;
17
+ }
18
+
19
+ interface UseRoomAudioReturn {
20
+ isLiveKitConnected: boolean;
21
+ localAudioEnabled: boolean;
22
+ micPermissionDenied: boolean;
23
+ livekitRoom: Room | null;
24
+ }
25
+
26
+ export function useRoomAudio({ roomId, isSpeaker, isMuted, isConnected }: UseRoomAudioOptions): UseRoomAudioReturn {
27
+ const { getRoomToken } = useLiveConfig();
28
+ const [isLiveKitConnected, setIsLiveKitConnected] = useState(false);
29
+ const [localAudioEnabled, setLocalAudioEnabled] = useState(false);
30
+ const [micPermissionDenied, setMicPermissionDenied] = useState(false);
31
+ const [livekitRoom, setLivekitRoom] = useState<Room | null>(null);
32
+ const roomRef = useRef<Room | null>(null);
33
+ const audioElementsRef = useRef<Map<string, HTMLMediaElement>>(new Map());
34
+
35
+ useEffect(() => {
36
+ if (!isConnected || Platform.OS === 'web') return;
37
+ (async () => {
38
+ try {
39
+ await setAudioModeAsync({ shouldPlayInBackground: true, playsInSilentMode: true, interruptionMode: 'duckOthers' });
40
+ } catch {}
41
+ if (AudioSession) AudioSession.startAudioSession();
42
+ })();
43
+ return () => { if (AudioSession) AudioSession.stopAudioSession(); };
44
+ }, [isConnected]);
45
+
46
+ useEffect(() => {
47
+ if (!isConnected || !roomId) return;
48
+ let cancelled = false;
49
+ const room = new Room({ adaptiveStream: true, dynacast: true });
50
+ roomRef.current = room;
51
+ setLivekitRoom(room);
52
+
53
+ room.on(RoomEvent.Connected, () => { if (!cancelled) { setIsLiveKitConnected(true); } });
54
+ room.on(RoomEvent.Disconnected, () => { if (!cancelled) { setIsLiveKitConnected(false); setLocalAudioEnabled(false); } });
55
+ room.on(RoomEvent.TrackSubscribed, (track, _pub, _participant) => {
56
+ if (track.kind === Track.Kind.Audio && Platform.OS === 'web' && typeof track.attach === 'function') {
57
+ const el = track.attach();
58
+ audioElementsRef.current.set(track.sid ?? '', el);
59
+ }
60
+ });
61
+ room.on(RoomEvent.TrackUnsubscribed, (track) => {
62
+ if (track.kind === Track.Kind.Audio && Platform.OS === 'web' && typeof track.detach === 'function') {
63
+ track.detach();
64
+ audioElementsRef.current.delete(track.sid ?? '');
65
+ }
66
+ });
67
+
68
+ (async () => {
69
+ try {
70
+ const { token, url } = await getRoomToken(roomId);
71
+ if (cancelled || !url) return;
72
+ await room.connect(url, token);
73
+ } catch (err) { console.warn('[RoomAudio] LiveKit connection error:', err); }
74
+ })();
75
+
76
+ return () => {
77
+ cancelled = true;
78
+ if (Platform.OS === 'web') {
79
+ audioElementsRef.current.forEach((el) => { el.pause(); el.srcObject = null; el.remove(); });
80
+ audioElementsRef.current.clear();
81
+ }
82
+ room.disconnect();
83
+ roomRef.current = null;
84
+ setLivekitRoom(null);
85
+ setIsLiveKitConnected(false);
86
+ setLocalAudioEnabled(false);
87
+ };
88
+ }, [isConnected, roomId, getRoomToken]);
89
+
90
+ useEffect(() => {
91
+ const room = roomRef.current;
92
+ if (!room || room.state !== ConnectionState.Connected) return;
93
+ const shouldPublish = isSpeaker && !isMuted;
94
+ room.localParticipant.setMicrophoneEnabled(shouldPublish)
95
+ .then(() => { setLocalAudioEnabled(shouldPublish); setMicPermissionDenied(false); })
96
+ .catch((err) => {
97
+ console.warn('[RoomAudio] Failed to toggle mic:', err);
98
+ if (err instanceof Error && err.name === 'NotAllowedError') setMicPermissionDenied(true);
99
+ });
100
+ }, [isSpeaker, isMuted, isLiveKitConnected]);
101
+
102
+ return { isLiveKitConnected, localAudioEnabled, micPermissionDenied, livekitRoom };
103
+ }
@@ -0,0 +1,162 @@
1
+ import { useState, useEffect, useCallback, useRef } from 'react';
2
+ import { useAuth } from '@oxyhq/services';
3
+ import { createAudioPlayer } from 'expo-audio';
4
+ import { useLiveConfig } from '../context/LiveConfigContext';
5
+ import type { RoomParticipant, StreamInfo } from '../types';
6
+
7
+ interface UseRoomConnectionOptions {
8
+ roomId: string;
9
+ enabled?: boolean;
10
+ }
11
+
12
+ interface UseRoomConnectionReturn {
13
+ isConnected: boolean;
14
+ participants: RoomParticipant[];
15
+ myRole: 'host' | 'speaker' | 'listener' | null;
16
+ isMuted: boolean;
17
+ speakerRequests: Array<{ userId: string; requestedAt: string }>;
18
+ activeStream: StreamInfo | null;
19
+ isRecording: boolean;
20
+ activeRecordingId: string | null;
21
+ join: () => void;
22
+ leave: () => void;
23
+ toggleMute: () => void;
24
+ requestToSpeak: () => void;
25
+ approveSpeaker: (userId: string) => void;
26
+ denySpeaker: (userId: string) => void;
27
+ removeSpeaker: (userId: string) => void;
28
+ isRoomEnded: boolean;
29
+ }
30
+
31
+ export function useRoomConnection({
32
+ roomId,
33
+ enabled = true,
34
+ }: UseRoomConnectionOptions): UseRoomConnectionReturn {
35
+ const { user, isAuthenticated, isReady, oxyServices } = useAuth();
36
+ const { roomSocketService, introSound } = useLiveConfig();
37
+ const userId = user?.id;
38
+
39
+ const [isConnected, setIsConnected] = useState(false);
40
+ const [participants, setParticipants] = useState<RoomParticipant[]>([]);
41
+ const [isMuted, setIsMuted] = useState(true);
42
+ const [speakerRequests, setSpeakerRequests] = useState<Array<{ userId: string; requestedAt: string }>>([]);
43
+ const [isRoomEnded, setIsRoomEnded] = useState(false);
44
+ const [activeStream, setActiveStream] = useState<StreamInfo | null>(null);
45
+ const [isRecording, setIsRecording] = useState(false);
46
+ const [activeRecordingId, setActiveRecordingId] = useState<string | null>(null);
47
+ const hasJoined = useRef(false);
48
+
49
+ const myParticipant = participants.find((p) => p.userId === userId);
50
+ const myRole = myParticipant?.role ?? null;
51
+
52
+ useEffect(() => {
53
+ if (!enabled || !isAuthenticated || !isReady || !userId) return;
54
+ const token = oxyServices?.getAccessToken() ?? undefined;
55
+ if (!token) return;
56
+ roomSocketService.connect(userId, token);
57
+ const interval = setInterval(() => { setIsConnected(roomSocketService.isConnected); }, 500);
58
+ return () => { clearInterval(interval); };
59
+ }, [enabled, isAuthenticated, isReady, userId, roomSocketService, oxyServices]);
60
+
61
+ useEffect(() => {
62
+ if (!enabled) return;
63
+ const unsubs: Array<() => void> = [];
64
+
65
+ unsubs.push(roomSocketService.onParticipantsUpdate((data) => {
66
+ if (data.roomId === roomId) setParticipants(data.participants);
67
+ }));
68
+ unsubs.push(roomSocketService.onParticipantMute((data) => {
69
+ setParticipants((prev) => prev.map((p) => p.userId === data.userId ? { ...p, isMuted: data.isMuted } : p));
70
+ if (data.userId === userId) setIsMuted(data.isMuted);
71
+ }));
72
+ unsubs.push(roomSocketService.onSpeakerRequestReceived((data) => {
73
+ if (data.roomId === roomId) {
74
+ setSpeakerRequests((prev) => {
75
+ if (prev.some((r) => r.userId === data.userId)) return prev;
76
+ return [...prev, { userId: data.userId, requestedAt: data.timestamp }];
77
+ });
78
+ }
79
+ }));
80
+ unsubs.push(roomSocketService.onRoomEnded((data) => {
81
+ if (data.roomId === roomId) { setIsRoomEnded(true); setActiveStream(null); }
82
+ }));
83
+ unsubs.push(roomSocketService.onSpeakerRemoved((data) => {
84
+ if (data.roomId === roomId) setIsMuted(true);
85
+ }));
86
+ unsubs.push(roomSocketService.onStreamStarted((data) => {
87
+ if (data.roomId === roomId) setActiveStream({ title: data.title, image: data.image, description: data.description, startedAt: data.startedAt, durationSec: data.durationSec });
88
+ }));
89
+ unsubs.push(roomSocketService.onStreamStopped((data) => {
90
+ if (data.roomId === roomId) setActiveStream(null);
91
+ }));
92
+ unsubs.push(roomSocketService.onRecordingStarted((data) => {
93
+ if (data.roomId === roomId) {
94
+ setIsRecording(true);
95
+ setActiveRecordingId(data.recordingId);
96
+ }
97
+ }));
98
+ unsubs.push(roomSocketService.onRecordingStopped((data) => {
99
+ if (data.roomId === roomId) {
100
+ setIsRecording(false);
101
+ setActiveRecordingId(null);
102
+ }
103
+ }));
104
+
105
+ return () => { unsubs.forEach((fn) => fn()); };
106
+ }, [enabled, roomId, userId, roomSocketService]);
107
+
108
+ const join = useCallback(() => {
109
+ if (hasJoined.current) return;
110
+ roomSocketService.joinRoom(roomId, (res) => {
111
+ if (res.success && res.participants) {
112
+ setParticipants(res.participants);
113
+ hasJoined.current = true;
114
+ if (introSound) {
115
+ try { const player = createAudioPlayer(introSound); player.play(); } catch {}
116
+ }
117
+ }
118
+ });
119
+ }, [roomId, roomSocketService, introSound]);
120
+
121
+ const leave = useCallback(() => {
122
+ roomSocketService.leaveRoom(roomId);
123
+ setParticipants([]);
124
+ hasJoined.current = false;
125
+ }, [roomId, roomSocketService]);
126
+
127
+ const toggleMute = useCallback(() => {
128
+ const newMuted = !isMuted;
129
+ setIsMuted(newMuted);
130
+ roomSocketService.setMute(roomId, newMuted);
131
+ }, [roomId, isMuted, roomSocketService]);
132
+
133
+ const requestToSpeak = useCallback(() => {
134
+ if (myRole === 'speaker' || myRole === 'host') return;
135
+ roomSocketService.requestToSpeak(roomId);
136
+ }, [roomId, myRole, roomSocketService]);
137
+
138
+ const approveSpeaker = useCallback((targetUserId: string) => {
139
+ roomSocketService.approveSpeaker(roomId, targetUserId);
140
+ setSpeakerRequests((prev) => prev.filter((r) => r.userId !== targetUserId));
141
+ }, [roomId, roomSocketService]);
142
+
143
+ const denySpeaker = useCallback((targetUserId: string) => {
144
+ roomSocketService.denySpeaker(roomId, targetUserId);
145
+ setSpeakerRequests((prev) => prev.filter((r) => r.userId !== targetUserId));
146
+ }, [roomId, roomSocketService]);
147
+
148
+ const removeSpeaker = useCallback((targetUserId: string) => {
149
+ roomSocketService.removeSpeaker(roomId, targetUserId);
150
+ }, [roomId, roomSocketService]);
151
+
152
+ useEffect(() => {
153
+ return () => {
154
+ if (hasJoined.current && roomId) {
155
+ roomSocketService.leaveRoom(roomId);
156
+ hasJoined.current = false;
157
+ }
158
+ };
159
+ }, [roomId, roomSocketService]);
160
+
161
+ return { isConnected, participants, myRole, isMuted, speakerRequests, activeStream, isRecording, activeRecordingId, join, leave, toggleMute, requestToSpeak, approveSpeaker, denySpeaker, removeSpeaker, isRoomEnded };
162
+ }
@@ -0,0 +1,17 @@
1
+ import { useState, useCallback } from "react";
2
+ import type { RoomAttachmentData } from "../types";
3
+
4
+ export const useRoomManager = () => {
5
+ const [room, setRoom] = useState<RoomAttachmentData | null>(null);
6
+
7
+ const attachRoom = useCallback((data: RoomAttachmentData) => { setRoom(data); }, []);
8
+ const removeRoom = useCallback(() => { setRoom(null); }, []);
9
+ const hasContent = useCallback(() => {
10
+ if (!room) return false;
11
+ return Boolean(room.roomId && room.title?.trim());
12
+ }, [room]);
13
+ const loadRoomFromDraft = useCallback((draftRoom: RoomAttachmentData | null) => { setRoom(draftRoom); }, []);
14
+ const clearRoom = useCallback(() => { setRoom(null); }, []);
15
+
16
+ return { room, setRoom, attachRoom, removeRoom, hasContent, loadRoomFromDraft, clearRoom };
17
+ };