セルフホスト型音楽配信サーバ&プレイヤーKanadeを作った

はじめに#

「前編の続きがないな, おかしいな…」

それは… 本当に… そう

2022年にRaspberry PiとMediaPlayerを使ってOpenHomeレンダラを作る (前編)という記事を書いて, 最後にしれっと「後編に続く」と書き残したのですが, 続きませんでした.

4年も.

この記事は, 2022年の記事で予告していた「MediaPlayerで続きをやる後編」ではありません.

その代わり, 使っているうちに色々と気になり始めて, 気付いたら自分で音楽サーバを作っていたという話です.

つまり今回は, 「前編の続き」ではあるのですが, 内容としてはかなり斜め上です.

前編では, OpenHomeレンダラとして使えそうなJava製のMediaPlayerを見つけて,

これで行けそうだし, 後編で環境構築を書こう

みたいな顔をして終わっていました.

しかし現実には,

  • しばらくMediaPlayerを使った
  • 便利ではあった
  • でも不満がじわじわ溜まった
  • どうせなら自分の欲しい構成に全部合わせたい
  • じゃあ作るか

という流れで, セルフホスト型の音楽配信サーバ兼プレイヤーであるKanadeを作ることになりました1.

この記事では, 前編のその後として

  • MediaPlayerを使ってどう感じたか
  • なぜ一から作る方向に転んだのか
  • Kanadeがどういうものなのか
  • どんな設計にして, 何を気に入っているのか
  • 実際どうデプロイして, どう使っているのか

を, だいぶ長めに書いていきます. マジで長いです.

今回作ったもの:

GitHub - petitstrawberry/kanade: A self-hosted music player server written in Rust.

A self-hosted music player server written in Rust. - petitstrawberry/kanade

GitHub - petitstrawberry/KanadeApp: Native iOS & macOS client for kanade — a self-hosted, multi-room music system.

Native iOS & macOS client for kanade — a self-hosted, multi-room music system. - petitstrawberry/KanadeApp

しばらく前編の続きと動機の話が続くので, Kanade自体の説明が読みたい方はこちらまで飛ばしてください.

前編のあと, どうなったのか#

まずは2022年の記事の後日談からです.

前編を書いた時点では, MediaPlayerはかなり良さそうに見えていました.

実際, OpenHomeレンダラとしてちゃんと使えるというだけでも当時の自分にはかなり魅力的でした.

moOdeやVolumioのような「全部入りの音楽再生ディストリビューション」は便利なのですが, 前編でも書いたように, 自分が欲しかったのはあくまで

  • OpenHomeコントローラから扱えること
  • 余計なレイヤーで隠蔽されすぎていないこと
  • 必要なら自分でいじれること

でした.

その意味で, MediaPlayerはかなり筋が良かったです.

Java製で, OpenHomeを話せて, バックエンドにMPDを使えて, プラグインもある.

当時の私は「これだ」と思いました.

思ったのですが, しばらく使っていると, 当然ながら色々欲が出てきます.

MediaPlayerがダメだったわけではない#

GitHub - PeteManchester/MediaPlayer

Contribute to PeteManchester/MediaPlayer development by creating an account on GitHub.

ここで大事なのは, MediaPlayerがダメだったという話ではないということです.

むしろしばらく動いていましたし, 前編で苦しんでいた「音楽再生ディストリビューションの都合に振り回される感じ」からはかなり解放されました.

ただ, 使えば使うほど,「自分が本当に欲しいのは何なのか」がより具体的になってきます.

統合されたシステムが欲しい#

特に大きかったのは, 再生の責務がいくつかのソフトにまたがる構成に対する, うっすらしたもやもやです.

前編の頃からずっと, 私は

  • ライブラリ管理
  • コントロール
  • レンダラ
  • 配信
  • 付加機能

が分かれた構成そのものは嫌いではありませんでした.

むしろUnix哲学っぽくて好きです.

でも, 好きと毎日気持ちよく使えるは別でした.

バラバラだった自宅の音楽再生環境#

具体的に言うと, Kanadeを作る前の私の音楽再生環境はこんな感じでした.

  • 自宅サーバでMinimServerをDLNAサーバとして動かす
  • リビングのRaspberry PiはDLNAレンダラとして, Lumin Appなどのコントローラから操作
  • 一方でスマホでは, foobar2000 mobileでDLNAサーバから無理やり引っ張って再生

要するに, 家の中と外で別の仕組みを使っていました.

リビングで聴く時はLumin Appを開いて, DLNA経由でRaspberry Piに投げる.

外出中はfoobar2000 mobileを開いて, DLNAサーバから直接引っ張ってくる.

ライブラリは同じMinimServerにあるのですが, アクセス方法もUIもプレイリストもキューも全部別.

しかもfoobar2000 mobileのDLNAクライアントとしての使い勝手は, 正直「なんとか聴ける」レベルでした. アーティスト一覧が出るまでが遅いし, キューの管理も微妙だし, そもそもDLNA経由だと外出先から自宅にアクセスするのにVPNを張るなり工夫が必要です.

Roon ARCという商用サービスを知っている方はいるかもしれません. Roonは自宅ライブラリの管理とリモート再生を統合したシステムで, Roon ARCというアプリを使えば外出先からも自宅ライブラリにアクセスして, スマホでそのまま聴けます. 統一されたライブラリ, 統一されたUI, 家でも外でも同じ体験.

これが欲しかった…

ただしRoonはサブスクリプションで年額$150くらいかかるし, 自分のサーバで自分のライブラリを自分の都合で扱いたいという趣旨からは外れます.

結局のところ, 商用のSpotifyやApple Musicのような「統合されたシステム」が, いかに日常的な使い勝手を高めているかを痛感することになりました.

では, DLNAサーバを主軸にした構成を捨てて, メディアサーバを主軸にした構成に移行しようという考えになります.

なぜ既存のセルフホスト型音楽サーバではダメだったのか#

「いや, 世の中にセルフホスト型音楽サーバなんていくらでもあるんだから,それ使えばいいだろ」と思う方がいるかもしれません.

Jellyfin, Navidrome, Subsonic, Airsonic, Gonic, etc…

セルフホスト界隈は, 音楽系サーバの種類が豊富です. 豊富なんですが, 実際に使ってみると, 私の用途とはだいぶズレていました.

どれも「音楽をブラウザやアプリから聴く」という目的には十分なのですが,

  • タグの扱いが雑
    • 日本語のタグが怪しい, DSDをサポートしないなど
  • そもそも重い
  • オーディオ志向の機能(lossless配信, DSD対応)が薄い
  • 「音楽ライブラリを自分の好みに合わせて扱う」という思想が弱い

あたりが, 私の用途とは合いませんでした.

要するに,

「日本語のタグをちゃんと読んで, losslessで配信して, 軽くて, 自分の好きにできるやつ」

が欲しかったのです.

そんな都合の良いものがある訳がなく, じゃあ作るか, という流れです.

じゃあ何が欲しかったのか#

MediaPlayerやMPDまわりでしばらく遊び, さらに既存の音楽サーバも一通り試した結果, 欲しいものはだいぶ明確になりました.

大雑把に言うと, 欲しかったのはこんなシステムです.

  1. 自分の音楽ライブラリを自分で持てる
  2. サーバが権威を持ち, クライアントは薄くしたい
  3. スマホで, 商用サービスと同じ体験で聴けること(一番大事)
  4. 再生先を複数マシンに分散したい
  5. でも操作感は一つのシステムとして揃えたい
  6. iPhoneやMacでも再生したい (外出時も)
  7. Webからも雑に使いたい
  8. 付加機能はあとから足したい

こうして書くと, まあまあ面倒くさいですね.

というか, 既存ソフトを組み合わせて作れなくはないが, 綺麗にはまとまらない類の要求です.

しかし, どうしてもこの構成が欲しかったので, じゃあ自分で作るかということになりました.

Kanadeとは何か#

Kanadeは, 自分の音楽ライブラリをスキャンして管理し, 複数のクライアントから操作でき, 複数の再生ノードへ配信できる, セルフホスト型音楽配信サーバ&プレイヤーです.

GitHub - petitstrawberry/kanade: A self-hosted music player server written in Rust.

A self-hosted music player server written in Rust. - petitstrawberry/kanade

名前は日本語で「奏」です. (あの, 某吹奏楽アニメのキャラから取りました…)

自分のライブラリを, 自分のサーバで, 自分のプレイヤーに送る.

分かりやすくて良い.

やっていることをざっくり言うと,

  • サーバが音楽ライブラリをスキャンする
  • クライアントはサーバに繋いで一覧や検索結果を見る
  • 再生先となるノードは別プロセスとしてサーバに接続する
  • 各ノードは独立したキューを持つ
  • 必要に応じてローカル再生もできる

という構成です.

全体のアーキテクチャ#

まずは雑に全体像を出します.

  Web / TUI / iOS / macOS
             │ WebSocket / HTTP
      Kanade Server (:8080)
   ┌───────────────────────┐
   │ kanade-core           │
   │ kanade-db (SQLite)    │
   │ kanade-scanner        │
   │ axum WS + media       │
   └───────────────────────┘
        │             │
        │             └── HTTP media / HLS
        ├── WebSocket → Node A (MPDなど)
        ├── WebSocket → Node B (別マシン)
        └── WebSocket → Node C (別マシン)

ポイントは, サーバが単一の権威を持つことと, クライアントやノードはそこにぶら下がることです.

この構成にしたことで, 「複数クライアントから見た状態がズレる」問題をかなり避けやすくなりました.

技術スタック#

サーバ側はRustです.

主に使っているものは以下の通り.

  • Rust
  • tokio
  • axum
  • SQLite
  • lofty

クライアント側は用途ごとに分かれています.

  • Web: Svelte 5 + hls.js
  • TUI: ratatui
  • iOS / macOS: Swift + AVFoundation

Kanadeの主な機能・特徴#

Kanade Web UI

機能を列挙するとだいたい以下の通りです.

  • ライブラリ管理
  • ストリーミング再生
  • 独立した再生ノード
  • ローカル再生

ライブラリ管理#

  • ローカルの音楽フォルダをスキャン
  • メタデータを保存して全文検索対応
  • アルバム, アーティスト, ジャンル, 曲単位でブラウズ
  • プレイリスト管理
    • 手動プレイリスト
    • スマートプレイリスト

対応フォーマットはかなり広めで,

  • FLAC
  • MP3
  • M4A
  • Ogg Vorbis / Opus
  • WAV
  • AIFF
  • WMA
  • APE
  • DSD (DSF)

などを扱えます.

DSDまで視野に入っているのは, オーディオ系の自宅ライブラリとしては割と大事です.

「そのへんの一般的なWeb音楽アプリの都合」ではなく, 自分都合で自由に追加したり修正したりできるのが大きいです.

スマートプレイリスト#

スマートプレイリストは個人的にかなり好きな機能です.

単なるプレイリストではなく, ルールベースで曲を集められます.

例えば,

  • ジャンルがClassical
  • かつ composer に特定文字列を含む
  • あるいは album artist に特定文字列を含む

みたいな条件を, AND / ORで組み合わせられます.

いわゆる「賢いプレイリスト」です.

賢いのはプレイリストであって, 作っている側はそこまで賢くありません.

この機能は, Audirvanaに同様のものがあって, 使っていた時に非常に便利だったのです. ライブラリが増えてくると, 「この条件で絞り込んだプレイリスト」が日常の選曲でかなり効く. ジャンルで絞ったり, 最近追加した曲だけ拾ったり, 特定の作曲家だけ集めたり.

ところが, これをDLNA/UPnPの世界でやろうとすると, まずできません.

DLNAサーバは, せいぜいディレクトリ構造を配信しているだけです. 設定を作り込めば可能かもしれませんが,DLNAクライアントアプリから中身を編集したりはできません.普通の商用サービスならアプリで全部完結するので, これは認められないです.

Audirvanaであれだけ便利だったスマートプレイリストを, モバイルでも, 据え置きの再生機でも, 同じように使いたい. これもKanadeを作る動機の一つでした.

ストリーミング再生#

KanadeはHLS経由で音声を配信します.

Webクライアントではhls.jsを使ってそのまま再生できますし, ネイティブクライアントでもHLSプレイリストをフェッチして再生する形です.

ストリーミングとローカル再生が同じ配信経路に乗っているので, クライアント側の実装が統一されています.

独立した再生ノード#

Kanadeの最大の特徴が, ノードごとに再生状態が独立していることです.

各ノードはそれぞれ

  • キュー
  • 音量
  • シャッフル
  • リピート
  • 現在位置

を個別に持ちます.

つまり,

  • リビングではアルバムを通しで流す
  • 書斎では別のキューを作る
  • 手元のiPhoneではローカル再生する

みたいなことを, 一つのサーバ配下で扱えます.

この「部屋ごと, 端末ごとに独立しているが, ライブラリは一つ」という感じが, 私が欲しかった形そのものでした.

マルチルーム#

ノードは別マシンに置けます.

つまり, Raspberry Piでも, Linuxマシンでも, 何なら別のMacでも, サーバに繋いで再生先として扱えます.

結果として,

  • サーバはNASの近くに置く
  • 再生ノードは各部屋のマシンに置く
  • コントロールはWeb/TUI/iPhone/Macからやる

という構成が素直に組めます.

前編ではRaspberry Pi上のレンダラ構築が主題でしたが, Kanadeではその発想をもう少し一般化して, 「音が出る場所」をノードとして抽象化しました.

ローカル再生#

サーバから別ノードへ投げるだけではなく, 手元の端末自体で再生することもできます.

具体的には,

  • iOS / macOSではAVFoundation
  • Webではhls.js

を使ってローカル再生できます.

Apple系のネイティブアプリは別リポジトリです2.

GitHub - petitstrawberry/KanadeApp: Native iOS & macOS client for kanade — a self-hosted, multi-room music system.

Native iOS & macOS client for kanade — a self-hosted, multi-room music system. - petitstrawberry/KanadeApp

これがあると, 据え置きの再生ノードに投げる使い方だけでなく,

「今日はヘッドホンで聴きたいから, この端末自身で鳴らす」

が自然にできます.

ここは「サーバなのにプレイヤーでもある」というタイトルの, まさに後半部分です.

詳細設計と実装の話#

ここからは, 何ができるかではなく, どう作って, なぜそうしたのかという細かい話です. 長いのでスキップしてもらっても全然大丈夫です.

詳細設計と実装の話を開く

サーバが権威を持つ#

Kanadeでかなり強く意識したのが, クライアントをなるべくステートレスにすることです.

クライアントが賢くなりすぎると,

  • WebとiPhoneで違う挙動になる
  • TUIだけちょっと状態同期がズレる
  • 接続し直した時に何が正なのか分からなくなる

みたいなことが起きがちです.

これは地味ですが, 毎日使う道具としてはかなりつらいです.

なのでKanadeでは,

  • 状態はサーバが持つ
  • クライアントは表示と操作に集中する
  • 状態変化はサーバからpushする

という形に寄せました.

WebSocketを中心にしているのもそのためです.

一度繋いでしまえば,

  • 現在のノード一覧
  • 選択中ノード
  • キュー
  • 再生状態
  • スキャン後の更新

といった情報を, サーバから素直に流せます.

論理再生先と物理出力を分けた#

音楽サーバを作っていると, 「どこで音を出すか」という一つの概念の中に, ごちゃ混ぜになりがちなものがあります.

  • キューとかシャッフルとか, ユーザが見るもの
  • MPDを叩くとか, 実際に音を出すI/Oの話

これをそのまま一つの扱いにすると, 一見簡単そうでいて後から広げにくくなります.

なのでKanadeでは, ここを二つの概念に分けました.

Node は論理的な再生先です. 「リビング」「書斎」「このiPhone」みたいな単位で,

  • 名前を持つ
  • 独自のキューを持つ
  • 再生状態を持つ
  • 音量やシャッフル, リピートを持つ

というものです. ユーザが見て操作する対象はこっちです.

Output は物理的な音の出口です. 実際に音を出すためのI/Oアダプタで, MPDを叩くものでもいいし, 将来的には別のバックエンドでも構いません. こっちは実装詳細です.

Nodeをユーザが見る対象, Outputを実装詳細として分けたことで,

  • ノード間ハンドオフ
  • 将来的な複数出力
  • バックエンド差し替え

のような話がだいぶ扱いやすくなりました.

独立キューとハンドオフ#

NodeとOutputを分けた結果, 自然に生えた機能が独立キューハンドオフです.

独立キュー#

これは実際に使い始めるとかなり効きます.

従来の「一つのプレイヤーに一つのキュー」だと,

  • ある部屋で再生していた流れを壊したくない
  • でも別の部屋では別のものを流したい
  • さらに手元の端末では試聴したい

みたいな時に困ります.

Kanadeではノード単位でキューが独立しているので, そのへんが綺麗です.

しかもノードごとに設定も独立しているので,

  • こっちはshuffle off
  • こっちはshuffle on
  • こっちはrepeat all

みたいなことも普通にできます.

このあたりは, 「マルチルーム対応」というより複数の再生文脈を同時に持てるという方が実感に近いです.

ハンドオフ#

KanadeApp側も含めて気に入っているのが, ハンドオフです.

ハンドオフは, あるノードのキューと再生位置を別ノードへ引き継ぐ機能です.

要するに,

  • リビングで流していたものを
  • そのまま書斎へ持っていく

みたいなことができます.

しかも単に曲を投げ直すだけではなく,

  • キュー
  • 現在位置
  • シャッフル
  • リピート

のような「再生文脈」も一緒に持っていけるのが嬉しいところです.

音楽再生って, 単にファイルを再生するだけではなくて, その時点の文脈が結構大事なんですよね.

アルバムを何曲目まで聴いたとか,

適当に追加したキューの並びとか,

そういうのを含めて今の再生です.

だからハンドオフは, 思っていた以上に「生活の中の音楽再生」に効きました.

配信の仕組み#

ローカル再生, 特にWebやApple系クライアントのことを考えると, どうやって音を届けるかはかなり重要です.

KanadeではここにHLSを使っています.

具体的には, 音声ファイルをfMP4へremuxしてセグメント化し, HLSプレイリスト経由で配信します. 音声データそのものの再エンコードはしないので, losslessのまま持っていける形式はそのまま配信できます.

HLSセグメントは事前に全部作るのではなく, 必要になった時にremuxしてキャッシュします.

この方式にしたので,

  • 事前変換でディスクを浪費しにくい
  • 初回アクセスだけ多少仕事する
  • 二回目以降はキャッシュが効く

というバランスになります.

自宅で使うセルフホスト用途としては, かなりちょうど良いです.

署名付きURL#

メディア配信では, セキュリティ面もそれなりに考えました.

Kanadeの /media/*署名付きURLで保護しています.

仕組みとしては,

  • WebSocket接続ごとにセッションを作る
  • サーバがセッション単位のHMAC-SHA256鍵を持つ
  • クライアントは生鍵を受け取らない
  • 必要なパスに対してサーバへ署名済みURLを要求する
  • URLは15分で失効する

という形です.

クライアントは使い捨てのURLを受け取るだけなので, かなり扱いやすいです.

この方式だと,

  • 画像タグ
  • audioタグ
  • HLSプレイリスト取得

のような「HTTPでそのまま取りに行きたいもの」と相性が良いです.

実際今回も, MPDやiOS/macOSのAVPlayerでHLSをストリーミングするために, こういう形にしました.

タグとアートワーク#

ここは地味ですが, 割と大事なポイントです.

既存の音楽サーバ——Jellyfin, Navidrome, Subsonic系——は, FLACやMP3あたりのメジャーなフォーマットなら問題なく扱えます. タグもアートワークもちゃんと読む. そこは良い.

しかし, 日本語のタグDSD (DSF) などのマイナーなフォーマットになると, 急に怪しくなります.

日本語タグの問題#

日本語の音楽ファイルは, タグのエンコーディングや構造が欧美のファイルと異なることが多く,

  • アーティスト名が文字化けする
  • album artistとartistの区別が怪しい
  • 読み取ったと思ったら別のフィールドに入っている

みたいなことが起きます. 前の方でもNavidromeの話で触れましたが, これはNavidromeに限った話ではなく, 多くの音楽サーバが抱えている問題です.

「文字化けしてても曲は再生できるからいいじゃん」と思うかもしれませんが, アーティスト名が壊れていると検索にもヒットしないし, ブラウズもままならないので, 実用上かなり困ります.

DSDのタグとアートワーク#

DSDファイルのタグ構造は, FLACやMP3とは異なる部分があり, 多くのサーバライブラリはDSDを「一応読めるけどタグは適当」くらいの扱いになります. アーティスト名が出ないとか, アルバムアートが表示されないとか, そういう地味な不便が積み重なります.

オーディオ趣味を持っていると, 自宅ライブラリにはDSDソースが結構混ざります. SACDのリッピングとか, ハイレゾ配信サイトから買ったDSDファイルとか.

Kanadeでは, DSDのタグ読み取りにdsf-metaという専用のライブラリを使っています. 一般的なloftyだけだとDSD周りのカバーが弱いので, そこを別途補強する形です.

結果として,

  • DSDファイルのアーティスト, アルバム, タイトルが正しく表示される
  • アルバムアートもちゃんと拾ってくる
  • FLACやMP3と混ざっていても違和感なくブラウズできる

という状態になっています.

……もっとも, DSDの再生自体はまだクライアント側が追いついていないので, 現状はタグとアートワークがちゃんと見える, という段階です. 再生はこれから.

でも, ライブラリにちゃんと表示されることが第一歩なので, ここはちゃんとやれて良かったと思っています.

「対応フォーマットが多い」というとよくある機能に聞こえますが, 実際に日本語のタグが混ざったライブラリで, DSDファイルも混ざった状態で使ってみると, これがちゃんと動くサーバは意外とないのです.

ここがSubsonic系やJellyfinを使わない理由の一つでもあります. 対応しているはずなのに, 実際に自分のファイルを投げ込むとタグが壊れるとか, アートが出ないとか, そういう微妙なずれが積もっていく.

Kanadeは自分のライブラリで実際に使うことを前提に作っているので, こういう「他のサーバでは後回しにされがちな端っこ」も手を入れています.

シンプルであること#

ここまで色々書いてきましたが, Kanadeの設計は意外とシンプルです.

JellyfinやAirsonicのような「全部入りのメディアサーバ」と比べると, やっていることは音楽だけに絞っている分, 剥がせる層が多くありません.

動画プレイヤーもいらない, Live TVもいらない, ユーザ管理の複雑な権限モデルもいらない, プラグインのエコシステムもいらない.

「音楽ライブラリをスキャンして, メタデータを保存して, ストリーミングして, 再生制御する」

という, 音楽サーバとしてのコアだけをやる.

シンプルであることは, メンテナンスの観点でも大きな利点です. コードを読む範囲が限定的で, 壊れた時の切り分けもやりやすい.

自宅で自分が使うためのものなので, 「何でもできる」よりも「必要なことだけをちゃんとやる」方が結局長く使えます.

運用と使い心地#

ここからは運用の話です.

セルフホスト系の話は, どうしても設計の話だけで終わるとふわっとするので, 実際のデプロイ感も書いておきます.

デプロイ#

KanadeはDocker Composeでも, Nix flakeでも扱えます.

私はこの手のものは, 開発中はNixが便利で, 常用運用はComposeが楽という顔をしがちです.

だいたい皆そうかもしれません.

Docker Composeでの基本構成#

サーバ側はだいたいこんな感じです.

services:
  kanade:
    image: ghcr.io/petitstrawberry/kanade:main
    ports:
      - "8080:8080"
    environment:
      MUSIC_DIR: /music
      DB_PATH: /data/kanade.db
      HLS_CACHE_DIR: /data/hls-cache
      BIND_ADDR: "0.0.0.0:8080"
      PUBLIC_HOST: "kanade.example.com"
      SCAN_INTERVAL_SECS: "300"
    volumes:
      - ./music:/music:ro
      - kanade-data:/data

大事な環境変数はこのあたりです.

  • MUSIC_DIR
    • 音楽ライブラリのルート
  • DB_PATH
    • データベース配置先
  • HLS_CACHE_DIR
    • HLS remuxキャッシュ置き場
  • BIND_ADDR
    • WebSocket + HTTPメディア面の待受
  • PUBLIC_HOST
    • クライアント向けに見せる公開ホスト名
  • SCAN_INTERVAL_SECS
    • 定期スキャン間隔

PUBLIC_HOST は, リバースプロキシ越しに使う時に特に大事です.

サーバ内部の見え方と, クライアントが実際にアクセスするURLがズレると, メディアURLや発見まわりが綺麗に繋がりません.

この手の変数は地味ですが, ちゃんとあると運用がすごく楽になります.

NASとの連携#

上の例では ./music:/music:ro とローカルディレクトリをマウントしていますが, 実際には音楽ファイルはNAS上にあることが多いと思います.

Kanadeでは, SMB/CIFS用のCompose overlayを用意しています.

# docker-compose.smb.yml
# Usage: docker compose -f docker-compose.yml -f docker-compose.smb.yml up -d
services:
  kanade:
    volumes:
      - music:/music:ro
  mpd:
    volumes:
      - music:/music:ro

volumes:
  music:
    driver_opts:
      type: cifs
      o: "username=${SMB_USER},password=${SMB_PASS},ro,vers=3.0,iocharset=utf8"
      device: "//${SMB_HOST}/${SMB_SHARE}"

.env にNASの接続情報を書いておけば, あとはoverlayを重ねるだけでSMBボリュームが /music にマウントされます.

docker compose -f docker-compose.yml -f docker-compose.smb.yml up -d

NAS側でファイルを追加・整理したものが, Kanadeの定期スキャンで自動的に反映されます.

別マシンにノードを置く#

Kanadeの面白いところは, ノードを別マシンに出せることです.

例えばRaspberry Piや小型PC側に kanade-node だけ置いて, そこからローカルMPDを叩く構成にできます.

Standalone nodeのComposeはだいたいこんな感じです.

services:
  node:
    image: ghcr.io/petitstrawberry/kanade:main
    command: kanade-node
    environment:
      NODE_NAME: "living-room"
      SERVER_ADDR: "kanade.example.com:8080"
      MPD_HOST: "127.0.0.1"
      MPD_PORT: "6600"

必要な変数は少なめです.

  • NODE_NAME
    • 表示用のノード名
  • SERVER_ADDR
    • Kanadeサーバの接続先
  • MPD_HOST
    • ローカルMPDの接続先
  • MPD_PORT
    • ローカルMPDのポート

これだけで, サーバとノードを別マシンに分けられます.

リバースプロキシ#

外からHTTPSで気持ちよく使いたいなら, リバースプロキシを前に置くのが素直です.

nginxやCaddyで,

  • TLS終端
  • WebSocketのupgrade
  • 静的ファイルとAPIの振り分け

をやれば, iPhoneでもMacでもブラウザでもかなり素直に扱えます.

クライアントの使い分け#

Kanadeはクライアントが複数あります.

これも一見やりすぎ感がありますが, 実際には使い分けると結構便利です.

Web#

まず一番気軽なのはWebです.

ブラウザがあればすぐ使えますし, 家の中のどの端末からでもアクセスしやすい.

しかもHLS経由でそのままローカル再生できるので, 「ちょっと今この端末で聴きたい」という時に強いです.

雑に使えることの価値は高い.

TUI#

TUIは完全に趣味枠に見えて, 実際かなり趣味枠です.

でも, ターミナル常駐民にとっては妙にしっくりきます.

SSH越しに雑に触れるのも良いですし, 作業中に別ウィンドウで状態を見るのにも向いています. 楽しいというのは大事です.

iOS / macOSネイティブ#

KanadeApp on iPhone KanadeApp on Mac

日常使用という意味では, やはりネイティブクライアントがかなり便利です. ほとんどをこのクライアントの開発に時間を割いています.

単純にコントローラとして使うことができるだけでなく, その端末でローカル再生もできるのが大きいです.

GitHub - petitstrawberry/KanadeApp: Native iOS & macOS client for kanade — a self-hosted, multi-room music system.

Native iOS & macOS client for kanade — a self-hosted, multi-room music system. - petitstrawberry/KanadeApp

「今手元にあるものをそのまま再生先にできる」というのは, 思っている以上に自然です.

特にMacでは, 据え置き再生機に投げるか, このMacで鳴らすかをその場で選べるのが快適です.

実際に使ってみてどうか#

ここは一番大事なところです.

自作システムは, 作っている時点では何でも最高に見えます.

しかし, 本当に重要なのは毎日雑に使っても嫌にならないかです.

Kanadeについて言うと, 今のところかなり良いです.

良いところ#

まず, 自分の欲しかった形にかなり近いです.

特に以下の点は満足度が高いです.

  • ライブラリと再生が一つのシステムとして繋がっている
  • サーバが権威なので, クライアントを跨いでも状態が分かりやすい
  • ノード単位で再生文脈を持てる
  • ローカル再生と据え置き再生機への出力を同列に扱える
  • 電車の中でもスマホでSpotifyと同じ感覚で聴ける

前編を書いていた頃の自分が見たら, たぶん「なんでそんな大事にしたんだ」と言うと思います.

私もそう思います.

それでも自作は自作#

もちろん, 自作システムなので万能ではありません.

既製品や成熟したOSSのように, あらゆる環境で雑に動くというものではないですし, 細かい詰めはまだ色々あります.

ただ, 少なくとも自分が普段使う文脈では,

不満の出どころを自分で直せる

というのが本当に大きいです.

前編の頃は, あるレイヤーの都合が気になっても, そこから先に進むのが重かった.

今はその大半が自分の管理下にあります.

これは自由であると同時に, 普通に保守責任も自分へ飛んでくるということですが, まあ趣味なので大丈夫です.

たぶん.

そのうち書きたいこと#

Kanadeまわりは, この記事だけでは書ききれない話がまだ色々あります.

例えば,

  • node protocolの詳細
  • KanadeApp側の実装
  • HLS remuxまわりの実装詳細

などは, それぞれ単独記事にできるくらいにはあります.

ただし, ここで「続きは後編で」と言うと嫌な予感しかしません.

学習しました. ので, 今回はあまり大きな予告はしません.

書けたら書きます.

おわりに#

という訳で, 4年間放置された前編の続きとして,

今回はMediaPlayerの後編ではなく, 自作のセルフホスト型音楽配信サーバ&プレイヤーKanadeを作ったという話を書きました.

前編を書いた時点では, まさかここまで話が大きくなるとは思っていませんでした.

興味があればリポジトリも見てみてください.