LWMP 1.0.0リリース - localwebmediaplayer
開発::util
序
localwebmediaplayer、通称LWMPはついにv1.0.0となった。
このソフトウェアは7/24から急速に、本当に急速に開発され、main.js
に至っては実に2.5倍にもなった。
もともとは本当に簡素で、最低限のコードで目的を発生するという意識だった。 しかし、欲しい機能を追加している間に「こうしたほうが親切なのでは」と思い始め、徐々に自分以外の環境も考慮するコードが増えていった。
結果的にかなりきれいに整うところまできてしまったし、使い勝手も良い。 きっと需要もかなりあるだろうと思い、正式リリースに踏み切った。 これにより、LWMPはメンテナンスされるプロジェクトになる。
超急激な開発
1.0を出すに至るまでがどれほど急な坂道だったか。
これは、コミットの日付と、main.js
, *.rb
, theme.css
, README
の行数/文字数を見るとわかる。
コミット日 | js | js(c) | rb | rb(c) | css | css(c) | read | me |
---|---|---|---|---|---|---|---|---|
2024-11-02 | 214 | 5880 | 68 | 1488 | 100 | 1476 | 62 | 1821 |
2024-11-03 | 274 | 7545 | 101 | 2558 | 118 | 1693 | 69 | 2016 |
2024-11-04 | 290 | 7957 | 101 | 2558 | 140 | 1993 | 69 | 2016 |
2024-11-05 | 319 | 9134 | 104 | 2768 | 153 | 2151 | 69 | 2016 |
2025-07-08 | 330 | 9391 | 104 | 2768 | 166 | 2516 | 69 | 2016 |
2025-07-24 | 596 | 18148 | 112 | 3042 | 239 | 3590 | 79 | 2356 |
2025-07-25 | 716 | 21011 | 113 | 3069 | 239 | 3590 | 112 | 3609 |
2025-07-26 | 715 | 20961 | 113 | 3069 | 239 | 3590 | 129 | 4045 |
2025-07-29 | 715 | 20961 | 158 | 3943 | 239 | 3590 | 264 | 8713 |
2025-07-30 | 751 | 21921 | 165 | 4243 | 280 | 4287 | 264 | 8711 |
2025-08-01 | 804 | 23491 | 250 | 6444 | 322 | 4884 | 317 | 11008 |
これではピンとこないだろうから、グラフにしたものがこちら。

そして、X軸を日付にするとこうなる

だいぶクレイジーである。
主要な機能で見るとこう:
コミット日 | 主な更新 |
---|---|
2024-11-02 | 最初のリリース |
2024-11-03 | セーフティ機能とプレイリストの追加 |
2024-11-05 | 見た目関連のアップデート、初期パスを与えられるように。スクロール位置を覚えるようになった |
2025-07-08 | コミット数はそれなりにあるが、細かな調整やバグ修正が中心 |
2025-07-24 | イメージビューワーを追加 |
2025-07-24 | ブックリーダーを追加 |
2025-07-25 | mediaSession系の機能とキーボード操作を追加 |
2025-07-29 | 起動方式を変更 |
2025-07-30 | テキストビューワを追加 |
2025-08-01 | 音声/ビデオのメタデータ関連機能を追加 |
改めてLWMPとは
概要
ローカルネットワーク/ローカルサイト向けにPC上にあるファイルをウェブで配信するソフトウェアである。
基本的にLinuxシステム/Lighttpdを想定しており、ミニマル&エッセンシャルな作りになっている。 (WindowsではWSLの利用を想定。)
Vanilla JavaScript SPAを採用しており、ファイルブラウザから快適にメディアを探して再生することができる。
「PC上の音楽やビデオをベッドでスマートフォンで楽しみたい」という気持ちから生まれた。 これは、様々な標準的な方法を試した上で満足できなかったから生まれたのである。
主要な機能
ファイルブラウザ
ファイルブラウザは再生可能なデータのみを表示するため、ファイルマネージャ的な目的ではなく、あくまで再生したいファイルにアクセスするためにある。
マルチメディアプレイヤー
音楽とビデオを再生するプレイヤーである。
再生機能自体はaudio
, video
要素を利用しており、基本的にブラウザ任せ。
プレイリスト機能を持っており、次のトラック/前のトラックという概念がある。
単体再生とディレクトリ再生に対応。相対パスでアクセスできる範囲なら.m3u
プレイリストもサポートされている。
カバーアートやmediaSessionメタデータにも対応している。
画像ビューワ
3ゾーンタップナビゲーションの画像ビューワ。 ディレクトリ内の画像を次々に見ることもできる。
スマートフォン上では電子書籍を読むときもこれでいい。
ブックリーダー
見開き表示、進行方向反転、ページジャンプ、1/2ページめくりに対応した画像ビューワ。 5ゾーンタップナビゲーションになっており、キーボード操作も可能。
キー | 進行 |
---|---|
← |
左へ2ページめくる |
→ |
右へ2ページめくる |
↓ |
2ページ進む |
↑ |
2ページ戻る |
PageDown |
1ページ進む |
PageUp |
1ページ戻る |
テキストビューワ
テキストファイルとして認識できるものをtextarea
で表示するシンプルな機能。
編集はできない。
プログラムのソースコードは対象外だが、.json
や.yaml
なんかは読める。
ソフトウェアとしての特徴
- シンプル&ミニマル
- それでいて欲しい機能は結構ある
- クライアント側環境は自由で、モバイルだけでなくSSHFSの使いにくいWindows環境でも便利
- 非常に軽快
- マルチインスタンスで、複数のディレクトリをそれぞれのポートで配信できる
- ローカル環境向けの割り切り
- DLsite Voice Utilsからの連携もある
- サーバーは基本的にLinux環境向け (Lighttpdへの依存とファイルパスの取り扱いが主な理由)
ちなみに、私がGitHub上でReleasesを切った初めてのソフトウェアでもある。
技術的な特徴
- Vanilla JavaScript SPAとLighttpdありきのCGIというミニマル構成
- 表示/再生機能は基本的にブラウザのネイティブな機能を利用する
- 外部ライブラリ1を必要としない
cgi.rb
を使いつつJSON APIを解釈する割と珍しい構成- メディアファイルへのアクセスはアプリケーションは介入せずHTTPサーバー任せ
- Lighttpdありきで設計されている
NextCloudと連携してインターネットでも使ってみる
LWMPが「ローカル」である理由
LWMPがlocal web media playerであるのは次のような理由だ。
- 認証機能がない
- マルチユーザーでもない。HTTPアクセスすれば誰でも同じコンテンツにアクセスできる
- ファイルパス処理など、悪意あるアクセスに対抗できるほど十分に安全に設計されていない
これらの問題は利用者が自分ひとりで、なおかつアクセスに十分に安全な認証を必要とすれば解決する問題でもある。
簡単な方法としてはBasic認証、もしくはDegest認証が十分に安全だとみなせるのであればLighttpdで実施できるため、意外と難しくない。
私の場合、インターネット経由での配信をしたいケースというのは基本的にスマートフォンからアクセスしたい場合であり、端末も固定なので、今回はmTLSを試してみる。
mTLSする
まずはルート証明書を作る
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 365 -out ca.crt
openssl genrsa -out client.key 2048
中間鍵をつくる
openssl genrsa -out int_ca.key 4096
openssl req -new -key int_ca.key -out int_ca.csr
openssl x509 -req -in int_ca.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out int_ca.crt -days 365 -sha256
クライアント証明書をつくる
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr
openssl x509 -req -in client.csr -CA int_ca.crt -CAkey int_ca.key -CAcreateserial -out client.crt -days 365 -sha256
openssl pkcs12 -export -out client.p12 -inkey client.key -in client.crt -certfile int_ca.crt
CAファイルを配置し、Nginxに設定を入れてリロードしておく
ssl_client_certificate /path/to/cert/ca.crt;
ssl_verify_client on;
あとはモバイル端末にclient.p12
を転送して開くだけ。
なのだが、Find X2 ProだとAESな証明書がインポートできない上に、インポートしたところでmTLSで証明書を送信しなかった。 X6のほうではうまく動作したので目的は達成したと言えなくはないが、PC側でもうまく動作せず、全体的にいまいちだった。
NextCloudとの連携
NextCloudはファイル自体は普通にファイルシステムに配置し、インデックス等はMySQLに持っている。
LWMPはファイルさえ見られればいいため、NextCloudのファイルを共有すれば良い。
場所は//volumes/data/$user/files
である。
NextCloudはボリューム外へのシンボリックリンクでは機能しないが、Lighttpdはルート外へのシンボリックリンクが機能する。
だからNextCloudのボリュームへのシンボリックリンクをmedia
にすれば良いのだが、私のケースではパーミッション的に難しかったのでbind mountで解決した。
普通に/etc/fstab
に書けば良いので、運用が固まっていれば簡単な解決方法。
LWMPの話題
普通にでかいJavaScript
ミニマルだと言っているが、1.0.0時点でJavaScriptは804行23491文字。 ミニマルの定義が行方不明である。
だが、実際見てみるとコード自体はessentialなので、減らそうとすると何かを犠牲にしなくてはいけない。
例えば、テキストビューワを表示するshow_textview()
は
const show_textview = async function(path) {
const box = document.getElementById("TextViewerBox")
const area = document.getElementById("TextViewer")
.style.height = window.innerHeight + "px"
box.style.width = window.innerWidth + "px"
boxconst body = await http.get(("/media/" + path), {disable_parse_json: true})
.value = body
area.style.display = "grid"
box.currentView = "textview"
currentState }
となっている。
ちなみに、例外はキャッチしていない。
http.get()
が例外を発生したとして、伝播する先もないので処理が中断される。
get()
する前に行ったのは非表示の要素のheight/width調整だけなので特に問題はない。
こんな感じで相当essentialに作ってある。
ただ、もともとは微妙なところがあっても使う分には支障なければOKとしていた。 しかし、そのあたりは1.0.0に至るまでに心配りをするように変更されてきており、それによってコード量は5〜10%程度増加している。
例えば、Loading Splashはロード中であることがわかるようにするもので、本質的には必要ない。 しかしあったほうが親切だ。 そして一般的にそう長くはないロード中にウィンドウリサイズする人はまずいないと思うし、したとしたら表示がおかしくなってもなぜおかしくなったのかはわかると思うが、一応resizeイベントに追従してresizeするようにしている。
CGI
CGIというのが非合理的に思える人もいるかもしれないが、実のところCGIであることは結構重要である。
まず、LWMPの構造としては/
と/media/
の2つのルートを持っている。
そして、これらの大部分は静的ファイルの配信を行う。
アプリケーションサーバー(例えばPuma+Rack)に静的ファイル配信をやらせると、効率や機能の面で問題が出やすい。 基本的にこういうのは、それを得意とする汎用のウェブサーバーにやらせるほうが良い。
そして相対的にアプリケーションの役割が軽い。 アプリケーション要素は現状「ディレクトリのアイテムリストの取得」と「メタデータの取得」に限られている。 頻度は高くない上に自分一人しか使わないものなので、トラフィックを憂慮する必要はない。
汎用ウェブサーバーとアプリケーションサーバーの2つにしてしまうと、ユーザーが起動すべきサーバーが2つになる。 ホストレベルでウェブサーバーを立てるような構造にしてしまうと一気に取り回しが悪くなるし、より手軽な方向に倒すならひとつにしたい。
bundlerを使うようになるとハードルも上がるし手間も増える。
CGIにしておけば「起動するサーバーは1つでいい」「ライブラリの導入などの手順が不要」といったメリットがある。
少なくともcgi.rb
が標準ライブラリである間はこの構造のメリットが大きい。
ちなみに、cgi.rb
がgem化されたら、それはもうcgi.rb
の存在価値が失われるときだと思って良いだろう。
LWMPの歩み
- 「PC上のメディアファイルをベッドでスマートフォンで楽しみたい」と考えた
- 最初はAndroidでSSHFSを使う方法を考えたが、うまくいかなかった
- DLNAを試したが、あまりうまく行かなかった
- VLCを試したが、うまく行かなかった
- HTTPで配信し、mpvで再生する方式を試したが、あまりうまく行かなかった
- ウェブサーバー+ディレクトリ表示機能に頼ってみたが、快適ではなかった
- もはや自作するしかない、ということでLWMPを作り始める
- ファイルブラウザ, メディアプレイヤー, プレイリストを持つLWMPが誕生
.m3u
プレイリストのサポートを追加。プレイリストがオーディオ/ビデオ混合を許すようになった- プレイリストのnext/prevが可能に
- スマートフォン向けのUIを強化
- ファイルブラウザはディレクトリごとにスクロール位置を記憶するように
- ディレクトリ単位の再生のみカバーアートをサポート
- クエリパラメータで起動時の初期パスを指定できるようになった
- 「電子書籍も読みたい」と思うようになった。NextCloudだといまいち
- 手始めにイメージビューワを追加
- 大掛かりに手を書けてブックリーダーを追加
- ちゃんとドキュメントを整備し始めた
- ブックリーダーでキーボード操作できないのがストレスなので、MComix風のショートカットを追加
- 併せて、nexttrack/previoustrackにも対応
- ついバックナビゲーションしがちなので、バックナビゲーションが動作するように改良
- ユーザー向けのやさしい機能としてスタートアップスクリプトを追加
- MEGAではコピペ用テキストをモバイル用に共有していたりしたので、テキストビューワを追加
- 1.0.0に向けて整備
正確にはfetchwrapperを使っているが、src以下に普通に置いているし、そもそも私が作ったものだし、文字通り標準の
fetch()
を使いやすくする関数を提供するだけなので、外部ライブラリとはまぁ言わないでいいと思う。metadata.rb
がgdbm
を読むけれども、あれは(Rubyディストリビューションに含まれる)Default Gemsだから外部ライブラリではないと言うのは問題ないだろう。↩︎