perf(video): cache attachment auth so Range playback stops stuttering; strict on-demand

Root cause of a downloaded/streaming video buffering repeatedly: every /files Range request
(a playing video fires dozens) re-ran the full attachment authorization, which scans the
messages table by attachment_id (un-indexed) — a per-chunk table scan = stutter. Now the
auth decision is cached per user+attachment for 60s (module-level, bounded), so range
requests after the first are ~free.

Also: preload='none' (nothing about a video downloads until the user taps play — only the
small poster loads), per 'no auto-download'. And the buffering spinner no longer hides on
canplay/loadeddata (they fire mid-buffer), so it reliably spins whenever it's buffering.
build batch159.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 22:27:37 +05:30
parent b7cd6df625
commit 2127397408
2 changed files with 31 additions and 17 deletions
+3 -3
View File
@@ -1165,7 +1165,7 @@
</head>
<body>
<script src="/icons.js?v=6"></script>
<script>window.__BUILD='2026-07-22-batch158';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
<script>window.__BUILD='2026-07-22-batch159';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
// Emoji are rendered with the OS's own (colour) emoji font — instant, zero network.
//
// We used to run Twemoji over every emoji, which swapped each one for an <img> pulled INDIVIDUALLY from
@@ -2013,7 +2013,7 @@ function bubbleHTML(m){
const A = m.attachment;
const att = A ? (
A.isImage ? '<img class="att-img" src="/files/'+pEsc(A.id)+'" data-img="/files/'+pEsc(A.id)+'" alt="'+pEsc(A.name)+'" title="Click to view">'
: A.isVideo ? '<div class="att-vid"><video class="att-vid-v" src="/files/'+pEsc(A.id)+'#t=0.1" poster="/thumbs/'+pEsc(A.id)+'" controls playsinline preload="metadata" title="'+pEsc(A.name)+'"></video><span class="att-vid-spin" aria-hidden="true"></span></div>'
: A.isVideo ? '<div class="att-vid"><video class="att-vid-v" src="/files/'+pEsc(A.id)+'#t=0.1" poster="/thumbs/'+pEsc(A.id)+'" controls playsinline preload="none" title="'+pEsc(A.name)+'"></video><span class="att-vid-spin" aria-hidden="true"></span></div>'
: '<a class="att-file" href="/files/'+pEsc(A.id)+'" download="'+pEsc(A.name)+'">'+ic('file',15)+' <span>'+pEsc(A.name)+'</span> <span class="att-sz">'+fmtSize(A.size)+'</span></a>') : '';
const mentionsMe=convoIsGroup && !mine && Array.isArray(m.mentions) && (m.mentions.includes(ME.id)||m.mentions.includes('everyone'));
// DM ticks: sent (1 grey) → delivered (2 grey) → read (2 yellow).
@@ -5047,7 +5047,7 @@ window.addEventListener('popstate', ()=>{ if(bzcBack()){ try{ history.pushState(
(function(){
const set=(on)=>(e)=>{ const v=e.target; if(v && v.classList && v.classList.contains('att-vid-v')){ const w=v.parentElement; if(w && w.classList.contains('att-vid')) w.classList.toggle('buffering', on); } };
['waiting','seeking','stalled'].forEach(ev=>document.addEventListener(ev, set(true), true));
['playing','canplay','seeked','pause','ended','error','loadeddata'].forEach(ev=>document.addEventListener(ev, set(false), true));
['playing','seeked','pause','ended','error'].forEach(ev=>document.addEventListener(ev, set(false), true)); // NOT canplay/loadeddata — they fire mid-buffer and would hide the spinner too early
})();
// #9: swipe a message bubble to the right to reply to it (mobile), like WhatsApp/Teams. The bubble
// follows the finger a little; releasing past the threshold opens the reply composer for that message.