V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
kfj92
V2EX  ›  程序员

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

  •  
  •   kfj92 · 8 天前 · 1597 次点击

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

    项目背景

    「静听」是我独立开发的一款 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: [暂未开源,考虑中]

    欢迎交流讨论!

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

    文件管理我现在用的是“documents”,各种文件直接通过 ios 的文件移动就可以自动识别管理和打开。
    tangshanliu
        18
    tangshanliu  
       6 天前
    @kfj92 我倒是觉得静听挺好的。即有致敬“千千静听”感觉,又有一种自己安静的欣赏的感觉,意境非常好。
    kfj92
        19
    kfj92  
    OP
       6 天前
    @tangshanliu 后者大致就是我取这个名的本意
    kfj92
        20
    kfj92  
    OP
       6 天前
    @tangshanliu documents 很少用,只是下载过,可以加 APP 里边的微信,详聊。
    tangshanliu
        21
    tangshanliu  
       5 天前
    @huangqihong 车载需要特殊的功能吗?早上试过了,我手机蓝牙连接车载蓝牙音频输入,目前是可用的,播放信息也能同.
    huangqihong
        22
    huangqihong  
       5 天前
    @tangshanliu #21 我用的 carplay ,方便看导航还有听歌
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   Solana   ·   944 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 21:28 · PVG 05:28 · LAX 14:28 · JFK 17:28
    ♥ Do have faith in what you're doing.