自分で作ったオーディオプレーヤー
(nexo.sh)- 2025年になっても、iPhoneでMP3音楽を自由に再生することには制約が多い
- Appleおよびサードパーティアプリの多くは有料サービスであるか、ユーザーの使い勝手が不十分
- 自作アプリは全文検索、iCloud対応、ローカル優先環境などを提供
- React Nativeなどのクロスプラットフォームアプローチには、ファイルシステムの制約と安定性の問題から限界があった
- SwiftUIとSQLiteベースの設計により、独立性が高く拡張しやすい音楽管理体験を実現した
概要
2025年になっても、ユーザーが自分で所有するMP3ファイルをiPhoneで自由に再生するのは難しいという現実に対する不便さを、自ら解決しようとして、開発者が自分自身で音楽プレーヤーアプリを作るに至った経緯と結果を紹介する。Appleの音楽サービスや外部アプリにはさまざまな制限や課金モデルがある一方で、自作したアプリはユーザーの音楽ファイル管理と再生に最適化された体験を提供する。
音楽プレーヤーを自作した理由
- Apple MusicやiCloud Music Libraryなどのクラウド同期機能は、有料サービスを契約しなければ使えない
- サブスクリプションをやめると、自動同期や音楽フォルダへのアクセスがすべて遮断される
- 既存の音楽ライブラリ統合や汎用デバイス活用において、所有者としての権限が欠けていると実感した
- 「購入したスマートフォンを使って、本当に必要な機能は自分で作ることもできる」という自己決定性を実現したかった
Appleとサードパーティの音楽再生ソリューション分析
Apple標準アプリ
- Filesアプリを使ってiCloudフォルダ内の音楽ファイルを再生することはできるが、プレイリスト管理、メタデータ並べ替え、キュー操作などの基本機能が弱い
- 音楽鑑賞に最適化されていないユーザー体験になっている
サードパーティアプリ
- App Storeにはさまざまな外部アプリがあるが、サブスクリプション型の課金モデルが多く、アプリごとに機能水準もばらつきがある
- Dopplerのような一部の有料(買い切り)アプリもあるが、大規模なiCloudフォルダに対する検索・インポート体験はスムーズではなかった
「ビルダーモード」へ進んだ技術的な道のり
- 自分で音楽プレーヤーアプリを作ると決意した
- 必要な機能: iCloudフォルダ全体に対する高速な全文検索、公式音楽アプリ水準の音楽管理機能(キュー、プレイリスト、並べ替えなど)、そして直感的なインターフェース
React Nativeでの最初の試み
- Swiftでの過去の不便さ(以前はasync/await未対応など)から、React Native/Expoを好んでいた
- オープンソースのテンプレート活用などでUI実装は比較的順調だったが、ファイルシステム(iCloudフォルダ)へのアクセスと同期の処理で、アプリクラッシュや機能上の限界を感じた
- iOSのサンドボックス方針では、明示的な権限なしに外部フォルダへアクセスできないと気づき、Swiftへ移行した
SwiftUIを選んだ理由
- SwiftUIの宣言的UIパラダイム、モダンなasync/awaitとSwift Actor対応を活用
- UIとデータロジックを徹底的に分離し、クリーンなデータフローとドメイン並行処理を実装
- LLM(OpenAI o1、DeepSeekなど)の活用を最適化し、生成されるUIコードの依存性最小化にも役立った
アプリのアーキテクチャとデータモデル
-
SQLiteをデータストアとして採用し、**全文検索(FTS5)**をそのまま使えるためCoreDataではなくこちらを選択した
-
アプリの3つの主要画面/モード:
- ライブラリインポート: iCloudフォルダごとにオーディオファイルのパスをDBへ一括保存し、柔軟な検索・管理を支援
- ライブラリ管理: プレイリスト、曲の分類、編集など
- 音楽再生: キュー管理(リピート、シャッフルなど)と基本的な曲コントロール
-
ライブラリインポート時のユーザーフロー:
- 初期の空状態から「Add iCloud Source」でフォルダを選び、ツリーをスキャン
- インデックス作成が完了するとライブラリタブへ移動し、キーワード別のリストとミニプレーヤーを表示
- 新しい曲を追加するとバックグラウンドで自動マージされる
バックエンド風のロジックレイヤー
- Web/クラウド系スタートアップでの開発経験をもとに、ロジック層とUI/ViewModelを徹底的に分離
- **ドメインレイヤー(Swift actors)**が主要なビジネスロジック(インポート、検索、キューなど)を持ち、スレッドセーフ性を確保
- UI更新はViewModel経由できれいに分割し、ファイル同期・再生・UI間の依存を最小化して保守性を高めた
SQLiteで全文検索を実装
- iOS 11以降では追加設定なしにFTS5対応SQLiteを利用できる
- 外部検索インデックスやサーバーなしで高速検索を実現
- SQLite.swiftライブラリで通常クエリを処理し、FTSクエリでは直接SQL文を使用
FTSテーブル構造
songs_fts: 曲のartist、title、album、albumArtistなどをインデックスsource_paths_fts: ファイルパス、ファイル名をインデックス- メインテーブルと並んで存在し、UIでは検索(READ)専用に使用
- インデックス更新はDBトランザクションで処理し、データ整合性を維持
あいまい検索とランキング
- 入力値の末尾にワイルドカードを自動追加し、BM25ベースで関連度順にソート
- 全体として、ネットワーク依存のない予測可能なデータ構造と強力なローカル検索環境を実現した
iOSファイルシステムとブックマークの活用
- iOSはmacOSと異なり、security-scoped bookmarksをサポートしておらず、外部ファイル拡張アクセスの永続性が不足している
- パス情報しか保存されないため、再アクセス時にはユーザーが再度権限を承認する必要がある
- 解決策として、アクセスを許可した時点でファイル自体をアプリのサンドボックスへコピー保存する
- バックグラウンドで自動コピーし、ファイルインデックス作成速度も改善
- 端末再起動後は外部ファイルの直接再生が依然として難しく、iOSのファイルアクセス制約の厳しさを示している
AVFoundationベースの再生とインターフェース設計
- AVFoundationフレームワークとAVURLAssetを使ってオーディオファイルのメタデータを解析
- track numberなど一部の項目は手動で解析(ID3タグを活用)
- オーディオ再生にはAVAudioPlayerを使用し、コントロールセンター連携のためMPRemoteCommandCenterとDelegateプロトコルを実装
開発後に感じたこととAppleの方針への考察
不便だった点
- Xcodeの制約的な環境: SwiftUIのライブプレビューは進化しているが、VSCodeやFlutterの使い勝手には及ばない
- エディタの柔軟性不足: NeovimやVSCodeでSwift LSPを活用するには追加設定が必要で、完成度も低い
- 一部SDKの奥まった部分はObjective-C寄り: メタデータ検索などでモダンなSwiftに親和的なAPIが不足
- iCloud連携のデバッグが面倒: SwiftUIプレビューではクラウド機能を完全に実装できず、直接モックを構築する必要がある
良かった点
- Async/awaitにより、I/Oバウンドな並行処理コードが大幅に書きやすくなった
- 豊富なネイティブライブラリ: オープンソースバインディングの限界を離れ、より多様な機能を開発可能
- SwiftUIの宣言的UI設計: React的な強みと高い生産性を実感
結論: 開発はもっと簡単になるべきではないか
- 1.5週間の開発でローカル/オフライン音楽プレーヤーという目的を達成
- 実際にはアプリ自体の配布にも制限がある: 開発者証明書がなければ7日後に使用不可となり、年間99ドルの開発者登録が必要
- EU DMA Act後もサイドローディングは完全には開放されておらず、個人・趣味開発者には依然として制約が続く
- PWAもiOSでは主要API未対応などの制限がある(Web Bluetooth/USB/NFC、Background Syncなど)
- AIによって開発の障壁は下がったが、iOSだけは人為的な規制で参入障壁が高い
- 独立開発者とユーザーの権限制限は依然として続いており、iOSの閉鎖性は今なおイノベーションの障害要因となっている
1件のコメント
Hacker Newsのコメント
yt-dlpを使ってサーバーにダウンロードされ、そこからストリーミングできる。再生位置を常に記憶するので、車で聴いていた位置をそのまま職場のノートPCで続きから再生できるという。NTS Radioなど他ソースのミックス追加もうまく機能している