独立开发日记:今天给「静听」音乐播放器做了十几个优化 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
kfj92
V2EX    程序员

独立开发日记:今天给「静听」音乐播放器做了十几个优化

  •  
  •   kfj92 1 天前 1284 次点击

    独立开发日记:今天给「静听」音乐播放器做了十几个优化

    项目背景

    「静听」是我独立开发的一款 iOS 本地音乐播放器,主打无损格式支持、WiFi 传歌、无广告体验。开发一年多了,一直在持续优化。

    今日优化清单

    播放体验修复

    1. 单曲循环 bug:之前循环播放时只重复最后几秒,现已修复
    2. 随机播放逻辑:优化了算法,现在是真正的全曲库随机
    3. 播放连续性:歌曲播完后自动切下一首,逻辑更符合直觉
    4. 播放时间显示:修复了偶尔「卡住」不走的罕见问题

    蓝牙交互优化

    1. 蓝牙自动恢复:连接蓝牙耳机自动继续播放,断开自动暂停
    2. Siri 兼容性:修复了唤起 Siri 时闪退的问题
    3. 音频中断处理:微信语音等中断后智能恢复播放位置

    UI/UX 细节

    1. 歌单封面:无封面歌单自动显示第一首歌的封面
    2. 静默刷新:修改歌曲信息后列表自动刷新,无闪烁
    3. 播放队列定位:新增「一键定位」到当前播放歌曲
    4. 工具栏同步:底部工具栏播放模式修改即时生效

    核心功能

    1. WiFi 传歌:增加取消导入功能,修复重复导入跳过逻辑
    2. 编辑页面:优化封面保存逻辑,不再保存占位图
    3. 歌词显示:修复导入的歌词文件显示空白的问题

    技术底层

    1. 音频引擎:换用 ffmpeg ,支持更多音频格式
    2. 状态同步:播放模式修改后全局同步更新
    3. 状态恢复:重启 App 正确记住播放状态和队列
    4. 批量管理:页面底部显示筛选后的歌曲总数

    技术细节分享

    单曲循环修复

    问题出现在 AVPlayertimeObserver 回调时机处理上。原逻辑在歌曲即将结束时就开始准备循环,导致只播放最后几秒。

    解决方案:

    // 修复后的逻辑 player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 0.5, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: .main) { [weak self] time in guard let self = self else { return } let currentTime = CMTimeGetSeconds(time) let duration = CMTimeGetSeconds(self.player.currentItem?.duration ?? CMTime.zero) // 在歌曲结束前 0.1 秒开始准备循环 if duration - currentTime < 0.1 && self.playMode == .singleLoop { self.seek(to: 0) self.play() } } 

    蓝牙中断处理

    iOS 的音频会话管理比较 tricky ,特别是蓝牙设备连接/断开时的状态恢复。

    关键代码:

    // 监听蓝牙状态变化 NotificationCenter.default.addObserver( self, selector: #selector(handleAudioRouteChange), name: AVAudioSession.routeChangeNotification, object: nil ) @objc func handleAudioRouteChange(notification: Notification) { guard let userInfo = notification.userInfo, let reasOnValue= userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt, let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { return } switch reason { case .newDeviceAvailable: // 新设备可用(如连接蓝牙) if shouldResumePlayback { resumePlayback() } case .oldDeviceUnavailable: // 旧设备不可用(如断开蓝牙) pausePlayback() savePlaybackPosition() default: break } } 

    播放模式全局同步

    使用 UserDefaults + NotificationCenter 实现状态同步:

    // 设置播放模式时 UserDefaults.standard.set(playMode.rawValue, forKey: "currentPlayMode") NotificationCenter.default.post(name: .playModeChanged, object: playMode) // 各处监听 NotificationCenter.default.addObserver( self, selector: #selector(updatePlayModeUI), name: .playModeChanged, object: nil ) 

    遇到的问题和解决方案

    1. 随机播放只在几首歌里随机

    问题:原算法使用了 Array.shuffled(),但在每次切歌时都重新 shuffle ,导致随机性不够。

    解决:改为一次性 shuffle 整个播放队列,然后顺序播放。

    2. 播放时间偶尔不走

    问题AVPlayertimeObserver 在某些情况下(如后台播放、网络波动)会停止回调。

    解决:增加保活机制,定期检查播放状态,必要时重新添加 observer 。

    3. 编辑页面封面逻辑

    问题:用户不选择封面时,系统会保存一个占位图,导致不必要的存储。

    解决:判断用户是否真的选择了新封面,如果没有,保持原封面或使用默认 App logo 。

    开发感悟

    做独立开发最有趣的地方就是这些「小修小补」。每个 bug 的修复、每个体验的优化,都能让产品更接近「完美」。

    今天修复的这些问题,大多都是用户反馈或自己使用中发现的。有时候一个看似简单的「继续播放」逻辑,背后涉及音频会话管理、状态恢复、用户体验等多个方面。

    下一步计划

    1. 批量管理筛选:增加按专辑、艺术家、最近播放等筛选功能
    2. 播放列表管理:优化播放列表的创建、编辑、分享功能
    3. 音频效果:考虑增加更多均衡器预设和音效
    4. 多设备同步:研究 iCloud 同步播放列表和播放进度的可行性

    讨论点

    1. 大家在使用音乐播放器时,最在意哪些功能或细节?
    2. 对于本地音乐播放器,还有什么功能是你们觉得必备的?
    3. 在音频播放和蓝牙设备兼容性方面,有什么经验或坑可以分享?

    静听 - 无损音乐播放器 & 本地传歌 App Store: [搜索「静听」即可下载] GitHub: [暂未开源,考虑中]

    欢迎交流讨论!

    20 条回复    2026-03-11 09:22:43 +08:00
    huangqihong
        1
    huangqihong  
       1 天前
    可以车载吗
    kfj92
        2
    kfj92  
    OP
       1 天前
    @huangqihong 暂时不可以,后边会做
    afirefish
        3
    afirefish  
       1 天前
    要不加个 [千千] 吧,名字更好听
    ttsh
        4
    ttsh  
       1 天前
    先顺序播放,然后在播放页设置为随机播放,不会变随机,还是顺序
    Yasuke
        5
    Yasuke  
       1 天前
    @afirefish 那就叫千千阙歌吧
    igofreely
        6
    igofreely  
       23 小时 46 分钟前
    网易云音乐有个新功能,一首歌还没放完,下一首歌的声音就开始混进来播放了,「静听」音乐播放器可以加上这个功能。
    bugsnail
        7
    bugsnail  
       23 小时 45 分钟前
    这么纯粹的嘛,连个链接都不放一个?
    toan
        8
    toan  
       23 小时 31 分钟前
    这么纯粹的 当今很少见!
    imHarveyy
        9
    imHarveyy  
       21 小时 4 分钟前
    @igofreely apple music 也有这个功能 类似渐进渐出 但是效果不好,经常无缘无故的就会加速,或者唱一半就开始切换了
    kfj92
        10
    kfj92  
    OP
       20 小时 30 分钟前
    @ttsh 好的,我优化一下
    kfj92
        11
    kfj92  
    OP
       20 小时 29 分钟前
    @afirefish 可以,静听千千挺好听的
    kfj92
        12
    kfj92  
    OP
       20 小时 28 分钟前
    @igofreely 我后边研究一下
    kfj92
        13
    kfj92  
    OP
       20 小时 27 分钟前
    @bugsnail 哈哈,我可不纯粹,我就是忘了
    https://apps.apple.com/cn/app/id6755151133
    kfj92
        14
    kfj92  
    OP
       20 小时 26 分钟前
    heziqiang
        15
    heziqiang  
       18 小时 53 分钟前
    是开源的吗,
    发出来大家给你一起修 issue 吧
    kfj92
        16
    kfj92  
    OP
       14 小时 24 分钟前
    @heziqiang 垃圾代码,开源了怕被大佬们喷
    tangshanliu
        17
    tangshanliu  
       1 小时 18 分钟前
    刚刚下载了。
    如果能直接读取文件,替换现在的文件导入就好了。
    我刚刚尝试通过 ios 的文件管理把本地的音乐文档移动到对应的文件夹下没有自动识别。
    只能通过首页的文件导入识别。导入后又形成了新的副本,文件名也重新命名了。

    文件管理我现在用的是“documents”,各种文件直接通过 ios 的文件移动就可以自动识别管理和打开。
    tangshanliu
        18
    tangshanliu  
       1 小时 16 分钟前
    @kfj92 我倒是觉得静听挺好的。即有致敬“千千静听”感觉,又有一种自己安静的欣赏的感觉,意境非常好。
    kfj92
        19
    kfj92  
    OP
       51 分钟前
    @tangshanliu 后者大致就是我取这个名的本意
    kfj92
        20
    kfj92  
    OP
       50 分钟前
    @tangshanliu documents 很少用,只是下载过,可以加 APP 里边的微信,详聊。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     6182 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 80ms UTC 02:13 PVG 10:13 LAX 19:13 JFK 22:13
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86