[enhancement]: support viewing current/setting current time in remote
This commit is contained in:
parent
b347b794b9
commit
5b2977e5e8
9 changed files with 61 additions and 13 deletions
|
@ -34,13 +34,14 @@ interface MimeType {
|
||||||
js: string;
|
js: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StatefulWebSocket extends WebSocket {
|
declare class StatefulWebSocket extends WebSocket {
|
||||||
alive: boolean;
|
alive: boolean;
|
||||||
|
|
||||||
auth: boolean;
|
auth: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let server: Server | undefined;
|
let server: Server | undefined;
|
||||||
let wsServer: WsServer<StatefulWebSocket> | undefined;
|
let wsServer: WsServer<typeof StatefulWebSocket> | undefined;
|
||||||
|
|
||||||
const settings: RemoteConfig = {
|
const settings: RemoteConfig = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
@ -327,9 +328,9 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(config.port, resolve);
|
server.listen(config.port, resolve);
|
||||||
wsServer = new WebSocketServer({ server });
|
wsServer = new WebSocketServer<typeof StatefulWebSocket>({ server });
|
||||||
|
|
||||||
wsServer.on('connection', (ws) => {
|
wsServer!.on('connection', (ws: StatefulWebSocket) => {
|
||||||
let authFail: number | undefined;
|
let authFail: number | undefined;
|
||||||
ws.alive = true;
|
ws.alive = true;
|
||||||
|
|
||||||
|
@ -471,6 +472,15 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'position': {
|
||||||
|
const { position } = json;
|
||||||
|
if (mprisPlayer) {
|
||||||
|
mprisPlayer.getPosition = () => position * 1e6;
|
||||||
|
}
|
||||||
|
getMainWindow()?.webContents.send('request-position', {
|
||||||
|
position,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -496,7 +506,7 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
||||||
});
|
});
|
||||||
}, PING_TIMEOUT_MS);
|
}, PING_TIMEOUT_MS);
|
||||||
|
|
||||||
wsServer.on('close', () => {
|
wsServer!.on('close', () => {
|
||||||
clearInterval(heartBeat);
|
clearInterval(heartBeat);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -649,3 +659,8 @@ if (mprisPlayer) {
|
||||||
broadcast({ data: volume, event: 'volume' });
|
broadcast({ data: volume, event: 'volume' });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipcMain.on('update-position', (_event, position: number) => {
|
||||||
|
currentState.position = position;
|
||||||
|
broadcast({ data: position, event: 'position' });
|
||||||
|
});
|
||||||
|
|
|
@ -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;
|
mprisPlayer.getPosition = () => arg * 1e6;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,10 @@ const updateVolume = (volume: number) => {
|
||||||
ipcRenderer.send('update-volume', volume);
|
ipcRenderer.send('update-volume', volume);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updatePosition = (timeSec: number) => {
|
||||||
|
ipcRenderer.send('update-position', timeSec);
|
||||||
|
};
|
||||||
|
|
||||||
export const remote = {
|
export const remote = {
|
||||||
requestFavorite,
|
requestFavorite,
|
||||||
requestPosition,
|
requestPosition,
|
||||||
|
@ -95,6 +99,7 @@ export const remote = {
|
||||||
updateFavorite,
|
updateFavorite,
|
||||||
updatePassword,
|
updatePassword,
|
||||||
updatePlayback,
|
updatePlayback,
|
||||||
|
updatePosition,
|
||||||
updateRating,
|
updateRating,
|
||||||
updateRepeat,
|
updateRepeat,
|
||||||
updateSetting,
|
updateSetting,
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { Tooltip } from '/@/renderer/components/tooltip';
|
||||||
import { Rating } from '/@/renderer/components/rating';
|
import { Rating } from '/@/renderer/components/rating';
|
||||||
|
|
||||||
export const RemoteContainer = () => {
|
export const RemoteContainer = () => {
|
||||||
const { repeat, shuffle, song, status, volume } = useInfo();
|
const { position, repeat, shuffle, song, status, volume } = useInfo();
|
||||||
const send = useSend();
|
const send = useSend();
|
||||||
const showImage = useShowImage();
|
const showImage = useShowImage();
|
||||||
|
|
||||||
|
@ -154,6 +154,16 @@ export const RemoteContainer = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
{id && position !== undefined && (
|
||||||
|
<WrapperSlider
|
||||||
|
label={(value) => formatDuration(value * 1e3)}
|
||||||
|
leftLabel={formatDuration(position * 1e3)}
|
||||||
|
max={song.duration / 1e3}
|
||||||
|
rightLabel={formatDuration(song.duration)}
|
||||||
|
value={position}
|
||||||
|
onChangeEnd={(e) => send({ event: 'position', position: e })}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<WrapperSlider
|
<WrapperSlider
|
||||||
leftLabel={<RiVolumeUpFill size={20} />}
|
leftLabel={<RiVolumeUpFill size={20} />}
|
||||||
max={100}
|
max={100}
|
||||||
|
|
|
@ -139,6 +139,12 @@ export const useRemoteStore = create<SettingsSlice>()(
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'position': {
|
||||||
|
set((state) => {
|
||||||
|
state.info.position = data;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'proxy': {
|
case 'proxy': {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
if (state.info.song) {
|
if (state.info.song) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import type { QueueSong } from '/@/renderer/api/types';
|
||||||
import type { PlayerRepeat, PlayerStatus, SongState } from '/@/renderer/types';
|
import type { PlayerRepeat, PlayerStatus, SongState } from '/@/renderer/types';
|
||||||
|
|
||||||
export interface SongUpdateSocket extends Omit<SongState, 'song'> {
|
export interface SongUpdateSocket extends Omit<SongState, 'song'> {
|
||||||
|
position?: number;
|
||||||
song?: QueueSong | null;
|
song?: QueueSong | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +21,10 @@ export interface ServerPlayStatus {
|
||||||
event: 'playback';
|
event: 'playback';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ServerPosition {
|
||||||
|
data: number;
|
||||||
|
event: 'position';
|
||||||
|
}
|
||||||
export interface ServerProxy {
|
export interface ServerProxy {
|
||||||
data: string;
|
data: string;
|
||||||
event: 'proxy';
|
event: 'proxy';
|
||||||
|
@ -59,6 +64,7 @@ export type ServerEvent =
|
||||||
| ServerError
|
| ServerError
|
||||||
| ServerFavorite
|
| ServerFavorite
|
||||||
| ServerPlayStatus
|
| ServerPlayStatus
|
||||||
|
| ServerPosition
|
||||||
| ServerRating
|
| ServerRating
|
||||||
| ServerRepeat
|
| ServerRepeat
|
||||||
| ServerShuffle
|
| ServerShuffle
|
||||||
|
@ -93,8 +99,14 @@ export interface ClientAuth {
|
||||||
header: string;
|
header: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ClientPosition {
|
||||||
|
event: 'position';
|
||||||
|
position: number;
|
||||||
|
}
|
||||||
|
|
||||||
export type ClientEvent =
|
export type ClientEvent =
|
||||||
| ClientAuth
|
| ClientAuth
|
||||||
|
| ClientPosition
|
||||||
| ClientSimpleEvent
|
| ClientSimpleEvent
|
||||||
| ClientFavorite
|
| ClientFavorite
|
||||||
| ClientRating
|
| ClientRating
|
||||||
|
|
|
@ -131,16 +131,15 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
|
||||||
const formattedTime = formatDuration(currentTime * 1000 || 0);
|
const formattedTime = formatDuration(currentTime * 1000 || 0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let interval: any;
|
let interval: ReturnType<typeof setInterval>;
|
||||||
|
|
||||||
if (status === PlayerStatus.PLAYING && !isSeeking) {
|
if (status === PlayerStatus.PLAYING && !isSeeking) {
|
||||||
if (!isElectron() || playbackType === PlaybackType.WEB) {
|
if (!isElectron() || playbackType === PlaybackType.WEB) {
|
||||||
|
// Update twice a second for slightly better performance
|
||||||
interval = setInterval(() => {
|
interval = setInterval(() => {
|
||||||
setCurrentTime(currentPlayerRef.getCurrentTime());
|
setCurrentTime(currentPlayerRef.getCurrentTime());
|
||||||
}, 1000);
|
}, 500);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
clearInterval(interval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
|
|
|
@ -669,11 +669,11 @@ export const useCenterControls = (args: { playersRef: any }) => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (utils?.isLinux()) {
|
if (remote) {
|
||||||
const unsubCurrentTime = usePlayerStore.subscribe(
|
const unsubCurrentTime = usePlayerStore.subscribe(
|
||||||
(state) => state.current.time,
|
(state) => state.current.time,
|
||||||
(time) => {
|
(time) => {
|
||||||
mpris?.updatePosition(time);
|
remote.updatePosition(time);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -212,6 +212,7 @@ export type GridCardData = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SongState = {
|
export type SongState = {
|
||||||
|
position?: number;
|
||||||
repeat?: PlayerRepeat;
|
repeat?: PlayerRepeat;
|
||||||
shuffle?: boolean;
|
shuffle?: boolean;
|
||||||
song?: QueueSong;
|
song?: QueueSong;
|
||||||
|
|
Reference in a new issue