世界を動かす技術を、日本語で。

自分だけのオーディオプレーヤーを作りました

2025年5月22日原文(nexo.sh)

概要

  • 2025年のiPhoneでは、自分の音楽ファイル再生が困難であり、Appleの有料サービスや制限を回避する必要がある現状を説明。
  • その課題を解決するため、独自の音楽プレーヤーアプリをゼロから開発した経緯を紹介。
  • iCloud対応、全文検索、ローカル優先設計など、主要な技術的特徴を解説。
  • React NativeからSwiftUIへの技術選択の流れと理由を詳述。
  • SQLiteを用いた全文検索やiOSファイル管理の実装ポイントを整理。

iPhoneで自分の音楽を再生する困難さと独自プレーヤー開発

サブスクリプション依存とAppleの制限

  • iPhoneで 自分の音楽ファイル再生 は、Apple MusicやiCloudの 有料サービス を利用しないと実現が難しい現状を確認。
  • Apple Music解約後は iCloud Music Libraryの同期機能 も利用不可となり、iTunes Match(年額$24.99)で部分的に復活するが、 オリジナルファイル管理や柔軟性 に課題が残ることを認識。
  • サブスクリプション型の第三者アプリも多く、 所有ファイル再生という単純なニーズ に対して不満を感じるユーザーが多いことを確認。

公式・サードパーティアプリの現状

  • AppleのFilesアプリでは 音楽再生機能が限定的 で、プレイリスト管理やメタデータ並び替え、キュー管理などが不十分であることを確認。
  • サードパーティ製アプリ(例:Doppler)は 単体課金モデル も存在するが、iCloudからの大量インポートや全文検索機能が弱く、 理想的なUXに到達していない ことを確認。

技術選択と開発プロセス

React NativeからSwiftUIへの移行

  • 最初は React Native/Expo で開発を開始し、Web開発経験を活かそうとしたが、 iCloudディレクトリの深い階層探索やファイルアクセス制限 で多くの問題に直面。
    • expo-filesystem等のライブラリは 大規模なフォルダの再帰処理に弱く、アプリクラッシュが発生しやすい状況を確認。
    • iOSのサンドボックス制約により、 外部フォルダへの安定アクセスが困難 であることを実感。
  • SwiftUI へ切り替え、宣言的UIと async/await・Swift Actors によるデータ同期・並列処理のしやすさを評価。
    • ViewModel分離により LLM(OpenAI o1, DeepSeek等)によるUIコード生成 の効率化も実現。
    • AppleネイティブAPI活用で iCloudファイルアクセスや権限管理 が容易になったことを確認。

アプリ構成とデータモデル

  • SQLite を用いた永続データストアを選択し、スキーマ制御や全文検索(FTS5)を重視。
    • CoreDataは 柔軟なクエリや全文検索 が難しいため採用せず。
  • アプリは 3画面構成 で以下の機能を実装。
    • ライブラリインポート画面:iCloudフォルダ選択・スキャン・DB登録を実行。
    • ライブラリ管理画面:追加済み楽曲のプレイリスト・管理・整理を実現。
    • プレーヤー画面: キュー管理(リピート・シャッフル)や再生操作 を実装。
  • ユーザーフロー例:
    • 初回起動時はSyncタブで「Add iCloud Source」ボタン表示→フォルダ選択→インデックス化→Libraryタブへ遷移→曲選択でミニプレーヤー起動→必要に応じてSyncで追加フォルダインポート可能。

バックエンド的なロジックレイヤー設計

  • サーバアプリ的な レイヤードアーキテクチャ を採用し、UI/ViewModel層とドメインロジック層を明確に分離。
    • SQLiteが最下層で生データ・FTSインデックスを保持。
    • Repository層が非同期APIを提供し、その上にSwift Actorによる ビジネスロジック(インポート・検索・キュー管理) を実装。
    • ViewModelはActorからデータを受け取りUI用Structへ変換、SwiftUIビューはそれを描画するのみとすることで 疎結合設計 を実現。

SQLiteによる全文検索実装

FTS5テーブル設計

  • iOS 11以降は SQLite FTS5拡張が標準搭載 されており、サードパーティ依存なしで 高速なファジー検索 を実現。
  • SQLite.swiftライブラリで通常クエリ、FTSは生SQLで実装。
  • FTS5テーブルは2種用意:
    • 楽曲インデックス用(artist, title, album, albumArtist列)
    • フォルダインポート用パスインデックス(fullPath, fileName列)
  • テーブル例:
    CREATE VIRTUAL TABLE IF NOT EXISTS songs_fts USING fts5(
      songId UNINDEXED, artist, title, album, albumArtist, tokenize='unicode61'
    );
    
    • unicode61トークナイザーで多言語対応。
  • 追加・更新は トランザクションで安全に処理 し、インデックスの不整合を防止。

ファジー検索とランキング

  • ユーザー入力に自動でワイルドカードを付与し、 部分一致検索 を高速化。
  • 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 ?;
    

iOSファイル管理とブックマーク運用

  • iOSでは セキュリティスコープ付きブックマーク がmacOSのみ対応で、iOSでは通常のブックマークのみ利用可能。
  • ブックマークの有効期限切れや権限リセットに備え、 ファイルをアプリ内サンドボックスへバックグラウンドコピー することで、 再生時のパス不整合リスクを回避 することを重視。

このように、 Appleの制約や既存アプリの限界 に対して、自作プレーヤーを開発し、 全文検索・ローカル優先・iCloud連携 を実現した技術的アプローチと設計思想を体系的に整理した。

Hackerたちの意見

これはいい読み物だね、まだ読み終わってないけど。開発者が決める細かい詳細やその理由について読むのが好きなんだ。正直言うと、音楽アプリの中で試したプレーヤーはどれも好きじゃないけど、画面やUIのレイアウトはほとんど共通してる気がするし…なんかピンとこないんだよね。どの音楽アプリとも戦ってる気分だよ…新しいものを作ろうとする人には感謝してる。

Music.appでローカル音楽を管理して、FinderでiPhoneと同期するのはまだうまくいってるけど、Music.appはここ数年メンテされてないみたいで、ひどいBig Surのリライト以来、いくつかのイライラするバグがあるんだ。それでもこの組み合わせは、見た中で最高の音楽ライブラリ管理とモバイル同期のソリューションだと思う(おすすめあったら教えて!)、でもソフトウェアがメンテされてないしクロスプラットフォームじゃないから、最終的には自分でシステムを書かなきゃいけなくなると思う。

Musicの「リライト」の問題の一部は、実際にはリライトというよりiTunesからのコピペに近いってことだね。OS 9時代からの名残のようなモーダル設定/プリファレンスウィンドウなど、iTunesの quirks がたくさん残ってる。私の予想では、最近出たWinUIベースの新しいWindows版Musicに似た、ゼロからの本格的なリライトが進行中なんじゃないかな。

私の5世代のビデオiPodでは、Finderの同期がまだすごく壊れてる。最もイライラするのはポッドキャストの同期を試みたときで、iTunesを使ってた頃は問題なかったけど、Big SurのFinder同期を使うとバグが多くて、他のトラック/ポッドキャストに移動して戻るとポッドキャストの再生位置を覚えてくれないから、数分以上のポッドキャストはデバイスで使えなくなっちゃう。

僕のアプリも試してみてよ: https://www.plastaq.com/minimoon

フルアルバムを聴きながら休憩したりデバイスを切り替えたりできるウェブアプリを作ったんだ。アルバムを最初から最後まで聴くのが好きなんだけど、少なくともYouTube Musicは再生位置を覚えてくれないし、他のデバイスに切り替えるときにアルバムを再度開いて、どこまで聴いたかを探さなきゃいけないのが面倒なんだ。私のウェブアプリではURLを貼り付けると、yt-dlpを使ってサーバーにダウンロードされて、そこからストリーミングできるんだ。再生位置を常に覚えてくれるから、車ではスマホで聴いて、仕事のノートパソコンではその続きから聴けるんだ。他のソースからのミックスを追加するのにもすごく便利で、NTS Radioみたいな私のお気に入りにも対応してるよ。

あなたが言ってることは、YouTube Musicに対する私の最大のフラストレーションの一つだね。キューを保存したり、デバイスをもっとスムーズに切り替えられたらいいのに。もしあなたのウェブアプリが利用可能なら、ぜひ見てみたいな。

最初はSwiftを避けてたんだ、以前の経験から […] 当時はネイティブのasync/awaitがなかったから、GoやJS/TSと比べて並行コードを書くのがもっさりしててボイラープレートが多かった。私は反対だな。Asyncは並行コードを書くのを簡単にするかもしれないけど、成長するにつれて考えるのが難しくなることもある。複雑なasyncコードベースでは、コードの流れや並行性を考えるのが難しいと感じる。スレッドコードの実行コストを下げるのが目的なら、軽量スレッドのグリーンライトという解決策がある。スレッドコードのメンテナンスコストを下げるのが目的なら、長期的にはasyncがもっと手間がかかる結果になると思う。

でも、彼の使い方だと、シンプルなオーディオプレーヤーが欲しいだけだから、これが問題になることはないと思う。

非同期は同時実行コードを書くのを簡単にするかもしれないけど、成長するにつれて理解しにくくなることもあるよね。複雑な非同期コードベースでは、コードの流れや同時実行性を理解するのが難しくなる。良い同時実行性は、コードが成長するにつれて理解しやすくするべきだと思う。プロセスやサービスベースのカプセル化を持つだけでも大きな勝利だよ。俺の意見では、これは非同期/待機の抽象化の失敗であって、同時実行性そのものの問題じゃない。

私はWinampが音楽プレーヤーの定番だった時代から来たんだ。今でもストリーミングサービスの時代でも、フォルダに整理されたローカル音楽ライブラリを持ってる。だから、ここにいる他の人たちと同じように、オフラインで音楽を聴くために昔風の音楽プレーヤーを趣味で作ったんだ。1ページのhtml/jsアプリで、フルキーボードコントロールがあって、シンプルなキュー機能もあるよ。見てみてね: https://nobsutils.com/mp

僕にとって、音楽プレーヤーはずっとfoobar2000だったよ。(今日はCogアプリに変わったけど)

Hacker Newsで議論の続きを見る