mirror of
https://github.com/jeffvli/feishin.git
synced 2024-11-20 06:27:09 +01:00
simplify remote/media session (#632)
This commit is contained in:
parent
d57b4b4b68
commit
110a1a63f0
@ -8,9 +8,10 @@ import { app, ipcMain } from 'electron';
|
||||
import { Server as WsServer, WebSocketServer, WebSocket } from 'ws';
|
||||
import manifest from './manifest.json';
|
||||
import { ClientEvent, ServerEvent } from '../../../../remote/types';
|
||||
import { PlayerRepeat, SongUpdate } from '../../../../renderer/types';
|
||||
import { PlayerRepeat, PlayerStatus, SongState } from '../../../../renderer/types';
|
||||
import { getMainWindow } from '../../../main';
|
||||
import { isLinux } from '../../../utils';
|
||||
import type { QueueSong } from '/@/renderer/api/types';
|
||||
|
||||
let mprisPlayer: any | undefined;
|
||||
|
||||
@ -100,9 +101,7 @@ enum Encoding {
|
||||
const GZIP_REGEX = /\bgzip\b/;
|
||||
const ZLIB_REGEX = /bdeflate\b/;
|
||||
|
||||
let currentSong: SongUpdate = {
|
||||
currentTime: 0,
|
||||
};
|
||||
const currentState: SongState = {};
|
||||
|
||||
const getEncoding = (encoding: string | string[]): Encoding => {
|
||||
const encodingArray = Array.isArray(encoding) ? encoding : [encoding];
|
||||
@ -388,7 +387,7 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
||||
break;
|
||||
}
|
||||
case 'proxy': {
|
||||
const toFetch = currentSong.song?.imageUrl?.replaceAll(
|
||||
const toFetch = currentState.song?.imageUrl?.replaceAll(
|
||||
/&(size|width|height=\d+)/g,
|
||||
'',
|
||||
);
|
||||
@ -438,9 +437,9 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
||||
volume = 0;
|
||||
}
|
||||
|
||||
currentSong.volume = volume;
|
||||
currentState.volume = volume;
|
||||
|
||||
broadcast({ data: { volume }, event: 'song' });
|
||||
broadcast({ data: volume, event: 'volume' });
|
||||
getMainWindow()?.webContents.send('request-volume', {
|
||||
volume,
|
||||
});
|
||||
@ -452,22 +451,22 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
||||
}
|
||||
case 'favorite': {
|
||||
const { favorite, id } = json;
|
||||
if (id && id === currentSong.song?.id) {
|
||||
if (id && id === currentState.song?.id) {
|
||||
getMainWindow()?.webContents.send('request-favorite', {
|
||||
favorite,
|
||||
id,
|
||||
serverId: currentSong.song.serverId,
|
||||
serverId: currentState.song.serverId,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'rating': {
|
||||
const { rating, id } = json;
|
||||
if (id && id === currentSong.song?.id) {
|
||||
if (id && id === currentState.song?.id) {
|
||||
getMainWindow()?.webContents.send('request-rating', {
|
||||
id,
|
||||
rating,
|
||||
serverId: currentSong.song.serverId,
|
||||
serverId: currentState.song.serverId,
|
||||
});
|
||||
}
|
||||
break;
|
||||
@ -482,7 +481,7 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
||||
ws.alive = true;
|
||||
});
|
||||
|
||||
ws.send(JSON.stringify({ data: currentSong, event: 'song' }));
|
||||
ws.send(JSON.stringify({ data: currentState, event: 'state' }));
|
||||
});
|
||||
|
||||
const heartBeat = setInterval(() => {
|
||||
@ -564,13 +563,13 @@ ipcMain.on('remote-username', (_event, username: string) => {
|
||||
});
|
||||
|
||||
ipcMain.on('update-favorite', (_event, favorite: boolean, serverId: string, ids: string[]) => {
|
||||
if (currentSong.song?.serverId !== serverId) return;
|
||||
if (currentState.song?.serverId !== serverId) return;
|
||||
|
||||
const id = currentSong.song.id;
|
||||
const id = currentState.song.id;
|
||||
|
||||
for (const songId of ids) {
|
||||
if (songId === id) {
|
||||
currentSong.song.userFavorite = favorite;
|
||||
currentState.song.userFavorite = favorite;
|
||||
broadcast({ data: { favorite, id: songId }, event: 'favorite' });
|
||||
return;
|
||||
}
|
||||
@ -578,13 +577,13 @@ ipcMain.on('update-favorite', (_event, favorite: boolean, serverId: string, ids:
|
||||
});
|
||||
|
||||
ipcMain.on('update-rating', (_event, rating: number, serverId: string, ids: string[]) => {
|
||||
if (currentSong.song?.serverId !== serverId) return;
|
||||
if (currentState.song?.serverId !== serverId) return;
|
||||
|
||||
const id = currentSong.song.id;
|
||||
const id = currentState.song.id;
|
||||
|
||||
for (const songId of ids) {
|
||||
if (songId === id) {
|
||||
currentSong.song.userRating = rating;
|
||||
currentState.song.userRating = rating;
|
||||
broadcast({ data: { id: songId, rating }, event: 'rating' });
|
||||
return;
|
||||
}
|
||||
@ -592,42 +591,32 @@ ipcMain.on('update-rating', (_event, rating: number, serverId: string, ids: stri
|
||||
});
|
||||
|
||||
ipcMain.on('update-repeat', (_event, repeat: PlayerRepeat) => {
|
||||
currentSong.repeat = repeat;
|
||||
broadcast({ data: { repeat }, event: 'song' });
|
||||
currentState.repeat = repeat;
|
||||
broadcast({ data: repeat, event: 'repeat' });
|
||||
});
|
||||
|
||||
ipcMain.on('update-shuffle', (_event, shuffle: boolean) => {
|
||||
currentSong.shuffle = shuffle;
|
||||
broadcast({ data: { shuffle }, event: 'song' });
|
||||
currentState.shuffle = shuffle;
|
||||
broadcast({ data: shuffle, event: 'shuffle' });
|
||||
});
|
||||
|
||||
ipcMain.on('update-song', (_event, data: SongUpdate) => {
|
||||
const { song, ...rest } = data;
|
||||
const songChanged = song?.id !== currentSong.song?.id;
|
||||
ipcMain.on('update-playback', (_event, status: PlayerStatus) => {
|
||||
currentState.status = status;
|
||||
broadcast({ data: status, event: 'playback' });
|
||||
});
|
||||
|
||||
if (!song?.id) {
|
||||
currentSong = {
|
||||
...currentSong,
|
||||
...data,
|
||||
song: undefined,
|
||||
};
|
||||
} else {
|
||||
currentSong = {
|
||||
...currentSong,
|
||||
...data,
|
||||
};
|
||||
}
|
||||
ipcMain.on('update-song', (_event, song: QueueSong | undefined) => {
|
||||
const songChanged = song?.id !== currentState.song?.id;
|
||||
currentState.song = song;
|
||||
|
||||
if (songChanged) {
|
||||
broadcast({ data: { ...rest, song: song || null }, event: 'song' });
|
||||
} else {
|
||||
broadcast({ data: rest, event: 'song' });
|
||||
broadcast({ data: song || null, event: 'song' });
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('update-volume', (_event, volume: number) => {
|
||||
currentSong.volume = volume;
|
||||
broadcast({ data: { volume }, event: 'song' });
|
||||
currentState.volume = volume;
|
||||
broadcast({ data: volume, event: 'volume' });
|
||||
});
|
||||
|
||||
if (mprisPlayer) {
|
||||
@ -639,13 +628,13 @@ if (mprisPlayer) {
|
||||
? PlayerRepeat.ONE
|
||||
: PlayerRepeat.NONE;
|
||||
|
||||
currentSong.repeat = repeat;
|
||||
broadcast({ data: { repeat }, event: 'song' });
|
||||
currentState.repeat = repeat;
|
||||
broadcast({ data: repeat, event: 'repeat' });
|
||||
});
|
||||
|
||||
mprisPlayer.on('shuffle', (shuffle: boolean) => {
|
||||
currentSong.shuffle = shuffle;
|
||||
broadcast({ data: { shuffle }, event: 'song' });
|
||||
currentState.shuffle = shuffle;
|
||||
broadcast({ data: shuffle, event: 'shuffle' });
|
||||
});
|
||||
|
||||
mprisPlayer.on('volume', (vol: number) => {
|
||||
@ -656,7 +645,7 @@ if (mprisPlayer) {
|
||||
} else if (volume < 0) {
|
||||
volume = 0;
|
||||
}
|
||||
currentSong.volume = volume;
|
||||
broadcast({ data: { volume }, event: 'song' });
|
||||
currentState.volume = volume;
|
||||
broadcast({ data: volume, event: 'volume' });
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import Player from 'mpris-service';
|
||||
import { PlayerRepeat, PlayerStatus, SongUpdate } from '../../../renderer/types';
|
||||
import { PlayerRepeat, PlayerStatus } from '../../../renderer/types';
|
||||
import { getMainWindow } from '../../main';
|
||||
import { QueueSong } from '/@/renderer/api/types';
|
||||
|
||||
const mprisPlayer = Player({
|
||||
identity: 'Feishin',
|
||||
@ -117,6 +118,10 @@ ipcMain.on('update-volume', (_event, volume) => {
|
||||
mprisPlayer.volume = Number(volume) / 100;
|
||||
});
|
||||
|
||||
ipcMain.on('update-playback', (_event, status: PlayerStatus) => {
|
||||
mprisPlayer.playbackStatus = status === PlayerStatus.PLAYING ? 'Playing' : 'Paused';
|
||||
});
|
||||
|
||||
const REPEAT_TO_MPRIS: Record<PlayerRepeat, string> = {
|
||||
[PlayerRepeat.ALL]: 'Playlist',
|
||||
[PlayerRepeat.ONE]: 'Track',
|
||||
@ -131,21 +136,9 @@ ipcMain.on('update-shuffle', (_event, shuffle: boolean) => {
|
||||
mprisPlayer.shuffle = shuffle;
|
||||
});
|
||||
|
||||
ipcMain.on('update-song', (_event, args: SongUpdate) => {
|
||||
const { song, status, repeat, shuffle } = args || {};
|
||||
|
||||
ipcMain.on('update-song', (_event, song: QueueSong | undefined) => {
|
||||
try {
|
||||
mprisPlayer.playbackStatus = status === PlayerStatus.PLAYING ? 'Playing' : 'Paused';
|
||||
|
||||
if (repeat) {
|
||||
mprisPlayer.loopStatus = REPEAT_TO_MPRIS[repeat];
|
||||
}
|
||||
|
||||
if (shuffle) {
|
||||
mprisPlayer.shuffle = shuffle;
|
||||
}
|
||||
|
||||
if (!song) {
|
||||
if (!song?.id) {
|
||||
mprisPlayer.metadata = {};
|
||||
return;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { IpcRendererEvent, ipcRenderer } from 'electron';
|
||||
import { SongUpdate } from '/@/renderer/types';
|
||||
import { QueueSong } from '/@/renderer/api/types';
|
||||
import { PlayerStatus } from '/@/renderer/types';
|
||||
|
||||
const requestFavorite = (
|
||||
cb: (
|
||||
@ -46,6 +47,10 @@ const updatePassword = (password: string) => {
|
||||
ipcRenderer.send('remote-password', password);
|
||||
};
|
||||
|
||||
const updatePlayback = (playback: PlayerStatus) => {
|
||||
ipcRenderer.send('update-playback', playback);
|
||||
};
|
||||
|
||||
const updateSetting = (
|
||||
enabled: boolean,
|
||||
port: number,
|
||||
@ -67,7 +72,7 @@ const updateShuffle = (shuffle: boolean) => {
|
||||
ipcRenderer.send('update-shuffle', shuffle);
|
||||
};
|
||||
|
||||
const updateSong = (args: SongUpdate) => {
|
||||
const updateSong = (args: QueueSong | undefined) => {
|
||||
ipcRenderer.send('update-song', args);
|
||||
};
|
||||
|
||||
@ -89,6 +94,7 @@ export const remote = {
|
||||
setRemotePort,
|
||||
updateFavorite,
|
||||
updatePassword,
|
||||
updatePlayback,
|
||||
updateRating,
|
||||
updateRepeat,
|
||||
updateSetting,
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
import { PlayerRepeat, PlayerStatus } from '/@/renderer/types';
|
||||
import { WrapperSlider } from '/@/remote/components/wrapped-slider';
|
||||
import { Tooltip } from '/@/renderer/components/tooltip';
|
||||
import { Rating } from '/@/renderer/components';
|
||||
import { Rating } from '/@/renderer/components/rating';
|
||||
|
||||
export const RemoteContainer = () => {
|
||||
const { repeat, shuffle, song, status, volume } = useInfo();
|
||||
@ -38,7 +38,7 @@ export const RemoteContainer = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{song && (
|
||||
{id && (
|
||||
<>
|
||||
<Title order={1}>{song.name}</Title>
|
||||
<Group align="flex-end">
|
||||
@ -61,7 +61,7 @@ export const RemoteContainer = () => {
|
||||
spacing={0}
|
||||
>
|
||||
<RemoteButton
|
||||
disabled={!song}
|
||||
disabled={!id}
|
||||
tooltip="Previous track"
|
||||
variant="default"
|
||||
onClick={() => send({ event: 'previous' })}
|
||||
@ -69,8 +69,8 @@ export const RemoteContainer = () => {
|
||||
<RiSkipBackFill size={25} />
|
||||
</RemoteButton>
|
||||
<RemoteButton
|
||||
disabled={!song}
|
||||
tooltip={song && status === PlayerStatus.PLAYING ? 'Pause' : 'Play'}
|
||||
disabled={!id}
|
||||
tooltip={id && status === PlayerStatus.PLAYING ? 'Pause' : 'Play'}
|
||||
variant="default"
|
||||
onClick={() => {
|
||||
if (status === PlayerStatus.PLAYING) {
|
||||
@ -80,14 +80,14 @@ export const RemoteContainer = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{song && status === PlayerStatus.PLAYING ? (
|
||||
{id && status === PlayerStatus.PLAYING ? (
|
||||
<RiPauseFill size={25} />
|
||||
) : (
|
||||
<RiPlayFill size={25} />
|
||||
)}
|
||||
</RemoteButton>
|
||||
<RemoteButton
|
||||
disabled={!song}
|
||||
disabled={!id}
|
||||
tooltip="Next track"
|
||||
variant="default"
|
||||
onClick={() => send({ event: 'next' })}
|
||||
@ -127,7 +127,7 @@ export const RemoteContainer = () => {
|
||||
</RemoteButton>
|
||||
<RemoteButton
|
||||
$active={song?.userFavorite}
|
||||
disabled={!song}
|
||||
disabled={!id}
|
||||
tooltip={song?.userFavorite ? 'Unfavorite' : 'Favorite'}
|
||||
variant="default"
|
||||
onClick={() => {
|
||||
|
@ -4,7 +4,7 @@ import merge from 'lodash/merge';
|
||||
import { create } from 'zustand';
|
||||
import { devtools, persist } from 'zustand/middleware';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
import { ClientEvent, ServerEvent, SongUpdateSocket } from '/@/remote/types';
|
||||
import type { ClientEvent, ServerEvent, SongUpdateSocket } from '/@/remote/types';
|
||||
|
||||
interface StatefulWebSocket extends WebSocket {
|
||||
natural: boolean;
|
||||
@ -133,6 +133,12 @@ export const useRemoteStore = create<SettingsSlice>()(
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'playback': {
|
||||
set((state) => {
|
||||
state.info.status = data;
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'proxy': {
|
||||
set((state) => {
|
||||
if (state.info.song) {
|
||||
@ -149,9 +155,34 @@ export const useRemoteStore = create<SettingsSlice>()(
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'repeat': {
|
||||
set((state) => {
|
||||
state.info.repeat = data;
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'shuffle': {
|
||||
set((state) => {
|
||||
state.info.shuffle = data;
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'song': {
|
||||
set((nested) => {
|
||||
nested.info = { ...nested.info, ...data };
|
||||
set((state) => {
|
||||
console.log(data);
|
||||
state.info.song = data;
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'state': {
|
||||
set((state) => {
|
||||
state.info = data;
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'volume': {
|
||||
set((state) => {
|
||||
state.info.volume = data;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -212,11 +243,9 @@ export const useRemoteStore = create<SettingsSlice>()(
|
||||
{ name: 'store_settings' },
|
||||
),
|
||||
{
|
||||
merge: (persistedState, currentState) => {
|
||||
return merge(currentState, persistedState);
|
||||
},
|
||||
merge: (persistedState, currentState) => merge(currentState, persistedState),
|
||||
name: 'store_settings',
|
||||
version: 6,
|
||||
version: 7,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { QueueSong } from '/@/renderer/api/types';
|
||||
import type { SongUpdate } from '/@/renderer/types';
|
||||
import type { PlayerRepeat, PlayerStatus, SongState } from '/@/renderer/types';
|
||||
|
||||
export interface SongUpdateSocket extends Omit<SongUpdate, 'song'> {
|
||||
export interface SongUpdateSocket extends Omit<SongState, 'song'> {
|
||||
song?: QueueSong | null;
|
||||
}
|
||||
|
||||
@ -15,6 +15,11 @@ export interface ServerFavorite {
|
||||
event: 'favorite';
|
||||
}
|
||||
|
||||
export interface ServerPlayStatus {
|
||||
data: PlayerStatus;
|
||||
event: 'playback';
|
||||
}
|
||||
|
||||
export interface ServerProxy {
|
||||
data: string;
|
||||
event: 'proxy';
|
||||
@ -25,12 +30,42 @@ export interface ServerRating {
|
||||
event: 'rating';
|
||||
}
|
||||
|
||||
export interface ServerRepeat {
|
||||
data: PlayerRepeat;
|
||||
event: 'repeat';
|
||||
}
|
||||
|
||||
export interface ServerShuffle {
|
||||
data: boolean;
|
||||
event: 'shuffle';
|
||||
}
|
||||
|
||||
export interface ServerSong {
|
||||
data: SongUpdateSocket;
|
||||
data: QueueSong | null;
|
||||
event: 'song';
|
||||
}
|
||||
|
||||
export type ServerEvent = ServerError | ServerFavorite | ServerRating | ServerSong | ServerProxy;
|
||||
export interface ServerState {
|
||||
data: SongState;
|
||||
event: 'state';
|
||||
}
|
||||
|
||||
export interface ServerVolume {
|
||||
data: number;
|
||||
event: 'volume';
|
||||
}
|
||||
|
||||
export type ServerEvent =
|
||||
| ServerError
|
||||
| ServerFavorite
|
||||
| ServerPlayStatus
|
||||
| ServerRating
|
||||
| ServerRepeat
|
||||
| ServerShuffle
|
||||
| ServerSong
|
||||
| ServerState
|
||||
| ServerProxy
|
||||
| ServerVolume;
|
||||
|
||||
export interface ClientSimpleEvent {
|
||||
event: 'next' | 'pause' | 'play' | 'previous' | 'proxy' | 'repeat' | 'shuffle';
|
||||
|
@ -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(
|
||||
(
|
||||
|
@ -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]);
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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 (
|
||||
|
@ -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();
|
||||
},
|
||||
};
|
||||
|
@ -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],
|
||||
|
39
src/renderer/features/player/update-remote-song.tsx
Normal file
39
src/renderer/features/player/update-remote-song.tsx
Normal 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);
|
||||
};
|
@ -211,8 +211,7 @@ export type GridCardData = {
|
||||
route: CardRoute;
|
||||
};
|
||||
|
||||
export type SongUpdate = {
|
||||
currentTime?: number;
|
||||
export type SongState = {
|
||||
repeat?: PlayerRepeat;
|
||||
shuffle?: boolean;
|
||||
song?: QueueSong;
|
||||
|
Loading…
Reference in New Issue
Block a user