Spaces:
Running
Running
darren.zhang Claude Opus 5 (1M context) commited on
Commit ·
0f8fa12
1
Parent(s): c8f96cb
teleopSample: step annotations + timeline on the older pages, autoplay on ours
Browse filesenhance.js adds to the 14 existing episode pages what the Faber U pick-and-place
pages have: an instruction / object / target / scene header, a segmented step
timeline over the scrubber, and a panel with the current step's annotation and
measured signals (end-effector travel, height change, speed, gripper). Wording
switched from "spans" to "steps" throughout, including the sidebar. The eight
pick-and-place pages now autoplay and loop like the older ones.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- teleopSample/drink_ep0.html +2 -1
- teleopSample/drink_ep1.html +2 -1
- teleopSample/enhance.js +167 -0
- teleopSample/ep0/viewer.html +3 -2
- teleopSample/ep1/viewer.html +3 -2
- teleopSample/ep2/viewer.html +3 -2
- teleopSample/ep3/viewer.html +3 -2
- teleopSample/ep4/viewer.html +3 -2
- teleopSample/ep5/viewer.html +3 -2
- teleopSample/faberU/t2_pet_bottle_into_steel_tray/20260707052527/viewer.html +18 -1
- teleopSample/faberU/t2_pet_bottle_into_steel_tray/20260707052609/viewer.html +18 -1
- teleopSample/faberU/t3_juice_bottle_onto_plate/20260724050737/viewer.html +18 -1
- teleopSample/faberU/t3_juice_bottle_onto_plate/20260724050827/viewer.html +18 -1
- teleopSample/faberU/t4_juice_bottle_into_black_tray/20260728075709/viewer.html +18 -1
- teleopSample/faberU/t4_juice_bottle_into_black_tray/20260728080339/viewer.html +18 -1
- teleopSample/faberU/t6_pick_up_green_tea_bottle/20260730045050/viewer.html +18 -1
- teleopSample/faberU/t6_pick_up_green_tea_bottle/20260730045152/viewer.html +18 -1
- teleopSample/index.html +1 -1
- teleopSample/mem_ep0.html +2 -1
- teleopSample/mem_ep1.html +2 -1
- teleopSample/sort_ep0.html +2 -1
- teleopSample/sort_ep1.html +2 -1
- teleopSample/wipe_ep0.html +2 -1
- teleopSample/wipe_ep1.html +2 -1
teleopSample/drink_ep0.html
CHANGED
|
@@ -54,7 +54,7 @@ header{padding:16px 22px 8px}header h1{margin:0 0 3px;font-size:21px}
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
-
<div class="spans-head">
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
@@ -242,4 +242,5 @@ CAMS.forEach(c=>{ const a=document.createElement('a'); a.className='file-chip';
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
|
|
|
| 245 |
</body></html>
|
|
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
+
<div class="spans-head">annotated steps (3) · instruction segments</div>
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
| 245 |
+
<script src="enhance.js"></script>
|
| 246 |
</body></html>
|
teleopSample/drink_ep1.html
CHANGED
|
@@ -54,7 +54,7 @@ header{padding:16px 22px 8px}header h1{margin:0 0 3px;font-size:21px}
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
-
<div class="spans-head">
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
@@ -242,4 +242,5 @@ CAMS.forEach(c=>{ const a=document.createElement('a'); a.className='file-chip';
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
|
|
|
| 245 |
</body></html>
|
|
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
+
<div class="spans-head">annotated steps (3) · instruction segments</div>
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
| 245 |
+
<script src="enhance.js"></script>
|
| 246 |
</body></html>
|
teleopSample/enhance.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Brings the older sample pages up to the layout of the Faber U pick-and-place pages:
|
| 2 |
+
an instruction / object / target / scene header, a segmented step timeline above the
|
| 3 |
+
scrubber, and a panel showing the current step's annotation and measured signals.
|
| 4 |
+
Also switches the wording from "spans" to "steps".
|
| 5 |
+
Loaded with <script src="enhance.js"> (or ../enhance.js) — the page itself is untouched. */
|
| 6 |
+
(function () {
|
| 7 |
+
const J = id => { const e = document.getElementById(id); return e ? JSON.parse(e.textContent) : null; };
|
| 8 |
+
const S = J('series'), STEPS = J('spans') || [], META = J('meta');
|
| 9 |
+
if (!S || !STEPS.length) return;
|
| 10 |
+
|
| 11 |
+
const slug = (META && META.slug) || (location.pathname.match(/\/(ep\d+)\//) || [])[1] || '';
|
| 12 |
+
const family = slug.replace(/_?ep\d+$/, '') || 'legacy';
|
| 13 |
+
// object / target / scene, written from each recording's own description
|
| 14 |
+
const TASK = {
|
| 15 |
+
mem: { object: 'DDR memory module', target: 'motherboard slot',
|
| 16 |
+
scene: 'workbench with an open motherboard, dual-arm grippers' },
|
| 17 |
+
drink: { object: 'bottled green beverage', target: 'coaster on the countertop',
|
| 18 |
+
scene: 'countertop with the drink and a coaster set apart' },
|
| 19 |
+
sort: { object: 'packed garment bags (pink / white / beige / green)',
|
| 20 |
+
target: 'left side of the table', scene: 'table with a material bin of packed garment bags' },
|
| 21 |
+
wipe: { object: 'sponge', target: 'stains on the tabletop', scene: 'tabletop with stains' },
|
| 22 |
+
legacy: { object: 'water bottle', target: 'basket',
|
| 23 |
+
scene: 'tabletop with a water bottle and a basket' },
|
| 24 |
+
}[family] || {};
|
| 25 |
+
const instruction = (META && META.high_level_instruction) ||
|
| 26 |
+
(document.querySelector('header h1') || {}).textContent || '';
|
| 27 |
+
const sceneLine = (document.querySelector('.hilevel') || {}).textContent || '';
|
| 28 |
+
const scene = (sceneLine.replace(/^\s*scene\s*/i, '').trim()) || TASK.scene || '';
|
| 29 |
+
|
| 30 |
+
const COLORS = ["#8a8f98", "#4f8ff7", "#41c76b", "#e0a53e", "#b46fe0", "#e0616f", "#e08f5f", "#5fc9e0"];
|
| 31 |
+
const col = i => COLORS[i % COLORS.length];
|
| 32 |
+
const DUR = S.t ? S.t[S.n - 1] : (S.n - 1) / S.fps;
|
| 33 |
+
const END = S.end || S.end_pos; // [n][6] = left xyz, right xyz
|
| 34 |
+
const frameAt = t => Math.max(0, Math.min(S.n - 1, Math.round(t * S.fps)));
|
| 35 |
+
|
| 36 |
+
// ---------- styles
|
| 37 |
+
const css = document.createElement('style');
|
| 38 |
+
css.textContent = `
|
| 39 |
+
.x-instr{margin:10px 22px 0;padding:11px 15px;background:#12151c;border:1px solid #232733;border-radius:8px;font-size:13px;line-height:1.65}
|
| 40 |
+
.x-instr .k{font-size:10.5px;letter-spacing:.04em;text-transform:uppercase;color:#9aa1ad;margin-right:8px}
|
| 41 |
+
.x-instr .obj{color:#7fb2ff}.x-instr .tgt{color:#7fe0a0}
|
| 42 |
+
.x-tlwrap{margin:12px 22px 0;padding:9px 14px;background:#12151c;border:1px solid #232733;border-radius:8px}
|
| 43 |
+
.x-tl{position:relative;display:flex;height:26px;border-radius:5px;overflow:hidden;cursor:pointer}
|
| 44 |
+
.x-tl .seg{position:relative;opacity:.85;transition:opacity .12s}
|
| 45 |
+
.x-tl .seg.on{opacity:1;box-shadow:inset 0 0 0 2px #fff}
|
| 46 |
+
.x-tl .seg span{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:10.5px;color:#0b0d12;font-weight:700}
|
| 47 |
+
.x-tl .cur{position:absolute;top:0;bottom:0;width:2px;background:#fff;pointer-events:none}
|
| 48 |
+
.x-tlnums{display:flex;justify-content:space-between;font-size:10.5px;color:#6b7686;margin-top:4px;font-variant-numeric:tabular-nums}
|
| 49 |
+
.x-annot{margin:12px 22px 0;padding:14px 18px;background:#12151c;border:1px solid #232733;border-radius:8px}
|
| 50 |
+
.x-annot .t{font-size:11px;letter-spacing:.04em;color:#9aa1ad;text-transform:uppercase;margin-bottom:10px}
|
| 51 |
+
.x-annot h3{margin:0 0 2px;font-size:16px}
|
| 52 |
+
.x-annot h3 .tok{font-size:11.5px;color:#6f92c8;font-family:ui-monospace,monospace;margin-left:8px}
|
| 53 |
+
.x-annot .when{font-size:12px;color:#8890a0;font-variant-numeric:tabular-nums;margin-bottom:8px}
|
| 54 |
+
.x-annot p{margin:0 0 6px;line-height:1.6;font-size:13.5px;color:#dfe4ec}
|
| 55 |
+
.x-sgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:7px 26px;margin-top:10px}
|
| 56 |
+
.x-sg{display:flex;justify-content:space-between;border-bottom:1px dashed #232733;padding-bottom:3px;font-size:12.5px}
|
| 57 |
+
.x-sg .k{color:#9aa1ad}.x-sg .v{font-variant-numeric:tabular-nums}`;
|
| 58 |
+
document.head.appendChild(css);
|
| 59 |
+
|
| 60 |
+
// ---------- instruction header
|
| 61 |
+
const head = document.querySelector('header');
|
| 62 |
+
const instr = document.createElement('div');
|
| 63 |
+
instr.className = 'x-instr';
|
| 64 |
+
instr.innerHTML =
|
| 65 |
+
`<div><span class="k">instruction</span>${instruction}</div>` +
|
| 66 |
+
`<div style="margin-top:6px"><span class="k">object</span><span class="obj">${TASK.object || '—'}</span>` +
|
| 67 |
+
`<span class="k" style="margin-left:14px">target</span><span class="tgt">${TASK.target || '—'}</span>` +
|
| 68 |
+
`<span class="k" style="margin-left:14px">scene</span>${scene}</div>`;
|
| 69 |
+
head.parentNode.insertBefore(instr, head.nextSibling);
|
| 70 |
+
|
| 71 |
+
// ---------- step timeline over the scrubber
|
| 72 |
+
const controls = document.querySelector('.panel.controls');
|
| 73 |
+
const tlw = document.createElement('div');
|
| 74 |
+
tlw.className = 'x-tlwrap';
|
| 75 |
+
tlw.innerHTML = `<div class="x-tl" id="xTl"><div class="cur" id="xCur"></div></div>` +
|
| 76 |
+
`<div class="x-tlnums"><span>0.00s</span><span>${DUR.toFixed(2)}s</span></div>`;
|
| 77 |
+
controls.parentNode.insertBefore(tlw, controls);
|
| 78 |
+
const tl = tlw.querySelector('#xTl'), cur = tlw.querySelector('#xCur');
|
| 79 |
+
const videos = [...document.querySelectorAll('video')];
|
| 80 |
+
const master = videos[0];
|
| 81 |
+
const seek = t => videos.forEach(v => { try { v.currentTime = t; } catch (e) {} });
|
| 82 |
+
const num = (st, i) => (st.id != null ? st.id : i + 1); // keep the page's own numbering
|
| 83 |
+
STEPS.forEach((st, i) => {
|
| 84 |
+
const sg = document.createElement('div');
|
| 85 |
+
sg.className = 'seg';
|
| 86 |
+
sg.style.background = col(i);
|
| 87 |
+
sg.style.width = (100 * (st.end_s - st.start_s) / DUR) + '%';
|
| 88 |
+
sg.title = `${num(st, i)}. ${st.title} ${st.start_s.toFixed(2)}-${st.end_s.toFixed(2)}s`;
|
| 89 |
+
sg.innerHTML = `<span>${num(st, i)}</span>`;
|
| 90 |
+
sg.onclick = e => { e.stopPropagation(); seek(st.start_s + 0.01); };
|
| 91 |
+
tl.insertBefore(sg, cur);
|
| 92 |
+
});
|
| 93 |
+
tl.onclick = e => { const r = tl.getBoundingClientRect(); seek((e.clientX - r.left) / r.width * DUR); };
|
| 94 |
+
|
| 95 |
+
// ---------- current-step annotation
|
| 96 |
+
const an = document.createElement('div');
|
| 97 |
+
an.className = 'x-annot';
|
| 98 |
+
an.innerHTML = `<div class="t">step annotation · current step</div><h3 id="xLbl">–</h3>` +
|
| 99 |
+
`<div class="when" id="xWhen"></div><p id="xDesc"></p><div class="x-sgrid" id="xSig"></div>`;
|
| 100 |
+
controls.parentNode.insertBefore(an, controls.nextSibling);
|
| 101 |
+
|
| 102 |
+
const stat = (a, b) => { // measurements over a frame range
|
| 103 |
+
a = Math.max(0, a); b = Math.min(S.n - 1, b);
|
| 104 |
+
const arm = (o) => {
|
| 105 |
+
let path = 0, dz = 0, vmax = 0;
|
| 106 |
+
for (let i = a + 1; i <= b; i++) {
|
| 107 |
+
const p = END[i], q = END[i - 1];
|
| 108 |
+
const d = Math.hypot(p[o] - q[o], p[o + 1] - q[o + 1], p[o + 2] - q[o + 2]);
|
| 109 |
+
path += d; vmax = Math.max(vmax, d * S.fps);
|
| 110 |
+
}
|
| 111 |
+
dz = END[b][o + 2] - END[a][o + 2];
|
| 112 |
+
const dxy = Math.hypot(END[b][o] - END[a][o], END[b][o + 1] - END[a][o + 1]);
|
| 113 |
+
return { path, dz, dxy, vmax, vmean: path / Math.max(1e-6, (b - a) / S.fps) };
|
| 114 |
+
};
|
| 115 |
+
const L = arm(0), R = arm(3);
|
| 116 |
+
const act = L.path >= R.path ? L : R, side = L.path >= R.path ? 'left' : 'right';
|
| 117 |
+
return { act, side, L, R };
|
| 118 |
+
};
|
| 119 |
+
|
| 120 |
+
let curIdx = -1;
|
| 121 |
+
function render(i, t) {
|
| 122 |
+
const st = STEPS[i];
|
| 123 |
+
if (i !== curIdx) {
|
| 124 |
+
curIdx = i;
|
| 125 |
+
const a = frameAt(st.start_s), b = frameAt(st.end_s), m = stat(a, b);
|
| 126 |
+
document.getElementById('xLbl').innerHTML =
|
| 127 |
+
`${num(st, i)}. ${st.title}` + (st.token ? `<span class="tok">${st.token}</span>` : '');
|
| 128 |
+
document.getElementById('xWhen').textContent =
|
| 129 |
+
`${st.start_s.toFixed(2)}s – ${st.end_s.toFixed(2)}s · ${(st.end_s - st.start_s).toFixed(2)}s · ` +
|
| 130 |
+
`frames ${a}–${b} (${b - a + 1})` +
|
| 131 |
+
(st.success_s != null ? ` · success at ${st.success_s.toFixed(2)}s` : '');
|
| 132 |
+
const seg = META && META.instruction_segments &&
|
| 133 |
+
META.instruction_segments.find(x => Math.abs(x.start_frame_index / S.fps - st.start_s) < 0.2);
|
| 134 |
+
document.getElementById('xDesc').textContent = seg ? seg.instruction :
|
| 135 |
+
`${m.side} arm · ${m.act.path.toFixed(3)} m of end-effector travel in ` +
|
| 136 |
+
`${(st.end_s - st.start_s).toFixed(2)}s (mean ${m.act.vmean.toFixed(2)}, peak ` +
|
| 137 |
+
`${m.act.vmax.toFixed(2)} m/s), height change ${m.act.dz >= 0 ? '+' : ''}${m.act.dz.toFixed(2)} m.`;
|
| 138 |
+
const rows = [
|
| 139 |
+
['active arm', m.side],
|
| 140 |
+
['ee path', m.act.path.toFixed(3) + ' m'],
|
| 141 |
+
['ee Δz', (m.act.dz >= 0 ? '+' : '') + m.act.dz.toFixed(3) + ' m'],
|
| 142 |
+
['ee Δxy', m.act.dxy.toFixed(3) + ' m'],
|
| 143 |
+
['speed mean / max', `${m.act.vmean.toFixed(2)} / ${m.act.vmax.toFixed(2)} m/s`],
|
| 144 |
+
['gripper L', `${S.grip_l[a].toFixed(2)} → ${S.grip_l[b].toFixed(2)}`],
|
| 145 |
+
['gripper R', `${S.grip_r[a].toFixed(2)} → ${S.grip_r[b].toFixed(2)}`],
|
| 146 |
+
];
|
| 147 |
+
if (S.intervened) rows.push(['operator-sourced frames',
|
| 148 |
+
`${Math.round(100 * S.intervened.slice(a, b + 1).reduce((p, c) => p + (c ? 1 : 0), 0) / (b - a + 1))}%`]);
|
| 149 |
+
document.getElementById('xSig').innerHTML = rows.map(r =>
|
| 150 |
+
`<div class="x-sg"><span class="k">${r[0]}</span><span class="v">${r[1]}</span></div>`).join('');
|
| 151 |
+
tl.querySelectorAll('.seg').forEach((e, k) => e.classList.toggle('on', k === i));
|
| 152 |
+
}
|
| 153 |
+
cur.style.left = (100 * t / DUR) + '%';
|
| 154 |
+
}
|
| 155 |
+
function at(t) { for (let i = 0; i < STEPS.length; i++) if (t >= STEPS[i].start_s && t < STEPS[i].end_s) return i; return t < STEPS[0].start_s ? 0 : STEPS.length - 1; }
|
| 156 |
+
(function tick() { if (master) render(at(master.currentTime), master.currentTime); requestAnimationFrame(tick); })();
|
| 157 |
+
|
| 158 |
+
// ---------- wording: spans / subtask -> steps
|
| 159 |
+
document.querySelectorAll('.spans-head').forEach(e => {
|
| 160 |
+
e.textContent = e.textContent.replace(/temporal spans/i, 'annotated steps').replace(/spans/gi, 'steps');
|
| 161 |
+
});
|
| 162 |
+
document.querySelectorAll('.ro .k, .rgrid .k').forEach(e => {
|
| 163 |
+
const t = e.textContent.trim().toLowerCase();
|
| 164 |
+
if (t === 'subtask') e.textContent = 'step';
|
| 165 |
+
if (t === 'span progress') e.textContent = 'step progress';
|
| 166 |
+
});
|
| 167 |
+
})();
|
teleopSample/ep0/viewer.html
CHANGED
|
@@ -74,7 +74,7 @@ canvas{width:100%;display:block;cursor:pointer}
|
|
| 74 |
<div class="panel"><div class="camgrid" id="camgrid"></div></div>
|
| 75 |
<div class="sidecol">
|
| 76 |
<div class="panel">
|
| 77 |
-
<div class="spans-head">
|
| 78 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 79 |
</div>
|
| 80 |
<div class="panel tacpanel">
|
|
@@ -488,4 +488,5 @@ setTimeout(fitHand,2000); setTimeout(fitHand,4000);
|
|
| 488 |
var probe=grid.querySelector('.cam');
|
| 489 |
if(probe && window.ResizeObserver) new ResizeObserver(function(){fitHand();}).observe(probe);
|
| 490 |
})();
|
| 491 |
-
</script></
|
|
|
|
|
|
| 74 |
<div class="panel"><div class="camgrid" id="camgrid"></div></div>
|
| 75 |
<div class="sidecol">
|
| 76 |
<div class="panel">
|
| 77 |
+
<div class="spans-head">annotated steps (3) · auto-derived</div>
|
| 78 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 79 |
</div>
|
| 80 |
<div class="panel tacpanel">
|
|
|
|
| 488 |
var probe=grid.querySelector('.cam');
|
| 489 |
if(probe && window.ResizeObserver) new ResizeObserver(function(){fitHand();}).observe(probe);
|
| 490 |
})();
|
| 491 |
+
</script><script src="../enhance.js"></script>
|
| 492 |
+
</body></html>
|
teleopSample/ep1/viewer.html
CHANGED
|
@@ -74,7 +74,7 @@ canvas{width:100%;display:block;cursor:pointer}
|
|
| 74 |
<div class="panel"><div class="camgrid" id="camgrid"></div></div>
|
| 75 |
<div class="sidecol">
|
| 76 |
<div class="panel">
|
| 77 |
-
<div class="spans-head">
|
| 78 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 79 |
</div>
|
| 80 |
<div class="panel tacpanel">
|
|
@@ -488,4 +488,5 @@ setTimeout(fitHand,2000); setTimeout(fitHand,4000);
|
|
| 488 |
var probe=grid.querySelector('.cam');
|
| 489 |
if(probe && window.ResizeObserver) new ResizeObserver(function(){fitHand();}).observe(probe);
|
| 490 |
})();
|
| 491 |
-
</script></
|
|
|
|
|
|
| 74 |
<div class="panel"><div class="camgrid" id="camgrid"></div></div>
|
| 75 |
<div class="sidecol">
|
| 76 |
<div class="panel">
|
| 77 |
+
<div class="spans-head">annotated steps (3) · auto-derived</div>
|
| 78 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 79 |
</div>
|
| 80 |
<div class="panel tacpanel">
|
|
|
|
| 488 |
var probe=grid.querySelector('.cam');
|
| 489 |
if(probe && window.ResizeObserver) new ResizeObserver(function(){fitHand();}).observe(probe);
|
| 490 |
})();
|
| 491 |
+
</script><script src="../enhance.js"></script>
|
| 492 |
+
</body></html>
|
teleopSample/ep2/viewer.html
CHANGED
|
@@ -74,7 +74,7 @@ canvas{width:100%;display:block;cursor:pointer}
|
|
| 74 |
<div class="panel"><div class="camgrid" id="camgrid"></div></div>
|
| 75 |
<div class="sidecol">
|
| 76 |
<div class="panel">
|
| 77 |
-
<div class="spans-head">
|
| 78 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 79 |
</div>
|
| 80 |
<div class="panel tacpanel">
|
|
@@ -488,4 +488,5 @@ setTimeout(fitHand,2000); setTimeout(fitHand,4000);
|
|
| 488 |
var probe=grid.querySelector('.cam');
|
| 489 |
if(probe && window.ResizeObserver) new ResizeObserver(function(){fitHand();}).observe(probe);
|
| 490 |
})();
|
| 491 |
-
</script></
|
|
|
|
|
|
| 74 |
<div class="panel"><div class="camgrid" id="camgrid"></div></div>
|
| 75 |
<div class="sidecol">
|
| 76 |
<div class="panel">
|
| 77 |
+
<div class="spans-head">annotated steps (3) · auto-derived</div>
|
| 78 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 79 |
</div>
|
| 80 |
<div class="panel tacpanel">
|
|
|
|
| 488 |
var probe=grid.querySelector('.cam');
|
| 489 |
if(probe && window.ResizeObserver) new ResizeObserver(function(){fitHand();}).observe(probe);
|
| 490 |
})();
|
| 491 |
+
</script><script src="../enhance.js"></script>
|
| 492 |
+
</body></html>
|
teleopSample/ep3/viewer.html
CHANGED
|
@@ -74,7 +74,7 @@ canvas{width:100%;display:block;cursor:pointer}
|
|
| 74 |
<div class="panel"><div class="camgrid" id="camgrid"></div></div>
|
| 75 |
<div class="sidecol">
|
| 76 |
<div class="panel">
|
| 77 |
-
<div class="spans-head">
|
| 78 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 79 |
</div>
|
| 80 |
<div class="panel tacpanel">
|
|
@@ -488,4 +488,5 @@ setTimeout(fitHand,2000); setTimeout(fitHand,4000);
|
|
| 488 |
var probe=grid.querySelector('.cam');
|
| 489 |
if(probe && window.ResizeObserver) new ResizeObserver(function(){fitHand();}).observe(probe);
|
| 490 |
})();
|
| 491 |
-
</script></
|
|
|
|
|
|
| 74 |
<div class="panel"><div class="camgrid" id="camgrid"></div></div>
|
| 75 |
<div class="sidecol">
|
| 76 |
<div class="panel">
|
| 77 |
+
<div class="spans-head">annotated steps (3) · auto-derived</div>
|
| 78 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 79 |
</div>
|
| 80 |
<div class="panel tacpanel">
|
|
|
|
| 488 |
var probe=grid.querySelector('.cam');
|
| 489 |
if(probe && window.ResizeObserver) new ResizeObserver(function(){fitHand();}).observe(probe);
|
| 490 |
})();
|
| 491 |
+
</script><script src="../enhance.js"></script>
|
| 492 |
+
</body></html>
|
teleopSample/ep4/viewer.html
CHANGED
|
@@ -74,7 +74,7 @@ canvas{width:100%;display:block;cursor:pointer}
|
|
| 74 |
<div class="panel"><div class="camgrid" id="camgrid"></div></div>
|
| 75 |
<div class="sidecol">
|
| 76 |
<div class="panel">
|
| 77 |
-
<div class="spans-head">
|
| 78 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 79 |
</div>
|
| 80 |
<div class="panel tacpanel">
|
|
@@ -488,5 +488,6 @@ setTimeout(fitHand,2000); setTimeout(fitHand,4000);
|
|
| 488 |
var probe=grid.querySelector('.cam');
|
| 489 |
if(probe && window.ResizeObserver) new ResizeObserver(function(){fitHand();}).observe(probe);
|
| 490 |
})();
|
| 491 |
-
</script></
|
|
|
|
| 492 |
<!-- rev2 regular-file -->
|
|
|
|
| 74 |
<div class="panel"><div class="camgrid" id="camgrid"></div></div>
|
| 75 |
<div class="sidecol">
|
| 76 |
<div class="panel">
|
| 77 |
+
<div class="spans-head">annotated steps (3) · auto-derived</div>
|
| 78 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 79 |
</div>
|
| 80 |
<div class="panel tacpanel">
|
|
|
|
| 488 |
var probe=grid.querySelector('.cam');
|
| 489 |
if(probe && window.ResizeObserver) new ResizeObserver(function(){fitHand();}).observe(probe);
|
| 490 |
})();
|
| 491 |
+
</script><script src="../enhance.js"></script>
|
| 492 |
+
</body></html>
|
| 493 |
<!-- rev2 regular-file -->
|
teleopSample/ep5/viewer.html
CHANGED
|
@@ -74,7 +74,7 @@ canvas{width:100%;display:block;cursor:pointer}
|
|
| 74 |
<div class="panel"><div class="camgrid" id="camgrid"></div></div>
|
| 75 |
<div class="sidecol">
|
| 76 |
<div class="panel">
|
| 77 |
-
<div class="spans-head">
|
| 78 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 79 |
</div>
|
| 80 |
<div class="panel tacpanel">
|
|
@@ -488,4 +488,5 @@ setTimeout(fitHand,2000); setTimeout(fitHand,4000);
|
|
| 488 |
var probe=grid.querySelector('.cam');
|
| 489 |
if(probe && window.ResizeObserver) new ResizeObserver(function(){fitHand();}).observe(probe);
|
| 490 |
})();
|
| 491 |
-
</script></
|
|
|
|
|
|
| 74 |
<div class="panel"><div class="camgrid" id="camgrid"></div></div>
|
| 75 |
<div class="sidecol">
|
| 76 |
<div class="panel">
|
| 77 |
+
<div class="spans-head">annotated steps (3) · auto-derived</div>
|
| 78 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 79 |
</div>
|
| 80 |
<div class="panel tacpanel">
|
|
|
|
| 488 |
var probe=grid.querySelector('.cam');
|
| 489 |
if(probe && window.ResizeObserver) new ResizeObserver(function(){fitHand();}).observe(probe);
|
| 490 |
})();
|
| 491 |
+
</script><script src="../enhance.js"></script>
|
| 492 |
+
</body></html>
|
teleopSample/faberU/t2_pet_bottle_into_steel_tray/20260707052527/viewer.html
CHANGED
|
@@ -192,7 +192,24 @@ function stepAt(t){for(let i=0;i<STEPS.length;i++)if(t>=STEPS[i].start_s&&t<STEP
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 197 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 198 |
master.addEventListener('seeked',resync);
|
|
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
+
function setPlaying(p){
|
| 196 |
+
playing=p; playBtn.textContent=p?'❚❚':'▶︎';
|
| 197 |
+
if(p){videos.forEach(v=>v.play().catch(()=>{}));requestAnimationFrame(loop);}
|
| 198 |
+
else videos.forEach(v=>v.pause());
|
| 199 |
+
}
|
| 200 |
+
playBtn.onclick=()=>setPlaying(!playing);
|
| 201 |
+
// autoplay once every tile can render, then loop — same scheme as the other sample pages
|
| 202 |
+
let started=false, iv=setInterval(tryStart,400);
|
| 203 |
+
function tryStart(){
|
| 204 |
+
if(started||playing||!videos.length||!videos.every(v=>v.readyState>=2))return;
|
| 205 |
+
started=true; clearInterval(iv); videos.forEach(v=>v.muted=true); setPlaying(true);
|
| 206 |
+
}
|
| 207 |
+
videos.forEach(v=>{v.addEventListener('canplay',tryStart);v.addEventListener('loadeddata',tryStart);});
|
| 208 |
+
window.addEventListener('load',tryStart);
|
| 209 |
+
setTimeout(()=>clearInterval(iv),90000);
|
| 210 |
+
setInterval(()=>{
|
| 211 |
+
if(playing&&master.currentTime>=DUR-0.08){seekTo(0);videos.forEach(v=>v.play().catch(()=>{}));}
|
| 212 |
+
},200);
|
| 213 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 214 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 215 |
master.addEventListener('seeked',resync);
|
teleopSample/faberU/t2_pet_bottle_into_steel_tray/20260707052609/viewer.html
CHANGED
|
@@ -192,7 +192,24 @@ function stepAt(t){for(let i=0;i<STEPS.length;i++)if(t>=STEPS[i].start_s&&t<STEP
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 197 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 198 |
master.addEventListener('seeked',resync);
|
|
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
+
function setPlaying(p){
|
| 196 |
+
playing=p; playBtn.textContent=p?'❚❚':'▶︎';
|
| 197 |
+
if(p){videos.forEach(v=>v.play().catch(()=>{}));requestAnimationFrame(loop);}
|
| 198 |
+
else videos.forEach(v=>v.pause());
|
| 199 |
+
}
|
| 200 |
+
playBtn.onclick=()=>setPlaying(!playing);
|
| 201 |
+
// autoplay once every tile can render, then loop — same scheme as the other sample pages
|
| 202 |
+
let started=false, iv=setInterval(tryStart,400);
|
| 203 |
+
function tryStart(){
|
| 204 |
+
if(started||playing||!videos.length||!videos.every(v=>v.readyState>=2))return;
|
| 205 |
+
started=true; clearInterval(iv); videos.forEach(v=>v.muted=true); setPlaying(true);
|
| 206 |
+
}
|
| 207 |
+
videos.forEach(v=>{v.addEventListener('canplay',tryStart);v.addEventListener('loadeddata',tryStart);});
|
| 208 |
+
window.addEventListener('load',tryStart);
|
| 209 |
+
setTimeout(()=>clearInterval(iv),90000);
|
| 210 |
+
setInterval(()=>{
|
| 211 |
+
if(playing&&master.currentTime>=DUR-0.08){seekTo(0);videos.forEach(v=>v.play().catch(()=>{}));}
|
| 212 |
+
},200);
|
| 213 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 214 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 215 |
master.addEventListener('seeked',resync);
|
teleopSample/faberU/t3_juice_bottle_onto_plate/20260724050737/viewer.html
CHANGED
|
@@ -192,7 +192,24 @@ function stepAt(t){for(let i=0;i<STEPS.length;i++)if(t>=STEPS[i].start_s&&t<STEP
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 197 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 198 |
master.addEventListener('seeked',resync);
|
|
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
+
function setPlaying(p){
|
| 196 |
+
playing=p; playBtn.textContent=p?'❚❚':'▶︎';
|
| 197 |
+
if(p){videos.forEach(v=>v.play().catch(()=>{}));requestAnimationFrame(loop);}
|
| 198 |
+
else videos.forEach(v=>v.pause());
|
| 199 |
+
}
|
| 200 |
+
playBtn.onclick=()=>setPlaying(!playing);
|
| 201 |
+
// autoplay once every tile can render, then loop — same scheme as the other sample pages
|
| 202 |
+
let started=false, iv=setInterval(tryStart,400);
|
| 203 |
+
function tryStart(){
|
| 204 |
+
if(started||playing||!videos.length||!videos.every(v=>v.readyState>=2))return;
|
| 205 |
+
started=true; clearInterval(iv); videos.forEach(v=>v.muted=true); setPlaying(true);
|
| 206 |
+
}
|
| 207 |
+
videos.forEach(v=>{v.addEventListener('canplay',tryStart);v.addEventListener('loadeddata',tryStart);});
|
| 208 |
+
window.addEventListener('load',tryStart);
|
| 209 |
+
setTimeout(()=>clearInterval(iv),90000);
|
| 210 |
+
setInterval(()=>{
|
| 211 |
+
if(playing&&master.currentTime>=DUR-0.08){seekTo(0);videos.forEach(v=>v.play().catch(()=>{}));}
|
| 212 |
+
},200);
|
| 213 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 214 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 215 |
master.addEventListener('seeked',resync);
|
teleopSample/faberU/t3_juice_bottle_onto_plate/20260724050827/viewer.html
CHANGED
|
@@ -192,7 +192,24 @@ function stepAt(t){for(let i=0;i<STEPS.length;i++)if(t>=STEPS[i].start_s&&t<STEP
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 197 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 198 |
master.addEventListener('seeked',resync);
|
|
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
+
function setPlaying(p){
|
| 196 |
+
playing=p; playBtn.textContent=p?'❚❚':'▶︎';
|
| 197 |
+
if(p){videos.forEach(v=>v.play().catch(()=>{}));requestAnimationFrame(loop);}
|
| 198 |
+
else videos.forEach(v=>v.pause());
|
| 199 |
+
}
|
| 200 |
+
playBtn.onclick=()=>setPlaying(!playing);
|
| 201 |
+
// autoplay once every tile can render, then loop — same scheme as the other sample pages
|
| 202 |
+
let started=false, iv=setInterval(tryStart,400);
|
| 203 |
+
function tryStart(){
|
| 204 |
+
if(started||playing||!videos.length||!videos.every(v=>v.readyState>=2))return;
|
| 205 |
+
started=true; clearInterval(iv); videos.forEach(v=>v.muted=true); setPlaying(true);
|
| 206 |
+
}
|
| 207 |
+
videos.forEach(v=>{v.addEventListener('canplay',tryStart);v.addEventListener('loadeddata',tryStart);});
|
| 208 |
+
window.addEventListener('load',tryStart);
|
| 209 |
+
setTimeout(()=>clearInterval(iv),90000);
|
| 210 |
+
setInterval(()=>{
|
| 211 |
+
if(playing&&master.currentTime>=DUR-0.08){seekTo(0);videos.forEach(v=>v.play().catch(()=>{}));}
|
| 212 |
+
},200);
|
| 213 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 214 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 215 |
master.addEventListener('seeked',resync);
|
teleopSample/faberU/t4_juice_bottle_into_black_tray/20260728075709/viewer.html
CHANGED
|
@@ -192,7 +192,24 @@ function stepAt(t){for(let i=0;i<STEPS.length;i++)if(t>=STEPS[i].start_s&&t<STEP
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 197 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 198 |
master.addEventListener('seeked',resync);
|
|
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
+
function setPlaying(p){
|
| 196 |
+
playing=p; playBtn.textContent=p?'❚❚':'▶︎';
|
| 197 |
+
if(p){videos.forEach(v=>v.play().catch(()=>{}));requestAnimationFrame(loop);}
|
| 198 |
+
else videos.forEach(v=>v.pause());
|
| 199 |
+
}
|
| 200 |
+
playBtn.onclick=()=>setPlaying(!playing);
|
| 201 |
+
// autoplay once every tile can render, then loop — same scheme as the other sample pages
|
| 202 |
+
let started=false, iv=setInterval(tryStart,400);
|
| 203 |
+
function tryStart(){
|
| 204 |
+
if(started||playing||!videos.length||!videos.every(v=>v.readyState>=2))return;
|
| 205 |
+
started=true; clearInterval(iv); videos.forEach(v=>v.muted=true); setPlaying(true);
|
| 206 |
+
}
|
| 207 |
+
videos.forEach(v=>{v.addEventListener('canplay',tryStart);v.addEventListener('loadeddata',tryStart);});
|
| 208 |
+
window.addEventListener('load',tryStart);
|
| 209 |
+
setTimeout(()=>clearInterval(iv),90000);
|
| 210 |
+
setInterval(()=>{
|
| 211 |
+
if(playing&&master.currentTime>=DUR-0.08){seekTo(0);videos.forEach(v=>v.play().catch(()=>{}));}
|
| 212 |
+
},200);
|
| 213 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 214 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 215 |
master.addEventListener('seeked',resync);
|
teleopSample/faberU/t4_juice_bottle_into_black_tray/20260728080339/viewer.html
CHANGED
|
@@ -192,7 +192,24 @@ function stepAt(t){for(let i=0;i<STEPS.length;i++)if(t>=STEPS[i].start_s&&t<STEP
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 197 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 198 |
master.addEventListener('seeked',resync);
|
|
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
+
function setPlaying(p){
|
| 196 |
+
playing=p; playBtn.textContent=p?'❚❚':'▶︎';
|
| 197 |
+
if(p){videos.forEach(v=>v.play().catch(()=>{}));requestAnimationFrame(loop);}
|
| 198 |
+
else videos.forEach(v=>v.pause());
|
| 199 |
+
}
|
| 200 |
+
playBtn.onclick=()=>setPlaying(!playing);
|
| 201 |
+
// autoplay once every tile can render, then loop — same scheme as the other sample pages
|
| 202 |
+
let started=false, iv=setInterval(tryStart,400);
|
| 203 |
+
function tryStart(){
|
| 204 |
+
if(started||playing||!videos.length||!videos.every(v=>v.readyState>=2))return;
|
| 205 |
+
started=true; clearInterval(iv); videos.forEach(v=>v.muted=true); setPlaying(true);
|
| 206 |
+
}
|
| 207 |
+
videos.forEach(v=>{v.addEventListener('canplay',tryStart);v.addEventListener('loadeddata',tryStart);});
|
| 208 |
+
window.addEventListener('load',tryStart);
|
| 209 |
+
setTimeout(()=>clearInterval(iv),90000);
|
| 210 |
+
setInterval(()=>{
|
| 211 |
+
if(playing&&master.currentTime>=DUR-0.08){seekTo(0);videos.forEach(v=>v.play().catch(()=>{}));}
|
| 212 |
+
},200);
|
| 213 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 214 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 215 |
master.addEventListener('seeked',resync);
|
teleopSample/faberU/t6_pick_up_green_tea_bottle/20260730045050/viewer.html
CHANGED
|
@@ -192,7 +192,24 @@ function stepAt(t){for(let i=0;i<STEPS.length;i++)if(t>=STEPS[i].start_s&&t<STEP
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 197 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 198 |
master.addEventListener('seeked',resync);
|
|
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
+
function setPlaying(p){
|
| 196 |
+
playing=p; playBtn.textContent=p?'❚❚':'▶︎';
|
| 197 |
+
if(p){videos.forEach(v=>v.play().catch(()=>{}));requestAnimationFrame(loop);}
|
| 198 |
+
else videos.forEach(v=>v.pause());
|
| 199 |
+
}
|
| 200 |
+
playBtn.onclick=()=>setPlaying(!playing);
|
| 201 |
+
// autoplay once every tile can render, then loop — same scheme as the other sample pages
|
| 202 |
+
let started=false, iv=setInterval(tryStart,400);
|
| 203 |
+
function tryStart(){
|
| 204 |
+
if(started||playing||!videos.length||!videos.every(v=>v.readyState>=2))return;
|
| 205 |
+
started=true; clearInterval(iv); videos.forEach(v=>v.muted=true); setPlaying(true);
|
| 206 |
+
}
|
| 207 |
+
videos.forEach(v=>{v.addEventListener('canplay',tryStart);v.addEventListener('loadeddata',tryStart);});
|
| 208 |
+
window.addEventListener('load',tryStart);
|
| 209 |
+
setTimeout(()=>clearInterval(iv),90000);
|
| 210 |
+
setInterval(()=>{
|
| 211 |
+
if(playing&&master.currentTime>=DUR-0.08){seekTo(0);videos.forEach(v=>v.play().catch(()=>{}));}
|
| 212 |
+
},200);
|
| 213 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 214 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 215 |
master.addEventListener('seeked',resync);
|
teleopSample/faberU/t6_pick_up_green_tea_bottle/20260730045152/viewer.html
CHANGED
|
@@ -192,7 +192,24 @@ function stepAt(t){for(let i=0;i<STEPS.length;i++)if(t>=STEPS[i].start_s&&t<STEP
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 197 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 198 |
master.addEventListener('seeked',resync);
|
|
|
|
| 192 |
const playBtn=document.getElementById('play'),seek=document.getElementById('seek'),tlabel=document.getElementById('tlabel');
|
| 193 |
let playing=false;
|
| 194 |
function seekTo(t){videos.forEach(v=>{try{v.currentTime=t;}catch(e){}});}
|
| 195 |
+
function setPlaying(p){
|
| 196 |
+
playing=p; playBtn.textContent=p?'❚❚':'▶︎';
|
| 197 |
+
if(p){videos.forEach(v=>v.play().catch(()=>{}));requestAnimationFrame(loop);}
|
| 198 |
+
else videos.forEach(v=>v.pause());
|
| 199 |
+
}
|
| 200 |
+
playBtn.onclick=()=>setPlaying(!playing);
|
| 201 |
+
// autoplay once every tile can render, then loop — same scheme as the other sample pages
|
| 202 |
+
let started=false, iv=setInterval(tryStart,400);
|
| 203 |
+
function tryStart(){
|
| 204 |
+
if(started||playing||!videos.length||!videos.every(v=>v.readyState>=2))return;
|
| 205 |
+
started=true; clearInterval(iv); videos.forEach(v=>v.muted=true); setPlaying(true);
|
| 206 |
+
}
|
| 207 |
+
videos.forEach(v=>{v.addEventListener('canplay',tryStart);v.addEventListener('loadeddata',tryStart);});
|
| 208 |
+
window.addEventListener('load',tryStart);
|
| 209 |
+
setTimeout(()=>clearInterval(iv),90000);
|
| 210 |
+
setInterval(()=>{
|
| 211 |
+
if(playing&&master.currentTime>=DUR-0.08){seekTo(0);videos.forEach(v=>v.play().catch(()=>{}));}
|
| 212 |
+
},200);
|
| 213 |
seek.oninput=()=>{const t=seek.value/1000*DUR;seekTo(t);update();};
|
| 214 |
function resync(){const t=master.currentTime;videos.forEach(v=>{if(v!==master&&Math.abs(v.currentTime-t)>0.2){try{v.currentTime=t;}catch(e){}}});}
|
| 215 |
master.addEventListener('seeked',resync);
|
teleopSample/index.html
CHANGED
|
@@ -54,7 +54,7 @@ function build(){
|
|
| 54 |
leaves.forEach(m=>{
|
| 55 |
const el = document.createElement('div'); el.className = 'leaf';
|
| 56 |
el.innerHTML = `<div class="lt">${esc(m.label)}</div>
|
| 57 |
-
<div class="lm"><span class="rig">${esc(rig)}</span>${m.dur.toFixed(0)}s · ${m.spans}
|
| 58 |
el.onclick = ()=>go(m.hash);
|
| 59 |
el._hash = m.hash; el._m = m; el._cat = cat;
|
| 60 |
body.appendChild(el);
|
|
|
|
| 54 |
leaves.forEach(m=>{
|
| 55 |
const el = document.createElement('div'); el.className = 'leaf';
|
| 56 |
el.innerHTML = `<div class="lt">${esc(m.label)}</div>
|
| 57 |
+
<div class="lm"><span class="rig">${esc(rig)}</span>${m.dur.toFixed(0)}s · ${m.spans} steps</div>`;
|
| 58 |
el.onclick = ()=>go(m.hash);
|
| 59 |
el._hash = m.hash; el._m = m; el._cat = cat;
|
| 60 |
body.appendChild(el);
|
teleopSample/mem_ep0.html
CHANGED
|
@@ -54,7 +54,7 @@ header{padding:16px 22px 8px}header h1{margin:0 0 3px;font-size:21px}
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
-
<div class="spans-head">
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
@@ -242,4 +242,5 @@ CAMS.forEach(c=>{ const a=document.createElement('a'); a.className='file-chip';
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
|
|
|
| 245 |
</body></html>
|
|
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
+
<div class="spans-head">annotated steps (4) · instruction segments</div>
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
| 245 |
+
<script src="enhance.js"></script>
|
| 246 |
</body></html>
|
teleopSample/mem_ep1.html
CHANGED
|
@@ -54,7 +54,7 @@ header{padding:16px 22px 8px}header h1{margin:0 0 3px;font-size:21px}
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
-
<div class="spans-head">
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
@@ -242,4 +242,5 @@ CAMS.forEach(c=>{ const a=document.createElement('a'); a.className='file-chip';
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
|
|
|
| 245 |
</body></html>
|
|
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
+
<div class="spans-head">annotated steps (4) · instruction segments</div>
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
| 245 |
+
<script src="enhance.js"></script>
|
| 246 |
</body></html>
|
teleopSample/sort_ep0.html
CHANGED
|
@@ -54,7 +54,7 @@ header{padding:16px 22px 8px}header h1{margin:0 0 3px;font-size:21px}
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
-
<div class="spans-head">
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
@@ -242,4 +242,5 @@ CAMS.forEach(c=>{ const a=document.createElement('a'); a.className='file-chip';
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
|
|
|
| 245 |
</body></html>
|
|
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
+
<div class="spans-head">annotated steps (16) · instruction segments</div>
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
| 245 |
+
<script src="enhance.js"></script>
|
| 246 |
</body></html>
|
teleopSample/sort_ep1.html
CHANGED
|
@@ -54,7 +54,7 @@ header{padding:16px 22px 8px}header h1{margin:0 0 3px;font-size:21px}
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
-
<div class="spans-head">
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
@@ -242,4 +242,5 @@ CAMS.forEach(c=>{ const a=document.createElement('a'); a.className='file-chip';
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
|
|
|
| 245 |
</body></html>
|
|
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
+
<div class="spans-head">annotated steps (16) · instruction segments</div>
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
| 245 |
+
<script src="enhance.js"></script>
|
| 246 |
</body></html>
|
teleopSample/wipe_ep0.html
CHANGED
|
@@ -54,7 +54,7 @@ header{padding:16px 22px 8px}header h1{margin:0 0 3px;font-size:21px}
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
-
<div class="spans-head">
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
@@ -242,4 +242,5 @@ CAMS.forEach(c=>{ const a=document.createElement('a'); a.className='file-chip';
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
|
|
|
| 245 |
</body></html>
|
|
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
+
<div class="spans-head">annotated steps (3) · instruction segments</div>
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
| 245 |
+
<script src="enhance.js"></script>
|
| 246 |
</body></html>
|
teleopSample/wipe_ep1.html
CHANGED
|
@@ -54,7 +54,7 @@ header{padding:16px 22px 8px}header h1{margin:0 0 3px;font-size:21px}
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
-
<div class="spans-head">
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
@@ -242,4 +242,5 @@ CAMS.forEach(c=>{ const a=document.createElement('a'); a.className='file-chip';
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
|
|
|
| 245 |
</body></html>
|
|
|
|
| 54 |
<div class="layout">
|
| 55 |
<div class="panel camwrap"><div id="camgrid" class="camgrid"></div></div>
|
| 56 |
<div class="panel sidecol-spans">
|
| 57 |
+
<div class="spans-head">annotated steps (3) · instruction segments</div>
|
| 58 |
<div class="chips" id="chips"></div><div id="spanList"></div>
|
| 59 |
</div>
|
| 60 |
</div>
|
|
|
|
| 242 |
})();
|
| 243 |
master.addEventListener('loadedmetadata',update); window.addEventListener('load',update);
|
| 244 |
</script>
|
| 245 |
+
<script src="enhance.js"></script>
|
| 246 |
</body></html>
|