diff --git a/src/plugins/api-server/backend/main.ts b/src/plugins/api-server/backend/main.ts index 11600de8b5..ffe1d41f74 100644 --- a/src/plugins/api-server/backend/main.ts +++ b/src/plugins/api-server/backend/main.ts @@ -14,7 +14,7 @@ import { registerCallback } from '@/providers/song-info'; import { createBackend } from '@/utils'; import { JWTPayloadSchema } from './scheme'; -import { registerAuth, registerControl, registerWebsocket } from './routes'; +import { registerAuth, registerControl, registerWebsocket, wsNotifyClose } from './routes'; import { APPLICATION_NAME } from '@/i18n'; @@ -31,8 +31,14 @@ import type { export const backend = createBackend({ async start(ctx) { + const { app } = await import('electron'); const config = await ctx.getConfig(); + // Notify WebSocket clients that playback has stopped before quitting + app.on('before-quit', () => { + wsNotifyClose?.(); + }); + this.init(ctx); registerCallback((songInfo) => { this.songInfo = songInfo; @@ -201,6 +207,7 @@ export const backend = createBackend({ } }, end() { + wsNotifyClose?.(); this.server?.close(); this.server = undefined; }, diff --git a/src/plugins/api-server/backend/routes/index.ts b/src/plugins/api-server/backend/routes/index.ts index a2467098e8..1c4370594b 100644 --- a/src/plugins/api-server/backend/routes/index.ts +++ b/src/plugins/api-server/backend/routes/index.ts @@ -1,3 +1,3 @@ export { register as registerControl } from './control'; export { register as registerAuth } from './auth'; -export { register as registerWebsocket } from './websocket'; +export { register as registerWebsocket, wsNotifyClose } from './websocket'; diff --git a/src/plugins/api-server/backend/routes/websocket.ts b/src/plugins/api-server/backend/routes/websocket.ts index c71987d832..86a4b1a282 100644 --- a/src/plugins/api-server/backend/routes/websocket.ts +++ b/src/plugins/api-server/backend/routes/websocket.ts @@ -56,6 +56,18 @@ export const register = ( ); }; + // Notify all WebSocket clients that playback has stopped (e.g. app closing/hiding) + // We intentionally do NOT close or clear sockets here so that clients like + // Boring Notch keep their connection alive and can receive updates when + // Pear Desktop comes back from hiding. + wsNotifyClose = () => { + console.log(`[API Server WebSocket] NotifyClose called. Sending isPlaying:false to ${sockets.size} clients.`); + send(DataTypes.PlayerStateChanged, { + isPlaying: false, + position: lastSongInfo?.elapsedSeconds ?? 0, + }); + }; + const createPlayerState = ({ songInfo, volumeState, @@ -152,3 +164,7 @@ export const register = ( })) as (ctx: Context, next: Next) => Promise, ); }; + +// Exposed so the backend can call it on app close/hide +export let wsNotifyClose: (() => void) | undefined; +