75 lines
8.7 KiB
JavaScript
75 lines
8.7 KiB
JavaScript
'use strict';
|
|
let playbackRange=null;
|
|
function playbackSourceHint(){renderRecordingStatus();if($('#archive-source').value==='recorder')$('#play-info').textContent='录像机原有录像待接入;查看本系统保存的录像请选择“小电脑本机录像”。';}
|
|
|
|
function renderRecordingStatus(){
|
|
const camera=state.cameras.find(c=>c.id===$('#play-camera').value),box=$('#recording-health');
|
|
box.hidden=!camera||$('#archive-source').value==='recorder';if(box.hidden)return;
|
|
const messages={recording:'正在录像 · 主码流持续保存',off:'未开启本机录像 · 实时画面接通不代表已保存录像',disabled:'录像已暂停 · 设备连接已停用',paused:'录像已暂停 · 已触发容量或磁盘保护',unavailable:'录像服务未连接 · 请联系管理员检查服务',offline:'等待摄像头接通 · 当前没有新录像写入',waiting:'已开启录像 · 等待文件写入,请稍候;若持续等待请检查存储'};
|
|
$('#recording-health-text').textContent=messages[camera.recordingState]||'正在读取录像状态';
|
|
const policy=state.storage||{};
|
|
$('#recording-policy-note').textContent=policy.retentionDays?`最长保留 ${policy.retentionDays} 天,过期自动清理;全平台录像上限 ${policy.maxGB} GB,磁盘预留 ${policy.reserveGB} GB。容量不足会提前暂停,实际可留时长取决于码率和路数。`:'录像由服务器按保留策略自动管理。';
|
|
$('#recording-start-note').textContent='本机只保存开启录像期间的画面。新录像按 5 分钟分段保存,回放查询可能稍有延迟。';
|
|
const button=$('#recording-configure');button.hidden=!isAdmin();button.dataset.editCamera=camera.id;
|
|
}
|
|
|
|
function localDateTime(value){const d=new Date(value);return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}T${d.toTimeString().slice(0,8)}`;}
|
|
function resetPlayback(){
|
|
playbackGeneration++;playbackRange=null;const v=$('#play-video');v.pause();v.removeAttribute('src');v.load();
|
|
$('#play-empty').hidden=false;$('#download').hidden=true;$('#clip-download').hidden=true;$('#clip-status').textContent='';$('#clip-form').reset();
|
|
spans=[];queryCamera='';queryStart=null;queryEnd=null;$('#play-info').textContent='选择日期,查看实际录像时段';$('#play-position').textContent='';drawTimeline();$('#recording-list').innerHTML='';
|
|
}
|
|
async function queryRecordings(){
|
|
resetPlayback();const generation=playbackGeneration,id=$('#play-camera').value,date=$('#play-date').value;
|
|
if(!id||!date){toast('请选择摄像头和日期');return;}
|
|
if($('#archive-source').value==='recorder'){$('#play-info').textContent='录像机历史回放接口待接入。现有录像仍可在录像机或原厂 App 中查看。';return;}
|
|
queryStart=new Date(date+'T00:00:00');queryEnd=new Date(queryStart);queryEnd.setDate(queryEnd.getDate()+1);queryCamera=id;
|
|
$('#play-info').textContent='正在查找录像…';
|
|
try{const d=await api('/api/recordings?'+new URLSearchParams({camera:id,start:queryStart.toISOString(),end:queryEnd.toISOString()}));
|
|
if(generation!==playbackGeneration||!loggedIn)return;
|
|
spans=RecordingRanges.normalize(d.items,queryStart,queryEnd);
|
|
$('#play-info').textContent=spans.length?`找到 ${spans.length} 段连续录像`:'该日期暂无可回放录像';drawTimeline();
|
|
$('#recording-list').innerHTML=spans.map(s=>`<button class="recording-item" data-span="${new Date(s.start).toISOString()}">${new Date(s.start).toLocaleTimeString()} — ${new Date(s.end).toLocaleTimeString()}<small>${((s.end-s.start)/60000).toFixed(1)} 分钟 · 点击播放</small></button>`).join('');
|
|
if(spans.length)playAt(new Date(spans[0].start));
|
|
}catch(e){if(!e.stale&&generation===playbackGeneration)$('#play-info').textContent=e.message;}
|
|
}
|
|
function drawTimeline(){
|
|
const c=$('#timeline'),w=c.clientWidth;if(!w)return;const scale=window.devicePixelRatio||1;c.width=w*scale;c.height=76*scale;const ctx=c.getContext('2d');ctx.scale(scale,scale);const theme=getComputedStyle(document.documentElement);ctx.fillStyle=theme.getPropertyValue('--timeline-paper').trim()||'#ede8dd';ctx.fillRect(0,0,w,76);ctx.strokeStyle=theme.getPropertyValue('--timeline-grid').trim()||'#d3ccbc';
|
|
for(let i=0;i<=24;i++){const x=w*i/24;ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,i%6===0?76:12);ctx.stroke();}
|
|
if(!queryStart||!queryEnd)return;ctx.fillStyle=theme.getPropertyValue('--timeline-range').trim()||'#4c7761';const total=queryEnd-queryStart;
|
|
for(const s of spans){const a=(s.start-queryStart)/total,b=(s.end-queryStart)/total;if(b>a)ctx.fillRect(a*w,25,Math.max(2,(b-a)*w),25);}
|
|
}
|
|
function playbackParams(range){return new URLSearchParams({camera:queryCamera,start:new Date(range.start).toISOString(),duration:String((range.end-range.start)/1000)});}
|
|
function playAt(date,preserveClip=false){
|
|
const range=RecordingRanges.at(spans,date);if(!range){toast('这个时间没有录像,请选择绿色时段');return;}
|
|
playbackRange=range;const params=playbackParams(range),v=$('#play-video');v.src='/media/playback?'+params;v.play().catch(()=>{});
|
|
$('#play-info').textContent='回放起点 '+new Date(range.start).toLocaleString()+' · 每次最多一小时,结束后按续播选项处理';$('#play-empty').hidden=true;
|
|
$('#download').href='/media/playback?'+params+'&download=1';$('#download').setAttribute('download','recording.mp4');$('#download').hidden=false;
|
|
if(!preserveClip){$('#clip-start').value=localDateTime(range.start);$('#clip-end').value=localDateTime(range.end);$('#clip-download').hidden=true;$('#clip-status').textContent='可修改起止时间后生成下载链接';}
|
|
}
|
|
function continuePlayback(){
|
|
const v=$('#play-video');if(!loggedIn||activePage!=='playback'||!playbackRange||!v.ended)return;
|
|
if(v.currentTime+2<(playbackRange.end-playbackRange.start)/1000){$('#play-info').textContent='录像提前结束,已停止续播;请核对该片段或重新查询';return;}
|
|
if(!$('#play-autonext').checked){$('#play-info').textContent='本段播放结束,自动续播已关闭';return;}
|
|
const end=playbackRange.end,next=RecordingRanges.next(spans,end);
|
|
if(!next){$('#play-info').textContent='所选日期的已查询录像播放完毕';return;}
|
|
playAt(new Date(next.start),true);if(next.start>end)$('#play-info').textContent=`已跳过无录像时段,续播 ${new Date(next.start).toLocaleString()}`;
|
|
}
|
|
function makeClip(){
|
|
$('#clip-download').hidden=true;
|
|
try{if(!queryCamera)throw Error('请先查询录像');const range=RecordingRanges.clip(spans,new Date($('#clip-start').value),new Date($('#clip-end').value));
|
|
const a=$('#clip-download');a.href='/media/playback?'+playbackParams(range)+'&download=1';a.setAttribute('download','recording.mp4');a.hidden=false;$('#clip-status').textContent=`已选 ${((range.end-range.start)/60e3).toFixed(2)} 分钟,下载保留原始编码。`;
|
|
}catch(e){$('#clip-status').textContent=e.message;}
|
|
}
|
|
$('#query-recordings').onclick=queryRecordings;
|
|
$('#play-camera').onchange=()=>{resetPlayback();const c=state.cameras.find(c=>c.id===$('#play-camera').value);$('#archive-source').value=c?.archiveSource||'local';playbackSourceHint();};$('#archive-source').onchange=()=>{resetPlayback();playbackSourceHint();};$('#play-date').onchange=resetPlayback;
|
|
$('#seek-button').onclick=()=>{if(!queryCamera){toast('请先查询录像');return;}playAt(new Date($('#play-date').value+'T'+$('#seek-time').value));};
|
|
$('#timeline').onclick=e=>{if(!queryStart)return;const r=e.currentTarget.getBoundingClientRect();playAt(new Date(+queryStart+(queryEnd-queryStart)*(e.clientX-r.left)/r.width));};
|
|
$('#play-video').onerror=()=>{if($('#play-video').getAttribute('src'))$('#play-info').textContent='回放失败:请稍后重试,或下载原始片段;服务器最多同时转换两个回放。';};
|
|
$('#play-video').onplaying=()=>{$('#play-empty').hidden=true;};$('#play-video').onended=continuePlayback;
|
|
$('#play-video').ontimeupdate=()=>{if(playbackRange)$('#play-position').textContent='录像时间 '+new Date(Math.min(playbackRange.end,playbackRange.start+$('#play-video').currentTime*1000)).toLocaleString();};
|
|
$('#clip-form').onsubmit=e=>{e.preventDefault();makeClip();};
|
|
for(const id of ['clip-start','clip-end'])$('#'+id).oninput=()=>{$('#clip-download').hidden=true;$('#clip-status').textContent='起止时间已修改,请重新生成链接';};
|
|
$$('[data-clip-mark]').forEach(b=>b.onclick=()=>{if(!playbackRange||!$('#play-video').videoWidth){toast('请先播放录像');return;}$('#clip-'+b.dataset.clipMark).value=localDateTime(Math.min(playbackRange.end,playbackRange.start+$('#play-video').currentTime*1000));$('#clip-download').hidden=true;$('#clip-status').textContent='已使用当前播放位置,请重新生成链接';});
|
|
$('#play-date').value=localDateTime(Date.now()).slice(0,10);window.addEventListener('resize',drawTimeline);
|