@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.
- package/editor.css +1 -0
- package/editor.js +33891 -0
- package/package.json +8 -4
- package/prefabs/admin-tools/composite.json +77 -0
- package/prefabs/admin-tools/data.json +28 -0
- package/prefabs/admin-tools/icons/admin-panel-control-button.png +0 -0
- package/prefabs/admin-tools/icons/admin-panel-moderation-control-button.png +0 -0
- package/prefabs/admin-tools/icons/admin-panel-rewards-control-button.png +0 -0
- package/prefabs/admin-tools/icons/admin-panel-smart-item-control-button.png +0 -0
- package/prefabs/admin-tools/icons/admin-panel-text-announcement-control-button.png +0 -0
- package/prefabs/admin-tools/icons/admin-panel-video-control-button.png +0 -0
- package/prefabs/admin-tools/icons/admin-tool-background.png +0 -0
- package/prefabs/admin-tools/icons/announcements/text-announcement-chat-message.png +0 -0
- package/prefabs/admin-tools/icons/announcements/text-announcement-check.png +0 -0
- package/prefabs/admin-tools/icons/announcements/text-announcement-close-button.png +0 -0
- package/prefabs/admin-tools/icons/moderation/admin-panel-verified-user.png +0 -0
- package/prefabs/admin-tools/icons/moderation/ban.png +0 -0
- package/prefabs/admin-tools/icons/moderation/chevron-back.png +0 -0
- package/prefabs/admin-tools/icons/moderation/chevron-forward.png +0 -0
- package/prefabs/admin-tools/icons/moderation/close.png +0 -0
- package/prefabs/admin-tools/icons/moderation/error.png +0 -0
- package/prefabs/admin-tools/icons/moderation/person-outline.png +0 -0
- package/prefabs/admin-tools/icons/moderation-control-icon.png +0 -0
- package/prefabs/admin-tools/icons/rewards/close.png +0 -0
- package/prefabs/admin-tools/icons/rewards/send.png +0 -0
- package/prefabs/admin-tools/icons/rewards-control.png +0 -0
- package/prefabs/admin-tools/icons/smart-item-control.png +0 -0
- package/prefabs/admin-tools/icons/text-announcement-control.png +0 -0
- package/prefabs/admin-tools/icons/video/chevron-down.png +0 -0
- package/prefabs/admin-tools/icons/video/chevron-up.png +0 -0
- package/prefabs/admin-tools/icons/video/copy-to-clipboard.png +0 -0
- package/prefabs/admin-tools/icons/video/error.png +0 -0
- package/prefabs/admin-tools/icons/video/eye-off.png +0 -0
- package/prefabs/admin-tools/icons/video/eye.png +0 -0
- package/prefabs/admin-tools/icons/video/help.png +0 -0
- package/prefabs/admin-tools/icons/video/info.png +0 -0
- package/prefabs/admin-tools/icons/video/person-outline.png +0 -0
- package/prefabs/admin-tools/icons/video/star.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-dcl-cast.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-live.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-loop.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-mute.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-pause-button.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-play-button.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-video-icon.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-volume-minus-button.png +0 -0
- package/prefabs/admin-tools/icons/video/video-control-volume-plus-button.png +0 -0
- package/prefabs/admin-tools/icons/video-control.png +0 -0
- package/prefabs/admin-tools/scripts/actions.ts +206 -0
- package/prefabs/admin-tools/scripts/admin.tsx +253 -0
- package/prefabs/admin-tools/scripts/api.ts +125 -0
- package/prefabs/admin-tools/scripts/components.ts +190 -0
- package/prefabs/admin-tools/scripts/icons.ts +53 -0
- package/prefabs/admin-tools/scripts/message-bus.ts +297 -0
- package/prefabs/admin-tools/scripts/state.ts +69 -0
- package/prefabs/admin-tools/scripts/tabs/Moderation.tsx +155 -0
- package/prefabs/admin-tools/scripts/tabs/Rewards.tsx +136 -0
- package/prefabs/admin-tools/scripts/tabs/SmartItemActions.tsx +167 -0
- package/prefabs/admin-tools/scripts/tabs/TextAnnouncements.tsx +195 -0
- package/prefabs/admin-tools/scripts/tabs/VideoControl.tsx +156 -0
- package/prefabs/admin-tools/scripts/tabs/announcements/Overlay.tsx +94 -0
- package/prefabs/admin-tools/scripts/tabs/announcements/icons.ts +15 -0
- package/prefabs/admin-tools/scripts/tabs/announcements/state.ts +71 -0
- package/prefabs/admin-tools/scripts/tabs/announcements/ui.tsx +37 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/AddUserInput.tsx +184 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/RemoveAdminConfirmation.tsx +93 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/UsersList.tsx +228 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/api.ts +35 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/icons.ts +23 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/kick.ts +95 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/state.ts +69 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/ui.tsx +172 -0
- package/prefabs/admin-tools/scripts/tabs/moderation/utils.ts +88 -0
- package/prefabs/admin-tools/scripts/tabs/rewards/Button.tsx +52 -0
- package/prefabs/admin-tools/scripts/tabs/rewards/Captcha.tsx +88 -0
- package/prefabs/admin-tools/scripts/tabs/rewards/api.ts +138 -0
- package/prefabs/admin-tools/scripts/tabs/rewards/claim.ts +99 -0
- package/prefabs/admin-tools/scripts/tabs/rewards/controller.ts +103 -0
- package/prefabs/admin-tools/scripts/tabs/rewards/icons.ts +13 -0
- package/prefabs/admin-tools/scripts/tabs/rewards/state.ts +32 -0
- package/prefabs/admin-tools/scripts/tabs/smart-items/selection.ts +90 -0
- package/prefabs/admin-tools/scripts/tabs/video/DclCast.tsx +226 -0
- package/prefabs/admin-tools/scripts/tabs/video/LiveStream.tsx +284 -0
- package/prefabs/admin-tools/scripts/tabs/video/SpeakerShowcase.tsx +112 -0
- package/prefabs/admin-tools/scripts/tabs/video/VideoUrl.tsx +107 -0
- package/prefabs/admin-tools/scripts/tabs/video/Volume.tsx +69 -0
- package/prefabs/admin-tools/scripts/tabs/video/api.ts +172 -0
- package/prefabs/admin-tools/scripts/tabs/video/controls.ts +108 -0
- package/prefabs/admin-tools/scripts/tabs/video/icons.ts +59 -0
- package/prefabs/admin-tools/scripts/tabs/video/speakers.ts +43 -0
- package/prefabs/admin-tools/scripts/tabs/video/state.ts +82 -0
- package/prefabs/admin-tools/scripts/tabs/video/ui.tsx +243 -0
- package/prefabs/admin-tools/scripts/types.ts +46 -0
- package/prefabs/admin-tools/scripts/ui.tsx +122 -0
- package/prefabs/admin-tools/thumbnail.png +0 -0
- package/prefabs/announcer/ai.md +76 -0
- package/prefabs/announcer/composite.json +31 -0
- package/prefabs/announcer/data.json +35 -0
- package/prefabs/announcer/scripts/announcer.tsx +79 -0
- package/prefabs/announcer/scripts/pure/toast.ts +30 -0
- package/prefabs/announcer/thumbnail.png +0 -0
- package/prefabs/black-chair/HighBackChair.glb +0 -0
- package/prefabs/black-chair/composite.json +74 -0
- package/prefabs/black-chair/data.json +30 -0
- package/prefabs/black-chair/scripts/seat.ts +191 -0
- package/prefabs/black-chair/thumbnail.png +0 -0
- package/prefabs/catalog.json +1 -0
- package/prefabs/classic-bench/Bench_01.glb +0 -0
- package/prefabs/classic-bench/composite.json +100 -0
- package/prefabs/classic-bench/data.json +34 -0
- package/prefabs/classic-bench/file1.png +0 -0
- package/prefabs/classic-bench/scripts/seat.ts +191 -0
- package/prefabs/classic-bench/thumbnail.png +0 -0
- package/prefabs/classic-bench-armrests/Bench 1.glb +0 -0
- package/prefabs/classic-bench-armrests/composite.json +100 -0
- package/prefabs/classic-bench-armrests/data.json +30 -0
- package/prefabs/classic-bench-armrests/scripts/seat.ts +191 -0
- package/prefabs/classic-bench-armrests/thumbnail.png +0 -0
- package/prefabs/classroom-chair/Chair_02.glb +0 -0
- package/prefabs/classroom-chair/composite.json +74 -0
- package/prefabs/classroom-chair/data.json +34 -0
- package/prefabs/classroom-chair/file1.png +0 -0
- package/prefabs/classroom-chair/scripts/seat.ts +191 -0
- package/prefabs/classroom-chair/thumbnail.png +0 -0
- package/prefabs/court-chair/Chair_Wood_01.glb +0 -0
- package/prefabs/court-chair/FanstasyPack_TX.png +0 -0
- package/prefabs/court-chair/composite.json +74 -0
- package/prefabs/court-chair/data.json +34 -0
- package/prefabs/court-chair/scripts/seat.ts +191 -0
- package/prefabs/court-chair/thumbnail.png +0 -0
- package/prefabs/curved-couch/CurvedCouch.glb +0 -0
- package/prefabs/curved-couch/composite.json +100 -0
- package/prefabs/curved-couch/data.json +30 -0
- package/prefabs/curved-couch/scripts/seat.ts +191 -0
- package/prefabs/curved-couch/thumbnail.png +0 -0
- package/prefabs/game-flow/ai.md +94 -0
- package/prefabs/game-flow/composite.json +45 -0
- package/prefabs/game-flow/data.json +27 -0
- package/prefabs/game-flow/scripts/game-flow.ts +168 -0
- package/prefabs/game-flow/scripts/pure/flow.ts +185 -0
- package/prefabs/game-flow/thumbnail.png +0 -0
- package/prefabs/health-respawn/ai.md +76 -0
- package/prefabs/health-respawn/composite.json +31 -0
- package/prefabs/health-respawn/data.json +38 -0
- package/prefabs/health-respawn/scripts/health-respawn.ts +98 -0
- package/prefabs/health-respawn/scripts/health.ts +22 -0
- package/prefabs/health-respawn/scripts/pure/health.ts +60 -0
- package/prefabs/health-respawn/thumbnail.png +0 -0
- package/prefabs/high-stool/HighStool.glb +0 -0
- package/prefabs/high-stool/composite.json +74 -0
- package/prefabs/high-stool/data.json +30 -0
- package/prefabs/high-stool/scripts/seat.ts +191 -0
- package/prefabs/high-stool/thumbnail.png +0 -0
- package/prefabs/large-couch/ThreeSeater_Couch.glb +0 -0
- package/prefabs/large-couch/composite.json +100 -0
- package/prefabs/large-couch/data.json +30 -0
- package/prefabs/large-couch/scripts/seat.ts +191 -0
- package/prefabs/large-couch/thumbnail.png +0 -0
- package/prefabs/leaderboard/ai.md +75 -0
- package/prefabs/leaderboard/board-panel.glb +0 -0
- package/prefabs/leaderboard/composite.json +82 -0
- package/prefabs/leaderboard/data.json +35 -0
- package/prefabs/leaderboard/scripts/leaderboard.ts +66 -0
- package/prefabs/leaderboard/scripts/pure/board.ts +71 -0
- package/prefabs/leaderboard/thumbnail.png +0 -0
- package/prefabs/loveseat/Couch_01.glb +0 -0
- package/prefabs/loveseat/composite.json +100 -0
- package/prefabs/loveseat/data.json +34 -0
- package/prefabs/loveseat/file1.png +0 -0
- package/prefabs/loveseat/scripts/seat.ts +191 -0
- package/prefabs/loveseat/thumbnail.png +0 -0
- package/prefabs/modern-sofa/ModernSofa.glb +0 -0
- package/prefabs/modern-sofa/composite.json +100 -0
- package/prefabs/modern-sofa/data.json +30 -0
- package/prefabs/modern-sofa/scripts/seat.ts +191 -0
- package/prefabs/modern-sofa/thumbnail.png +0 -0
- package/prefabs/outdoor-chair/Chair_07.glb +0 -0
- package/prefabs/outdoor-chair/composite.json +74 -0
- package/prefabs/outdoor-chair/data.json +34 -0
- package/prefabs/outdoor-chair/file1.png +0 -0
- package/prefabs/outdoor-chair/scripts/seat.ts +191 -0
- package/prefabs/outdoor-chair/thumbnail.png +0 -0
- package/prefabs/rustic-bench/Bench_01.glb +0 -0
- package/prefabs/rustic-bench/PiratesPack_TX.png.png +0 -0
- package/prefabs/rustic-bench/composite.json +100 -0
- package/prefabs/rustic-bench/data.json +34 -0
- package/prefabs/rustic-bench/scripts/seat.ts +191 -0
- package/prefabs/rustic-bench/thumbnail.png +0 -0
- package/prefabs/rustic-round-stool/Chairwood_01.glb +0 -0
- package/prefabs/rustic-round-stool/PiratesPack_TX.png.png +0 -0
- package/prefabs/rustic-round-stool/composite.json +74 -0
- package/prefabs/rustic-round-stool/data.json +34 -0
- package/prefabs/rustic-round-stool/scripts/seat.ts +191 -0
- package/prefabs/rustic-round-stool/thumbnail.png +0 -0
- package/prefabs/rustic-square-stool/Chairwood_02.glb +0 -0
- package/prefabs/rustic-square-stool/PiratesPack_TX.png.png +0 -0
- package/prefabs/rustic-square-stool/composite.json +74 -0
- package/prefabs/rustic-square-stool/data.json +34 -0
- package/prefabs/rustic-square-stool/scripts/seat.ts +191 -0
- package/prefabs/rustic-square-stool/thumbnail.png +0 -0
- package/prefabs/server-clock/ai.md +67 -0
- package/prefabs/server-clock/composite.json +45 -0
- package/prefabs/server-clock/data.json +24 -0
- package/prefabs/server-clock/scripts/clock-hud.tsx +55 -0
- package/prefabs/server-clock/scripts/server-clock.ts +71 -0
- package/prefabs/server-clock/thumbnail.png +0 -0
- package/prefabs/simple-chair/Chair_03.glb +0 -0
- package/prefabs/simple-chair/composite.json +74 -0
- package/prefabs/simple-chair/data.json +34 -0
- package/prefabs/simple-chair/file1.png +0 -0
- package/prefabs/simple-chair/scripts/seat.ts +191 -0
- package/prefabs/simple-chair/thumbnail.png +0 -0
- package/prefabs/sit-spot/composite.json +45 -0
- package/prefabs/sit-spot/data.json +30 -0
- package/prefabs/sit-spot/scripts/seat.ts +191 -0
- package/prefabs/sit-spot/thumbnail.png +0 -0
- package/prefabs/sit-spot-edge/composite.json +42 -0
- package/prefabs/sit-spot-edge/data.json +32 -0
- package/prefabs/sit-spot-edge/emotes/Sit_Edge1_emote.glb +0 -0
- package/prefabs/sit-spot-edge/emotes/Sit_Edge2_emote.glb +0 -0
- package/prefabs/sit-spot-edge/scripts/seat.ts +191 -0
- package/prefabs/sit-spot-edge/thumbnail.png +0 -0
- package/prefabs/small-couch/TwoSeater_Couch.glb +0 -0
- package/prefabs/small-couch/composite.json +100 -0
- package/prefabs/small-couch/data.json +30 -0
- package/prefabs/small-couch/scripts/seat.ts +191 -0
- package/prefabs/small-couch/thumbnail.png +0 -0
- package/prefabs/spawner/ai.md +109 -0
- package/prefabs/spawner/composite.json +73 -0
- package/prefabs/spawner/data.json +22 -0
- package/prefabs/spawner/scripts/spawner.ts +380 -0
- package/prefabs/spawner/thumbnail.png +0 -0
- package/prefabs/steampunk-bench/Steampunk Bench.glb +0 -0
- package/prefabs/steampunk-bench/composite.json +100 -0
- package/prefabs/steampunk-bench/data.json +30 -0
- package/prefabs/steampunk-bench/scripts/seat.ts +191 -0
- package/prefabs/steampunk-bench/thumbnail.png +0 -0
- package/prefabs/steampunk-chair/Steampunk Chair.glb +0 -0
- package/prefabs/steampunk-chair/composite.json +74 -0
- package/prefabs/steampunk-chair/data.json +30 -0
- package/prefabs/steampunk-chair/scripts/seat.ts +191 -0
- package/prefabs/steampunk-chair/thumbnail.png +0 -0
- package/prefabs/tall-stool/Stool_D.glb +0 -0
- package/prefabs/tall-stool/composite.json +74 -0
- package/prefabs/tall-stool/data.json +30 -0
- package/prefabs/tall-stool/scripts/seat.ts +191 -0
- package/prefabs/tall-stool/thumbnail.png +0 -0
- package/prefabs/trigger-zone/ai.md +115 -0
- package/prefabs/trigger-zone/composite.json +52 -0
- package/prefabs/trigger-zone/data.json +31 -0
- package/prefabs/trigger-zone/scripts/trigger-zone.ts +89 -0
- package/prefabs/trigger-zone/thumbnail.png +0 -0
- package/prefabs/video-screen/composite.json +98 -0
- package/prefabs/video-screen/data.json +21 -0
- package/prefabs/video-screen/thumbnail.png +0 -0
- package/prefabs/video-screen/video_player.glb +0 -0
- package/prefabs/wooden-chair/Chair 1.glb +0 -0
- package/prefabs/wooden-chair/composite.json +74 -0
- package/prefabs/wooden-chair/data.json +30 -0
- package/prefabs/wooden-chair/scripts/seat.ts +191 -0
- package/prefabs/wooden-chair/thumbnail.png +0 -0
- package/prefabs/wooden-chair-armrests/Chair 3.glb +0 -0
- package/prefabs/wooden-chair-armrests/composite.json +74 -0
- package/prefabs/wooden-chair-armrests/data.json +30 -0
- package/prefabs/wooden-chair-armrests/scripts/seat.ts +191 -0
- package/prefabs/wooden-chair-armrests/thumbnail.png +0 -0
- package/scene/about +17 -0
- package/scene/bafkreias2w4xuqaic6sdcvblrisayi6xg7drfowzpt5ubjkmrk3mx6czc4 +1 -0
- package/scene/bafkreiby2gmthtauf7cckttphmhpecsueyypi2hzlke4lcodg7fm4cm2sq +23 -0
- package/scene/bafkreidhwqwajqk4nsuffef5fkdrgmp5ql6vnn2knpqsuqalyosv5o6lk4 +1 -0
- package/scene/bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku +0 -0
- package/scene/bafybeihw3xlbff4sf5sc3kvmlmlwf2visnty5lfymawlunzt5vrkp5fd7u +90 -0
- package/web-build/esbuild.wasm +0 -0
- package/web-build/index.js +3 -0
- package/web-build/sdk-snapshot.json +1 -0
- package/web-build/worker.js +701 -0
- 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
|
+
}
|