simplify remote/media session (#632)

This commit is contained in:
Kendall Garner 2024-07-03 08:47:26 +00:00 committed by GitHub
parent d57b4b4b68
commit 110a1a63f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 236 additions and 230 deletions

View file

@ -40,13 +40,13 @@ type WebAudio = {
gain: GainNode;
};
// Credits: http://stackoverflow.com/questions/12150729/ddg
// Credits: https://gist.github.com/novwhisky/8a1a0168b94f3b6abfaa?permalink_comment_id=1551393#gistcomment-1551393
// This is used so that the player will always have an <audio> element. This means that
// player1Source and player2Source are connected BEFORE the user presses play for
// the first time. This workaround is important for Safari, which seems to require the
// source to be connected PRIOR to resuming audio context
const EMPTY_SOURCE =
'data:audio/wav;base64,UklGRjIAAABXQVZFZm10IBIAAAABAAEAQB8AAEAfAAABAAgAAABmYWN0BAAAAAAAAABkYXRhAAAAAA==';
'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU2LjM2LjEwMAAAAAAAAAAAAAAA//OEAAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAAEAAABIADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV6urq6urq6urq6urq6urq6urq6urq6urq6v////////////////////////////////8AAAAATGF2YzU2LjQxAAAAAAAAAAAAAAAAJAAAAAAAAAAAASDs90hvAAAAAAAAAAAAAAAAAAAA//MUZAAAAAGkAAAAAAAAA0gAAAAATEFN//MUZAMAAAGkAAAAAAAAA0gAAAAARTMu//MUZAYAAAGkAAAAAAAAA0gAAAAAOTku//MUZAkAAAGkAAAAAAAAA0gAAAAANVVV';
export const AudioPlayer = forwardRef(
(

View file

@ -60,6 +60,7 @@ import {
import { usePlaybackType } from '/@/renderer/store/settings.store';
import { Play, PlaybackType } from '/@/renderer/types';
import { ItemDetailsModal } from '/@/renderer/features/item-details/components/item-details-modal';
import { updateSong } from '/@/renderer/features/player/update-remote-song';
import { controller } from '/@/renderer/api/controller';
type ContextMenuContextProps = {
@ -89,7 +90,6 @@ const JELLYFIN_IGNORED_MENU_ITEMS: ContextMenuItemType[] = ['setRating', 'shareI
// const SUBSONIC_IGNORED_MENU_ITEMS: ContextMenuItemType[] = [];
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
const remote = isElectron() ? window.electron.remote : null;
export interface ContextMenuProviderProps {
children: ReactNode;
@ -643,7 +643,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
ctx.tableApi?.redrawRows();
if (isCurrentSongRemoved) {
remote?.updateSong({ song: playerData.current.song });
updateSong(playerData.current.song);
}
}, [ctx.dataNodes, ctx.tableApi, playbackType, removeFromQueue]);

View file

@ -14,13 +14,13 @@ import {
} from 'react-icons/ri';
import { Song } from '/@/renderer/api/types';
import { usePlayerControls, useQueueControls } from '/@/renderer/store';
import { PlaybackType, PlayerStatus, TableType } from '/@/renderer/types';
import { PlaybackType, TableType } from '/@/renderer/types';
import { usePlaybackType } from '/@/renderer/store/settings.store';
import { usePlayerStore, useSetCurrentTime } from '../../../store/player.store';
import { TableConfigDropdown } from '/@/renderer/components/virtual-table';
import { updateSong } from '/@/renderer/features/player/update-remote-song';
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
const remote = isElectron() ? window.electron.remote : null;
interface PlayQueueListOptionsProps {
tableRef: MutableRefObject<{ grid: AgGridReactType<Song> } | null>;
@ -79,7 +79,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
}
if (isCurrentSongRemoved) {
remote?.updateSong({ song: playerData.current.song });
updateSong(playerData.current.song);
}
};
@ -91,7 +91,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
mpvPlayer!.pause();
}
remote?.updateSong({ song: undefined, status: PlayerStatus.PAUSED });
updateSong(undefined);
setCurrentTime(0);
pause();

View file

@ -30,16 +30,16 @@ import debounce from 'lodash/debounce';
import { ErrorBoundary } from 'react-error-boundary';
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
import { ErrorFallback } from '/@/renderer/features/action-required';
import { PlaybackType, PlayerStatus, TableType } from '/@/renderer/types';
import { PlaybackType, TableType } from '/@/renderer/types';
import { LibraryItem, QueueSong } from '/@/renderer/api/types';
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
import { QUEUE_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
import { useAppFocus } from '/@/renderer/hooks';
import { PlayersRef } from '/@/renderer/features/player/ref/players-ref';
import { updateSong } from '/@/renderer/features/player/update-remote-song';
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
const remote = isElectron() ? window.electron.remote : null;
type QueueProps = {
type: TableType;
@ -82,11 +82,7 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref<any>) => {
const handleDoubleClick = (e: CellDoubleClickedEvent) => {
const playerData = setCurrentTrack(e.data.uniqueId);
remote?.updateSong({
currentTime: 0,
song: playerData.current.song,
status: PlayerStatus.PLAYING,
});
updateSong(playerData.current.song);
if (playbackType === PlaybackType.LOCAL) {
mpvPlayer!.volume(volume);

View file

@ -1,5 +1,4 @@
import { useCallback } from 'react';
import isElectron from 'is-electron';
import styled from 'styled-components';
import { usePlaybackType, useSettingsStore } from '/@/renderer/store/settings.store';
import { PlaybackType } from '/@/renderer/types';
@ -17,6 +16,7 @@ import { CenterControls } from './center-controls';
import { LeftControls } from './left-controls';
import { RightControls } from './right-controls';
import { PlayersRef } from '/@/renderer/features/player/ref/players-ref';
import { updateSong } from '/@/renderer/features/player/update-remote-song';
const PlayerbarContainer = styled.div`
width: 100vw;
@ -59,8 +59,6 @@ const CenterGridItem = styled.div`
overflow: hidden;
`;
const remote = isElectron() ? window.electron.remote : null;
export const Playerbar = () => {
const playersRef = PlayersRef;
const settings = useSettingsStore((state) => state.playback);
@ -75,13 +73,7 @@ export const Playerbar = () => {
const autoNextFn = useCallback(() => {
const playerData = autoNext();
if (remote) {
remote.updateSong({
currentTime: 0,
song: playerData.current.song,
});
}
updateSong(playerData.current.song);
}, [autoNext]);
return (

View file

@ -14,9 +14,9 @@ import {
import { usePlaybackType } from '/@/renderer/store/settings.store';
import { useScrobble } from '/@/renderer/features/player/hooks/use-scrobble';
import debounce from 'lodash/debounce';
import { QueueSong } from '/@/renderer/api/types';
import { toast } from '/@/renderer/components';
import { useTranslation } from 'react-i18next';
import { updateSong } from '/@/renderer/features/player/update-remote-song';
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
const mpvPlayerListener = isElectron() ? window.electron.mpvPlayerListener : null;
@ -24,7 +24,7 @@ const ipc = isElectron() ? window.electron.ipc : null;
const utils = isElectron() ? window.electron.utils : null;
const mpris = isElectron() && utils?.isLinux() ? window.electron.mpris : null;
const remote = isElectron() ? window.electron.remote : null;
const mediaSession = !isElectron() || !utils?.isLinux() ? navigator.mediaSession : null;
const mediaSession = navigator.mediaSession;
export const useCenterControls = (args: { playersRef: any }) => {
const { t } = useTranslation();
@ -46,6 +46,23 @@ export const useCenterControls = (args: { playersRef: any }) => {
const { handleScrobbleFromSongRestart, handleScrobbleFromSeek } = useScrobble();
useEffect(() => {
if (mediaSession) {
mediaSession.playbackState =
playerStatus === PlayerStatus.PLAYING ? 'playing' : 'paused';
}
remote?.updatePlayback(playerStatus);
}, [playerStatus]);
useEffect(() => {
remote?.updateRepeat(repeatStatus);
}, [repeatStatus]);
useEffect(() => {
remote?.updateShuffle(shuffleStatus !== PlayerShuffle.NONE);
}, [shuffleStatus]);
const resetPlayers = useCallback(() => {
if (player1Ref.getInternalPlayer()) {
player1Ref.getInternalPlayer().currentTime = 0;
@ -76,61 +93,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
const isMpvPlayer = isElectron() && playbackType === PlaybackType.LOCAL;
const mprisUpdateSong = (args?: {
currentTime?: number;
song?: QueueSong;
status?: PlayerStatus;
}) => {
const { song, currentTime, status } = args || {};
const time = currentTime || usePlayerStore.getState().current.time;
const playStatus = status || usePlayerStore.getState().current.status;
const track = song || usePlayerStore.getState().current.song;
remote?.updateSong({
currentTime: time,
repeat: usePlayerStore.getState().repeat,
shuffle: usePlayerStore.getState().shuffle !== PlayerShuffle.NONE,
song: track,
status: playStatus,
});
if (mediaSession) {
mediaSession.playbackState = playStatus === PlayerStatus.PLAYING ? 'playing' : 'paused';
let metadata: MediaMetadata;
if (track) {
let artwork: MediaImage[];
if (track.imageUrl) {
const image300 = track.imageUrl
?.replace(/&size=\d+/, '&size=300')
.replace(/\?width=\d+/, '?width=300')
.replace(/&height=\d+/, '&height=300');
artwork = [{ sizes: '300x300', src: image300, type: 'image/png' }];
} else {
artwork = [];
}
metadata = new MediaMetadata({
album: track.album ?? '',
artist: track.artistName,
artwork,
title: track.name,
});
} else {
metadata = new MediaMetadata();
}
mediaSession.metadata = metadata;
}
};
const handlePlay = useCallback(() => {
mprisUpdateSong({ status: PlayerStatus.PLAYING });
if (isMpvPlayer) {
mpvPlayer?.volume(usePlayerStore.getState().volume);
mpvPlayer!.play();
@ -145,8 +108,6 @@ export const useCenterControls = (args: { playersRef: any }) => {
}, [currentPlayerRef, isMpvPlayer, play]);
const handlePause = useCallback(() => {
mprisUpdateSong({ status: PlayerStatus.PAUSED });
if (isMpvPlayer) {
mpvPlayer!.pause();
}
@ -155,8 +116,6 @@ export const useCenterControls = (args: { playersRef: any }) => {
}, [isMpvPlayer, pause]);
const handleStop = useCallback(() => {
mprisUpdateSong({ status: PlayerStatus.PAUSED });
if (isMpvPlayer) {
mpvPlayer!.pause();
mpvPlayer!.seekTo(0);
@ -212,13 +171,13 @@ export const useCenterControls = (args: { playersRef: any }) => {
const handleRepeatAll = {
local: () => {
const playerData = autoNext();
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
updateSong(playerData.current.song);
mpvPlayer!.autoNext(playerData);
play();
},
web: () => {
const playerData = autoNext();
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
updateSong(playerData.current.song);
},
};
@ -226,15 +185,12 @@ export const useCenterControls = (args: { playersRef: any }) => {
local: () => {
if (isLastTrack) {
const playerData = setCurrentIndex(0);
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PAUSED });
updateSong(playerData.current.song);
mpvPlayer!.setQueue(playerData, true);
pause();
} else {
const playerData = autoNext();
mprisUpdateSong({
song: playerData.current.song,
status: PlayerStatus.PLAYING,
});
updateSong(playerData.current.song);
mpvPlayer!.autoNext(playerData);
play();
}
@ -242,14 +198,10 @@ export const useCenterControls = (args: { playersRef: any }) => {
web: () => {
if (isLastTrack) {
resetPlayers();
mprisUpdateSong({ status: PlayerStatus.PAUSED });
pause();
} else {
const playerData = autoNext();
mprisUpdateSong({
song: playerData.current.song,
status: PlayerStatus.PLAYING,
});
updateSong(playerData.current.song);
resetPlayers();
}
},
@ -258,20 +210,15 @@ export const useCenterControls = (args: { playersRef: any }) => {
const handleRepeatOne = {
local: () => {
const playerData = autoNext();
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
updateSong(playerData.current.song);
mpvPlayer!.autoNext(playerData);
play();
},
web: () => {
if (isLastTrack) {
mprisUpdateSong({ status: PlayerStatus.PAUSED });
resetPlayers();
} else {
const playerData = autoNext();
mprisUpdateSong({
song: playerData.current.song,
status: PlayerStatus.PLAYING,
});
autoNext();
resetPlayers();
}
},
@ -309,12 +256,12 @@ export const useCenterControls = (args: { playersRef: any }) => {
const handleRepeatAll = {
local: () => {
const playerData = next();
mprisUpdateSong({ song: playerData.current.song });
updateSong(playerData.current.song);
mpvPlayer!.setQueue(playerData);
},
web: () => {
const playerData = next();
mprisUpdateSong({ song: playerData.current.song });
updateSong(playerData.current.song);
},
};
@ -322,27 +269,24 @@ export const useCenterControls = (args: { playersRef: any }) => {
local: () => {
if (isLastTrack) {
const playerData = setCurrentIndex(0);
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PAUSED });
updateSong(playerData.current.song);
mpvPlayer!.setQueue(playerData, true);
pause();
} else {
const playerData = next();
mprisUpdateSong({ song: playerData.current.song });
updateSong(playerData.current.song);
mpvPlayer!.setQueue(playerData);
}
},
web: () => {
if (isLastTrack) {
const playerData = setCurrentIndex(0);
mprisUpdateSong({
song: playerData.current.song,
status: PlayerStatus.PAUSED,
});
updateSong(playerData.current.song);
resetPlayers();
pause();
} else {
const playerData = next();
mprisUpdateSong({ song: playerData.current.song });
updateSong(playerData.current.song);
resetPlayers();
}
},
@ -352,14 +296,14 @@ export const useCenterControls = (args: { playersRef: any }) => {
local: () => {
if (!isLastTrack) {
const playerData = next();
mprisUpdateSong({ song: playerData.current.song });
updateSong(playerData.current.song);
mpvPlayer!.setQueue(playerData);
}
},
web: () => {
if (!isLastTrack) {
const playerData = next();
mprisUpdateSong({ song: playerData.current.song });
updateSong(playerData.current.song);
}
},
};
@ -413,22 +357,22 @@ export const useCenterControls = (args: { playersRef: any }) => {
local: () => {
if (!isFirstTrack) {
const playerData = previous();
mprisUpdateSong({ song: playerData.current.song });
updateSong(playerData.current.song);
mpvPlayer!.setQueue(playerData);
} else {
const playerData = setCurrentIndex(queue.length - 1);
mprisUpdateSong({ song: playerData.current.song });
updateSong(playerData.current.song);
mpvPlayer!.setQueue(playerData);
}
},
web: () => {
if (isFirstTrack) {
const playerData = setCurrentIndex(queue.length - 1);
mprisUpdateSong({ song: playerData.current.song });
updateSong(playerData.current.song);
resetPlayers();
} else {
const playerData = previous();
mprisUpdateSong({ song: playerData.current.song });
updateSong(playerData.current.song);
resetPlayers();
}
},
@ -438,26 +382,22 @@ export const useCenterControls = (args: { playersRef: any }) => {
local: () => {
if (isFirstTrack) {
const playerData = setCurrentIndex(0);
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PAUSED });
updateSong(playerData.current.song);
mpvPlayer!.setQueue(playerData, true);
pause();
} else {
const playerData = previous();
mprisUpdateSong({
currentTime: usePlayerStore.getState().current.time,
song: playerData.current.song,
});
updateSong(playerData.current.song);
mpvPlayer!.setQueue(playerData);
}
},
web: () => {
if (isFirstTrack) {
resetPlayers();
mprisUpdateSong({ status: PlayerStatus.PAUSED });
pause();
} else {
const playerData = previous();
mprisUpdateSong({ song: playerData.current.song });
updateSong(playerData.current.song);
resetPlayers();
}
},
@ -466,12 +406,12 @@ export const useCenterControls = (args: { playersRef: any }) => {
const handleRepeatOne = {
local: () => {
const playerData = previous();
mprisUpdateSong({ song: playerData.current.song });
updateSong(playerData.current.song);
mpvPlayer!.setQueue(playerData);
},
web: () => {
const playerData = previous();
mprisUpdateSong({ song: playerData.current.song });
updateSong(playerData.current.song);
resetPlayers();
},
};

View file

@ -2,13 +2,7 @@ import { useCallback, useRef } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { useCurrentServer, usePlayerControls, usePlayerStore } from '/@/renderer/store';
import { usePlaybackType } from '/@/renderer/store/settings.store';
import {
PlayQueueAddOptions,
Play,
PlaybackType,
PlayerStatus,
PlayerShuffle,
} from '/@/renderer/types';
import { PlayQueueAddOptions, Play, PlaybackType } from '/@/renderer/types';
import { toast } from '/@/renderer/components/toast/index';
import isElectron from 'is-electron';
import { nanoid } from 'nanoid/non-secure';
@ -30,6 +24,7 @@ import {
import { queryKeys } from '/@/renderer/api/query-keys';
import { useTranslation } from 'react-i18next';
import { PlayersRef } from '/@/renderer/features/player/ref/players-ref';
import { updateSong } from '/@/renderer/features/player/update-remote-song';
const getRootQueryKey = (itemType: LibraryItem, serverId: string) => {
let queryKey;
@ -59,7 +54,6 @@ const getRootQueryKey = (itemType: LibraryItem, serverId: string) => {
};
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
const remote = isElectron() ? window.electron.remote : null;
const addToQueue = usePlayerStore.getState().actions.addToQueue;
@ -171,6 +165,8 @@ export const useHandlePlayQueueAdd = () => {
const hadSong = usePlayerStore.getState().queue.default.length > 0;
const playerData = addToQueue({ initialIndex: initialSongIndex, playType, songs });
updateSong(playerData.current.song);
if (playbackType === PlaybackType.LOCAL) {
mpvPlayer!.volume(usePlayerStore.getState().volume);
@ -197,14 +193,6 @@ export const useHandlePlayQueueAdd = () => {
play();
}
remote?.updateSong({
currentTime: usePlayerStore.getState().current.time,
repeat: usePlayerStore.getState().repeat,
shuffle: usePlayerStore.getState().shuffle !== PlayerShuffle.NONE,
song: playerData.current.song,
status: PlayerStatus.PLAYING,
});
return null;
},
[play, playbackType, queryClient, server, t],

View file

@ -0,0 +1,39 @@
import isElectron from 'is-electron';
import { QueueSong } from '/@/renderer/api/types';
const remote = isElectron() ? window.electron.remote : null;
const mediaSession = navigator.mediaSession;
export const updateSong = (song: QueueSong | undefined) => {
if (mediaSession) {
let metadata: MediaMetadata;
if (song?.id) {
let artwork: MediaImage[];
if (song.imageUrl) {
const image300 = song.imageUrl
?.replace(/&size=\d+/, '&size=300')
.replace(/\?width=\d+/, '?width=300')
.replace(/&height=\d+/, '&height=300');
artwork = [{ sizes: '300x300', src: image300, type: 'image/png' }];
} else {
artwork = [];
}
metadata = new MediaMetadata({
album: song.album ?? '',
artist: song.artistName,
artwork,
title: song.name,
});
} else {
metadata = new MediaMetadata();
}
mediaSession.metadata = metadata;
}
remote?.updateSong(song);
};

View file

@ -211,8 +211,7 @@ export type GridCardData = {
route: CardRoute;
};
export type SongUpdate = {
currentTime?: number;
export type SongState = {
repeat?: PlayerRepeat;
shuffle?: boolean;
song?: QueueSong;