"""Backend-agnostic gradio UI helpers, copied from optimized/mlx/scripts/sa3_gradio.py. Forked rather than shared: the MLX and TensorRT trees install independently, with their own requirements, and a common module would couple two deployment units that today have no reason to know about each other. Nothing in here touches a backend -- these are player/history/queue renderers and filename helpers -- so the two copies only need to be reconciled when the UI itself changes. """ from __future__ import annotations import html as html_lib import re import time import urllib.parse import wave SAMPLE_RATE = 44100 # every SA3 codec is 44.1 kHz stereo _JS_FIX_SLIDERS = ( "() => setTimeout(() => {" "document.querySelectorAll('input[type=range]').forEach(r => {" "const min = +r.min || 0, max = +r.max || 100, v = +r.value;" "if (max > min) r.style.setProperty('--range_progress'," "((v - min) / (max - min) * 100) + '%');" "});}, 150)" ) _JS_PAUSE_OTHERS = ( "if(this.isConnected){" "var t=this;var L=window._sa3All=(window._sa3All||[]);" "if(L.indexOf(t)<0)L.push(t);" "L.forEach(function(o){if(o!==t){try{o.pause()}catch(e){}}});" "document.querySelectorAll('audio').forEach(function(o){if(o!==t)o.pause();});" "window._sa3All=L.filter(function(o){return o===t||o.isConnected;});}" "else{this.pause();}") _JS_PLAYHEAD = ("var p=this.closest('.blk').querySelector('.ph');" "if(p&&this.duration)p.style.left=(this.currentTime/this.duration*100)+'%';") # Main-player position ledger (for Hotswap): every timeupdate/play/pause stamps # the current position so a freshly swapped-in element can resume exactly there. # Guards (all verified via CDP against the live page): # - isConnected: teardown fires a pause AND a final timeupdate on the removed # element — detached elements never write the ledger. # - unresumed hotswap candidates (data-hs set, hsd not yet) don't write either: # with autoplay, the NEW element's 'play' event fires BEFORE loadedmetadata # and would stamp t≈0 over the old clip's position right before the resume # handler reads it. _JS_POS_RECORD = ("if(this.isConnected&&!(this.dataset.hs==='1'&&!this.dataset.hsd))" "{window._sa3Pos={t:this.currentTime,playing:!this.paused,ts:Date.now()};}") # timeupdate variant: a PAUSED element only fires timeupdate on seeks — e.g. the # resume handler's own currentTime assignment. Stamping playing:false there # poisons the ledger for any second render of the same clip (gradio sometimes # renders a component update twice), which froze the handoff chain. While # paused, only real pause events may write. _JS_POS_RECORD_TU = ("if(this.isConnected&&!this.paused&&" "!(this.dataset.hs==='1'&&!this.dataset.hsd))" "{window._sa3Pos={t:this.currentTime,playing:true,ts:Date.now()};}") # Resilient play: Chrome's autoplay policy can reject programmatic play() once # the transient user activation from the Generate click has expired (seconds), # leaving the clip correctly positioned but paused. Try, retry shortly after, # and as a last resort arm a one-shot listener so the user's next click or # keypress anywhere resumes playback. # Every attempt re-checks isConnected: a superseded (detached) element must # never revive itself from a queued retry and fight the current player. _JS_TRY_PLAY = ( "var A=this;A.play().catch(function(){setTimeout(function(){" "if(!A.isConnected)return;" "A.play().catch(function(){console.warn('play blocked by autoplay policy — " "will resume on next interaction');" "var f=function(){if(A.isConnected)A.play();" "document.removeEventListener('pointerdown',f,true);" "document.removeEventListener('keydown',f,true);};" "document.addEventListener('pointerdown',f,true);" "document.addEventListener('keydown',f,true);});},150);});") # Hotswap resume: if the previous main audio was playing when this one arrived, # jump to its position (+ the split-second since the last stamp) and keep going; # beyond the new clip's duration -> start at zero. Guarded (hsd) so it applies # once, on whichever of loadedmetadata/canplay fires first. _JS_HOTSWAP = ("if(this.isConnected&&this.dataset.hs==='1'&&!this.dataset.hsd&&this.duration){" "this.dataset.hsd=1;var s=window._sa3Pos;" "this.dataset.dbg=s?(s.playing?'playing@'+s.t.toFixed(2):'notplaying@'+s.t.toFixed(2)):'noledger';" "if(s&&s.playing){var tt=s.t+(Date.now()-s.ts)/1000;" "this.currentTime=(tt