Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
26 lines
3.8 KiB
JavaScript
26 lines
3.8 KiB
JavaScript
const tok=location.pathname.split("/")[2],$=(s)=>document.querySelector(s),au=$("#au");
|
||
const G={avia:"Авиа",marine:"Морские",pmr:"PMR446",lpd:"LPD433",ham:"Любители",rail:"ЖД",other:"Другое"};
|
||
const fd=(s)=>{s=Math.round(s||0);return s<60?`${s} с`:`${Math.floor(s/60)}:${String(s%60).padStart(2,"0")}`};
|
||
const esc=(s)=>String(s??"").replace(/[&<>"]/g,(c)=>({"&":"&","<":"<",">":">",'"':"""}[c]));
|
||
let items=[],cur=-1,chain=false;
|
||
function wave(cv,pk,frac){const r=devicePixelRatio||1,w=cv.clientWidth,h=cv.clientHeight;if(!w)return;cv.width=w*r;cv.height=h*r;const x=cv.getContext("2d");x.scale(r,r);
|
||
const n=Math.max(1,Math.floor(w/3)),mx=Math.max(1e-6,...pk);for(let i=0;i<n;i++){const v=pk.length?pk[Math.floor(i*pk.length/n)]/mx:.15;const bh=Math.max(2,v*(h-4));
|
||
x.fillStyle=i/n<frac?getComputedStyle(document.documentElement).getPropertyValue("--waveOn"):"#3a3a3a";x.fillRect(i*3,(h-bh)/2,2,bh)}}
|
||
function draw(){document.querySelectorAll(".row").forEach((r)=>{const i=+r.dataset.i;const f=i===cur&&au.duration?au.currentTime/au.duration:i<cur&&chain?1:0;wave(r.querySelector("canvas"),items[i].peaks||[],f);
|
||
r.classList.toggle("playing",i===cur&&!au.paused);r.querySelector(".pp").innerHTML=i===cur&&!au.paused?"❚❚":"▶"})}
|
||
function play(i,keep){if(!keep)chain=false;if(i===cur&&!au.paused){au.pause();return draw()}if(i!==cur){cur=i;au.src=`/s/${tok}/a/${i}`}au.play();draw()}
|
||
au.ontimeupdate=draw;au.onpause=draw;au.onplay=draw;au.onended=()=>{if(chain&&cur+1<items.length)play(cur+1,true);else{chain=false;draw()}};
|
||
fetch(`/s/${tok}/data`).then((r)=>r.ok?r.json():Promise.reject()).then((d)=>{items=d.items;const me=items[d.me];
|
||
document.title=`${me.channel} · радио`;$("#t").textContent=me.channel;
|
||
const dt=new Date(me.ts);$("#m").innerHTML=`<span class="badge g-${esc(me.group)}">${esc(G[me.group]||me.group)}</span><span class=mono>${me.freq_mhz.toFixed(4).replace(".",",")} МГц</span><span>${dt.toLocaleDateString("ru-RU",{day:"numeric",month:"long",year:"numeric"})}</span>`;
|
||
const tot=items.reduce((s,x)=>s+(x.duration_s||0),0),span=(new Date(items[items.length-1].ts)-new Date(items[0].ts))/1000+(items[items.length-1].duration_s||0);
|
||
if(items.length>1){$("#bar").hidden=false;$("#sum").textContent=`${items.length} передач · эфир ${fd(tot)} за ${fd(span)}`}
|
||
let h="";items.forEach((it,i)=>{if(i){const p=items[i-1],g=Math.max(0,(new Date(it.ts)-new Date(p.ts))/1000-(p.duration_s||0));h+=`<div class="gap${g>12?" miss":""}">пауза ${g.toFixed(1).replace(".",",")} с${g>12?" · сканер мог пропустить ответ":""}</div>`}
|
||
h+=`<div class="row${i===d.me?" me":""}" data-i=${i}><button class=pp aria-label="Слушать">▶</button><span class=t>${i===d.me?"<small>эта запись</small>":""}${new Date(it.ts).toLocaleTimeString("ru-RU")}</span><canvas class=wave></canvas><span class=d>${fd(it.duration_s)}</span><a class=dl href="/s/${tok}/a/${i}?dl=1" download title="Скачать" onclick="event.stopPropagation()">⬇</a></div>`});
|
||
$("#l").innerHTML=h;$("#f").textContent=items.length>1?"Разговор — передачи на одной частоте с паузами меньше 30 секунд.":"";
|
||
document.querySelectorAll(".row").forEach((r)=>r.addEventListener("click",(e)=>{const i=+r.dataset.i;const cv=r.querySelector("canvas");
|
||
if(e.target===cv&&i===cur&&au.duration){const b=cv.getBoundingClientRect();au.currentTime=(e.clientX-b.left)/b.width*au.duration;return}play(i)}));
|
||
$("#all").onclick=()=>{chain=true;cur=-1;play(0,true)};addEventListener("resize",draw);draw();
|
||
document.querySelector(".row.me").scrollIntoView({block:"center"});
|
||
}).catch(()=>{$("#t").textContent="Запись недоступна";$("#m").innerHTML="<span class=err>ссылка устарела или запись удалена</span>"});
|