codebook / assets /js /video_segment.js
vidhimudaliar's picture
Upload 4 files
d2aea71 verified
// assets/js/video_segment.js
const DEFAULT_FPS = 30;
/**
* Playback rules by label (case-insensitive):
*
* time : video start -> endFrame
* success : entire video
* failure : entire video
* safety conflict : startFrame -> endFrame
* safety avoidance : startFrame -> end of video
* passive wait : startFrame -> endFrame
* redundant retrieval : startFrame -> end of video
* task model uncertainty : startFrame -> end of video
* capability miscalibration : startFrame -> endFrame
* missed grab : (startFrame-10) -> (endFrame+50 or end of video)
* slippage : (startFrame-10) -> (endFrame+50 or end of video)
*
* Manual control requirements:
* 1) If user pauses, DO NOT auto-resume.
* 2) If user seeks/scrubs anywhere, allow it. When playback reaches the window end,
* it loops back to the window start (and continues only if user is playing).
*/
function normalizeLabel(label) {
return String(label || "").trim().toLowerCase();
}
function computeWindowFrames(label, startFrame, endFrame) {
const lab = normalizeLabel(label);
let s = Number(startFrame);
let e = Number(endFrame);
if (!Number.isFinite(s)) s = 0;
if (!Number.isFinite(e)) e = 0;
if (s > e) [s, e] = [e, s];
let start = s;
let end = e;
let endIsVideo = false;
const isTime = lab === "time";
const isSuccess = lab === "success";
const isFailure = lab === "failure";
const isSafetyConflict = lab === "safety conflict";
const isSafetyAvoidance = lab === "safety avoidance";
const isPassiveWait = lab === "passive wait";
const isRedundantRetrieval = lab === "redundant retrieval";
const isTMU = lab === "task model uncertainty";
const isCapMis = lab === "capability miscalibration";
const isMissedGrab = lab === "missed grab";
const isSlippage = lab === "slippage";
if (isTime) {
start = 0;
end = e;
endIsVideo = false;
} else if (isSuccess || isFailure) {
start = 0;
endIsVideo = true;
} else if (isSafetyConflict || isPassiveWait || isCapMis) {
start = s;
end = e;
endIsVideo = false;
} else if (isSafetyAvoidance || isRedundantRetrieval || isTMU) {
start = s;
endIsVideo = true;
} else if (isMissedGrab || isSlippage) {
start = Math.max(0, s - 10);
end = e + 50; // will clamp to duration
endIsVideo = false;
} else {
// Unknown label: just use annotated segment
start = s;
end = e;
endIsVideo = false;
}
start = Math.max(0, Math.floor(start));
if (!endIsVideo) end = Math.max(start, Math.floor(end));
return { startFrameAdj: start, endFrameAdj: end, endIsVideo };
}
/**
* Attaches labeled looping to a <video>, while preserving manual user control.
*
* - Autoplays (muted) so the page is lively.
* - If user pauses, we do NOT force play().
* - If user seeks anywhere, we allow it.
* - When playback crosses the computed end time, we jump back to the start time.
* If the video was playing, we keep playing. If paused, it stays paused.
*/
function attachLabeledLoop(videoEl, { label, startFrame, endFrame, fps = DEFAULT_FPS }) {
const { startFrameAdj, endFrameAdj, endIsVideo } = computeWindowFrames(label, startFrame, endFrame);
const startSec = Math.max(0, startFrameAdj / fps);
let endSec = Math.max(startSec, endFrameAdj / fps);
// Autoplay compatibility: muted + playsInline
videoEl.muted = true;
videoEl.playsInline = true;
// Track whether a pause was user-initiated (so we never override it)
let userPaused = false;
function safePlay() {
const p = videoEl.play();
if (p && typeof p.catch === "function") p.catch(() => { });
}
function jumpToStart(keepPaused) {
try {
videoEl.currentTime = startSec;
} catch (_) { }
if (!keepPaused) safePlay();
}
// Setup final endSec based on duration once known
videoEl.addEventListener("loadedmetadata", () => {
const dur = Number.isFinite(videoEl.duration) ? videoEl.duration : null;
if (dur != null) {
if (endIsVideo) {
endSec = dur;
} else if (endSec > dur) {
endSec = dur;
}
}
// Start at window start and autoplay (muted)
userPaused = false;
jumpToStart(false);
});
// If user pauses, respect it
videoEl.addEventListener("pause", () => {
// If it paused because it reached the natural end (rare), userPaused still ok
userPaused = true;
});
// If user hits play, allow it (resume normal looping)
videoEl.addEventListener("play", () => {
userPaused = false;
});
// Core loop logic: when reaching endSec, loop back to startSec
videoEl.addEventListener("timeupdate", () => {
// epsilon to prevent jitter
if (endSec <= startSec + 0.01) return;
if (videoEl.currentTime >= endSec - 0.02) {
// Loop back, but DO NOT resume if user paused
const keepPaused = videoEl.paused || userPaused;
jumpToStart(keepPaused);
}
});
// IMPORTANT CHANGE: allow free seeking/scrubbing.
// No snapping during seek; we only enforce the window when time reaches endSec.
}
window.DEFAULT_FPS = DEFAULT_FPS;
window.attachLabeledLoop = attachLabeledLoop;
window.computeWindowFrames = computeWindowFrames;