From 0c3487fdb0e9892135eea80afc983c3432a3a4ea Mon Sep 17 00:00:00 2001 From: sravan Date: Fri, 24 Jul 2026 16:21:02 +0530 Subject: [PATCH] polish(share): picker avatars + target name + pre-warm video poster MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-ups from testing the share flow (which now works end-to-end): - Send-to picker showed only initials — now shows the real profile photo when the chat has one (matching the sidebar/forward avatars), coloured initials otherwise. - During send it only said "Uploading 60%" with no idea WHO to — now the header and progress name the target ("Sending to Manasa Rapolu · 60%"). - After sending, a video bubble sat blank (just a timestamp) for a second or two while the poster generated on first view. media.js now warms the poster thumbnail at UPLOAD (temp-then-rename), and the on-demand /thumbs handler also writes via a temp, so the two can't serve a half-written JPEG. The bubble shows its poster right away. Web + server only — live on deploy. Does NOT address the share extension failing to auto-open the app (an iOS limitation, handled next in the native build). Co-Authored-By: Claude Opus 4.8 --- server/media.js | 20 ++++++++++++++++++++ server/public/home.html | 17 ++++++++++++----- server/static.js | 8 ++++++-- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/server/media.js b/server/media.js index 4c40fcf..b1ccde3 100644 --- a/server/media.js +++ b/server/media.js @@ -151,10 +151,30 @@ function transcode(id, done) { }); } +// Poster frame, generated to a temp then renamed so a reader never sees a half-written JPEG (the /thumbs +// handler and this can both target the same file). Warming it at upload means the chat bubble shows the +// poster immediately instead of a blank tile while ffmpeg runs on the first view. +function ensureThumb(id) { + const thumb = path.join(UPLOADS_DIR, id + '.thumb.jpg'); + const src = path.join(UPLOADS_DIR, id); + if (!src.startsWith(UPLOADS_DIR)) return; + if (fs.existsSync(thumb)) return; + fs.stat(src, (e) => { + if (e) return; + const tmp = thumb + '.part'; + execFile('ffmpeg', ['-y', '-ss', '0.5', '-i', src, '-frames:v', '1', '-vf', 'scale=480:-2', '-q:v', '4', tmp], + { timeout: 15000 }, (err) => { + if (err) { try { fs.unlinkSync(tmp); } catch (_) {} return; } + try { fs.renameSync(tmp, thumb); } catch (_) { try { fs.unlinkSync(tmp); } catch (__) {} } + }); + }); +} + // Queue a freshly uploaded (or first-played) video. Cheap and idempotent: safe to call on every // /stream hit, which is also how pre-existing uploads get backfilled. function ensureWebRendition(id, mime) { if (!/^video\//.test(mime || '')) return; + ensureThumb(id); // warm the poster so the bubble isn't blank if (pending.has(id) || failed.has(id) || hasWebRendition(id)) return; pending.add(id); queue.push(id); diff --git a/server/public/home.html b/server/public/home.html index db08e02..0861912 100644 --- a/server/public/home.html +++ b/server/public/home.html @@ -1197,7 +1197,7 @@ -