99 lines
6.1 KiB
JavaScript
99 lines
6.1 KiB
JavaScript
(function() {
|
|
'use strict';
|
|
const video=document.getElementById('cameraVideo'), canvas=document.getElementById('trackingOverlay');
|
|
const ctx=canvas.getContext('2d'), label=document.getElementById('trackingEstado');
|
|
const R=window.Rastreamento;
|
|
const fresh=new R.FreshTarget();
|
|
const connections=[[0,1],[1,2],[2,3],[3,4],[0,5],[5,6],[6,7],[7,8],[5,9],[9,10],[10,11],[11,12],[9,13],[13,14],[14,15],[15,16],[13,17],[0,17],[17,18],[18,19],[19,20]];
|
|
let worker=null, ready=false, pending=false, generation=0, active=false, config={}, session=null;
|
|
let result=null, target=null, at=0, interval=50, nextFrame=0, lastVideo=-1, samples=0, retryAt=0, previousSample=0;
|
|
const scratch=document.createElement('canvas'), sc=scratch.getContext('2d');
|
|
function clear() { ctx.clearRect(0,0,canvas.width,canvas.height); }
|
|
function stop(text) {
|
|
if (active && window.CarinhaVideo) window.CarinhaVideo.rastrear(null);
|
|
active=false; generation++; if(worker) worker.terminate(); worker=null;
|
|
pending=false; ready=false; result=null; target=null; lastVideo=-1; fresh.clear(); previousSample=0; clear(); label.textContent=text;
|
|
}
|
|
function draw() {
|
|
const rect=canvas.parentElement.getBoundingClientRect();
|
|
if(canvas.width!==Math.round(rect.width) || canvas.height!==Math.round(rect.height)) {
|
|
canvas.width=Math.round(rect.width); canvas.height=Math.round(rect.height);
|
|
}
|
|
clear(); canvas.hidden=!config.mostrarRastreamento;
|
|
if(canvas.hidden || !result || performance.now()-at>350 || !video.videoWidth) return;
|
|
const fit=R.fit(canvas.width,canvas.height,video.videoWidth,video.videoHeight);
|
|
const point=p=>R.point(p,fit);
|
|
ctx.lineWidth=2; ctx.font='13px Segoe UI';
|
|
for(const f of result.faces) {
|
|
const p=point(f); ctx.strokeStyle='#5cd6ff'; ctx.fillStyle='#5cd6ff';
|
|
ctx.strokeRect(p.x,p.y,f.w*fit.w,f.h*fit.h);
|
|
ctx.fillText('Rosto '+Math.round(f.score*100)+'%',p.x,Math.max(15,p.y-6));
|
|
for(const q of f.pontos || []) { const v=point(q); ctx.beginPath();ctx.arc(v.x,v.y,3,0,Math.PI*2);ctx.fill(); }
|
|
}
|
|
for(const h of result.hands) {
|
|
ctx.strokeStyle='#78ffa0'; ctx.fillStyle='#78ffa0';
|
|
for(const [a,b] of connections) { const p=point(h[a]),q=point(h[b]); ctx.beginPath();ctx.moveTo(p.x,p.y);ctx.lineTo(q.x,q.y);ctx.stroke(); }
|
|
for(const q of h) { const p=point(q);ctx.beginPath();ctx.arc(p.x,p.y,3,0,Math.PI*2);ctx.fill(); }
|
|
const p=point(h[0]);ctx.fillText('Mão',p.x+6,p.y+16);
|
|
}
|
|
if(target) { const p=point(target);ctx.strokeStyle='#ffd860';ctx.beginPath();ctx.arc(p.x,p.y,12,0,Math.PI*2);ctx.moveTo(p.x-18,p.y);ctx.lineTo(p.x+18,p.y);ctx.moveTo(p.x,p.y-18);ctx.lineTo(p.x,p.y+18);ctx.stroke(); }
|
|
}
|
|
function start() {
|
|
active=true; const gen=++generation; worker=new Worker('/rastreador-worker.js');
|
|
label.textContent='Carregando rastreadores locais…';
|
|
worker.onerror=e=>{ retryAt=Date.now()+10000; stop('Falha no rastreador: '+e.message); };
|
|
worker.onmessage=({data})=>{
|
|
if(gen!==generation) return;
|
|
pending=false;
|
|
if(data.tipo==='erro') { retryAt=Date.now()+10000; stop('Rastreamento indisponível: '+data.erro); return; }
|
|
ready=true;
|
|
if(data.tipo==='pronto') { label.textContent='Rastreadores prontos. Aguardando imagem…'; return; }
|
|
result=data; at=data.timestamp; samples++;
|
|
const prior=target;
|
|
target=R.target(data.faces,data.hands,prior);
|
|
if(target) target.atividade=!!prior && target.tipo===prior.tipo && Math.hypot(target.x-prior.x,target.y-prior.y)>.008;
|
|
if(target) fresh.accept(target,at); else fresh.clear();
|
|
interval=Math.max(50,Math.min(200,data.ms*1.2));
|
|
const hz=previousSample?Math.round(1000/(data.timestamp-previousSample)):0; previousSample=data.timestamp;
|
|
label.textContent=data.faces.length+' rosto(s) · '+data.hands.length+' mão(s) · '+hz+' análises/s · '+Math.round(data.ms)+' ms' +
|
|
(target?' · alvo: '+(target.tipo==='mao'?'mão':'rosto'):' · procurando…');
|
|
if(config.seguirRastreamento && window.CarinhaVideo) window.CarinhaVideo.rastrear(target);
|
|
draw();
|
|
};
|
|
pending=true; worker.postMessage({config:{rosto:config.rastrearRosto,maos:config.rastrearMaos}});
|
|
}
|
|
async function frame(now) {
|
|
if(!active || !ready || pending || now<nextFrame || video.readyState<2 || video.hidden || video.currentTime===lastVideo) return;
|
|
pending=true; nextFrame=now+interval; lastVideo=video.currentTime;
|
|
const gen=generation;
|
|
try {
|
|
const scale=Math.min(1,640/video.videoWidth);scratch.width=Math.round(video.videoWidth*scale);scratch.height=Math.round(video.videoHeight*scale);
|
|
sc.drawImage(video,0,0,scratch.width,scratch.height);
|
|
const bitmap=await createImageBitmap(scratch);
|
|
if(gen!==generation) { bitmap.close(); return; }
|
|
worker.postMessage({bitmap,timestamp:now,config:{rosto:config.rastrearRosto,maos:config.rastrearMaos}},[bitmap]);
|
|
} catch(e) { if(gen===generation) stop('Falha ao analisar vídeo: '+e.message); }
|
|
}
|
|
setInterval(()=>{const now=performance.now();frame(now);
|
|
if(fresh.value && !fresh.get(now)) { fresh.clear(); target=null; if(config.seguirRastreamento && window.CarinhaVideo) window.CarinhaVideo.rastrear(null); }
|
|
if(result && now-at>350) { result=null;target=null;clear(); }
|
|
},20);
|
|
window.addEventListener('resize',draw);
|
|
window.CarinhaRastreamento={
|
|
atualizar(status,c) {
|
|
const changed=config.rastrearRosto!==c.rastrearRosto || config.rastrearMaos!==c.rastrearMaos;
|
|
if(config.seguirRastreamento && !c.seguirRastreamento && window.CarinhaVideo) window.CarinhaVideo.rastrear(null);
|
|
config=c;
|
|
const allowed=status.online && c.preview && c.sensorCamera!==false && (!(status.estado || {}).dormindo || c.cameraSono);
|
|
if(!allowed || (!c.rastrearRosto && !c.rastrearMaos)) {
|
|
stop(!allowed?'Ligue o vídeo para rastrear no PC.':'Rastreamento desligado.'); return;
|
|
}
|
|
if(changed && active) stop('Atualizando rastreadores…');
|
|
if(!active && Date.now()>=retryAt) start(); draw();
|
|
},
|
|
parar() { stop('Vídeo desligado.'); },
|
|
estado() { return {active,ready,pending,samples,result,target}; }
|
|
};
|
|
window.addEventListener('pagehide',()=>stop('Painel fechado.'));
|
|
})();
|