feat: add video playback progress reporting

This commit is contained in:
Codex
2026-06-29 20:38:12 +08:00
parent 38db867d58
commit f6710ace8b
9 changed files with 223 additions and 3 deletions

View File

@@ -1694,6 +1694,35 @@ async function testVideos() {
quotaPlayback.watermark?.traceId,
'quota video play event should record watermark trace id',
);
const progressStarted = await request('/api/videos/progress', {
method: 'POST',
body: { playToken: quotaPlayback.playToken, eventType: 'start', progressSeconds: 3, durationSeconds: 90 },
});
assert.equal(progressStarted.item?.status, 'started', 'video progress start should mark event started');
assert.equal(progressStarted.item?.playback?.watchedSeconds, 3, 'video progress start should record watched seconds');
const progressHeartbeat = await request('/api/videos/progress', {
method: 'POST',
body: { playToken: quotaPlayback.playToken, eventType: 'heartbeat', progressSeconds: 45, watchedSeconds: 48, durationSeconds: 90 },
});
assert.equal(progressHeartbeat.item?.status, 'started', 'video heartbeat should keep event started before completion threshold');
assert.equal(progressHeartbeat.item?.playback?.watchedSeconds, 48, 'video heartbeat should keep max watched seconds');
const progressCompleted = await request('/api/videos/progress', {
method: 'POST',
body: { playToken: quotaPlayback.playToken, eventType: 'complete', progressSeconds: 90, durationSeconds: 90 },
});
assert.equal(progressCompleted.item?.status, 'completed', 'video completion should mark event completed');
assert.equal(progressCompleted.item?.playback?.completionRate, 1, 'video completion should record completion rate');
const secondUserProgressDenied = await request('/api/videos/progress', {
userId: SECOND_STUDENT_USER_ID,
method: 'POST',
body: { playToken: quotaPlayback.playToken, eventType: 'heartbeat', progressSeconds: 10 },
expectStatus: 404,
});
assert.equal(secondUserProgressDenied.code, 'VIDEO_PLAY_EVENT_NOT_FOUND', 'another user must not update a play token');
}
async function testVocabulary() {
@@ -3735,6 +3764,13 @@ async function testTenantContentAssetsAndImports() {
assert.equal(mediaVideoSummary.videoPlay?.svipPlays >= 1, true, 'media analytics summary should count SVIP video plays');
assert.ok(mediaVideoSummary.videoTop?.some(item => item.videoId === ids.video), 'media analytics summary should include top videos');
const mediaQuotaVideoSummary = await request('/api/tenant-content/media-analytics/summary', {
userId: TENANT_ADMIN_USER_ID,
query: { timeRange: '30d', videoId: ids.quotaVideo },
});
assert.equal(mediaQuotaVideoSummary.videoPlay?.completedEvents >= 1, true, 'media analytics summary should count completed video events');
assert.equal(mediaQuotaVideoSummary.videoPlay?.watchedSeconds >= 90, true, 'media analytics summary should count watched seconds');
const mediaVideoEventsByVideo = await request('/api/tenant-content/media-analytics/video-events', {
userId: TENANT_ADMIN_USER_ID,
query: { timeRange: '30d', videoId: ids.video, limit: 5 },