diff --git a/package.json b/package.json
index 8b85075..8fa399f 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,9 @@
"@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^4.0.0",
"@unblockneteasemusic/server": "^0.27.8-patch.1",
+ "cors": "^2.8.5",
"electron-store": "^8.1.0",
+ "express": "^4.18.2",
"electron-updater": "^6.6.2",
"font-list": "^1.5.1",
"netease-cloud-music-api-alger": "^4.26.1",
@@ -54,7 +56,7 @@
"autoprefixer": "^10.4.20",
"axios": "^1.7.7",
"cross-env": "^7.0.3",
- "electron": "^35.2.0",
+ "electron": "^36.0.0",
"electron-builder": "^25.1.8",
"electron-vite": "^3.1.0",
"eslint": "^9.0.0",
diff --git a/resources/html/remote-control.html b/resources/html/remote-control.html
new file mode 100644
index 0000000..59c2e7a
--- /dev/null
+++ b/resources/html/remote-control.html
@@ -0,0 +1,486 @@
+
+
+
+
+
+ AlgerMusicPlayer 远程控制
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 准备就绪
+
+
+
+
+
\ No newline at end of file
diff --git a/src/i18n/lang/en-US/common.ts b/src/i18n/lang/en-US/common.ts
index e6bb675..b943617 100644
--- a/src/i18n/lang/en-US/common.ts
+++ b/src/i18n/lang/en-US/common.ts
@@ -26,6 +26,7 @@ export default {
delete: 'Delete',
refresh: 'Refresh',
retry: 'Retry',
+ reset: 'Reset',
validation: {
required: 'This field is required',
invalidInput: 'Invalid input',
diff --git a/src/i18n/lang/en-US/settings.ts b/src/i18n/lang/en-US/settings.ts
index 7235b30..153dde8 100644
--- a/src/i18n/lang/en-US/settings.ts
+++ b/src/i18n/lang/en-US/settings.ts
@@ -83,7 +83,9 @@ export default {
unlimitedDownload: 'Unlimited Download',
unlimitedDownloadDesc: 'Enable unlimited download mode for music , default limit 300 songs',
downloadPath: 'Download Directory',
- downloadPathDesc: 'Choose download location for music files'
+ downloadPathDesc: 'Choose download location for music files',
+ remoteControl: 'Remote Control',
+ remoteControlDesc: 'Set remote control function'
},
network: {
apiPort: 'Music API Port',
@@ -230,5 +232,15 @@ export default {
disableAll: 'All shortcuts disabled, please save to apply',
enableAll: 'All shortcuts enabled, please save to apply'
}
+ },
+ remoteControl: {
+ title: 'Remote Control',
+ enable: 'Enable Remote Control',
+ port: 'Port',
+ allowedIps: 'Allowed IPs',
+ addIp: 'Add IP',
+ emptyListHint: 'Empty list means allow all IPs',
+ saveSuccess: 'Remote control settings saved',
+ accessInfo: 'Remote control access address:',
}
};
diff --git a/src/i18n/lang/zh-CN/common.ts b/src/i18n/lang/zh-CN/common.ts
index 2d57b40..cbe7a4f 100644
--- a/src/i18n/lang/zh-CN/common.ts
+++ b/src/i18n/lang/zh-CN/common.ts
@@ -26,6 +26,7 @@ export default {
delete: '删除',
refresh: '刷新',
retry: '重试',
+ reset: '重置',
validation: {
required: '此项是必填的',
invalidInput: '输入无效',
diff --git a/src/i18n/lang/zh-CN/settings.json b/src/i18n/lang/zh-CN/settings.json
deleted file mode 100644
index 090be0a..0000000
--- a/src/i18n/lang/zh-CN/settings.json
+++ /dev/null
@@ -1,5 +0,0 @@
-"playback": {
- "musicSources": "音源设置",
- "musicSourcesDesc": "选择音乐解析使用的音源平台",
- "musicSourcesWarning": "至少需要选择一个音源平台"
-}
\ No newline at end of file
diff --git a/src/i18n/lang/zh-CN/settings.ts b/src/i18n/lang/zh-CN/settings.ts
index 4e888ba..52db911 100644
--- a/src/i18n/lang/zh-CN/settings.ts
+++ b/src/i18n/lang/zh-CN/settings.ts
@@ -83,7 +83,9 @@ export default {
unlimitedDownload: '无限制下载',
unlimitedDownloadDesc: '开启后将无限制下载音乐(可能出现下载失败的情况), 默认限制 300 首',
downloadPath: '下载目录',
- downloadPathDesc: '选择音乐文件的下载位置'
+ downloadPathDesc: '选择音乐文件的下载位置',
+ remoteControl: '远程控制',
+ remoteControlDesc: '设置远程控制功能'
},
network: {
apiPort: '音乐API端口',
@@ -230,5 +232,15 @@ export default {
disableAll: '已禁用所有快捷键,请记得保存',
enableAll: '已启用所有快捷键,请记得保存'
}
+ },
+ remoteControl: {
+ title: '远程控制',
+ enable: '启用远程控制',
+ port: '服务端口',
+ allowedIps: '允许的IP地址',
+ addIp: '添加IP',
+ emptyListHint: '空列表表示允许所有IP访问',
+ saveSuccess: '远程控制设置已保存',
+ accessInfo: '远程控制访问地址:',
}
};
diff --git a/src/main/index.ts b/src/main/index.ts
index 542d4ae..70c5510 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -8,6 +8,7 @@ import { loadLyricWindow } from './lyric';
import { initializeConfig } from './modules/config';
import { initializeFileManager } from './modules/fileManager';
import { initializeFonts } from './modules/fonts';
+import { initializeRemoteControl } from './modules/remoteControl';
import { initializeShortcuts, registerShortcuts } from './modules/shortcuts';
import { initializeStats, setupStatsHandlers } from './modules/statsService';
import { initializeTray, updateCurrentSong, updatePlayState, updateTrayMenu } from './modules/tray';
@@ -66,6 +67,9 @@ function initialize() {
// 初始化快捷键
initializeShortcuts(mainWindow);
+ // 初始化远程控制服务
+ initializeRemoteControl(mainWindow);
+
// 初始化更新处理程序
setupUpdateHandlers(mainWindow);
}
diff --git a/src/main/modules/remoteControl.ts b/src/main/modules/remoteControl.ts
new file mode 100644
index 0000000..6a3d3b3
--- /dev/null
+++ b/src/main/modules/remoteControl.ts
@@ -0,0 +1,227 @@
+import { ipcMain } from 'electron';
+import express from 'express';
+import cors from 'cors';
+import os from 'os';
+import { getStore } from './config';
+import path from 'path';
+import fs from 'fs';
+
+// 定义远程控制相关接口
+export interface RemoteControlConfig {
+ enabled: boolean;
+ port: number;
+ allowedIps: string[];
+}
+
+// 默认配置
+export const defaultRemoteControlConfig: RemoteControlConfig = {
+ enabled: false,
+ port: 31888,
+ allowedIps: []
+};
+
+let app: express.Application | null = null;
+let server: any = null;
+let mainWindowRef: Electron.BrowserWindow | null = null;
+let currentSong: any = null;
+let isPlaying: boolean = false;
+
+// 获取本地IP地址
+function getLocalIpAddresses(): string[] {
+ const interfaces = os.networkInterfaces();
+ const addresses: string[] = [];
+
+ for (const key in interfaces) {
+ const iface = interfaces[key];
+ if (iface) {
+ for (const alias of iface) {
+ if (alias.family === 'IPv4' && !alias.internal) {
+ addresses.push(alias.address);
+ }
+ }
+ }
+ }
+
+ return addresses;
+}
+
+// 初始化远程控制服务
+export function initializeRemoteControl(mainWindow: Electron.BrowserWindow) {
+ mainWindowRef = mainWindow;
+ const store = getStore() as any;
+ let config = store.get('remoteControl') as RemoteControlConfig;
+
+ // 如果配置不存在,使用默认配置
+ if (!config) {
+ config = defaultRemoteControlConfig;
+ store.set('remoteControl', config);
+ }
+
+ // 监听当前歌曲变化
+ ipcMain.on('update-current-song', (_, song: any) => {
+ currentSong = song;
+ });
+
+ // 监听播放状态变化
+ ipcMain.on('update-play-state', (_, playing: boolean) => {
+ isPlaying = playing;
+ });
+
+ // 监听远程控制配置变化
+ ipcMain.on('update-remote-control-config', (_, newConfig: RemoteControlConfig) => {
+ if (server) {
+ stopServer();
+ }
+
+ store.set('remoteControl', newConfig);
+
+ if (newConfig.enabled) {
+ startServer(newConfig);
+ }
+ });
+
+ // 获取远程控制配置
+ ipcMain.handle('get-remote-control-config', () => {
+ const config = store.get('remoteControl') as RemoteControlConfig;
+ return config || defaultRemoteControlConfig;
+ });
+
+ // 获取本地IP地址
+ ipcMain.handle('get-local-ip-addresses', () => {
+ return getLocalIpAddresses();
+ });
+
+ // 如果启用了远程控制,启动服务器
+ if (config.enabled) {
+ startServer(config);
+ }
+}
+
+// 启动远程控制服务器
+function startServer(config: RemoteControlConfig) {
+ if (!mainWindowRef) {
+ console.error('主窗口未初始化,无法启动远程控制服务');
+ return;
+ }
+
+ app = express();
+
+ // 跨域配置
+ app.use(cors());
+ app.use(express.json());
+
+ // IP 过滤中间件
+ app.use((req, res, next) => {
+ const clientIp = req.ip || req.socket.remoteAddress || '';
+ const cleanIp = clientIp.replace(/^::ffff:/, ''); // 移除IPv6前缀
+ console.log('config',config)
+ if (config.allowedIps.length === 0 || config.allowedIps.includes(cleanIp)) {
+ next();
+ } else {
+ res.status(403).json({ error: '未授权的IP地址' });
+ }
+ });
+
+ // 路由配置
+ setupRoutes(app);
+
+ // 启动服务器
+ try {
+ server = app.listen(config.port, () => {
+ console.log(`远程控制服务已启动,监听端口: ${config.port}`);
+ });
+ } catch (error) {
+ console.error('启动远程控制服务失败:', error);
+ }
+}
+
+// 停止远程控制服务器
+function stopServer() {
+ if (server) {
+ server.close();
+ server = null;
+ app = null;
+ console.log('远程控制服务已停止');
+ }
+}
+
+// 设置路由
+function setupRoutes(app: express.Application) {
+ // 获取当前播放状态
+ app.get('/api/status', (_, res) => {
+ res.json({
+ isPlaying,
+ currentSong
+ });
+ });
+
+ // 播放/暂停
+ app.post('/api/toggle-play', (_, res) => {
+ if (!mainWindowRef) {
+ return res.status(500).json({ error: '主窗口未初始化' });
+ }
+ mainWindowRef.webContents.send('global-shortcut', 'togglePlay');
+ res.json({ success: true, message: '已发送播放/暂停指令' });
+ });
+
+ // 上一首
+ app.post('/api/prev', (_, res) => {
+ if (!mainWindowRef) {
+ return res.status(500).json({ error: '主窗口未初始化' });
+ }
+ mainWindowRef.webContents.send('global-shortcut', 'prevPlay');
+ res.json({ success: true, message: '已发送上一首指令' });
+ });
+
+ // 下一首
+ app.post('/api/next', (_, res) => {
+ if (!mainWindowRef) {
+ return res.status(500).json({ error: '主窗口未初始化' });
+ }
+ mainWindowRef.webContents.send('global-shortcut', 'nextPlay');
+ res.json({ success: true, message: '已发送下一首指令' });
+ });
+
+ // 音量加
+ app.post('/api/volume-up', (_, res) => {
+ if (!mainWindowRef) {
+ return res.status(500).json({ error: '主窗口未初始化' });
+ }
+ mainWindowRef.webContents.send('global-shortcut', 'volumeUp');
+ res.json({ success: true, message: '已发送音量加指令' });
+ });
+
+ // 音量减
+ app.post('/api/volume-down', (_, res) => {
+ if (!mainWindowRef) {
+ return res.status(500).json({ error: '主窗口未初始化' });
+ }
+ mainWindowRef.webContents.send('global-shortcut', 'volumeDown');
+ res.json({ success: true, message: '已发送音量减指令' });
+ });
+
+ // 收藏/取消收藏
+ app.post('/api/toggle-favorite', (_, res) => {
+ if (!mainWindowRef) {
+ return res.status(500).json({ error: '主窗口未初始化' });
+ }
+ mainWindowRef.webContents.send('global-shortcut', 'toggleFavorite');
+ res.json({ success: true, message: '已发送收藏/取消收藏指令' });
+ });
+
+ // 提供远程控制界面HTML
+ app.get('/', (_, res) => {
+ try {
+ const htmlPath = path.join(process.cwd(), 'resources', 'html', 'remote-control.html');
+ if (fs.existsSync(htmlPath)) {
+ res.sendFile(htmlPath);
+ } else {
+ res.status(404).send('远程控制界面文件未找到');
+ console.error('远程控制界面文件不存在:', htmlPath);
+ }
+ } catch (error) {
+ console.error('加载远程控制界面失败:', error);
+ res.status(500).send('加载远程控制界面失败');
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/renderer/components.d.ts b/src/renderer/components.d.ts
index 0d700f4..0c4f1b1 100644
--- a/src/renderer/components.d.ts
+++ b/src/renderer/components.d.ts
@@ -19,8 +19,10 @@ declare module 'vue' {
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
NCollapse: typeof import('naive-ui')['NCollapse']
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
+ NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
+ NDivider: typeof import('naive-ui')['NDivider']
NDrawer: typeof import('naive-ui')['NDrawer']
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
NDropdown: typeof import('naive-ui')['NDropdown']
@@ -50,6 +52,7 @@ declare module 'vue' {
NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs']
NTag: typeof import('naive-ui')['NTag']
+ NText: typeof import('naive-ui')['NText']
NTooltip: typeof import('naive-ui')['NTooltip']
NVirtualList: typeof import('naive-ui')['NVirtualList']
RouterLink: typeof import('vue-router')['RouterLink']
diff --git a/src/renderer/components/home/TopBanner.vue b/src/renderer/components/home/TopBanner.vue
index 6f9d0e0..c60385c 100644
--- a/src/renderer/components/home/TopBanner.vue
+++ b/src/renderer/components/home/TopBanner.vue
@@ -168,7 +168,6 @@ import {
setBackgroundImg
} from '@/utils';
import { getArtistDetail } from '@/api/artist';
-import { cloneDeep } from 'lodash';
const userStore = useUserStore();
const playerStore = usePlayerStore();
diff --git a/src/renderer/views/artist/detail.vue b/src/renderer/views/artist/detail.vue
index b6172aa..5c64a33 100644
--- a/src/renderer/views/artist/detail.vue
+++ b/src/renderer/views/artist/detail.vue
@@ -72,7 +72,7 @@
\ No newline at end of file