diff --git a/src/main/features/core/remote/index.ts b/src/main/features/core/remote/index.ts index e5a933de..6489285f 100644 --- a/src/main/features/core/remote/index.ts +++ b/src/main/features/core/remote/index.ts @@ -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 => { 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 => { 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 => { } 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 => { 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' }); }); } diff --git a/src/main/features/linux/mpris.ts b/src/main/features/linux/mpris.ts index fcdf4ac0..9d0bd65d 100644 --- a/src/main/features/linux/mpris.ts +++ b/src/main/features/linux/mpris.ts @@ -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.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; } diff --git a/src/main/preload/remote.ts b/src/main/preload/remote.ts index 16fa140a..cf46d72b 100644 --- a/src/main/preload/remote.ts +++ b/src/main/preload/remote.ts @@ -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, diff --git a/src/remote/components/remote-container.tsx b/src/remote/components/remote-container.tsx index 496bb610..c7f3e63a 100644 --- a/src/remote/components/remote-container.tsx +++ b/src/remote/components/remote-container.tsx @@ -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 && ( <> {song.name} @@ -61,7 +61,7 @@ export const RemoteContainer = () => { spacing={0} > send({ event: 'previous' })} @@ -69,8 +69,8 @@ export const RemoteContainer = () => { { if (status === PlayerStatus.PLAYING) { @@ -80,14 +80,14 @@ export const RemoteContainer = () => { } }} > - {song && status === PlayerStatus.PLAYING ? ( + {id && status === PlayerStatus.PLAYING ? ( ) : ( )} send({ event: 'next' })} @@ -127,7 +127,7 @@ export const RemoteContainer = () => { { diff --git a/src/remote/store/index.ts b/src/remote/store/index.ts index dc3c2166..dcde033d 100644 --- a/src/remote/store/index.ts +++ b/src/remote/store/index.ts @@ -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()( }); 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()( }); 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()( { name: 'store_settings' }, ), { - merge: (persistedState, currentState) => { - return merge(currentState, persistedState); - }, + merge: (persistedState, currentState) => merge(currentState, persistedState), name: 'store_settings', - version: 6, + version: 7, }, ), ); diff --git a/src/remote/types.ts b/src/remote/types.ts index 0ef32ed9..87e99618 100644 --- a/src/remote/types.ts +++ b/src/remote/types.ts @@ -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 { +export interface SongUpdateSocket extends Omit { 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'; diff --git a/src/renderer/components/audio-player/index.tsx b/src/renderer/components/audio-player/index.tsx index 7c4f08a4..85262054 100644 --- a/src/renderer/components/audio-player/index.tsx +++ b/src/renderer/components/audio-player/index.tsx @@ -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