fix MPV visualizer on macOS and handle exclusive mode UX (#1930)

This commit is contained in:
York
2026-04-14 11:47:03 +08:00
committed by GitHub
parent f5839bf39c
commit b99899f128
7 changed files with 58 additions and 6 deletions
@@ -19,6 +19,7 @@ export function useVisualizerSystemAudio(options: {
onDeniedRef.current = onSystemAudioCaptureDenied;
onSuccessRef.current = onSystemAudioCaptureSuccess;
const playbackType = usePlaybackType();
const isMacOS = Boolean(window.api?.utils?.isMacOS?.());
const { setWebAudio, webAudio } = useWebAudio();
const webAudioRef = useRef(webAudio);
const streamRef = useRef<MediaStream | null>(null);
@@ -80,7 +81,7 @@ export function useVisualizerSystemAudio(options: {
try {
const stream = await navigator.mediaDevices.getDisplayMedia({
audio: true,
video: false,
video: isMacOS, // On macOS, getDisplayMedia requires video to be requested in order to capture system audio
});
const audioTracks = stream.getAudioTracks();
@@ -124,7 +125,7 @@ export function useVisualizerSystemAudio(options: {
} finally {
connectInFlightRef.current = false;
}
}, [disconnect, setWebAudio]);
}, [disconnect, isMacOS, setWebAudio]);
const connectRef = useRef(connect);
connectRef.current = connect;
@@ -1,16 +1,17 @@
import isElectron from 'is-electron';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useIsLocalVisualizerSurfaceVisible } from '/@/renderer/features/player/hooks/use-is-local-visualizer-surface-visible';
import { useVisualizerSystemAudio } from '/@/renderer/features/player/hooks/use-visualizer-system-audio';
import { closeLocalVisualizerSurfaces } from '/@/renderer/features/player/utils/close-local-visualizer-surfaces';
import { usePlaybackType } from '/@/renderer/store';
import { useMpvSettings, usePlaybackType } from '/@/renderer/store';
import { Button } from '/@/shared/components/button/button';
import { Group } from '/@/shared/components/group/group';
import { Modal } from '/@/shared/components/modal/modal';
import { Stack } from '/@/shared/components/stack/stack';
import { Text } from '/@/shared/components/text/text';
import { toast } from '/@/shared/components/toast/toast';
import { useDisclosure } from '/@/shared/hooks/use-disclosure';
import { PlayerType } from '/@/shared/types/types';
@@ -31,12 +32,21 @@ export function VisualizerSystemAudioBridgeHook() {
function VisualizerSystemAudioBridge() {
const { t } = useTranslation();
const playbackType = usePlaybackType();
const { audioExclusiveMode } = useMpvSettings();
const isVisualizerSurfaceVisible = useIsLocalVisualizerSurfaceVisible();
const [promptState, setPromptState] = useState<PromptState>('loading');
const [sessionAllowCapture, setSessionAllowCapture] = useState(false);
const wasBlockedByExclusiveModeRef = useRef(false);
const [isPromptOpen, { close: closePrompt, open: openPrompt, toggle: togglePrompt }] =
useDisclosure(false);
const isExclusiveModeEnabled = audioExclusiveMode === 'yes';
const isVisualizerBlockedByExclusiveMode =
isElectron() &&
playbackType === PlayerType.LOCAL &&
isVisualizerSurfaceVisible &&
isExclusiveModeEnabled;
const persistConsent = useCallback((granted: boolean) => {
if (!isElectron() || !window.api.localSettings) {
return;
@@ -67,6 +77,7 @@ function VisualizerSystemAudioBridge() {
const eligibleForPrompt =
isElectron() &&
playbackType === PlayerType.LOCAL &&
!isExclusiveModeEnabled &&
isVisualizerSurfaceVisible &&
promptState !== 'loading' &&
!promptState.consent &&
@@ -80,9 +91,25 @@ function VisualizerSystemAudioBridge() {
}
}, [eligibleForPrompt, closePrompt, openPrompt]);
useEffect(() => {
if (isVisualizerBlockedByExclusiveMode && !wasBlockedByExclusiveModeRef.current) {
toast.error({
message: t('visualizer.systemAudioExclusiveModeNotSupported', {
postProcess: 'sentenceCase',
}),
});
setSessionAllowCapture(false);
closePrompt();
closeLocalVisualizerSurfaces();
}
wasBlockedByExclusiveModeRef.current = isVisualizerBlockedByExclusiveMode;
}, [closePrompt, isVisualizerBlockedByExclusiveMode, t]);
const shouldAttemptConnection =
isElectron() &&
playbackType === PlayerType.LOCAL &&
!isExclusiveModeEnabled &&
isVisualizerSurfaceVisible &&
promptState !== 'loading' &&
(promptState.consent || sessionAllowCapture);