Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion packages/client/components/app/menus/UserContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import MdReport from "@material-design-icons/svg/outlined/report.svg?component-s
import MdChecked from "@material-symbols/svg-400/outlined/check_box.svg?component-solid";
import MdUnchecked from "@material-symbols/svg-400/outlined/check_box_outline_blank.svg?component-solid";

import { useVoice } from "@revolt/rtc";
import {
ContextMenu,
ContextMenuButton,
Expand All @@ -45,13 +46,15 @@ export function UserContextMenu(props: {
member?: ServerMember;
contextMessage?: Message;
inVoice?: boolean;
isScreenshare?: boolean;
}) {
// TODO: if we take serverId instead, we could dynamically fetch server member here
// same for the floating menu I guess?
const state = useState();
const client = useClient();
const navigate = useNavigate();
const { openModal } = useModals();
const voice = useVoice();

// server context
const params = useSmartParams();
Expand Down Expand Up @@ -191,7 +194,7 @@ export function UserContextMenu(props: {

return (
<ContextMenu class="UserContextMenu">
<Show when={props.inVoice && !props.user.self}>
<Show when={props.inVoice && !props.user.self && !props.isScreenshare}>
<ContextMenuButton
onMouseDown={(e) => e.stopImmediatePropagation()}
onClick={(e) => e.stopImmediatePropagation()}
Expand Down Expand Up @@ -230,6 +233,45 @@ export function UserContextMenu(props: {

<ContextMenuDivider />
</Show>
<Show when={props.isScreenshare && !props.user.self}>
<ContextMenuButton
onMouseDown={(e) => e.stopImmediatePropagation()}
onClick={(e) => e.stopImmediatePropagation()}
>
<Text class="label">
<Trans>Screen Share Volume</Trans>
</Text>
<Slider
min={0}
max={3}
step={0.1}
value={voice.getScreenshareVolume(props.user.id)}
onInput={(event) =>
voice.setScreenshareVolume(
props.user.id,
event.currentTarget.value,
)
}
labelFormatter={(label) => (label * 100).toFixed(0) + "%"}
/>
</ContextMenuButton>
<ContextMenuButton
icon={MdMicOff}
onClick={() =>
voice.setScreenshareMuted(
props.user.id,
!voice.getScreenshareMuted(props.user.id),
)
}
actionSymbol={
voice.getScreenshareMuted(props.user.id) ? MdChecked : MdUnchecked
}
>
<Trans>Mute Screen Share</Trans>
</ContextMenuButton>

<ContextMenuDivider />
</Show>

<Show when={props.channel?.type === "DirectMessage"}>
<ContextMenuButton icon={MdClose} onClick={closeDm}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,14 @@ export function RoomAudioManager() {
trackRef={track()}
volume={
state.voice.outputVolume *
state.voice.getUserVolume(track().participant.identity)
(track().source === Track.Source.ScreenShareAudio
? voice.getScreenshareVolume(track().participant.identity)
: state.voice.getUserVolume(track().participant.identity))
}
muted={
state.voice.getUserMuted(track().participant.identity) ||
(track().source === Track.Source.ScreenShareAudio
? voice.getScreenshareMuted(track().participant.identity)
: state.voice.getUserMuted(track().participant.identity)) ||
voice.deafen()
}
enableBoosting
Expand Down
39 changes: 37 additions & 2 deletions packages/client/components/rtc/state.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {
Accessor,
JSX,
Setter,
batch,
createContext,
createSignal,
JSX,
Setter,
useContext,
} from "solid-js";
import { RoomContext } from "solid-livekit-components";
Expand All @@ -18,6 +18,7 @@ import { Voice as VoiceSettings } from "@revolt/state/stores/Voice";
import { VoiceCallCardContext } from "@revolt/ui/components/features/voice/callCard/VoiceCallCard";

import { CONFIGURATION } from "@revolt/common";
import { createStore, SetStoreFunction } from "solid-js/store";
import { InRoom } from "./components/InRoom";
import { RoomAudioManager } from "./components/RoomAudioManager";

Expand All @@ -28,6 +29,11 @@ type State =
| "CONNECTED"
| "RECONNECTING";

type ScreenShareSettings = {
volumes: Record<string, number>;
mutes: Record<string, boolean>;
};

class Voice {
#settings: VoiceSettings;

Expand All @@ -52,6 +58,9 @@ class Voice {
screenshare: Accessor<boolean>;
#setScreenshare: Setter<boolean>;

screenshareSettingStore: ScreenShareSettings;
#setScreenshareSettingStore: SetStoreFunction<ScreenShareSettings>;

constructor(voiceSettings: VoiceSettings) {
this.#settings = voiceSettings;

Expand Down Expand Up @@ -82,6 +91,15 @@ class Voice {
const [screenshare, setScreenshare] = createSignal(false);
this.screenshare = screenshare;
this.#setScreenshare = setScreenshare;

const [screenshareSettingsStore, setScreenshareSettingsStore] = createStore(
{
mutes: {},
volumes: {},
} as ScreenShareSettings,
);
this.screenshareSettingStore = screenshareSettingsStore as never;
this.#setScreenshareSettingStore = setScreenshareSettingsStore;
}

async connect(channel: Channel, auth?: { url: string; token: string }) {
Expand Down Expand Up @@ -179,6 +197,7 @@ class Voice {
if (!room) throw "invalid state";
await room.localParticipant.setScreenShareEnabled(
!room.localParticipant.isScreenShareEnabled,
{ audio: true },
);

this.#setScreenshare(room.localParticipant.isScreenShareEnabled);
Expand All @@ -195,6 +214,22 @@ class Voice {
get speakingPermission() {
return !!this.channel()?.havePermission("Speak");
}

setScreenshareVolume(userId: string, volume: number) {
this.#setScreenshareSettingStore("volumes", userId, volume);
}

getScreenshareVolume(userId: string): number {
return this.screenshareSettingStore.volumes[userId] || 1.0;
}

setScreenshareMuted(userId: string, muted: boolean) {
this.#setScreenshareSettingStore("mutes", userId, muted);
}

getScreenshareMuted(userId: string): boolean {
return this.screenshareSettingStore.mutes[userId] || false;
}
}

const voiceContext = createContext<Voice>(null as unknown as Voice);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { styled } from "styled-system/jsx";

import { UserContextMenu } from "@revolt/app";
import { useUser } from "@revolt/markdown/users";
import { InRoom } from "@revolt/rtc";
import { InRoom, useVoice } from "@revolt/rtc";
import { Avatar } from "@revolt/ui/components/design";
import { OverflowingText } from "@revolt/ui/components/utils";
import { Symbol } from "@revolt/ui/components/utils/Symbol";
Expand Down Expand Up @@ -229,6 +229,7 @@ function ScreenshareTile() {
const participant = useEnsureParticipant();
const track = useMaybeTrackRefContext();
const user = useUser(participant.identity);
const voice = useVoice();

const isMuted = useIsMuted({
participant,
Expand All @@ -237,22 +238,34 @@ function ScreenshareTile() {

let videoRef: HTMLDivElement | undefined;

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

const isScreenshareMuted = () =>
voice.getScreenshareMuted(user().user!.id) ? "by-user" : isMuted() || false;

return (
<div
ref={videoRef}
class={tile() + " group"}
onClick={toggleFullscreen}
style={{ cursor: "pointer" }}
use:floating={{
contextMenu: () => (
<UserContextMenu
user={user().user!}
member={user().member}
isScreenshare
/>
),
}}
>
<VideoTrack
style={{
Expand All @@ -268,8 +281,17 @@ function ScreenshareTile() {
<Overlay showOnHover>
<OverlayInner>
<OverflowingText>{user().username}</OverflowingText>
<Show when={isMuted()}>
<Symbol size={18}>no_sound</Symbol>
<Show when={isScreenshareMuted()}>
<Symbol
size={18}
color={
isScreenshareMuted() === "by-user"
? "var(--md-sys-color-error)"
: undefined
}
>
no_sound
</Symbol>
</Show>
<Symbol size={18}>fullscreen</Symbol>
</OverlayInner>
Expand Down
Loading