Skip to content

Repository files navigation

Simple Local Music Player logo

Simple Local Music Player

把本地音乐文件夹,变成浏览器里一打开就能听的完整播放器

自动提取封面与歌手信息、内嵌/外部 LRC 歌词、歌单管理、频谱可视化、深浅色主题 —— 全部本地运行,不上传、不注册、零广告。

Node.js >= 24 No Framework Built-in SQLite [HTTP Range Streaming] License MIT

体验在线 Demo

⚠️ 在线演示基于临时服务器资源,带宽与运行时均受限,并且由于经常需要更新代码,可能随时重启,请勿依赖于此。访问可能出现 502、加载缓慢等情况,敬请谅解。服务器配置:上古 MacBook Air。


目录

为什么用它 · 快速开始 · 特性一览 · 性能与架构 · 使用手册 · HTTP API · 开发者文档 · 许可证


为什么用它

把歌放进 music/,打开浏览器就是完整播放器:

  • 真 · 本地播放 —— 音乐文件从不出设备,localhost 直连,网络断开也能听;
  • 秒开不卡列表 —— 页面与 API 全部走本地 SQLite 元数据库,几千首歌曲的列表、封面、歌词也是毫秒级返回,不会逐首现解析 ID3;
  • 拿来即用 —— 无需数据库、无需构建、无需框架,一个 node server.js 起服务,浏览器即用;
  • 细节拉满 —— 内嵌封面、LRC 伴唱歌词、歌单、正则搜索、频谱、媒体键、深浅色,一个不少。

快速开始

前置条件

仅需 Node.js >= 24(元数据库使用内置 node:sqlite,无需任何额外安装)。

三步跑起来

git clone https://github.com/EndlessPixel/simple-local-music-player.git
cd simple-local-music-player

npm install        # 安装唯一的运行依赖:music-metadata(解析音频标签)
node server.js     # 或:bash start.sh / start.bat

在浏览器打开 http://localhost:18250 —— 服务默认监听 0.0.0.0,同局域网内手机也能访问。

放入音乐

在项目根目录创建 music/ 文件夹,直接扔歌进去即可,支持任意层级子目录:

music/
├── song1.mp3
├── song2.flac
├── 中文歌名.mp3
├── song_歌词.lrc        # 伴唱歌词(自动匹配)
└── album/
    ├── track01.mp3
    └── track01.lrc      # 同名歌词自动匹配

首次启动会自动把 music/ 扫描入库到 data/library.db;之后再重启只会做增量同步(见下文「性能与架构」)。

(可选)用 Docker 运行

无需本机安装 Node.js。镜像基于 node:24-alpine,内置 SQLite 元数据库,零额外配置:

# 构建镜像
docker build -t simple-local-music-player .

# 运行:把宿主音乐目录挂载进容器(:ro 只读挂载,杜绝容器误写)
docker run -d --name slmp \
  -p 18250:18250 \
  -v /你的/音乐目录:/app/music:ro \
  -v slmp-data:/app/data \
  simple-local-music-player
  • /app/music:音乐目录挂载点,建议 :ro 只读挂载;
  • /app/data:元数据库与封面缓存目录,用命名卷 slmp-data 持久化(不挂载则每次重建容器会重新扫描一遍);
  • 容器内以非 root 的 node 用户运行,访问 http://localhost:18250 即可使用。

若挂载的是本机目录(bind mount)且播放器无法读取,通常是宿主目录权限不足:确保该目录对容器用户(uid 1000)开放读/执行权限。


特性一览

播放核心

能力
多格式 MP3、FLAC、WAV、OGG、M4A、AAC、WMA
封面 自动提取音频内嵌封面(按内容哈希缓存,重复文件只存一份)
元数据 歌手、曲名、专辑、音轨号、风格、年份、时长、码率/采样率
歌词 内嵌歌词优先;外部 .lrc 智能匹配(忽略大小写、支持 _歌词 -歌词 等后缀)
变速 0.25x ~ 2.0x,跨歌曲记住你的习惯
频谱 播放时随音频实时绘制可视化频谱
进度 点按、拖拽进度条跳转

浏览与整理

  • 浏览视图:文件夹 / 歌手 / 专辑 一键切换(各视图分组状态分别记忆);歌手缺失时按「歌手 - 歌名」文件名启发式归类,专辑视图组内按音轨号排序
  • 目录分组、文件夹折叠记忆
  • 实时搜索过滤:多关键词、支持子目录名匹配、命中字符高亮;普通 / 正则双模式
  • 重复文件提示:基于内容 SHA-256 检测,行尾出现 ⧉ 角标,一键跳转定位另一份相同文件
  • 歌曲行元信息:格式 / Hi-Res 徽标、歌手 · 目录、时长
  • 歌曲多选、批量加入歌单
  • 歌单:下拉切换、新建、重命名、删除、批量加入、失效引用自动清理,全部引用型设计(见折叠的完整说明)
  • 一键下载当前歌曲;自动刷新开关
  • 播放模式:列表循环 / 单曲循环 / 随机

体验细节

  • 系统媒体键(Media Session):键盘上一首 / 下一首 / 播放暂停
  • 键盘快捷键:空格播放暂停,方向键控制音量与进度
  • 音量、倍速、主题、折叠状态等偏好自动记忆
  • 深浅色主题:按钮切换 + 跟随系统,状态持久化
  • 三种分享链接:?song_sha=… SHA-256 内容指纹(改名 / 移动不失效)、?song=…&folder=… 高精度、?song_id=… 简短 ID

性能与架构

以前每次打开列表都要逐首读文件解析 ID3,歌一多就明显卡顿。本项目把这一层做成了常驻元数据库

music/           音乐文件(唯一数据源)
   │
   │  增量扫描:比对 mtime + size,只动新增/变更/删除的文件
   ▼
data/library.db  SQLite:每首歌的元数据 + mtime + size + sha256 + 歌词全文 + 封面状态
   │
   │  接口按需读取,命中即返回;文件变化才“单文件自愈”
   ▼
/api/songs  /api/meta  /api/lyrics  /api/cover
  • 列表零解析 —— /api/songs 直接读库返回,一次请求几毫秒,不再实时遍历磁盘解析 ID3;
  • 增量同步 —— 启动后每分钟自动扫描 + POST /api/refresh 手动触发;只对新增 / mtime+size 变化的文件重新解析和哈希,重启秒开
  • 封面去重 —— 封面按歌曲内容 sha256 落盘到 data/covers/,同一首歌无论拷贝多少份都只存一张图;歌曲删除时同步清理孤儿缓存;
  • 自愈式查询 —— /api/meta/api/lyrics/api/cover 命中库记录即返回;若发现文件已变化则只重解析那一首并回写,保证不读到过期数据。

只依赖 Node 24 内置的 node:sqlite,无新增原生依赖,SQLite 文件同样留在项目内、可整体备份或删除重建。


使用手册

搜索表达式(普通 / 正则)

搜索框右侧可切换 普通正则 模式。普通模式支持多关键词(空格分隔、需全部命中),也能按子目录名搜索,并自动忽略空格 / - / _ 等分隔符差异,例如 周杰伦 晴天 可以命中 周杰伦 - 晴天.mp3正则模式输入框提示 输入正则表达式,如 周杰伦|林俊杰,即 JavaScript 正则,匹配歌名(文件名去扩展名),不区分大小写:

表达式 含义 匹配示例
周杰伦 包含「周杰伦」 周杰伦的歌
周杰伦|林俊杰 包含「周杰伦」或「林俊杰」 两位歌手的歌
^爱 以「爱」开头 爱如潮水
电音$ 以「电音」结尾 夜店电音
20\d\d 任意 2000–2099 年份 2008、2021
.*摇滚.* 包含「摇滚」 经典摇滚

输入非法正则时输入框显示红框提示,且不会过滤列表,避免误清空;想回到简单搜索,点回「普通」即可。

键盘快捷键
按键 功能
Space 播放 / 暂停
/ 快退 / 快进 5 秒
/ 音量 +5% / -5%
浏览视图(文件夹 / 歌手 / 专辑)与重复检测

列表上方有三个视图标签:文件夹 / 歌手 / 专辑(会记住你的选择)。

  • 文件夹:按磁盘目录分组,行为与旧版一致;
  • 歌手:优先使用音频标签的歌手,缺失时按文件名「歌手 - 歌名」启发式拆分,实在取不到归入「未知歌手」;
  • 专辑:按标签专辑名分组,组内按音轨号排序,无专辑信息归入「未知专辑」。

每个视图都支持搜索与歌单引用过滤(自定义歌单先在当前视图里按引用筛选再分组),点击任一行即可播放。

重复文件检测:以歌曲内容的 SHA-256 指纹比对,发现内容相同但路径不同的文件时,行尾会显示橙色 ⧉ 角标(悬停显示还有几份相同文件)。点击角标即跳转定位到「下一份」相同文件并闪烁高亮 —— 定位时若目标不在当前视图/搜索范围内,会自动切回文件夹视图并清空搜索。播放器是只读的,不会替你删除文件:确认重复后手动删掉多余副本,点刷新即可。

老库升级后服务会在首次启动时对全部歌曲做一次元数据回填(补专辑/音轨号/风格/年份,进度体现在启动日志的「回填」计数),之后照常增量扫描,不影响使用。

分享链接格式
类型 参数 示例 特点
SHA-256 song_sha ?song_sha=<64位hex> 以文件内容指纹定位,改名 / 移动 / 重名都不失效,链接最长
高精度 song + folder ?song=track.mp3&folder=album 准确性高但较长
简短 song_id ?song_id=12 链接短但目录变化后可能失效
外部 LRC 命名规则
歌曲文件 可自动匹配的 LRC 文件名
song.mp3 song.lrcSong.LRCsong_歌词.lrcsong-歌词.lrcsong_lrc.lrcsong_lyric.lrcsong_lyrics.lrc
歌单管理(完整行为说明)

歌单采用引用型设计:只保存对主列表歌曲的引用标识,不复制歌曲数据,数据存于浏览器 localStorage

  • 歌单下拉选择器 —— 内嵌歌曲列表上方;默认内置「全部歌曲」(显示完整主列表,不可改名/删除)
  • 新建歌单 —— 点击 +,名称不可为空、不可与已有歌单重名
  • 重命名 —— 点击 ✎;重名会被拦截,「全部歌曲」不可重命名
  • 删除 —— 点击 🗑,需二次确认;「全部歌曲」不可删除
  • 移出歌单 —— 自定义歌单每行右侧悬停出现 ✕,点击即移除
  • 批量加入 —— 歌曲行左侧勾选后点 ⇲,批量加入当前自定义歌单
  • 失效引用处理 —— 初始化与切换歌单时自动比对主列表;已删除/失效的歌曲标红,点击后弹窗确认剔除,切换歌单时也会静默自动清理
  • 所有变更即时写回 localStorage

HTTP API

方法 路径 说明
GET /api/songs 按文件夹分组的歌曲列表(读元数据库,毫秒级)
GET /api/library 全库轻量元数据行(专辑/歌手/时长/采样率/SHA-256 等,供浏览视图与重复检测)
POST /api/refresh 触发增量扫描,返回 202 后台异步执行
GET /api/cover?song=xxx&folder=yyy 返回内嵌封面(浏览器 24h 缓存)
GET /api/meta?song=xxx&folder=yyy 歌曲元数据:歌手、曲名、时长、码率、内容 SHA-256 等
GET /api/lyrics?song=xxx&folder=yyy 歌词(内嵌优先,回退外部 .lrc
GET /api/by-sha?sha=xxx 按文件内容 SHA-256 反查定位歌曲(sha 分享链接使用)
GET /<文件名>.mp3 播放 / 下载,支持 HTTP Range 流式

所有响应统一设置 CORS 与 CSP 安全头。


开发者文档

目录结构
├── server.js          # 后端:路由 / 静态文件 / 代理 / 流式响应
├── songstore.js       # 歌曲信息 SQLite 元数据库 + 增量扫描(mtime/size/sha256)
├── play.html          # 播放器页面
├── script.js          # 前端逻辑
├── style.css          # 样式表
├── favicon.svg        # 网站图标
├── package.json       # 项目配置与依赖
├── eslint.config.js   # ESLint 配置
├── start.sh           # Linux / macOS 启动脚本
├── start.bat          # Windows 启动脚本
├── Dockerfile         # Docker 镜像构建(可选使用)
├── .dockerignore      # Docker 构建上下文排除项
├── music/             # 你的音乐目录(需自行创建)
├── data/              # 运行时生成:library.db 元数据库 + covers/ 封面缓存(勿提交)
├── old/               # 历史版本(已废弃)
└── old-2/             # 历史版本(已废弃)
技术栈与后端架构亮点
  • 后端 — Node.js 原生 http 模块 + 流式文件传输,唯一的运行时依赖是 music-metadata(解析音频标签)
  • 前端 — 原生 HTML / CSS / JavaScript(ESM),无框架
  • 元数据库 — Node 内置 node:sqlite,持久化到 data/library.db
  • 图标Ionicons v7(本地代码同源代理,无跨域外链)

后端实现要点:

  • SQLite 元数据库 —— 扫描时一次性解析 ID3 / 格式并入库;接口只读库,不再每次解析音频
  • 增量同步 —— 仅对比 mtime + size,只对新增 / 变化 / 删除的歌曲重新解析;重启 0 次解析
  • 封面内容寻址缓存 —— 按 sha256 落盘,重复内容只存一份,随歌曲删除自动清理
  • HTTP Range 流式传输 —— 全异步 I/O,支持拖动进度秒开、断点续播
  • 路径安全 —— 统一处理 URL 解码、.. 穿越检测、目录边界校验
  • 通用代理 —— proxyRequest 复用 Ionicons 与 GitHub API 代理
  • 响应头中间件 —— 统一 CORS / CSP 设置;集中配置管理端口、扩展名、MIME 等
本地存储(localStorage 键)
说明
musicVolume 音量值 (0-100)
musicSpeed 播放速度 (0.25 ~ 2.0)
theme-preference 主题偏好 (light / dark)
collapsedFolders 已折叠的文件夹列表 (JSON)
musicAutoRefreshEnabled 自动刷新开关 ('0' / '1')
musicSearchHistory 搜索历史 (JSON, 最多 20 条)

许可证

MIT © EndlessPixel

About

Description Short description of this repository Website Enter a valid URL Topics (separate with spaces)

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Contributors

Languages