diff --git a/src/components/VideoPlayer/BaseVideoPlayer.tsx b/src/components/VideoPlayer/BaseVideoPlayer.tsx index 140d3f5eccc46..9234dc4521d06 100644 --- a/src/components/VideoPlayer/BaseVideoPlayer.tsx +++ b/src/components/VideoPlayer/BaseVideoPlayer.tsx @@ -90,7 +90,7 @@ function BaseVideoPlayer({ const isCurrentlyURLSet = currentlyPlayingURL === url; const isUploading = CONST.ATTACHMENT_LOCAL_URL_PREFIX.some((prefix) => url.startsWith(prefix)); const videoStateRef = useRef(null); - const {updateVolume} = useVolumeContext(); + const {updateVolume, lastNonZeroVolume} = useVolumeContext(); const {videoPopoverMenuPlayerRef, currentPlaybackSpeed, setCurrentPlaybackSpeed} = useVideoPopoverMenuContext(); const {source} = videoPopoverMenuPlayerRef.current?.props ?? {}; const shouldUseNewRate = typeof source === 'number' || !source || source.uri !== sourceURL; @@ -187,9 +187,8 @@ function BaseVideoPlayer({ }, [playVideo, videoResumeTryNumberRef], ); - - const prevIsMutedRef = useRef(false); - const prevVolumeRef = useRef(0); + const prevIsMuted = useSharedValue(true); + const prevVolume = useSharedValue(0); const handlePlaybackStatusUpdate = useCallback( (status: AVPlaybackStatus) => { @@ -211,14 +210,17 @@ function BaseVideoPlayer({ setIsEnded(false); } - if (prevIsMutedRef.current && prevVolumeRef.current === 0 && !status.isMuted) { - updateVolume(0.25); + // These two conditions are essential for the mute and unmute functionality to work properly during + // fullscreen playback on the web + if (prevIsMuted.get() && prevVolume.get() === 0 && !status.isMuted) { + updateVolume(lastNonZeroVolume.get()); } - if (isFullScreenRef.current && prevVolumeRef.current !== 0 && status.volume === 0 && !status.isMuted) { + + if (isFullScreenRef.current && prevVolume.get() !== 0 && status.volume === 0 && !status.isMuted) { currentVideoPlayerRef.current?.setStatusAsync({isMuted: true}); } - prevIsMutedRef.current = status.isMuted; - prevVolumeRef.current = status.volume; + prevIsMuted.set(status.isMuted); + prevVolume.set(status.volume); const isVideoPlaying = status.isPlaying; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing @@ -266,7 +268,6 @@ function BaseVideoPlayer({ if (!('isMuted' in status)) { return; } - updateVolume(status.isMuted ? 0 : status.volume || 1); }); diff --git a/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx b/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx index a6e44607ea62f..9b79cf57b6cf3 100644 --- a/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx +++ b/src/components/VideoPlayer/VideoPlayerControls/VolumeButton/index.tsx @@ -33,7 +33,7 @@ const getVolumeIcon = (volume: number) => { function VolumeButton({style, small = false}: VolumeButtonProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const {updateVolume, volume} = useVolumeContext(); + const {updateVolume, volume, toggleMute} = useVolumeContext(); const [sliderHeight, setSliderHeight] = useState(1); const [volumeIcon, setVolumeIcon] = useState({icon: getVolumeIcon(volume.get())}); const [isSliderBeingUsed, setIsSliderBeingUsed] = useState(false); @@ -95,7 +95,7 @@ function VolumeButton({style, small = false}: VolumeButtonProps) { updateVolume(volume.get() === 0 ? 1 : 0)} + onPress={toggleMute} src={volumeIcon.icon} small={small} shouldForceRenderingTooltipBelow diff --git a/src/components/VideoPlayerContexts/VolumeContext.tsx b/src/components/VideoPlayerContexts/VolumeContext.tsx index 1f7b3bf665515..ec42c1fa78774 100644 --- a/src/components/VideoPlayerContexts/VolumeContext.tsx +++ b/src/components/VideoPlayerContexts/VolumeContext.tsx @@ -9,6 +9,8 @@ const Context = React.createContext(null); function VolumeContextProvider({children}: ChildrenProps) { const {currentVideoPlayerRef, originalParent} = usePlaybackContext(); const volume = useSharedValue(0); + // We need this field to remember the last value before clicking mute + const lastNonZeroVolume = useSharedValue(1); const updateVolume = useCallback( (newVolume: number) => { @@ -16,11 +18,22 @@ function VolumeContextProvider({children}: ChildrenProps) { return; } currentVideoPlayerRef.current.setStatusAsync({volume: newVolume, isMuted: newVolume === 0}); + volume.set(newVolume); }, [currentVideoPlayerRef, volume], ); + // This function ensures mute and unmute functionality. Overwriting lastNonZeroValue + // only in the case of mute guarantees that a pan gesture reducing the volume to zero won’t cause + // us to lose this value. As a result, unmute restores the last non-zero value. + const toggleMute = useCallback(() => { + if (volume.get() !== 0) { + lastNonZeroVolume.set(volume.get()); + } + updateVolume(volume.get() === 0 ? lastNonZeroVolume.get() : 0); + }, [lastNonZeroVolume, updateVolume, volume]); + // We want to update the volume when currently playing video changes. // When originalParent changed we're sure that currentVideoPlayerRef is updated. So we can apply the new volume. useEffect(() => { @@ -30,7 +43,16 @@ function VolumeContextProvider({children}: ChildrenProps) { updateVolume(volume.get()); }, [originalParent, updateVolume, volume]); - const contextValue = useMemo(() => ({updateVolume, volume}), [updateVolume, volume]); + const contextValue = useMemo( + () => ({ + updateVolume, + volume, + lastNonZeroVolume, + toggleMute, + }), + [updateVolume, volume, lastNonZeroVolume, toggleMute], + ); + return {children}; } diff --git a/src/components/VideoPlayerContexts/types.ts b/src/components/VideoPlayerContexts/types.ts index bfccebb2df3f7..b376e9dd5f146 100644 --- a/src/components/VideoPlayerContexts/types.ts +++ b/src/components/VideoPlayerContexts/types.ts @@ -25,6 +25,8 @@ type PlaybackContext = { type VolumeContext = { updateVolume: (newVolume: number) => void; volume: SharedValue; + lastNonZeroVolume: SharedValue; + toggleMute: () => void; }; type VideoPopoverMenuContext = {