Skip to content

Commit 368b251

Browse files
authored
feat(api-server): add endpoint to get shuffle state (#2792)
1 parent 3339f99 commit 368b251

File tree

6 files changed

+102
-6
lines changed

6 files changed

+102
-6
lines changed

src/plugins/api-server/backend/routes/control.ts

+37-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,24 @@ const routes = {
173173
},
174174
},
175175
}),
176-
176+
getShuffleState: createRoute({
177+
method: 'get',
178+
path: `/api/${API_VERSION}/shuffle`,
179+
summary: 'get shuffle state',
180+
description: 'Get the current shuffle state',
181+
responses: {
182+
200: {
183+
description: 'Success',
184+
content: {
185+
'application/json': {
186+
schema: z.object({
187+
state: z.boolean().nullable(),
188+
}),
189+
},
190+
},
191+
},
192+
},
193+
}),
177194
shuffle: createRoute({
178195
method: 'post',
179196
path: `/api/${API_VERSION}/shuffle`,
@@ -581,6 +598,25 @@ export const register = (
581598
ctx.status(204);
582599
return ctx.body(null);
583600
});
601+
602+
app.openapi(routes.getShuffleState, async (ctx) => {
603+
const stateResponsePromise = new Promise<boolean>((resolve) => {
604+
ipcMain.once(
605+
'ytmd:get-shuffle-response',
606+
(_, isShuffled: boolean | undefined) => {
607+
return resolve(!!isShuffled);
608+
},
609+
);
610+
611+
controller.requestShuffleInformation();
612+
});
613+
614+
const isShuffled = await stateResponsePromise;
615+
616+
ctx.status(200);
617+
return ctx.json({ state: isShuffled });
618+
});
619+
584620
app.openapi(routes.shuffle, (ctx) => {
585621
controller.shuffle();
586622

src/plugins/shortcuts/mpris-service.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ declare module '@jellybrick/mpris-service' {
8686
supportedMimeTypes: string[];
8787
canQuit: boolean;
8888
canRaise: boolean;
89-
canSetFullscreen?: boolean;
89+
canUsePlayerControls?: boolean;
9090
desktopEntry?: string;
9191
hasTrackList: boolean;
9292

src/plugins/shortcuts/mpris.ts

+21-4
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ function setupMPRIS() {
7777

7878
instance.canRaise = true;
7979
instance.canQuit = false;
80-
instance.canSetFullscreen = true;
80+
instance.canUsePlayerControls = true;
8181
instance.supportedUriSchemes = ['http', 'https'];
8282
instance.desktopEntry = 'youtube-music';
8383
return instance;
@@ -93,6 +93,7 @@ function registerMPRIS(win: BrowserWindow) {
9393
shuffle,
9494
switchRepeat,
9595
setFullscreen,
96+
requestShuffleInformation,
9697
requestFullscreenInformation,
9798
requestQueueInformation,
9899
} = songControls;
@@ -126,8 +127,10 @@ function registerMPRIS(win: BrowserWindow) {
126127
win.webContents.send('ytmd:setup-time-changed-listener', 'mpris');
127128
win.webContents.send('ytmd:setup-repeat-changed-listener', 'mpris');
128129
win.webContents.send('ytmd:setup-volume-changed-listener', 'mpris');
130+
win.webContents.send('ytmd:setup-shuffle-changed-listener', 'mpris');
129131
win.webContents.send('ytmd:setup-fullscreen-changed-listener', 'mpris');
130132
win.webContents.send('ytmd:setup-autoplay-changed-listener', 'mpris');
133+
requestShuffleInformation();
131134
requestFullscreenInformation();
132135
requestQueueInformation();
133136
});
@@ -156,8 +159,16 @@ function registerMPRIS(win: BrowserWindow) {
156159
requestQueueInformation();
157160
});
158161

162+
ipcMain.on('ytmd:shuffle-changed', (_, shuffleEnabled: boolean) => {
163+
if (player.shuffle === undefined || !player.canUsePlayerControls) {
164+
return;
165+
}
166+
167+
player.shuffle = shuffleEnabled ?? !player.shuffle;
168+
});
169+
159170
ipcMain.on('ytmd:fullscreen-changed', (_, changedTo: boolean) => {
160-
if (player.fullscreen === undefined || !player.canSetFullscreen) {
171+
if (player.fullscreen === undefined || !player.canUsePlayerControls) {
161172
return;
162173
}
163174

@@ -168,7 +179,7 @@ function registerMPRIS(win: BrowserWindow) {
168179
ipcMain.on(
169180
'ytmd:set-fullscreen',
170181
(_, isFullscreen: boolean | undefined) => {
171-
if (!player.canSetFullscreen || isFullscreen === undefined) {
182+
if (!player.canUsePlayerControls || isFullscreen === undefined) {
172183
return;
173184
}
174185

@@ -179,7 +190,7 @@ function registerMPRIS(win: BrowserWindow) {
179190
ipcMain.on(
180191
'ytmd:fullscreen-changed-supported',
181192
(_, isFullscreenSupported: boolean) => {
182-
player.canSetFullscreen = isFullscreenSupported;
193+
player.canUsePlayerControls = isFullscreenSupported;
183194
},
184195
);
185196
ipcMain.on('ytmd:autoplay-changed', (_) => {
@@ -272,6 +283,12 @@ function registerMPRIS(win: BrowserWindow) {
272283
player.on('position', seekTo);
273284

274285
player.on('shuffle', (enableShuffle) => {
286+
if (!player.canUsePlayerControls || enableShuffle === undefined) {
287+
return;
288+
}
289+
290+
player.shuffle = enableShuffle;
291+
275292
if (enableShuffle) {
276293
shuffle();
277294
requestQueueInformation();

src/providers/song-controls.ts

+3
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ export default (win: BrowserWindow) => {
6262
win.webContents.send('ytmd:seek-by', seconds);
6363
}
6464
},
65+
requestShuffleInformation: () => {
66+
win.webContents.send('ytmd:get-shuffle');
67+
},
6568
shuffle: () => win.webContents.send('ytmd:shuffle'),
6669
switchRepeat: (n: ArgsType<number> = 1) => {
6770
const repeat = parseNumberFromArgsType(n);

src/providers/song-info-front.ts

+26
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,28 @@ export const setupVolumeChangedListener = singleton((api: YoutubePlayer) => {
8787
window.ipcRenderer.send('ytmd:volume-changed', api.getVolume());
8888
});
8989

90+
export const setupShuffleChangedListener = singleton(() => {
91+
const playerBar = document.querySelector('ytmusic-player-bar');
92+
93+
if (!playerBar) {
94+
window.ipcRenderer.send('ytmd:shuffle-changed-supported', false);
95+
return;
96+
}
97+
98+
const observer = new MutationObserver(() => {
99+
window.ipcRenderer.send(
100+
'ytmd:shuffle-changed',
101+
(playerBar?.attributes.getNamedItem('shuffle-on') ?? null) !== null,
102+
);
103+
});
104+
105+
observer.observe(playerBar, {
106+
attributes: true,
107+
childList: false,
108+
subtree: false,
109+
});
110+
});
111+
90112
export const setupFullScreenChangedListener = singleton(() => {
91113
const playerBar = document.querySelector('ytmusic-player-bar');
92114

@@ -139,6 +161,10 @@ export default (api: YoutubePlayer) => {
139161
setupVolumeChangedListener(api);
140162
});
141163

164+
window.ipcRenderer.on('ytmd:setup-shuffle-changed-listener', () => {
165+
setupShuffleChangedListener();
166+
});
167+
142168
window.ipcRenderer.on('ytmd:setup-fullscreen-changed-listener', () => {
143169
setupFullScreenChangedListener();
144170
});

src/renderer.ts

+14
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,20 @@ async function onApiLoaded() {
8080
>('ytmusic-player-bar')
8181
?.queue.shuffle();
8282
});
83+
84+
const isShuffled = () => {
85+
const isShuffled =
86+
document
87+
.querySelector<HTMLElement>('ytmusic-player-bar')
88+
?.attributes.getNamedItem('shuffle-on') ?? null;
89+
90+
return isShuffled !== null;
91+
};
92+
93+
window.ipcRenderer.on('ytmd:get-shuffle', () => {
94+
window.ipcRenderer.send('ytmd:get-shuffle-response', isShuffled());
95+
});
96+
8397
window.ipcRenderer.on(
8498
'ytmd:update-like',
8599
(_, status: 'LIKE' | 'DISLIKE' = 'LIKE') => {

0 commit comments

Comments
 (0)