Chienomi

LAN内別デバイスでメディアファイルを再生 Local Web Media Player

開発::util

  • TOP
  • Articles
  • 開発
  • LAN内別デバイスでメディアファイルを再生 Local Web Media Player

諸君は覚えているだろうか。

「PCのメディアファイルをスマホで再生したい」

たったそれだけのために私が苦しみ続けていることを。

PCであれば(Linux前提の話だが)SSHFSを使えば簡単に実現できることだ。 たとえいくらか制約を加えられたとしても、PCなら割と「なんとかなりそう」のレベルにある。 (実際はWindowsだと結構苦労するが)

最初はAndroidでSSHFSを使うことを考えたが、満足できる結果にはならなかった。

代えてDLNAを試し、これが容易なことではないということを感じ始めた。

これをなんとか解決すべく、HTTPで配信し、mpvで再生するという方法を試した。 ある程度意図したことは実現できたが、問題も残った上にとにかく使いづらかった。

ここでwebブラウザに備わっているメディア再生機能に頼るという方向に舵を切る。 Lighttpdを使って簡単に実現できたが、これは単独ファイルの再生に限った話になってしまった。

もう悟ったのだ。 「ちゃんと作る以外に道はない」 と。

だから、作った。

今回は、事前パートを除いても3時間ほどかかった。 いつもの「サクッと簡単に作る」と比べるとかなり手間をかけているし、コード的にも「簡単な」コードになっていない。 webアプリケーションらしい、コツコツと同じようなことを積み上げるようなコードになってしまっている。

だからこそやりたくなかったのだ。 汎用性があって完結なソリューションをとりたかった。 しかしこれに関してはもはや「ちゃんと作るのが一番早い」になってしまっていたので、観念した。

事前準備とLighttpd

実は事前準備も2時間くらいかかっている。

Webサーバーとしての機能は非常に簡潔なものを求めており、だからこそ最初はRubyで書く道を模索した。

だが非常にシンプルなことだが、「パスによって複数のルートにマップし、一部はアプリケーションとして機能させる」がうまいこと書ける方法がなかなか見つからない。 Webrickでできることは知っているし、もしWebrickが今も標準ライブラリだったならば私はWebrickで書いただろうが、結局それも外部ライブラリ頼みだ。

また、Rubyのアプリケーションサーバーども(Puma, Thin)は静的ファイル配信があんまりスマートでなく、そもそもwebサーバーとしての機能も微妙である。 今回の場合、メディアファイルを静的に配信するというのは肝になるので、最終的には諦めたのだが、その結論に至るまでの試行錯誤に2時間を費やしたのである。

Rubyで書くことを諦めれば、このような目的にLighttpdが非常に適していることは知っている。 あまり使われていないが、Lighttpdはかなり器用にいろいろできるソフトウェアであり、こういうときは本当に重宝する。

アプリケーションデザイン

webサーバーとしての機能はLighttpdに委ね、Lighttpdに静的ファイル配信とCGI実行をさせる方式。

クライアントアプリケーションとなるSPAもLighttpdで配信する。 SPAはいつもどおりVanillaJSである。

LighttpdのCGIは、規格外の仕様でかなり使いやすくしてくれていたりするのだが、今回はEOFだけを使っている。 ちなみに、RubyのCGIライブラリはEOFしないサーバー(たとえばHiawatha)では機能しない。

アプリケーションの機能の大部分はクライアント側にあり、メディア再生の機能もクライアント持ち。 メディアのロードはLighttpdによってルーティングされる静的ファイル配信によって行われるため、サーバーサイドアプリケーションは関与しない。

CGIスクリプトが行うのは「ファイルリストをJSONで返す」というだけのことである。 ただし、ここでファイルがフォルダであるかメディアファイルであるかの判定をするほか、対象外のファイルを除外するフィルタリングを行っている。

クライアントアプリケーション

Finder並の大変シンプルなファイルブラウザである。

ファイルブラウザ

ここで提供されている機能は

  • フォルダをひらく
  • ひとつ上の階層へ
  • 単一のオーディオ再生
  • 単一のビデオ再生
  • フォルダ内のオーディオ再生
  • フォルダ内のビデオ再生
  • プレイヤービューに切り替え

である。

「フォルダ内再生」があるのが重要。 普段、私はこの機能をNemo Actionsで実現しているが、リモートデバイスで行うためそれ相当の機能を書いた。

基本的に表示しているファイルアイテムとなるdiv要素に、サーバーレスポンスで得られたファイルパスを持たせており、そのファイルパスを使ってメディアファイルを要求したり、アプリケーションに対してリストを要求したりする方式で、DOM Level 2を使ってこのあたりはシンプルに書けている。

プレイヤービュー

プレイヤー機能はvideo要素とaudio要素でブラウザに委ねている。 メディアファイル自体を配信するとFirefox Focusでは動作しないが、ページ内に埋め込めばFirefox Focusでも動作する。

そのために必要なのはファイルがビデオか音声かの判定だが、ここはサーバー側で拡張子に基づいて判定した上で返しているので苦はない。

問題はプレイリストである。 現状、video/audio要素には連続する複数ファイルを再生する機能がないため、プレイリスト機能はアプリケーション側で持たなくてはいけない。

リストへの追加は、フォルダをロードしたときに得られたHTTPレスポンスを外のスコープで保存しておき、ここからビデオだけ、あるいは音声だけをピックアップしてプレイリスト変数にセットすれば実現できる。

現在プレイ中のアイテムの強調表示、および再生終了時に再生するための「次のアイテム」を再生するための位置情報については、同様に外のスコープの変数に現在再生中のアイテムのプレイリスト中のインデックスを保存することで実現した。

正直どれも難しくないが、ある程度の行数を必要とするものなので、コードがごちゃごちゃする。

中途半端問題

今回の目的から言って、あくまでもローカルで動かし性善説に基づく方式を取っている。

だからインターネット経由にするような形では、セキュリティ問題上使えない。

まぁそこはいつも通りの割り切りだとして、実際はこのアプリケーションちょっと気になるなぁという部分もある。

一番はビジュアルデザインだろう。

UIデザインとしては十分に使いやすいのだが、条件によって表示が微妙になることもあるし、なにより見栄えが悪い。 普段ならあまり気にならないところだが、ここまでちゃんとしてるっぽく作ってしまうと気になり始めてしまう。 アプリケーションとしてその境界を越えてしまっているのだ。

別にReact使って練習代わりにやってもよかったのだけれど、そうするとコード規模がだいぶ大きくなってしまうし、シンプルかつnimbleな感じにしたいという私の好みからも大きく外れてしまう。 もっと単純にアイコンを駆使してすっきりさせてもよかったのだが、外部リソースへの依存度を上げたくなかった。 (今回アイコンはFeatherを使っている)

また、プレイリストに対応してないとか、セキュリティ的に甘く作られているとか、「ちゃんと作れよ」と言われそうな要素を持ってしまっている。 これも、本来はローカルで使うためのものだから必要のないところなのだけど、そこそこちゃんと作ってしまったが故に気になるようになってしまう。

ただ実際のところ、私が自分で使うソフトウェアとしては、これで十分なのだ。 まぁ、m3u対応は限定的にすると思うが。

GitHubもGitLabもダメすぎる

GitHubが2FAを必須にした。

それだけなら大したことではないのだけど、GitHubはかなり頻繁に自動的にログアウトする方式なので、割と「使うたびに認証アプリが必要となる」な作りをしている。

そもそもそういうのはユーザーの意思でやるべきことなのに必須にするのもおかしいし、必須にするにしては根本的に使い勝手が悪すぎる。 私はPCも複数使っているからブラウザ拡張で使うのは無理だし、スマートフォンに入れたところで普段スマートフォンを持ち歩いていないから、事実上外出中はGitHubにアクセスできないになってしまった。

しかも、その2FAは複数アプリが使えない方式。 私のように複数のスマートフォンを使い分けている人は、特定端末を常に持ち歩かなければならないという意味で最悪だし、特定端末がダメになったらアクセスできないという意味でも最悪。 バックアップを使ってコードを共有するようなテクニックはあるが、それはGitHubのコードだけに閉じた話ではなくなるし、ものすごく使い勝手が悪い。1

なのでもうGitHubをやめてGitLabにしたいのだが、今のGitLabもだいぶ悪い。

まず言うべきはほぼ強制的にデフォルトブランチ名がmainにされていることだろう。 グループのリポジトリなら変更可能なようだが、ユーザーに帰属するリポジトリではできなくなっているようだ。

また、UIもCI/CDのような「必ずしもリポジトリにおいて使うわけではない」機能の比重が重すぎ、開発していく上で重要な機能にアクセスしづらい。 ピン機能も並べ替えが機能しないなど問題が重い。

GitLabの現状のように、「提示されるすべてを拒否して白紙にすれば自分のしたいことができる」というものでなんとかしようとするのは、経験上幸せになれない。 GitHubのように論理性を失って短絡的な振る舞いをするのも許しがたいが、ソフトウェアを公開することは私の余力で行うボランティアに過ぎないのだから、普段の手間が少ないほうがまだましである。

というわけで、今回もGitHubにしておいた。

最近こういうのばかりでうんざりしている。 GitHubもMicrosoftイズムが鼻につくようになってきた。

アップデート

リリース後毎日アップデートしているので、変更点をここに記載。

0.1.1 (2024-11-03)

  • 自分より下の相対パスのみから構成される.m3uプレイリストをサポート
  • 音声/動画混合の再生をサポート
  • UIにアイコンを追加
  • プレイリストのnext/prevボタンを追加
  • CGIスクリプトの文字エンコーディング関連を修正
  • ファイル名がUTF-8でソートされるように変更

0.1.2 (2024-11-04)

  • フォルダを移動したとき、そのフォルダでのスクロール位置を記憶する機能を追加 (リサイズでリセット)
  • スマートフォン向けにナロービューポートでよりコンパクトなUIを採用
スマートフォンUI

0.1.3 (2024-11-05)

  • フォルダ単位の音楽再生のみ、cover.jpgをサポート
  • リサイズでのリセットが、幅が変更されていない場合は機能しないように変更
  • クエリパラメータで初期フォルダのパスを指定できるように
  • パス表示を全幅に
  • パスに./が含まれないように変更