Skip to content

Commit 1f95fb6

Browse files
Dadadahchrishultin
andauthored
feat: Enable video with an env var (stoatchat#847)
* feat: Enable video and screenshare with an env variable for self host This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht <dadadah@echoha.us> * feat: Enable fullscreening video and screen share streams This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht <dadadah@echoha.us> * fix: Env var works now, camera view is disabled when camera turned off This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht <dadadah@echoha.us> * fix: Leave fullscreen when a user stops their camera stream This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht <dadadah@echoha.us> * fix: run linter This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht <dadadah@echoha.us> * fix: Access this env var safely as it may not exist This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht <dadadah@echoha.us> * fix: Only attempt to exit fullscreen if in fullscreen This commit was made without the use of generative AI. Signed-off-by: Jacob Schlecht <dadadah@echoha.us> --------- Signed-off-by: Jacob Schlecht <dadadah@echoha.us> Co-authored-by: Christopher Hultin <chrishultin@google.com>
1 parent b673c70 commit 1f95fb6

6 files changed

Lines changed: 113 additions & 15 deletions

File tree

‎Dockerfile‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ ENV VITE_WS_URL=__VITE_WS_URL__
4747
ENV VITE_MEDIA_URL=__VITE_MEDIA_URL__
4848
ENV VITE_PROXY_URL=__VITE_PROXY_URL__
4949
ENV VITE_HCAPTCHA_SITEKEY=__VITE_HCAPTCHA_SITEKEY__
50+
ENV VITE_CFG_ENABLE_VIDEO=__VITE_CFG_ENABLE_VIDEO__
5051
ENV BASE_PATH=/
5152

5253
RUN pnpm --filter client exec vite build
@@ -73,6 +74,7 @@ ENV VITE_WS_URL=""
7374
ENV VITE_MEDIA_URL=""
7475
ENV VITE_PROXY_URL=""
7576
ENV VITE_HCAPTCHA_SITEKEY=""
77+
ENV VITE_CFG_ENABLE_VIDEO=""
7678
ENV REVOLT_PUBLIC_URL=""
7779

7880
CMD ["npm", "start"]

‎docker/inject.js‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
const { cpSync, rmSync, readFileSync, writeFileSync, readdirSync } = require("node:fs");
1+
const {
2+
cpSync,
3+
rmSync,
4+
readFileSync,
5+
writeFileSync,
6+
readdirSync,
7+
} = require("node:fs");
28
const { join } = require("node:path");
39

410
const BUILD_DIR = "dist";
@@ -13,6 +19,7 @@ const REPLACEMENTS = {
1319
__VITE_MEDIA_URL__: process.env.VITE_MEDIA_URL || "",
1420
__VITE_PROXY_URL__: process.env.VITE_PROXY_URL || "",
1521
__VITE_HCAPTCHA_SITEKEY__: process.env.VITE_HCAPTCHA_SITEKEY || "",
22+
__VITE_CFG_ENABLE_VIDEO__: process.env.VITE_CFG_ENABLE_VIDEO || "",
1623
};
1724

1825
console.log("Preparing injected build...");

‎packages/client/.env.example‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ VITE_HCAPTCHA_SITEKEY=
1111
# specify Sentry DSN
1212
VITE_SENTRY_DSN=
1313
VITE_SENTRY_TUNNEL=
14+
15+
# configs
16+
VITE_CFG_ENABLE_VIDEO=

‎packages/client/components/common/lib/env.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ export default {
7474
*/
7575
MAX_FILE_SIZE:
7676
(import.meta.env.VITE_CFG_MAX_FILE_SIZE as number) ?? 20_000_000,
77+
/**
78+
* Enable video allows the web client to enable video and screensharing
79+
*/
80+
ENABLE_VIDEO:
81+
((import.meta.env.VITE_CFG_ENABLE_VIDEO as string) ?? "").toLowerCase() ==
82+
"true",
7783
/**
7884
* Session ID to set during development.
7985
*/

‎packages/client/components/ui/components/features/voice/callCard/VoiceCallCardActions.tsx‎

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Show } from "solid-js";
33
import { useLingui } from "@lingui-solid/solid/macro";
44
import { styled } from "styled-system/jsx";
55

6+
import { CONFIGURATION } from "@revolt/common";
67
import { useVoice } from "@revolt/rtc";
78
import { Button, IconButton } from "@revolt/ui/components/design";
89
import { Symbol } from "@revolt/ui/components/utils/Symbol";
@@ -11,6 +12,10 @@ export function VoiceCallCardActions(props: { size: "xs" | "sm" }) {
1112
const voice = useVoice();
1213
const { t } = useLingui();
1314

15+
function isVideoEnabled() {
16+
return CONFIGURATION.ENABLE_VIDEO;
17+
}
18+
1419
return (
1520
<Actions>
1621
<Show when={props.size === "xs"}>
@@ -61,29 +66,48 @@ export function VoiceCallCardActions(props: { size: "xs" | "sm" }) {
6166
</IconButton>
6267
<IconButton
6368
size={props.size}
64-
variant={"tonal"}
69+
variant={isVideoEnabled() && voice.video() ? "filled" : "tonal"}
70+
onPress={() => {
71+
if (isVideoEnabled()) voice.toggleCamera();
72+
}}
6573
use:floating={{
6674
tooltip: {
6775
placement: "top",
68-
content: "Coming soon! 👀",
76+
content: isVideoEnabled()
77+
? voice.video()
78+
? "Stop Camera"
79+
: "Start Camera"
80+
: "Coming soon! 👀",
6981
},
7082
}}
71-
isDisabled
83+
isDisabled={!isVideoEnabled()}
7284
>
7385
<Symbol>camera_video</Symbol>
7486
</IconButton>
7587
<IconButton
7688
size={props.size}
77-
variant={"tonal"}
89+
variant={isVideoEnabled() && voice.screenshare() ? "filled" : "tonal"}
90+
onPress={() => {
91+
if (isVideoEnabled()) voice.toggleScreenshare();
92+
}}
7893
use:floating={{
7994
tooltip: {
8095
placement: "top",
81-
content: "Coming soon! 👀",
96+
content: isVideoEnabled()
97+
? voice.screenshare()
98+
? "Stop Sharing"
99+
: "Share Screen"
100+
: "Coming soon! 👀",
82101
},
83102
}}
84-
isDisabled
103+
isDisabled={!isVideoEnabled()}
85104
>
86-
<Symbol>screen_share</Symbol>
105+
<Show
106+
when={!isVideoEnabled() || voice.screenshare()}
107+
fallback={<Symbol>stop_screen_share</Symbol>}
108+
>
109+
<Symbol>screen_share</Symbol>
110+
</Show>
87111
</IconButton>
88112
<Button
89113
size={props.size}

‎packages/client/components/ui/components/features/voice/callCard/VoiceCallCardActiveRoom.tsx‎

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { Match, Show, Switch } from "solid-js";
1+
import { createEffect, Match, Show, Switch } from "solid-js";
22
import {
3+
isTrackReference,
34
TrackLoop,
45
TrackReference,
5-
VideoTrack,
6-
isTrackReference,
76
useEnsureParticipant,
87
useIsMuted,
98
useIsSpeaking,
109
useMaybeTrackRefContext,
1110
useTrackRefContext,
1211
useTracks,
12+
VideoTrack,
1313
} from "solid-livekit-components";
1414

1515
import { Track } from "livekit-client";
@@ -128,15 +128,40 @@ function UserTile() {
128128
source: Track.Source.Microphone,
129129
});
130130

131+
const isVideoMuted = useIsMuted({
132+
participant,
133+
source: Track.Source.Camera,
134+
});
135+
131136
const isSpeaking = useIsSpeaking(participant);
132137

133138
const user = useUser(participant.identity);
134139

140+
let videoRef: HTMLDivElement | undefined;
141+
142+
function toggleFullscreen() {
143+
if (!videoRef || !isTrackReference(track) || isVideoMuted()) return;
144+
if (!document.fullscreenElement) {
145+
videoRef.requestFullscreen();
146+
} else {
147+
document.exitFullscreen();
148+
}
149+
}
150+
151+
createEffect(() => {
152+
if (isVideoMuted() && document.fullscreenElement) {
153+
document.exitFullscreen();
154+
}
155+
});
156+
135157
return (
136158
<div
159+
ref={videoRef}
137160
class={tile({
138161
speaking: isSpeaking(),
139162
})}
163+
onClick={toggleFullscreen}
164+
style={{ cursor: "pointer" }}
140165
use:floating={{
141166
userCard: {
142167
user: user().user!,
@@ -159,9 +184,14 @@ function UserTile() {
159184
</AvatarOnly>
160185
}
161186
>
162-
<Match when={isTrackReference(track)}>
187+
<Match when={isTrackReference(track) && !isVideoMuted()}>
163188
<VideoTrack
164-
style={{ "grid-area": "1/1" }}
189+
style={{
190+
"grid-area": "1/1",
191+
"object-fit": "contain",
192+
width: "100%",
193+
height: "100%",
194+
}}
165195
trackRef={track as TrackReference}
166196
manageSubscription={true}
167197
/>
@@ -175,6 +205,9 @@ function UserTile() {
175205
userId={participant.identity}
176206
muted={isMuted()}
177207
/>
208+
<Show when={isTrackReference(track) && !isVideoMuted()}>
209+
<Symbol size={18}>fullscreen</Symbol>
210+
</Show>
178211
</OverlayInner>
179212
</Overlay>
180213
</div>
@@ -202,10 +235,32 @@ function ScreenshareTile() {
202235
source: Track.Source.ScreenShareAudio,
203236
});
204237

238+
let videoRef: HTMLDivElement | undefined;
239+
240+
const toggleFullscreen = () => {
241+
if (!videoRef) return;
242+
if (!isTrackReference(track)) return;
243+
if (!document.fullscreenElement) {
244+
videoRef.requestFullscreen();
245+
} else {
246+
document.exitFullscreen();
247+
}
248+
};
249+
205250
return (
206-
<div class={tile() + " group"}>
251+
<div
252+
ref={videoRef}
253+
class={tile() + " group"}
254+
onClick={toggleFullscreen}
255+
style={{ cursor: "pointer" }}
256+
>
207257
<VideoTrack
208-
style={{ "grid-area": "1/1" }}
258+
style={{
259+
"grid-area": "1/1",
260+
"object-fit": "contain",
261+
width: "100%",
262+
height: "100%",
263+
}}
209264
trackRef={track as TrackReference}
210265
manageSubscription={true}
211266
/>
@@ -216,6 +271,7 @@ function ScreenshareTile() {
216271
<Show when={isMuted()}>
217272
<Symbol size={18}>no_sound</Symbol>
218273
</Show>
274+
<Symbol size={18}>fullscreen</Symbol>
219275
</OverlayInner>
220276
</Overlay>
221277
</div>

0 commit comments

Comments
 (0)