diff --git a/public/tool-video.js b/public/tool-video.js index 198eb59..03f11bf 100644 --- a/public/tool-video.js +++ b/public/tool-video.js @@ -42,6 +42,10 @@ var vid = document.createElement('video'); 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 // this to 'metadata' so an expanded panel shows a real first frame rather // than a black rectangle. diff --git a/server.js b/server.js index bf891c2..84ea477 100644 --- a/server.js +++ b/server.js @@ -1068,13 +1068,15 @@ async function handleApi(req,res,pathname){ // // Range support is not optional here: without it the browser cannot seek, so // the scrub bar looks present but does nothing. - if((req.method==='GET'||req.method==='HEAD')&&/^\/tv\/[a-z0-9-]{1,60}\.mp4$/.test(pathname)){ - const slug=pathname.slice(4).replace(/\.mp4$/,''); - const vfile=path.join(DATA_DIR,'tool-videos',slug+'.mp4'); + if((req.method==='GET'||req.method==='HEAD')&&/^\/tv\/[a-z0-9-]{1,60}\.(mp4|jpg)$/.test(pathname)){ + const isImg=/\.jpg$/.test(pathname); + const name=pathname.slice(4); + const vfile=path.join(DATA_DIR,'tool-videos',name); let st=null; try{ st=fs.statSync(vfile); }catch(err){ return json(res,404,{error:'No walkthrough for that tool yet.'}); } - const range=req.headers.range; - const base={'Content-Type':'video/mp4','Accept-Ranges':'bytes','Cache-Control':'public, max-age=3600'}; + const range=isImg?null:req.headers.range; + const base={'Content-Type':isImg?'image/jpeg':'video/mp4','Accept-Ranges':isImg?'none':'bytes', + 'Cache-Control':'public, max-age=3600'}; if(range){ const m=/bytes=(\d*)-(\d*)/.exec(range)||[]; const start=m[1]?parseInt(m[1],10):0;