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 <noreply@anthropic.com>
THE ANSWER to "why does an already-downloaded video still buffer?" — it was never
the download, and it was not the server. Probing the real uploads on the box:
d0e49e58… 1920x1080 19.4 Mbps 75 MB / 31 s
ad929d0b… 1920x1080 19.0 Mbps 27 MB / 11 s
9f4e0865… 720x1584 3.6 Mbps 14 MB / 31 s
To play a 19 Mbps file the client has to SUSTAIN a 19 Mbps download for the whole
clip. No mobile link does, so the <video> buffer drains every few seconds: buffers,
plays, buffers, plays. Server-side disk read was instant and load was 1.7 on 20
cores throughout — the bottleneck is the media itself, not the delivery path.
Second, independent defect: phone MP4s store `moov` AFTER `mdat` (verified on two
uploads), so the player must fetch the file's tail before it can start at all.
Fix — keep the original bytes untouched (that is what the download button serves,
full quality) and build <id>.web.mp4 beside it: longest side capped at 1280,
~2.5 Mbps ceiling, +faststart. Measured on the 19 Mbps file:
27.3 MB @ 19.0 Mbps -> 2.55 MB @ 1.78 Mbps (10.7x less bandwidth)
transcode took 2.4 s for an 11.5 s clip
- server/media.js (new): probe, decide, 2-at-a-time background queue. Already
light + correctly sized + faststart => no rendition at all. Light but wrong atom
order => remux -c copy (seconds, no re-encode). Otherwise re-encode. A rendition
that lands bigger than the original is discarded. MP4 box-walker for the
faststart test is unit-checked against known fast/slow files, both directions.
- /stream/<id> serves the rendition, falling back to the original while it is still
transcoding, so a video is never unplayable. /files/<id> is unchanged and still
serves the pristine original for download.
- Renditions are queued at upload, and backfilled 15 s after boot for the videos
that predate this. Range serving is now one shared helper for both routes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>