diff --git a/src/main/features/core/remote/index.ts b/src/main/features/core/remote/index.ts index f1fa55d7..0adcf835 100644 --- a/src/main/features/core/remote/index.ts +++ b/src/main/features/core/remote/index.ts @@ -34,13 +34,14 @@ interface MimeType { js: string; } -interface StatefulWebSocket extends WebSocket { +declare class StatefulWebSocket extends WebSocket { alive: boolean; + auth: boolean; } let server: Server | undefined; -let wsServer: WsServer | undefined; +let wsServer: WsServer | undefined; const settings: RemoteConfig = { enabled: false, @@ -327,9 +328,9 @@ const enableServer = (config: RemoteConfig): Promise => { }); server.listen(config.port, resolve); - wsServer = new WebSocketServer({ server }); + wsServer = new WebSocketServer({ server }); - wsServer.on('connection', (ws) => { + wsServer!.on('connection', (ws: StatefulWebSocket) => { let authFail: number | undefined; ws.alive = true; @@ -471,6 +472,15 @@ const enableServer = (config: RemoteConfig): Promise => { } break; } + case 'position': { + const { position } = json; + if (mprisPlayer) { + mprisPlayer.getPosition = () => position * 1e6; + } + getMainWindow()?.webContents.send('request-position', { + position, + }); + } } } catch (error) { console.error(error); @@ -496,7 +506,7 @@ const enableServer = (config: RemoteConfig): Promise => { }); }, PING_TIMEOUT_MS); - wsServer.on('close', () => { + wsServer!.on('close', () => { clearInterval(heartBeat); }); @@ -649,3 +659,8 @@ if (mprisPlayer) { broadcast({ data: volume, event: 'volume' }); }); } + +ipcMain.on('update-position', (_event, position: number) => { + currentState.position = position; + broadcast({ data: position, event: 'position' }); +}); diff --git a/src/main/features/linux/mpris.ts b/src/main/features/linux/mpris.ts index 9d0bd65d..6d400f8e 100644 --- a/src/main/features/linux/mpris.ts +++ b/src/main/features/linux/mpris.ts @@ -106,7 +106,7 @@ mprisPlayer.on('seek', (event: number) => { }); }); -ipcMain.on('mpris-update-position', (_event, arg) => { +ipcMain.on('update-position', (_event, arg: number) => { mprisPlayer.getPosition = () => arg * 1e6; }); diff --git a/src/main/preload/remote.ts b/src/main/preload/remote.ts index cf46d72b..a589be68 100644 --- a/src/main/preload/remote.ts +++ b/src/main/preload/remote.ts @@ -84,6 +84,10 @@ const updateVolume = (volume: number) => { ipcRenderer.send('update-volume', volume); }; +const updatePosition = (timeSec: number) => { + ipcRenderer.send('update-position', timeSec); +}; + export const remote = { requestFavorite, requestPosition, @@ -95,6 +99,7 @@ export const remote = { updateFavorite, updatePassword, updatePlayback, + updatePosition, updateRating, updateRepeat, updateSetting, diff --git a/src/remote/components/remote-container.tsx b/src/remote/components/remote-container.tsx index 8862fd5b..595be38c 100644 --- a/src/remote/components/remote-container.tsx +++ b/src/remote/components/remote-container.tsx @@ -21,7 +21,7 @@ import { Tooltip } from '/@/renderer/components/tooltip'; import { Rating } from '/@/renderer/components/rating'; export const RemoteContainer = () => { - const { repeat, shuffle, song, status, volume } = useInfo(); + const { position, repeat, shuffle, song, status, volume } = useInfo(); const send = useSend(); const showImage = useShowImage(); @@ -154,6 +154,16 @@ export const RemoteContainer = () => { )} + {id && position !== undefined && ( + formatDuration(value * 1e3)} + leftLabel={formatDuration(position * 1e3)} + max={song.duration / 1e3} + rightLabel={formatDuration(song.duration)} + value={position} + onChangeEnd={(e) => send({ event: 'position', position: e })} + /> + )} } max={100} diff --git a/src/remote/store/index.ts b/src/remote/store/index.ts index dcde033d..6844ea23 100644 --- a/src/remote/store/index.ts +++ b/src/remote/store/index.ts @@ -139,6 +139,12 @@ export const useRemoteStore = create()( }); break; } + case 'position': { + set((state) => { + state.info.position = data; + }); + break; + } case 'proxy': { set((state) => { if (state.info.song) { diff --git a/src/remote/types.ts b/src/remote/types.ts index 87e99618..34672fcb 100644 --- a/src/remote/types.ts +++ b/src/remote/types.ts @@ -2,6 +2,7 @@ import type { QueueSong } from '/@/renderer/api/types'; import type { PlayerRepeat, PlayerStatus, SongState } from '/@/renderer/types'; export interface SongUpdateSocket extends Omit { + position?: number; song?: QueueSong | null; } @@ -20,6 +21,10 @@ export interface ServerPlayStatus { event: 'playback'; } +export interface ServerPosition { + data: number; + event: 'position'; +} export interface ServerProxy { data: string; event: 'proxy'; @@ -59,6 +64,7 @@ export type ServerEvent = | ServerError | ServerFavorite | ServerPlayStatus + | ServerPosition | ServerRating | ServerRepeat | ServerShuffle @@ -93,8 +99,14 @@ export interface ClientAuth { header: string; } +export interface ClientPosition { + event: 'position'; + position: number; +} + export type ClientEvent = | ClientAuth + | ClientPosition | ClientSimpleEvent | ClientFavorite | ClientRating diff --git a/src/renderer/features/player/components/center-controls.tsx b/src/renderer/features/player/components/center-controls.tsx index a0615419..88a48698 100644 --- a/src/renderer/features/player/components/center-controls.tsx +++ b/src/renderer/features/player/components/center-controls.tsx @@ -131,16 +131,15 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => { const formattedTime = formatDuration(currentTime * 1000 || 0); useEffect(() => { - let interval: any; + let interval: ReturnType; if (status === PlayerStatus.PLAYING && !isSeeking) { if (!isElectron() || playbackType === PlaybackType.WEB) { + // Update twice a second for slightly better performance interval = setInterval(() => { setCurrentTime(currentPlayerRef.getCurrentTime()); - }, 1000); + }, 500); } - } else { - clearInterval(interval); } return () => clearInterval(interval); diff --git a/src/renderer/features/player/hooks/use-center-controls.ts b/src/renderer/features/player/hooks/use-center-controls.ts index 4ac3bc30..159acfc4 100644 --- a/src/renderer/features/player/hooks/use-center-controls.ts +++ b/src/renderer/features/player/hooks/use-center-controls.ts @@ -669,11 +669,11 @@ export const useCenterControls = (args: { playersRef: any }) => { ]); useEffect(() => { - if (utils?.isLinux()) { + if (remote) { const unsubCurrentTime = usePlayerStore.subscribe( (state) => state.current.time, (time) => { - mpris?.updatePosition(time); + remote.updatePosition(time); }, ); diff --git a/src/renderer/types.ts b/src/renderer/types.ts index 1f56670e..154cf68a 100644 --- a/src/renderer/types.ts +++ b/src/renderer/types.ts @@ -212,6 +212,7 @@ export type GridCardData = { }; export type SongState = { + position?: number; repeat?: PlayerRepeat; shuffle?: boolean; song?: QueueSong;