Poster frames for the walkthrough videos
The player was showing a blank white rectangle, which reads as broken. That is frame zero of each recording — the instant before the page painted. Only frame zero is white (by 0.5s the real UI is up), but frame zero is exactly what a browser picks when no poster is set. Each video now has a poster pulled from ~30% in, where the tool is in a meaningful state rather than mid-intro. Posters live beside the videos in the volume and the player derives the URL from the video's own, so adding a new walkthrough needs no page change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,10 @@
|
|||||||
|
|
||||||
var vid = document.createElement('video');
|
var vid = document.createElement('video');
|
||||||
vid.controls = true;
|
vid.controls = true;
|
||||||
|
// Poster lives beside the video (same slug, .jpg). Without it the browser
|
||||||
|
// shows frame zero, and frame zero is the instant before the page painted
|
||||||
|
// — a blank white rectangle that reads as a broken player.
|
||||||
|
vid.poster = SRC.replace(/\.mp4$/, '.jpg');
|
||||||
// Nothing is fetched until the panel is actually opened; open() then bumps
|
// Nothing is fetched until the panel is actually opened; open() then bumps
|
||||||
// this to 'metadata' so an expanded panel shows a real first frame rather
|
// this to 'metadata' so an expanded panel shows a real first frame rather
|
||||||
// than a black rectangle.
|
// than a black rectangle.
|
||||||
|
|||||||
@@ -1068,13 +1068,15 @@ async function handleApi(req,res,pathname){
|
|||||||
//
|
//
|
||||||
// Range support is not optional here: without it the browser cannot seek, so
|
// Range support is not optional here: without it the browser cannot seek, so
|
||||||
// the scrub bar looks present but does nothing.
|
// the scrub bar looks present but does nothing.
|
||||||
if((req.method==='GET'||req.method==='HEAD')&&/^\/tv\/[a-z0-9-]{1,60}\.mp4$/.test(pathname)){
|
if((req.method==='GET'||req.method==='HEAD')&&/^\/tv\/[a-z0-9-]{1,60}\.(mp4|jpg)$/.test(pathname)){
|
||||||
const slug=pathname.slice(4).replace(/\.mp4$/,'');
|
const isImg=/\.jpg$/.test(pathname);
|
||||||
const vfile=path.join(DATA_DIR,'tool-videos',slug+'.mp4');
|
const name=pathname.slice(4);
|
||||||
|
const vfile=path.join(DATA_DIR,'tool-videos',name);
|
||||||
let st=null;
|
let st=null;
|
||||||
try{ st=fs.statSync(vfile); }catch(err){ return json(res,404,{error:'No walkthrough for that tool yet.'}); }
|
try{ st=fs.statSync(vfile); }catch(err){ return json(res,404,{error:'No walkthrough for that tool yet.'}); }
|
||||||
const range=req.headers.range;
|
const range=isImg?null:req.headers.range;
|
||||||
const base={'Content-Type':'video/mp4','Accept-Ranges':'bytes','Cache-Control':'public, max-age=3600'};
|
const base={'Content-Type':isImg?'image/jpeg':'video/mp4','Accept-Ranges':isImg?'none':'bytes',
|
||||||
|
'Cache-Control':'public, max-age=3600'};
|
||||||
if(range){
|
if(range){
|
||||||
const m=/bytes=(\d*)-(\d*)/.exec(range)||[];
|
const m=/bytes=(\d*)-(\d*)/.exec(range)||[];
|
||||||
const start=m[1]?parseInt(m[1],10):0;
|
const start=m[1]?parseInt(m[1],10):0;
|
||||||
|
|||||||
Reference in New Issue
Block a user