課程進度追蹤機制#

概述#

平台採用雙重追蹤機制來準確記錄學習進度,確保用戶真實觀看影片內容,而非透過快轉作弊。

兩種進度指標#

1. 播放位置(lastWatchedPosition)#

用途: 斷點續看

記錄用戶在影片中的位置(以秒為單位),用於下次進入時從該位置繼續播放。

範例:

  • 30 分鐘影片,看到第 15 分鐘
  • 系統記錄:lastWatchedPosition = 900
  • 下次打開影片,自動從第 15 分鐘開始播放

特性:

  • ✅ 可以自由拖拉進度條
  • ✅ 會即時更新位置
  • 不影響課程完成度

2. 實際觀看時間(watchedTime)#

用途: 防作弊,計算課程完成度

記錄用戶真實觀看的累積時間,這是計算課程完成度的核心指標。

範例:

  • 30 分鐘影片(1800 秒)
  • 用戶實際觀看 1500 秒
  • 課程完成度:1500 / 1800 = 83%

特性:

  • ✅ 只有連續播放才會累積
  • ✅ 重複觀看可以累積(直到 100%)
  • ❌ 快轉跳躍不會增加觀看時間
  • ❌ 拖拉進度條不會增加觀看時間

防作弊機制詳解#

運作原理#

系統每秒檢查一次影片播放狀態:

每 1 秒執行:
  1. 記錄當前播放位置 A
  2. 比較上一秒的位置 B
  3. 計算時間差:A - B = 差值

  判斷:
  - 如果差值 = 0-2 秒 → ✅ 正常播放,累積觀看時間
  - 如果差值 > 2 秒  → ❌ 偵測到跳躍,不累積觀看時間
  - 如果差值 < 0 秒  → ❌ 向後拖拉,不累積觀看時間

為什麼允許 2 秒誤差?

  • 考慮網路緩衝
  • 影片載入延遲
  • 系統時鐘誤差

實際案例說明#

案例一:正常觀看#

小明的操作:

  1. 播放 30 分鐘影片(1800 秒)
  2. 從頭開始看到結束,中途暫停喝水 3 次
  3. 總播放時間:40 分鐘(包含暫停)
  4. 實際觀看時間:30 分鐘

系統記錄:

時間軸:
0秒  ► 播放開始
1秒  ► 檢查:1-0=1秒 ✅ 累積 +1秒 (總計: 1秒)
2秒  ► 檢查:2-1=1秒 ✅ 累積 +1秒 (總計: 2秒)
3秒  ► 檢查:3-2=1秒 ✅ 累積 +1秒 (總計: 3秒)
...
300秒 ► 用戶暫停喝水
...
320秒 ► 用戶繼續播放(暫停 20 秒不計時)
321秒 ► 檢查:321-320=1秒 ✅ 累積 +1秒 (總計: 301秒)
...
1800秒 ► 影片結束

最終:watchedTime = 1800秒 (100%)

結果: ✅ 可以領取獎勵


案例二:嘗試快轉作弊#

小華的操作:

  1. 播放 30 分鐘影片(1800 秒)
  2. 看了 5 分鐘(300 秒)
  3. 直接拖拉進度條到最後
  4. 播放最後 10 秒
  5. 影片結束

系統記錄:

時間軸:
0秒  ► 播放開始
1秒  ► 檢查:1-0=1秒 ✅ 累積 +1秒 (總計: 1秒)
...
300秒 ► 檢查:300-299=1秒 ✅ 累積 +1秒 (總計: 300秒)

>>> 用戶拖拉進度條 <<<

1790秒 ► 檢查:1790-300=1490秒 ❌ 偵測到跳躍,不累積
       ⚠️ 警告:跳過 1490 秒(約 24.8 分鐘)
1791秒 ► 檢查:1791-1790=1秒 ✅ 累積 +1秒 (總計: 301秒)
...
1800秒 ► 影片結束

最終:watchedTime = 310秒 (17%)

顯示訊息:

⚠️ 偵測到跳躍: 300秒 -> 1790秒 (跳過 1490秒,不計入觀看時間)

結果: ❌ 進度只有 17%,無法領取獎勵(需要 100%)


案例三:重複觀看累積進度#

小李的觀看過程:

小李購買了「設計模式入門」課程,影片長度 30 分鐘(1800 秒)。

第一天:只看了前半段

操作:播放 0 → 900 秒,然後關閉瀏覽器

系統記錄:
- watchedTime: 900 秒
- progress: 900/1800 = 50%
- 可以領取獎勵?❌ 需要 100%

第二天:想快速完成,嘗試快轉

操作:
1. 打開影片,自動從 900 秒繼續播放
2. 看了 100 秒(現在在 1000 秒位置)
3. 想說「剩下 800 秒太長了,直接拖到最後吧」
4. 拖拉進度條到 1790 秒
5. 播放最後 10 秒

系統記錄:
時間軸:
900秒  ► 繼續播放(上次停止的位置)
901秒  ► 檢查:901-900=1秒 ✅ 累積 +1秒 (總計: 901秒)
...
1000秒 ► 檢查:1000-999=1秒 ✅ 累積 +1秒 (總計: 1000秒)

>>> 拖拉到 1790 秒 <<<

1790秒 ► 檢查:1790-1000=790秒 ❌ 偵測到跳躍
       ⚠️ 警告:跳過 790 秒,不計入觀看時間
1791秒 ► 檢查:1791-1790=1秒 ✅ 累積 +1秒 (總計: 1001秒)
...
1800秒 ► 影片結束

最終:watchedTime = 1010秒 (56%)

小李的想法: 「蛤?我明明看到最後了,怎麼才 56%?」

系統回應: 「您只實際觀看了 1010 秒(約 17 分鐘),還需要觀看 790 秒才能達到 100%。」

第三天:小李改變策略,重複觀看

操作:
1. 小李決定重新播放影片
2. 這次從頭開始(位置 0 秒)
3. 連續播放 800 秒(前 13 分鐘的內容)
4. 雖然這 800 秒已經看過,但系統仍會累積

系統記錄:
0秒    ► 播放開始(重新觀看)
1秒    ► 檢查:1-0=1秒 ✅ 累積 +1秒 (總計: 1011秒)
...
800秒  ► 檢查:800-799=1秒 ✅ 累積 +1秒 (總計: 1810秒)

最終:
- watchedTime = 1810秒
- 但影片總長度只有 1800 秒
- 系統限制最大值:Math.min(1810, 1800) = 1800秒
- progress: 1800/1800 = 100% ✅

結果: ✅ 可以領取獎勵了!

小李學到的教訓:

  • ❌ 快轉無法增加觀看時間
  • ✅ 重複觀看可以累積進度
  • ✅ 達到總時長後自動 100%

課程完成度計算#

公式#

課程完成度 = (實際觀看時間 / 影片總時長) × 100%

進度上限 = 100%(重複觀看不會超過)

領取獎勵條件#

唯一條件: 課程完成度達到 100%

常見誤解:

  • ❌ 「拖到最後就能領獎勵」→ 錯,必須實際觀看
  • ❌ 「看到 80% 就能領」→ 錯,必須 100%
  • ❌ 「暫停會影響進度」→ 錯,暫停不計時但不影響已累積的觀看時間

達到 100% 的方法#

方法一:連續播放(推薦)#

30 分鐘影片
↓
從頭播放到尾(1800 秒)
↓
watchedTime = 1800 秒
↓
progress = 100% ✅

方法二:分段觀看#

第一天:看 0-900 秒(前 15 分鐘)
        watchedTime = 900 秒 (50%)

第二天:看 900-1800 秒(後 15 分鐘)
        watchedTime = 900 + 900 = 1800 秒 (100%) ✅

方法三:重複觀看(如果之前快轉了)#

已累積:1000 秒 (56%)
還需要:1800 - 1000 = 800 秒

操作:重新播放影片前 800 秒
結果:1000 + 800 = 1800 秒 (100%) ✅

進度顯示#

在影片播放頁面,你會看到兩種進度:

播放位置進度條#

影片進度條:▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░  70%
            ↑ 當前播放位置

用途: 顯示當前在影片的哪個位置

可以做:

  • 拖拉到任意位置
  • 快轉、倒轉
  • 查看縮圖

不影響: 課程完成度

課程完成度#

課程完成度:56%
實際觀看:1010 秒 / 1800 秒

用途: 決定能否領取獎勵

如何增加:

  • ✅ 連續播放影片
  • ✅ 重複觀看任意片段
  • ❌ 無法透過快轉增加

控制台日誌(開發者模式)#

打開瀏覽器開發者工具(F12),可以看到詳細的追蹤日誌:

正常播放#

✓ 觀看時間累積: +1秒 (總計: 1秒)
✓ 觀看時間累積: +1秒 (總計: 2秒)
✓ 觀看時間累積: +1秒 (總計: 3秒)
📊 更新進度: 位置 17% | 實際觀看 17%

快轉跳躍#

⚠️ 偵測到跳躍: 300秒 -> 1790秒 (跳過 1490秒,不計入觀看時間)

恢復進度#

✅ 恢復觀看進度: 1010秒 (56%)

常見問題#

Q1: 為什麼我看完影片了,但進度只有 50%?#

A: 您可能使用了快轉功能。系統只計算實際連續播放的時間,快轉跳過的部分不計入。

解決方法: 重新播放影片,或播放您快轉跳過的片段。


Q2: 我重複看同一段會累積進度嗎?#

A: 會!重複觀看任何片段都會累積實際觀看時間,直到達到影片總時長(100%)。

範例:

  • 30 分鐘影片
  • 第一次看前 20 分鐘(1200 秒)
  • 第二次再看前 10 分鐘(600 秒)
  • 總觀看時間:1200 + 600 = 1800 秒 = 100% ✅

Q3: 暫停會影響進度嗎?#

A: 不會。暫停只是停止播放,不會減少已累積的觀看時間。

範例:

  • 看了 10 分鐘(600 秒)
  • 暫停去上廁所 5 分鐘
  • 繼續播放
  • 觀看時間仍然是 600 秒(暫停期間不計時)

Q4: 可以用 1.5 倍速播放嗎?#

A: 可以!YouTube 播放器支援調整播放速度(0.5x - 2x)。

注意:

  • 影片總時長仍以正常速度計算
  • 例如:30 分鐘影片用 2 倍速播放,只需 15 分鐘
  • 但系統仍會記錄為觀看 30 分鐘(因為內容確實看完了)

這是允許的嗎? ✅ 是的,因為您仍然在真實觀看內容,只是速度較快。


Q5: 我看到 80% 可以領獎勵嗎?#

A: 不行,必須達到 100% 才能領取獎勵。


Q6: 進度會過期嗎?#

A: 不會!已累積的觀看時間永久保存,隨時可以繼續累積。

範例:

  • 今天看 10 分鐘(33%)
  • 一個月後回來再看 20 分鐘(67%)
  • 總進度:33% + 67% = 100% ✅

Q7: 關閉瀏覽器會丟失進度嗎?#

A: 不會。系統每 3 秒會自動儲存進度到伺服器。

最壞情況: 如果瀏覽器突然崩潰,最多只會丟失最後 3 秒的進度。


Q8: 為什麼要這麼嚴格?直接拖到最後不行嗎?#

A: 這是為了確保學習品質。課程內容是講師精心設計的,跳過內容會影響學習效果。

類比:

  • 🏃‍♂️ 馬拉松:必須跑完全程,不能坐車到終點
  • 📖 讀書:必須真正閱讀,不能只看封面和結尾
  • 🎓 上課:必須實際出席,不能只簽到就走

平台目標: 培養真正的技術能力,而非只是收集證書。


設計理念#

為什麼選擇這種機制?#

1. 確保學習品質

  • 學員必須真實觀看內容
  • 講師的心血不會被浪費
  • 證書代表真實的學習成果

2. 公平性

  • 所有學員都以相同標準計算進度
  • 排行榜和經驗值反映真實努力
  • 防止作弊者獲得不公平優勢

3. 彈性與友善

  • 允許分段學習(不必一次看完)
  • 允許重複觀看(加深理解)
  • 允許調整播放速度(配合個人習慣)
  • 自動斷點續看(無縫學習體驗)

4. 透明度

  • 控制台日誌清楚顯示追蹤過程
  • 用戶可以理解為何進度是這個數字
  • 沒有隱藏的規則

與其他平台的比較#

平台進度計算方式可以快轉作弊?
YouTube無強制追蹤✅ 隨意快轉
Coursera播放位置❌ 有防作弊
Udemy播放位置✅ 可以快轉
水球學院實際觀看時間嚴格防作弊

技術細節(給技術人員)#

前端追蹤#

// 每秒檢查一次
setInterval(() => {
  const currentTime = player.getCurrentTime();
  const lastTime = lastPosition;
  const timeDiff = currentTime - lastTime;

  // 只有連續播放(誤差 ≤ 2 秒)才累積
  if (timeDiff >= 0 && timeDiff <= 2) {
    watchedTime += timeDiff;
  } else if (timeDiff > 2) {
    console.warn('偵測到跳躍,不計入觀看時間');
  }

  lastPosition = currentTime;
}, 1000);

後端驗證#

// 優先使用實際觀看時間計算進度
if (watchedTime != null && watchedTime > 0) {
    progressPercent = (duration > 0)
        ? (int) ((watchedTime * 100.0) / duration)
        : 0;
} else {
    // 向後兼容:沒有 watchedTime 時使用播放位置
    progressPercent = (duration > 0)
        ? (int) ((watchedPosition * 100.0) / duration)
        : 0;
}

// 限制最大累積時間 = 影片總長度
int cappedWatchedTime = Math.min(watchedTime, duration);
missionProgress.setWatchedTime(cappedWatchedTime);

資料庫欄位#

CREATE TABLE mission_progress (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT NOT NULL,
    mission_id BIGINT NOT NULL,
    progress INTEGER NOT NULL,           -- 百分比 (0-100)
    completed BOOLEAN NOT NULL,          -- 是否完成
    reward_claimed BOOLEAN NOT NULL,     -- 是否領取獎勵
    last_watched_position INTEGER,       -- 最後播放位置(秒)
    video_duration INTEGER,              -- 影片總長度(秒)
    watched_time INTEGER,                -- 實際累積觀看時間(秒)
    completed_at TIMESTAMP,
    updated_at TIMESTAMP
);

總結#

水球軟體學院的進度追蹤系統採用實際觀看時間作為核心指標,確保:

學習品質:學員真實觀看課程內容 ✅ 防止作弊:無法透過快轉完成課程 ✅ 學習彈性:可以分段學習、重複觀看 ✅ 公平競爭:所有學員使用相同標準 ✅ 用戶友善:自動儲存、斷點續看

雖然這讓「快速完成課程」變得不可能,但這正是我們的目標:培養真正的技術能力,而非只是收集證書