如果你已经厌倦了那些动辄几百MB、一启动就抢内存的"全家桶"音乐播放器,那么这个叫Music_nano的小东西可能会让你眼前一亮。它的定位非常简单:一个把"播放音乐"这件事做到极致的轻量级命令行工具。没有封面墙、没有社交功能、没有推荐算法,只有一个干净利落的播放器内核,加上一个顺手好用的控制界面,跑在树莓派、老笔记本、云服务器甚至嵌入式板子上都毫无压力。
这篇文章不是官方文档的复读,而是我自己从零构建 Music_nano 的完整记录。我会把为什么要这么设计、音频解码器怎么选、播放队列怎么管、踩过哪些坑,以及最后怎么把它压到几十兆内存的整个过程都摊开讲清楚。不管你是想找个轻量替代方案,还是想自己动手写一个类似的迷你播放器,里面都有可以直接抄作业的东西。
1. 项目定位:先想清楚"极简"到底意味着什么
很多项目一上来就堆功能,结果做成了四不像。Music_nano 的出发点恰恰相反,我在动手之前先问了自己一个问题:一个音乐播放器,最核心的体验到底是什么?答案很简单,就是三件事——能解码、能出声、能切歌。至于歌词滚动、可视化频谱、歌单同步这些,全都属于"锦上添花",不属于"雪中送炭"。
1.1 解决的核心痛点
现在的播放器普遍存在几个让人抓狂的问题。首先是资源占用离谱,Electron 套壳的播放器开个界面就能吃 500MB 内存,这在只有 1GB 内存的树莓派上几乎不可用。其次是启动慢,从点击图标到能听到声音,快的要两三秒,慢的能等上十秒。再就是操作路径长,想切一首歌,要鼠标点好几下,快捷键还不一定能自定义。
Music_nano 的定位就是把这几个痛点一次性解决掉。整个程序编译后只有几兆大小,运行时内存占用控制在 50MB 以内,启动速度在本地文件系统上能做到 300 毫秒以内出声音。操作上完全走命令行交互,所有控制动作都绑定快捷键和管道命令,配合 tmux 或者直接挂后台都行,真正做到"装上就忘掉它,只听歌就行"。
1.2 适用场景与目标用户
这个工具不是面向所有人的,它的目标用户很明确。首先是树莓派和 NAS 玩家,这类设备常年挂在角落,资源紧张,需要一个不吃内存的播放方案。其次是开发者和命令行爱好者,习惯在终端里解决一切问题,不希望为一个播放器单独开一个 GUI。再就是老设备爱好者,手头有吃灰的旧笔记本,装个大播放器卡得不行,但跑 Music_nano 依然流畅。
还有一个容易被忽略的场景是服务器环境的背景音乐。很多人在服务器上挂着远程开发环境,或者跑自动化任务,想放点背景音乐,但服务器上没有桌面环境。Music_nano 只需一个终端就能跑起来,配合 SSH 远程控制,完全不受环境限制。
1.3 与重型播放器的核心差异
我整理了一张对比表,看完你应该就明白为什么这个项目值得花时间折腾:
| 维度 | Music_nano | 重型图形播放器 |
|---|---|---|
| 安装包体积 | 约 5MB | 100MB 起步 |
| 内存占用 | 30~50MB | 300MB 起步 |
| 启动到出声 | 约 300ms | 2~10 秒 |
| 操作方式 | 快捷键/管道命令 | 鼠标点击 |
| 音频格式 | mp3/flac/wav/aac/ogg | 全覆盖 |
| 网络依赖 | 完全离线 | 常需联网 |
核心差异其实就是"少即是多"的实践。去掉界面层、社交层、推荐层之后,播放器就回归了它的本质——一个高效处理音频数据的工具。这也是为什么我做这个项目时,第一条原则就是:任何功能如果不能让"听歌"这件事更流畅,就坚决不做。
2. 技术选型:音频解码这块硬骨头怎么啃
坦白说,写播放器的界面一点不难,真正硬核的是音频解码和输出。Music_nano 刚立项的时候,我在技术选型上花了好几天时间,反复比较了几个方案,最后才定下来。这部分的经验对任何想做音频工具的人都值得一看。
2.1 音频后端对比:为什么最终选了 miniaudio
市面上主流的音频解码方案大概有这么几类。第一类是让底层系统帮你解,比如 Windows 的 Media Foundation,macOS 的 AVFoundation,Linux 的 GStreamer,好处是支持格式多,坏处是每个平台都要写一套适配代码,维护地狱。第二类是自己挨个接解码库,mp3 用 libmpg123,flac 用 libFLAC,aac 用 fdk-aac,这种方案灵活但是工程量爆炸,光是处理格式头就要写一大堆边角代码。
第三类就是 Music_nano 最终采用的方案:直接基于 miniaudio 这个单头文件库来做。它把解码和音频输出统一封装好了,底层根据平台自动选择 ALSA、CoreAudio 或者 WASAPI,对外暴露一套干净的 C API。我只需要调用ma_decoder系列函数就能读取压缩音频并解码成 PCM 数据,再通过ma_device把数据推给声卡播放,完全不用关心底层细节。
对比下来,用 miniaudio 有几个实打实的好处。一是单文件,拷贝进工程就能编译,省掉了复杂的依赖管理;二是绑定层丰富,除了 C/C++,还有 Rust、Zig、C# 的官方绑定,扩展性极强;三是内置了对 mp3、flac、wav、aac、ogg、opus、wma 等几乎所有常见格式的原生解码支持,不需要额外引入一堆解码库。
2.2 输出设备管理:直接绑定默认设备
音频输出的管理也是容易翻车的地方。很多播放器会花很大精力做一个音频设备列表,让你在多个输出设备之间手动切换。Music_nano 的选择是把这部分做到最简:启动时枚举一次系统设备,默认使用系统默认输出设备,不提供运行时热切换。
这个决定在普通笔记本上没任何影响,因为大部分人只有一个输出设备。但在树莓派上会有点意思,树莓派默认的音频设备经常是 HDMI,而不是 3.5mm 耳机孔,这需要你在系统层面通过 raspi-config 或者配置文件把默认设备改掉,Music_nano 只需要尊重系统默认即可。这个设计让代码里少了一大块设备管理的逻辑,稳定性反而更高了。
2.3 音频流处理管线设计
解码和播放之间还有一个容易忽略的环节——音频数据的搬运方式。miniaudio 采用回调模式,它会从一个后台线程定期调用你注册的回调函数,向你要数据。这个模式的坑在于:如果你的读文件和解码操作比较耗时,回调就会断流,声音就会卡顿。
Music_nano 的解决办法是多线程 + 缓冲队列。解码线程负责持续从文件解码出 PCM 数据,塞进一个环形缓冲区;音频回调线程只管从缓冲区里搬数据。只要环形缓冲区的容量足够(我设了 2 秒的音频数据量),回调永远不会被磁盘IO打断。我在实测中发现,这个设计让播放过程中的 CPU 占用率极低,解码线程大部分时间都在等待,而不是忙转。
这里补充一个关键参数的理解:环形缓冲区的大小不是越大越好。缓冲区太小,碰到网络文件或者磁盘抖动就断流;缓冲区太大,切歌的时候延迟会变得明显,而且内存占用也会上升。2 秒的 PCM 数据在 44.1kHz、16bit、双声道下大概是 352KB,这个量级对内存压力不大,同时 2 秒的缓冲足以覆盖绝大多数情况下的 IO 波动,是经过实测后的平衡值。
3. 核心实现:从零搭建 Music_nano 的完整流程
接下来进入正题。我会按照我自己实际的开发顺序,把这个项目的每个核心模块拆开来讲。你不需要完全照搬我的代码,但理解了这些设计思路之后,写你自己的迷你播放器会非常顺手。
3.1 项目结构设计
Music_nano 用 C 语言编写,整个项目保持了极简的结构,核心文件就这几个:
music_nano/ ├── src/ │ ├── main.c # 入口与主循环 │ ├── player.c # 播放核心(解码+输出) │ ├── player.h # 播放器对外接口 │ ├── playlist.c # 播放列表管理 │ ├── playlist.h │ ├── controller.c # 命令行控制交互 │ └── controller.h ├── third_party/ │ ├── miniaudio.h # 单头文件音频库 │ └── ini.h # 配置文件解析 ├── config.ini # 默认配置 ├── Makefile └── README.md我一直坚持把模块之间的依赖控制在最小范围。三个核心模块只通过明确的头文件接口通信:controller 收到用户输入后,调用 player 和 playlist 的接口完成动作;player 内部持有播放列表的上下文,但不知道控制层怎么工作。这样目录结构也起到一个约束作用——任何模块想越权访问别的模块内部细节,在代码评审的时候一眼就能看出来。
模块划分的心得是:播放器这种项目,最大的复杂度来自于异步。解码线程、控制线程、音频回调之间需要正确的同步,如果模块边界不清,很容易就出现竞态条件。划定好接口后,我强制自己遵守"跨线程只传 ID 和命令字,不传指针"的原则,把同步问题限制在 player.c 内部处理,大幅降低了出 bug 的概率。
3.2 配置读取:用最轻的方式管理选项
Music_nano 没有做成硬编码参数的程序,而是支持通过一个config.ini文件配置行为。项目初期有人建议我用 JSON 或者 YAML,但考虑到极简的定位,我选择了ini.h这个不到 200 行的单头文件解析器。配置文件长这样:
[audio] device_index = -1 ; -1 表示用系统默认设备 sample_rate = 44100 buffer_seconds = 2.0 [playlist] shuffle = false repeat_mode = all ; off / one / all [behavior] auto_next = true startup_logo = false command_prefix = "/"解析逻辑没什么好说的,就是读文件、查键值、转类型。但有一个细节值得注意:配置文件的字段名要和代码里的变量名完全一致,而不是让解析器去做模糊映射。这样虽然不够"优雅",但胜在直接——出错时你能瞬间定位问题在哪个配置项上。我在开发时走过弯路,用过一段时间的反射式配置映射,结果调试配置相关的 bug 时痛苦得要命。
这里补充一个经验:配置项要有合理的默认值,并且默认值必须保证程序在零配置的情况下也能正常启动。我的做法是先写好一份"模板配置文件",代码里所有配置都带默认值,配置文件缺失或者某个字段缺失时,直接落到默认值上,不报错。原因很简单,这个工具的目标场景是"拿来就跑",用户哪怕完全不看文档也能用起来。
3.3 播放列表管理:支持文件夹与索引两种模式
播放列表的设计决定了整个交互的顺滑程度。Music_nano 支持两种模式。
第一种是文件夹模式,启动时直接指定一个音乐目录,程序会递归扫描目录下所有支持的音频文件,按文件名排序生成播放列表。这个模式的实现很简单,就是用nftw递归遍历目录,然后对匹配到的后缀名做过滤。对于像我这样按"艺术家/专辑"目录结构整理音乐库的人来说,这个模式已经足够了。
第二种是 m3u 播放列表模式,用户提供一个playlist.m3u文件,文件内容就是每一行一个音频文件的路径,程序按行读取即可。我还在解析器里顺带支持了#EXTM3U头信息和#EXTINF信息,虽然目前没用上这些信息,但至少不会因为格式差异解析失败。
两种模式的统一入口是播放列表的 "游标" 概念。我维护一个当前播放索引current_index,在all模式下它加上 1 就能得到下一首,在shuffle模式下则通过一个随机函数生成。这里有个细节:随机切歌时,我用的是一个全局的mt19937随机数生成器,但每次切换到随机模式时会重新随机化一次种子,确保再次开启随机时不会重复上一次的播放顺序,不然会有"伪随机雷同感"。
播放列表核心的数据结构我用的是动态数组,而不是链表。虽然链表在逻辑上更直观,但动态数组能获得更好的缓存局部性,而且播放列表本身基本不涉及频繁的中间插入删除操作,就算有,也主要是拖动排序这类低频操作。实测下来,扫描 5000 首歌曲时,动态数组方式从扫描到建立索引只用了不到 200 毫秒,完全没有性能压力。
3.4 核心播放逻辑:状态机的正确打开方式
播放器的核心逻辑非常适合用状态机来建模。Music_nano 的播放器模块维护这几个状态:
STOPPED:停止状态,没有任何播放任务PLAYING:正在播放,解码线程在跑PAUSED:暂停状态,解码线程挂起,但资源还持有ENDED:一首播完,等待切换到下一首
每个状态之间的跳转是受限的,比如PAUSED只能回到PLAYING,不能直接跳到STOPPED之外的其他状态。这样可以保证任何时候,程序的内部变量都处于一个"可解释"的状态,排查问题时大大降低了心智负担。
状态切换时我要保证一件事:所有内部资源(解码器实例、文件句柄、缓冲区内容)必须和状态严格一致。比如在STOPPED状态下,解码器实例必须已经释放,文件句柄必须已关闭。为此我在状态切换函数里统一做了资源清理逻辑,切换顺序永远遵循"新的状态可以立刻执行该状态的主循环操作"这一原则,避免出现在PLAYING状态却拿着一个空解码器的尴尬。
3.5 控制与交互:快捷键和管道命令怎么设计
命令行工具最大的优势就是可以被脚本化、被远程操控。Music_nano 的控制层设计了两套交互方式,不冲突,还能互相配合。
第一套是交互模式。进入程序后,终端进入一个类似 REPL 的循环,你输入命令加回车执行。支持的命令包括:
p # 暂停/恢复播放 s # 停止 n # 下一首 b # 上一首 + / - # 音量增减 f # 显示当前播放信息 l # 列出播放列表 g 42 # 跳转到第 42 首 q # 退出第二套是管道命令模式。如果你不希望程序占据交互终端,可以用-c参数让它从标准输入读取命令,比如配合echo命令使用:
echo "n" | music_nano -c # 切到下一首 echo "p" | music_nano -c # 暂停这里有个关键的细节:管道模式处理完一条命令后不会立刻退出,而是用select等待标准输入的超时。这就允许你多次往同一个进程发命令。但为了让退出更可控,我增加了一个q命令,同时支持在-c模式下用--once参数让程序处理完一条命令后就退出。这样既保证了交互模式下的持续性,又满足了脚本调用的单次确定性。
还有一点值得分享:所有控制命令都封装成了send_command(int cmd)这个内部函数,而普通输入解析和管道读入只是把这个函数的入口串起来了。这么做的原因是,我把控制层和播放核心彻底解耦,以后想加个fifo控制管道或者网络控制接口,只需要再开一个数据源,完全不需要改播放核心代码。
3.6 事件机制与主循环:如何让一切协同工作
如果你把播放器当成一个整天跑着的事件系统,会比较好理解它的全局结构。Music_nano 的主循环是这样运作的:控制层阻塞在输入读取上,当用户按下按键或者输入命令时,控制层解析出命令字,调用播放层接口;播放层内部的解码线程在完成一首歌的解码后,会设置一个track_ended事件标志;主循环通过poll同时监听"用户输入"和"播放结束"两个事件,一旦任一事件发生,就进入对应的处理逻辑。
这个设计最关键的地方在于:播放核心的控制入口可以来自多个来源,而不会互相打架。键盘输入、管道输入、播放结束事件,三条路径在事件循环中被统一调度。当一首歌自然播完时,程序检查repeat_mode配置,决定是重新播放当前曲目、播下一首还是停止。如果你正在手动切歌,主循环会先清空还没来得及处理的"播放结束"事件,避免出现切歌之后又立刻自动切一次的诡异现象。
4. 实测过程中的典型问题与排查记录
这里整理我在开发 Music_nano 和真实使用过程中碰到的问题。每一个都是能写进 BUG 列表的级别,解决它们的过程花了我不少时间,现在分享出来,希望帮你少走弯路。
4.1 树莓派上无声:默认音频设备的坑
第一次把 Music_nano 部署到树莓派上时,启动一切正常,但就是听不到声音。检查配置文件和代码都没有问题,后来才发现根因在系统层面:树莓派默认的音频输出是 HDMI,而不是 3.5mm 耳机孔。你需要先通过raspi-config将音频输出切换到耳机孔,或者编辑/usr/share/alsa.conf强制默认声卡。
不过更隐蔽的坑在于:即使你切换了系统默认设备,miniaudio 在树莓派上默认用的还是 ALSA 的第一个设备。我最初的做法是在代码里增加了一个device_index配置项,允许用户指定输出设备。后来在文档里特别标注了树莓派和普通 Linux 主板的差异——我建议在树莓派上直接用一个小的脚本来探测可用的 ALSA 设备,然后把device_index固定下来,一了百了。
4.2 播放 flac 高采样率文件时的爆音
另一个让我排查了很久的问题是:播放 96kHz/24bit 的 flac 文件时,声音偶发爆音,但听 mp3 完全正常。一开始我以为是内存不够或者磁盘卡顿,后来通过调试输出发现,问题出在我的缓冲时间设置上。miniaudio 的buffer_seconds参数如果设置得太小,高采样率、高分位深的解码过程会触发更频繁的缓冲队列空转,导致瞬间断流。
解决办法是动态计算缓冲容量。我根据当前文件的采样率和位深,把缓冲时间换算成实际的frame_count,而不是固定用 2 秒。具体逻辑是:buffer_frames = buffer_seconds * sample_rate,在 96kHz 下 2 秒就是 192000 帧。这样做之后,爆音问题彻底消失,代价是内存占用略微上升,但完全可接受。
4.3 随机模式中的重复播放
在实现随机播放时,最初的算法是每次随机从整个列表里挑一首,结果经常出现同一首歌连续播两次的情况。人类对随机性的感知是有偏差的,完全均匀的随机反而看起来不像随机。所以很多人会要求"上一首播过的歌不能立刻再播"。
我在 Music_nano 里采用的是"防重复最近 N 首"策略。维护一个长度为 10 的最近播放历史队列,生成随机候选时,过滤掉队列里的曲目,再从剩余曲目中随机选。这既保证了短期的随机多样性,也不至于让整体的随机分布失去均匀性。实测下来,这个策略在播放 200 首歌时,连续重复的概率几乎为零,听感自然了很多。
4.4 配置文件缺行导致的诡异崩溃
还有一个适配问题值得提醒。早期版本里,配置文件里如果只写了部分配置项,程序启动时就会因为某个字段没值而崩溃。Workaround 有两种方向:一种是在解析器里做兜底,另一种是在代码里全部使用"先填充默认值再覆盖"的模式。
我最终采用的是后者,在加载配置时先把所有配置项赋默认值,然后用解析出来的值逐个覆盖。这样即使配置文件是空白或者被截断的,程序也只会在某些功能上退化到默认行为,而不会直接崩溃。这是一条非常通用的经验——任何配置驱动的程序都应该默认具备"最少配置也能运行"的能力。
4.5 典型问题速查表
我顺手把遇到过的高频问题整理成一张速查表,方便你直接对照排查:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 启动无声音 | 默认音频设备错误 | 检查系统默认设备,配置device_index |
| 高采样 flac 爆音 | 缓冲帧数不足 | 根据采样率动态计算 buffer_frames |
| 随机播放连续重复 | 随机算法太均匀 | 引入最近历史过滤机制 |
| 切歌瞬间声音残留 | 旧曲目资源未清理干净 | 确保状态切换前调用解码器关闭 |
| 配置文件报错 | 字段缺失 | 代码中采用默认值先行覆盖策略 |
| 服务器上无声音 | 没有声卡设备 | 使用 ALSA 的null设备或音频 loopback |
| SSH 会话断开后播放停止 | 程序所在终端被收掉 | 用 tmux/screen 挂后台运行 |
5. 性能优化与扩展方向
项目做到这,基础功能已经稳定,我花了一些时间做性能优化,也规划了几个可能的扩展方向。这部分对已经完全理解核心逻辑的人最有价值。
5.1 内存与 CPU 调优实录
我用time和perf简单测了下性能数据。启动一个空播放器进入待机状态,RSS大约 11MB。加载一个 1.5GB 的 flac 文件开始解码播放,整体内存峰值约 39MB,CPU 占用大概在 3% 到 7% 之间。
内存优化上最有价值的一个改动是:解码缓冲区不再一次性分配,而是分块按需分配加复用。具体做法是在解码线程内部维护一个堆上的 buffer 池,解码出一段数据就放进池里,回调消费完再还给池里。这样避免了每次切歌或暂停恢复时反复malloc/free带来的内存碎片和分配开销。实测下来,连续播放几十首歌,内存占用一直稳定在三四十兆的区间内,没有随时间缓慢增长的迹象。
CPU 优化上,我严格控制了日志打印的频率。调试时开着详细日志,播放时控制台输出会变成"毒药",因为频繁的printf会加锁、刷新缓冲区,直接吃掉 CPU 和 IO。我把运行日志分成了 debug 和 info 两级,默认只在重要事件(如切歌、错误)时输出,Debug 级别只在显式开-v时打开。这样既保留了排查能力,又不影响日常使用的流畅性。
5.2 扩展方向:这些功能值得做
虽然定位极简,但有些扩展是真正提升体验的,而且不会破坏"极简"的底线。按投入产出比排序,我的建议是这样的:
第一个值得做的是音频可视化频谱。miniaudio 的回调里我们可以拿到 PCM 数据,用一个小的 FFT 库就能在终端里渲染出实时频谱。终端里的频谱条有一种复古的科技感,非常契合这个工具的气质,而且实现不算复杂。
第二个是歌词同步显示。解析 LRC 歌词文件,根据播放进度把当前句高亮显示在终端。交互模式下这体验很好,配合tmux分屏,一边看频幕一边听歌的感觉很舒服。不过要保持极简,歌词显示默认关闭,需要手动开启。
第三个是无损播放状态监控。对于音频发烧友来说,播放器最好能随时显示当前的采样率、位深和码率。Music_nano 目前只是简单显示格式信息,但扩展成实时显示并不困难,在回调里从解码器上下文读取即可。
第四个是多播放列表切换。当前只支持单播放列表,如果需要同时维护"工作BGM"和"深夜精神粮食"两套歌单,就需要扩展成多列表模式。这个改动牵扯到游标模型的重构,我会谨慎推进。
我的原则是:任何扩展都必须保持主循环结构的清晰,可选的扩展尽量做成插件式的独立模块,默认关闭,不影响核心路径的简洁性。这样才能保证 Music_nano 永远跑得轻快。
5.3 后续优化方向:编译与分发
做开源项目你还会遇到一个实际的问题:怎么让别人方便地用起来。Music_nano 目前提供两种分发方式:一种是源码编译,依赖只有 GCC 和 Make,make一下就搞定;另一种我预编译了几个常见平台的二进制文件,放到 GitHub Releases 里,用户下载即用。
编译优化的经验是:用-O2开启优化,并且加上-fno-plt来减小 PLT 开销,解码性能能提升 5% 到 10%。另外我启用了-fstack-protector-strong来增加安全性,这让二进制稍微变大了几百 KB,但对于一个面向公众分发的工具来说,安全投入是值得的。如果你要打包给更广泛的用户,还可以考虑用musl静态编译,把 glibc 的依赖也干掉,这样哪怕是没有任何运行时库的容器镜像里也能跑起来。
通过实际使用反馈,我发现有些用户希望 Music_nano 也能处理流媒体播放,比如直接播放一个网络电台的 URL。这一步其实不算太难,因为 miniaudio 的解码器本身就支持通过回调拉取数据,给ma_decoder_config里挂上一个网络读取回调就行。但涉及网络超时和缓冲管理,复杂度会明显上升,所以我把它列在"考虑中"而不是"已实现"。如果后续真的做了,我会在流媒体缓冲策略上多花心思——网络音频的断流体验比本地文件严重得多,没有足够的缓冲管理经验,不如先不做。
6. 一些实用的小技巧和最终心得
到这里,Music_nano 的核心实现和优化过程都讲完了。最后说几个我个人在真实使用中总结的技巧,算是对这个项目的一个补充彩蛋。
第一个技巧是配合 tmux 使用。把 Music_nano 跑在 tmux 的独立窗口里,你可以随时用快捷键切过去控制,切走也不会打断播放。你甚至可以在 tmux 里把窗口标题改成当前歌名,这样在 minibuffer 里一眼就能看到正在播什么。
第二个技巧是用 shell 别名把常用操作缩短。我在.bashrc里加了这么几行:
alias mn="music_nano" alias mnp="echo p | music_nano -c" alias mnn="echo n | music_nano -c" alias mnb="echo b | music_nano -c" alias mnq="echo q | music_nano -c"这样平时想切歌,直接在任意终端里敲mnn就行,完全不需要切到播放器的窗口。
第三个技巧是配置文件的管理。我在配置文件里加了一个include指令,支持嵌套引入其他配置文件。这个功能最初是给树莓派场景准备的——一套通用配置,再加上每台机器的专属覆盖配置。实际操作中你会发现,这套方案比想象中好用得多:你可以在某个目录维护自己的音乐习惯配置,换机器时只拷贝一份通用配置即可。
最后聊点真心话。做 Music_nano 这个项目让我重新理解了"合适"比"多"更重要。工具的价值不在于功能列表有多长,而在于它在你真正需要的那条路径上是否足够扎实。在把功能一个个砍掉的过程中,我反而对播放器底层的音频管线有了更深入的理解。如果你也要做类似的极简工具,我的建议是:先严格定义"核心体验",然后所有工作围着它转,其他功能一律进扩展开关。只有这样,工具才会越用越顺,而不是越用越臃肿。
如果你也折腾出了自己的版本,或者在这个思路基础上做了有趣的改造,欢迎交流。毕竟对一个开源项目来说,最开心的事情就是看到它在一个你没想到的场景里被别人用得飞起。