vibe coding这么久了,大家有没有较好的AI开发模式经验沉淀下来?
电视秒变 K 歌房 + MTV 频道:NASMusicTV v2.16 全新进化
上次聊 NASMusicTV 还是 v2.6.2 -- 那时候它只是一个"电视上能好好显示歌词的 NAS 播放器"。现在它能把人声消掉让你在家 K 歌,还能自动搜索 B 站 MV 把电视变成音乐频道。
一、从"能听"到"能唱"
如果你看过上一篇文章,应该记得那个核心卖点:电视上逐字变色歌词。播一首《晴天》,右侧歌词跟着进度逐字亮起 -- 体验确实不错。
但听久了你会发现一个问题:光看歌词不过瘾,想跟着唱。
手机上的音乐 App 早就有"伴奏模式"了 -- 消掉人声,只留背景音乐。但电视上?几乎没人做。
原因很简单:人声消除是 DSP(数字信号处理)活,吃 CPU、吃延迟、吃音频管线设计。手机上做这个已经够麻烦了,电视上还要兼顾遥控器操作和低版本设备兼容 -- 大多数开发者懒得碰。
但 v2.13 做了。而且做得比你想的更深。
人声消除:不是"挖空",是"深度衰减"
最简单的人声消除算法是左右声道相减(Mid/Side 分离)。人声通常在正中间(左右声道相同),相减后人声归零,伴奏保留。
但问题是:很多乐器也在正中间。主旋律吉他、贝斯、底鼓 -- 这些和人声同频段的居中乐器会被一并抹掉。结果就是:人声没了,音乐也塌了,听感像从水管里传出来的。
NASMusicTV 的方案是参考 Audacity 的官方建议:
- 人声频段不是完全挖空,而是深度衰减保留 15% -- 留一点人声残影,不至于太空洞
- Side 声道(立体声宽度信息)只轻度削减,保留 50% -- 保住混响和立体声宽度
- 高通截止 8kHz -- 保留镲片和空气感
- 补偿增益 1.25x -- 衰减处理后电平会掉,适度补偿避免削波
实际效果:人声大幅减弱但背景音乐完整不塌方。不会像专业伴奏那么干净,但在电视音响上 K 歌完全够用。
K 歌模式:全屏沉浸 + 逐字"前快后慢"
有了人声消除,下一步就是 K 歌体验。
播放页点「K歌」按钮 -> 进入全屏沉浸 K 歌页。双行歌词窗口逐行滚动 + 逐字变色推进。底部工具栏随时切换"原唱/伴奏"、退出返回播放页。
这里有个细节值得一提:逐字变色的节奏。
最初版本是线性推进 -- 每个字均匀亮起,50% 进度时正好亮一半。但真实唱歌不是这样的:句首唱得快,句尾有拖音。所以后来改成了幂曲线 progress^0.6 -- 行内时间过半时已经覆盖约 2/3 的字,剩余的字在后半段慢慢亮起。这种"前快后慢"的节奏更接近真实演唱的呼吸感。
歌词框下缘还有一条 2dp 的青色渐变进度细线,实时指示整曲进度。纯视觉指示,不参与焦点和 seek -- 但那种"一条线慢慢走完一整首歌"的感觉,意外地治愈。
二、MTV:把电视变成音乐频道
K 歌做完了,下一个想法很自然:既然能搜到歌,能不能搜到 MV?
B 站:唯一现实选项
调研了一圈免费 MV 接口:
| 候选 | 国内可用 | 免登录 | 播放可行性 | 结论 |
|---|---|---|---|---|
| B 站非官方 API | 直连 | 免登录 | 直链 mp4 | 选用 |
| 网易云 MV | 可用 | 需登录 | 公共端点挡 MV 接口 | 弃用 |
| YouTube | 不可达 | 需 API Key | 需爬 playerResponse | 排除 |
| http://Archive.org | 可用 | 免登录 | 无中文歌 MV | 排除 |
B 站成了唯一选项。但 B 站的 API 有几个坑:
三步取流:搜索拿到 bvid -> 调 view 接口拿 cid -> 调 playurl 接口拿直链。三步 HTTP 请求,每一步都可能失败。
风控:B 站搜索接口对未登录请求有风控,可能返回 412。代码里做了 wbi/legacy 双路径回退 -- 先试 wbi 签名接口,失败回退非 wbi 变体。最终还是失败?按钮置暗,不打扰用户。
防盗链:B 站直链校验 Referer 和 User-Agent。OkHttp 请求头必须带 Referer: https://www.bilibili.com + 浏览器 UA,否则返回 403。
直链过期:B 站直链有小时级有效期。缓存必须带时间戳,过期重建。
全屏视频页 + K 歌歌词联动
MTV 按钮在播放页「K歌」旁边。搜到 MV 则亮起可点击,没搜到则半透明不可点。点击进入全屏视频页:
- 独立 ExoPlayer 播放视频(不碰主音频播放器的 DSP 管线)
- 进入时暂停主播放器,退出时恢复 -- 两套播放器互不干扰
- 暗色渐变遮罩保证底部控制条可读
- 可选叠加 K 歌逐字歌词(按「歌词」按钮切换)
- 上一首/下一首/播放暂停/切换 -- 完整控制条
控制条 5 秒无操作自动虚化至 0.15 透明度(几乎看不见,不挡视频),遥控器一操作就完全显化。这个交互在手机上很常见(视频播放器都这么做),但在 TV 上用 Compose 实现需要注意:D-pad 焦点变化也要触发显化,不只是按钮点击。
连播模式:预搜 + 无缝切换
MV 播完后怎么办?最初版本是停在最后一帧,用户手动按返回。体验很差。
后来做了"连播模式":当前 MV 播放时,后台预搜下一首歌的 MV。MV 播完 -> 用预搜结果直接切换 -> 无闪烁、无混音。
这里有个工程难点:主播放器的音频不能漏出来。
切歌时如果调 next(),主播放器会加载新歌并开始播放 -- 但此时用户还在看 MV 视频页,主播放器的音频和 MV 视频的音频会混在一起。
解决方案是 suppressPlayback 标志:进入 MTV 模式时置 true,所有 play() 调用都被拦截。退出时置 false,恢复正常播放。
还有个细节:pause() 方法原来有 if (it.isPlaying) 守卫 -- 如果 ExoPlayer 恰好在 BUFFERING 状态,isPlaying 返回 false,pause() 被跳过,缓冲结束后自动恢复播放。改成无条件 pause() 才彻底解决。
MV 切换状态机:2 轮后自动找更多
B 站搜索一首歌可能返回多个 MV(不同 up 主上传的不同版本)。用户可以按「切换」按钮换一个看。
但这里有个设计问题:如果只搜到一个,按「切换」没反应 -- 用户不知道是程序卡了还是真的只有一个。
最终设计了这样一个状态机:
- 有多个候选:按「切换」循环播放(A -> B -> C -> A)
- 循环 2 轮后:用户明显不满意现有候选 -> 自动触发重搜,排除已展示的 bvid,降低相似度阈值,从 B 站找更多结果
- 只有一个结果:直接触发重搜
- 重搜上限 2 次:防止无限重搜。阈值递降 0.5 -> 0.3 -> 0.1,每次放宽条件
- 重搜不打断播放:后台搜索,当前 MV 继续播。搜到新结果才切换,搜不到就提示"未找到更多视频"
switchMv 失败(网络抖动/视频被删)也不再静默吞掉 -- 显示"切换失败,请重试"提示,回退切换计数。
MV 持久缓存:跨会话记住你喜欢的版本
每次切歌都搜一遍 B 站太浪费。但直链会过期,不能直接缓存 URL。
方案是缓存 bvid(B 站视频唯一 ID,不过期)而非直链。播放时先查缓存拿 bvid,再 resolveMv(bvid) 获取新鲜直链。
更有意思的是 markCompleted 机制:MV 播完时(STATE_ENDED)写入持久缓存,playCount++。如果用户切换过 MV 后播完了新版本,覆盖旧 bvid -- 说明用户更认可这个版本。下次播同一首歌,直接用用户认可的 bvid。
缓存上限 5000 条,LRU 淘汰。存在 app 的 filesDir 下的 JSON 文件里,跨会话持久。
三、可视化均衡器:电视屏幕上的频谱动画
这个功能没什么技术深度,但视觉效果意外地好。
实时 FFT 频谱分析,3 种视觉主题:
- ColorFlow:彩色渐变柱状图,频率越高颜色越暖
- NeonPulse:霓虹脉冲,节拍感强
- ClassicalWave:经典波形,优雅柔和
播放页底部一排跳动的频谱柱,配合封面和歌词 -- 那种"音乐可视化了"的感觉,在电视大屏上特别有冲击力。
技术实现上用的是 Android 的 Visualizer 类(android.media.audiofx.Visualizer),从 ExoPlayer 的 audio session id 拿到频谱数据,20fps 刷新渲染成 Compose Canvas。没什么花哨的算法,就是 FFT 数据映射到矩形高度 + 颜色渐变。
四、完整备份/恢复:换设备不丢设置
电视盒子坏了或者换了新电视,重新配置一遍所有设置是很痛苦的。
v2.16 的备份功能现在覆盖了所有设置项:
- 服务器地址(不含密码/Token)
- 全部 App 设置(主题、动画、播放模式、缓存、歌词偏移、端点配置等)
- 网络收藏、本地歌单、播放队列
- 播放次数、最近播放、搜索历史
- 均衡器预设和频段
- 天气设置(开关、城市、自动刷新,API Key 不备份)
- 封面滤镜(模糊、暗色遮罩)
- 歌词字号
- MV 持久缓存(bvid 映射表,跨设备复用)
导出为 JSON 文件存在 Downloads/NASMusic/,可以通过文件管理器或扫码传输到新设备。恢复后服务器需重新输入密码连接(敏感字段不备份),其余设置一键恢复。
之前有几个版本遗漏了天气设置、封面滤镜、歌词字号等 8 项 -- 这次全部补上了。旧版备份文件恢复时新字段用默认值,向后兼容。
五、一些技术细节
suppressPlayback:一标志封堵所有混音
MTV 模式下主播放器必须保持静音。但 ExoPlayer 的播放路径有多条:
resume()-- 用户退出沉浸模式时恢复playQueue()-- 网络歌曲 URL 解析完成后播放next()REPEAT_ONE 模式 -- 显式调play()
每条路径都可能因为异步回调(URL 解析、歌曲自动过渡)在 MTV 模式下意外触发播放。加了一个 suppressPlayback 布尔标志,在这三个方法的 play() 调用前检查 -- 一个标志封堵所有路径。
advanceIndexSilently:不碰 ExoPlayer 的切歌
MTV 连播切歌时,主播放器的队列索引需要推进(这样退出 MTV 后播的是下一首),但不能调 next()(因为 next() 会触发 ExoPlayer 加载新歌并播放)。
advanceIndexSilently 只更新 _currentIndex 和 _currentSong 两个 StateFlow,完全不碰 ExoPlayer。退出 MTV 时用 syncAndPlayCurrent() 同步 -- 用 setMediaItems 恢复完整队列 + seek 到当前索引,而不是 setMediaItem 替换成单曲(那样 next()/previous() 会失效)。
endedHandled 绑定 videoUrl
MTV 无缝切歌时不走 Searching 状态,MvPlaybackScreen 不离开 Composition。endedHandled(标记 STATE_ENDED 是否已处理)如果用 remember 不带 key,第一首 MV 设 true 后后续 MV 的 STATE_ENDED 全被忽略 -- 表现为"播几首后停在最后一帧"。
改成 remember(mv.videoUrl) -- 新 URL 触发标志重置,每首 MV 都能正常触发 onPlaybackEnded。
六、版本历程
从 v2.6.2 到 v2.16.0,跨度虽然大,但核心就三件事:
- v2.13.x:人声消除(DSP Mid/Side)+ K 歌全屏页 + 逐字"前快后慢"节奏 + 可视化均衡器
- v2.14.0-v2.15.0:MTV 音乐视频搜索(B 站三步取流)+ 全屏视频页 + 连播预搜 + 上一首/下一首 + 多结果切换
- v2.16.0:MV 持久缓存(bvid 跨会话 + markCompleted 追踪偏好)+ 切换状态机(2 轮重搜排除已展示)+ 控制条自动虚化 + 备份补全
测试用例从 89 条增长到 116 条(新增 MvSearchManagerTest 12 + BilibiliMvServiceTest 15,覆盖缓存/fallback/排除 bvid/阈值调整/解析)。
七、写在最后
从"能听歌看歌词"到"能 K 歌看 MV",NASMusicTV 走了 10 个版本。
老实说这些功能在手机 App 上不算新鲜 -- 网易云、QQ音乐都有 K 歌和 MV。但在 Android TV + NAS 这个组合上,能做到这个完成度的,目前可能就这一个。
电视大屏 + 音响 + 遥控器 + NAS 曲库 + B 站 MV -- 这套组合拼出来的体验,比想象中要好。播一首喜欢的歌,逐字歌词亮着,封面模糊做背景,频谱柱跳动,按一下 MTV 按钮切到 B 站 MV -- 那种"客厅变成了私人音乐空间"的感觉,真的会上瘾。
如果你有 Android TV 和 NAS,可以试试:
GitHub:https://github.com/hxzhang2000/NASMusicTV
Release 页面有预编译 APK,下载装上就行。项目完全开源(GPL v3),欢迎 Star、Issue、PR。
本文介绍 NASMusicTV v2.16.0 的功能。项目遵循 GPL v3 开源协议。