mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-16 12:42:06 +00:00
feat(api-server): Add websocket as /api/v1/ws route (#3707)
Co-authored-by: JellyBrick <shlee1503@naver.com>
This commit is contained in:
@ -72,6 +72,7 @@
|
|||||||
"@ghostery/adblocker-electron": "2.11.6",
|
"@ghostery/adblocker-electron": "2.11.6",
|
||||||
"@ghostery/adblocker-electron-preload": "2.11.6",
|
"@ghostery/adblocker-electron-preload": "2.11.6",
|
||||||
"@hono/node-server": "1.19.1",
|
"@hono/node-server": "1.19.1",
|
||||||
|
"@hono/node-ws": "1.2.0",
|
||||||
"@hono/swagger-ui": "0.5.2",
|
"@hono/swagger-ui": "0.5.2",
|
||||||
"@hono/zod-openapi": "1.1.0",
|
"@hono/zod-openapi": "1.1.0",
|
||||||
"@hono/zod-validator": "0.7.2",
|
"@hono/zod-validator": "0.7.2",
|
||||||
|
|||||||
19
pnpm-lock.yaml
generated
19
pnpm-lock.yaml
generated
@ -63,6 +63,9 @@ importers:
|
|||||||
'@hono/node-server':
|
'@hono/node-server':
|
||||||
specifier: 1.19.1
|
specifier: 1.19.1
|
||||||
version: 1.19.1(hono@4.9.6)
|
version: 1.19.1(hono@4.9.6)
|
||||||
|
'@hono/node-ws':
|
||||||
|
specifier: 1.2.0
|
||||||
|
version: 1.2.0(@hono/node-server@1.19.1(hono@4.9.6))(bufferutil@4.0.9)(hono@4.9.6)(utf-8-validate@6.0.5)
|
||||||
'@hono/swagger-ui':
|
'@hono/swagger-ui':
|
||||||
specifier: 0.5.2
|
specifier: 0.5.2
|
||||||
version: 0.5.2(hono@4.9.6)
|
version: 0.5.2(hono@4.9.6)
|
||||||
@ -813,6 +816,13 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
hono: ^4
|
hono: ^4
|
||||||
|
|
||||||
|
'@hono/node-ws@1.2.0':
|
||||||
|
resolution: {integrity: sha512-OBPQ8OSHBw29mj00wT/xGYtB6HY54j0fNSdVZ7gZM3TUeq0So11GXaWtFf1xWxQNfumKIsj0wRuLKWfVsO5GgQ==}
|
||||||
|
engines: {node: '>=18.14.1'}
|
||||||
|
peerDependencies:
|
||||||
|
'@hono/node-server': ^1.11.1
|
||||||
|
hono: ^4.6.0
|
||||||
|
|
||||||
'@hono/swagger-ui@0.5.2':
|
'@hono/swagger-ui@0.5.2':
|
||||||
resolution: {integrity: sha512-7wxLKdb8h7JTdZ+K8DJNE3KXQMIpJejkBTQjrYlUWF28Z1PGOKw6kUykARe5NTfueIN37jbyG/sBYsbzXzG53A==}
|
resolution: {integrity: sha512-7wxLKdb8h7JTdZ+K8DJNE3KXQMIpJejkBTQjrYlUWF28Z1PGOKw6kUykARe5NTfueIN37jbyG/sBYsbzXzG53A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -5260,6 +5270,15 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
hono: 4.9.6
|
hono: 4.9.6
|
||||||
|
|
||||||
|
'@hono/node-ws@1.2.0(@hono/node-server@1.19.1(hono@4.9.6))(bufferutil@4.0.9)(hono@4.9.6)(utf-8-validate@6.0.5)':
|
||||||
|
dependencies:
|
||||||
|
'@hono/node-server': 1.19.1(hono@4.9.6)
|
||||||
|
hono: 4.9.6
|
||||||
|
ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
'@hono/swagger-ui@0.5.2(hono@4.9.6)':
|
'@hono/swagger-ui@0.5.2(hono@4.9.6)':
|
||||||
dependencies:
|
dependencies:
|
||||||
hono: 4.9.6
|
hono: 4.9.6
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { type Context, Hono } from 'hono';
|
|||||||
import { cors } from 'hono/cors';
|
import { cors } from 'hono/cors';
|
||||||
import { serve } from '@hono/node-server';
|
import { serve } from '@hono/node-server';
|
||||||
|
|
||||||
import registerCallback, { type SongInfo } from '@/providers/song-info';
|
import { registerCallback, type SongInfo } from '@/providers/song-info';
|
||||||
import { createBackend } from '@/utils';
|
import { createBackend } from '@/utils';
|
||||||
|
|
||||||
import type { AmuseSongInfo } from './types';
|
import type { AmuseSongInfo } from './types';
|
||||||
|
|||||||
1
src/plugins/api-server/backend/api-version.ts
Normal file
1
src/plugins/api-server/backend/api-version.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const API_VERSION = 'v1';
|
||||||
@ -3,12 +3,13 @@ import { OpenAPIHono as Hono } from '@hono/zod-openapi';
|
|||||||
import { cors } from 'hono/cors';
|
import { cors } from 'hono/cors';
|
||||||
import { swaggerUI } from '@hono/swagger-ui';
|
import { swaggerUI } from '@hono/swagger-ui';
|
||||||
import { serve } from '@hono/node-server';
|
import { serve } from '@hono/node-server';
|
||||||
|
import { createNodeWebSocket } from '@hono/node-ws';
|
||||||
|
|
||||||
import registerCallback from '@/providers/song-info';
|
import { registerCallback } from '@/providers/song-info';
|
||||||
import { createBackend } from '@/utils';
|
import { createBackend } from '@/utils';
|
||||||
|
|
||||||
import { JWTPayloadSchema } from './scheme';
|
import { JWTPayloadSchema } from './scheme';
|
||||||
import { registerAuth, registerControl } from './routes';
|
import { registerAuth, registerControl, registerWebsocket } from './routes';
|
||||||
|
|
||||||
import { type APIServerConfig, AuthStrategy } from '../config';
|
import { type APIServerConfig, AuthStrategy } from '../config';
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ export const backend = createBackend<BackendType, APIServerConfig>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ctx.ipc.on('ytmd:player-api-loaded', () => {
|
ctx.ipc.on('ytmd:player-api-loaded', () => {
|
||||||
|
ctx.ipc.send('ytmd:setup-seeked-listener');
|
||||||
ctx.ipc.send('ytmd:setup-time-changed-listener');
|
ctx.ipc.send('ytmd:setup-time-changed-listener');
|
||||||
ctx.ipc.send('ytmd:setup-repeat-changed-listener');
|
ctx.ipc.send('ytmd:setup-repeat-changed-listener');
|
||||||
ctx.ipc.send('ytmd:setup-like-changed-listener');
|
ctx.ipc.send('ytmd:setup-like-changed-listener');
|
||||||
@ -67,6 +69,9 @@ export const backend = createBackend<BackendType, APIServerConfig>({
|
|||||||
// Custom
|
// Custom
|
||||||
init(backendCtx) {
|
init(backendCtx) {
|
||||||
this.app = new Hono();
|
this.app = new Hono();
|
||||||
|
const ws = createNodeWebSocket({
|
||||||
|
app: this.app,
|
||||||
|
});
|
||||||
|
|
||||||
this.app.use('*', cors());
|
this.app.use('*', cors());
|
||||||
|
|
||||||
@ -115,6 +120,7 @@ export const backend = createBackend<BackendType, APIServerConfig>({
|
|||||||
() => this.volumeState,
|
() => this.volumeState,
|
||||||
);
|
);
|
||||||
registerAuth(this.app, backendCtx);
|
registerAuth(this.app, backendCtx);
|
||||||
|
registerWebsocket(this.app, ws);
|
||||||
|
|
||||||
// swagger
|
// swagger
|
||||||
this.app.openAPIRegistry.registerComponent(
|
this.app.openAPIRegistry.registerComponent(
|
||||||
@ -142,6 +148,8 @@ export const backend = createBackend<BackendType, APIServerConfig>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.app.get('/swagger', swaggerUI({ url: '/doc' }));
|
this.app.get('/swagger', swaggerUI({ url: '/doc' }));
|
||||||
|
|
||||||
|
this.injectWebSocket = ws.injectWebSocket.bind(this);
|
||||||
},
|
},
|
||||||
run(hostname, port) {
|
run(hostname, port) {
|
||||||
if (!this.app) return;
|
if (!this.app) return;
|
||||||
@ -152,6 +160,10 @@ export const backend = createBackend<BackendType, APIServerConfig>({
|
|||||||
port,
|
port,
|
||||||
hostname,
|
hostname,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.injectWebSocket && this.server) {
|
||||||
|
this.injectWebSocket(this.server);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import { createRoute, z } from '@hono/zod-openapi';
|
import { createRoute, z } from '@hono/zod-openapi';
|
||||||
|
|
||||||
import { ipcMain } from 'electron';
|
import { ipcMain } from 'electron';
|
||||||
|
|
||||||
import getSongControls from '@/providers/song-controls';
|
import getSongControls from '@/providers/song-controls';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LikeType,
|
LikeType,
|
||||||
type RepeatMode,
|
type RepeatMode,
|
||||||
@ -25,6 +23,7 @@ import {
|
|||||||
SwitchRepeatSchema,
|
SwitchRepeatSchema,
|
||||||
type ResponseSongInfo,
|
type ResponseSongInfo,
|
||||||
} from '../scheme';
|
} from '../scheme';
|
||||||
|
import { API_VERSION } from '../api-version';
|
||||||
|
|
||||||
import type { SongInfo } from '@/providers/song-info';
|
import type { SongInfo } from '@/providers/song-info';
|
||||||
import type { BackendContext } from '@/types/contexts';
|
import type { BackendContext } from '@/types/contexts';
|
||||||
@ -33,8 +32,6 @@ import type { HonoApp } from '../types';
|
|||||||
import type { QueueResponse } from '@/types/youtube-music-desktop-internal';
|
import type { QueueResponse } from '@/types/youtube-music-desktop-internal';
|
||||||
import type { Context } from 'hono';
|
import type { Context } from 'hono';
|
||||||
|
|
||||||
const API_VERSION = 'v1';
|
|
||||||
|
|
||||||
const routes = {
|
const routes = {
|
||||||
previous: createRoute({
|
previous: createRoute({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export { register as registerControl } from './control';
|
export { register as registerControl } from './control';
|
||||||
export { register as registerAuth } from './auth';
|
export { register as registerAuth } from './auth';
|
||||||
|
export { register as registerWebsocket } from './websocket';
|
||||||
|
|||||||
137
src/plugins/api-server/backend/routes/websocket.ts
Normal file
137
src/plugins/api-server/backend/routes/websocket.ts
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import { ipcMain } from 'electron';
|
||||||
|
import { createRoute } from '@hono/zod-openapi';
|
||||||
|
|
||||||
|
import { type NodeWebSocket } from '@hono/node-ws';
|
||||||
|
|
||||||
|
import {
|
||||||
|
registerCallback,
|
||||||
|
type SongInfo,
|
||||||
|
SongInfoEvent,
|
||||||
|
} from '@/providers/song-info';
|
||||||
|
|
||||||
|
import { API_VERSION } from '../api-version';
|
||||||
|
|
||||||
|
import type { WSContext } from 'hono/ws';
|
||||||
|
import type { Context, Next } from 'hono';
|
||||||
|
import type { RepeatMode, VolumeState } from '@/types/datahost-get-state';
|
||||||
|
import type { HonoApp } from '../types';
|
||||||
|
|
||||||
|
enum DataTypes {
|
||||||
|
PlayerInfo = 'PLAYER_INFO',
|
||||||
|
VideoChanged = 'VIDEO_CHANGED',
|
||||||
|
PlayerStateChanged = 'PLAYER_STATE_CHANGED',
|
||||||
|
PositionChanged = 'POSITION_CHANGED',
|
||||||
|
VolumeChanged = 'VOLUME_CHANGED',
|
||||||
|
RepeatChanged = 'REPEAT_CHANGED',
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlayerState = {
|
||||||
|
song?: SongInfo;
|
||||||
|
isPlaying: boolean;
|
||||||
|
muted: boolean;
|
||||||
|
position: number;
|
||||||
|
volume: number;
|
||||||
|
repeat: RepeatMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const register = (app: HonoApp, nodeWebSocket: NodeWebSocket) => {
|
||||||
|
let volumeState: VolumeState | undefined = undefined;
|
||||||
|
let repeat: RepeatMode = 'NONE';
|
||||||
|
let lastSongInfo: SongInfo | undefined = undefined;
|
||||||
|
|
||||||
|
const sockets = new Set<WSContext<WebSocket>>();
|
||||||
|
|
||||||
|
const send = (type: DataTypes, state: Partial<PlayerState>) => {
|
||||||
|
sockets.forEach((socket) =>
|
||||||
|
socket.send(JSON.stringify({ type, ...state })),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createPlayerState = ({
|
||||||
|
songInfo,
|
||||||
|
volumeState,
|
||||||
|
repeat,
|
||||||
|
}: {
|
||||||
|
songInfo?: SongInfo;
|
||||||
|
volumeState?: VolumeState;
|
||||||
|
repeat: RepeatMode;
|
||||||
|
}): PlayerState => ({
|
||||||
|
song: songInfo,
|
||||||
|
isPlaying: songInfo ? !songInfo.isPaused : false,
|
||||||
|
muted: volumeState?.isMuted ?? false,
|
||||||
|
position: songInfo?.elapsedSeconds ?? 0,
|
||||||
|
volume: volumeState?.state ?? 100,
|
||||||
|
repeat,
|
||||||
|
});
|
||||||
|
|
||||||
|
registerCallback((songInfo, event) => {
|
||||||
|
if (event === SongInfoEvent.VideoSrcChanged) {
|
||||||
|
send(DataTypes.VideoChanged, { song: songInfo, position: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event === SongInfoEvent.PlayOrPaused) {
|
||||||
|
send(DataTypes.PlayerStateChanged, {
|
||||||
|
isPlaying: !(songInfo?.isPaused ?? true),
|
||||||
|
position: songInfo.elapsedSeconds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event === SongInfoEvent.TimeChanged) {
|
||||||
|
send(DataTypes.PositionChanged, { position: songInfo.elapsedSeconds });
|
||||||
|
}
|
||||||
|
|
||||||
|
lastSongInfo = { ...songInfo };
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('ytmd:volume-changed', (_, newVolumeState: VolumeState) => {
|
||||||
|
volumeState = newVolumeState;
|
||||||
|
send(DataTypes.VolumeChanged, {
|
||||||
|
volume: volumeState.state,
|
||||||
|
muted: volumeState.isMuted,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('ytmd:repeat-changed', (_, mode: RepeatMode) => {
|
||||||
|
repeat = mode;
|
||||||
|
send(DataTypes.RepeatChanged, { repeat });
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('ytmd:seeked', (_, t: number) => {
|
||||||
|
send(DataTypes.PositionChanged, { position: t });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.openapi(
|
||||||
|
createRoute({
|
||||||
|
method: 'get',
|
||||||
|
path: `/api/${API_VERSION}/ws`,
|
||||||
|
summary: 'websocket endpoint',
|
||||||
|
description: 'WebSocket endpoint for real-time updates',
|
||||||
|
responses: {
|
||||||
|
101: {
|
||||||
|
description: 'Switching Protocols',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
nodeWebSocket.upgradeWebSocket(() => ({
|
||||||
|
onOpen(_, ws) {
|
||||||
|
// "Unsafe argument of type `WSContext<WebSocket>` assigned to a parameter of type `WSContext<WebSocket>`. (@typescript-eslint/no-unsafe-argument)" ????? what?
|
||||||
|
sockets.add(ws as WSContext<WebSocket>);
|
||||||
|
|
||||||
|
ws.send(
|
||||||
|
JSON.stringify({
|
||||||
|
type: DataTypes.PlayerInfo,
|
||||||
|
...createPlayerState({
|
||||||
|
songInfo: lastSongInfo,
|
||||||
|
volumeState,
|
||||||
|
repeat,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
onClose(_, ws) {
|
||||||
|
sockets.delete(ws as WSContext<WebSocket>);
|
||||||
|
},
|
||||||
|
})) as (ctx: Context, next: Next) => Promise<Response>,
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -14,6 +14,7 @@ export type BackendType = {
|
|||||||
songInfo?: SongInfo;
|
songInfo?: SongInfo;
|
||||||
currentRepeatMode?: RepeatMode;
|
currentRepeatMode?: RepeatMode;
|
||||||
volumeState?: VolumeState;
|
volumeState?: VolumeState;
|
||||||
|
injectWebSocket?: (server: ReturnType<typeof serve>) => void;
|
||||||
|
|
||||||
init: (ctx: BackendContext<APIServerConfig>) => void;
|
init: (ctx: BackendContext<APIServerConfig>) => void;
|
||||||
run: (hostname: string, port: number) => void;
|
run: (hostname: string, port: number) => void;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { app } from 'electron';
|
import { app } from 'electron';
|
||||||
|
|
||||||
import registerCallback, { SongInfoEvent } from '@/providers/song-info';
|
import { registerCallback, SongInfoEvent } from '@/providers/song-info';
|
||||||
import { createBackend } from '@/utils';
|
import { createBackend } from '@/utils';
|
||||||
|
|
||||||
import { DiscordService } from './discord-service';
|
import { DiscordService } from './discord-service';
|
||||||
|
|||||||
@ -17,7 +17,8 @@ import {
|
|||||||
sendFeedback as sendFeedback_,
|
sendFeedback as sendFeedback_,
|
||||||
setBadge,
|
setBadge,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import registerCallback, {
|
import {
|
||||||
|
registerCallback,
|
||||||
cleanupName,
|
cleanupName,
|
||||||
getImage,
|
getImage,
|
||||||
MediaType,
|
MediaType,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { net } from 'electron';
|
import { net } from 'electron';
|
||||||
|
|
||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
import registerCallback from '@/providers/song-info';
|
import { registerCallback } from '@/providers/song-info';
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
type LumiaData = {
|
type LumiaData = {
|
||||||
|
|||||||
@ -8,7 +8,8 @@ import previousIcon from '@assets/media-icons-black/previous.png?asset&asarUnpac
|
|||||||
import { notificationImage, secondsToMinutes, ToastStyles } from './utils';
|
import { notificationImage, secondsToMinutes, ToastStyles } from './utils';
|
||||||
|
|
||||||
import getSongControls from '@/providers/song-controls';
|
import getSongControls from '@/providers/song-controls';
|
||||||
import registerCallback, {
|
import {
|
||||||
|
registerCallback,
|
||||||
type SongInfo,
|
type SongInfo,
|
||||||
SongInfoEvent,
|
SongInfoEvent,
|
||||||
} from '@/providers/song-info';
|
} from '@/providers/song-info';
|
||||||
|
|||||||
@ -5,7 +5,8 @@ import is from 'electron-is';
|
|||||||
import { notificationImage } from './utils';
|
import { notificationImage } from './utils';
|
||||||
import interactive from './interactive';
|
import interactive from './interactive';
|
||||||
|
|
||||||
import registerCallback, {
|
import {
|
||||||
|
registerCallback,
|
||||||
type SongInfo,
|
type SongInfo,
|
||||||
SongInfoEvent,
|
SongInfoEvent,
|
||||||
} from '@/providers/song-info';
|
} from '@/providers/song-info';
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { type BrowserWindow } from 'electron';
|
import { type BrowserWindow } from 'electron';
|
||||||
|
|
||||||
import registerCallback, {
|
import {
|
||||||
|
registerCallback,
|
||||||
MediaType,
|
MediaType,
|
||||||
type SongInfo,
|
type SongInfo,
|
||||||
SongInfoEvent,
|
SongInfoEvent,
|
||||||
|
|||||||
@ -14,7 +14,8 @@ import MprisPlayer, {
|
|||||||
type Track,
|
type Track,
|
||||||
} from '@jellybrick/mpris-service';
|
} from '@jellybrick/mpris-service';
|
||||||
|
|
||||||
import registerCallback, {
|
import {
|
||||||
|
registerCallback,
|
||||||
type SongInfo,
|
type SongInfo,
|
||||||
SongInfoEvent,
|
SongInfoEvent,
|
||||||
} from '@/providers/song-info';
|
} from '@/providers/song-info';
|
||||||
|
|||||||
@ -8,7 +8,8 @@ import previousIcon from '@assets/media-icons-black/previous.png?asset&asarUnpac
|
|||||||
|
|
||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
import getSongControls from '@/providers/song-controls';
|
import getSongControls from '@/providers/song-controls';
|
||||||
import registerCallback, {
|
import {
|
||||||
|
registerCallback,
|
||||||
type SongInfo,
|
type SongInfo,
|
||||||
SongInfoEvent,
|
SongInfoEvent,
|
||||||
} from '@/providers/song-info';
|
} from '@/providers/song-info';
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { nativeImage, type NativeImage, TouchBar } from 'electron';
|
|||||||
|
|
||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
import getSongControls from '@/providers/song-controls';
|
import getSongControls from '@/providers/song-controls';
|
||||||
import registerCallback, { SongInfoEvent } from '@/providers/song-info';
|
import { registerCallback, SongInfoEvent } from '@/providers/song-info';
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
import youtubeMusicIcon from '@assets/youtube-music.png?asset&asarUnpack';
|
import youtubeMusicIcon from '@assets/youtube-music.png?asset&asarUnpack';
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { net } from 'electron';
|
|||||||
import is from 'electron-is';
|
import is from 'electron-is';
|
||||||
|
|
||||||
import { createPlugin } from '@/utils';
|
import { createPlugin } from '@/utils';
|
||||||
import registerCallback from '@/providers/song-info';
|
import { registerCallback } from '@/providers/song-info';
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|
||||||
interface Data {
|
interface Data {
|
||||||
|
|||||||
@ -184,7 +184,7 @@ export type SongInfoCallback = (
|
|||||||
const callbacks: Set<SongInfoCallback> = new Set();
|
const callbacks: Set<SongInfoCallback> = new Set();
|
||||||
|
|
||||||
// This function will allow plugins to register callback that will be triggered when data changes
|
// This function will allow plugins to register callback that will be triggered when data changes
|
||||||
const registerCallback = (callback: SongInfoCallback) => {
|
export const registerCallback = (callback: SongInfoCallback) => {
|
||||||
callbacks.add(callback);
|
callbacks.add(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -282,5 +282,4 @@ export function cleanupName(name: string): string {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default registerCallback;
|
|
||||||
export const setupSongInfo = registerProvider;
|
export const setupSongInfo = registerProvider;
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import pausedTrayIconAsset from '@assets/youtube-music-tray-paused.png?asset&asa
|
|||||||
import config from './config';
|
import config from './config';
|
||||||
|
|
||||||
import { restart } from './providers/app-controls';
|
import { restart } from './providers/app-controls';
|
||||||
import registerCallback, { SongInfoEvent } from './providers/song-info';
|
import { registerCallback, SongInfoEvent } from './providers/song-info';
|
||||||
import getSongControls from './providers/song-controls';
|
import getSongControls from './providers/song-controls';
|
||||||
|
|
||||||
import { t } from '@/i18n';
|
import { t } from '@/i18n';
|
||||||
|
|||||||
Reference in New Issue
Block a user