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

7 小时 55 分钟前
 kfj92

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

项目背景

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

欢迎交流讨论!

240 次点击
所在节点    程序员
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1196992

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX