@dcl-regenesislabs/web-editor 0.0.0-stage → 0.1.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 (277) hide show
  1. package/editor.css +1 -0
  2. package/editor.js +33891 -0
  3. package/package.json +8 -4
  4. package/prefabs/admin-tools/composite.json +77 -0
  5. package/prefabs/admin-tools/data.json +28 -0
  6. package/prefabs/admin-tools/icons/admin-panel-control-button.png +0 -0
  7. package/prefabs/admin-tools/icons/admin-panel-moderation-control-button.png +0 -0
  8. package/prefabs/admin-tools/icons/admin-panel-rewards-control-button.png +0 -0
  9. package/prefabs/admin-tools/icons/admin-panel-smart-item-control-button.png +0 -0
  10. package/prefabs/admin-tools/icons/admin-panel-text-announcement-control-button.png +0 -0
  11. package/prefabs/admin-tools/icons/admin-panel-video-control-button.png +0 -0
  12. package/prefabs/admin-tools/icons/admin-tool-background.png +0 -0
  13. package/prefabs/admin-tools/icons/announcements/text-announcement-chat-message.png +0 -0
  14. package/prefabs/admin-tools/icons/announcements/text-announcement-check.png +0 -0
  15. package/prefabs/admin-tools/icons/announcements/text-announcement-close-button.png +0 -0
  16. package/prefabs/admin-tools/icons/moderation/admin-panel-verified-user.png +0 -0
  17. package/prefabs/admin-tools/icons/moderation/ban.png +0 -0
  18. package/prefabs/admin-tools/icons/moderation/chevron-back.png +0 -0
  19. package/prefabs/admin-tools/icons/moderation/chevron-forward.png +0 -0
  20. package/prefabs/admin-tools/icons/moderation/close.png +0 -0
  21. package/prefabs/admin-tools/icons/moderation/error.png +0 -0
  22. package/prefabs/admin-tools/icons/moderation/person-outline.png +0 -0
  23. package/prefabs/admin-tools/icons/moderation-control-icon.png +0 -0
  24. package/prefabs/admin-tools/icons/rewards/close.png +0 -0
  25. package/prefabs/admin-tools/icons/rewards/send.png +0 -0
  26. package/prefabs/admin-tools/icons/rewards-control.png +0 -0
  27. package/prefabs/admin-tools/icons/smart-item-control.png +0 -0
  28. package/prefabs/admin-tools/icons/text-announcement-control.png +0 -0
  29. package/prefabs/admin-tools/icons/video/chevron-down.png +0 -0
  30. package/prefabs/admin-tools/icons/video/chevron-up.png +0 -0
  31. package/prefabs/admin-tools/icons/video/copy-to-clipboard.png +0 -0
  32. package/prefabs/admin-tools/icons/video/error.png +0 -0
  33. package/prefabs/admin-tools/icons/video/eye-off.png +0 -0
  34. package/prefabs/admin-tools/icons/video/eye.png +0 -0
  35. package/prefabs/admin-tools/icons/video/help.png +0 -0
  36. package/prefabs/admin-tools/icons/video/info.png +0 -0
  37. package/prefabs/admin-tools/icons/video/person-outline.png +0 -0
  38. package/prefabs/admin-tools/icons/video/star.png +0 -0
  39. package/prefabs/admin-tools/icons/video/video-control-dcl-cast.png +0 -0
  40. package/prefabs/admin-tools/icons/video/video-control-live.png +0 -0
  41. package/prefabs/admin-tools/icons/video/video-control-loop.png +0 -0
  42. package/prefabs/admin-tools/icons/video/video-control-mute.png +0 -0
  43. package/prefabs/admin-tools/icons/video/video-control-pause-button.png +0 -0
  44. package/prefabs/admin-tools/icons/video/video-control-play-button.png +0 -0
  45. package/prefabs/admin-tools/icons/video/video-control-video-icon.png +0 -0
  46. package/prefabs/admin-tools/icons/video/video-control-volume-minus-button.png +0 -0
  47. package/prefabs/admin-tools/icons/video/video-control-volume-plus-button.png +0 -0
  48. package/prefabs/admin-tools/icons/video-control.png +0 -0
  49. package/prefabs/admin-tools/scripts/actions.ts +206 -0
  50. package/prefabs/admin-tools/scripts/admin.tsx +253 -0
  51. package/prefabs/admin-tools/scripts/api.ts +125 -0
  52. package/prefabs/admin-tools/scripts/components.ts +190 -0
  53. package/prefabs/admin-tools/scripts/icons.ts +53 -0
  54. package/prefabs/admin-tools/scripts/message-bus.ts +297 -0
  55. package/prefabs/admin-tools/scripts/state.ts +69 -0
  56. package/prefabs/admin-tools/scripts/tabs/Moderation.tsx +155 -0
  57. package/prefabs/admin-tools/scripts/tabs/Rewards.tsx +136 -0
  58. package/prefabs/admin-tools/scripts/tabs/SmartItemActions.tsx +167 -0
  59. package/prefabs/admin-tools/scripts/tabs/TextAnnouncements.tsx +195 -0
  60. package/prefabs/admin-tools/scripts/tabs/VideoControl.tsx +156 -0
  61. package/prefabs/admin-tools/scripts/tabs/announcements/Overlay.tsx +94 -0
  62. package/prefabs/admin-tools/scripts/tabs/announcements/icons.ts +15 -0
  63. package/prefabs/admin-tools/scripts/tabs/announcements/state.ts +71 -0
  64. package/prefabs/admin-tools/scripts/tabs/announcements/ui.tsx +37 -0
  65. package/prefabs/admin-tools/scripts/tabs/moderation/AddUserInput.tsx +184 -0
  66. package/prefabs/admin-tools/scripts/tabs/moderation/RemoveAdminConfirmation.tsx +93 -0
  67. package/prefabs/admin-tools/scripts/tabs/moderation/UsersList.tsx +228 -0
  68. package/prefabs/admin-tools/scripts/tabs/moderation/api.ts +35 -0
  69. package/prefabs/admin-tools/scripts/tabs/moderation/icons.ts +23 -0
  70. package/prefabs/admin-tools/scripts/tabs/moderation/kick.ts +95 -0
  71. package/prefabs/admin-tools/scripts/tabs/moderation/state.ts +69 -0
  72. package/prefabs/admin-tools/scripts/tabs/moderation/ui.tsx +172 -0
  73. package/prefabs/admin-tools/scripts/tabs/moderation/utils.ts +88 -0
  74. package/prefabs/admin-tools/scripts/tabs/rewards/Button.tsx +52 -0
  75. package/prefabs/admin-tools/scripts/tabs/rewards/Captcha.tsx +88 -0
  76. package/prefabs/admin-tools/scripts/tabs/rewards/api.ts +138 -0
  77. package/prefabs/admin-tools/scripts/tabs/rewards/claim.ts +99 -0
  78. package/prefabs/admin-tools/scripts/tabs/rewards/controller.ts +103 -0
  79. package/prefabs/admin-tools/scripts/tabs/rewards/icons.ts +13 -0
  80. package/prefabs/admin-tools/scripts/tabs/rewards/state.ts +32 -0
  81. package/prefabs/admin-tools/scripts/tabs/smart-items/selection.ts +90 -0
  82. package/prefabs/admin-tools/scripts/tabs/video/DclCast.tsx +226 -0
  83. package/prefabs/admin-tools/scripts/tabs/video/LiveStream.tsx +284 -0
  84. package/prefabs/admin-tools/scripts/tabs/video/SpeakerShowcase.tsx +112 -0
  85. package/prefabs/admin-tools/scripts/tabs/video/VideoUrl.tsx +107 -0
  86. package/prefabs/admin-tools/scripts/tabs/video/Volume.tsx +69 -0
  87. package/prefabs/admin-tools/scripts/tabs/video/api.ts +172 -0
  88. package/prefabs/admin-tools/scripts/tabs/video/controls.ts +108 -0
  89. package/prefabs/admin-tools/scripts/tabs/video/icons.ts +59 -0
  90. package/prefabs/admin-tools/scripts/tabs/video/speakers.ts +43 -0
  91. package/prefabs/admin-tools/scripts/tabs/video/state.ts +82 -0
  92. package/prefabs/admin-tools/scripts/tabs/video/ui.tsx +243 -0
  93. package/prefabs/admin-tools/scripts/types.ts +46 -0
  94. package/prefabs/admin-tools/scripts/ui.tsx +122 -0
  95. package/prefabs/admin-tools/thumbnail.png +0 -0
  96. package/prefabs/announcer/ai.md +76 -0
  97. package/prefabs/announcer/composite.json +31 -0
  98. package/prefabs/announcer/data.json +35 -0
  99. package/prefabs/announcer/scripts/announcer.tsx +79 -0
  100. package/prefabs/announcer/scripts/pure/toast.ts +30 -0
  101. package/prefabs/announcer/thumbnail.png +0 -0
  102. package/prefabs/black-chair/HighBackChair.glb +0 -0
  103. package/prefabs/black-chair/composite.json +74 -0
  104. package/prefabs/black-chair/data.json +30 -0
  105. package/prefabs/black-chair/scripts/seat.ts +191 -0
  106. package/prefabs/black-chair/thumbnail.png +0 -0
  107. package/prefabs/catalog.json +1 -0
  108. package/prefabs/classic-bench/Bench_01.glb +0 -0
  109. package/prefabs/classic-bench/composite.json +100 -0
  110. package/prefabs/classic-bench/data.json +34 -0
  111. package/prefabs/classic-bench/file1.png +0 -0
  112. package/prefabs/classic-bench/scripts/seat.ts +191 -0
  113. package/prefabs/classic-bench/thumbnail.png +0 -0
  114. package/prefabs/classic-bench-armrests/Bench 1.glb +0 -0
  115. package/prefabs/classic-bench-armrests/composite.json +100 -0
  116. package/prefabs/classic-bench-armrests/data.json +30 -0
  117. package/prefabs/classic-bench-armrests/scripts/seat.ts +191 -0
  118. package/prefabs/classic-bench-armrests/thumbnail.png +0 -0
  119. package/prefabs/classroom-chair/Chair_02.glb +0 -0
  120. package/prefabs/classroom-chair/composite.json +74 -0
  121. package/prefabs/classroom-chair/data.json +34 -0
  122. package/prefabs/classroom-chair/file1.png +0 -0
  123. package/prefabs/classroom-chair/scripts/seat.ts +191 -0
  124. package/prefabs/classroom-chair/thumbnail.png +0 -0
  125. package/prefabs/court-chair/Chair_Wood_01.glb +0 -0
  126. package/prefabs/court-chair/FanstasyPack_TX.png +0 -0
  127. package/prefabs/court-chair/composite.json +74 -0
  128. package/prefabs/court-chair/data.json +34 -0
  129. package/prefabs/court-chair/scripts/seat.ts +191 -0
  130. package/prefabs/court-chair/thumbnail.png +0 -0
  131. package/prefabs/curved-couch/CurvedCouch.glb +0 -0
  132. package/prefabs/curved-couch/composite.json +100 -0
  133. package/prefabs/curved-couch/data.json +30 -0
  134. package/prefabs/curved-couch/scripts/seat.ts +191 -0
  135. package/prefabs/curved-couch/thumbnail.png +0 -0
  136. package/prefabs/game-flow/ai.md +94 -0
  137. package/prefabs/game-flow/composite.json +45 -0
  138. package/prefabs/game-flow/data.json +27 -0
  139. package/prefabs/game-flow/scripts/game-flow.ts +168 -0
  140. package/prefabs/game-flow/scripts/pure/flow.ts +185 -0
  141. package/prefabs/game-flow/thumbnail.png +0 -0
  142. package/prefabs/health-respawn/ai.md +76 -0
  143. package/prefabs/health-respawn/composite.json +31 -0
  144. package/prefabs/health-respawn/data.json +38 -0
  145. package/prefabs/health-respawn/scripts/health-respawn.ts +98 -0
  146. package/prefabs/health-respawn/scripts/health.ts +22 -0
  147. package/prefabs/health-respawn/scripts/pure/health.ts +60 -0
  148. package/prefabs/health-respawn/thumbnail.png +0 -0
  149. package/prefabs/high-stool/HighStool.glb +0 -0
  150. package/prefabs/high-stool/composite.json +74 -0
  151. package/prefabs/high-stool/data.json +30 -0
  152. package/prefabs/high-stool/scripts/seat.ts +191 -0
  153. package/prefabs/high-stool/thumbnail.png +0 -0
  154. package/prefabs/large-couch/ThreeSeater_Couch.glb +0 -0
  155. package/prefabs/large-couch/composite.json +100 -0
  156. package/prefabs/large-couch/data.json +30 -0
  157. package/prefabs/large-couch/scripts/seat.ts +191 -0
  158. package/prefabs/large-couch/thumbnail.png +0 -0
  159. package/prefabs/leaderboard/ai.md +75 -0
  160. package/prefabs/leaderboard/board-panel.glb +0 -0
  161. package/prefabs/leaderboard/composite.json +82 -0
  162. package/prefabs/leaderboard/data.json +35 -0
  163. package/prefabs/leaderboard/scripts/leaderboard.ts +66 -0
  164. package/prefabs/leaderboard/scripts/pure/board.ts +71 -0
  165. package/prefabs/leaderboard/thumbnail.png +0 -0
  166. package/prefabs/loveseat/Couch_01.glb +0 -0
  167. package/prefabs/loveseat/composite.json +100 -0
  168. package/prefabs/loveseat/data.json +34 -0
  169. package/prefabs/loveseat/file1.png +0 -0
  170. package/prefabs/loveseat/scripts/seat.ts +191 -0
  171. package/prefabs/loveseat/thumbnail.png +0 -0
  172. package/prefabs/modern-sofa/ModernSofa.glb +0 -0
  173. package/prefabs/modern-sofa/composite.json +100 -0
  174. package/prefabs/modern-sofa/data.json +30 -0
  175. package/prefabs/modern-sofa/scripts/seat.ts +191 -0
  176. package/prefabs/modern-sofa/thumbnail.png +0 -0
  177. package/prefabs/outdoor-chair/Chair_07.glb +0 -0
  178. package/prefabs/outdoor-chair/composite.json +74 -0
  179. package/prefabs/outdoor-chair/data.json +34 -0
  180. package/prefabs/outdoor-chair/file1.png +0 -0
  181. package/prefabs/outdoor-chair/scripts/seat.ts +191 -0
  182. package/prefabs/outdoor-chair/thumbnail.png +0 -0
  183. package/prefabs/rustic-bench/Bench_01.glb +0 -0
  184. package/prefabs/rustic-bench/PiratesPack_TX.png.png +0 -0
  185. package/prefabs/rustic-bench/composite.json +100 -0
  186. package/prefabs/rustic-bench/data.json +34 -0
  187. package/prefabs/rustic-bench/scripts/seat.ts +191 -0
  188. package/prefabs/rustic-bench/thumbnail.png +0 -0
  189. package/prefabs/rustic-round-stool/Chairwood_01.glb +0 -0
  190. package/prefabs/rustic-round-stool/PiratesPack_TX.png.png +0 -0
  191. package/prefabs/rustic-round-stool/composite.json +74 -0
  192. package/prefabs/rustic-round-stool/data.json +34 -0
  193. package/prefabs/rustic-round-stool/scripts/seat.ts +191 -0
  194. package/prefabs/rustic-round-stool/thumbnail.png +0 -0
  195. package/prefabs/rustic-square-stool/Chairwood_02.glb +0 -0
  196. package/prefabs/rustic-square-stool/PiratesPack_TX.png.png +0 -0
  197. package/prefabs/rustic-square-stool/composite.json +74 -0
  198. package/prefabs/rustic-square-stool/data.json +34 -0
  199. package/prefabs/rustic-square-stool/scripts/seat.ts +191 -0
  200. package/prefabs/rustic-square-stool/thumbnail.png +0 -0
  201. package/prefabs/server-clock/ai.md +67 -0
  202. package/prefabs/server-clock/composite.json +45 -0
  203. package/prefabs/server-clock/data.json +24 -0
  204. package/prefabs/server-clock/scripts/clock-hud.tsx +55 -0
  205. package/prefabs/server-clock/scripts/server-clock.ts +71 -0
  206. package/prefabs/server-clock/thumbnail.png +0 -0
  207. package/prefabs/simple-chair/Chair_03.glb +0 -0
  208. package/prefabs/simple-chair/composite.json +74 -0
  209. package/prefabs/simple-chair/data.json +34 -0
  210. package/prefabs/simple-chair/file1.png +0 -0
  211. package/prefabs/simple-chair/scripts/seat.ts +191 -0
  212. package/prefabs/simple-chair/thumbnail.png +0 -0
  213. package/prefabs/sit-spot/composite.json +45 -0
  214. package/prefabs/sit-spot/data.json +30 -0
  215. package/prefabs/sit-spot/scripts/seat.ts +191 -0
  216. package/prefabs/sit-spot/thumbnail.png +0 -0
  217. package/prefabs/sit-spot-edge/composite.json +42 -0
  218. package/prefabs/sit-spot-edge/data.json +32 -0
  219. package/prefabs/sit-spot-edge/emotes/Sit_Edge1_emote.glb +0 -0
  220. package/prefabs/sit-spot-edge/emotes/Sit_Edge2_emote.glb +0 -0
  221. package/prefabs/sit-spot-edge/scripts/seat.ts +191 -0
  222. package/prefabs/sit-spot-edge/thumbnail.png +0 -0
  223. package/prefabs/small-couch/TwoSeater_Couch.glb +0 -0
  224. package/prefabs/small-couch/composite.json +100 -0
  225. package/prefabs/small-couch/data.json +30 -0
  226. package/prefabs/small-couch/scripts/seat.ts +191 -0
  227. package/prefabs/small-couch/thumbnail.png +0 -0
  228. package/prefabs/spawner/ai.md +109 -0
  229. package/prefabs/spawner/composite.json +73 -0
  230. package/prefabs/spawner/data.json +22 -0
  231. package/prefabs/spawner/scripts/spawner.ts +380 -0
  232. package/prefabs/spawner/thumbnail.png +0 -0
  233. package/prefabs/steampunk-bench/Steampunk Bench.glb +0 -0
  234. package/prefabs/steampunk-bench/composite.json +100 -0
  235. package/prefabs/steampunk-bench/data.json +30 -0
  236. package/prefabs/steampunk-bench/scripts/seat.ts +191 -0
  237. package/prefabs/steampunk-bench/thumbnail.png +0 -0
  238. package/prefabs/steampunk-chair/Steampunk Chair.glb +0 -0
  239. package/prefabs/steampunk-chair/composite.json +74 -0
  240. package/prefabs/steampunk-chair/data.json +30 -0
  241. package/prefabs/steampunk-chair/scripts/seat.ts +191 -0
  242. package/prefabs/steampunk-chair/thumbnail.png +0 -0
  243. package/prefabs/tall-stool/Stool_D.glb +0 -0
  244. package/prefabs/tall-stool/composite.json +74 -0
  245. package/prefabs/tall-stool/data.json +30 -0
  246. package/prefabs/tall-stool/scripts/seat.ts +191 -0
  247. package/prefabs/tall-stool/thumbnail.png +0 -0
  248. package/prefabs/trigger-zone/ai.md +115 -0
  249. package/prefabs/trigger-zone/composite.json +52 -0
  250. package/prefabs/trigger-zone/data.json +31 -0
  251. package/prefabs/trigger-zone/scripts/trigger-zone.ts +89 -0
  252. package/prefabs/trigger-zone/thumbnail.png +0 -0
  253. package/prefabs/video-screen/composite.json +98 -0
  254. package/prefabs/video-screen/data.json +21 -0
  255. package/prefabs/video-screen/thumbnail.png +0 -0
  256. package/prefabs/video-screen/video_player.glb +0 -0
  257. package/prefabs/wooden-chair/Chair 1.glb +0 -0
  258. package/prefabs/wooden-chair/composite.json +74 -0
  259. package/prefabs/wooden-chair/data.json +30 -0
  260. package/prefabs/wooden-chair/scripts/seat.ts +191 -0
  261. package/prefabs/wooden-chair/thumbnail.png +0 -0
  262. package/prefabs/wooden-chair-armrests/Chair 3.glb +0 -0
  263. package/prefabs/wooden-chair-armrests/composite.json +74 -0
  264. package/prefabs/wooden-chair-armrests/data.json +30 -0
  265. package/prefabs/wooden-chair-armrests/scripts/seat.ts +191 -0
  266. package/prefabs/wooden-chair-armrests/thumbnail.png +0 -0
  267. package/scene/about +17 -0
  268. package/scene/bafkreias2w4xuqaic6sdcvblrisayi6xg7drfowzpt5ubjkmrk3mx6czc4 +1 -0
  269. package/scene/bafkreiby2gmthtauf7cckttphmhpecsueyypi2hzlke4lcodg7fm4cm2sq +23 -0
  270. package/scene/bafkreidhwqwajqk4nsuffef5fkdrgmp5ql6vnn2knpqsuqalyosv5o6lk4 +1 -0
  271. package/scene/bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku +0 -0
  272. package/scene/bafybeihw3xlbff4sf5sc3kvmlmlwf2visnty5lfymawlunzt5vrkp5fd7u +90 -0
  273. package/web-build/esbuild.wasm +0 -0
  274. package/web-build/index.js +3 -0
  275. package/web-build/sdk-snapshot.json +1 -0
  276. package/web-build/worker.js +701 -0
  277. package/README.md +0 -3
@@ -0,0 +1,172 @@
1
+ // Video-tab endpoints: the stream key lifecycle (/scene-stream-access) and the
2
+ // DCL Cast room (/cast/*), both on comms-gatekeeper. Port of @dcl/asset-packs
3
+ // src/admin-toolkit-ui/VideoControl/api.ts.
4
+ //
5
+ // The gatekeeper answers in snake_case; the Hub runs every body through a generic
6
+ // toCamelCase. Reading both spellings per field keeps the payload typed instead of
7
+ // pushing an `any` through a key rewriter.
8
+ import { getActiveVideoStreams } from '~system/CommsApi'
9
+ import { ENDPOINTS, request, type Result } from '../../api'
10
+
11
+ export interface StreamKey {
12
+ streamingUrl: string
13
+ streamingKey: string
14
+ createdAt: number
15
+ endsAt: number
16
+ }
17
+
18
+ export interface CastRoom {
19
+ streamLink: string
20
+ watcherLink: string
21
+ streamingKey: string
22
+ placeId: string
23
+ placeName: string
24
+ expiresAt: number
25
+ expiresInDays: number
26
+ }
27
+
28
+ export interface CastTrack {
29
+ sid: string
30
+ identity: string
31
+ sourceType: number
32
+ name: string
33
+ }
34
+
35
+ export interface CastParticipant {
36
+ identity: string
37
+ name: string
38
+ tracks: CastTrack[]
39
+ }
40
+
41
+ function isRecord(value: unknown): value is Record<string, unknown> {
42
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
43
+ }
44
+
45
+ function readString(source: Record<string, unknown>, ...keys: string[]): string {
46
+ for (const key of keys) {
47
+ const value = source[key]
48
+ if (typeof value === 'string') return value
49
+ }
50
+ return ''
51
+ }
52
+
53
+ function readNumber(source: Record<string, unknown>, ...keys: string[]): number {
54
+ for (const key of keys) {
55
+ const value = source[key]
56
+ if (typeof value === 'number' && Number.isFinite(value)) return value
57
+ }
58
+ return 0
59
+ }
60
+
61
+ function toStreamKey(body: unknown): StreamKey {
62
+ const source = isRecord(body) ? body : {}
63
+ return {
64
+ streamingUrl: readString(source, 'streamingUrl', 'streaming_url'),
65
+ streamingKey: readString(source, 'streamingKey', 'streaming_key'),
66
+ createdAt: readNumber(source, 'createdAt', 'created_at'),
67
+ endsAt: readNumber(source, 'endsAt', 'ends_at')
68
+ }
69
+ }
70
+
71
+ function toCastRoom(body: unknown): CastRoom {
72
+ const source = isRecord(body) ? body : {}
73
+ return {
74
+ streamLink: readString(source, 'streamLink', 'stream_link'),
75
+ watcherLink: readString(source, 'watcherLink', 'watcher_link'),
76
+ streamingKey: readString(source, 'streamingKey', 'streaming_key'),
77
+ placeId: readString(source, 'placeId', 'place_id'),
78
+ placeName: readString(source, 'placeName', 'place_name'),
79
+ expiresAt: readNumber(source, 'expiresAt', 'expires_at'),
80
+ expiresInDays: readNumber(source, 'expiresInDays', 'expires_in_days')
81
+ }
82
+ }
83
+
84
+ async function streamKeyRequest(method?: string): Promise<Result<StreamKey>> {
85
+ const init = method === undefined ? undefined : { method, headers: {} }
86
+ const [error, body] = await request<unknown>({ url: ENDPOINTS.streamAccess(), init })
87
+ return error === null ? [null, toStreamKey(body)] : [error, null]
88
+ }
89
+
90
+ export async function getStreamKey(): Promise<Result<StreamKey>> {
91
+ return streamKeyRequest()
92
+ }
93
+
94
+ export async function generateStreamKey(): Promise<Result<StreamKey>> {
95
+ return streamKeyRequest('POST')
96
+ }
97
+
98
+ export async function revokeStreamKey(): Promise<Result<StreamKey>> {
99
+ return streamKeyRequest('DELETE')
100
+ }
101
+
102
+ export async function resetStreamKey(): Promise<Result<StreamKey>> {
103
+ return streamKeyRequest('PUT')
104
+ }
105
+
106
+ export async function getCastRoom(): Promise<Result<CastRoom>> {
107
+ const [error, body] = await request<unknown>({ url: ENDPOINTS.castStreamLink() })
108
+ return error === null ? [null, toCastRoom(body)] : [error, null]
109
+ }
110
+
111
+ export async function getPresenters(): Promise<Result<string[]>> {
112
+ const [error, body] = await request<unknown>({ url: ENDPOINTS.castPresenters() })
113
+ if (error !== null) return [error, null]
114
+ const list = Array.isArray(body) ? body : isRecord(body) ? body.addresses : undefined
115
+ if (!Array.isArray(list)) return [null, []]
116
+ return [null, list.filter((item): item is string => typeof item === 'string')]
117
+ }
118
+
119
+ export async function promotePresenter(address: string): Promise<Result<unknown>> {
120
+ return request({
121
+ url: `${ENDPOINTS.castPresenters()}/${address}`,
122
+ init: { method: 'PUT', headers: {} }
123
+ })
124
+ }
125
+
126
+ // A cast admin has to hold the presenter role before their browser tab can push a
127
+ // track into the room; the Hub promotes silently on every mount.
128
+ export async function ensurePresenterRole(address: string): Promise<void> {
129
+ const [error, presenters] = await getPresenters()
130
+ if (error !== null) return
131
+ const lower = address.toLowerCase()
132
+ if (presenters.some((presenter) => presenter.toLowerCase() === lower)) return
133
+ await promotePresenter(lower)
134
+ }
135
+
136
+ export const SOURCE_LABELS: Record<number, string> = {
137
+ 1: 'Camera',
138
+ 2: 'Screen',
139
+ 3: 'Presentation'
140
+ }
141
+
142
+ export function sourceLabel(sourceType: number): string {
143
+ return SOURCE_LABELS[sourceType] ?? 'Unknown'
144
+ }
145
+
146
+ export function trackName(track: CastTrack): string {
147
+ return `${track.name} - ${sourceLabel(track.sourceType)}`
148
+ }
149
+
150
+ export async function getActiveStreams(): Promise<CastTrack[]> {
151
+ try {
152
+ const response = await getActiveVideoStreams({})
153
+ return response.streams.map((stream) => ({
154
+ sid: stream.trackSid,
155
+ identity: stream.identity,
156
+ sourceType: stream.sourceType,
157
+ name: stream.identity
158
+ }))
159
+ } catch {
160
+ return []
161
+ }
162
+ }
163
+
164
+ export function groupTracks(tracks: CastTrack[]): CastParticipant[] {
165
+ const byIdentity = new Map<string, CastParticipant>()
166
+ for (const track of tracks) {
167
+ const existing = byIdentity.get(track.identity)
168
+ if (existing !== undefined) existing.tracks.push(track)
169
+ else byIdentity.set(track.identity, { identity: track.identity, name: track.name, tracks: [track] })
170
+ }
171
+ return Array.from(byIdentity.values())
172
+ }
@@ -0,0 +1,108 @@
1
+ // Everything that mutates a video screen goes through the admin message bus, so
2
+ // the change is validated against the sender's wallet on every client instead of
3
+ // riding the CRDT channel. Local-only writes here would desync the scene.
4
+ import { engine, Name, VideoPlayer, type Entity } from '@dcl/sdk/ecs'
5
+ import type { AdminToolsValue, VideoPlayerRef } from '../../components'
6
+ import type { AdminMessageBus, VideoState } from '../../message-bus'
7
+
8
+ export const LIVEKIT_STREAM_SRC = 'livekit-video://current-stream'
9
+ export const CAST_SRC_PREFIX = 'livekit-video://'
10
+ export const VIDEO_URL_PREFIX = 'https://'
11
+ export const DEFAULT_VOLUME = 1
12
+ export const VOLUME_STEP = 0.1
13
+
14
+ export interface VideoScreenRef {
15
+ entity: Entity
16
+ customName: string
17
+ }
18
+
19
+ export interface VideoControls {
20
+ play: () => void
21
+ pause: () => void
22
+ restart: () => void
23
+ setSource: (src: string) => void
24
+ setLoop: (loop: boolean) => void
25
+ stepVolume: (step: number) => void
26
+ setVolume: (volume: number) => void
27
+ }
28
+
29
+ function entityName(entity: Entity, index: number): string {
30
+ const named = Name.getOrNull(entity)
31
+ if (named !== null && named.value !== '') return named.value
32
+ return `Screen ${index + 1}`
33
+ }
34
+
35
+ // `linkAllVideoPlayers` puts every screen in the scene under admin control; the
36
+ // authored list still wins on naming and ordering.
37
+ export function managedScreens(config: AdminToolsValue): VideoScreenRef[] {
38
+ const authored: readonly VideoPlayerRef[] = config.videoControl.videoPlayers ?? []
39
+ const screens: VideoScreenRef[] = authored.map((ref, index) => ({
40
+ entity: ref.entity as Entity,
41
+ customName: ref.customName !== '' ? ref.customName : entityName(ref.entity as Entity, index)
42
+ }))
43
+ if (!config.videoControl.linkAllVideoPlayers) return screens
44
+
45
+ const known = new Set(screens.map((screen) => screen.entity))
46
+ for (const [entity] of engine.getEntitiesWith(VideoPlayer)) {
47
+ if (known.has(entity)) continue
48
+ known.add(entity)
49
+ screens.push({ entity, customName: entityName(entity, screens.length) })
50
+ }
51
+ return screens
52
+ }
53
+
54
+ // A cast room and a plain RTMP stream share `livekit-video://current-stream`, so
55
+ // src alone cannot tell them apart — callers break the tie with the media source
56
+ // the admin picked, exactly like the Hub's `selectedStream`.
57
+ export function isCastSource(src: string): boolean {
58
+ return src.startsWith(CAST_SRC_PREFIX)
59
+ }
60
+
61
+ export function isLiveSource(src: string): boolean {
62
+ return src === LIVEKIT_STREAM_SRC
63
+ }
64
+
65
+ export function isVideoUrl(src: string): boolean {
66
+ return src.startsWith(VIDEO_URL_PREFIX)
67
+ }
68
+
69
+ export function volumeOf(entity: Entity): number {
70
+ return VideoPlayer.getOrNull(entity)?.volume ?? DEFAULT_VOLUME
71
+ }
72
+
73
+ // Sound-off scenes never want an unmuted screen, not even for the admin who
74
+ // opened the panel — mute on sight, the same check the Hub runs per render.
75
+ export function enforceMute(screens: VideoScreenRef[]): void {
76
+ for (const screen of screens) {
77
+ const video = VideoPlayer.getOrNull(screen.entity)
78
+ if (video === null || video.volume === 0) continue
79
+ VideoPlayer.getMutable(screen.entity).volume = 0
80
+ }
81
+ }
82
+
83
+ export function createControls(
84
+ bus: AdminMessageBus | null,
85
+ entity: Entity,
86
+ soundDisabled: boolean
87
+ ): VideoControls {
88
+ const send = (command: Partial<VideoState>): void => {
89
+ bus?.setVideo(entity, command)
90
+ }
91
+ return {
92
+ play: () => send({ playing: true }),
93
+ pause: () => send({ playing: false }),
94
+ restart: () => send({ playing: true, position: 0 }),
95
+ setSource: (src) => send({ src, playing: true }),
96
+ setLoop: (loop) => send({ loop }),
97
+ stepVolume: (step) => {
98
+ if (soundDisabled) return
99
+ const steps = Math.round(volumeOf(entity) * 10)
100
+ const next = Math.max(0, Math.min(10, steps + Math.round(step * 10)))
101
+ send({ volume: next / 10 })
102
+ },
103
+ setVolume: (volume) => {
104
+ if (soundDisabled) return
105
+ send({ volume })
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,59 @@
1
+ import { iconPath } from '../../icons'
2
+
3
+ export interface VideoIcons {
4
+ play: string
5
+ pause: string
6
+ loop: string
7
+ mute: string
8
+ volumeMinus: string
9
+ volumePlus: string
10
+ sourceVideo: string
11
+ sourceLive: string
12
+ sourceCast: string
13
+ info: string
14
+ help: string
15
+ eyeShow: string
16
+ eyeHide: string
17
+ copy: string
18
+ star: string
19
+ chevronUp: string
20
+ chevronDown: string
21
+ person: string
22
+ error: string
23
+ }
24
+
25
+ function build(base: string): VideoIcons {
26
+ const icon = (name: string): string => iconPath(base, `video/${name}`)
27
+ return {
28
+ play: icon('video-control-play-button.png'),
29
+ pause: icon('video-control-pause-button.png'),
30
+ loop: icon('video-control-loop.png'),
31
+ mute: icon('video-control-mute.png'),
32
+ volumeMinus: icon('video-control-volume-minus-button.png'),
33
+ volumePlus: icon('video-control-volume-plus-button.png'),
34
+ sourceVideo: icon('video-control-video-icon.png'),
35
+ sourceLive: icon('video-control-live.png'),
36
+ sourceCast: icon('video-control-dcl-cast.png'),
37
+ info: icon('info.png'),
38
+ help: icon('help.png'),
39
+ eyeShow: icon('eye.png'),
40
+ eyeHide: icon('eye-off.png'),
41
+ copy: icon('copy-to-clipboard.png'),
42
+ star: icon('star.png'),
43
+ chevronUp: icon('chevron-up.png'),
44
+ chevronDown: icon('chevron-down.png'),
45
+ person: icon('person-outline.png'),
46
+ error: icon('error.png')
47
+ }
48
+ }
49
+
50
+ // Rebuilt only when the prefab folder changes — this runs on every rendered frame.
51
+ let cachedKey: string | undefined
52
+ let cached: VideoIcons = build('')
53
+
54
+ export function videoIcons(base: string): VideoIcons {
55
+ if (base === cachedKey) return cached
56
+ cachedKey = base
57
+ cached = build(base)
58
+ return cached
59
+ }
@@ -0,0 +1,43 @@
1
+ // Live participants of the DCL Cast room, refreshed from the explorer's comms
2
+ // state. The Hub polls on a 5s interval; scenes have no setInterval, so the poll
3
+ // rides one engine system registered once for the lifetime of the scene and does
4
+ // nothing unless the speaker list is on screen.
5
+ import { engine } from '@dcl/sdk/ecs'
6
+ import { getActiveStreams, groupTracks } from './api'
7
+ import { videoTab } from './state'
8
+
9
+ const POLL_SECONDS = 5
10
+
11
+ let registered = false
12
+ let countdown = 0
13
+ let inFlight = false
14
+
15
+ async function poll(): Promise<void> {
16
+ inFlight = true
17
+ try {
18
+ videoTab.participants = groupTracks(await getActiveStreams())
19
+ } finally {
20
+ inFlight = false
21
+ }
22
+ }
23
+
24
+ export function refreshSpeakers(): void {
25
+ if (inFlight) return
26
+ countdown = POLL_SECONDS
27
+ void poll()
28
+ }
29
+
30
+ export function ensureSpeakerPolling(): void {
31
+ if (registered) return
32
+ registered = true
33
+ engine.addSystem((dt: number) => {
34
+ if (!videoTab.showSpeakers) {
35
+ countdown = 0
36
+ return
37
+ }
38
+ countdown -= dt
39
+ if (countdown > 0 || inFlight) return
40
+ countdown = POLL_SECONDS
41
+ void poll()
42
+ })
43
+ }
@@ -0,0 +1,82 @@
1
+ // Tab-scoped state. The shared AdminState carries the selected screen index (and
2
+ // nothing else this tab needs), while everything below outlives a tab remount and
3
+ // is deliberately module-level: the panel is a singleton, and hook state resets
4
+ // every time the shell re-mounts the tab.
5
+ import type { CastParticipant, CastRoom } from './api'
6
+
7
+ export type MediaSource = 'video-url' | 'live' | 'dcl-cast'
8
+
9
+ export interface VideoTabState {
10
+ source: MediaSource | undefined
11
+ streamKeyLoaded: boolean
12
+ hasStreamKey: boolean
13
+ streamKeyEndsAt: number
14
+ revealedKey: string
15
+ revealUntil: number
16
+ confirmReset: boolean
17
+ castRoom: CastRoom | undefined
18
+ castError: string
19
+ castMinimized: boolean
20
+ participants: CastParticipant[]
21
+ activeTrackSid: string | undefined
22
+ showSpeakers: boolean
23
+ copiedAt: Map<string, number>
24
+ error: string
25
+ }
26
+
27
+ export const videoTab: VideoTabState = {
28
+ source: undefined,
29
+ streamKeyLoaded: false,
30
+ hasStreamKey: false,
31
+ streamKeyEndsAt: 0,
32
+ revealedKey: '',
33
+ revealUntil: 0,
34
+ confirmReset: false,
35
+ castRoom: undefined,
36
+ castError: '',
37
+ castMinimized: false,
38
+ participants: [],
39
+ activeTrackSid: undefined,
40
+ showSpeakers: false,
41
+ copiedAt: new Map(),
42
+ error: ''
43
+ }
44
+
45
+ export const REVEAL_SECONDS = 30
46
+ const FEEDBACK_MS = 1500
47
+
48
+ export function markCopied(id: string): void {
49
+ videoTab.copiedAt.set(id, Date.now())
50
+ }
51
+
52
+ export function wasCopied(id: string): boolean {
53
+ const at = videoTab.copiedAt.get(id)
54
+ return at !== undefined && Date.now() - at < FEEDBACK_MS
55
+ }
56
+
57
+ export function revealKey(key: string): void {
58
+ videoTab.revealedKey = key
59
+ videoTab.revealUntil = Date.now() + REVEAL_SECONDS * 1000
60
+ }
61
+
62
+ export function hideKey(): void {
63
+ videoTab.revealedKey = ''
64
+ videoTab.revealUntil = 0
65
+ }
66
+
67
+ // The UI re-renders every frame, so "is the key still visible" is a function of
68
+ // the clock — no timer system, no cleanup to leak. Expiry also drops the key from
69
+ // memory, which is the point of the auto-hide.
70
+ export function revealRemaining(): number {
71
+ if (videoTab.revealedKey === '') return 0
72
+ const remaining = (videoTab.revealUntil - Date.now()) / 1000
73
+ if (remaining <= 0) {
74
+ hideKey()
75
+ return 0
76
+ }
77
+ return remaining
78
+ }
79
+
80
+ export function isKeyVisible(): boolean {
81
+ return revealRemaining() > 0
82
+ }