Skip to content

画面設計: 動画視聴・進捗追跡

関連: api.md §3 / data-model.md §3.6 / requirements.md §3.4

URL

  • /app/video — 動画一覧
  • /app/video/:videoId — プレイヤー

目的

動画コンテンツの視聴を通じた学習、および視聴ログ(区間/時間/スキップ)をもとに学習行動の一貫性指標の入力とする。

1. 一覧 /app/video

  • 担当動画一覧(サムネ・タイトル・長さ・視聴状況)
  • フィルタ:未視聴/進行中/完了
  • API: GET /learner/videos

2. プレイヤー /app/video/:videoId

レイアウト

  • 動画プレイヤー(16:9)
  • タイトル・説明
  • 進捗バー・再生コントロール(再生/一時停止、倍速、音量、字幕)
  • 付随クイズ(任意):視聴後に表示
  • 「完了」ボタン(サーバ判定も併用)

視聴ログ仕様(VideoWatchLog

  • セッションID:動画再生開始時に UUID を生成、以降の全イベントに付与
  • イベント種別
    • play / pause / seek / ended / heartbeat
    • heartbeat は 15 秒ごとに現在位置を送信
  • 保持項目
    • distinct_segments: [{start, end}, ...] 実際に視聴した区間(ユニオンとして保持。重複区間は 1 回のみ含む)
    • watched_seconds = Σ(distinct_segments.length)(重複視聴は加算しない。同一秒を複数回再生しても 1 回のみカウント)
    • skipped_segments: [{start, end}, ...] seek で飛ばした区間
    • completed: distinct_segments のユニオン長 / video.durationSeconds90% 以上 で true(分母は動画全長固定)

スキップ検出ロジック(ドラフト)

  • seek により 5 秒以上進めた場合、該当区間を skipped_segments に記録
  • 1.5x 以上の倍速再生区間を sped_up_segments にオプション記録
  • タブ非表示(visibility=hidden)中は watched_seconds に加算しない

API

  • POST /learner/videos/:id/watch-events
    • ボディ: { sessionId, event, positionSeconds, meta }
    • Idempotency-Key ヘッダ必須
  • GET /learner/videos/:id 応答に視聴ログサマリーと次回再生位置を含む

エッジケース

  • ネットワーク断:イベントはローカルキュー(IndexedDB)に退避、再接続時に順序保持で送信
  • バッテリー節約・バックグラウンド:heartbeat を停止
  • 重複再生タブ:同一動画複数タブの検出(StorageEvent)で警告

一貫性指標への寄与

  • 実測学習時間の入力:1 動画あたりの watched_seconds(ユニオン方式)を上限 video.durationSeconds でクランプしてから StudyTimeLog.measured_minutes に加算(同一動画の再視聴は時間加算しない方針。再視聴学習を反映したい場合は replay_count として別カラムに記録)
  • スキップ率:skipped_segments 総和 / 動画長
  • 完了率:completed

アクセシビリティ

  • 字幕必須(日本語/ベトナム語)
  • キーボード操作:スペースで再生/停止、← → シーク、↑ ↓ 音量
  • 視覚障害:音声トラックの切替

文言と倫理

  • 「スキップ多すぎです」等の責める表現は使わない
  • 一貫性指標として使うことを別画面(要件準拠)で事前開示済み前提

RBAC

  • LR のみ自分の視聴ログを送信可(§auth-rbac.md §1.0 スコープ)
  • TA/OA は管理画面側(別 issue)で閲覧

モック(M1)

  • 公開サンプル動画(短尺)を使用
  • 視聴ログはコンソールに出力、Backend 連携なし

未確定

  • 動画プラットフォーム(Cloudflare Stream / YouTube / 自前)は Q3
  • 通信量制限下での画質選択 UI

Internal — thriveJobs