mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
fix(voice): stop the in call speaker slider clamping a boosted output volume (#1785)
This commit is contained in:
1 parent
17eab43245
commit
ef6536644c
5 files changed
+48
-11
No files matched your search
@@ -9,6 +9,7 @@ import {
|
||||
} 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_VOLUME_MAX_SLIDER_VOLUME} from '@app/features/voice/utils/VoiceVolumeUtils';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
import type React from 'react';
|
||||
import {useCallback, useEffect, useRef} from 'react';
|
||||
@@ -30,7 +31,9 @@ export const CallVolumeControl: React.FC<CallVolumeControlProps> = observer(func
|
||||
lastNonZeroVolumeRef.current = resolveLastNonZeroCallVolume(outputVolume, lastNonZeroVolumeRef.current);
|
||||
}, [outputVolume]);
|
||||
const handleVolumeChange = useCallback((volume: number) => {
|
||||
VoiceSettingsCommands.update({outputVolume: sliderVolumeToCallVolumePercent(volume)});
|
||||
const nextPercent = sliderVolumeToCallVolumePercent(volume);
|
||||
if (nextPercent === VoiceSettings.outputVolume) return;
|
||||
VoiceSettingsCommands.update({outputVolume: nextPercent});
|
||||
}, []);
|
||||
const handleToggleMute = useCallback(() => {
|
||||
VoiceSettingsCommands.update({
|
||||
@@ -41,6 +44,7 @@ export const CallVolumeControl: React.FC<CallVolumeControlProps> = observer(func
|
||||
<MediaVerticalVolumeControl
|
||||
volume={callVolumePercentToSliderVolume(outputVolume)}
|
||||
isMuted={outputVolume === 0}
|
||||
maxVolume={VOICE_VOLUME_MAX_SLIDER_VOLUME}
|
||||
onVolumeChange={handleVolumeChange}
|
||||
onToggleMute={handleToggleMute}
|
||||
iconSize={iconSize}
|
||||
|
||||
@@ -23,6 +23,11 @@ describe('callVolumePercentToSliderVolume', () => {
|
||||
it('clamps negative percent to zero', () => {
|
||||
expect(callVolumePercentToSliderVolume(-10)).toBe(0);
|
||||
});
|
||||
|
||||
it('reaches the boosted ceiling and clamps past it', () => {
|
||||
expect(callVolumePercentToSliderVolume(200)).toBe(2);
|
||||
expect(callVolumePercentToSliderVolume(250)).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sliderVolumeToCallVolumePercent', () => {
|
||||
@@ -34,7 +39,12 @@ describe('sliderVolumeToCallVolumePercent', () => {
|
||||
|
||||
it('clamps out-of-range slider values', () => {
|
||||
expect(sliderVolumeToCallVolumePercent(-0.5)).toBe(0);
|
||||
expect(sliderVolumeToCallVolumePercent(2)).toBe(100);
|
||||
expect(sliderVolumeToCallVolumePercent(2.5)).toBe(200);
|
||||
});
|
||||
|
||||
it('maps the boosted range above unity', () => {
|
||||
expect(sliderVolumeToCallVolumePercent(1.5)).toBe(150);
|
||||
expect(sliderVolumeToCallVolumePercent(2)).toBe(200);
|
||||
});
|
||||
|
||||
it('falls back to the default for non-finite input', () => {
|
||||
@@ -70,3 +80,18 @@ describe('resolveCallVolumeMuteToggle', () => {
|
||||
expect(resolveCallVolumeMuteToggle(0, 0)).toBe(CALL_VOLUME_DEFAULT_PERCENT);
|
||||
});
|
||||
});
|
||||
|
||||
describe('call volume round trip', () => {
|
||||
it('preserves a stored boosted volume through the slider', () => {
|
||||
expect(sliderVolumeToCallVolumePercent(callVolumePercentToSliderVolume(200))).toBe(200);
|
||||
});
|
||||
|
||||
it('steps a boosted volume down by one keyboard step instead of collapsing to unity', () => {
|
||||
expect(sliderVolumeToCallVolumePercent(Math.max(0, callVolumePercentToSliderVolume(200) - 0.1))).toBe(190);
|
||||
});
|
||||
|
||||
it('restores a boosted volume after a mute toggle', () => {
|
||||
expect(resolveCallVolumeMuteToggle(200, 200)).toBe(0);
|
||||
expect(resolveCallVolumeMuteToggle(0, resolveLastNonZeroCallVolume(0, 200))).toBe(200);
|
||||
});
|
||||
});
|
||||
@@ -1,20 +1,24 @@
|
||||
// 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_SLIDER_MAX_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_SLIDER_MAX_PERCENT;
|
||||
return CALL_VOLUME_DEFAULT_PERCENT / CALL_VOLUME_UNITY_PERCENT;
|
||||
}
|
||||
return Math.max(0, percent / CALL_VOLUME_SLIDER_MAX_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_SLIDER_MAX_PERCENT);
|
||||
const percent = Math.round(volume * CALL_VOLUME_UNITY_PERCENT);
|
||||
return Math.max(0, Math.min(CALL_VOLUME_SLIDER_MAX_PERCENT, percent));
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ import VoiceCallLayout from '@app/features/voice/state/VoiceCallLayout';
|
||||
import {hasValidRoomForVoiceCallContext} from '@app/features/voice/utils/VoiceCallContext';
|
||||
import {VOICE_CALL_DESCRIPTOR} from '@app/features/voice/utils/VoiceMessageDescriptors';
|
||||
import {parseVoiceParticipantIdentity} from '@app/features/voice/utils/VoiceParticipantIdentity';
|
||||
import {VOICE_VOLUME_MAX_SLIDER_VOLUME} from '@app/features/voice/utils/VoiceVolumeUtils';
|
||||
import {ME} from '@fluxer/constants/src/AppConstants';
|
||||
import {msg, plural} from '@lingui/core/macro';
|
||||
import {useLingui} from '@lingui/react/macro';
|
||||
@@ -620,6 +621,7 @@ const VoiceCallViewInner = observer(
|
||||
<MediaVerticalVolumeControl
|
||||
volume={focusedStreamVolume / 100}
|
||||
isMuted={isFocusedStreamMuted}
|
||||
maxVolume={VOICE_VOLUME_MAX_SLIDER_VOLUME}
|
||||
onVolumeChange={handleFocusedStreamVolumeChange}
|
||||
onToggleMute={handleFocusedStreamToggleMute}
|
||||
iconSize={18}
|
||||
|
||||
+7
-5
@@ -48,6 +48,7 @@ interface MediaVerticalVolumeControlProps {
|
||||
iconSize?: number;
|
||||
className?: string;
|
||||
position?: 'above' | 'below';
|
||||
maxVolume?: number;
|
||||
}
|
||||
|
||||
function getVolumeIcon(volume: number, isMuted: boolean) {
|
||||
@@ -73,6 +74,7 @@ export function MediaVerticalVolumeControl({
|
||||
iconSize = 18,
|
||||
className,
|
||||
position = 'above',
|
||||
maxVolume = 1,
|
||||
}: MediaVerticalVolumeControlProps) {
|
||||
const {i18n} = useLingui();
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
@@ -178,9 +180,9 @@ export function MediaVerticalVolumeControl({
|
||||
}, [isDragging, scheduleClose]);
|
||||
const handleSliderValueChange = useCallback(
|
||||
(nextValue: number) => {
|
||||
onVolumeChange(Math.max(0, Math.min(1, nextValue / 100)));
|
||||
onVolumeChange(Math.max(0, Math.min(maxVolume, nextValue / 100)));
|
||||
},
|
||||
[onVolumeChange],
|
||||
[maxVolume, onVolumeChange],
|
||||
);
|
||||
const handleSliderInteractionChange = useCallback(
|
||||
(nextIsDragging: boolean) => {
|
||||
@@ -209,7 +211,7 @@ export function MediaVerticalVolumeControl({
|
||||
case 'ArrowRight':
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
newVolume = Math.min(1, volume + step);
|
||||
newVolume = Math.min(maxVolume, volume + step);
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
case 'ArrowLeft':
|
||||
@@ -228,7 +230,7 @@ export function MediaVerticalVolumeControl({
|
||||
}
|
||||
onVolumeChange(newVolume);
|
||||
},
|
||||
[volume, onVolumeChange, onToggleMute],
|
||||
[maxVolume, volume, onVolumeChange, onToggleMute],
|
||||
);
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -264,7 +266,7 @@ export function MediaVerticalVolumeControl({
|
||||
defaultValue={sliderValue}
|
||||
factoryDefaultValue={100}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
maxValue={maxVolume * 100}
|
||||
step={1}
|
||||
value={sliderValue}
|
||||
orientation="vertical"
|
||||
|
||||
Reference in new issue
Block a user