From 4d626377efedb00b8fb0e493a98ce078ef9ec61c Mon Sep 17 00:00:00 2001 From: jeffvli Date: Thu, 4 Dec 2025 01:31:57 -0800 Subject: [PATCH] fix mediasession controls --- src/main/features/core/player/media-keys.ts | 6 +-- src/main/index.ts | 9 +++- .../player/hooks/use-media-session.ts | 19 +++---- .../hotkeys/media-session-settings.tsx | 49 ++++++++++++++----- .../hotkeys/window-hotkey-settings.tsx | 28 +++++++---- .../components/window/window-settings.tsx | 23 +-------- .../features/settings/restart-toast.ts | 22 +++++++++ 7 files changed, 98 insertions(+), 58 deletions(-) create mode 100644 src/renderer/features/settings/restart-toast.ts diff --git a/src/main/features/core/player/media-keys.ts b/src/main/features/core/player/media-keys.ts index 865d2a131..d56749401 100644 --- a/src/main/features/core/player/media-keys.ts +++ b/src/main/features/core/player/media-keys.ts @@ -1,6 +1,6 @@ import { BrowserWindow, globalShortcut, systemPreferences } from 'electron'; -import { isMacOS, isWindows } from '../../../utils'; +import { isLinux, isMacOS } from '../../../utils'; import { store } from '../settings'; import { PlayerType } from '/@/shared/types/types'; @@ -25,10 +25,10 @@ export const enableMediaKeys = (window: BrowserWindow | null) => { } } - const enableWindowsMediaSession = store.get('mediaSession', false) as boolean; + const enableMediaSession = store.get('mediaSession', false) as boolean; const playbackType = store.get('playbackType', PlayerType.WEB) as PlayerType; - if (!enableWindowsMediaSession || !isWindows() || playbackType !== PlayerType.WEB) { + if (!enableMediaSession || isLinux() || playbackType !== PlayerType.WEB) { globalShortcut.register('MediaStop', () => { window?.webContents.send('renderer-player-stop'); }); diff --git a/src/main/index.ts b/src/main/index.ts index 941e2337e..8f0ccfae9 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -477,10 +477,15 @@ async function createWindow(first = true): Promise { } } -const enableWindowsMediaSession = store.get('mediaSession', false) as boolean; +// Only allow hardware media key handling if: +// 1. The "Enable Media Session" setting is enabled +// 2. The playback type is WEB (mpv not supported) +// 3. The platform is not Linux (because we are using mpris instead) +const enableMediaSession = store.get('mediaSession', false) as boolean; const playbackType = store.get('playbackType', PlayerType.WEB) as PlayerType; const shouldDisableMediaFeatures = - !isWindows() || !enableWindowsMediaSession || playbackType !== PlayerType.WEB; + isLinux() || !enableMediaSession || playbackType !== PlayerType.WEB; + if (shouldDisableMediaFeatures) { app.commandLine.appendSwitch( 'disable-features', diff --git a/src/renderer/features/player/hooks/use-media-session.ts b/src/renderer/features/player/hooks/use-media-session.ts index 7864986d9..79c277aec 100644 --- a/src/renderer/features/player/hooks/use-media-session.ts +++ b/src/renderer/features/player/hooks/use-media-session.ts @@ -3,17 +3,19 @@ import { useEffect, useMemo } from 'react'; import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events'; import { usePlayer } from '/@/renderer/features/player/context/player-context'; import { usePlaybackSettings, useSettingsStore, useTimestampStoreBase } from '/@/renderer/store'; -import { PlayerStatus } from '/@/shared/types/types'; +import { PlayerStatus, PlayerType } from '/@/shared/types/types'; + +const mediaSession = navigator.mediaSession; export const useMediaSession = () => { const { mediaSession: mediaSessionEnabled } = usePlaybackSettings(); const player = usePlayer(); - const mediaSession = navigator.mediaSession; const skip = useSettingsStore((state) => state.general.skipButtons); + const playbackType = useSettingsStore((state) => state.playback.type); const isMediaSessionEnabled = useMemo(() => { - return mediaSessionEnabled && mediaSession; - }, [mediaSessionEnabled, mediaSession]); + return Boolean(mediaSessionEnabled && playbackType === PlayerType.WEB); + }, [mediaSessionEnabled, playbackType]); useEffect(() => { if (!isMediaSessionEnabled) { @@ -21,6 +23,7 @@ export const useMediaSession = () => { } mediaSession.setActionHandler('nexttrack', () => { + console.log('mediaSession.nexttrack'); player.mediaNext(); }); @@ -73,13 +76,7 @@ export const useMediaSession = () => { mediaSession.setActionHandler('seekbackward', null); mediaSession.setActionHandler('seekforward', null); }; - }, [ - player, - skip?.skipBackwardSeconds, - skip?.skipForwardSeconds, - isMediaSessionEnabled, - mediaSession, - ]); + }, [player, skip?.skipBackwardSeconds, skip?.skipForwardSeconds, isMediaSessionEnabled]); usePlayerEvents( { diff --git a/src/renderer/features/settings/components/hotkeys/media-session-settings.tsx b/src/renderer/features/settings/components/hotkeys/media-session-settings.tsx index 30004fe19..453112a6b 100644 --- a/src/renderer/features/settings/components/hotkeys/media-session-settings.tsx +++ b/src/renderer/features/settings/components/hotkeys/media-session-settings.tsx @@ -5,23 +5,48 @@ import { SettingOption, SettingsSection, } from '/@/renderer/features/settings/components/settings-section'; -import { usePlaybackSettings, useSettingsStoreActions } from '/@/renderer/store/settings.store'; +import { openRestartRequiredToast } from '/@/renderer/features/settings/restart-toast'; +import { + useHotkeySettings, + usePlaybackSettings, + useSettingsStoreActions, +} from '/@/renderer/store/settings.store'; import { Switch } from '/@/shared/components/switch/switch'; import { PlayerType } from '/@/shared/types/types'; -const isWindows = isElectron() ? window.api.utils.isWindows() : null; +const isLinux = isElectron() ? window.api.utils.isLinux() : false; const isDesktop = isElectron(); -const ipc = isElectron() ? window.api.ipc : null; +const localSettings = isElectron() ? window.api.localSettings : null; export const MediaSessionSettings = () => { const { t } = useTranslation(); const { mediaSession, type: playbackType } = usePlaybackSettings(); - const { toggleMediaSession } = useSettingsStoreActions(); + const playbackSettings = usePlaybackSettings(); + const hotkeySettings = useHotkeySettings(); + const { setSettings } = useSettingsStoreActions(); - function handleMediaSessionChange() { - const current = mediaSession; - toggleMediaSession(); - ipc?.send('settings-set', { property: 'mediaSession', value: !current }); + function handleMediaSessionChange(e: boolean) { + // If media session is enabled, disable global media hotkeys + if (e) { + localSettings!.set('global_media_hotkeys', false); + setSettings({ + hotkeys: { + ...hotkeySettings, + globalMediaHotkeys: false, + }, + }); + } + + localSettings!.set('mediaSession', e); + setSettings({ + playback: { + ...playbackSettings, + mediaSession: e, + }, + }); + + // Restart is always required because the media session is a startup setting + openRestartRequiredToast(); } const mediaSessionOptions: SettingOption[] = [ @@ -29,16 +54,16 @@ export const MediaSessionSettings = () => { control: ( handleMediaSessionChange(e.currentTarget.checked)} /> ), description: t('setting.mediaSession', { context: 'description', postProcess: 'sentenceCase', }), - isHidden: !isWindows || !isDesktop, + isHidden: isLinux || !isDesktop, note: t('common.restartRequired', { postProcess: 'sentenceCase' }), title: t('setting.mediaSession', { postProcess: 'sentenceCase' }), }, diff --git a/src/renderer/features/settings/components/hotkeys/window-hotkey-settings.tsx b/src/renderer/features/settings/components/hotkeys/window-hotkey-settings.tsx index 60f2958fd..49810dcdc 100644 --- a/src/renderer/features/settings/components/hotkeys/window-hotkey-settings.tsx +++ b/src/renderer/features/settings/components/hotkeys/window-hotkey-settings.tsx @@ -5,42 +5,52 @@ import { SettingOption, SettingsSection, } from '/@/renderer/features/settings/components/settings-section'; +import { openRestartRequiredToast } from '/@/renderer/features/settings/restart-toast'; import { useHotkeySettings, usePlaybackSettings, useSettingsStoreActions } from '/@/renderer/store'; import { Switch } from '/@/shared/components/switch/switch'; -import { PlayerType } from '/@/shared/types/types'; const localSettings = isElectron() ? window.api.localSettings : null; -const isWindows = isElectron() ? window.api.utils.isWindows() : false; export const WindowHotkeySettings = () => { const { t } = useTranslation(); const settings = useHotkeySettings(); + const playbackSettings = usePlaybackSettings(); const { setSettings } = useSettingsStoreActions(); - const { mediaSession: enableWindowsMediaSession, type: playbackType } = usePlaybackSettings(); + const { mediaSession } = usePlaybackSettings(); const options: SettingOption[] = [ { control: ( { + localSettings!.set('global_media_hotkeys', e.currentTarget.checked); setSettings({ hotkeys: { ...settings, globalMediaHotkeys: e.currentTarget.checked, }, }); - localSettings!.set('global_media_hotkeys', e.currentTarget.checked); if (e.currentTarget.checked) { localSettings!.enableMediaKeys(); } else { localSettings!.disableMediaKeys(); } + + // Restart is required if media session was previously enabled + // Though the global hotkey should override the media session, it's better to restart to be safe + if (e.currentTarget.checked && mediaSession) { + localSettings!.set('mediaSession', false); + setSettings({ + playback: { + ...playbackSettings, + mediaSession: false, + }, + }); + openRestartRequiredToast(); + } }} /> ), diff --git a/src/renderer/features/settings/components/window/window-settings.tsx b/src/renderer/features/settings/components/window/window-settings.tsx index 86dc26d7b..2a4e711c7 100644 --- a/src/renderer/features/settings/components/window/window-settings.tsx +++ b/src/renderer/features/settings/components/window/window-settings.tsx @@ -5,10 +5,10 @@ import { SettingOption, SettingsSection, } from '/@/renderer/features/settings/components/settings-section'; +import { openRestartRequiredToast } from '/@/renderer/features/settings/restart-toast'; import { useSettingsStoreActions, useWindowSettings } from '/@/renderer/store'; import { Select } from '/@/shared/components/select/select'; import { Switch } from '/@/shared/components/switch/switch'; -import { toast } from '/@/shared/components/toast/toast'; import { Platform } from '/@/shared/types/types'; const WINDOW_BAR_OPTIONS = [ @@ -44,26 +44,7 @@ export const WindowSettings = () => { const requireRestart = isSwitchingToFrame || isSwitchingToNoFrame; if (requireRestart) { - toast.info({ - autoClose: false, - id: 'restart-toast', - message: t('common.forceRestartRequired', { - postProcess: 'sentenceCase', - }), - onClose: () => { - window.api.ipc!.send('app-restart'); - }, - title: t('common.restartRequired', { - postProcess: 'sentenceCase', - }), - }); - } else { - toast.update({ - autoClose: 0, - id: 'restart-toast', - message: '', - onClose: () => {}, - }); // clean old toasts + openRestartRequiredToast(); } localSettings?.set('window_window_bar_style', e as Platform); diff --git a/src/renderer/features/settings/restart-toast.ts b/src/renderer/features/settings/restart-toast.ts new file mode 100644 index 000000000..5c5b6907b --- /dev/null +++ b/src/renderer/features/settings/restart-toast.ts @@ -0,0 +1,22 @@ +import { t } from 'i18next'; +import isElectron from 'is-electron'; + +import { toast } from '/@/shared/components/toast/toast'; + +const ipc = isElectron() ? window.api.ipc : null; + +export const openRestartRequiredToast = () => { + return toast.info({ + autoClose: false, + id: 'restart-toast', + message: t('common.forceRestartRequired', { + postProcess: 'sentenceCase', + }), + onClose: () => { + ipc?.send('app-restart'); + }, + title: t('common.restartRequired', { + postProcess: 'sentenceCase', + }), + }); +};