Files
starr-philproperties/apps/api/services/playback_position.service.js

41 lines
2.0 KiB
JavaScript

/***********************************************************************************************************************************************************************
* File Name: playback_position.service.js
* Type of Program: Service
* Description: Last-known video/audio playback position per (user, lesson, block) — entirely
* decoupled from CompletionRequirement, tracked for ANY video/audio block regardless
* of whether the lesson has a watch-type completion requirement configured. Powers
* "resume where I left off" only; no anti-cheat/wall-clock validation here since
* there's nothing being gated — see completion_requirements.service.js for that.
*
* recordPlaybackPosition — last-write-wins upsert (not a ratcheted max — a deliberate rewind
* should resume there, not snap back to a prior high-water mark).
* getPlaybackPositions — { [block_id]: percent } for every block tracked on a lesson.
*
* Author: Kenneth Obsequio (@lash0000)
* Date Created: Jul. 17, 2026
***********************************************************************************************************************************************************************/
'use strict';
const MediaPlaybackPosition = require('../models/courses/media_playback_position.mdl');
async function recordPlaybackPosition(userId, { lessonId, blockId, percent }) {
if (!blockId) return;
await MediaPlaybackPosition.upsert({
user_id: userId,
lesson_id: lessonId,
block_id: blockId,
percent: Math.min(100, Math.max(0, Math.round(percent))),
}, { conflictFields: ['user_id', 'lesson_id', 'block_id'] });
}
async function getPlaybackPositions(userId, lessonId) {
const rows = await MediaPlaybackPosition.findAll({
where: { user_id: userId, lesson_id: lessonId },
attributes: ['block_id', 'percent'],
});
return Object.fromEntries(rows.map((r) => [r.block_id, r.percent]));
}
module.exports = { recordPlaybackPosition, getPlaybackPositions };