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 { Server as WsServer, WebSocketServer, WebSocket } from 'ws';
|
||||||
import manifest from './manifest.json';
|
import manifest from './manifest.json';
|
||||||
import { ClientEvent, ServerEvent } from '../../../../remote/types';
|
import { ClientEvent, ServerEvent } from '../../../../remote/types';
|
||||||
import { PlayerRepeat, SongUpdate } from '../../../../renderer/types';
|
import { PlayerRepeat, PlayerStatus, SongState } from '../../../../renderer/types';
|
||||||
import { getMainWindow } from '../../../main';
|
import { getMainWindow } from '../../../main';
|
||||||
import { isLinux } from '../../../utils';
|
import { isLinux } from '../../../utils';
|
||||||
|
import type { QueueSong } from '/@/renderer/api/types';
|
||||||
|
|
||||||
let mprisPlayer: any | undefined;
|
let mprisPlayer: any | undefined;
|
||||||
|
|
||||||
@ -100,9 +101,7 @@ enum Encoding {
|
|||||||
const GZIP_REGEX = /\bgzip\b/;
|
const GZIP_REGEX = /\bgzip\b/;
|
||||||
const ZLIB_REGEX = /bdeflate\b/;
|
const ZLIB_REGEX = /bdeflate\b/;
|
||||||
|
|
||||||
let currentSong: SongUpdate = {
|
const currentState: SongState = {};
|
||||||
currentTime: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const getEncoding = (encoding: string | string[]): Encoding => {
|
const getEncoding = (encoding: string | string[]): Encoding => {
|
||||||
const encodingArray = Array.isArray(encoding) ? encoding : [encoding];
|
const encodingArray = Array.isArray(encoding) ? encoding : [encoding];
|
||||||
@ -388,7 +387,7 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'proxy': {
|
case 'proxy': {
|
||||||
const toFetch = currentSong.song?.imageUrl?.replaceAll(
|
const toFetch = currentState.song?.imageUrl?.replaceAll(
|
||||||
/&(size|width|height=\d+)/g,
|
/&(size|width|height=\d+)/g,
|
||||||
'',
|
'',
|
||||||
);
|
);
|
||||||
@ -438,9 +437,9 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
|||||||
volume = 0;
|
volume = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentSong.volume = volume;
|
currentState.volume = volume;
|
||||||
|
|
||||||
broadcast({ data: { volume }, event: 'song' });
|
broadcast({ data: volume, event: 'volume' });
|
||||||
getMainWindow()?.webContents.send('request-volume', {
|
getMainWindow()?.webContents.send('request-volume', {
|
||||||
volume,
|
volume,
|
||||||
});
|
});
|
||||||
@ -452,22 +451,22 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
|||||||
}
|
}
|
||||||
case 'favorite': {
|
case 'favorite': {
|
||||||
const { favorite, id } = json;
|
const { favorite, id } = json;
|
||||||
if (id && id === currentSong.song?.id) {
|
if (id && id === currentState.song?.id) {
|
||||||
getMainWindow()?.webContents.send('request-favorite', {
|
getMainWindow()?.webContents.send('request-favorite', {
|
||||||
favorite,
|
favorite,
|
||||||
id,
|
id,
|
||||||
serverId: currentSong.song.serverId,
|
serverId: currentState.song.serverId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'rating': {
|
case 'rating': {
|
||||||
const { rating, id } = json;
|
const { rating, id } = json;
|
||||||
if (id && id === currentSong.song?.id) {
|
if (id && id === currentState.song?.id) {
|
||||||
getMainWindow()?.webContents.send('request-rating', {
|
getMainWindow()?.webContents.send('request-rating', {
|
||||||
id,
|
id,
|
||||||
rating,
|
rating,
|
||||||
serverId: currentSong.song.serverId,
|
serverId: currentState.song.serverId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -482,7 +481,7 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
|||||||
ws.alive = true;
|
ws.alive = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.send(JSON.stringify({ data: currentSong, event: 'song' }));
|
ws.send(JSON.stringify({ data: currentState, event: 'state' }));
|
||||||
});
|
});
|
||||||
|
|
||||||
const heartBeat = setInterval(() => {
|
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[]) => {
|
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) {
|
for (const songId of ids) {
|
||||||
if (songId === id) {
|
if (songId === id) {
|
||||||
currentSong.song.userFavorite = favorite;
|
currentState.song.userFavorite = favorite;
|
||||||
broadcast({ data: { favorite, id: songId }, event: 'favorite' });
|
broadcast({ data: { favorite, id: songId }, event: 'favorite' });
|
||||||
return;
|
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[]) => {
|
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) {
|
for (const songId of ids) {
|
||||||
if (songId === id) {
|
if (songId === id) {
|
||||||
currentSong.song.userRating = rating;
|
currentState.song.userRating = rating;
|
||||||
broadcast({ data: { id: songId, rating }, event: 'rating' });
|
broadcast({ data: { id: songId, rating }, event: 'rating' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -592,42 +591,32 @@ ipcMain.on('update-rating', (_event, rating: number, serverId: string, ids: stri
|
|||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('update-repeat', (_event, repeat: PlayerRepeat) => {
|
ipcMain.on('update-repeat', (_event, repeat: PlayerRepeat) => {
|
||||||
currentSong.repeat = repeat;
|
currentState.repeat = repeat;
|
||||||
broadcast({ data: { repeat }, event: 'song' });
|
broadcast({ data: repeat, event: 'repeat' });
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('update-shuffle', (_event, shuffle: boolean) => {
|
ipcMain.on('update-shuffle', (_event, shuffle: boolean) => {
|
||||||
currentSong.shuffle = shuffle;
|
currentState.shuffle = shuffle;
|
||||||
broadcast({ data: { shuffle }, event: 'song' });
|
broadcast({ data: shuffle, event: 'shuffle' });
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('update-song', (_event, data: SongUpdate) => {
|
ipcMain.on('update-playback', (_event, status: PlayerStatus) => {
|
||||||
const { song, ...rest } = data;
|
currentState.status = status;
|
||||||
const songChanged = song?.id !== currentSong.song?.id;
|
broadcast({ data: status, event: 'playback' });
|
||||||
|
});
|
||||||
|
|
||||||
if (!song?.id) {
|
ipcMain.on('update-song', (_event, song: QueueSong | undefined) => {
|
||||||
currentSong = {
|
const songChanged = song?.id !== currentState.song?.id;
|
||||||
...currentSong,
|
currentState.song = song;
|
||||||
...data,
|
|
||||||
song: undefined,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
currentSong = {
|
|
||||||
...currentSong,
|
|
||||||
...data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (songChanged) {
|
if (songChanged) {
|
||||||
broadcast({ data: { ...rest, song: song || null }, event: 'song' });
|
broadcast({ data: song || null, event: 'song' });
|
||||||
} else {
|
|
||||||
broadcast({ data: rest, event: 'song' });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('update-volume', (_event, volume: number) => {
|
ipcMain.on('update-volume', (_event, volume: number) => {
|
||||||
currentSong.volume = volume;
|
currentState.volume = volume;
|
||||||
broadcast({ data: { volume }, event: 'song' });
|
broadcast({ data: volume, event: 'volume' });
|
||||||
});
|
});
|
||||||
|
|
||||||
if (mprisPlayer) {
|
if (mprisPlayer) {
|
||||||
@ -639,13 +628,13 @@ if (mprisPlayer) {
|
|||||||
? PlayerRepeat.ONE
|
? PlayerRepeat.ONE
|
||||||
: PlayerRepeat.NONE;
|
: PlayerRepeat.NONE;
|
||||||
|
|
||||||
currentSong.repeat = repeat;
|
currentState.repeat = repeat;
|
||||||
broadcast({ data: { repeat }, event: 'song' });
|
broadcast({ data: repeat, event: 'repeat' });
|
||||||
});
|
});
|
||||||
|
|
||||||
mprisPlayer.on('shuffle', (shuffle: boolean) => {
|
mprisPlayer.on('shuffle', (shuffle: boolean) => {
|
||||||
currentSong.shuffle = shuffle;
|
currentState.shuffle = shuffle;
|
||||||
broadcast({ data: { shuffle }, event: 'song' });
|
broadcast({ data: shuffle, event: 'shuffle' });
|
||||||
});
|
});
|
||||||
|
|
||||||
mprisPlayer.on('volume', (vol: number) => {
|
mprisPlayer.on('volume', (vol: number) => {
|
||||||
@ -656,7 +645,7 @@ if (mprisPlayer) {
|
|||||||
} else if (volume < 0) {
|
} else if (volume < 0) {
|
||||||
volume = 0;
|
volume = 0;
|
||||||
}
|
}
|
||||||
currentSong.volume = volume;
|
currentState.volume = volume;
|
||||||
broadcast({ data: { volume }, event: 'song' });
|
broadcast({ data: volume, event: 'volume' });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { ipcMain } from 'electron';
|
import { ipcMain } from 'electron';
|
||||||
import Player from 'mpris-service';
|
import Player from 'mpris-service';
|
||||||
import { PlayerRepeat, PlayerStatus, SongUpdate } from '../../../renderer/types';
|
import { PlayerRepeat, PlayerStatus } from '../../../renderer/types';
|
||||||
import { getMainWindow } from '../../main';
|
import { getMainWindow } from '../../main';
|
||||||
|
import { QueueSong } from '/@/renderer/api/types';
|
||||||
|
|
||||||
const mprisPlayer = Player({
|
const mprisPlayer = Player({
|
||||||
identity: 'Feishin',
|
identity: 'Feishin',
|
||||||
@ -117,6 +118,10 @@ ipcMain.on('update-volume', (_event, volume) => {
|
|||||||
mprisPlayer.volume = Number(volume) / 100;
|
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> = {
|
const REPEAT_TO_MPRIS: Record<PlayerRepeat, string> = {
|
||||||
[PlayerRepeat.ALL]: 'Playlist',
|
[PlayerRepeat.ALL]: 'Playlist',
|
||||||
[PlayerRepeat.ONE]: 'Track',
|
[PlayerRepeat.ONE]: 'Track',
|
||||||
@ -131,21 +136,9 @@ ipcMain.on('update-shuffle', (_event, shuffle: boolean) => {
|
|||||||
mprisPlayer.shuffle = shuffle;
|
mprisPlayer.shuffle = shuffle;
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('update-song', (_event, args: SongUpdate) => {
|
ipcMain.on('update-song', (_event, song: QueueSong | undefined) => {
|
||||||
const { song, status, repeat, shuffle } = args || {};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mprisPlayer.playbackStatus = status === PlayerStatus.PLAYING ? 'Playing' : 'Paused';
|
if (!song?.id) {
|
||||||
|
|
||||||
if (repeat) {
|
|
||||||
mprisPlayer.loopStatus = REPEAT_TO_MPRIS[repeat];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shuffle) {
|
|
||||||
mprisPlayer.shuffle = shuffle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!song) {
|
|
||||||
mprisPlayer.metadata = {};
|
mprisPlayer.metadata = {};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { IpcRendererEvent, ipcRenderer } from 'electron';
|
import { IpcRendererEvent, ipcRenderer } from 'electron';
|
||||||
import { SongUpdate } from '/@/renderer/types';
|
import { QueueSong } from '/@/renderer/api/types';
|
||||||
|
import { PlayerStatus } from '/@/renderer/types';
|
||||||
|
|
||||||
const requestFavorite = (
|
const requestFavorite = (
|
||||||
cb: (
|
cb: (
|
||||||
@ -46,6 +47,10 @@ const updatePassword = (password: string) => {
|
|||||||
ipcRenderer.send('remote-password', password);
|
ipcRenderer.send('remote-password', password);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updatePlayback = (playback: PlayerStatus) => {
|
||||||
|
ipcRenderer.send('update-playback', playback);
|
||||||
|
};
|
||||||
|
|
||||||
const updateSetting = (
|
const updateSetting = (
|
||||||
enabled: boolean,
|
enabled: boolean,
|
||||||
port: number,
|
port: number,
|
||||||
@ -67,7 +72,7 @@ const updateShuffle = (shuffle: boolean) => {
|
|||||||
ipcRenderer.send('update-shuffle', shuffle);
|
ipcRenderer.send('update-shuffle', shuffle);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateSong = (args: SongUpdate) => {
|
const updateSong = (args: QueueSong | undefined) => {
|
||||||
ipcRenderer.send('update-song', args);
|
ipcRenderer.send('update-song', args);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,6 +94,7 @@ export const remote = {
|
|||||||
setRemotePort,
|
setRemotePort,
|
||||||
updateFavorite,
|
updateFavorite,
|
||||||
updatePassword,
|
updatePassword,
|
||||||
|
updatePlayback,
|
||||||
updateRating,
|
updateRating,
|
||||||
updateRepeat,
|
updateRepeat,
|
||||||
updateSetting,
|
updateSetting,
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
import { PlayerRepeat, PlayerStatus } from '/@/renderer/types';
|
import { PlayerRepeat, PlayerStatus } from '/@/renderer/types';
|
||||||
import { WrapperSlider } from '/@/remote/components/wrapped-slider';
|
import { WrapperSlider } from '/@/remote/components/wrapped-slider';
|
||||||
import { Tooltip } from '/@/renderer/components/tooltip';
|
import { Tooltip } from '/@/renderer/components/tooltip';
|
||||||
import { Rating } from '/@/renderer/components';
|
import { Rating } from '/@/renderer/components/rating';
|
||||||
|
|
||||||
export const RemoteContainer = () => {
|
export const RemoteContainer = () => {
|
||||||
const { repeat, shuffle, song, status, volume } = useInfo();
|
const { repeat, shuffle, song, status, volume } = useInfo();
|
||||||
@ -38,7 +38,7 @@ export const RemoteContainer = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{song && (
|
{id && (
|
||||||
<>
|
<>
|
||||||
<Title order={1}>{song.name}</Title>
|
<Title order={1}>{song.name}</Title>
|
||||||
<Group align="flex-end">
|
<Group align="flex-end">
|
||||||
@ -61,7 +61,7 @@ export const RemoteContainer = () => {
|
|||||||
spacing={0}
|
spacing={0}
|
||||||
>
|
>
|
||||||
<RemoteButton
|
<RemoteButton
|
||||||
disabled={!song}
|
disabled={!id}
|
||||||
tooltip="Previous track"
|
tooltip="Previous track"
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={() => send({ event: 'previous' })}
|
onClick={() => send({ event: 'previous' })}
|
||||||
@ -69,8 +69,8 @@ export const RemoteContainer = () => {
|
|||||||
<RiSkipBackFill size={25} />
|
<RiSkipBackFill size={25} />
|
||||||
</RemoteButton>
|
</RemoteButton>
|
||||||
<RemoteButton
|
<RemoteButton
|
||||||
disabled={!song}
|
disabled={!id}
|
||||||
tooltip={song && status === PlayerStatus.PLAYING ? 'Pause' : 'Play'}
|
tooltip={id && status === PlayerStatus.PLAYING ? 'Pause' : 'Play'}
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (status === PlayerStatus.PLAYING) {
|
if (status === PlayerStatus.PLAYING) {
|
||||||
@ -80,14 +80,14 @@ export const RemoteContainer = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{song && status === PlayerStatus.PLAYING ? (
|
{id && status === PlayerStatus.PLAYING ? (
|
||||||
<RiPauseFill size={25} />
|
<RiPauseFill size={25} />
|
||||||
) : (
|
) : (
|
||||||
<RiPlayFill size={25} />
|
<RiPlayFill size={25} />
|
||||||
)}
|
)}
|
||||||
</RemoteButton>
|
</RemoteButton>
|
||||||
<RemoteButton
|
<RemoteButton
|
||||||
disabled={!song}
|
disabled={!id}
|
||||||
tooltip="Next track"
|
tooltip="Next track"
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={() => send({ event: 'next' })}
|
onClick={() => send({ event: 'next' })}
|
||||||
@ -127,7 +127,7 @@ export const RemoteContainer = () => {
|
|||||||
</RemoteButton>
|
</RemoteButton>
|
||||||
<RemoteButton
|
<RemoteButton
|
||||||
$active={song?.userFavorite}
|
$active={song?.userFavorite}
|
||||||
disabled={!song}
|
disabled={!id}
|
||||||
tooltip={song?.userFavorite ? 'Unfavorite' : 'Favorite'}
|
tooltip={song?.userFavorite ? 'Unfavorite' : 'Favorite'}
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -4,7 +4,7 @@ import merge from 'lodash/merge';
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { devtools, persist } from 'zustand/middleware';
|
import { devtools, persist } from 'zustand/middleware';
|
||||||
import { immer } from 'zustand/middleware/immer';
|
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 {
|
interface StatefulWebSocket extends WebSocket {
|
||||||
natural: boolean;
|
natural: boolean;
|
||||||
@ -133,6 +133,12 @@ export const useRemoteStore = create<SettingsSlice>()(
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'playback': {
|
||||||
|
set((state) => {
|
||||||
|
state.info.status = data;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'proxy': {
|
case 'proxy': {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
if (state.info.song) {
|
if (state.info.song) {
|
||||||
@ -149,9 +155,34 @@ export const useRemoteStore = create<SettingsSlice>()(
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'repeat': {
|
||||||
|
set((state) => {
|
||||||
|
state.info.repeat = data;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'shuffle': {
|
||||||
|
set((state) => {
|
||||||
|
state.info.shuffle = data;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'song': {
|
case 'song': {
|
||||||
set((nested) => {
|
set((state) => {
|
||||||
nested.info = { ...nested.info, ...data };
|
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' },
|
{ name: 'store_settings' },
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
merge: (persistedState, currentState) => {
|
merge: (persistedState, currentState) => merge(currentState, persistedState),
|
||||||
return merge(currentState, persistedState);
|
|
||||||
},
|
|
||||||
name: 'store_settings',
|
name: 'store_settings',
|
||||||
version: 6,
|
version: 7,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { QueueSong } from '/@/renderer/api/types';
|
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;
|
song?: QueueSong | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,6 +15,11 @@ export interface ServerFavorite {
|
|||||||
event: 'favorite';
|
event: 'favorite';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ServerPlayStatus {
|
||||||
|
data: PlayerStatus;
|
||||||
|
event: 'playback';
|
||||||
|
}
|
||||||
|
|
||||||
export interface ServerProxy {
|
export interface ServerProxy {
|
||||||
data: string;
|
data: string;
|
||||||
event: 'proxy';
|
event: 'proxy';
|
||||||
@ -25,12 +30,42 @@ export interface ServerRating {
|
|||||||
event: 'rating';
|
event: 'rating';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ServerRepeat {
|
||||||
|
data: PlayerRepeat;
|
||||||
|
event: 'repeat';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerShuffle {
|
||||||
|
data: boolean;
|
||||||
|
event: 'shuffle';
|
||||||
|
}
|
||||||
|
|
||||||
export interface ServerSong {
|
export interface ServerSong {
|
||||||
data: SongUpdateSocket;
|
data: QueueSong | null;
|
||||||
event: 'song';
|
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 {
|
export interface ClientSimpleEvent {
|
||||||
event: 'next' | 'pause' | 'play' | 'previous' | 'proxy' | 'repeat' | 'shuffle';
|
event: 'next' | 'pause' | 'play' | 'previous' | 'proxy' | 'repeat' | 'shuffle';
|
||||||
|
@ -40,13 +40,13 @@ type WebAudio = {
|
|||||||
gain: GainNode;
|
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
|
// 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
|
// 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
|
// the first time. This workaround is important for Safari, which seems to require the
|
||||||
// source to be connected PRIOR to resuming audio context
|
// source to be connected PRIOR to resuming audio context
|
||||||
const EMPTY_SOURCE =
|
const EMPTY_SOURCE =
|
||||||
'data:audio/wav;base64,UklGRjIAAABXQVZFZm10IBIAAAABAAEAQB8AAEAfAAABAAgAAABmYWN0BAAAAAAAAABkYXRhAAAAAA==';
|
'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU2LjM2LjEwMAAAAAAAAAAAAAAA//OEAAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAAEAAABIADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV6urq6urq6urq6urq6urq6urq6urq6urq6v////////////////////////////////8AAAAATGF2YzU2LjQxAAAAAAAAAAAAAAAAJAAAAAAAAAAAASDs90hvAAAAAAAAAAAAAAAAAAAA//MUZAAAAAGkAAAAAAAAA0gAAAAATEFN//MUZAMAAAGkAAAAAAAAA0gAAAAARTMu//MUZAYAAAGkAAAAAAAAA0gAAAAAOTku//MUZAkAAAGkAAAAAAAAA0gAAAAANVVV';
|
||||||
|
|
||||||
export const AudioPlayer = forwardRef(
|
export const AudioPlayer = forwardRef(
|
||||||
(
|
(
|
||||||
|
@ -60,6 +60,7 @@ import {
|
|||||||
import { usePlaybackType } from '/@/renderer/store/settings.store';
|
import { usePlaybackType } from '/@/renderer/store/settings.store';
|
||||||
import { Play, PlaybackType } from '/@/renderer/types';
|
import { Play, PlaybackType } from '/@/renderer/types';
|
||||||
import { ItemDetailsModal } from '/@/renderer/features/item-details/components/item-details-modal';
|
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';
|
import { controller } from '/@/renderer/api/controller';
|
||||||
|
|
||||||
type ContextMenuContextProps = {
|
type ContextMenuContextProps = {
|
||||||
@ -89,7 +90,6 @@ const JELLYFIN_IGNORED_MENU_ITEMS: ContextMenuItemType[] = ['setRating', 'shareI
|
|||||||
// const SUBSONIC_IGNORED_MENU_ITEMS: ContextMenuItemType[] = [];
|
// const SUBSONIC_IGNORED_MENU_ITEMS: ContextMenuItemType[] = [];
|
||||||
|
|
||||||
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
||||||
const remote = isElectron() ? window.electron.remote : null;
|
|
||||||
|
|
||||||
export interface ContextMenuProviderProps {
|
export interface ContextMenuProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -643,7 +643,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
|||||||
ctx.tableApi?.redrawRows();
|
ctx.tableApi?.redrawRows();
|
||||||
|
|
||||||
if (isCurrentSongRemoved) {
|
if (isCurrentSongRemoved) {
|
||||||
remote?.updateSong({ song: playerData.current.song });
|
updateSong(playerData.current.song);
|
||||||
}
|
}
|
||||||
}, [ctx.dataNodes, ctx.tableApi, playbackType, removeFromQueue]);
|
}, [ctx.dataNodes, ctx.tableApi, playbackType, removeFromQueue]);
|
||||||
|
|
||||||
|
@ -14,13 +14,13 @@ import {
|
|||||||
} from 'react-icons/ri';
|
} from 'react-icons/ri';
|
||||||
import { Song } from '/@/renderer/api/types';
|
import { Song } from '/@/renderer/api/types';
|
||||||
import { usePlayerControls, useQueueControls } from '/@/renderer/store';
|
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 { usePlaybackType } from '/@/renderer/store/settings.store';
|
||||||
import { usePlayerStore, useSetCurrentTime } from '../../../store/player.store';
|
import { usePlayerStore, useSetCurrentTime } from '../../../store/player.store';
|
||||||
import { TableConfigDropdown } from '/@/renderer/components/virtual-table';
|
import { TableConfigDropdown } from '/@/renderer/components/virtual-table';
|
||||||
|
import { updateSong } from '/@/renderer/features/player/update-remote-song';
|
||||||
|
|
||||||
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
||||||
const remote = isElectron() ? window.electron.remote : null;
|
|
||||||
|
|
||||||
interface PlayQueueListOptionsProps {
|
interface PlayQueueListOptionsProps {
|
||||||
tableRef: MutableRefObject<{ grid: AgGridReactType<Song> } | null>;
|
tableRef: MutableRefObject<{ grid: AgGridReactType<Song> } | null>;
|
||||||
@ -79,7 +79,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isCurrentSongRemoved) {
|
if (isCurrentSongRemoved) {
|
||||||
remote?.updateSong({ song: playerData.current.song });
|
updateSong(playerData.current.song);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ export const PlayQueueListControls = ({ type, tableRef }: PlayQueueListOptionsPr
|
|||||||
mpvPlayer!.pause();
|
mpvPlayer!.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
remote?.updateSong({ song: undefined, status: PlayerStatus.PAUSED });
|
updateSong(undefined);
|
||||||
|
|
||||||
setCurrentTime(0);
|
setCurrentTime(0);
|
||||||
pause();
|
pause();
|
||||||
|
@ -30,16 +30,16 @@ import debounce from 'lodash/debounce';
|
|||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
|
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
|
||||||
import { ErrorFallback } from '/@/renderer/features/action-required';
|
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 { LibraryItem, QueueSong } from '/@/renderer/api/types';
|
||||||
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
|
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
|
||||||
import { QUEUE_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
import { QUEUE_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
||||||
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
|
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
|
||||||
import { useAppFocus } from '/@/renderer/hooks';
|
import { useAppFocus } from '/@/renderer/hooks';
|
||||||
import { PlayersRef } from '/@/renderer/features/player/ref/players-ref';
|
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 mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
||||||
const remote = isElectron() ? window.electron.remote : null;
|
|
||||||
|
|
||||||
type QueueProps = {
|
type QueueProps = {
|
||||||
type: TableType;
|
type: TableType;
|
||||||
@ -82,11 +82,7 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref<any>) => {
|
|||||||
|
|
||||||
const handleDoubleClick = (e: CellDoubleClickedEvent) => {
|
const handleDoubleClick = (e: CellDoubleClickedEvent) => {
|
||||||
const playerData = setCurrentTrack(e.data.uniqueId);
|
const playerData = setCurrentTrack(e.data.uniqueId);
|
||||||
remote?.updateSong({
|
updateSong(playerData.current.song);
|
||||||
currentTime: 0,
|
|
||||||
song: playerData.current.song,
|
|
||||||
status: PlayerStatus.PLAYING,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (playbackType === PlaybackType.LOCAL) {
|
if (playbackType === PlaybackType.LOCAL) {
|
||||||
mpvPlayer!.volume(volume);
|
mpvPlayer!.volume(volume);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import isElectron from 'is-electron';
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { usePlaybackType, useSettingsStore } from '/@/renderer/store/settings.store';
|
import { usePlaybackType, useSettingsStore } from '/@/renderer/store/settings.store';
|
||||||
import { PlaybackType } from '/@/renderer/types';
|
import { PlaybackType } from '/@/renderer/types';
|
||||||
@ -17,6 +16,7 @@ import { CenterControls } from './center-controls';
|
|||||||
import { LeftControls } from './left-controls';
|
import { LeftControls } from './left-controls';
|
||||||
import { RightControls } from './right-controls';
|
import { RightControls } from './right-controls';
|
||||||
import { PlayersRef } from '/@/renderer/features/player/ref/players-ref';
|
import { PlayersRef } from '/@/renderer/features/player/ref/players-ref';
|
||||||
|
import { updateSong } from '/@/renderer/features/player/update-remote-song';
|
||||||
|
|
||||||
const PlayerbarContainer = styled.div`
|
const PlayerbarContainer = styled.div`
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
@ -59,8 +59,6 @@ const CenterGridItem = styled.div`
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const remote = isElectron() ? window.electron.remote : null;
|
|
||||||
|
|
||||||
export const Playerbar = () => {
|
export const Playerbar = () => {
|
||||||
const playersRef = PlayersRef;
|
const playersRef = PlayersRef;
|
||||||
const settings = useSettingsStore((state) => state.playback);
|
const settings = useSettingsStore((state) => state.playback);
|
||||||
@ -75,13 +73,7 @@ export const Playerbar = () => {
|
|||||||
|
|
||||||
const autoNextFn = useCallback(() => {
|
const autoNextFn = useCallback(() => {
|
||||||
const playerData = autoNext();
|
const playerData = autoNext();
|
||||||
|
updateSong(playerData.current.song);
|
||||||
if (remote) {
|
|
||||||
remote.updateSong({
|
|
||||||
currentTime: 0,
|
|
||||||
song: playerData.current.song,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [autoNext]);
|
}, [autoNext]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -14,9 +14,9 @@ import {
|
|||||||
import { usePlaybackType } from '/@/renderer/store/settings.store';
|
import { usePlaybackType } from '/@/renderer/store/settings.store';
|
||||||
import { useScrobble } from '/@/renderer/features/player/hooks/use-scrobble';
|
import { useScrobble } from '/@/renderer/features/player/hooks/use-scrobble';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { QueueSong } from '/@/renderer/api/types';
|
|
||||||
import { toast } from '/@/renderer/components';
|
import { toast } from '/@/renderer/components';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { updateSong } from '/@/renderer/features/player/update-remote-song';
|
||||||
|
|
||||||
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
||||||
const mpvPlayerListener = isElectron() ? window.electron.mpvPlayerListener : 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 utils = isElectron() ? window.electron.utils : null;
|
||||||
const mpris = isElectron() && utils?.isLinux() ? window.electron.mpris : null;
|
const mpris = isElectron() && utils?.isLinux() ? window.electron.mpris : null;
|
||||||
const remote = isElectron() ? window.electron.remote : 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 }) => {
|
export const useCenterControls = (args: { playersRef: any }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -46,6 +46,23 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
|||||||
|
|
||||||
const { handleScrobbleFromSongRestart, handleScrobbleFromSeek } = useScrobble();
|
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(() => {
|
const resetPlayers = useCallback(() => {
|
||||||
if (player1Ref.getInternalPlayer()) {
|
if (player1Ref.getInternalPlayer()) {
|
||||||
player1Ref.getInternalPlayer().currentTime = 0;
|
player1Ref.getInternalPlayer().currentTime = 0;
|
||||||
@ -76,61 +93,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
|||||||
|
|
||||||
const isMpvPlayer = isElectron() && playbackType === PlaybackType.LOCAL;
|
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(() => {
|
const handlePlay = useCallback(() => {
|
||||||
mprisUpdateSong({ status: PlayerStatus.PLAYING });
|
|
||||||
|
|
||||||
if (isMpvPlayer) {
|
if (isMpvPlayer) {
|
||||||
mpvPlayer?.volume(usePlayerStore.getState().volume);
|
mpvPlayer?.volume(usePlayerStore.getState().volume);
|
||||||
mpvPlayer!.play();
|
mpvPlayer!.play();
|
||||||
@ -145,8 +108,6 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
|||||||
}, [currentPlayerRef, isMpvPlayer, play]);
|
}, [currentPlayerRef, isMpvPlayer, play]);
|
||||||
|
|
||||||
const handlePause = useCallback(() => {
|
const handlePause = useCallback(() => {
|
||||||
mprisUpdateSong({ status: PlayerStatus.PAUSED });
|
|
||||||
|
|
||||||
if (isMpvPlayer) {
|
if (isMpvPlayer) {
|
||||||
mpvPlayer!.pause();
|
mpvPlayer!.pause();
|
||||||
}
|
}
|
||||||
@ -155,8 +116,6 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
|||||||
}, [isMpvPlayer, pause]);
|
}, [isMpvPlayer, pause]);
|
||||||
|
|
||||||
const handleStop = useCallback(() => {
|
const handleStop = useCallback(() => {
|
||||||
mprisUpdateSong({ status: PlayerStatus.PAUSED });
|
|
||||||
|
|
||||||
if (isMpvPlayer) {
|
if (isMpvPlayer) {
|
||||||
mpvPlayer!.pause();
|
mpvPlayer!.pause();
|
||||||
mpvPlayer!.seekTo(0);
|
mpvPlayer!.seekTo(0);
|
||||||
@ -212,13 +171,13 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
|||||||
const handleRepeatAll = {
|
const handleRepeatAll = {
|
||||||
local: () => {
|
local: () => {
|
||||||
const playerData = autoNext();
|
const playerData = autoNext();
|
||||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
|
updateSong(playerData.current.song);
|
||||||
mpvPlayer!.autoNext(playerData);
|
mpvPlayer!.autoNext(playerData);
|
||||||
play();
|
play();
|
||||||
},
|
},
|
||||||
web: () => {
|
web: () => {
|
||||||
const playerData = autoNext();
|
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: () => {
|
local: () => {
|
||||||
if (isLastTrack) {
|
if (isLastTrack) {
|
||||||
const playerData = setCurrentIndex(0);
|
const playerData = setCurrentIndex(0);
|
||||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PAUSED });
|
updateSong(playerData.current.song);
|
||||||
mpvPlayer!.setQueue(playerData, true);
|
mpvPlayer!.setQueue(playerData, true);
|
||||||
pause();
|
pause();
|
||||||
} else {
|
} else {
|
||||||
const playerData = autoNext();
|
const playerData = autoNext();
|
||||||
mprisUpdateSong({
|
updateSong(playerData.current.song);
|
||||||
song: playerData.current.song,
|
|
||||||
status: PlayerStatus.PLAYING,
|
|
||||||
});
|
|
||||||
mpvPlayer!.autoNext(playerData);
|
mpvPlayer!.autoNext(playerData);
|
||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
@ -242,14 +198,10 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
|||||||
web: () => {
|
web: () => {
|
||||||
if (isLastTrack) {
|
if (isLastTrack) {
|
||||||
resetPlayers();
|
resetPlayers();
|
||||||
mprisUpdateSong({ status: PlayerStatus.PAUSED });
|
|
||||||
pause();
|
pause();
|
||||||
} else {
|
} else {
|
||||||
const playerData = autoNext();
|
const playerData = autoNext();
|
||||||
mprisUpdateSong({
|
updateSong(playerData.current.song);
|
||||||
song: playerData.current.song,
|
|
||||||
status: PlayerStatus.PLAYING,
|
|
||||||
});
|
|
||||||
resetPlayers();
|
resetPlayers();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -258,20 +210,15 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
|||||||
const handleRepeatOne = {
|
const handleRepeatOne = {
|
||||||
local: () => {
|
local: () => {
|
||||||
const playerData = autoNext();
|
const playerData = autoNext();
|
||||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PLAYING });
|
updateSong(playerData.current.song);
|
||||||
mpvPlayer!.autoNext(playerData);
|
mpvPlayer!.autoNext(playerData);
|
||||||
play();
|
play();
|
||||||
},
|
},
|
||||||
web: () => {
|
web: () => {
|
||||||
if (isLastTrack) {
|
if (isLastTrack) {
|
||||||
mprisUpdateSong({ status: PlayerStatus.PAUSED });
|
|
||||||
resetPlayers();
|
resetPlayers();
|
||||||
} else {
|
} else {
|
||||||
const playerData = autoNext();
|
autoNext();
|
||||||
mprisUpdateSong({
|
|
||||||
song: playerData.current.song,
|
|
||||||
status: PlayerStatus.PLAYING,
|
|
||||||
});
|
|
||||||
resetPlayers();
|
resetPlayers();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -309,12 +256,12 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
|||||||
const handleRepeatAll = {
|
const handleRepeatAll = {
|
||||||
local: () => {
|
local: () => {
|
||||||
const playerData = next();
|
const playerData = next();
|
||||||
mprisUpdateSong({ song: playerData.current.song });
|
updateSong(playerData.current.song);
|
||||||
mpvPlayer!.setQueue(playerData);
|
mpvPlayer!.setQueue(playerData);
|
||||||
},
|
},
|
||||||
web: () => {
|
web: () => {
|
||||||
const playerData = next();
|
const playerData = next();
|
||||||
mprisUpdateSong({ song: playerData.current.song });
|
updateSong(playerData.current.song);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -322,27 +269,24 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
|||||||
local: () => {
|
local: () => {
|
||||||
if (isLastTrack) {
|
if (isLastTrack) {
|
||||||
const playerData = setCurrentIndex(0);
|
const playerData = setCurrentIndex(0);
|
||||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PAUSED });
|
updateSong(playerData.current.song);
|
||||||
mpvPlayer!.setQueue(playerData, true);
|
mpvPlayer!.setQueue(playerData, true);
|
||||||
pause();
|
pause();
|
||||||
} else {
|
} else {
|
||||||
const playerData = next();
|
const playerData = next();
|
||||||
mprisUpdateSong({ song: playerData.current.song });
|
updateSong(playerData.current.song);
|
||||||
mpvPlayer!.setQueue(playerData);
|
mpvPlayer!.setQueue(playerData);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
web: () => {
|
web: () => {
|
||||||
if (isLastTrack) {
|
if (isLastTrack) {
|
||||||
const playerData = setCurrentIndex(0);
|
const playerData = setCurrentIndex(0);
|
||||||
mprisUpdateSong({
|
updateSong(playerData.current.song);
|
||||||
song: playerData.current.song,
|
|
||||||
status: PlayerStatus.PAUSED,
|
|
||||||
});
|
|
||||||
resetPlayers();
|
resetPlayers();
|
||||||
pause();
|
pause();
|
||||||
} else {
|
} else {
|
||||||
const playerData = next();
|
const playerData = next();
|
||||||
mprisUpdateSong({ song: playerData.current.song });
|
updateSong(playerData.current.song);
|
||||||
resetPlayers();
|
resetPlayers();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -352,14 +296,14 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
|||||||
local: () => {
|
local: () => {
|
||||||
if (!isLastTrack) {
|
if (!isLastTrack) {
|
||||||
const playerData = next();
|
const playerData = next();
|
||||||
mprisUpdateSong({ song: playerData.current.song });
|
updateSong(playerData.current.song);
|
||||||
mpvPlayer!.setQueue(playerData);
|
mpvPlayer!.setQueue(playerData);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
web: () => {
|
web: () => {
|
||||||
if (!isLastTrack) {
|
if (!isLastTrack) {
|
||||||
const playerData = next();
|
const playerData = next();
|
||||||
mprisUpdateSong({ song: playerData.current.song });
|
updateSong(playerData.current.song);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -413,22 +357,22 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
|||||||
local: () => {
|
local: () => {
|
||||||
if (!isFirstTrack) {
|
if (!isFirstTrack) {
|
||||||
const playerData = previous();
|
const playerData = previous();
|
||||||
mprisUpdateSong({ song: playerData.current.song });
|
updateSong(playerData.current.song);
|
||||||
mpvPlayer!.setQueue(playerData);
|
mpvPlayer!.setQueue(playerData);
|
||||||
} else {
|
} else {
|
||||||
const playerData = setCurrentIndex(queue.length - 1);
|
const playerData = setCurrentIndex(queue.length - 1);
|
||||||
mprisUpdateSong({ song: playerData.current.song });
|
updateSong(playerData.current.song);
|
||||||
mpvPlayer!.setQueue(playerData);
|
mpvPlayer!.setQueue(playerData);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
web: () => {
|
web: () => {
|
||||||
if (isFirstTrack) {
|
if (isFirstTrack) {
|
||||||
const playerData = setCurrentIndex(queue.length - 1);
|
const playerData = setCurrentIndex(queue.length - 1);
|
||||||
mprisUpdateSong({ song: playerData.current.song });
|
updateSong(playerData.current.song);
|
||||||
resetPlayers();
|
resetPlayers();
|
||||||
} else {
|
} else {
|
||||||
const playerData = previous();
|
const playerData = previous();
|
||||||
mprisUpdateSong({ song: playerData.current.song });
|
updateSong(playerData.current.song);
|
||||||
resetPlayers();
|
resetPlayers();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -438,26 +382,22 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
|||||||
local: () => {
|
local: () => {
|
||||||
if (isFirstTrack) {
|
if (isFirstTrack) {
|
||||||
const playerData = setCurrentIndex(0);
|
const playerData = setCurrentIndex(0);
|
||||||
mprisUpdateSong({ song: playerData.current.song, status: PlayerStatus.PAUSED });
|
updateSong(playerData.current.song);
|
||||||
mpvPlayer!.setQueue(playerData, true);
|
mpvPlayer!.setQueue(playerData, true);
|
||||||
pause();
|
pause();
|
||||||
} else {
|
} else {
|
||||||
const playerData = previous();
|
const playerData = previous();
|
||||||
mprisUpdateSong({
|
updateSong(playerData.current.song);
|
||||||
currentTime: usePlayerStore.getState().current.time,
|
|
||||||
song: playerData.current.song,
|
|
||||||
});
|
|
||||||
mpvPlayer!.setQueue(playerData);
|
mpvPlayer!.setQueue(playerData);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
web: () => {
|
web: () => {
|
||||||
if (isFirstTrack) {
|
if (isFirstTrack) {
|
||||||
resetPlayers();
|
resetPlayers();
|
||||||
mprisUpdateSong({ status: PlayerStatus.PAUSED });
|
|
||||||
pause();
|
pause();
|
||||||
} else {
|
} else {
|
||||||
const playerData = previous();
|
const playerData = previous();
|
||||||
mprisUpdateSong({ song: playerData.current.song });
|
updateSong(playerData.current.song);
|
||||||
resetPlayers();
|
resetPlayers();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -466,12 +406,12 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
|||||||
const handleRepeatOne = {
|
const handleRepeatOne = {
|
||||||
local: () => {
|
local: () => {
|
||||||
const playerData = previous();
|
const playerData = previous();
|
||||||
mprisUpdateSong({ song: playerData.current.song });
|
updateSong(playerData.current.song);
|
||||||
mpvPlayer!.setQueue(playerData);
|
mpvPlayer!.setQueue(playerData);
|
||||||
},
|
},
|
||||||
web: () => {
|
web: () => {
|
||||||
const playerData = previous();
|
const playerData = previous();
|
||||||
mprisUpdateSong({ song: playerData.current.song });
|
updateSong(playerData.current.song);
|
||||||
resetPlayers();
|
resetPlayers();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -2,13 +2,7 @@ import { useCallback, useRef } from 'react';
|
|||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { useCurrentServer, usePlayerControls, usePlayerStore } from '/@/renderer/store';
|
import { useCurrentServer, usePlayerControls, usePlayerStore } from '/@/renderer/store';
|
||||||
import { usePlaybackType } from '/@/renderer/store/settings.store';
|
import { usePlaybackType } from '/@/renderer/store/settings.store';
|
||||||
import {
|
import { PlayQueueAddOptions, Play, PlaybackType } from '/@/renderer/types';
|
||||||
PlayQueueAddOptions,
|
|
||||||
Play,
|
|
||||||
PlaybackType,
|
|
||||||
PlayerStatus,
|
|
||||||
PlayerShuffle,
|
|
||||||
} from '/@/renderer/types';
|
|
||||||
import { toast } from '/@/renderer/components/toast/index';
|
import { toast } from '/@/renderer/components/toast/index';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { nanoid } from 'nanoid/non-secure';
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
@ -30,6 +24,7 @@ import {
|
|||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PlayersRef } from '/@/renderer/features/player/ref/players-ref';
|
import { PlayersRef } from '/@/renderer/features/player/ref/players-ref';
|
||||||
|
import { updateSong } from '/@/renderer/features/player/update-remote-song';
|
||||||
|
|
||||||
const getRootQueryKey = (itemType: LibraryItem, serverId: string) => {
|
const getRootQueryKey = (itemType: LibraryItem, serverId: string) => {
|
||||||
let queryKey;
|
let queryKey;
|
||||||
@ -59,7 +54,6 @@ const getRootQueryKey = (itemType: LibraryItem, serverId: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
const mpvPlayer = isElectron() ? window.electron.mpvPlayer : null;
|
||||||
const remote = isElectron() ? window.electron.remote : null;
|
|
||||||
|
|
||||||
const addToQueue = usePlayerStore.getState().actions.addToQueue;
|
const addToQueue = usePlayerStore.getState().actions.addToQueue;
|
||||||
|
|
||||||
@ -171,6 +165,8 @@ export const useHandlePlayQueueAdd = () => {
|
|||||||
const hadSong = usePlayerStore.getState().queue.default.length > 0;
|
const hadSong = usePlayerStore.getState().queue.default.length > 0;
|
||||||
const playerData = addToQueue({ initialIndex: initialSongIndex, playType, songs });
|
const playerData = addToQueue({ initialIndex: initialSongIndex, playType, songs });
|
||||||
|
|
||||||
|
updateSong(playerData.current.song);
|
||||||
|
|
||||||
if (playbackType === PlaybackType.LOCAL) {
|
if (playbackType === PlaybackType.LOCAL) {
|
||||||
mpvPlayer!.volume(usePlayerStore.getState().volume);
|
mpvPlayer!.volume(usePlayerStore.getState().volume);
|
||||||
|
|
||||||
@ -197,14 +193,6 @@ export const useHandlePlayQueueAdd = () => {
|
|||||||
play();
|
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;
|
return null;
|
||||||
},
|
},
|
||||||
[play, playbackType, queryClient, server, t],
|
[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;
|
route: CardRoute;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SongUpdate = {
|
export type SongState = {
|
||||||
currentTime?: number;
|
|
||||||
repeat?: PlayerRepeat;
|
repeat?: PlayerRepeat;
|
||||||
shuffle?: boolean;
|
shuffle?: boolean;
|
||||||
song?: QueueSong;
|
song?: QueueSong;
|
||||||
|
Loading…
Reference in New Issue
Block a user