MoonTV —— Vibe Coding の試み
約半年前から、LibreTV というプロジェクトに注目していました。これは映像コンテンツを集約するプロジェクトで、さまざまな収集サイトのリソースを統合して検索・再生を可能にしています。当時、彼女向けにドラマ視聴プラットフォームを用意しようと思い、このプロジェクトに注目しました。その後、多くのコードを貢献し、HTML5 プレイヤーの差し替えなども行い、プロジェクトの動作ロジックにも詳しくなりました。しかし使い続けるうちに、このプロジェクトの欠点も明らかになってきました。これは純粋なフロントエンドプロジェクトで、再生履歴などの情報はブラウザの localstorage に保存されるため、ブラウザを変えるとデータが失われます。また、コードはすべて純粋な JavaScript で書かれており、複数の人が手を加えたためコードが混沌としていて、静的解析もないため多くのコードを気軽に変更できず、大幅な改修も難しい状況でした。
数ヶ月後、同僚から Cursor が支付宝決済に対応したと聞き、気持ちが高まりました。以前は Trae を使っていましたが、モデル性能が低く、ピーク時には待ち行列が発生し、さらに有料化され、無料版では数百人の待ち行列が常態化し、ほぼ使い物にならない状態でした。そこで急いで1ヶ月分の Cursor Pro を購入し、体験することにしました。ちょうど LibreTV の書き直しに使おうと思っていたところでした。
フロントエンド経験が全くない私が、Vibe Coding を始めました。ほとんどのコードは Cursor と対話しながら直接完成させました。こうして、MoonTV が誕生しました:
Loading repository data...
わずか1ヶ月で、4.6K の Star と 5.4K の Fork を獲得しました(Star を付けないただ乗りユーザーもいますが)。Star の推移は以下の通りで、国内での違法動画需要の大きさがうかがえます(すべては愛優騰の不親切な対応と高い壁のおかげです)。
プロジェクトは NextJS と React を基盤にし、Vercel、Cloudflare Pages、Docker でのデプロイをサポートしています。localstorage、Redis、Cloudflare D1 の3つのストレージ方式に対応し、Redis と Cloudflare D1 では複数アカウントの情報分離やブラウザ間の情報同期が可能で、管理パネルも使いやすく設計されています。今後は Upstash との連携も予定しています。
これが私にとって初めての Vibe Coding の挑戦であり、初めてのフロントエンド/フルスタック(この言葉は主に node/JS エコシステムで使われるようです)プロジェクトであり、初めての(比較的真面目な)オープンソースプロジェクトの挑戦でもあります。Cursor のサポートにより、MoonTV の開発作業はコードを書くことからコードレビューに変わりました。要望があれば Cursor と話すだけで実装が完了します。ただし、その「話す」には多少のコツも必要です。
今回の Vibe Coding 体験で最初に直面した困難は、体験の初めの段階で起きました。フロントエンドの知識が皆無で、多くの流行技術スタックも「名前は知っているが意味はよくわからない」程度の理解でした。最新技術を追うために、Serverless に優しい NextJS と Tailwind CSS をプロジェクトの主体フレームワークに選びました。フォルダを新規作成し、AI に NextJS と Tailwind の初期化を依頼しました。しかし、半日以上試行錯誤しても、AI が初期化したプロジェクトは正常に動作せず、ほとんどがスタイルが失われてテキストが積み重なっただけのような状態でした。原因がわからず、最終的に諦めて GitHub で既存のスキャフォールドプロジェクトを基盤として使い、ようやく開発が順調に進み始めました。
本格的な開発は比較的順調でした。AI モデルは主に Claude Sonnet 4.0 と GPT O3 の二つを使いました。全体を通して、この二つのモデルには明確な好みがありました。Sonnet 4.0 はプロジェクト全体の大規模な改修に適しており、小さな問題の修正でもプロジェクト全体をリファクタリングしようとすることがあり、新機能の実装に向いています。一方、GPT O3 は小さな修正に適しており、変更範囲を狭く抑え、無秩序に全ファイルを変更しないため、既存機能のバグ修正に適しています。
もちろん、AI だけでは対応できないケースもありました。再生ページの実装時、状態遷移が複雑で多くのデータが依存・関連していました。AI が作成したバージョンでは、データ依存がほぼ網状構造になっており、例えばエピソード切り替えや再生ソース変更の際に、プレイヤーに何度も(五、六回)初期化が伝わり、プレイヤーが点滅する問題がありました。多くのモデルを試しても解決できず、最終的に自分でデータ依存の伝播経路を整理し、重複初期化問題を解決しました。
また、AI が解決できないマイナーな問題もありました。例えば m3u8 の動画は多くのプレイヤーで AirPlay や Chromecast のキャスト機能が有効になりませんが、実は小さなトリックで強制的に有効にできます。この要望を AI に尋ねると、不可能と言われるか、誤った解決策を提示されるか、あるいは「xxx プレイヤーに変えましょう」と提案されることもありました。最終的にネットを隈なく探し、hls.js のある issue[https://github.com/video-dev/hls.js/issues/6482#issuecomment-2582666967]を見つけて AirPlay を有効化できました。現状の AI の能力は一般的な問題の解決に限られ、マイナーすぎる問題や人間でも解決困難な問題は期待しないほうが良いでしょう。
さらに大きな困難は多プラットフォーム・多環境対応にありました。プロジェクト立ち上げ時の計画は Vercel、Cloudflare Pages、Docker でのデプロイ対応でした。NextJS はネイティブに Vercel で動作するため、Vercel では特に問題はありませんでした。Cloudflare Pages 用には next-on-pages 依存を使いコードを変換しているため、互換性の問題が時折発生しました。Docker 対応はさらに複雑で、Docker 環境では API 処理は Node 環境で動作させる必要がありますが、NextJS はミドルウェア処理(認証など)用に Edge 環境も保持しており、この二つの環境はメモリを共有していません。開発時は環境ごとの依存関係や利用可能な API を厳密に区別する必要がありました。加えてクライアント環境も複雑で、特に WebKit エンジンに起因する問題がありました。iOS や iPadOS ではすべてのブラウザが WebKit エンジンを使わざるを得ず、WebKit と Chromium で多くの API や CSS スタイルに大きな差異があるため、特別な対応が必要でした。
MoonTV は LibreTV から派生したため、当初の宣伝は LibreTV の交流グループ内で行われ、専用の議論グループも LibreTV のものをそのまま使用していました。積極的な宣伝は linux.do フォーラムでのみ行い、大きなアップデート時に changelog を投稿する程度でした。しかしプロジェクトの後期には、多くのテック系ブロガーが自発的に MoonTV を宣伝し始めました。X(旧 Twitter)でフォローしているブロガーや、Telegram のテックチャンネル、さらには YouTube、Bilibili、さらには小紅書の多くのアップロード者が MoonTV の構築方法を積極的に教えています。
利用者が増えるにつれて、さまざまな issue が次々と寄せられました。大きく分けて二種類あります。Bug report(バグ報告)が大半で、コードを書く以上バグは避けられませんし、ましてや AI が書いたコードならなおさらです(責任転嫁)。Feature Request(機能要望)はさらに二種類に分かれます。合理的な要望はプロジェクトの発展に寄与し、レイアウトやインタラクション、データ連携の改善が多いです。一方、最も歓迎されないのは商用機能の要望で、こうした人たちはプロジェクトを構築して運営し課金したいだけで、商用機能がなく自分で実装できないため要望を出してきます。こうした人は楽をしたいだけで、無料で使いたいだけなので、要望は少ないほど良いと思っています。ある人物は一気に複数の同様の issue を出し、ban された後も Telegram グループで粘着し、本当に迷惑でした。また、無効なバグ報告もあり、多くは Readme を読まずにデプロイに失敗したり、ネットワーク問題で検索できずに騒ぎ立てるものです。こうした issue は議論グループのメンバーも通りすがりに軽蔑の目を向けて去っていきます。
もちろん、プロジェクトが有名になると、それを取り巻く関連プロジェクトも現れます。MoonTV には OrionTV という Android TV 向けクライアント実装があり、MoonTV をバックエンドとして使い、再生履歴を同期できます。議論グループのメンバーもそれぞれ Android 版や iOS 版を作っていますが、iOS 版は様々な制約によりストアに公開できません。
Loading repository data...
以上です。この文章は本来7月中に書き終える予定で、その時点では Star はまだ 2k ほどでしたが、ずっと遅れてしまいました。やはり文章を書くよりコードを書くほうが楽しいのです。私の性格はそういうもので、何か手元にやるべきことがあると、食事中も睡眠中も四六時中考えてしまいます。夜中の2、3時にバグを思いついて起きて修正することもよくあり、2週間ほど連続で毎日6時間ちょっとしか寝ていません。まさに昼夜を問わず取り組んでいました。