@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,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
|
+
}
|