@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,284 @@
1
+ import ReactEcs, { Label, UiEntity } from '@dcl/sdk/react-ecs'
2
+ import { VideoPlayer, type Entity } from '@dcl/sdk/ecs'
3
+ import { copyToClipboard, openExternalUrl } from '~system/RestrictedActions'
4
+ import { VideoControlState } from '../../components'
5
+ import { generateStreamKey, getStreamKey, resetStreamKey } from './api'
6
+ import { isLiveSource, LIVEKIT_STREAM_SRC, type VideoControls } from './controls'
7
+ import type { VideoIcons } from './icons'
8
+ import {
9
+ hideKey,
10
+ isKeyVisible,
11
+ markCopied,
12
+ REVEAL_SECONDS,
13
+ revealKey,
14
+ revealRemaining,
15
+ videoTab,
16
+ wasCopied
17
+ } from './state'
18
+ import { Volume } from './Volume'
19
+ import { Button, COLORS, Column, ErrorNote, Hint, Row, SectionLabel, SubHeader, ReadOnlyField } from './ui'
20
+
21
+ const RTMP_SERVER_URL = 'rtmps://dcl.rtmp.livekit.cloud/x'
22
+ const SUPPORT_URL = 'https://docs.decentraland.org//creator/editor/live-streaming'
23
+
24
+ function formatRemaining(endsAt: number): string {
25
+ const remaining = Math.max(0, endsAt - Date.now())
26
+ const days = Math.floor(remaining / 86_400_000)
27
+ if (days >= 1) return `${days} ${days === 1 ? 'day' : 'days'}`
28
+ const hours = Math.floor(remaining / 3_600_000)
29
+ const minutes = Math.floor((remaining % 3_600_000) / 60_000)
30
+ const seconds = Math.floor((remaining % 60_000) / 1000)
31
+ return [hours, minutes, seconds].map((part) => part.toString().padStart(2, '0')).join(':')
32
+ }
33
+
34
+ function storeEndsAt(self: Entity, endsAt: number | undefined): void {
35
+ const state = VideoControlState.getMutableOrNull(self)
36
+ if (state === null) return
37
+ state.endsAt = endsAt
38
+ }
39
+
40
+ export function LiveStream(props: {
41
+ self: Entity
42
+ entity: Entity
43
+ icons: VideoIcons
44
+ controls: VideoControls
45
+ soundDisabled: boolean
46
+ }): ReactEcs.JSX.Element {
47
+ const [loading, setLoading] = ReactEcs.useState(!videoTab.streamKeyLoaded)
48
+ const [busy, setBusy] = ReactEcs.useState(false)
49
+ const [error, setError] = ReactEcs.useState('')
50
+
51
+ ReactEcs.useEffect(() => {
52
+ if (videoTab.streamKeyLoaded) return
53
+ void (async () => {
54
+ const [failure, data] = await getStreamKey()
55
+ videoTab.streamKeyLoaded = true
56
+ videoTab.hasStreamKey = failure === null
57
+ videoTab.streamKeyEndsAt = failure === null ? data.endsAt : 0
58
+ storeEndsAt(props.self, failure === null ? data.endsAt : undefined)
59
+ setLoading(false)
60
+ })()
61
+ }, [])
62
+
63
+ const video = VideoPlayer.getOrNull(props.entity)
64
+ const active = isLiveSource(video?.src ?? '')
65
+ const visible = isKeyVisible()
66
+ const endsAt = VideoControlState.getOrNull(props.self)?.endsAt ?? videoTab.streamKeyEndsAt
67
+
68
+ if (videoTab.confirmReset) {
69
+ return (
70
+ <Column uiTransform={{ alignItems: 'center', padding: { top: 24, bottom: 24 } }}>
71
+ <Label
72
+ value="<b>Are you sure you want to reset your Stream Key?</b>"
73
+ fontSize={16}
74
+ color={COLORS.offWhite}
75
+ />
76
+ <Hint
77
+ text="Active streams using this stream key will be disconnected."
78
+ uiTransform={{ margin: { top: 6, bottom: 20 } }}
79
+ />
80
+ <Row uiTransform={{ justifyContent: 'center' }}>
81
+ <Button
82
+ label="Cancel"
83
+ variant="primary"
84
+ disabled={busy}
85
+ uiTransform={{ width: 'auto', margin: { right: 16 } }}
86
+ onClick={() => {
87
+ videoTab.confirmReset = false
88
+ }}
89
+ />
90
+ <Button
91
+ label="Reset"
92
+ variant="danger"
93
+ disabled={busy}
94
+ uiTransform={{ width: 'auto' }}
95
+ onClick={() => {
96
+ setBusy(true)
97
+ void (async () => {
98
+ const [failure, data] = await resetStreamKey()
99
+ setBusy(false)
100
+ if (failure !== null) {
101
+ setError(failure)
102
+ return
103
+ }
104
+ hideKey()
105
+ videoTab.hasStreamKey = true
106
+ videoTab.streamKeyEndsAt = data.endsAt
107
+ storeEndsAt(props.self, data.endsAt)
108
+ videoTab.confirmReset = false
109
+ })()
110
+ }}
111
+ />
112
+ </Row>
113
+ {error !== '' ? <ErrorNote icon={props.icons.error} text={error} /> : null}
114
+ </Column>
115
+ )
116
+ }
117
+
118
+ return (
119
+ <Column>
120
+ <SubHeader
121
+ icon={props.icons.sourceLive}
122
+ title="Stream"
123
+ helpIcon={props.icons.help}
124
+ onHelp={() => {
125
+ void openExternalUrl({ url: SUPPORT_URL })
126
+ }}
127
+ />
128
+ <Hint text="Use the RTMP server and stream key below in your broadcasting software to start streaming to this screen." />
129
+
130
+ {loading ? <Hint text="Loading stream key…" uiTransform={{ margin: { top: 16 } }} /> : null}
131
+
132
+ {!loading && !videoTab.hasStreamKey ? (
133
+ <Column uiTransform={{ alignItems: 'center', margin: { top: 16 } }}>
134
+ <Button
135
+ label={busy ? 'Generating…' : 'Get Stream Key'}
136
+ variant="primary"
137
+ disabled={busy}
138
+ fontSize={17}
139
+ uiTransform={{ width: 'auto', height: 46 }}
140
+ onClick={() => {
141
+ setBusy(true)
142
+ setError('')
143
+ void (async () => {
144
+ const [failure, data] = await generateStreamKey()
145
+ setBusy(false)
146
+ if (failure !== null) {
147
+ setError(failure)
148
+ return
149
+ }
150
+ videoTab.hasStreamKey = true
151
+ videoTab.streamKeyEndsAt = data.endsAt
152
+ storeEndsAt(props.self, data.endsAt)
153
+ })()
154
+ }}
155
+ />
156
+ <Hint
157
+ text="Do not share your stream key with anyone, and be careful not to display it on screen while streaming."
158
+ color={COLORS.danger}
159
+ uiTransform={{ margin: { top: 16 } }}
160
+ />
161
+ </Column>
162
+ ) : null}
163
+
164
+ {!loading && videoTab.hasStreamKey ? (
165
+ <Column>
166
+ <SectionLabel text="RTMP Server" />
167
+ <ReadOnlyField
168
+ value={RTMP_SERVER_URL}
169
+ actionLabel={wasCopied('rtmp') ? 'Copied!' : 'Copy'}
170
+ onAction={() => {
171
+ void copyToClipboard({ text: RTMP_SERVER_URL })
172
+ markCopied('rtmp')
173
+ }}
174
+ />
175
+ <SectionLabel text="Stream Key" />
176
+ <ReadOnlyField
177
+ value={visible ? videoTab.revealedKey : '************'}
178
+ actionLabel={wasCopied('key') ? 'Copied!' : 'Copy'}
179
+ onAction={() => {
180
+ if (visible) {
181
+ void copyToClipboard({ text: videoTab.revealedKey })
182
+ markCopied('key')
183
+ return
184
+ }
185
+ void (async () => {
186
+ const [failure, data] = await getStreamKey()
187
+ if (failure !== null) {
188
+ setError(failure)
189
+ return
190
+ }
191
+ void copyToClipboard({ text: data.streamingKey })
192
+ markCopied('key')
193
+ })()
194
+ }}
195
+ >
196
+ <UiEntity
197
+ uiTransform={{ width: 22, height: 22, margin: { right: 8 } }}
198
+ uiBackground={{
199
+ textureMode: 'stretch',
200
+ texture: { src: visible ? props.icons.eyeHide : props.icons.eyeShow },
201
+ color: COLORS.black
202
+ }}
203
+ onMouseDown={() => {
204
+ if (visible) {
205
+ hideKey()
206
+ return
207
+ }
208
+ void (async () => {
209
+ const [failure, data] = await getStreamKey()
210
+ if (failure !== null) {
211
+ setError(failure)
212
+ return
213
+ }
214
+ revealKey(data.streamingKey)
215
+ })()
216
+ }}
217
+ />
218
+ </ReadOnlyField>
219
+
220
+ <UiEntity
221
+ uiTransform={{ width: '100%', height: 4, display: visible ? 'flex' : 'none' }}
222
+ uiBackground={{ color: COLORS.line }}
223
+ >
224
+ <UiEntity
225
+ uiTransform={{ width: `${(revealRemaining() / REVEAL_SECONDS) * 100}%`, height: '100%' }}
226
+ uiBackground={{ color: COLORS.accent }}
227
+ />
228
+ </UiEntity>
229
+
230
+ <Row uiTransform={{ justifyContent: 'space-between', margin: { top: 12 } }}>
231
+ {endsAt > Date.now() ? (
232
+ <Column uiTransform={{ width: 'auto' }}>
233
+ <Hint text="Stream expires in:" color={COLORS.dim} />
234
+ <Hint text={formatRemaining(endsAt)} color={COLORS.dim} />
235
+ </Column>
236
+ ) : (
237
+ <ErrorNote
238
+ icon={props.icons.error}
239
+ text="Stream timed out. Please restart the stream in your broadcasting software."
240
+ />
241
+ )}
242
+ {active ? (
243
+ <Button
244
+ label="Deactivate"
245
+ variant="text"
246
+ uiTransform={{ width: 'auto' }}
247
+ onClick={() => props.controls.setSource('')}
248
+ />
249
+ ) : (
250
+ <Button
251
+ label="Activate"
252
+ variant="success"
253
+ uiTransform={{ width: 'auto' }}
254
+ onClick={() => props.controls.setSource(LIVEKIT_STREAM_SRC)}
255
+ />
256
+ )}
257
+ </Row>
258
+
259
+ <Volume
260
+ label="Stream Volume"
261
+ entity={props.entity}
262
+ icons={props.icons}
263
+ controls={props.controls}
264
+ soundDisabled={props.soundDisabled}
265
+ />
266
+
267
+ <Row uiTransform={{ margin: { top: 16 } }}>
268
+ <Button
269
+ label="Reset Stream Key"
270
+ variant="danger"
271
+ uiTransform={{ width: 'auto' }}
272
+ onClick={() => {
273
+ setError('')
274
+ videoTab.confirmReset = true
275
+ }}
276
+ />
277
+ </Row>
278
+ </Column>
279
+ ) : null}
280
+
281
+ {error !== '' ? <ErrorNote icon={props.icons.error} text={error} /> : null}
282
+ </Column>
283
+ )
284
+ }
@@ -0,0 +1,112 @@
1
+ import ReactEcs, { Label, UiEntity } from '@dcl/sdk/react-ecs'
2
+ import { sourceLabel, type CastParticipant, type CastTrack } from './api'
3
+ import type { VideoIcons } from './icons'
4
+ import { refreshSpeakers } from './speakers'
5
+ import { videoTab } from './state'
6
+ import { Button, COLORS, Column, Divider, Hint, Row } from './ui'
7
+
8
+ function ParticipantRow(props: {
9
+ key?: string
10
+ participant: CastParticipant
11
+ icons: VideoIcons
12
+ onSelect: (track: CastTrack) => void
13
+ }): ReactEcs.JSX.Element {
14
+ return (
15
+ <Column>
16
+ <Row uiTransform={{ justifyContent: 'space-between', margin: { top: 6, bottom: 6 } }}>
17
+ <Row uiTransform={{ width: 'auto' }}>
18
+ <UiEntity
19
+ uiTransform={{ width: 20, height: 20, margin: { right: 8 } }}
20
+ uiBackground={{
21
+ textureMode: 'stretch',
22
+ texture: { src: props.icons.person },
23
+ color: COLORS.white
24
+ }}
25
+ />
26
+ <Label
27
+ value={`<b>${props.participant.name}</b>`}
28
+ fontSize={14}
29
+ color={COLORS.white}
30
+ textAlign="middle-left"
31
+ />
32
+ </Row>
33
+ <Row uiTransform={{ width: 'auto' }}>
34
+ {props.participant.tracks.map((track) => (
35
+ <Button
36
+ key={track.sid}
37
+ label={sourceLabel(track.sourceType)}
38
+ variant={videoTab.activeTrackSid === track.sid ? 'primary' : 'secondary'}
39
+ icon={videoTab.activeTrackSid === track.sid ? props.icons.star : undefined}
40
+ fontSize={14}
41
+ uiTransform={{ width: 'auto', height: 32, margin: { left: 6 } }}
42
+ onClick={() => props.onSelect(track)}
43
+ />
44
+ ))}
45
+ </Row>
46
+ </Row>
47
+ <Divider />
48
+ </Column>
49
+ )
50
+ }
51
+
52
+ export function SpeakerShowcase(props: {
53
+ icons: VideoIcons
54
+ onSelectTrack: (track: CastTrack) => void
55
+ onAutomatic: () => void
56
+ }): ReactEcs.JSX.Element {
57
+ const automatic = videoTab.activeTrackSid === undefined
58
+ return (
59
+ <Column uiTransform={{ margin: { top: 12 } }}>
60
+ <Row uiTransform={{ justifyContent: 'space-between' }}>
61
+ <Label
62
+ value={`<b>SPEAKER SHOWCASE</b> (${videoTab.participants.length})`}
63
+ fontSize={16}
64
+ color={COLORS.white}
65
+ textAlign="middle-left"
66
+ />
67
+ <Button
68
+ label="Refresh"
69
+ variant="text"
70
+ fontSize={14}
71
+ uiTransform={{ width: 'auto', height: 30 }}
72
+ onClick={refreshSpeakers}
73
+ />
74
+ </Row>
75
+ <Row uiTransform={{ justifyContent: 'space-between', margin: { top: 8, bottom: 8 } }}>
76
+ <Column uiTransform={{ width: '60%' }}>
77
+ <Label
78
+ value="Automatic Showcase"
79
+ fontSize={14}
80
+ color={COLORS.white}
81
+ textAlign="middle-left"
82
+ uiTransform={{ height: 20 }}
83
+ />
84
+ <Hint text="Speakers are featured automatically when they speak." />
85
+ </Column>
86
+ <Button
87
+ label={automatic ? 'Active' : 'Turn On'}
88
+ icon={automatic ? props.icons.star : undefined}
89
+ variant={automatic ? 'primary' : 'secondary'}
90
+ fontSize={14}
91
+ disabled={automatic}
92
+ uiTransform={{ width: 'auto', height: 32 }}
93
+ onClick={props.onAutomatic}
94
+ />
95
+ </Row>
96
+ <Divider />
97
+ <Column uiTransform={{ maxHeight: 260, overflow: 'scroll' }}>
98
+ {videoTab.participants.length === 0 ? (
99
+ <Hint text="No active participants in the Cast room." />
100
+ ) : null}
101
+ {videoTab.participants.map((participant) => (
102
+ <ParticipantRow
103
+ key={participant.identity}
104
+ participant={participant}
105
+ icons={props.icons}
106
+ onSelect={props.onSelectTrack}
107
+ />
108
+ ))}
109
+ </Column>
110
+ </Column>
111
+ )
112
+ }
@@ -0,0 +1,107 @@
1
+ import ReactEcs, { Input } from '@dcl/sdk/react-ecs'
2
+ import { openExternalUrl } from '~system/RestrictedActions'
3
+ import { VideoPlayer, type Entity } from '@dcl/sdk/ecs'
4
+ import { isVideoUrl, type VideoControls } from './controls'
5
+ import type { VideoIcons } from './icons'
6
+ import { Volume } from './Volume'
7
+ import { Button, COLORS, Column, Hint, Row, SectionLabel, SubHeader } from './ui'
8
+
9
+ const HELP_URL = 'https://docs.decentraland.org/creator/scene-editor/interactivity/video-screen'
10
+
11
+ export function VideoUrl(props: {
12
+ entity: Entity
13
+ icons: VideoIcons
14
+ controls: VideoControls
15
+ soundDisabled: boolean
16
+ }): ReactEcs.JSX.Element {
17
+ const video = VideoPlayer.getOrNull(props.entity)
18
+ const src = video?.src ?? ''
19
+ const active = isVideoUrl(src)
20
+ const [draft, setDraft] = ReactEcs.useState(active ? src : '')
21
+
22
+ ReactEcs.useEffect(() => {
23
+ setDraft(isVideoUrl(src) ? src : '')
24
+ }, [props.entity])
25
+
26
+ return (
27
+ <Column>
28
+ <SubHeader
29
+ icon={props.icons.sourceVideo}
30
+ title="Video URL"
31
+ helpIcon={props.icons.help}
32
+ onHelp={() => {
33
+ void openExternalUrl({ url: HELP_URL })
34
+ }}
35
+ />
36
+ <Hint text="Play videos by pasting an .m3u8 video URL below." />
37
+ <SectionLabel text="Video URL" />
38
+ <Input
39
+ value={draft}
40
+ onChange={setDraft}
41
+ fontSize={15}
42
+ textAlign="middle-left"
43
+ placeholder="Paste your video URL"
44
+ placeholderColor={COLORS.gray}
45
+ color={COLORS.black}
46
+ uiBackground={{ color: COLORS.offWhite }}
47
+ uiTransform={{ width: '100%', height: 44, borderRadius: 8 }}
48
+ />
49
+ <Row uiTransform={{ justifyContent: 'flex-end', margin: { top: 10 } }}>
50
+ {active ? (
51
+ <Button
52
+ label="Deactivate"
53
+ variant="text"
54
+ uiTransform={{ width: 'auto', margin: { right: 8 } }}
55
+ onClick={() => props.controls.setSource('')}
56
+ />
57
+ ) : null}
58
+ {draft !== src ? (
59
+ <Button
60
+ label={active ? 'Update' : 'Activate'}
61
+ variant="success"
62
+ disabled={!isVideoUrl(draft)}
63
+ uiTransform={{ width: 'auto' }}
64
+ onClick={() => props.controls.setSource(draft)}
65
+ />
66
+ ) : null}
67
+ </Row>
68
+ <SectionLabel text="Video Playback" />
69
+ <Row>
70
+ <Button
71
+ label="Play"
72
+ icon={props.icons.play}
73
+ disabled={!active}
74
+ uiTransform={{ width: 'auto', margin: { right: 8 } }}
75
+ onClick={props.controls.play}
76
+ />
77
+ <Button
78
+ label="Pause"
79
+ icon={props.icons.pause}
80
+ disabled={!active}
81
+ uiTransform={{ width: 'auto', margin: { right: 8 } }}
82
+ onClick={props.controls.pause}
83
+ />
84
+ <Button
85
+ label="Restart"
86
+ disabled={!active}
87
+ uiTransform={{ width: 'auto', margin: { right: 8 } }}
88
+ onClick={props.controls.restart}
89
+ />
90
+ <Button
91
+ icon={props.icons.loop}
92
+ variant={video?.loop === true ? 'primary' : 'secondary'}
93
+ disabled={!active}
94
+ uiTransform={{ width: 46 }}
95
+ onClick={() => props.controls.setLoop(video?.loop !== true)}
96
+ />
97
+ </Row>
98
+ <Volume
99
+ label="Video Volume"
100
+ entity={props.entity}
101
+ icons={props.icons}
102
+ controls={props.controls}
103
+ soundDisabled={props.soundDisabled}
104
+ />
105
+ </Column>
106
+ )
107
+ }
@@ -0,0 +1,69 @@
1
+ import ReactEcs, { Label, UiEntity } from '@dcl/sdk/react-ecs'
2
+ import { DEFAULT_VOLUME, VOLUME_STEP, volumeOf, type VideoControls } from './controls'
3
+ import type { VideoIcons } from './icons'
4
+ import { Button, COLORS, Column, Row, SectionLabel } from './ui'
5
+ import type { Entity } from '@dcl/sdk/ecs'
6
+
7
+ export function Volume(props: {
8
+ label: string
9
+ entity: Entity
10
+ icons: VideoIcons
11
+ controls: VideoControls
12
+ soundDisabled: boolean
13
+ }): ReactEcs.JSX.Element {
14
+ if (props.soundDisabled) {
15
+ return (
16
+ <Row uiTransform={{ margin: { top: 12 } }}>
17
+ <UiEntity
18
+ uiTransform={{ width: 22, height: 22, margin: { right: 8 } }}
19
+ uiBackground={{
20
+ textureMode: 'stretch',
21
+ texture: { src: props.icons.mute },
22
+ color: COLORS.gray
23
+ }}
24
+ />
25
+ <Label
26
+ value="Sound is disabled for all screens"
27
+ fontSize={14}
28
+ color={COLORS.gray}
29
+ textAlign="middle-left"
30
+ />
31
+ </Row>
32
+ )
33
+ }
34
+
35
+ const volume = volumeOf(props.entity)
36
+ const percentage = `${Math.round((volume ?? DEFAULT_VOLUME) * 100)}%`
37
+
38
+ return (
39
+ <Column uiTransform={{ margin: { top: 12 } }}>
40
+ <SectionLabel text={props.label} />
41
+ <Row>
42
+ <Button
43
+ icon={props.icons.volumeMinus}
44
+ disabled={volume === 0}
45
+ uiTransform={{ width: 46, margin: { right: 10 } }}
46
+ onClick={() => props.controls.stepVolume(-VOLUME_STEP)}
47
+ />
48
+ <Label
49
+ value={percentage}
50
+ fontSize={16}
51
+ color={COLORS.gray}
52
+ uiTransform={{ width: 56, margin: { right: 10 } }}
53
+ />
54
+ <Button
55
+ icon={props.icons.volumePlus}
56
+ disabled={volume === 1}
57
+ uiTransform={{ width: 46, margin: { right: 10 } }}
58
+ onClick={() => props.controls.stepVolume(VOLUME_STEP)}
59
+ />
60
+ <Button
61
+ icon={props.icons.mute}
62
+ variant={volume === 0 ? 'primary' : 'secondary'}
63
+ uiTransform={{ width: 46 }}
64
+ onClick={() => props.controls.setVolume(volume === 0 ? DEFAULT_VOLUME : 0)}
65
+ />
66
+ </Row>
67
+ </Column>
68
+ )
69
+ }