SOUZOHSPECS

一般向けアプリ ・ CASTA

動画配信

会員制の動画配信プラットフォームのデモ

v1.0・作成 2026-08-17

デモを触る ↗MD

本番で使いたい方へデータの保存・セキュリティ・運用ルール・公開は、このデモには入っていません
  1. 要件定義書を保存する

    パソコンに casta-app のようなフォルダを作り、その中に保存します。

    要件定義書.md として保存

  2. ChatGPT で画面イメージを作る デザインにこだわりたい人

    ChatGPT に要件定義書を添付して、下の文を送ります。雰囲気のところは、好きな言葉に書きかえてください。

    ChatGPT に送る
    添付の要件定義書は、これから作るアプリ「動画配信」のものです。
    このアプリのいちばん大事な画面を、デザイン画像にしてください。
    実際のアプリのように、文字やボタンまで入れてください。
    雰囲気:(例:やさしい/高級感/ポップ/シンプル など、好きな言葉で)

    気に入るまで「もっと明るく」「ボタンを大きく」のように直してもらい、できた画像を保存します。ほかの画面も欲しければ、同じように作ってもらえます。

  3. Claude Code に渡して「作って」と頼む

    Claude のデスクトップアプリで「Code」タブを開き、「Select folder」から casta-app を選びます。画面イメージがある人は、入力欄に画像をドラッグして貼りつけてから、下の文を送ります。詳しく

    Claude Code に送る(画像あり)
    @要件定義書.md をもとに、このアプリを作ってください。
    デザインは添付の画像に合わせてください。
    Claude Code に送る(画像なし)
    @要件定義書.md をもとに、このアプリを作ってください。

    作り終わるまで、しばらくかかります。途中で「許可しますか」と聞かれたら、内容を見て許可してください。

  4. 動かして確かめる・直す

    できたら、実際に画面を開いて触ります。直してほしいところは、スクリーンショットを貼りつけて、言葉で伝えれば大丈夫です。

    Claude Code に送る
    アプリを起動して、ブラウザで開けるようにしてください。
    Claude Code に送る
    (画面名)で(操作)をすると(今の動き)になります。
    (こうなってほしい動き)にしてください。
    Claude Code に送る
    このエラーを直してください。
    
    (ここにエラーの文をそのまま貼る)
  5. 途中で止まったら 必要なときだけ

    要件定義書が長いので、一度で作りきれないことがあります。そのときは新しい会話で、こう送ります。

    Claude Code に送る
    @要件定義書.md の続きを作ってください。
    まず、どこまでできているかを確かめてから進めてください。

    それでも進まないときは、30分無料相談でお手伝いします。公式LINEからのご相談もどうぞ。

    1段階ずつ頼みたいとき(全6段階)

    要件定義書には、作る順番が Phase 0〜5 で書かれています。止まりやすいときは、1つずつ頼むと確実です。

    Phase 0基盤と動画素材
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 0 を作ってください。
    Phase 1プレーヤー
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 1 を作ってください。
    Phase 2カタログと会員
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 2 を作ってください。
    Phase 3配信管理
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 3 を作ってください。
    Phase 4視聴分析
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 4 を作ってください。
    Phase 5記事統合と仕上げ
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 5 を作ってください。
  6. 本番で使うなら

    できたアプリはデモです。データの保存・セキュリティ・運用のルール・公開の仕方は入っていないので、そのまま公開すると誰でも中身を見られます。本番で使うのに何が要るかは、このページの下にまとめました。

    自社向けに作り変えるなら、まずこう聞いてみてください。

    Claude Code に送る
    この要件定義書は、記事に埋め込むデモ用に書かれています。
    自分の会社で実際に使えるように変えたいです。外したほうがいいところと、残したほうがいいところを教えてください。

    本番運用について相談する

準備や、うまくいかないときの対処は 作り方ガイド にまとめています。分からないときは 30分無料相談 へどうぞ。

これはデモ用の要件定義書です。本番で使うには、あと4つ必要です

MDでダウンロード

CASTA — 要件定義書 兼 Claude Code 実装指示書

版数 1.0(DBレス構成 / 記事埋め込み型デモ)/ 作成日 2026-08-17 / 発行 ソウゾウ合同会社 用途 SEO記事「【デモで試せる】Claude Codeで動画配信アプリを開発する方法と費用|動画つき資料【2026年】」に組み込む実演デモ


このドキュメントの使い方(Claude Code へ)

これ1枚が、規約・仕様・タスクのすべて。リポジトリ直下に CLAUDE.md として置き、毎回読み込むこと。

  • 実装は「13. 実装タスク」の順に進める。フェーズを飛ばさない。
  • 各タスクの括弧内は要件ID。着手前に該当章を読むこと。
  • 仕様がここに書かれていない場合は、推測で実装せず質問する。
  • スコープ外(4章 Won't have)は、思いついても実装しない。
  • 「デモだから」を理由に品質を落とす判断はしない。

姉妹プロジェクトとの関係 HIREBASE(求人サイト)/ RELATE(顧客管理)に続く3本目。3つ並べて「同じテンプレ」と思われた時点で失敗。 HIREBASE=明朝・余白・静けさ(読ませる)/ RELATE=ゴシック・高密度・等幅数字(操作させる)/ CASTA=ダーク・映像が主役・UIが消える(見せる)。 9章のデザイン要件は意図的に第3の方向に振っている。トークンを流用しないこと。


目次

  1. プロジェクト概要
  2. 記事への組み込み設計
  3. アーキテクチャ方針
  4. スコープ定義
  5. ロールとデモ切替
  6. 画面一覧とユーザーフロー
  7. 機能要件
  8. データ設計
  9. デザイン要件
  10. 技術要件・ディレクトリ構成
  11. 非機能要件
  12. 費用設計
  13. 実装タスク
  14. 受入基準

1. プロジェクト概要

背景と目的

対象記事のキーワード意図は「動画配信アプリを作りたいが、作り方と費用が分からない」。この検索意図に対し、テキストの説明だけでは以下2点が伝わらない。

  1. どこまでのものが作れるのか — プレーヤーの完成度、会員制の作り込み、管理画面の実用性
  2. なぜ費用が読みにくいのか — 動画配信は開発費より運用費(配信コスト)の設計が本質。ここを説明できるかが専門性の証明になる

そこで記事内に、実際に動画が再生できるデモを組み込む。動画配信アプリは「モックでは絶対に伝わらない」領域であり、実際に再生が始まった瞬間に信頼が生まれる。

目的 内容
実装力の証明 HLS配信・画質切替・レジューム再生・字幕・チャプターまで実装した本物のプレーヤーを見せる
ビジネス機能の証明 会員制ペイウォール、プラン管理、視聴分析。「収益化まで作れる」ことを示す
費用説明の裏付け デモ自体の配信コストを実測値として記事に載せる。自分たちが払っている数字で語る
リード獲得 デモ内から記事・問い合わせへの導線を常設し、GA4で計測する

プロダクト定義

項目 内容
プロダクト名 CASTA(キャスタ)※仮称
一言定義 会員制サブスクリプション型の動画配信プラットフォーム
想定業態 オンラインスクール、社内研修、士業・コンサルの会員制コンテンツ、専門メディア
提供形態 レスポンシブWebアプリ。フロントエンド完結(サーバー側の永続化なし)+ 静的動画配信
主戦場 記事からの流入。モバイル比率が高い前提で設計する(SEO記事の読者はスマホが主)
想定利用者 記事の読者。登録なしで即座に再生でき、会員機能もロール切替で体験できる

プロダクトコンセプト

「アプリを見せない。動画を見せる。」

動画配信サービスのUIは、うまくいっているとき存在に気づかれない。CASTA は、画面を暗く落とし、装飾を削り、サムネイルと映像だけに光を渡す。操作は身体が覚えているもの(スペースで再生、矢印でシーク)をそのまま採用する。

デモとしての成功条件

  1. 記事を読んでいる人が、3秒以内に再生を開始できる
  2. プレーヤーが本物である — 画質切替、速度、字幕、チャプター、レジューム。ここが安っぽいと全部が嘘になる
  3. 有料ゲートが体験できる — 無料会員で有料コンテンツを開くと、プレビュー後にペイウォールが出る
  4. 管理側も見られる — 配信管理と視聴分析まで、ロール切替で確認できる
  5. 記事に戻れる/問い合わせできる — デモが行き止まりにならない

2. 記事への組み込み設計

この章が本プロジェクト固有の最重要要件。 単体で作って後から埋め込むのではなく、埋め込み前提で設計する。

配置方針

記事内で2段構えにする。

配置 中身 目的
① 記事内インライン 記事の冒頭〜中盤、<iframe> プレーヤー単体の軽量版(1本の動画 + 主要操作) スクロール中に「動く」ものを見せ、離脱を防ぐ
② 全画面デモ 「デモを開く」カード → 別タブ アプリ全体(カタログ・会員・管理画面) じっくり触ってもらう

アプリ全体を記事内 iframe に入れない。 理由は3つ。

  • 動画を含むアプリ全体は初期ロードが重く、記事本体のCore Web Vitalsを悪化させる
  • iframe 内のスクロールと記事のスクロールが競合し、モバイルで操作不能になる
  • アプリ内で画面遷移すると、読者が記事のどこにいたか分からなくなる

埋め込みモード(?embed=1

同一アプリを、URLパラメータで2つのモードで動作させる。別リポジトリに分けない。

通常モード 埋め込みモード ?embed=1
ヘッダー・サイドバー 表示 非表示
ロール切替バー 表示 非表示
表示内容 アプリ全体 指定された1動画のプレーヤーのみ
動画の自動再生 しない しない(音声ありの自動再生は禁止)
高さ 100vh 親に合わせる(16:9 + 操作バー分)
遷移 通常 アプリ内遷移を禁止。 操作は再生関連のみ。「全画面で開く」ボタンは target="_blank"

<iframe
  src="https://casta.souzoh-demo.com/watch/ep-001?embed=1"
  width="100%" style="aspect-ratio:16/9; border:0"
  loading="lazy"
  allow="fullscreen; picture-in-picture"
  title="CASTA 動画プレーヤーのデモ"></iframe>

記事本体のパフォーマンスを壊さない

ID 要件
EMB-01 記事内 iframe は loading="lazy" とし、ビューポート接近まで読み込まないこと
EMB-02 埋め込みモードの初期ロードは、ポスター画像1枚 + 再生ボタンのみ。動画のマニフェスト取得は再生ボタン押下後に開始すること
EMB-03 埋め込みモードのJS初期バンドルは 80KB以下(gzip後)とすること。プレーヤー本体(hls.js)は再生ボタン押下時に動的import すること
EMB-04 埋め込み iframe が記事の LCP 要素にならないこと。ポスター画像は記事側で preload しない
EMB-05 自動再生は行わない。音声ありの自動再生は明確に禁止

「動画つき資料」との一体化(重要な設計判断)

記事タイトルの「動画つき資料」を、別途用意するのではなくデモの中身そのものにする。

  • デモに投入するシリーズの1本目を「Claude Codeで動画配信アプリを作る」の解説動画にする
  • そのエピソードに、資料PDF(設計図・費用試算シート)を添付ファイルとして紐付ける(FR-208)
  • 読者は「デモを触る」と「資料を見る」を1つの体験として行う

これにより、デモ機能(添付資料ダウンロード)と記事コンテンツ(動画つき資料)が同じ実装で成立する。 二重に作らない。

導線と計測

ID 要件
EMB-10 全画面デモの上部に、元の記事に戻るリンクを常設すること
EMB-11 デモ内の適切な位置(カタログ下部、ペイウォール内、管理画面)に問い合わせ導線を配置すること。再生中のプレーヤー上には出さない
EMB-12 GA4 で以下を計測すること:demo_open(デモを開く)/video_playvideo_progress(25/50/75/100%)/paywall_viewrole_switchform_clickline_click
EMB-13 form_clickline_click は、既存メディアのキーイベント名と揃えること(媒体横断で集計するため)

SEO上の扱い

ID 要件
EMB-20 デモは記事とは別ドメイン/サブドメインで配信し、記事の評価と競合しないよう 固有の title / description を持つこと
EMB-21 デモ本体は index 可とするが、埋め込みモードのURL(?embed=1)は noindex とすること
EMB-22 デモの各ページから記事へのリンクを持ち、記事側にも rel="nofollow" を付けずリンクすること
EMB-23 記事側に VideoObject 構造化データを設置すること(name / description / thumbnailUrl / uploadDate / duration / contentUrl)

3. アーキテクチャ方針

基本方針

バックエンドとデータベースを持たない。ただし動画ファイルだけは実在させる。

一般的な構成 本プロジェクト
PostgreSQL / Supabase シードデータ(TypeScript)+ ブラウザ内ストア
認証・会員管理 デモ用ロール切替(ワンクリック)
決済(Stripe) プラン選択〜完了画面まで再現。課金処理なし
動画アップロード・エンコード UIとエンコード進捗を再現。実ファイルは事前に用意した固定素材
署名付きURL・DRM 実装しない。ペイウォールはUI上のゲートとして再現
動画の配信 これだけは本物。 静的オブジェクトストレージ + CDN から HLS で配信する

動画だけは実在させる理由

動画配信アプリのデモで動画が再生できないのは、求人サイトのデモで求人が0件なのと同じ。再生が始まるかどうかが、このデモの成否そのもの。

ただし「実在させる」のは配信であって、バックエンドではない。HLS は .m3u8 プレイリストと .ts セグメントというただの静的ファイルなので、オブジェクトストレージに置くだけで配信できる。サーバーは不要。

動画素材の方針

ID 要件
VID-01 動画素材はソウゾウが権利を持つものを使う。既存YouTubeチャンネルの動画を再エンコードして流用することを第一候補とする
VID-02 自社素材で埋まらない分は、商用利用可のライセンスが明示された素材のみを使う。取得元とライセンス種別を docs/assets-license.md に記録すること
VID-03 本数は 12〜16本。1本あたり 2〜8分。合計 60分以内に収める
VID-04 各動画に 3段階の画質(1080p / 720p / 360p)を用意し、画質切替を実演できるようにすること
VID-05 少なくとも3本に**日本語字幕(WebVTT)**を付けること。うち1本は英語字幕も付け、字幕切替を実演できるようにすること
VID-06 少なくとも5本に**チャプター(WebVTT chapters)**を設定すること
VID-07 1本目は「Claude Codeで動画配信アプリを作る」の解説動画とし、資料PDFを添付すること(2章参照)

エンコード仕様

入力: 1080p mp4
出力: HLS(fMP4 / CMAF)
  ├ 1080p  H.264 High  3,500kbps  ─┐
  ├  720p  H.264 Main  1,800kbps   ├ master.m3u8 に3レンディション
  └  360p  H.264 Base    600kbps  ─┘
  セグメント長 4秒 / GOP 2秒 / 音声 AAC 128kbps
  サムネイル: 各動画から3枚(ポスター / ホバープレビュー用スプライト / OGP用)

エンコードは ffmpeg のスクリプト(scripts/encode.sh)としてリポジトリに含める。これ自体が記事の「方法」パートの説明材料になる。

配信先

候補 判断
Cloudflare R2 + Cloudflare CDN 採用。 エグレス(転送量)課金がないため、デモが伸びてもコストが跳ねない
Vercel から静的配信 不採用。転送量課金が視聴時間に比例して増える
Cloudflare Stream / Mux 等の専用サービス 不採用(本デモでは)。ただし12章で比較対象として扱い、記事の費用パートで解説する

差し替え可能性の担保

データアクセスは必ず lib/repo/ のリポジトリ層を経由すること。 コンポーネントからストアを直接触らない。

[ コンポーネント ]
        ↓  呼ぶのはこの層だけ
[ lib/repo/*.ts ]   ← 全メソッドを async
        ↓
[ lib/store/*.ts ]  ← Zustand + persist(localStorage)
        ↓
[ lib/seed/*.ts ]   ← 初期データ(動画URLはR2を指す)

リポジトリの各メソッドは、中身が同期処理でも async で定義する。実案件へ転用する際、リポジトリの実装だけをAPI呼び出しに差し替えれば、UI層は変更不要。

永続化の範囲

対象 挙動
シードデータ(シリーズ・エピソード・会員) 初期投入。管理画面から編集可能
視聴進捗・視聴履歴 localStorage に保存。リロードしても「続きから」再生できる
マイリスト、プラン契約状態、通知既読 localStorage に保存
アップロードした動画 ファイル名とメタデータのみ保持。再生は既存素材で代替(FR-403)
リセット ヘッダーの「デモをリセット」で localStorage をクリア

擬似ディレイ は 150〜400ms。ただし再生開始に関わる操作にはディレイを入れない(動画配信で待たされるのは致命的な体験のため)。


4. スコープ定義

視聴者向け機能

ID 機能 概要 優先度
F-V01 動画プレーヤー HLS再生、画質切替、再生速度、音量、全画面、PiP、キーボード操作 Must
F-V02 レジューム再生 前回の停止位置から再開。「最初から見る」も選べる Must
F-V03 字幕 WebVTT。日本語/英語/オフの切替、表示サイズ・背景の調整 Must
F-V04 チャプター シークバー上のチャプター区切り、チャプター一覧からのジャンプ Must
F-V05 シークバープレビュー シークバーのホバー/ドラッグでサムネイルを表示 Should
F-V06 次のエピソード 終了30秒前にカード表示、自動再生(設定でOFF可) Should
F-V07 トップページ ヒーロー、続きから見る、カテゴリ別レーン、ランキング、新着 Must
F-V08 シリーズ詳細 エピソード一覧、各話の視聴進捗、シリーズ全体の進捗率 Must
F-V09 検索・絞り込み フリーワード、カテゴリ、タグ、再生時間、視聴状態(未視聴/視聴中/視聴済) Must
F-V10 マイリスト シリーズ・エピソードの保存 Should
F-V11 視聴履歴 時系列の履歴、進捗率、履歴の削除 Should
F-V12 添付資料 エピソードに紐づくPDF・スライドの閲覧とダウンロード Must
F-V13 ペイウォール 有料コンテンツはプレビュー(既定3分)後にゲート表示 Must
F-V14 プラン・契約 プラン比較、選択、決済フォーム、完了画面(課金処理なし) Must
F-V15 アカウント設定 再生設定(既定画質・速度・自動再生・字幕)、通知設定 Should
F-V16 メモ機能 再生中にタイムスタンプ付きメモを残し、クリックでその位置へジャンプ Could

配信管理(運営)向け機能

ID 機能 概要 優先度
F-A01 管理ダッシュボード 本日の再生数、公開待ち、人気コンテンツ、会員数推移 Must
F-A02 動画アップロード ドロップ、エンコード進捗の再現(待機→変換→完了)、失敗ケースの再現 Must
F-A03 エピソード編集 タイトル、説明、サムネイル選択、公開設定、公開予約、無料/有料区分 Must
F-A04 シリーズ管理 シリーズの作成、エピソードの並び替え、カテゴリ・タグ付与 Must
F-A05 字幕編集 字幕ファイルのアップロード、タイムライン上での文言修正、プレビュー Should
F-A06 チャプター編集 再生位置を見ながらチャプターを追加・編集 Should
F-A07 添付資料管理 エピソードへのファイル紐付け、公開範囲設定 Must
F-A08 会員管理 会員一覧、プラン、視聴状況、停止 Should
F-A09 プラン管理 プランの作成・編集、価格、視聴可能範囲 Should
F-A10 視聴分析 再生数・視聴完了率・離脱ヒートマップ・デバイス別・流入元 Must
F-A11 一括操作 複数エピソードの公開/非公開、カテゴリ付与 Should

共通・基盤

ID 機能 概要 優先度
F-S01 ロール切替 未ログイン/無料会員/有料会員/運営 をワンクリック切替 Must
F-S02 埋め込みモード ?embed=1 でプレーヤー単体表示(2章) Must
F-S03 記事への導線 元記事へのリンク、問い合わせ導線 Must
F-S04 デモリセット localStorage をクリアして初期状態へ Must
F-S05 通知センター 新着エピソード、シリーズ更新 Could
F-S06 ガイドツアー 初回訪問時に「何を試せるか」を3ステップで案内 Should

対象外(Won't have)

項目 理由
データベース・バックエンドAPI 3章の方針に基づく
本物の認証・実際の課金処理 ロール切替と完了画面で代替
DRM・署名付きURL・視聴制限の実効化 デモでは実装しない。 ただし12章で必要性とコストに触れる
ライブ配信・リアルタイム配信 VODに限定。要望が出た場合は別フェーズ
実際のトランスコード処理 進捗UIの再現に留める。素材は事前エンコード
コメント・レビュー・コミュニティ機能 対象外
ダウンロード視聴(オフライン再生) 対象外
多言語UI 日本語のみ(字幕の多言語は対象)
ネイティブアプリ・TVアプリ レスポンシブWebで対応

スコープリスク: 動画配信は「ライブ配信も」「DRMも」と広がりやすい。どちらも単体で数週間〜の領域。要望が出た場合は12章の費用表を使って別見積として提示し、この文書のスコープは変えない。


5. ロールとデモ切替

ロール定義

ロールID 名称 視聴できる範囲 特徴
guest 未ログイン 無料コンテンツのみ。有料はプレビュー3分でゲート 会員登録の導線を体験できる
free 無料会員 無料コンテンツ全編 + マイリスト・視聴履歴 有料コンテンツでペイウォールに当たる
premium 有料会員 全コンテンツ。添付資料もすべて取得可 制限なしの体験
admin 運営 全コンテンツ + 管理画面 配信管理・分析

ロールを跨ぐ体験(必ず動くようにする)

  1. 未ログインで有料エピソードを再生 → 3分でペイウォール → 有料会員に切替 → 同じ位置から続きが再生される
  2. 無料会員で途中まで視聴して離脱 → リロード → 「続きから見る」に表示され、停止位置から再開する
  3. 運営でエピソードを「非公開」に変更 → 視聴者に切替 → カタログから消えている
  4. 運営で新規エピソードを公開予約(1分後) → 待つ → 視聴者側のカタログに出現する
  5. 視聴した動画が、運営の視聴分析にカウントされ、離脱ヒートマップに反映される

デモ切替バー(F-S01)

画面上部に常時表示する固定バー。ただし埋め込みモードでは非表示。

  • 現在のロール表示と4ロールの切替
  • 「デモをリセット」ボタン(確認ダイアログ付き)
  • 元記事へ戻るリンク
  • 「これはデモです」の明示

デザイン上の扱い: アプリ本体がダークテーマなので、切替バーは逆に明るいグレーにして区別する。プロダクトのトークンを使わない。


6. 画面一覧とユーザーフロー

全28画面。画面IDはディレクトリ構成と 1:1 で対応させる。

視聴者画面

画面ID 画面名 パス 主要要素
SC-001 トップ / ヒーロー(自動再生なしの背景動画)、続きから見る、カテゴリ別レーン、ランキング、新着
SC-002 視聴(プレーヤー) /watch/[episodeId] プレーヤー、エピソード情報、チャプター一覧、添付資料、次の話、同シリーズ
SC-003 埋め込みプレーヤー /watch/[episodeId]?embed=1 プレーヤーのみ。全画面デモへのリンク
SC-004 シリーズ詳細 /series/[seriesId] シリーズ概要、エピソード一覧(進捗付き)、全体進捗率、マイリスト追加
SC-005 カタログ /browse カテゴリ絞り込み、並び替え、グリッド表示
SC-006 検索結果 /search フリーワード、絞り込みチップ、シリーズ/エピソードのタブ
SC-007 カテゴリ一覧 /browse/[categorySlug] カテゴリ別のシリーズ一覧
SC-010 マイページ /my 続きから見る、マイリスト、視聴履歴のタブ
SC-011 視聴履歴 /my/history 時系列、進捗率、削除
SC-012 マイリスト /my/list 保存したシリーズ・エピソード
SC-013 メモ一覧 /my/notes タイムスタンプ付きメモ。クリックで該当位置へ
SC-014 再生設定 /my/settings 既定画質・速度・自動再生・字幕表示設定
SC-020 プラン比較 /plans 3プランの比較表、視聴可能範囲、FAQ
SC-021 申込フォーム /plans/[planId]/checkout 会員情報、支払い方法(入力UIのみ)、確認
SC-022 申込完了 /plans/complete 完了、視聴開始への導線
SC-023 契約状況 /my/subscription 現在のプラン、変更、解約(デモ上での状態変更)

管理画面

画面ID 画面名 パス 主要要素
SC-100 管理ダッシュボード /admin 本日の再生数、公開待ち、人気コンテンツ、会員数推移
SC-110 エピソード一覧 /admin/episodes ステータス別タブ、一括操作、絞り込み
SC-111 アップロード /admin/episodes/upload ドロップ、エンコード進捗の再現、失敗ケース
SC-112 エピソード編集 /admin/episodes/[id] 基本情報、サムネイル選択、公開設定、公開予約、無料/有料
SC-113 字幕編集 /admin/episodes/[id]/captions タイムライン、行ごとの文言修正、プレビュー
SC-114 チャプター編集 /admin/episodes/[id]/chapters 再生位置を見ながら追加・編集
SC-115 添付資料管理 /admin/episodes/[id]/files ファイル紐付け、公開範囲
SC-120 シリーズ管理 /admin/series シリーズCRUD、エピソードの並び替え
SC-130 会員管理 /admin/members 一覧、プラン、視聴状況、停止
SC-131 プラン管理 /admin/plans プランCRUD、価格、視聴可能範囲
SC-140 視聴分析 /admin/analytics 再生数推移、視聴完了率、離脱ヒートマップ、デバイス別、流入元

主要フロー

フローA:記事の読者が動画に触れる(最重要)

SEO記事を読んでいる
   ↓
記事内の埋め込みプレーヤー(ポスター画像 + 再生ボタンのみ表示、まだ何も読み込んでいない)
   ↓
再生ボタンを押す ── ここで初めて hls.js とマニフェストを取得
   ↓
再生開始(3秒以内)── 画質切替・速度・字幕を触ってみる
   ↓
「全画面でデモを開く」→ 別タブでアプリ本体へ
   ↓
SC-001 トップ ── ガイドツアーが3ステップで「試せること」を案内
   ↓
SC-004 シリーズ詳細 → SC-002 視聴
   ↓
デモ切替バーで「運営」に切替 → SC-140 視聴分析
   ↓  ★ たった今自分が見た再生が、グラフに反映されている
問い合わせ導線 or 元記事に戻る

フローB:ペイウォールの体験

SC-001 トップ(未ログイン)── 有料バッジ付きのエピソードを選ぶ
   ↓
SC-002 視聴 ── 再生開始。プレーヤー上部に「無料プレビュー 残り2:47」
   ↓
3分経過 ── 再生が停止し、ペイウォールがオーバーレイ表示
   「続きを見るには有料プランへの登録が必要です」+ プラン概要 + CTA
   ↓
SC-020 プラン比較 → SC-021 申込フォーム → SC-022 完了
   ↓  ※ 決済処理は行わない。ロールが premium に変わる
SC-002 に戻る ── ★ 停止した3分00秒の位置から、そのまま続きが再生される

フローC:運営がエピソードを公開する

SC-111 アップロード ── ファイルをドロップ
   ↓
エンコード進捗の再現
   待機中 → 変換中(0〜100%、解像度ごとに段階表示)→ サムネイル生成 → 完了
   ※ 一定確率で「変換失敗」を再現し、リトライ導線を見せる
   ↓
SC-112 エピソード編集
   タイトル・説明・サムネイル選択(3候補から)
   シリーズへの割当、無料/有料の区分
   公開設定:下書き/即時公開/公開予約(日時指定)
   ↓
SC-114 チャプター編集 ── 再生しながらチャプターを打つ
   ↓
SC-115 添付資料 ── PDFを紐付け、公開範囲を「有料会員のみ」に
   ↓
公開
   ↓
【ロール切替:無料会員】
SC-001 トップの「新着」に出現。★ 添付資料はロックされている
   ↓
【ロール切替:有料会員】
★ 添付資料がダウンロード可能になっている

7. 機能要件

7.1 プレーヤー(このデモの心臓部

ID 要件 優先
FR-101 HLS(.m3u8)を再生できること。ネイティブHLS対応環境(Safari)ではネイティブを使い、非対応環境では hls.js を使うこと P1
FR-102 再生/一時停止/シーク/音量/ミュート/全画面/PiP を操作できること P1
FR-103 画質を「自動/1080p/720p/360p」から選択でき、切替時に再生位置と再生状態を維持すること。現在の実効画質を表示すること P1
FR-104 再生速度を 0.75 / 1.0 / 1.25 / 1.5 / 2.0 倍から選択できること P1
FR-105 字幕を「オフ/日本語/英語」から選択できること。表示サイズ・文字色・背景の不透明度を設定できること P1
FR-106 チャプターがある場合、シークバー上に区切りを表示し、現在のチャプター名をバー上部に表示すること P1
FR-107 チャプター一覧からクリックで該当位置へジャンプできること P1
FR-108 キーボード操作Space/K 再生停止、←→ 5秒シーク、J/L 10秒シーク、↑↓ 音量、F 全画面、M ミュート、C 字幕、0-9 位置ジャンプ、? ショートカット一覧 P1
FR-109 視聴位置を5秒ごとに保存し、次回アクセス時にその位置から再開できること。「最初から見る」も選べること P1
FR-110 視聴完了(95%以上)した場合、進捗を「視聴済」とし、次回は最初から再生すること P1
FR-111 シークバーのホバー/ドラッグ中に、その位置のサムネイルプレビューを表示すること P2
FR-112 終了30秒前に「次のエピソード」カードを表示し、カウントダウン後に自動再生すること。自動再生は設定でOFFにでき、カードからキャンセルできること P2
FR-113 通信状況の悪化時にバッファリング表示を出し、自動で下位画質へ切り替わること P1
FR-114 再生に失敗した場合、原因(ネットワーク/非対応形式/読み込み失敗)を区別したメッセージと再試行ボタンを表示すること。無言で止まらないこと P1
FR-115 3秒間操作がない場合、コントロールを自動的に隠すこと。ただし一時停止中は隠さない P1
FR-116 モバイルではダブルタップで前後10秒シークできること。ピンチで全画面に入れること P2
FR-117 再生中にタイムスタンプ付きメモを残せること。メモ一覧からクリックでその位置へジャンプできること P3

7.2 埋め込みモード

ID 要件 優先
FR-151 ?embed=1 でヘッダー・サイドバー・ロール切替バーを非表示にし、プレーヤーのみを表示すること P1
FR-152 埋め込みモードの初期表示はポスター画像と再生ボタンのみとし、hls.js とマニフェストの取得を再生ボタン押下まで遅延させること P1
FR-153 埋め込みモードではアプリ内の画面遷移を発生させないこと。「全画面で開く」は target="_blank" とすること P1
FR-154 埋め込みモードで音声ありの自動再生を行わないこと P1
FR-155 埋め込みモードのプレーヤーは、親要素の幅に追従し、16:9 を維持すること P1

7.3 カタログ・視聴体験

ID 要件 優先
FR-201 トップに「続きから見る」を最上部に配置し、進捗率のバー付きで表示すること。視聴中の作品がない場合はこのレーンを表示しないこと P1
FR-202 カテゴリ別レーンを横スクロールで表示し、キーボードの矢印キーでも移動できること P1
FR-203 サムネイルのホバー(デスクトップのみ)で、数秒のプレビューを再生すること。モバイルでは行わないこと P2
FR-204 シリーズ詳細で、各エピソードの視聴状態(未視聴/視聴中N%/視聴済)とシリーズ全体の進捗率を表示すること P1
FR-205 検索はタイトル・説明・シリーズ名・タグを対象とし、表記ゆれ(ひらがな/カタカナ/半角全角)を吸収すること P1
FR-206 絞り込みとして、カテゴリ、タグ、再生時間(〜5分/5〜15分/15分〜)、視聴状態、無料/有料 を提供すること P1
FR-207 絞り込み条件をURLクエリに反映し、リロード・共有で再現できること P1
FR-208 エピソードに添付資料(PDF等)を紐付け、視聴画面から閲覧・ダウンロードできること。公開範囲(全員/会員/有料会員)に応じてロック表示すること P1
FR-209 シリーズ・エピソードをマイリストに保存でき、マイページから確認できること P2
FR-210 視聴履歴を時系列で表示し、個別削除・全削除ができること P2
FR-211 再生設定(既定画質・既定速度・自動再生・字幕の既定表示)を保存し、次回以降の再生に適用すること P2

7.4 会員・ペイウォール

ID 要件 優先
FR-301 エピソードは「無料/有料」の区分を持ち、カタログ上でバッジ表示すること P1
FR-302 未ログイン・無料会員が有料エピソードを再生した場合、既定3分のプレビュー後に再生を停止し、ペイウォールをオーバーレイ表示すること P1
FR-303 プレビュー中、残り時間をプレーヤー上部に常時表示すること(「無料プレビュー 残り2:47」) P1
FR-304 ペイウォールには、プラン概要・視聴できるようになる内容・CTA を含めること。閉じて他の作品を探す導線も置くこと P1
FR-305 プラン加入後、プレビューが停止した位置から続きを再生できること P1
FR-306 プラン比較画面で3プラン(無料/スタンダード/プレミアム)の視聴可能範囲を表で示すこと P1
FR-307 申込フォームは会員情報と支払い方法の入力UIを持つこと。入力値の検証は実装し、送信時に課金処理は行わないこと。デモである旨を画面上に明示すること P1
FR-308 申込完了でロールが premium に変わり、切替バーの表示も更新されること P1
FR-309 契約状況画面からプラン変更・解約ができ、解約すると視聴範囲が即座に制限されること P2
FR-310 プレビュー時間はプラン設定・エピソード設定で変更でき、管理画面から編集できること P3

7.5 配信管理

ID 要件 優先
FR-401 動画ファイルをドラッグ&ドロップでアップロードできること。対応形式・最大サイズを事前に明示すること P1
FR-402 アップロード後、エンコード進捗を段階表示で再現すること:アップロード中(%)→ 待機中 → 変換中(解像度ごと)→ サムネイル生成 → 完了 P1
FR-403 エンコード完了後の再生は、事前に用意した素材で代替すること。この挙動をユーザーに誤解させないよう、デモである旨を管理画面上に明示すること P1
FR-404 一定確率で「変換失敗」を再現し、原因表示とリトライ導線を見せること P2
FR-405 エピソード編集で、タイトル・説明・シリーズ割当・カテゴリ・タグ・無料/有料区分 を設定できること P1
FR-406 サムネイルを3候補から選択、または任意の画像をアップロードして設定できること P1
FR-407 公開設定として「下書き/即時公開/公開予約(日時指定)/非公開」を選べること P1
FR-408 公開予約した日時が到来すると、視聴者側のカタログに自動的に出現すること(クライアント側のタイマーで判定) P1
FR-409 非公開にしたエピソードが、視聴者側のカタログ・検索結果から即座に消えること P1
FR-410 字幕ファイル(WebVTT)をアップロードでき、タイムライン上で行ごとに文言を修正できること。修正結果をプレーヤーでプレビューできること P2
FR-411 チャプターを、再生位置を確認しながら追加・編集・削除できること。「現在位置で追加」ができること P2
FR-412 エピソードに添付資料を紐付け、公開範囲(全員/会員/有料会員)を設定できること P1
FR-413 シリーズを作成し、エピソードをドラッグで並び替えられること P1
FR-414 複数エピソードを選択して、公開/非公開・カテゴリ付与を一括実行できること P2
FR-415 会員一覧で、プラン・登録日・最終視聴日・視聴本数を確認でき、停止できること P2
FR-416 プランを作成・編集でき、価格と視聴可能範囲を設定できること P2

7.6 視聴分析

ID 要件 優先
FR-501 再生数・総視聴時間・平均視聴完了率を、期間指定(今日/7日/30日/任意)で表示すること P1
FR-502 離脱ヒートマップ:エピソードの再生位置ごとの視聴者残存率を折れ線で表示し、離脱が大きい区間を強調すること P1
FR-503 視聴完了率をエピソード別にランキング表示すること P2
FR-504 デバイス別(PC/スマホ/タブレット)、画質別の再生比率を表示すること P2
FR-505 シリーズごとの継続率(第1話→第2話→…の視聴継続)をファネルで表示すること P2
FR-506 デモ中に視聴した実績が、これらの分析に反映されること(シードの統計に加算される) P1
FR-507 分析データをCSVでエクスポートできること P3

7.7 デモ基盤

ID 要件 優先
FR-601 4ロール(未ログイン/無料会員/有料会員/運営)をワンクリックで切り替えられること P1
FR-602 「デモをリセット」で localStorage をクリアし、確認ダイアログを経て初期状態に戻ること P1
FR-603 視聴進捗・マイリスト・契約状態がリロード後も保持されること P1
FR-604 元記事へ戻るリンクを常設すること P1
FR-605 初回訪問時に3ステップのガイドツアーを表示し、「今後表示しない」を選べること P2
FR-606 ロール権限外の画面(無料会員で /admin)にアクセスした場合、案内画面からロール切替できること。素の404を出さないこと P1
FR-607 権限で不可の操作はボタンを非活性にし、理由をツールチップで示すこと。実行してからエラーを出さないこと P1
FR-608 データスコープは lib/repo/_scope.ts に集約し、全リポジトリメソッドが通すこと P1

8. データ設計

型定義(lib/types/

// ---- コンテンツ ----
type Series = {
  id: string
  title: string
  slug: string
  description: string
  categoryId: string
  tagIds: string[]
  coverUrl: string              // 縦長(カタログ用)
  heroUrl: string               // 横長(トップのヒーロー用)
  episodeIds: string[]          // 並び順を保持
  isPublished: boolean
  publishedAt: string
  createdAt: string
}

type Episode = {
  id: string
  seriesId: string
  number: number                // 第N話
  title: string
  description: string
  durationSec: number
  // --- 配信情報(実在するファイルを指す) ---
  hlsUrl: string                // master.m3u8 のURL
  posterUrl: string
  spriteUrl: string             // シークプレビュー用スプライト
  spriteMeta: { cols: number; rows: number; interval: number; w: number; h: number }
  captions: Caption[]
  chapters: Chapter[]
  // --- 公開・課金 ---
  access: 'free' | 'paid'
  previewSec: number            // 有料時のプレビュー秒数(既定180)
  status: 'draft' | 'scheduled' | 'published' | 'unlisted' | 'private'
  scheduledAt?: string          // 公開予約日時(FR-408)
  publishedAt?: string
  // --- 付随 ---
  attachmentIds: string[]
  tagIds: string[]
  createdAt: string
  updatedAt: string
}

type Caption = { id: string; lang: 'ja' | 'en'; label: string; vttUrl: string; isDefault: boolean }
type Chapter = { id: string; startSec: number; title: string }

type Attachment = {
  id: string
  episodeId: string
  name: string
  fileType: 'pdf' | 'slide' | 'zip' | 'other'
  sizeBytes: number
  url: string
  visibility: 'public' | 'member' | 'premium'   // FR-412
}

type Category = { id: string; name: string; slug: string; sortOrder: number }
type Tag = { id: string; name: string }

// ---- 会員・プラン ----
type Plan = {
  id: string
  name: string                  // 無料 / スタンダード / プレミアム
  priceMonthly: number
  description: string
  features: string[]
  accessScope: 'free_only' | 'all'
  sortOrder: number
}

type Member = {
  id: string
  name: string
  email: string
  avatarUrl: string
  role: 'guest' | 'free' | 'premium' | 'admin'
  planId: string
  subscribedAt?: string
  status: 'active' | 'suspended' | 'cancelled'
  lastWatchedAt?: string
  watchedCount: number
  createdAt: string
}

// ---- 視聴 ----
type WatchProgress = {
  episodeId: string
  memberId: string
  positionSec: number           // 5秒ごとに更新(FR-109)
  durationSec: number
  percent: number               // 派生値だが検索性のため保持
  isCompleted: boolean          // 95%以上(FR-110)
  lastWatchedAt: string
}

type WatchEvent = {              // 分析の元データ
  id: string
  episodeId: string
  memberId: string
  type: 'start' | 'progress' | 'complete' | 'abandon' | 'quality_change' | 'seek'
  positionSec: number
  quality?: '1080p' | '720p' | '360p'
  device: 'desktop' | 'mobile' | 'tablet'
  occurredAt: string
}

type Note = { id: string; episodeId: string; memberId: string; positionSec: number; body: string; createdAt: string }
type ListItem = { id: string; memberId: string; targetType: 'series' | 'episode'; targetId: string; addedAt: string }

// ---- 分析(集計値。ストアには保存せず lib/calc で算出) ----
type RetentionPoint = { positionSec: number; retainedRatio: number }   // 離脱ヒートマップ(FR-502)
type EpisodeStats = {
  episodeId: string
  plays: number
  uniqueViewers: number
  totalWatchSec: number
  avgCompletionRate: number
  retention: RetentionPoint[]
  byDevice: Record<'desktop'|'mobile'|'tablet', number>
  byQuality: Record<'1080p'|'720p'|'360p', number>
}

// ---- アップロード(擬似) ----
type UploadJob = {
  id: string
  fileName: string
  sizeBytes: number
  stage: 'uploading' | 'queued' | 'transcoding' | 'thumbnailing' | 'done' | 'failed'
  progress: number              // 0-100
  transcodeProgress: Record<'1080p'|'720p'|'360p', number>
  errorReason?: string
  linkedEpisodeId?: string      // 完了時に既存素材へ紐付け(FR-403)
  createdAt: string
}

シードデータ(lib/seed/

ファイル 内容 件数
categories.ts tags.ts カテゴリ6、タグ24
series.ts シリーズ 5本
episodes.ts エピソード(実在する動画URLを指す 12〜16本
attachments.ts 添付資料(公開範囲を分散) 10件
plans.ts プラン 3件
members.ts 会員(無料/有料/停止中を分散、過去90日で登録日を散らす) 240名
watchEvents.ts 視聴イベント(過去90日分。離脱ヒートマップが意味を持つ分布で生成) 12,000件
progress.ts 操作用ペルソナの視聴進捗(視聴中2本、視聴済3本)

シード作成のルール(品質を左右する)

  • シリーズ構成は実在しそうな講座にする。 例:「Claude Code入門」「業務システムの作り方」「AI活用の基礎」等。タイトルの使い回し禁止
  • エピソードの説明文を書き分ける。 「第N話の説明です」のようなダミーを残さない
  • 視聴イベントは自然な離脱曲線で生成する。 冒頭30秒で15%落ち、以降なだらかに減衰、終盤で微増(まとめ部分)。均等分布だと分析画面が無意味になる
  • エピソードごとに離脱パターンを変える。 途中に大きな離脱がある回を意図的に作る(分析の価値が伝わる)
  • 日付は現在日時からの相対で生成する。固定日付を埋め込まない
  • 「続きから見る」が最初から表示されるよう、ペルソナに視聴途中のエピソードを持たせる。 トップが空だとデモとして成立しない
  • 公開予約されたエピソードを1本含める(フローCの実演用)

ストアとリポジトリ

lib/
├── types/          # 上記の型定義
├── seed/
├── store/
│   ├── session.ts  # ロール、操作中の会員
│   ├── content.ts  # series / episodes / attachments / categories
│   ├── watch.ts    # progress / events / notes / list
│   └── ui.ts       # 再生設定、ガイドツアー表示済みフラグ
└── repo/           # ★ コンポーネントが触るのはここだけ
    ├── _delay.ts _scope.ts
    ├── episodes.ts series.ts attachments.ts
    ├── watch.ts members.ts plans.ts uploads.ts analytics.ts

リポジトリ層の規約(厳守)

  • 全メソッドを async で定義。中身が同期でも例外なく
  • 戻り値は { ok: true; data: T } | { ok: false; error: string } に統一
  • コンポーネントから Zustand ストアを直接参照しない
  • 視聴可否の判定(canWatch(episode, role))は lib/repo/_scope.ts に集約する。 コンポーネント側で role === 'premium' のような分岐を書かない(FR-608)
  • 派生値(進捗率、離脱率、完了率)はストアに保存せず lib/calc/ で算出する
  • 再生開始に関わるメソッドには擬似ディレイを入れない
// lib/repo/_scope.ts — 実案件では署名付きURL発行・DRM に置き換わる箇所
export function resolveAccess(ep: Episode, role: Role): AccessResult {
  if (ep.access === 'free') return { kind: 'full' }
  if (role === 'premium' || role === 'admin') return { kind: 'full' }
  return { kind: 'preview', limitSec: ep.previewSec }   // guest / free
}

9. デザイン要件

アートディレクション

「画面を暗くして、映像に光を渡す」

動画配信のUIは、うまくいっているとき存在に気づかれない。CASTA は暗い背景に沈み、サムネイルと再生中の映像だけが明るい。装飾・グラデーション・不要な枠線を持たない。

  • ダークテーマを既定とする。 ライトテーマは実装しない(切替を作らない)
  • サムネイルが唯一の色。 UI自体はほぼ無彩色。彩度はコンテンツ側にある
  • アクセントは1色だけ。 再生・進捗・現在地にのみ使う
  • コントロールは消える。 3秒で自動的に隠れ、必要なときだけ現れる

HIREBASE / RELATE との差別化(重要) HIREBASE:明朝・白・広い余白(読ませる) RELATE:ゴシック・白・高密度・等幅数字(操作させる) CASTA:ダーク・映像優先・UIが消える(見せる) 3つ並べたとき、別の会社が作ったように見えることが理想。トークンをコピーしない。

カラートークン

:root {
  /* 背景は3段階の黒で奥行きを作る */
  --bg-base:    #0A0B0D;   /* ページ背景 */
  --bg-raised:  #131519;   /* カード・パネル */
  --bg-overlay: #1C1F25;   /* モーダル・ドロップダウン */

  --fg-high:    #F2F3F5;   /* 見出し・本文 */
  --fg-mid:     #A0A6B0;   /* 補助テキスト */
  --fg-low:     #6B7280;   /* ラベル・非活性 */

  --accent:     #E8503A;   /* 再生・進捗・現在地。★これ以外の色を持ち込まない */
  --accent-dim: rgba(232,80,58,.18);

  --border:     #24272E;   /* 罫線。極めて控えめに */
  --border-hi:  #343943;   /* フォーカス時 */

  /* シグナル(管理画面のみで使用) */
  --ok: #3FA37A; --warn: #C8933A; --danger: #D0544A;
  /* プレーヤー内グラデーション(コントロールの視認性確保) */
  --scrim: linear-gradient(to top, rgba(0,0,0,.85) 0%, rgba(0,0,0,0) 100%);
}

配色ルール

  • アクセント --accent は「再生・進捗・現在地」にのみ使う。 有料バッジ・エラー・装飾に使わない
  • 有料バッジは色ではなく枠線とアイコンで表現する。カタログが赤いバッジだらけになると台無しになる
  • カード同士は影ではなく背景の明度差で分ける(--bg-base の上に --bg-raised
  • プレーヤーのコントロールは、映像の明暗に負けないよう必ず --scrim の上に置く

タイポグラフィ

役割 書体 用途
見出し Noto Sans JP 700 作品タイトル、セクション見出し。字間を詰める(letter-spacing: -.01em
本文 Noto Sans JP 400 説明文、UI
時間表示 Roboto Mono 500(tabular-nums 再生時間、経過時間、チャプター時刻
トークン デスクトップ モバイル 用途
hero 36px / 700 24px ヒーローの作品タイトル
title 20px / 700 18px 作品タイトル
card 14px / 500 13px カード内タイトル(2行クランプ)
body 14px / 400 14px 説明文
meta 12px / 400 12px 再生時間、話数、カテゴリ
time 13px / 500 Mono 12px プレーヤーの時間表示

ダーク背景では文字が太って見える。 ライト背景と同じウェイトを使わず、本文は 400、見出しは 700 で明確に差をつける(500・600 を多用しない)。

レイアウトとスペーシング

項目 定義
スペーシング 4pxベース:4 / 8 / 12 / 16 / 24 / 32 / 48 / 64
ヘッダー 64px。スクロールで背景が透明→不透明に変化(トップのヒーローを活かすため)
コンテンツ幅 最大1440px。レーンは画面端まで伸ばし、見切れを作る(横スクロールできることを示すため)
カード比率 サムネイル 16:9、シリーズカバー 2:3
レーンのカード幅 デスクトップ 280px / タブレット 220px / モバイル 160px
角丸 6px(カード・サムネイル)/4px(ボタン・バッジ)/999px(アバター・チップ)
使わない。 明度差で階層を表現する。例外はモーダルのみ
プレーヤー 16:9 を維持。デスクトップ最大幅1280px、モバイルは画面幅いっぱい

プレーヤーUI仕様(最重要コンポーネント

┌──────────────────────────────────────────────┐
│  [無料プレビュー 残り2:47]        ← 有料時のみ、上部左  │
│                                              │
│                   映像                        │
│                                              │
│  ┌──── scrim グラデーション ────────────────┐ │
│  │  ● チャプター名(現在地)                  │ │
│  │  ━━━━━━━╋━━━━┃━━━━━━━━━━━━━━  ← シークバー│ │
│  │   ▶  ⏭  🔊━━  1:24 / 8:15   ⚙ CC ⛶ ⧉  │ │
│  └────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
要素 仕様
シークバー 高さ通常4px、ホバーで6px。バッファ済みは --fg-low の30%、再生済みは --accent。チャプター区切りは2pxの隙間で表現
シークプレビュー ホバー位置の上にサムネイル(160×90)+ 時刻。チャプター名も併記
再生ボタン 中央の大型ボタンは初回のみ。以降は下部コントロールに集約
設定メニュー(⚙) 画質/速度の2階層。現在値を親メニューに併記する(「画質 自動(720p)」)
字幕ボタン(CC) オン時はアクセント色。長押し/右クリックで字幕設定へ
コントロール自動非表示 3秒。一時停止中・設定メニュー展開中は隠さない(FR-115)
バッファリング 中央に控えめなインジケータ。映像を暗転させない
エラー 中央にメッセージ + 再試行ボタン。原因を区別して表示(FR-114)
モバイル コントロールを大きめに。ダブルタップシークは左右に波紋アニメーション

主要コンポーネント仕様

コンポーネント 仕様
コンテンツカード サムネイル16:9/再生済みは右下にチェック/視聴中は下端に進捗バー(--accent、高さ3px)/有料は左上に枠線バッジ
ホバープレビュー デスクトップのみ。600ms 停留で拡大 + 無音プレビュー再生。モバイルでは実装しない(FR-203)
横スクロールレーン 端で見切れを作る。左右の矢印はホバーで出現。キーボード矢印でも移動
ヒーロー 背景に静止画(動画背景は使わない — 帯域とバッテリーを浪費するため)。タイトル・説明2行・再生ボタン・詳細ボタン
エピソードリスト行 サムネイル/話数・タイトル/再生時間/進捗バー/有料バッジ/説明1行
ペイウォール 映像の上にオーバーレイ。暗転 + ブラー。プラン概要・CTA・閉じる導線を必ず持つ
添付資料カード ファイル名・種別アイコン・サイズ・ダウンロードボタン。ロック時は鍵アイコン + 必要プラン名
アップロード進捗 段階を横並びのステップで表示。現在の段階のみ進捗%を表示
離脱ヒートマップ 横軸=再生位置、縦軸=残存率の折れ線。大きな離脱区間を --danger で塗り、注釈を出す
空状態 全一覧に専用の空状態。ダーク背景に合う線画イラスト
スケルトン カード形状。ダーク背景では明るいシマー(--bg-raised--bg-overlay
デモ切替バー アプリ本体がダークなので、切替バーは明るいグレーにして区別する

モーション

対象 duration 内容
コントロールの表示・非表示 200ms フェード。位置は動かさない
ホバープレビューの拡大 300ms(600ms 停留後) scale 1.0 → 1.12 + z-index 上げ
レーンの横スクロール 400ms ease-out
モーダル・ペイウォール 240ms フェード + ブラー
進捗バーの更新 なし **アニメーションさせない。**実時間を表すため
次のエピソードのカウントダウン 円形プログレスで残り時間を可視化

prefers-reduced-motion: reduce 時は全アニメーションを無効化し、ホバープレビューの自動再生も停止すること。

アクセシビリティ(WCAG 2.1 AA)

動画プレーヤーは a11y の要求が特に高い領域。ここを作り込めることが差別化になる。

ID 要件
A11Y-01 ダーク背景でのコントラスト比は通常4.5:1以上、18px以上は3:1以上。--fg-mid の使用箇所を実測で検証すること
A11Y-02 プレーヤーの全操作をキーボードで完遂できること(FR-108)
A11Y-03 字幕を必ず提供すること。字幕のサイズ・背景不透明度をユーザーが調整できること
A11Y-04 音声のみでは伝わらない情報がある動画に、説明を本文で補うこと
A11Y-05 音声ありの自動再生を行わないこと(FR-154)
A11Y-06 3回/秒を超える点滅を含むコンテンツを使用しないこと
A11Y-07 フォーカスリングは2px・オフセット2pxで常時可視。映像の上でも視認できる色にすること
A11Y-08 シークバーは role="slider" とし、aria-valuenow / aria-valuetext(「1分24秒」)を更新すること
A11Y-09 再生状態の変化を aria-live="polite" で通知すること
A11Y-10 ペイウォール表示時にフォーカスをトラップし、Escで閉じられること
A11Y-11 情報を色のみで伝えない。視聴済み・有料はアイコンとテキストを併記すること

ライティング規約

原則
再生状態は簡潔に ○「無料プレビュー 残り2:47」/×「あなたは現在無料プレビューを視聴しています」
ペイウォールは責めない ○「続きは有料プランでご覧いただけます」/×「視聴権限がありません」
エラーは原因と対処 ○「通信が不安定なため再生できません。再試行するか、画質を下げてお試しください」/×「エラーが発生しました」
ロックは必要条件を書く ○「この資料はプレミアムプランでダウンロードできます」/×「ダウンロードできません」
空状態は誘導する ○「まだ視聴履歴がありません。おすすめから探してみましょう」+ ボタン
システム語を使わない ○「公開を予約する」/×「statusをscheduledに設定」

10. 技術要件・ディレクトリ構成

技術スタック(固定・勝手に変更しない)

レイヤ 技術 備考
フレームワーク Next.js 15(App Router)/ TypeScript strict
スタイリング Tailwind CSS + CSS Variables トークンは CSS 変数で定義
UIコンポーネント shadcn/ui(Radix UI基盤) ダークテーマに合わせて上書き
状態管理 Zustand + persist localStorage に永続化
HLS再生 hls.js(動的import) Safari はネイティブHLSを使う(FR-101)
プレーヤーUI 自前実装 video.js / Plyr を使わない。既製プレーヤーを貼るとデモの意味がなくなる
字幕 ネイティブ <track> + WebVTT
D&D dnd-kit エピソード並び替え。キーボード対応必須
グラフ Recharts 分析画面(動的import)
フォーム React Hook Form + Zod
日付 date-fns(ja locale)
アイコン lucide-react
ホスティング Vercel アプリ本体
動画配信 Cloudflare R2 + CDN 3章参照。エグレス無料
エンコード ffmpeg(scripts/encode.sh ローカル実行。CIには含めない
テスト Vitest / Playwright / axe-core

上記以外のライブラリを入れる前に必ず提案し、承認を得ること。特にプレーヤーライブラリの導入は禁止。

ディレクトリ構成

/
├── CLAUDE.md
├── app/
│   ├── layout.tsx
│   ├── page.tsx                    # SC-001 トップ
│   ├── watch/[episodeId]/page.tsx  # SC-002 / SC-003(?embed=1)
│   ├── series/[seriesId]/page.tsx  # SC-004
│   ├── browse/                     # SC-005, 007
│   ├── search/                     # SC-006
│   ├── my/                         # SC-010〜014
│   ├── plans/                      # SC-020〜022
│   ├── admin/                      # SC-100〜140
│   └── dev/components/             # コンポーネントカタログ
├── components/
│   ├── player/                     # ★ 最重要
│   │   ├── VideoPlayer.tsx         # ルート。hls.js の生存管理
│   │   ├── ControlBar.tsx
│   │   ├── SeekBar.tsx             # チャプター区切り・プレビュー
│   │   ├── SettingsMenu.tsx        # 画質・速度
│   │   ├── CaptionMenu.tsx
│   │   ├── ChapterList.tsx
│   │   ├── NextEpisodeCard.tsx
│   │   ├── Paywall.tsx
│   │   └── useHls.ts / useKeyboardShortcuts.ts / usePlaybackProgress.ts
│   ├── catalog/                    # ContentCard, Lane, Hero, HoverPreview
│   ├── domain/                     # EpisodeRow, AttachmentCard, UploadStepper, RetentionChart
│   ├── demo/                       # RoleSwitcher, ResetButton, GuideTour, BackToArticle
│   └── layout/
├── lib/
│   ├── types/ seed/ store/ repo/
│   ├── calc/                       # 進捗率、離脱率、完了率、継続率
│   ├── query/                      # 検索・絞り込み
│   ├── validation/
│   └── utils/
├── scripts/
│   └── encode.sh                   # ffmpeg エンコードスクリプト
├── docs/
│   └── assets-license.md           # ★ 動画・画像素材の取得元とライセンス(VID-02)
├── e2e/
└── public/

コーディング規約

  • TypeScript strict。any 禁止
  • ファイル名は kebab-case、コンポーネントは PascalCase
  • コンポーネントから Zustand ストアを直接参照しない。必ず lib/repo/ 経由
  • 視聴可否の判定を role === 'premium' のようにコンポーネントに書かない。 resolveAccess() を使う
  • 計算ロジックは lib/calc/ に置き、コンポーネントに埋め込まない
  • hls.js のインスタンスは必ず破棄する。 エピソード遷移・アンマウント時に hls.destroy() を呼ぶ。メモリリークはこのアプリで最も起きやすい不具合
  • <video> のイベントリスナーも同様に確実に解除する
  • コミットは1タスクごと。メッセージに要件IDを含める 例:feat(player): 画質切替時の再生位置維持を実装 (FR-103)

11. 非機能要件

ID 要件 目標値
NFR-01 再生開始までの時間(再生ボタン押下→最初のフレーム表示) 1.5秒以下
NFR-02 LCP(トップ・モバイル4G相当) 2.5秒以下
NFR-03 INP 200ms以下
NFR-04 CLS 0.1以下。プレーヤー領域は再生前からアスペクト比を確保すること
NFR-05 埋め込みモードの初期JSバンドル(gzip後) 80KB以下(EMB-03)
NFR-06 アプリ本体の初期JSバンドル(gzip後) 200KB以下(hls.js・Recharts は動的import)
NFR-07 シークバー操作中のフレームレート 60fps維持
NFR-08 5分間の連続再生でメモリ使用量が単調増加しないこと
NFR-09 Lighthouse Performance 90 / Accessibility 95 以上
NFR-10 対応ブラウザ Chrome / Edge / Firefox 最新2バージョン、Safari 16以降、iOS Safari 16以降、Android Chrome
NFR-11 動画配信のオリジンに適切なCORS・Cache-Controlを設定すること(セグメントは長期キャッシュ、マニフェストは短期)
NFR-12 localStorage の容量上限に達した場合、古い視聴イベントから削除し、データを破損させないこと

セキュリティ・権利(実案件への注記を含む)

ID 要件
SEC-01 動画・画像・字幕素材の取得元とライセンスを docs/assets-license.md に記録すること(VID-02)
SEC-02 第三者の著作物(音楽・映像・キャラクター)を含む素材を使用しないこと
SEC-03 ユーザー入力(メモ・管理画面の説明文)をサニタイズすること
SEC-04 申込フォームで入力された情報をどこにも送信しない。 ブラウザ内で完結し、リセットで消えることを画面上に明示すること

実案件への転用時の注意(デモ本体の要件ではない。商談で聞かれた際に説明できるよう記録)

  • 本デモの動画URLは公開されており、URLを知る第三者が直接再生できる。実サービスでは署名付きURL(有効期限付き)またはDRMが必要
  • 有料コンテンツを扱う場合、特定商取引法に基づく表記、解約条件の明示、資金決済法の観点(前払式支払手段に該当するか)の確認が必要
  • 会員情報は個人情報保護法の対象。利用目的の明示と安全管理措置が必要
  • 本デモは会員情報をサーバーに送信しないため、これらの義務は発生しない。「デモである」旨は画面上に常時明示する

12. 費用設計

記事の主題の半分は「費用」。この章は実装要件であると同時に、記事本文の原稿素材でもある。

費用は3層に分けて説明する

内容 特徴
① 初期開発費 設計・実装・テスト 一度きり。機能スコープで決まる
② 固定運用費 ホスティング、ドメイン、監視 視聴量に関係なくかかる。小さい
変動運用費(配信コスト) 転送量・ストレージ・エンコード 視聴時間に比例して増える。ここが読めないと事業計画が壊れる

動画配信アプリの費用相談で最も多い失敗は、③を計算せずに始めること。 記事ではここを主軸に据える。

変動運用費の計算式(記事に載せる中核)

月間転送量(GB) = 平均ビットレート(Mbps) ÷ 8 × 平均視聴秒数 × 月間再生数 ÷ 1024

例:720p(1.8Mbps)/ 平均視聴 6分(360秒)/ 月間 10,000再生
  = 1.8 ÷ 8 × 360 × 10,000 ÷ 1024
  ≈ 791 GB/月

この 791GB に、各サービスの単価を掛けたものが月額の配信コストになる。

規模 月間再生数 概算転送量 備考
小(社内研修・小規模スクール) 1,000 約 79 GB ほぼ無視できる規模
中(会員制メディア) 10,000 約 791 GB ここから配信先の選択が効いてくる
大(成長したサブスク) 100,000 約 7.7 TB 転送量課金のサービスだと月額が跳ねる

配信先の選択が費用構造を決める

方式 課金の考え方 向くケース 注意点
オブジェクトストレージ + エグレス無料CDN(Cloudflare R2 等) ストレージ容量 + 操作回数。転送量に課金されない 視聴時間が読めない/伸びる可能性がある エンコードは自前。DRM・視聴制限は別途実装
汎用クラウドストレージ + CDN(S3+CloudFront 等) ストレージ + 転送量 視聴量が小さく安定している 伸びるとコストが線形に増える
動画専用サービス(Cloudflare Stream / Mux 等) 配信分数または保存分数 + 配信分数 エンコード・DRM・分析まで任せたい 単価は高いが、実装工数が大幅に減る
汎用プラットフォームからの静的配信(Vercel 等) 転送量 検証・小規模のみ 本番の動画配信には向かない

判断基準はシンプル: 実装工数を払って変動費を抑えるか、変動費を払って実装工数を抑えるか。月間視聴時間が読めない初期は前者、事業として伸びてDRMや詳細分析が必要になったら後者、という移行が現実的。

記事に載せる際の注意: 各サービスの単価は変動するため、この文書に金額を書き込まない。 記事執筆時点で公式の料金ページを確認し、確認日を明記して掲載すること。古い単価を載せた記事は、それだけで信頼を失う。

開発費の目安(機能スコープ別)

スコープ 含まれるもの 目安
最小構成 動画一覧・再生(既製プレーヤー)・会員なし
標準構成(本デモ相当) 自前プレーヤー(画質切替・字幕・チャプター・レジューム)/会員制ペイウォール/配信管理CMS/視聴分析
拡張構成 上記 + DRM・署名付きURL・ライブ配信・アプリ配信

「動画配信アプリ」という言葉の幅が非常に広いことが、費用が伝わらない最大の原因。 記事ではこの3段階を明示し、デモは「標準構成」の実物であることを示す。具体的な金額はLPの価格体系に接続する。

デモ自体の運用コストを実測して記事に載せる

ID 要件
COST-01 デモ公開後、月次で「再生数・総視聴時間・転送量・実際にかかった費用」を記録すること
COST-02 記録した実測値を記事に掲載し、確認日を明記すること
COST-03 転送量が想定を大きく超えた場合に気づけるよう、上限アラートを設定すること

**「私たちのデモは、月にこれだけ再生されて、これだけかかっています」**という一文が、どんな料金表よりも説得力を持つ。


13. 実装タスク

この順に進める。フェーズを飛ばさない。 完了時は [x] に更新する。

Phase 0 — 基盤と動画素材(3日)

0-1. 初期化

  • Next.js 15 / TypeScript strict / App Router / Tailwind で初期化
  • ESLint / Prettier / husky(pre-commit で lint + typecheck)
  • 10章のディレクトリ構成を作成

0-2. 動画素材の準備(先にこれを終わらせる。素材がないとプレーヤーが作れない

  • 使用する動画を確定する(自社YouTube素材を第一候補)(VID-01)
  • scripts/encode.sh を作成(HLS 3レンディション、fMP4、4秒セグメント)
  • 12〜16本をエンコードし、サムネイル(ポスター/スプライト/OGP)を生成
  • 字幕(WebVTT)を3本以上、うち1本は日英2言語 (VID-05)
  • チャプター(WebVTT chapters)を5本以上 (VID-06)
  • Cloudflare R2 にアップロードし、CDN経由で配信できることを確認
  • CORS と Cache-Control を設定(セグメントは長期、マニフェストは短期)(NFR-11)
  • docs/assets-license.md に取得元とライセンスを記録 (VID-02, SEC-01)
  • 1本目に解説動画と資料PDFを配置(2章の「動画つき資料」との一体化)

0-3. 型とシード

  • lib/types/ に8章の型定義をすべて実装
  • lib/seed/ の全ファイル。エピソードの説明文を書き分ける。ダミー文言を残さない
  • watchEvents.ts自然な離脱曲線で生成(冒頭落ち→なだらか減衰→終盤微増)。エピソードごとにパターンを変える
  • ペルソナに視聴途中のエピソードを持たせる(「続きから見る」が最初から表示されるように)
  • 公開予約されたエピソードを1本含める

0-4. ストアとリポジトリ

  • lib/store/{session,content,watch,ui}.ts を Zustand + persist で実装
  • lib/repo/_scope.tsresolveAccess() を実装(FR-608)
  • lib/repo/ の全モジュール。全メソッド async、Result型、再生系にはディレイを入れない
  • lib/calc/ に 進捗率・離脱率・完了率・継続率 を実装
  • resolveAccess()calc の単体テストをこの時点で書く

0-5. デザインシステム

  • app/globals.css にカラートークンを CSS 変数で定義(ダークテーマのみ
  • tailwind.config.ts から CSS 変数を参照するよう theme を拡張
  • フォント(Noto Sans JP / Roboto Mono)。時間表示に tabular-nums
  • shadcn/ui を導入し、ダークトークンに合わせて上書き
  • 共通コンポーネント:ContentCard / Lane / Hero / EmptyState / Skeleton(ダーク用シマー)/ Badge
  • prefers-reduced-motion の全体対応
  • /dev/components にコンポーネントカタログを作成

0-6. デモ基盤

  • RoleSwitcher(4ロール)(FR-601)
  • デモ切替バー。アプリがダークなので切替バーは明るいグレー
  • 「デモをリセット」(FR-602)
  • 元記事へ戻るリンク (FR-604)
  • ロール権限外アクセス時の案内画面 (FR-606)

Phase 0 完了チェック

  • R2 の動画URLをブラウザで直接開いて再生できる
  • /dev/components で全コンポーネントがトークン通りに表示される
  • resolveAccess() のテストが全パターン通る

Phase 1 — プレーヤー(5日・最重要フェーズ

1-1. 再生基盤

  • useHls.ts:hls.js の初期化・破棄。Safari はネイティブHLS (FR-101)
  • hls.destroy() とイベントリスナー解除を確実に行う(メモリリーク対策)
  • VideoPlayer.tsx:再生/一時停止/シーク/音量/ミュート/全画面/PiP (FR-102)
  • ControlBar.tsx:scrim グラデーション上に配置
  • 自動非表示(3秒。一時停止中・メニュー展開中は隠さない)(FR-115)
  • バッファリング表示(映像を暗転させない)(FR-113)
  • エラー表示(原因を区別 + 再試行)(FR-114)

1-2. シークと進捗

  • SeekBar.tsx:再生済み/バッファ済み/チャプター区切り
  • role="slider"aria-valuetext(「1分24秒」)(A11Y-08)
  • シークプレビュー(スプライトからサムネイル抽出 + 時刻 + チャプター名)(FR-111)
  • usePlaybackProgress.ts:5秒ごとに進捗保存 (FR-109)
  • レジューム再生と「最初から見る」(FR-109)
  • 95%以上で「視聴済」判定 (FR-110)

1-3. 設定・字幕・チャプター

  • SettingsMenu.tsx:画質(自動/1080/720/360)。切替時に再生位置と再生状態を維持 (FR-103)
  • 現在の実効画質の表示(「画質 自動(720p)」)
  • 再生速度 5段階 (FR-104)
  • CaptionMenu.tsx:オフ/日本語/英語、サイズ・背景の調整 (FR-105, A11Y-03)
  • チャプター区切りの表示と現在チャプター名 (FR-106)
  • ChapterList.tsx:クリックでジャンプ (FR-107)

1-4. キーボードとモバイル

  • useKeyboardShortcuts.tsSpace/K/←→/J/L/↑↓/F/M/C/0-9/? (FR-108)
  • ショートカット一覧のオーバーレイ
  • モバイル:ダブルタップシーク(波紋アニメーション)、大きめコントロール (FR-116)
  • 映像の上でも視認できるフォーカスリング (A11Y-07)

1-5. 視聴画面と埋め込み

  • SC-002 視聴画面(エピソード情報、チャプター、添付資料、次の話、同シリーズ)
  • NextEpisodeCard.tsx:終了30秒前に表示、カウントダウン、キャンセル可 (FR-112)
  • SC-003 埋め込みモード (FR-151〜155)
  • ポスター + 再生ボタンのみの初期表示、hls.js の動的import (FR-152, EMB-02, EMB-03)
  • 埋め込みモードのバンドルサイズを実測し 80KB以下を確認

Phase 1 完了チェック

  • 再生開始まで1.5秒以下(NFR-01)
  • 画質を切り替えても再生位置と再生状態が維持される
  • キーボードのみでプレーヤーの全操作ができる
  • エピソードを10回続けて切り替えてもメモリが単調増加しない(NFR-08)
  • 埋め込みモードの初期バンドルが80KB以下

Phase 2 — カタログと会員(4日)

2-1. カタログ

  • SC-001 トップ(ヒーロー、続きから見る、カテゴリ別レーン、ランキング、新着)(FR-201)
  • Lane.tsx:横スクロール、端で見切れ、矢印、キーボード移動 (FR-202)
  • HoverPreview:デスクトップのみ、600ms停留で拡大 + 無音プレビュー (FR-203)
  • SC-004 シリーズ詳細(エピソード一覧、各話の進捗、全体進捗率)(FR-204)
  • SC-005 カタログ / SC-007 カテゴリ一覧
  • SC-006 検索(表記ゆれ吸収)(FR-205)
  • 絞り込み(カテゴリ/タグ/再生時間/視聴状態/無料有料)+ URL同期 (FR-206, FR-207)

2-2. マイページ

  • SC-010 マイページ / SC-011 視聴履歴 (FR-210) / SC-012 マイリスト (FR-209)
  • SC-014 再生設定(既定画質・速度・自動再生・字幕)と再生への適用 (FR-211)

2-3. ペイウォール(デモの見せ場

  • 有料バッジ表示(色ではなく枠線とアイコン)(FR-301)
  • プレビュー制限:既定3分で停止 (FR-302)
  • 残り時間の常時表示 (FR-303)
  • Paywall.tsx:オーバーレイ、プラン概要、CTA、閉じる導線、フォーカストラップ (FR-304, A11Y-10)
  • SC-020 プラン比較 (FR-306) / SC-021 申込フォーム (FR-307) / SC-022 完了 (FR-308)
  • 加入後、停止位置から続きを再生 (FR-305)
  • SC-023 契約状況、変更・解約 (FR-309)

2-4. 添付資料

  • AttachmentCard:公開範囲に応じたロック表示 (FR-208, FR-412)
  • ロック時は必要プラン名を明示

Phase 2 完了チェック

  • フローBが完走する(プレビュー → ペイウォール → 加入 → 続きから再生)
  • 5章「ロールを跨ぐ体験」の1が動作する
  • 有料バッジがカタログを赤くしていない(9章の配色ルールを満たす)

Phase 3 — 配信管理(4日)

  • SC-100 管理ダッシュボード
  • SC-111 アップロード:ドロップ、エンコード進捗の段階再現 (FR-401, FR-402)
  • 完了後は既存素材に紐付け。デモである旨を明示 (FR-403)
  • 変換失敗の再現とリトライ導線 (FR-404)
  • SC-112 エピソード編集(基本情報、サムネイル3候補、無料/有料)(FR-405, FR-406)
  • 公開設定(下書き/即時/予約/非公開)(FR-407)
  • 公開予約日時の到来でカタログに出現(クライアントタイマー)(FR-408)
  • 非公開で視聴者側から即座に消える (FR-409)
  • SC-113 字幕編集(アップロード、行ごと修正、プレビュー)(FR-410)
  • SC-114 チャプター編集(再生位置を見ながら、「現在位置で追加」)(FR-411)
  • SC-115 添付資料管理(紐付け、公開範囲)(FR-412)
  • SC-120 シリーズ管理(dnd-kit で並び替え、キーボード対応)(FR-413)
  • 一括操作(公開/非公開、カテゴリ付与)(FR-414)
  • SC-130 会員管理 (FR-415) / SC-131 プラン管理 (FR-416)

Phase 3 完了チェック

  • フローCが完走する(アップロード → 編集 → チャプター → 資料 → 公開 → 視聴者側に出現)
  • 5章「ロールを跨ぐ体験」の3と4が動作する

Phase 4 — 視聴分析(2日)

  • SC-140 視聴分析:再生数・総視聴時間・平均完了率、期間指定 (FR-501)
  • RetentionChart:離脱ヒートマップ。大きな離脱区間を強調 + 注釈 (FR-502)
  • 完了率のエピソード別ランキング (FR-503)
  • デバイス別・画質別の比率 (FR-504)
  • シリーズ継続率ファネル (FR-505)
  • デモ中の視聴が分析に反映されること (FR-506)
  • CSVエクスポート (FR-507)

Phase 4 完了チェック

  • 自分が今見た動画の視聴が、離脱ヒートマップに反映される(5章の体験5)
  • シードの離脱曲線が自然で、分析画面が意味を持って見える

Phase 5 — 記事統合と仕上げ(3日)

5-1. 記事統合

  • 記事側の埋め込みコードを作成し、実際の記事ページで動作確認 (EMB-01〜05)
  • 埋め込みが記事の LCP 要素になっていないことを確認 (EMB-04)
  • 記事の Core Web Vitals を埋め込み前後で比較し、悪化していないことを確認
  • 「デモを開く」カードの設置
  • 元記事へ戻るリンク、問い合わせ導線 (EMB-10, EMB-11)
  • GA4 イベント設定(demo_open / video_play / video_progress / paywall_view / role_switch / form_click / line_click)(EMB-12, EMB-13)
  • ?embed=1noindex に (EMB-21)
  • デモ本体の title / description / OGP (EMB-20)
  • 記事側に VideoObject 構造化データ (EMB-23)

5-2. 体験の総点検

  • 5章「ロールを跨ぐ体験」の5パターンをすべて手動で確認
  • 全28画面を開き、空の画面が1つもないことを確認
  • 空状態・ローディング・エラー状態を全画面で確認
  • ガイドツアーを実装 (FR-605)
  • localStorage 上限時の処理 (NFR-12)

5-3. アクセシビリティ監査

  • axe-core を全画面に実行し、Critical / Serious を0件に
  • ダーク背景でのコントラスト比を実測(特に --fg-mid)(A11Y-01)
  • キーボードのみで全画面・全プレーヤー操作を完遂
  • スクリーンリーダーでプレーヤーの状態変化が読み上げられることを確認 (A11Y-09)

5-4. パフォーマンス

  • hls.js / Recharts を動的import に切り出し、初期バンドルを200KB以下に (NFR-06)
  • 再生開始1.5秒以下を実測 (NFR-01)
  • シークバー操作の60fpsを実測 (NFR-07)
  • 5分連続再生でのメモリ推移を計測 (NFR-08)
  • Lighthouse で Performance 90 / Accessibility 95 (NFR-09)

5-5. 公開と記録

  • Playwright で フローA・B・C の E2E
  • Vitest:_scope calc query のカバレッジ80%以上
  • /dev/components を本番で非公開に
  • Vercel へデプロイ、R2 の転送量アラートを設定 (COST-03)
  • 転送量・費用の記録を開始 (COST-01)
  • 各フェーズのキャプチャを整理し、記事の「方法」パートの素材にまとめる

合計 21営業日(約4週間)/1名専任 + レビュー体制。 Phase 1(プレーヤー)に5日を割いているのは、ここの完成度がデモの成否そのものだから。既製プレーヤーを貼れば1日で済むが、それでは「Claude Codeで動画配信アプリが作れる」の証明にならない。


14. 受入基準

  1. 7章の優先度 P1 の要件がすべて実装されていること
  2. 6章の主要フローA・B・Cが、エンドツーエンドで完走すること
  3. 5章「ロールを跨ぐ体験」の5パターンがすべて動作すること
  4. 再生ボタン押下から1.5秒以内に最初のフレームが表示されること
  5. 画質・速度・字幕・チャプターのすべてが動作し、画質切替で再生位置と再生状態が維持されること
  6. プレーヤーの全操作がキーボードのみで完遂できること
  7. レジューム再生が動作し、リロード後も停止位置から再開できること
  8. ペイウォールが動作し、加入後に停止位置から続きが再生されること
  9. エピソードを10回連続で切り替えてもメモリが単調増加しないこと
  10. 埋め込みモードの初期バンドルが80KB以下で、記事のCore Web Vitalsを悪化させないこと
  11. デモ中の視聴が視聴分析に反映され、離脱ヒートマップが意味のある形状で表示されること
  12. 全28画面のいずれにも空の状態がなく、リアルなコンテンツが表示されていること
  13. axe-core で Critical / Serious の指摘が0件であること
  14. 動画・画像素材のライセンスが docs/assets-license.md にすべて記録されていること
  15. データアクセスがすべて lib/repo/ を経由し、視聴可否の判定が resolveAccess() に集約されていること
  16. HIREBASE / RELATE と並べたときに、明確に別のプロダクトとして見えること

判断に迷ったときのルール

  1. 仕様がこの文書にない場合は、実装せずに質問する。 推測で作らない
  2. プレーヤーの品質と、キーボード操作、空の画面を作らないことは削減対象外
  3. 既製のプレーヤーライブラリ(video.js / Plyr 等)を導入しない。 自前実装であることがこのデモの価値
  4. スコープ外(4章 Won't have)は、思いついても実装しない。特にライブ配信とDRM
  5. 「デモだから」を理由に品質を落とす判断はしない
  6. hls.js とイベントリスナーの破棄を忘れない。このアプリで最も起きやすい不具合はメモリリーク
  7. HIREBASE / RELATE のトークン・コンポーネントをコピーしない

FROM DEMO TO PRODUCTION

本番で使うには、あと4つ必要です

この要件定義書は、画面と動きを確かめるためのデモとして書かれています。「動画配信」を社内やお客さまに実際に使ってもらうには、見た目の裏側に次の4つが要ります。

  1. 1データの保存

    デモでは
    データはその端末のブラウザの中にしか残りません。ほかの端末やほかの人とは共有されず、消えることもあります。
    本番では
    サーバーのデータベースに保存し、バックアップと復元ができるようにします。
  2. 2セキュリティ

    デモでは
    ログインがなく、誰でもすべての画面とデータを見られます。
    本番では
    ログイン、権限(誰が何を見られるか)、通信とデータの暗号化、不正なアクセスへの対策を入れます。
  3. 3ガバナンス(運用ルール)

    デモでは
    決めていません。
    本番では
    誰がいつ何をしたかの記録、個人情報の扱い(プライバシーポリシー・同意)、アカウントの発行と削除、障害のときの連絡体制を決めます。
  4. 4公開・デプロイ

    デモでは
    手元で動かすか、デモとして公開するだけです。
    本番では
    独自ドメイン、本番とテストの環境分け、更新の手順、監視と障害対応、月々の費用の管理を整えます。

本番運用をお考えの方へ

ぜひ一度ご相談ください。最適なプランのご紹介と、進め方をお伝えします。代表の西澤が直接お話しします。

  • 自分で作ったアプリを、そのまま本番で使いたい
  • どこまで自分で作り、どこから任せるか決めたい
  • 社内の決まりやセキュリティの基準に合わせたい

下のカレンダーから、そのまま日程を選べます。

カレンダーが表示されないときは こちらから日程を選べます。日程を決める前に聞きたいことがあれば、公式LINEお問い合わせフォーム からどうぞ。

FREE CONSULTATION

分からないときは、お気軽に30分無料相談へ

途中で止まった、エラーが消えない、自社向けに作り変えたい。どんなことでも大丈夫です。

日程を決めて話す

代表の西澤と直接お話しできます。空いている日時を選ぶだけで予約できます。

代表 西澤と話す日程を選ぶ

まずは問い合わせる

要件定義書がほしい方・ご相談は公式LINEから。メールでのお問い合わせはフォームから。