fix(android): transmissao de video nao trava quando o servidor reinicia

Se o servidor caia no meio da transmissao, o app tentava reabrir a camera
ainda presa pelo sensor de movimento; o getUserMedia ficava esperando para
sempre e a fila de pedidos do painel travava (video "desligado" ate
reiniciar o app). Agora abrir a camera tem prazo de 8 s, cada pedido do
painel tem prazo de 12 s e uma camera encerrada de repente desfaz a sessao
para a proxima mensagem reabrir. Validado no S10: servidor reiniciado com
video ativo volta sozinho em ~8 s. APK 0.8.2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-06 03:10:39 -03:00
co-authored by Claude Opus 5.5
parent e0dd03411d
commit a67c5ea9af
2 changed files with 16 additions and 5 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="br.carinha.rosto" package="br.carinha.rosto"
android:versionCode="9" android:versionCode="10"
android:versionName="0.8.1"> android:versionName="0.8.2">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" /> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
+14 -3
View File
@@ -12,8 +12,13 @@ let config = {}, signature = '', movementEnabled = true, aviso = '', capacidades
let allowed = true; let allowed = true;
let lastMotionFrame = -1; let lastMotionFrame = -1;
function chave(c) { return JSON.stringify([c.cameraLado || 'user', c.videoResolucao || 'auto', c.videoFPS || 0]); } function chave(c) { return JSON.stringify([c.cameraLado || 'user', c.videoResolucao || 'auto', c.videoFPS || 0]); }
/* Nada pode esperar para sempre: se a câmera estiver presa (ex.: servidor caiu no meio e o
sensor de movimento ainda a segura), desiste e a próxima mensagem do painel tenta de novo. */
function comPrazo(promessa, ms, oque) {
return Promise.race([promessa, new Promise((_, rej) => setTimeout(() => rej(new Error(oque + ' demorou mais de ' + ms / 1000 + ' s')), ms))]);
}
async function capturar(c) { async function capturar(c) {
const media = await navigator.mediaDevices.getUserMedia({audio:false,video:{facingMode:{ideal:c.cameraLado || 'user'},width:{ideal:640},height:{ideal:360},frameRate:{ideal:30}}}); const media = await comPrazo(navigator.mediaDevices.getUserMedia({audio:false,video:{facingMode:{ideal:c.cameraLado || 'user'},width:{ideal:640},height:{ideal:360},frameRate:{ideal:30}}}), 8000, 'Abrir a câmera');
const track = media.getVideoTracks()[0]; const track = media.getVideoTracks()[0];
const facing = track.getSettings().facingMode; const facing = track.getSettings().facingMode;
if (facing && facing !== (c.cameraLado || 'user')) { if (facing && facing !== (c.cameraLado || 'user')) {
@@ -74,6 +79,12 @@ async function iniciar(id) {
const media = await capturar(config); signature = chave(config); const media = await capturar(config); signature = chave(config);
if (!wanted || captureGeneration !== generation) { media.getTracks().forEach(t => t.stop()); return; } if (!wanted || captureGeneration !== generation) { media.getTracks().forEach(t => t.stop()); return; }
stream = media; video.srcObject = stream; await video.play(); stream = media; video.srcObject = stream; await video.play();
// câmera fechada de repente (outro uso, erro do sistema): desfaz tudo; o próximo pacote reabre
stream.getVideoTracks()[0].onended = () => {
if (captureGeneration !== generation) return;
console.log('RTC câmera encerrada; recomeçando no próximo pedido');
parar(); sid = null;
};
pc = new RTCPeerConnection({iceServers:[]}); pc = new RTCPeerConnection({iceServers:[]});
sender = pc.addTrack(stream.getVideoTracks()[0], stream); sender = pc.addTrack(stream.getVideoTracks()[0], stream);
const mine = pc; const mine = pc;
@@ -133,11 +144,11 @@ async function receber(packet) {
} }
window.rtcReceber = packet => { window.rtcReceber = packet => {
allowed = true; allowed = true;
tasks = tasks.then(() => allowed ? receber(packet) : undefined).catch(e => { tasks = tasks.then(() => allowed ? comPrazo(receber(packet), 12000, 'Pedido de vídeo') : undefined).catch(e => {
console.log('RTC erro=' + e.name + ': ' + e.message); console.log('RTC erro=' + e.name + ': ' + e.message);
sinal({tipo:'erro',erro:e.name + ': ' + e.message}); sinal({tipo:'erro',erro:e.name + ': ' + e.message});
estado({tipo:'webrtc',estado:'erro',erro:e.message}); estado({tipo:'webrtc',estado:'erro',erro:e.message});
parar(); parar(); sid = null;
}); });
}; };
window.rtcParar = () => { allowed = false; parar(); sid = null; }; window.rtcParar = () => { allowed = false; parar(); sid = null; };