AndroidでデスクトップPCのメディア(音声・動画)ファイルを再生する
開発::util
序
これは、かなり前から考えていたことだ。 「PC上のメディアファイルをスマートフォン上で再生したい」と。
これは前々から考えていたことではある。 今回実現に至るまでにしたかったことは主にふたつだ。
ひとつは、最近のYouTubeのひどさだ。 不快な動画ばかりを並べられ、毎日大量にブロックしても無限に同じような(というかほとんど同じ種類の)動画を出し続ける。 しかも、YouTubeは自らのスロットリングスクリプトによってほとんどまともに再生できなくなっている。
これが特に困るのがお皿を洗っているときだ。 お皿洗いのしんどさを軽減するために動画を観ながらしているのだが、YouTubeを開くとただただブロックしているだけになってしまい、無駄な時間を浪費して、しかもただただストレスをためている。
もうひとつがおふとん用だ。 「まだ眠りに入れる状態ではないが、もう横になりたい」というときが結構ある。 こういうときは動画でもみながらゆっくり心を落ち着けたいのだが、ここでYouTubeを開くと……である。
ついでに言うと、ASMR作品を横になって聞きたいときというのもある。 だから、スマートフォンで再生できると再生コントロールも手元でできて便利。
それを実現したのがhttpplayだ。
httpplay自体は簡単なプログラムであり、やることが分かっていれば誰にでも簡単に実装できるようなものだ。 そこで、今回は知識と考え方に絞って解説していく。
なお、「YouTubeのかわり」というと、動画といえばほぼストリーミングの現代でそもそもローカルの動画ファイルとしてそんなにコンテンツがあるかという問題もある。 私に関してはこの問題はあまりない。 よく自動進行可能なゲームを放置で録画だけして、見やすいときに見るという方法をとっていたりする。 そんなに大量にあるわけではないが、ゲーム動画をソースにすればBGV程度に流しておける映像ソースはそれなりにある。
前提
PC上のデータをスマートフォンで再生する方法はそれなりに色々ある。 例えば、CIFS, SSH, WebDAVなどでファイルを共有する方法、SFTPに対応したプレイヤーで再生する方法、一時的にスマートフォンに転送する方法などなど。
だが、それらは
- すごくめんどくさい
- 様々なPCのファイルを共有するのには適さない
- セキュリティが低下する
といった問題を生じる。
Androidスマートフォンで安全かつ手軽に、メディアファイルをシェアする方法を求めているわけだ。
mpv
キーになるのがmpvだ。
mpvはffmpegをコーデックに利用するマルチメディアプレイヤーアプリケーションだ。 UIは最低限であるため、多くの一般ユーザーは好まない(というより、mpvをバックエンドに使う別のプレイヤーを使う)が、非常に多機能かつ反応が良い上に、大部分の機能にキーボードでアクセスでき、慣れれば非常に使いやすい。
mpvのAndroidバージョンは、Android向けViやSKKのようにただPC向けのものをAndroidに使えるようにしただけのものではなく、スマートフォンにしっかりと最適化されたUIになっている。
そして、最大のポイントになるのがサポートされているプロトコルだ。
http/httpsや、youtube-dlを使うytdl、smb、bd/dvd/cdda、fd(file
descriptor)などサポートは幅広い。
ちなみに、manpageにはないが、sftp://
も解釈される。
こうしたプロトコル、コマンドラインが使えないAndroidでは使えなさそうに見えるが、「URLを開く」を使い様々なプロトコルを利用できる。
ただし、sftp://
はうまく動作しない。
ここで重要なキーになるのが、http/httpsプロトコルをサポートしていることである。 これらのプロトコルでアクセス可能な動画ファイルがあればmpvから直接再生可能なのだ。 ちなみに、YouTubeの動画のページを指定することもできる。
mpvは基本的に(可能な限り)バッファリングしながら再生する。 httpでアクセスした場合も、全ファイルダウンロードしてからではなく、バッファリング分をダウンロードしながら再生する。 このため、すぐ再生できる一方、ダウンロードが間に合わないと再生が止まってしまう。このため、現在から大きく離れた時間に飛ぶこともできない。
m3u
m3uは非常にシンプルなプレイリストファイルだ。 最小では、ただ1行につき1つファイルパスを書けばプレイリストとして成立する。
m3uがどのように解釈されるかはプレイヤー次第だが、一般的にローカルファイルの相対パスの場合は、m3uファイルからの相対になる。
さて、mpvの場合はm3uプレイリストはファイルパスである必要はなく、mpvがサポートする任意のプロトコルをファイルパスとして書ける。 もちろん、httpでもだ。
#EXTINF
行を使ってタイトルを与えることもできる。
#EXTINF
は長さとタイトルから成るが、長さに-1
を与えると長さ不明のストリームとすることができる。
メディアファイルの構造
マルチメディアのコンテナフォーマットは基本的にデータファイルの構造や種別、そしてメタデータを先頭部分に置き、先頭部分だけを読めばデータの全体像を把握した上であとは先頭から再生していけるのが普通だ。
このため、#EXTINF
で長さを-1
にすると長さは不定であるように見えるが、実際はファイルを読み始めるとすぐに長さを判定できるのが普通。
なお、mpvはHTTPで返されるContent-Type
が実際と異なっていても、それなりにうまく再生してくれる。
以上を踏まえた仕組みと方法
基本的なコンセプト
mpvでHTTP経由で、httpプロトコルのファイルが列挙されたm3uファイルを取得すれば、ネットワーク経由の再生が可能だ。
httpのURLを入力すればmpvで完結できるが、それはなかなか大変なので、プレイリストのURLを得る部分はウェブブラウザでアクセスできると良いだろう。
機能要件はそれだけで、あとはこれを安全な形で実現できるようにすれば良い。
私は今回、使い慣れたRubyを使うことにし、Thin/Sinatraで実装した。 大まかな手順としてはこうだ。
- サーバーを起動する
- 対象に入れたいファイルを追加する
- スマートフォンのブラウザでアクセスし、プレイリストのURLをコピーする
- mpvでURLを開く
サーバーのAPI
サーバーが必要とするAPIは3つのGET
- ウェブブラウザでアクセスする、プレイリスト一覧のページを返す
- m3uプレイリストを返す
- メディアファイルを返す
メディアファイルを返す部分はContent-Typeをちゃんと設定する必要がある。
データベースとマッピング
プレイリストごとにAPIを生やすことができれば話は簡単だが、柔軟性と安全性を考えるとそうはいかない。 パスパラメータを使うことになる。
実際のファイルシステムへのアクセスを可能にするとセキュリティ上非常に問題があるため、データベースでマッピングを行い、完全に固定のファイルにのみアクセス可能にする。 データベースを文字列ハッシュマッピングとすることで、紛れの入り込む余地を消している。
一応、URLとしての紛れ、ファイルパスとしての紛れがなくなるよう、また正規化に関わるファイル名の問題を軽減するため、プレイリストID=ディレクトリパスをMD5のhex-digestとすることで扱いやすくしている。
再生するのはあくまで(再帰しない)ディレクトリ単位とするのも問題を簡単にするため。 サーバー側では普通にシンボリックリンクを解決できるので、ディレクトリにまとまっていない場合もリンクファームで対応できる。
サーバーでステートを持たず、常にデータベースを読むようにすることで、途中でプレイリスト追加→ブラウザでリロードとすることでプレイリストを更新していける。 タイミング問題が起きうる設計だが、自分ひとりで、かつ手動で使うものなので特に気にしない。何か問題があれば500が返るだけだ。
なお、メディアファイル取得も、ファイル名を使わずインデックスだけにすることによって紛れがないようにしている。
不正なインデックスが指定された場合はnil
になるので、やはり500が返るだけだ。
設定とフィルタ
普段はループバックデバイスにバインドすることで問題を避けているが、今回の場合はスマートフォンからアクセスするため、この方法が取れない。
ポートは設定可能にしておくとして、許容するソースアドレスを限定することで安全性を高める。