Appearance
画面設計: 動画視聴・進捗追跡
関連: 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/heartbeatheartbeatは 15 秒ごとに現在位置を送信
- 保持項目:
distinct_segments: [{start, end}, ...]実際に視聴した区間(ユニオンとして保持。重複区間は 1 回のみ含む)watched_seconds= Σ(distinct_segments.length)(重複視聴は加算しない。同一秒を複数回再生しても 1 回のみカウント)skipped_segments: [{start, end}, ...]seek で飛ばした区間completed: distinct_segments のユニオン長 /video.durationSecondsが 90% 以上 で 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