Spaces:
Running
Running
File size: 5,597 Bytes
d2aea71 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
// 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;
|