【Next.js #37】Three.js + MMD 時計アプリに動画壁紙を実装 — mp4をIndexedDBへ保存してループ再生する

はじめに

前回の記事では、Three.js + MMD 時計アプリに対して、設定UIや壁紙UIを含む各種操作パネルを整理し、アプリとして触れる範囲を少しずつ拡張していきました。

  • モデル管理
  • Rig / Debug / Wallpaper / Settings パネル
  • 静止画壁紙の切り替え
  • Tint / Brightness の調整
  • 時計表示とMMDキャラクターの共存

この土台ができたことで、今回はその続きとして、以前実装した Video Texture の知見も踏まえつつ、静止画に加えて mp4 の動画を壁紙として扱えるように拡張しました。

今回やったことは単純です。

  • mp4 をドラッグ&ドロップ
  • IndexedDB に保存
  • Wallpaper UI の下へ動画一覧を表示
  • 選択すると静止画壁紙の代わりに動画をループ再生
  • 音声は使わず、背景演出専用
  • 起動時に前回選んだ動画壁紙を復元

見た目としては小さな追加に見えますが、アプリとしてはかなり大きい一歩です。 静止画壁紙だけだった空間に、動く背景 を持ち込めるようになったことで、時計アプリの空気感が一気に変わりました。

動画(YouTube):

動画(PC):

関連記事

時計アプリ側の前回記事はこちらです。

また、今回の実装判断の背景には、以前まとめた Video Texture 記事の知見もあります。

今回やったこと

今回の追加実装を整理すると、主に次の5点です。

  • mp4 動画ファイルの drag & drop 受付
  • IndexedDB への動画保存
  • Wallpaper UI 下への動画リスト表示
  • 選択時の動画壁紙ループ再生
  • 起動時の壁紙状態復元

さらに、見た目を良くするために

  • 動画のサムネイル生成
  • 動画リストへのサムネ表示

まで入れました。

最初は単に MP4 というラベルだけを並べていましたが、やはり一覧性が弱く、どの動画か分かりにくい。 そこで動画から1フレームを抜き出してサムネ化し、静止画壁紙と同じように視覚的に選べるようにしています。

なぜ動画壁紙を入れたのか

理由はかなり単純です。

すでに時計アプリには

  • Wallpaper パネル
  • 静止画壁紙の管理
  • Tint / Brightness の調整
  • 背面に壁紙を貼る backwall の仕組み

がありました。

つまり、動く壁紙を差し込むための土台がすでに揃っていた ということです。

さらに、今はネット上に動く壁紙用の動画素材も多く、個人でも AI を使って壁紙向けの動画を作りやすくなっています。 加えて、自分自身が過去に Three.js で Video Texture を扱った経験もありました。

この条件が揃っていたので、今日みたいに疲れている日でも、

「これは今の既存資産に差し込める」

と判断できました。

ゼロから巨大機能を作るのではなく、既にある UI / IndexedDB / backwall / Three.js の流れを活かして、一段拡張する。 今回の実装はまさにその形です。

IndexedDB に mp4 を保存する

まず追加したのは、動画壁紙専用の保存ストアです。

既存の DB には、

  • Models
  • Wallpaper
  • MotionAssets

といったストアがありましたが、今回はここに新しく WallpaperVideos を追加しました。

保存する内容はシンプルです。

  • id
  • name
  • type
  • file
  • createdAt

静止画壁紙と同じく、ファイルそのものを IndexedDB に保存しておき、あとから一覧表示・取得・削除ができるようにしています。

これにより、ユーザーは mp4 をドラッグ&ドロップするだけで、その動画を壁紙資産として扱えるようになります。

Drag & Drop で動画を追加

既存の UI ではすでに画像やアセットファイルの drop を受け付けていました。 今回はその global drop 判定へ、mp4 ファイルの分岐を追加しています。

流れはこうです。

  1. ファイルを drop
  2. mp4 かどうか判定
  3. IndexedDB へ保存
  4. Wallpaper Video 一覧を再描画

この段階で、まずは「動画を壁紙アセットとして保持する」という最低限の流れが成立しました。

Wallpaper パネルへ動画一覧を追加

次にやったのは、静止画壁紙一覧の下へ Wallpaper Video セクションを追加することです。

ここでは保存済み動画を一覧化し、各動画ごとに

  • サムネ
  • ファイル名
  • 削除ボタン

を表示しています。

最初はプレースホルダとして MP4 というボタンだけを置いていましたが、やはり壁紙一覧としては弱い。 なので、後半で動画サムネ表示まで追加しました。

この時点で、静止画と動画を同じ Wallpaper パネル内で扱えるようになり、UI の流れがかなり自然になりました。

Three.js 側で動画を壁紙として再生

動画壁紙の表示には、Three.js 側で VideoTexture を使っています。

基本構造は以下です。

  • video 要素を生成
  • muted / loop / playsInline / autoplay
  • THREE.VideoTexture を作成
  • backwall と同じ plane に貼る
  • 静止画壁紙との排他制御を行う

ここで少し詰まったのが、描画順と透明処理 です。

静止画壁紙と同じ位置に動画 plane を重ねると、描画順や深度の競合で見え方が崩れます。 さらに、depthTest: false を安易に使うと、今度は背景ではなく前面オーバーレイっぽく振る舞ってしまい、MMD や時計まで透明人間化するという副作用が出ました。

最終的には、

  • 動画 plane を wall より少しだけ後ろへ置く
  • renderOrder を整理する
  • depthWrite: false にする
  • depthTest: false は使わない

という形で落ち着きました。

この修正で、動画壁紙が背景として正しく存在しつつ、MMD や時計の表示を壊さない状態に持っていけました。

静止画壁紙との排他制御

動画を選んだ時は、静止画壁紙を隠す必要があります。 逆に静止画を選んだ時は、動画再生を止める必要があります。

そのため、backwall 側では

  • setBackWallVideoFromFile()
  • clearBackWallVideo()

を追加し、画像と動画が同時に前面争いをしないようにしました。

このあたりを整理したことで、壁紙選択の挙動がかなり安定しました。

Wallpaper パネルを開くだけでリセットされる問題

途中で少し厄介だったのが、Wallpaper ボタンを押しただけで動画壁紙が消える問題です。

原因は、Wallpaper パネル初期化時に

  • 色スライダー
  • brightness
  • HEX テキスト

を初期化したあと、そのまま applyFinalColor() を呼んでいたことでした。

この処理の中で setBackWallColor() が走り、最終的に clearBackWallVideo() が呼ばれて、動画壁紙が消えていました。

対策としては単純で、初期化時は

  • UI に値を入れるだけ
  • 実際の壁紙反映はしない

ように変更しています。

この修正で、Wallpaper パネルだけリセットされるという謎挙動は止まりました。

起動時の壁紙復元

今回かなり大きいのがここです。

動画壁紙を選んでも、毎回起動時に消えるのでは使いづらい。 なので、

  • wallpaperMode
  • videoWallpaperId

を settings 側へ保存し、起動時にその状態を見て復元するようにしました。

流れとしては、

  1. 壁生成
  2. 静止画壁紙の既存設定を適用
  3. その後、wallpaperMode === "video" なら動画壁紙を復元

です。

ここで、復元処理を UI 初期化側に入れてしまうと、まだ backwall が作られていない段階で動画を復元しようとして失敗したり、後から静止画設定で上書きされる問題が出ました。

最終的には、app の初期化シーケンスの後半で呼ぶようにして解決しています。

これで、前回選んだ動画壁紙がちゃんと起動時に戻ってくるようになりました。

動画サムネ表示

最後に追加したのが、動画サムネ表示です。

これもかなり効果がありました。 単なる MP4 ラベルより、実際の動画の1フレームが見えた方が、Wallpaper UI としての完成度が一気に上がります。

今回は、

  • 動画ファイルから video 要素を生成
  • canvas に描画
  • data URL 化
  • 一覧ボタンへ img として表示

という流れで実装しています。

将来的には、保存時にサムネも一緒に持つ方が効率は良いですが、今の本数なら一覧表示時にその場で生成しても十分実用です。

実装して感じたこと

今回の実装は、見た目の派手さ以上に、既存資産を活かした小さな拡張の強さ を感じる内容でした。

  • Wallpaper UI がすでにある
  • 静止画壁紙の流れがある
  • IndexedDB もある
  • backwall もある
  • Video Texture の過去知見もある

この状態なら、動画壁紙は「新機能」ではあるけれど、完全な新規開発ではありません。 だから、朝からかなり色々やった後でも、まだ手を付けられた。

これは大きいです。

ゼロから全部作るのではなく、 積み上げた構造の上に次の機能を差し込む。 そのやり方が今回もうまくハマりました。

今後の拡張候補

今回の段階で、動画壁紙機能としてはかなり実用になりました。 ただ、次にやるならこのあたりです。

1. 選択中動画の active 表示

今は選んでも見た目上の選択状態が分かりにくいので、選択中のタイルを強調したいです。

2. 保存時サムネ生成

今は一覧表示時にサムネを都度生成していますが、保存時にサムネも一緒に持てばさらに軽くなります。

3. webm 対応

mp4 だけでなく webm にも広げると素材の自由度が上がります。

4. 動画ごとの調整値

Tint / Brightness を動画ごとに分けるか、壁紙全体で共通にするかは今後の整理ポイントです。

まとめ

今回は、Three.js + MMD 時計アプリに対して、静止画壁紙に加えて 動画壁紙機能 を実装しました。

主な内容は以下です。

  • mp4 の drag & drop 保存
  • IndexedDB での動画資産管理
  • Wallpaper Video 一覧表示
  • Three.js VideoTexture による無音ループ再生
  • 静止画壁紙との排他制御
  • 起動時復元
  • 動画サムネ表示

ここまで入ったことで、時計アプリは単なる「時刻表示 + キャラ表示」から、 背景演出まで含めてカスタマイズできる空間アプリ に一段近づきました。