当前位置: 首页 > news >正文

[开源项目] 一款功能强大的超高音质音乐播放器

文章目录

    • 项目简介
    • 预览地址
    • 功能实现
    • 音源解析
    • 项目启动
    • 项目打包
    • 关注我的CDDN博客


主要功能如下

  • 🎵 音乐推荐

  • 🔐 网易云账号登录与同步

  • 📝 功能

    • 播放历史记录
    • 歌曲收藏管理
    • 歌单 MV 排行榜 每日推荐
    • 自定义快捷键配置(全局或应用内)
  • 🎨 界面与交互

    • 沉浸式歌词显示(点击左下角封面进入)
    • 独立桌面歌词窗口
    • 明暗主题切换
    • 迷你模式
    • 状态栏控制
    • 多语言支持
  • 🎼 音乐功能

    • 支持歌单、MV、专辑等完整音乐服务
    • 灰色音乐资源解析(基于 @unblockneteasemusic/server)
    • 音乐单独解析
    • EQ均衡器
    • 定时播放 远程控制播放 倍速播放
    • 高品质音乐
    • 音乐文件下载(支持右键下载和批量下载, 附带歌词封面等信息)
    • 搜索 MV 音乐 专辑 歌单 bilibili
    • 音乐单独选择音源解析
  • 🚀 技术特性

    • 本地化服务,无需依赖在线API (基于 netease-cloud-music-api)
    • 全平台适配(Desktop & Web & Mobile Web & Android<测试> & ios<后续>)

项目简介

一个第三方音乐播放器、本地服务、桌面歌词、音乐下载、最高音质

预览地址

http://music.alger.fun/

功能实现

在这里插入图片描述

/*** 快捷键配置*/
export interface ShortcutConfig {/** 快捷键字符串 */key: string;/** 是否启用 */enabled: boolean;/** 作用范围: global(全局) 或 app(仅应用内) */scope: 'global' | 'app';
}/*** 快捷键配置集合*/
export interface ShortcutsConfig {[key: string]: ShortcutConfig;
}

在这里插入图片描述

import { BrowserWindow, IpcMain, screen } from 'electron';
import Store from 'electron-store';
import path, { join } from 'path';const store = new Store();
let lyricWindow: BrowserWindow | null = null;// 跟踪拖动状态
let isDragging = false;// 添加窗口大小变化防护
let originalSize = { width: 0, height: 0 };const createWin = () => {console.log('Creating lyric window');// 获取保存的窗口位置const windowBounds =(store.get('lyricWindowBounds') as {x?: number;y?: number;width?: number;height?: number;displayId?: number;}) || {};const { x, y, width, height, displayId } = windowBounds;// 获取所有屏幕的信息const displays = screen.getAllDisplays();let isValidPosition = false;let targetDisplay = displays[0]; // 默认使用主显示器// 如果有显示器ID,尝试按ID匹配if (displayId) {const matchedDisplay = displays.find((d) => d.id === displayId);if (matchedDisplay) {targetDisplay = matchedDisplay;console.log('Found matching display by ID:', displayId);}}// 验证位置是否在任何显示器的范围内if (x !== undefined && y !== undefined) {for (const display of displays) {const { bounds } = display;if (x >= bounds.x - 50 && // 允许一点偏移,避免卡在边缘x < bounds.x + bounds.width + 50 &&y >= bounds.y - 50 &&y < bounds.y + bounds.height + 50) {isValidPosition = true;targetDisplay = display;break;}}}// 确保宽高合理const defaultWidth = 800;const defaultHeight = 200;const maxWidth = 1600; // 设置最大宽度限制const maxHeight = 800; // 设置最大高度限制const validWidth = width && width > 0 && width <= maxWidth ? width : defaultWidth;const validHeight = height && height > 0 && height <= maxHeight ? height : defaultHeight;// 确定窗口位置let windowX = isValidPosition ? x : undefined;let windowY = isValidPosition ? y : undefined;// 如果位置无效,默认在当前显示器中居中if (windowX === undefined || windowY === undefined) {windowX = targetDisplay.bounds.x + (targetDisplay.bounds.width - validWidth) / 2;windowY = targetDisplay.bounds.y + (targetDisplay.bounds.height - validHeight) / 2;}lyricWindow = new BrowserWindow({width: validWidth,height: validHeight,x: windowX,y: windowY,frame: false,show: false,transparent: true,opacity: 1,hasShadow: false,alwaysOnTop: true,resizable: true,roundedCorners: false,titleBarStyle: 'hidden',titleBarOverlay: false,// 添加跨屏幕支持选项webPreferences: {preload: join(__dirname, '../preload/index.js'),sandbox: false,contextIsolation: true},backgroundColor: '#00000000'});// 监听窗口关闭事件lyricWindow.on('closed', () => {if (lyricWindow) {lyricWindow.destroy();lyricWindow = null;}});// 监听窗口大小变化事件,保存新的尺寸lyricWindow.on('resize', () => {// 如果正在拖动,忽略大小调整事件if (isDragging) return;if (lyricWindow && !lyricWindow.isDestroyed()) {const [width, height] = lyricWindow.getSize();const [x, y] = lyricWindow.getPosition();// 保存窗口位置和大小store.set('lyricWindowBounds', { x, y, width, height });}});lyricWindow.on('blur', () => lyricWindow && lyricWindow.setMaximizable(false))return lyricWindow;
};export const loadLyricWindow = (ipcMain: IpcMain, mainWin: BrowserWindow): void => {const showLyricWindow = () => {if (lyricWindow && !lyricWindow.isDestroyed()) {if (lyricWindow.isMinimized()) {lyricWindow.restore();}lyricWindow.focus();lyricWindow.show();return true;}return false;};ipcMain.on('open-lyric', () => {console.log('Received open-lyric request');if (showLyricWindow()) {return;}console.log('Creating new lyric window');const win = createWin();if (!win) {console.error('Failed to create lyric window');return;}if (process.env.NODE_ENV === 'development') {win.webContents.openDevTools({ mode: 'detach' });win.loadURL(`${process.env.ELECTRON_RENDERER_URL}/#/lyric`);} else {const distPath = path.resolve(__dirname, '../renderer');win.loadURL(`file://${distPath}/index.html#/lyric`);}win.setMinimumSize(600, 200);win.setSkipTaskbar(true);win.once('ready-to-show', () => {console.log('Lyric window ready to show');win.show();});});ipcMain.on('send-lyric', (_, data) => {if (lyricWindow && !lyricWindow.isDestroyed()) {try {lyricWindow.webContents.send('receive-lyric', data);} catch (error) {console.error('Error processing lyric data:', error);}}});ipcMain.on('top-lyric', (_, data) => {if (lyricWindow && !lyricWindow.isDestroyed()) {lyricWindow.setAlwaysOnTop(data);}});ipcMain.on('close-lyric', () => {if (lyricWindow && !lyricWindow.isDestroyed()) {lyricWindow.webContents.send('lyric-window-close');mainWin.webContents.send('lyric-control-back', 'close');mainWin.webContents.send('lyric-window-closed');lyricWindow.destroy();lyricWindow = null;}});// 处理鼠标事件ipcMain.on('mouseenter-lyric', () => {if (lyricWindow && !lyricWindow.isDestroyed()) {lyricWindow.setIgnoreMouseEvents(true);}});ipcMain.on('mouseleave-lyric', () => {if (lyricWindow && !lyricWindow.isDestroyed()) {lyricWindow.setIgnoreMouseEvents(false);}});// 开始拖动时设置标志ipcMain.on('lyric-drag-start', () => {isDragging = true;if (lyricWindow && !lyricWindow.isDestroyed()) {// 记录原始窗口大小const [width, height] = lyricWindow.getSize();originalSize = { width, height };// 在拖动时暂时禁用大小调整lyricWindow.setResizable(false);}});// 结束拖动时清除标志ipcMain.on('lyric-drag-end', () => {isDragging = false;if (lyricWindow && !lyricWindow.isDestroyed()) {// 确保窗口大小恢复原样lyricWindow.setSize(originalSize.width, originalSize.height);// 拖动结束后恢复可调整大小lyricWindow.setResizable(true);}});// 处理拖动移动ipcMain.on('lyric-drag-move', (_, { deltaX, deltaY }) => {if (!lyricWindow || lyricWindow.isDestroyed() || !isDragging) return;const [currentX, currentY] = lyricWindow.getPosition();// 使用记录的原始大小,而不是当前大小const windowWidth = originalSize.width;const windowHeight = originalSize.height;// 计算新位置const newX = currentX + deltaX;const newY = currentY + deltaY;try {// 获取当前鼠标所在的显示器const mousePoint = screen.getCursorScreenPoint();const currentDisplay = screen.getDisplayNearestPoint(mousePoint);// 拖动期间使用setBounds确保大小不变,使用false避免动画卡顿lyricWindow.setBounds({x: newX,y: newY,width: windowWidth,height: windowHeight},false);// 更新存储的位置const windowBounds = {x: newX,y: newY,width: windowWidth,height: windowHeight,displayId: currentDisplay.id // 记录当前显示器ID,有助于多屏幕处理};store.set('lyricWindowBounds', windowBounds);} catch (error) {console.error('Error during window drag:', error);// 出错时尝试使用更简单的方法lyricWindow.setPosition(newX, newY);}});// 添加鼠标穿透事件处理ipcMain.on('set-ignore-mouse', (_, shouldIgnore) => {if (!lyricWindow || lyricWindow.isDestroyed()) return;lyricWindow.setIgnoreMouseEvents(shouldIgnore, { forward: true });});// 添加播放控制处理ipcMain.on('control-back', (_, command) => {console.log('command', command);if (mainWin && !mainWin.isDestroyed()) {console.log('Sending control-back command:', command);mainWin.webContents.send('lyric-control-back', command);}});
};

在这里插入图片描述

音源解析

import { ipcMain } from 'electron';
import Store from 'electron-store';
import fs from 'fs';
import server from 'netease-cloud-music-api-alger/server';
import os from 'os';
import path from 'path';import { unblockMusic, type Platform } from './unblockMusic';const store = new Store();
if (!fs.existsSync(path.resolve(os.tmpdir(), 'anonymous_token'))) {fs.writeFileSync(path.resolve(os.tmpdir(), 'anonymous_token'), '', 'utf-8');
}// 设置音乐解析的处理程序
ipcMain.handle('unblock-music', async (_event, id, songData, enabledSources) => {try {const result = await unblockMusic(id, songData, 1, enabledSources as Platform[]);return result;} catch (error) {console.error('音乐解析失败:', error);return { error: (error as Error).message || '未知错误' };}
});async function startMusicApi(): Promise<void> {console.log('MUSIC API STARTED');const port = (store.get('set') as any).musicApiPort || 30488;await server.serveNcmApi({port});
}export { startMusicApi };

在这里插入图片描述

项目启动

npm install
npm run dev

项目打包

# web
npm run build 
# win
npm run build:win
# mac
npm run build:mac
# linux
npm run build:linux

💯 👉【我的更新汇总】
👉项目地址:

关注我的CDDN博客

更多资源可以查看我的CSDN博客

相关文章:

  • 无网络docker镜像迁移
  • 曲线匹配,让数据点在匹配数据的一侧?
  • ADS学习笔记(五) 谐波平衡仿真
  • 电子电路原理第十七章(线性运算放大器电路的应用)
  • 开疆智能Profinet转Profibus网关连接韦普泰克工业称重仪表配置案例
  • 【Qt开发】输入类控件
  • Python 字符串相似度计算:方法、应用与实践
  • WeakAuras Lua Script [ICC BOSS 11 - Sindragosa]
  • ROS2学习(10)------ROS2参数
  • STM32F103_Bootloader程序开发03 - 启动入口与升级模式判断(boot_entry.c与boot_entry.h)
  • SOC-ESP32S3部分:13-定时器
  • 多查询检索在RAG中的应用及为什么平均嵌入向量效果好
  • 【蓝桥杯嵌入式】【模块】八、UART相关配置及代码模板
  • [De1CTF 2019]SSRF Me
  • 今日行情明日机会——20250526
  • Redis批量删除Key的三种方式
  • 【杂谈】------使用 __int128 处理超大整数计算
  • MyBatis深度解析:XML/注解配置与动态SQL编写实战
  • TinyVue v3.23.0 正式发布:增加 NumberAnimation 数字动画组件、支持全局配置组件的 props
  • FreeRTOS 在物联网传感器节点的应用:低功耗实时数据采集与传输方案
  • wordpress的tag链接url/武汉seo关键字优化
  • 建设银行宁德分行网站/搜索引擎排名优化程序
  • 建站快车金牌代理商/长沙哪家网络公司做网站好
  • 网站开发是无形资产/网站建设优化推广系统
  • 城市旅游网站开发/怎么在网上推广产品
  • 输入法网站设计/电子商务网站建设多少钱