mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-07-16 23:57:30 +08:00
bc46024499
- 新建 DownloadManager 类(主进程),每个任务独立 AbortController 控制 - 新建 Pinia useDownloadStore 作为渲染进程单一数据源 - 支持暂停/恢复/取消下载,支持断点续传(Range header) - 批量下载全部完成后发送汇总系统通知,单首不重复通知 - 并发数可配置(1-5),队列持久化(重启后恢复) - 修复下载列表不全、封面加载失败、通知重复等 bug - 修复本地/下载歌曲歌词加载:优先从 ID3/FLAC 元数据提取,API 作为 fallback - 删除 useDownloadStatus.ts,统一状态管理 - DownloadDrawer/DownloadPage 全面重写,移除 @apply 违规 - 新增 5 语言 i18n 键值(暂停/恢复/取消/排队中等)
167 lines
7.2 KiB
TypeScript
167 lines
7.2 KiB
TypeScript
import { electronAPI } from '@electron-toolkit/preload';
|
|
import type { IpcRendererEvent } from 'electron';
|
|
import { contextBridge, ipcRenderer } from 'electron';
|
|
|
|
import type { AppUpdateState } from '../shared/appUpdate';
|
|
|
|
// Custom APIs for renderer
|
|
const api = {
|
|
minimize: () => ipcRenderer.send('minimize-window'),
|
|
maximize: () => ipcRenderer.send('maximize-window'),
|
|
close: () => ipcRenderer.send('close-window'),
|
|
quitApp: () => ipcRenderer.send('quit-app'),
|
|
dragStart: (data) => ipcRenderer.send('drag-start', data),
|
|
miniTray: () => ipcRenderer.send('mini-tray'),
|
|
miniWindow: () => ipcRenderer.send('mini-window'),
|
|
restore: () => ipcRenderer.send('restore-window'),
|
|
restart: () => ipcRenderer.send('restart'),
|
|
resizeWindow: (width, height) => ipcRenderer.send('resize-window', width, height),
|
|
resizeMiniWindow: (showPlaylist) => ipcRenderer.send('resize-mini-window', showPlaylist),
|
|
openLyric: () => ipcRenderer.send('open-lyric'),
|
|
sendLyric: (data) => ipcRenderer.send('send-lyric', data),
|
|
sendSong: (data) => ipcRenderer.send('update-current-song', data),
|
|
unblockMusic: (id, data, enabledSources) =>
|
|
ipcRenderer.invoke('unblock-music', id, data, enabledSources),
|
|
importCustomApiPlugin: () => ipcRenderer.invoke('import-custom-api-plugin'),
|
|
importLxMusicScript: () => ipcRenderer.invoke('import-lx-music-script'),
|
|
// 歌词窗口关闭事件
|
|
onLyricWindowClosed: (callback: () => void) => {
|
|
ipcRenderer.on('lyric-window-closed', () => callback());
|
|
},
|
|
// 歌词窗口就绪事件(Vue 加载完成,可以接收数据)
|
|
onLyricWindowReady: (callback: () => void) => {
|
|
ipcRenderer.on('lyric-window-ready', () => callback());
|
|
},
|
|
getAppUpdateState: () => ipcRenderer.invoke('app-update:get-state') as Promise<AppUpdateState>,
|
|
checkAppUpdate: (manual = false) =>
|
|
ipcRenderer.invoke('app-update:check', { manual }) as Promise<AppUpdateState>,
|
|
downloadAppUpdate: () => ipcRenderer.invoke('app-update:download') as Promise<AppUpdateState>,
|
|
installAppUpdate: () => ipcRenderer.invoke('app-update:quit-and-install') as Promise<boolean>,
|
|
openAppUpdatePage: () => ipcRenderer.invoke('app-update:open-release-page') as Promise<boolean>,
|
|
onAppUpdateState: (callback: (state: AppUpdateState) => void) => {
|
|
ipcRenderer.on('app-update:state', (_event, state: AppUpdateState) => callback(state));
|
|
},
|
|
removeAppUpdateListeners: () => {
|
|
ipcRenderer.removeAllListeners('app-update:state');
|
|
},
|
|
// 语言相关
|
|
onLanguageChanged: (callback: (locale: string) => void) => {
|
|
ipcRenderer.on('language-changed', (_event, locale) => {
|
|
callback(locale);
|
|
});
|
|
},
|
|
// 歌词缓存相关
|
|
invoke: (channel: string, ...args: any[]) => {
|
|
const validChannels = [
|
|
'get-lyrics',
|
|
'clear-lyrics-cache',
|
|
'get-system-fonts',
|
|
'get-cached-lyric',
|
|
'cache-lyric',
|
|
'clear-lyric-cache',
|
|
'scan-local-music',
|
|
'scan-local-music-with-stats',
|
|
'parse-local-music-metadata'
|
|
];
|
|
if (validChannels.includes(channel)) {
|
|
return ipcRenderer.invoke(channel, ...args);
|
|
}
|
|
return Promise.reject(new Error(`未授权的 IPC 通道: ${channel}`));
|
|
},
|
|
// 搜索建议
|
|
getSearchSuggestions: (keyword: string) => ipcRenderer.invoke('get-search-suggestions', keyword),
|
|
|
|
// 落雪音乐 HTTP 请求(绕过 CORS)
|
|
lxMusicHttpRequest: (request: { url: string; options: any; requestId: string }) =>
|
|
ipcRenderer.invoke('lx-music-http-request', request),
|
|
|
|
lxMusicHttpCancel: (requestId: string) => ipcRenderer.invoke('lx-music-http-cancel', requestId),
|
|
|
|
// 本地音乐扫描相关
|
|
scanLocalMusic: (folderPath: string) => ipcRenderer.invoke('scan-local-music', folderPath),
|
|
scanLocalMusicWithStats: (folderPath: string) =>
|
|
ipcRenderer.invoke('scan-local-music-with-stats', folderPath),
|
|
parseLocalMusicMetadata: (filePaths: string[]) =>
|
|
ipcRenderer.invoke('parse-local-music-metadata', filePaths),
|
|
|
|
// Download manager
|
|
downloadAdd: (task: any) => ipcRenderer.invoke('download:add', task),
|
|
downloadAddBatch: (tasks: any) => ipcRenderer.invoke('download:add-batch', tasks),
|
|
downloadPause: (taskId: string) => ipcRenderer.invoke('download:pause', taskId),
|
|
downloadResume: (taskId: string) => ipcRenderer.invoke('download:resume', taskId),
|
|
downloadCancel: (taskId: string) => ipcRenderer.invoke('download:cancel', taskId),
|
|
downloadCancelAll: () => ipcRenderer.invoke('download:cancel-all'),
|
|
downloadGetQueue: () => ipcRenderer.invoke('download:get-queue'),
|
|
downloadSetConcurrency: (n: number) => ipcRenderer.send('download:set-concurrency', n),
|
|
downloadGetCompleted: () => ipcRenderer.invoke('download:get-completed'),
|
|
downloadDeleteCompleted: (filePath: string) =>
|
|
ipcRenderer.invoke('download:delete-completed', filePath),
|
|
downloadClearCompleted: () => ipcRenderer.invoke('download:clear-completed'),
|
|
getEmbeddedLyrics: (filePath: string) =>
|
|
ipcRenderer.invoke('download:get-embedded-lyrics', filePath),
|
|
downloadProvideUrl: (taskId: string, url: string) =>
|
|
ipcRenderer.invoke('download:provide-url', { taskId, url }),
|
|
onDownloadProgress: (cb: (data: any) => void) => {
|
|
ipcRenderer.on('download:progress', (_event: any, data: any) => cb(data));
|
|
},
|
|
onDownloadStateChange: (cb: (data: any) => void) => {
|
|
ipcRenderer.on('download:state-change', (_event: any, data: any) => cb(data));
|
|
},
|
|
onDownloadBatchComplete: (cb: (data: any) => void) => {
|
|
ipcRenderer.on('download:batch-complete', (_event: any, data: any) => cb(data));
|
|
},
|
|
onDownloadRequestUrl: (cb: (data: any) => void) => {
|
|
ipcRenderer.on('download:request-url', (_event: any, data: any) => cb(data));
|
|
},
|
|
removeDownloadListeners: () => {
|
|
ipcRenderer.removeAllListeners('download:progress');
|
|
ipcRenderer.removeAllListeners('download:state-change');
|
|
ipcRenderer.removeAllListeners('download:batch-complete');
|
|
ipcRenderer.removeAllListeners('download:request-url');
|
|
}
|
|
};
|
|
|
|
// 创建带类型的ipcRenderer对象,暴露给渲染进程
|
|
const ipc = {
|
|
// 发送消息到主进程(无返回值)
|
|
send: (channel: string, ...args: any[]) => {
|
|
ipcRenderer.send(channel, ...args);
|
|
},
|
|
// 调用主进程方法(有返回值)
|
|
invoke: (channel: string, ...args: any[]) => {
|
|
return ipcRenderer.invoke(channel, ...args);
|
|
},
|
|
// 监听主进程消息
|
|
on: (channel: string, listener: (...args: any[]) => void) => {
|
|
const wrappedListener = (_event: IpcRendererEvent, ...args: any[]) => listener(...args);
|
|
ipcRenderer.on(channel, wrappedListener);
|
|
return () => {
|
|
ipcRenderer.removeListener(channel, wrappedListener);
|
|
};
|
|
},
|
|
// 移除所有监听器
|
|
removeAllListeners: (channel: string) => {
|
|
ipcRenderer.removeAllListeners(channel);
|
|
}
|
|
};
|
|
|
|
// Use `contextBridge` APIs to expose Electron APIs to
|
|
// renderer only if context isolation is enabled, otherwise
|
|
// just add to the DOM global.
|
|
if (process.contextIsolated) {
|
|
try {
|
|
contextBridge.exposeInMainWorld('electron', electronAPI);
|
|
contextBridge.exposeInMainWorld('api', api);
|
|
contextBridge.exposeInMainWorld('ipcRenderer', ipc);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
} else {
|
|
// @ts-ignore (define in dts)
|
|
window.electron = electronAPI;
|
|
// @ts-ignore (define in dts)
|
|
window.api = api;
|
|
// @ts-ignore (define in dts)
|
|
window.ipcRenderer = ipc;
|
|
}
|