Skip to content
Merged
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ ENV VITE_WS_URL=__VITE_WS_URL__
ENV VITE_MEDIA_URL=__VITE_MEDIA_URL__
ENV VITE_PROXY_URL=__VITE_PROXY_URL__
ENV VITE_HCAPTCHA_SITEKEY=__VITE_HCAPTCHA_SITEKEY__
ENV VITE_CFG_ENABLE_VIDEO=__VITE_CFG_ENABLE_VIDEO__
ENV BASE_PATH=/

RUN pnpm --filter client exec vite build
Expand All @@ -73,6 +74,7 @@ ENV VITE_WS_URL=""
ENV VITE_MEDIA_URL=""
ENV VITE_PROXY_URL=""
ENV VITE_HCAPTCHA_SITEKEY=""
ENV VITE_CFG_ENABLE_VIDEO=""
ENV REVOLT_PUBLIC_URL=""

CMD ["npm", "start"]
9 changes: 8 additions & 1 deletion docker/inject.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
const { cpSync, rmSync, readFileSync, writeFileSync, readdirSync } = require("node:fs");
const {
cpSync,
rmSync,
readFileSync,
writeFileSync,
readdirSync,
} = require("node:fs");
const { join } = require("node:path");

const BUILD_DIR = "dist";
Expand All @@ -13,6 +19,7 @@ const REPLACEMENTS = {
__VITE_MEDIA_URL__: process.env.VITE_MEDIA_URL || "",
__VITE_PROXY_URL__: process.env.VITE_PROXY_URL || "",
__VITE_HCAPTCHA_SITEKEY__: process.env.VITE_HCAPTCHA_SITEKEY || "",
__VITE_CFG_ENABLE_VIDEO__: process.env.VITE_CFG_ENABLE_VIDEO || "",
};

console.log("Preparing injected build...");
Expand Down
3 changes: 3 additions & 0 deletions packages/client/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ VITE_HCAPTCHA_SITEKEY=
# specify Sentry DSN
VITE_SENTRY_DSN=
VITE_SENTRY_TUNNEL=

# configs
VITE_CFG_ENABLE_VIDEO=
5 changes: 5 additions & 0 deletions packages/client/components/common/lib/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ export default {
*/
MAX_FILE_SIZE:
(import.meta.env.VITE_CFG_MAX_FILE_SIZE as number) ?? 20_000_000,
/**
* Enable video allows the web client to enable video and screensharing
*/
ENABLE_VIDEO:
((import.meta.env.VITE_CFG_ENABLE_VIDEO as string) ?? "").toLowerCase() == "true",
/**
* Session ID to set during development.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Show } from "solid-js";
import { useLingui } from "@lingui-solid/solid/macro";
import { styled } from "styled-system/jsx";

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

function isVideoEnabled() {
return CONFIGURATION.ENABLE_VIDEO;
}

return (
<Actions>
<Show when={props.size === "xs"}>
Expand Down Expand Up @@ -61,29 +66,48 @@ export function VoiceCallCardActions(props: { size: "xs" | "sm" }) {
</IconButton>
<IconButton
size={props.size}
variant={"tonal"}
variant={isVideoEnabled() && voice.video() ? "filled" : "tonal"}
onPress={() => {
if (isVideoEnabled()) voice.toggleCamera();
}}
use:floating={{
tooltip: {
placement: "top",
content: "Coming soon! 👀",
content: isVideoEnabled()
? voice.video()
? "Stop Camera"
: "Start Camera"
: "Coming soon! 👀",
},
}}
isDisabled
isDisabled={!isVideoEnabled()}
>
<Symbol>camera_video</Symbol>
</IconButton>
<IconButton
size={props.size}
variant={"tonal"}
variant={isVideoEnabled() && voice.screenshare() ? "filled" : "tonal"}
onPress={() => {
if (isVideoEnabled()) voice.toggleScreenshare();
}}
use:floating={{
tooltip: {
placement: "top",
content: "Coming soon! 👀",
content: isVideoEnabled()
? voice.screenshare()
? "Stop Sharing"
: "Share Screen"
: "Coming soon! 👀",
},
}}
isDisabled
isDisabled={!isVideoEnabled()}
>
<Symbol>screen_share</Symbol>
<Show
when={!isVideoEnabled() || voice.screenshare()}
fallback={<Symbol>stop_screen_share</Symbol>}
>
<Symbol>screen_share</Symbol>
</Show>
</IconButton>
<Button
size={props.size}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Match, Show, Switch } from "solid-js";
import { createEffect, Match, Show, Switch } from "solid-js";
import {
isTrackReference,
TrackLoop,
TrackReference,
VideoTrack,
isTrackReference,
useEnsureParticipant,
useIsMuted,
useIsSpeaking,
useMaybeTrackRefContext,
useTrackRefContext,
useTracks,
VideoTrack,
} from "solid-livekit-components";

import { Track } from "livekit-client";
Expand Down Expand Up @@ -128,15 +128,40 @@ function UserTile() {
source: Track.Source.Microphone,
});

const isVideoMuted = useIsMuted({
participant,
source: Track.Source.Camera,
});

const isSpeaking = useIsSpeaking(participant);

const user = useUser(participant.identity);

let videoRef: HTMLDivElement | undefined;

function toggleFullscreen() {
if (!videoRef || !isTrackReference(track) || isVideoMuted()) return;
if (!document.fullscreenElement) {
videoRef.requestFullscreen();
} else {
document.exitFullscreen();
}
}

createEffect(() => {
if (isVideoMuted() && document.fullscreenElement) {
document.exitFullscreen();
}
});

return (
<div
ref={videoRef}
class={tile({
speaking: isSpeaking(),
})}
onClick={toggleFullscreen}
style={{ cursor: "pointer" }}
use:floating={{
userCard: {
user: user().user!,
Expand All @@ -159,9 +184,14 @@ function UserTile() {
</AvatarOnly>
}
>
<Match when={isTrackReference(track)}>
<Match when={isTrackReference(track) && !isVideoMuted()}>
<VideoTrack
style={{ "grid-area": "1/1" }}
style={{
"grid-area": "1/1",
"object-fit": "contain",
width: "100%",
height: "100%",
}}
trackRef={track as TrackReference}
manageSubscription={true}
/>
Expand All @@ -175,6 +205,9 @@ function UserTile() {
userId={participant.identity}
muted={isMuted()}
/>
<Show when={isTrackReference(track) && !isVideoMuted()}>
<Symbol size={18}>fullscreen</Symbol>
</Show>
</OverlayInner>
</Overlay>
</div>
Expand Down Expand Up @@ -202,10 +235,32 @@ function ScreenshareTile() {
source: Track.Source.ScreenShareAudio,
});

let videoRef: HTMLDivElement | undefined;

const toggleFullscreen = () => {
if (!videoRef) return;
if (!isTrackReference(track)) return;
if (!document.fullscreenElement) {
videoRef.requestFullscreen();
} else {
document.exitFullscreen();
}
};

return (
<div class={tile() + " group"}>
<div
ref={videoRef}
class={tile() + " group"}
onClick={toggleFullscreen}
style={{ cursor: "pointer" }}
>
<VideoTrack
style={{ "grid-area": "1/1" }}
style={{
"grid-area": "1/1",
"object-fit": "contain",
width: "100%",
height: "100%",
}}
trackRef={track as TrackReference}
manageSubscription={true}
/>
Expand All @@ -216,6 +271,7 @@ function ScreenshareTile() {
<Show when={isMuted()}>
<Symbol size={18}>no_sound</Symbol>
</Show>
<Symbol size={18}>fullscreen</Symbol>
</OverlayInner>
</Overlay>
</div>
Expand Down
Loading