网易首页 > 网易号 > 正文 申请入驻

“iOS自带播放器不堪重用,我花1.5周从头开发了个离线替代品!”

0
分享至

在移动应用开发日益依赖云服务与框架的今天,一位独立开发者却选择回归本地、离线优先的设计初衷:用 1.5 周时间打造一款简洁的 iOS 音乐播放器。

谈及为什么要自己动手开发时,其在博客上表示:“2025 年,在 iPhone 上想听自己收藏的音乐竟然变得挺麻烦的。要么掏钱给苹果,要么得绕过一堆限制,挺烦的。于是我干脆从头写了一个自己的音乐播放器,能全文搜索、支持 iCloud,还优先本地播放。”

与此同时,他还将这个项目在 GitHub 上开源了出来,供更多的开发者参考:https://github.com/nexo-tech/music-app

原文链接:https://nexo.sh/posts/why-i-built-a-native-mp3-player-in-swiftui/

作者 | Oleg Pustovit

编译 | 苏宓

出品 | CSDN(ID:CSDNnews)

以下为译文:

为什么要自己动手开发一款播放器?

和很多人一样,我是那种会不知不觉订阅了一堆服务的人,有些是通过苹果官方渠道买的(比如 iCloud、Apple Music),还有一些是在其他平台上糊里糊涂续费的(像 Netflix,我居然都忘了还在付钱)。我其实也常用 Apple Music(之前也用过 Spotify),但后来发现流媒体听歌其实只是图个方便,真说不上是刚需。我自己整理了一个本地音乐库后,也没觉得损失什么,反倒不用被平台绑定了。

一开始我以为,取消 Apple Music 后还能继续用 iCloud 音乐库同步歌曲,结果一取消订阅,同步功能也没了——原来这功能是要钱的。虽然可以通过 iTunes Match(每年 24.99 美元)重新开通,但本质上只是把 256kbps 的 AAC 文件传上云端,原始音质的文件还是留在你设备上,除非你自己手动替换。现在的 Mac 上,这一切都要在 Music App 里操作。如果你一个会员都不买,那同步功能就彻底没戏了,只能靠连线或 Wi-Fi 手动同步。

我实在受够了这些限制,就决定自己动手。

如果我买了个计算设备(这里是 iPhone),那我为什么不能用代码把它变成我想用的样子呢?我想做的其实很简单:能加载音乐、整理它们、正常播放,顺便也提醒一下自己——iPhone 其实还是一台通用电脑,我应该有权自己决定怎么用它。

在开始写自己的 App 之前,我也先去研究了一下官方和第三方有哪些听本地音乐的方案。

Apple 自带的应用

从技术上讲,你可以在「文件」App 里直接播放 iCloud 里的音乐,但它根本不是为听歌设计的。没有播放列表、没有标签整理、没有播放队列……虽然勉强能放歌,但体验非常差,几乎没法用。

第三方 App

我去 App Store 找了一圈,虽然有不少看起来不错的 App,但很多都要订阅付费。说实话,这种只播放本地音乐的 App,搞订阅制真有点说不过去。

有一个叫 Doppler 的 App 我还挺喜欢的,试用了一下,它的界面主要围绕「专辑」来组织,搜索功能不太行,从 iCloud 导入音乐也挺慢,而且处理层级多的文件夹时特别麻烦。不过好的一点是,它是一次性买断,不搞订阅。

自己动手:技术折腾过程

于是我决定亲手做一个满足我需求的理想播放器:

  • 可以在 iCloud 文件夹里全文搜索音乐,快速选中并导入;

  • 至少要有跟官方音乐 App 差不多的功能:播放队列、播放列表、按专辑整理等等;

  • 界面要顺手、看着舒服。

一开始试了 React Native

一开始我没用 Swift,因为之前用过一次,感觉不太好。当时虽然它的语法挺像 TypeScript,也有点 Rust 那种内存安全的风格,但那时候没有原生的 async/await,用起来比 Go 或 JS/TS 写并发代码麻烦多了,得写一堆模板代码,让人挺沮丧的。所以这次我就想换个熟一点的技术栈。

我先用 React Native 或 Expo 来试试,想着可以复用以前做网页的经验,还能套用现成的 UI 模板。播放界面做起来挺顺利的,网上有很多开源项目和教学视频。

我选了 Gionatha Sturba 做的一个模板项目(https://github.com/CodeWithGionatha-Labs/music-player),它几乎具备我需要的所有功能。

我本来想通过现成的工具处理文件访问和 iCloud 同步,但很快就遇到了大问题。像 expo-filesystem 这样(https://docs.expo.dev/versions/latest/sdk/filesystem/)的库,虽然可以用来选文件,但要递归扫描 iCloud 里层级很深的文件夹,经常失败,甚至直接把 App 弄崩了。这时候我意识到:JavaScript 的方案反而把事情搞复杂了,还不如直接用 Apple 的原生 API,虽然学起来难度大点,但更靠谱。

iOS 系统的沙盒机制限制很严,App 要访问用户的文件必须获得明确授权。React Native 在这方面很不稳定,想访问 iCloud 里的文件夹不太现实。于是我决定转向 Swift 开发,这样对文件访问和权限控制能掌握得更细致。

转向 SwiftUI

我选了 SwiftUI 而不是 UIKit 或 storyboard,因为它的语法更现代、声明式风格更清爽,不会让 UI 代码干扰到我主要关注的逻辑处理和数据同步。Swift 的 async/await 和 actor 模型也很好用,让我在处理并发和数据流时省了不少力。SwiftUI 还能把 App 分成更清晰的 ViewModel 结构,这对我用 LLM(比如 OpenAI o1 或 DeepSeek)写 UI 代码也有帮助——模型可以直接输出干净的界面或绑定代码,不会出现各种乱七八糟的依赖。

应用架构与数据模型

整个 App 的架构我参考了写后端服务的方式来做:用 SQLite 存储数据,设计成一个简单但清晰的逻辑系统。我没有用 Apple 的 CoreData,因为我需要更高的自由度,比如自己定义数据库结构、写原生 SQL、做全文搜索等等。而 SQLite 原生支持 FTS5(全文搜索引擎),让我能非常高效地做模糊查找,不用再额外集成 Elasticsearch 或自己造轮子。

三个主要界面

这个 App 一共分三个核心页面:

  1. 导入音乐库用户选择 iCloud 文件夹后,App 会扫描所有子目录,找出音频文件,并把它们的路径存到 SQLite 数据库中。这样你就可以自由地搜索、添加文件夹和子文件夹了。Apple 自带的文件选择器非常不好用,不能一次性选中多个目录或按关键词筛选一批文件,这就是它的硬伤。

  2. 音乐管理界面:这里可以浏览和管理已导入的歌曲,整理播放列表。我大致照搬了 Apple Music 的操作逻辑,对我来说已经够用了。

  3. 播放器:负责播放、暂停、切歌、重复、随机播放、排队等功能。

一个简单的用户使用流程图如下:

  • 第一次打开 App 时,如果还没有导入音乐,会先进入 “同步” 页面,中间有个很大的「添加 iCloud 文件夹」按钮;

  • 选好一个文件夹后,App 会开始扫描文件,进度条会显示处理进度;

  • 扫描完后,会自动跳转到 “音乐库” 页面,展示播放列表 / 艺人 / 专辑 / 歌曲;

  • 点进任意一项,播放条会出现在底部;点播放条可以展开全屏播放器,里面有随机播放、重复、队列排序、音量控制等功能;

  • 你可以随时返回音乐库,音乐继续播放不受影响;

  • 想添加更多音乐,只需返回同步页,点右上角的「+」,再选新的文件夹,系统会在后台自动合并进当前音乐库,无需重启。

像做后端一样设计逻辑层

我之前写后端服务比较多,干脆把 App 的逻辑层也按后端思路来做。整个领域/逻辑层(处理同步、搜索、队列等)完全跟 UI 分离,数据存取也用 SQLite 操作得很精细。

简单说下架构是这样分层的:

  • 最底层是 SQLite,存原始歌曲数据和全文搜索索引;

  • 上面一层是 Repository,封装数据库访问逻辑,提供异步 API;

  • 然后是 Domain Actor(Swift actor),负责业务逻辑,比如导入、搜索、排队等;

  • ViewModel 再订阅这些 Actor,把数据转换成适合 UI 展示的格式;

  • 最上层的 SwiftUI 只负责“展示”数据,所有状态和逻辑处理都已经在底层搞定,彼此之间不会耦合。

这样一来,iCloud 同步、播放功能和界面展示都能分工明确、互不干扰。

在 SQLite 中实现全文搜索

我前面提到,iOS 从大概 iOS 11 开始,就原生集成了带 FTS 功能的 SQLite。这太好了,我可以不依赖第三方搜索引擎,就实现模糊搜索。

我用 SQLite.swift 这个库来写一般的数据库查询(它有点类似于安全型 SQL 构造器),但 FTS 搜索还是得用原生 SQL 语句来写。

SQLite 的 FTS5 功能对我来说非常关键,它能快速搜索歌曲名、艺人、专辑这些字段,而且不需要额外的索引系统。

创建全文搜索索引表

我建了两个 FTS 表:一个用于索引歌曲(艺术家/标题/专辑),另一个用于文件夹导入期间的文件路径。两个表都放在普通的 B-tree 表(songs、source_paths)旁边。FTS 表在 UI 层是只读的,所有写入都通过 Repository 完成,保证不会漏掉任何数据。

创建搜索索引

一个简单的建表语句如下:

try db.execute("""
CREATE VIRTUAL TABLE IF NOT EXISTS songs_fts USING fts5(
  songId UNINDEXED,
  artist, title, album, albumArtist,
  tokenize='unicode61'
);
""")

这里用了 unicode61 分词器,可以支持更多不同语言和字符类型。而像 songId 这样的字段我标记为 UNINDEXED,防止它们占用太多索引空间。

数据可靠更新

为了保证简单又安全,我把所有的更新和插入操作都包裹在事务中。这样一来,即便应用崩溃或中断,搜索索引也不会不同步。

func upsertSong(_ song: Song) async throws {
    db.transaction {
        // 插入或更新主歌曲数据
        // 插入或更新搜索索引数据
    }
}

模糊搜索查询

为了让搜索体验更友好,我自动添加了通配符支持。比如你输入“lumine”,系统内部会搜索“lumine*”,即便是部分匹配也能立刻返回结果。

我还利用了 SQLite 内置的智能排序算法(bm25),能在不增加额外复杂度的前提下返回更相关的结果:

SELECT s.*
FROM songs s JOIN songs_fts fts ON s.id = fts.songId
WHERE songs_fts MATCH ?
ORDER BY bm25(songs_fts)
LIMIT ? OFFSET ?;

总的来说,使用原生 SQLite 提供了我所需的灵活性:可预期的 schema、本地优先的访问方式、强大的全文搜索功能,而且无需依赖网络或外部服务。这种方式非常适合一个注重隐私、强调离线使用的应用。

与 iOS 文件系统和书签交互

在 iOS 上,应用可以存储对文件位置的持久书签(bookmarks),但所谓的“安全作用域书签”(security-scoped bookmarks),即允许访问沙盒外部文件的权限机制,仅在 macOS 上可用。iOS 应用只能使用普通书签记录路径,之后需通过文档选择器再次请求访问权限,而且这类访问不能悄无声息地持续生效。

为了缓解这个问题,我实现了一种回退机制:将文件复制到应用自身的沙盒目录中。这样可以规避安全作用域书签生命周期脆弱的问题——比如 iOS 重置权限后可能导致访问失败。我选择在后台主动复制文件,只要书签仍然有效,就能确保不会访问到无效音频路径。

这种方式还能提升索引速度。我可以在访问权限仍然有效时一次性遍历整个目录结构,只导入相关音频文件,并可靠地深入嵌套目录。但要在设备重启后仍能稳定播放这些外部音频文件,目前我还没找到解决方案。这也说明,即便对原生开发者来说,iOS 文件访问的这一用例仍然缺乏支持,处理起来也依然复杂。

构建播放功能和用户界面

元数据解析

为了从音频文件中解析元数据,我使用了 Apple 的 AVFoundation 框架,尤其是 AVURLAsset 类,可以用来检查媒体文件的标题、专辑艺术家等元信息。

虽然大部分元数据由原生 SDK 处理,但比如曲目编号等字段仍需手动从 ID3 标签中提取。我通过 GitHub 搜索(https://github.com/TastemakerDesign/Warper/blob/2af8c07ad8422f4dc3a539177d3a76ee8502e632/plugins/flutter_media_metadata/ios/Classes/Id3MetadataRetriever.swift)找到了一些处理边缘情况的代码示例,因为官方文档在这方面覆盖非常有限。

音频播放功能

当音乐库完成索引后,构建一个播放器其实很简单:初始化一个 AVAudioPlayer 实例播放音频即可。为了支持系统控制中心播放功能,我实现了 AVAudioPlayerDelegate 协议,并接入了 Apple 的 MPRemoteCommandCenter,从而可以响应系统级的播放控制事件。

一些反思

不足之处

  • Xcode 的局限性仍然令人沮丧。SwiftUI 的实时预览确实是进步,但整体开发体验依旧无法与五年前的 Flutter 相提并论——Flutter 拥有紧密的 VSCode 集成、实时模拟器热重载和熟悉的调试工具。

  • 编辑器灵活性差想要在 Neovim 或 VSCode 中配置 Swift 的 Language Server Protocol(LSP)支持,你还得安装像 xcode-build-server 这样的工具,效果依旧赶不上 web 开发那种轻快的体验。

  • Apple 的 SDK 有些部分还停留在 Objective-C 时代比如 Spotlight 文件搜索功能只能通过 NSMetadataQuery 使用,采用 KVO 和字符串键,没有 Swift 友好的封装。加上文档稀缺,学习成本也就更高。

  • SwiftUI 的声明式 UI 很棒,但调试 iCloud 相关功能仍需手动 mock。因为 SwiftUI 的预览无法模拟涉及 iCloud 权限的完整行为,必须自行模拟云端交互,虽然只是个小问题,但确实麻烦。

优点之处

  • async/await 真是福音终于可以像写同步代码一样写并发逻辑,告别烦人的回调地狱。甚至可以在 Actor 里写 I/O 密集的逻辑,像 JavaScript 那样直接调用,非常丝滑。

  • 丰富的原生库支持。在 React Native/Flutter 等生态里你可能会受限于开源绑定质量,但在 iOS 原生开发中你可以更自由地做“严肃一点”的应用。Apple 提供的许多 API 都附带示例代码,入门门槛反而降低了。

  • SwiftUI 本身就很棒。React 风格的 UI 构建方式让开发效率更高、更容易试验和构思。Apple 采纳它实在是明智之举。

总结:构建应用本应更简单

折腾了 1.5 周之后,我做出了一个完全满足个人需求的软件 —— 一个可以从云端导入音频文件的本地/离线音乐播放器。

但很快你会意识到,如今的开发者很难自由地把自己写的 App 装到设备上长期使用。没有开发者证书,应用只能运行 7 天,之后你必须重新构建。除非你每年向 Apple 支付 99 美元,注册开发者计划。

即便在欧盟《数字市场法》(DMA)生效后,sideloading(侧载)也仍非完全开放。虽然 EU 用户现在可以从第三方网站直接安装 App,但前提是开发者已经加入 Apple 的 $99/年开发者计划,并接受 Apple 的“替代条款”。对于纯粹的个人或爱好者来说,这并未真正解除“7 天使用期”的限制。

这根本说不通。一家声称推动技术创新的公司,反而在人为设置障碍,阻碍开发者自由创作。即便是渐进式 Web 应用(PWA)在 iOS 上也存在明显限制:即使在 iOS 16~18.x 中,PWA 仍运行在 Safari 的沙盒里。它们获得了 WebGL2 和 Web 推送,但仍然缺乏 Web 蓝牙/USB/NFC、后台同步,甚至连超过约 50MB 的稳定存储也不支持。WebGL 还通过 Metal 的中间层运行,实际帧率远低于原生 Metal 应用——对 UI 来说勉强够用,但无法支撑真正的 3A 级 3D 游戏。

现在 AI 已经显著降低了现代软件开发的门槛,让任何人都能快速上手未知技术、构建自己的工具。我们也看到 Web 开发因其开放性吸引了大量非技术背景的创作者,他们无需掌握一堆技术就能实现自己的想法。但在移动开发领域,你还是得遵守一整套人为的规则。即使是你自己为自己做的 App,Apple 仍然握有决定权,限制你运行超过 7 天。

这家公司曾经点燃了独立开发者的梦想,如今却亲手关上了那扇自由的大门。在 AI 让一切变得更简单的时代里,唯有 iOS 开发仍被死死锁住。

——对话 IEEE 首位华人主席、美国双院院士刘国瑞 | 万有引力

2025 全球产品经理大会

2025 年 8 月 15–16 日

北京·威斯汀酒店

2025 全球产品经理大会将汇聚互联网大厂、AI 创业公司、ToB/ToC 实战一线的产品人,围绕产品设计、用户体验、增长运营、智能落地等核心议题,展开 12 大专题分享,洞察趋势、拆解路径、对话未来。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
人去楼空,杉杉集团上海总部大楼流拍后降价4.5亿

人去楼空,杉杉集团上海总部大楼流拍后降价4.5亿

财视传播
2026-01-14 10:40:22
要逼华全额付款,不到24小时,美方收到中方通知,上亿元订单没了

要逼华全额付款,不到24小时,美方收到中方通知,上亿元订单没了

回京历史梦
2026-01-15 12:49:31
天呐!终于知道为什么有的人完全不会内耗了 ​​​

天呐!终于知道为什么有的人完全不会内耗了 ​​​

夜深爱杂谈
2026-01-06 20:24:39
北京发布超1.4万亿元重点工程项目清单

北京发布超1.4万亿元重点工程项目清单

新华社
2026-01-15 21:54:18
美通告全球,中方大抛美债,特朗普终于动手,八国央行向美宣战

美通告全球,中方大抛美债,特朗普终于动手,八国央行向美宣战

博览历史
2026-01-15 18:52:35
闫学晶事件迎来反转!林傲霏中戏毕业照曝光,中戏欺骗了所有考生

闫学晶事件迎来反转!林傲霏中戏毕业照曝光,中戏欺骗了所有考生

阿纂看事
2026-01-14 16:41:41
一夜3大消息!库明加申请交易,火箭旧将报销,快船重大利好

一夜3大消息!库明加申请交易,火箭旧将报销,快船重大利好

体坛小李
2026-01-16 07:34:58
开拓者伤病报告:阿夫迪亚确认缺阵,格兰特大概率出战

开拓者伤病报告:阿夫迪亚确认缺阵,格兰特大概率出战

懂球帝
2026-01-16 08:25:33
2025年入列054A型护卫舰:央视已公开曝光3艘,就等官宣519阜阳舰

2025年入列054A型护卫舰:央视已公开曝光3艘,就等官宣519阜阳舰

静儿家
2026-01-16 09:00:25
一家七口完美落袋31亿,卖掉公司后逃到美国,把麻烦留给17万股民

一家七口完美落袋31亿,卖掉公司后逃到美国,把麻烦留给17万股民

趣文说娱
2026-01-14 11:37:46
官宣!明年底前,北京全部中小学退出校外供餐!

官宣!明年底前,北京全部中小学退出校外供餐!

手工制作阿爱
2026-01-16 05:22:23
谁能拒绝白色厚丝袜的致命吸引力?

谁能拒绝白色厚丝袜的致命吸引力?

流水白莲花
2025-12-10 03:14:22
三人私闯庭院后续:正脸曝光社死,官方账号沦陷,大理文旅压力大

三人私闯庭院后续:正脸曝光社死,官方账号沦陷,大理文旅压力大

有范又有料
2026-01-14 20:07:45
吴亦凡13年牢坐满驱逐出境,李易峰海外开唱背巨债,结局早已注定

吴亦凡13年牢坐满驱逐出境,李易峰海外开唱背巨债,结局早已注定

观察鉴娱
2026-01-16 09:32:47
快碎掉了,公司恐将关停,解散式大裁员!

快碎掉了,公司恐将关停,解散式大裁员!

黯泉
2026-01-15 23:22:35
几乎全是假货!利润高达2400%,咋消费者还前赴后继争相购买?

几乎全是假货!利润高达2400%,咋消费者还前赴后继争相购买?

奇思妙想草叶君
2026-01-05 23:13:15
问题到底出在哪里?为什么那么多人不信官方说法…

问题到底出在哪里?为什么那么多人不信官方说法…

慧翔百科
2026-01-10 13:44:32
张雨绮穿吊带看着骨架有点大哦!这大体格谁看了不喜欢?

张雨绮穿吊带看着骨架有点大哦!这大体格谁看了不喜欢?

草莓解说体育
2025-12-21 00:52:27
特朗普抛出“弃台论”,岛内人马突然访美,郑丽文暗示统一倒计时

特朗普抛出“弃台论”,岛内人马突然访美,郑丽文暗示统一倒计时

博览历史
2026-01-14 22:17:01
泽连斯基回应特朗普:乌克兰绝非和平绊脚石,呼吁对俄施压

泽连斯基回应特朗普:乌克兰绝非和平绊脚石,呼吁对俄施压

起喜电影
2026-01-16 09:12:29
2026-01-16 10:31:00
CSDN incentive-icons
CSDN
成就一亿技术人
26263文章数 242218关注度
往期回顾 全部

数码要闻

英伟达:将继续出货所有GeForce显卡型号

头条要闻

马克龙警告美国:侵犯格陵兰岛将引发"前所未有后果"

头条要闻

马克龙警告美国:侵犯格陵兰岛将引发"前所未有后果"

体育要闻

聂卫平:黑白棋盘上的凡人棋圣

娱乐要闻

92岁陶玉玲去世,冯远征曹可凡悼念

财经要闻

深圳有白银商家爆雷 维权群超350人

科技要闻

被网友"催"着走,小米紧急"抄"了特斯拉

汽车要闻

吉利帝豪/缤越推冠军一口价 起售价4.88万

态度原创

时尚
数码
家居
旅游
本地

年度最扎心电影,看得中年男女坐立难安

数码要闻

Keychron海外推出K3 Max全木版机械键盘=,119.99美元

家居要闻

自在自宅 个性自由

旅游要闻

闪电新闻“孔孟之乡·运河之都”建设世界文化旅游名城专题上线!一站式解锁济宁文旅新体验

本地新闻

云游内蒙|黄沙与碧波撞色,乌海天生会“混搭”

无障碍浏览 进入关怀版