diff --git a/fluxer_app/src/features/voice/components/CallVolumeControl.tsx b/fluxer_app/src/features/voice/components/CallVolumeControl.tsx deleted file mode 100644 index 21c9ce0ee..000000000 --- a/fluxer_app/src/features/voice/components/CallVolumeControl.tsx +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -import * as VoiceSettingsCommands from '@app/features/voice/commands/VoiceSettingsCommands'; -import { - callVolumePercentToSliderVolume, - resolveCallVolumeMuteToggle, - resolveLastNonZeroCallVolume, - sliderVolumeToCallVolumePercent, -} from '@app/features/voice/components/CallVolumeState'; -import {MediaVerticalVolumeControl} from '@app/features/voice/components/media_player/components/MediaVerticalVolumeControl'; -import VoiceSettings from '@app/features/voice/state/VoiceSettings'; -import {VOICE_OUTPUT_VOLUME_DESCRIPTOR} from '@app/features/voice/utils/VoiceMessageDescriptors'; -import {VOICE_VOLUME_MAX_SLIDER_VOLUME} from '@app/features/voice/utils/VoiceVolumeUtils'; -import {useLingui} from '@lingui/react/macro'; -import {observer} from 'mobx-react-lite'; -import type React from 'react'; -import {useCallback, useEffect, useRef} from 'react'; - -interface CallVolumeControlProps { - className?: string; - position?: 'above' | 'below'; - iconSize?: number; -} - -export const CallVolumeControl: React.FC = observer(function CallVolumeControl({ - className, - position = 'above', - iconSize = 18, -}) { - const {i18n} = useLingui(); - const outputVolume = VoiceSettings.outputVolume; - const lastNonZeroVolumeRef = useRef(resolveLastNonZeroCallVolume(outputVolume, 0)); - useEffect(() => { - lastNonZeroVolumeRef.current = resolveLastNonZeroCallVolume(outputVolume, lastNonZeroVolumeRef.current); - }, [outputVolume]); - const handleVolumeChange = useCallback((volume: number) => { - const nextPercent = sliderVolumeToCallVolumePercent(volume); - if (nextPercent === VoiceSettings.outputVolume) return; - VoiceSettingsCommands.update({outputVolume: nextPercent}); - }, []); - const handleToggleMute = useCallback(() => { - VoiceSettingsCommands.update({ - outputVolume: resolveCallVolumeMuteToggle(VoiceSettings.outputVolume, lastNonZeroVolumeRef.current), - }); - }, []); - return ( - - ); -}); diff --git a/fluxer_app/src/features/voice/components/CallVolumeState.ts b/fluxer_app/src/features/voice/components/CallVolumeState.ts deleted file mode 100644 index ec13ba285..000000000 --- a/fluxer_app/src/features/voice/components/CallVolumeState.ts +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -import {VOICE_VOLUME_MAX_PERCENT} from '@app/features/voice/utils/VoiceVolumeUtils'; - -export const CALL_VOLUME_DEFAULT_PERCENT = 100; -export const CALL_VOLUME_UNITY_PERCENT = 100; -export const CALL_VOLUME_SLIDER_MAX_PERCENT = VOICE_VOLUME_MAX_PERCENT; - -export function callVolumePercentToSliderVolume(percent: number): number { - if (!Number.isFinite(percent)) { - return CALL_VOLUME_DEFAULT_PERCENT / CALL_VOLUME_UNITY_PERCENT; - } - const clamped = Math.max(0, Math.min(CALL_VOLUME_SLIDER_MAX_PERCENT, percent)); - return clamped / CALL_VOLUME_UNITY_PERCENT; -} - -export function sliderVolumeToCallVolumePercent(volume: number): number { - if (!Number.isFinite(volume)) { - return CALL_VOLUME_DEFAULT_PERCENT; - } - const percent = Math.round(volume * CALL_VOLUME_UNITY_PERCENT); - return Math.max(0, Math.min(CALL_VOLUME_SLIDER_MAX_PERCENT, percent)); -} - -export function resolveLastNonZeroCallVolume(currentPercent: number, previousLastNonZero: number): number { - if (currentPercent > 0) { - return currentPercent; - } - if (previousLastNonZero > 0) { - return previousLastNonZero; - } - return CALL_VOLUME_DEFAULT_PERCENT; -} - -export function resolveCallVolumeMuteToggle(currentPercent: number, lastNonZeroPercent: number): number { - if (currentPercent > 0) { - return 0; - } - if (lastNonZeroPercent > 0) { - return lastNonZeroPercent; - } - return CALL_VOLUME_DEFAULT_PERCENT; -} diff --git a/fluxer_app/src/features/voice/components/FocusedStreamVolumeControl.tsx b/fluxer_app/src/features/voice/components/FocusedStreamVolumeControl.tsx new file mode 100644 index 000000000..c809458a4 --- /dev/null +++ b/fluxer_app/src/features/voice/components/FocusedStreamVolumeControl.tsx @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {STREAM_VOLUME_DESCRIPTOR} from '@app/features/ui/action_menu/items/voice_participant_menu_data/shared'; +import {MediaVerticalVolumeControl} from '@app/features/voice/components/media_player/components/MediaVerticalVolumeControl'; +import {getStreamKey} from '@app/features/voice/components/StreamKeys'; +import MediaEngine, {useMediaEngineVersion} from '@app/features/voice/engine/MediaEngineFacade'; +import { + asVoiceTrackSource, + isScreenShareAudioPublicationLike, + VoiceTrackSource, +} from '@app/features/voice/engine/VoiceTrackSource'; +import StreamAudioPrefs from '@app/features/voice/state/StreamAudioPrefs'; +import {parseVoiceParticipantIdentity} from '@app/features/voice/utils/VoiceParticipantIdentity'; +import {VOICE_VOLUME_MAX_SLIDER_VOLUME} from '@app/features/voice/utils/VoiceVolumeUtils'; +import {useLingui} from '@lingui/react/macro'; +import type {TrackReferenceOrPlaceholder} from '@livekit/components-react'; +import {observer} from 'mobx-react-lite'; +import type React from 'react'; +import {useCallback} from 'react'; + +interface FocusedStreamVolumeControlProps { + track: TrackReferenceOrPlaceholder | null; + guildId: string | null | undefined; + channelId: string; + className?: string; +} + +export const FocusedStreamVolumeControl: React.FC = observer( + function FocusedStreamVolumeControl({track, guildId, channelId, className}) { + const {i18n} = useLingui(); + useMediaEngineVersion(); + const isRemoteScreenShare = + track != null && asVoiceTrackSource(track.source) === VoiceTrackSource.ScreenShare && !track.participant.isLocal; + const {userId, connectionId} = isRemoteScreenShare + ? parseVoiceParticipantIdentity(track.participant.identity) + : {userId: null, connectionId: null}; + const streamKey = connectionId ? getStreamKey(guildId, channelId, connectionId) : ''; + const hasStreamAudio = + isRemoteScreenShare && + [...track.participant.audioTrackPublications.values()].some((publication) => + isScreenShareAudioPublicationLike(publication), + ); + const isMuted = streamKey ? StreamAudioPrefs.isMuted(streamKey) : false; + const handleToggleMute = useCallback(() => { + if (!streamKey || !userId) return; + StreamAudioPrefs.setMuted(streamKey, !StreamAudioPrefs.isMuted(streamKey)); + MediaEngine.applyLocalAudioPreferencesForUser(userId); + }, [streamKey, userId]); + const handleVolumeChange = useCallback( + (volume: number) => { + if (!streamKey || !userId) return; + StreamAudioPrefs.setVolume(streamKey, Math.round(volume * 100)); + MediaEngine.applyLocalAudioPreferencesForUser(userId); + }, + [streamKey, userId], + ); + if (!streamKey || !userId || !hasStreamAudio) return null; + return ( + + ); + }, +); diff --git a/fluxer_app/src/features/voice/components/VoiceCallCornerControls.tsx b/fluxer_app/src/features/voice/components/VoiceCallCornerControls.tsx index 87304bf26..ee34c5919 100644 --- a/fluxer_app/src/features/voice/components/VoiceCallCornerControls.tsx +++ b/fluxer_app/src/features/voice/components/VoiceCallCornerControls.tsx @@ -1,7 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import {ChannelHeaderIcon} from '@app/features/channel/components/channel_header_components/ChannelHeaderIcon'; -import {CallVolumeControl} from '@app/features/voice/components/CallVolumeControl'; import {msg} from '@lingui/core/macro'; import {useLingui} from '@lingui/react/macro'; import type {Icon} from '@phosphor-icons/react'; @@ -54,12 +53,7 @@ export const VoiceCallCornerControls: React.FC = ( }, []); return (
- {volumeControl ?? ( - - )} + {volumeControl} {showPopout && onPopOut && ( - isScreenShareAudioPublicationLike(publication), - ), - ); - const canControlFocusedStreamVolume = focusedStreamInfo !== null && !effectiveFocusMainTrack?.participant.isLocal; const focusedStreamerDisplayName = useMemo(() => { if (!focusedStreamerUser) return ''; return NicknameUtils.getNickname(focusedStreamerUser, channel.guildId, channel.id); @@ -297,21 +283,6 @@ const VoiceCallViewInner = observer( focusedStreamKey, focusedStreamInfo?.userId, ); - const focusedStreamVolume = StreamAudioPrefs.getVolume(focusedStreamKey); - const isFocusedStreamMuted = StreamAudioPrefs.isMuted(focusedStreamKey); - const handleFocusedStreamToggleMute = useCallback(() => { - if (!focusedStreamKey || !focusedStreamInfo) return; - StreamAudioPrefs.setMuted(focusedStreamKey, !isFocusedStreamMuted); - MediaEngine.applyLocalAudioPreferencesForUser(focusedStreamInfo.userId); - }, [focusedStreamKey, isFocusedStreamMuted, focusedStreamInfo]); - const handleFocusedStreamVolumeChange = useCallback( - (newVolume: number) => { - if (!focusedStreamKey || !focusedStreamInfo) return; - StreamAudioPrefs.setVolume(focusedStreamKey, Math.round(newVolume * 100)); - MediaEngine.applyLocalAudioPreferencesForUser(focusedStreamInfo.userId); - }, - [focusedStreamKey, focusedStreamInfo], - ); const handleSpectatorsPopoutOpenChange = useCallback((open: boolean) => { setIsSpectatorsPopoutOpen(open); }, []); @@ -695,20 +666,13 @@ const VoiceCallViewInner = observer( fullscreenIcon={FullscreenButtonIcon} onToggleFullscreen={handleToggleVoiceCallAppFullscreen} volumeControl={ - isFocusedOnScreenShare && focusedStreamKey && hasFocusedStreamAudio && canControlFocusedStreamVolume ? ( - - ) : undefined + } data-flx="voice.voice-call-view.voice-call-view-inner.voice-call-corner-controls" /> diff --git a/fluxer_app/src/features/voice/components/compact_voice_call_view/CompactVoiceCallViewInner.tsx b/fluxer_app/src/features/voice/components/compact_voice_call_view/CompactVoiceCallViewInner.tsx index a9774aeb5..88547554c 100644 --- a/fluxer_app/src/features/voice/components/compact_voice_call_view/CompactVoiceCallViewInner.tsx +++ b/fluxer_app/src/features/voice/components/compact_voice_call_view/CompactVoiceCallViewInner.tsx @@ -27,6 +27,7 @@ import { } from '@app/features/voice/components/compact_voice_call_view/shared'; import {useCompactVoiceCallResize} from '@app/features/voice/components/compact_voice_call_view/useCompactVoiceCallResize'; import {useConnectionLabel} from '@app/features/voice/components/compact_voice_call_view/useConnectionLabel'; +import {FocusedStreamVolumeControl} from '@app/features/voice/components/FocusedStreamVolumeControl'; import {PoppedOutOverlay} from '@app/features/voice/components/popout/PoppedOutOverlay'; import { selectPoppedOutOverlayTransition, @@ -523,6 +524,14 @@ export const CompactVoiceCallViewInner: React.FC = ob fullscreenLabel={fullscreenButtonLabel} fullscreenIcon={FullscreenButtonIcon} onToggleFullscreen={handleToggleVoiceCallAppFullscreen} + volumeControl={ + + } data-flx="voice.compact-voice-call-view.compact-voice-call-view-inner.voice-call-corner-controls" /> {shouldRenderPoppedOutOverlay(poppedOutTransition.snapshot) && (