{"id":425,"date":"2025-10-31T20:18:44","date_gmt":"2025-10-31T20:18:44","guid":{"rendered":"https:\/\/gltecbrasil.com.br\/?page_id=425"},"modified":"2025-10-31T20:45:35","modified_gmt":"2025-10-31T20:45:35","slug":"codec-de-video","status":"publish","type":"page","link":"https:\/\/gltecbrasil.com.br\/index.php\/codec-de-video\/","title":{"rendered":"CODEC  DE VIDEO"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"425\" class=\"elementor elementor-425\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6e38585 e-flex e-con-boxed e-con e-parent\" data-id=\"6e38585\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2aec177 elementor-widget elementor-widget-html\" data-id=\"2aec177\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!doctype html>\n<html lang=\"pt-br\">\n<head>\n<meta charset=\"utf-8\"\/>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"\/>\n<title>GLTEC \u2014 Codec Web (\u00c1udio + V\u00eddeo)<\/title>\n<link rel=\"icon\" href=\"https:\/\/gltecbrasil.com.br\/wp-content\/uploads\/2025\/08\/GLTECK-_LOGO.png\">\n<style>\n  :root{\n    --bg:#0b1b2a;--bg2:#0c2234;--panel:#0f2538;--panel2:#10283c;--ink:#e7f2ff;--muted:#9fb6c9;\n    --stroke:rgba(255,255,255,.08);--brand:#00A3FF;--ok:#19d3a1;--warn:#f4b942;--err:#ff6b6b;--accent:#3bd1ff\n  }\n  *{box-sizing:border-box} html,body{height:100%}\n  body{\n    margin:0;background:linear-gradient(180deg,var(--bg),var(--bg2)) fixed;color:var(--ink);\n    font-family:Inter,system-ui,Segoe UI,Roboto,Arial,sans-serif;\n    scroll-padding-top:24px;\n  }\n  .wrap{max-width:1100px;margin:0 auto;padding:18px}\n\n  \/* ===== macOS window bar ===== *\/\n  .macbar-wrap{position:sticky; top:14px; z-index:1000; padding:0 18px;}\n  .macbar{\n    display:flex; align-items:center; gap:12px; max-width:1100px; margin:0 auto;\n    background:rgba(255,255,255,.04); border:1px solid var(--stroke); border-radius:12px;\n    padding:10px 14px; backdrop-filter:blur(6px);\n    box-shadow:0 8px 24px rgba(0,0,0,.2), inset 0 1px 0 rgba(255,255,255,.05);\n  }\n  .mac-dots{display:flex; gap:8px}\n  .mac-dot{width:10px; height:10px; border-radius:50%}\n  .mac-dot.red{background:#ff5f56}.mac-dot.yellow{background:#ffbd2e}.mac-dot.green{background:#27c93f}\n  .mac-title{font-weight:800}\n  .mac-spacer{flex:1}\n  .mac-credit{color:var(--muted); font-size:12px}\n  .mac-chip{\n    margin-left:6px;font-size:12px;font-weight:800;padding:3px 8px;border-radius:999px;\n    border:1px solid var(--stroke);background:rgba(255,255,255,.05);color:#dfefff\n  }\n  .mac-chip.on{background:rgba(21,217,162,.12); color:var(--ok)}\n\n  \/* ===== toolbar ===== *\/\n  .toolbar{margin-top:14px;display:grid;grid-template-columns:1.2fr 1fr 1fr 1fr 1fr auto;gap:10px}\n  label{font-size:12px;color:var(--muted)}\n  .inp,.sel{background:var(--panel2);color:var(--ink);border:1px solid var(--stroke);border-radius:10px;padding:8px 10px;width:100%}\n\n  .actions{display:flex; gap:10px; align-items:end}\n  .iconbtn{\n    display:inline-flex; align-items:center; justify-content:center;\n    height:44px; min-width:44px; padding:0 14px; border-radius:12px; border:1px solid var(--stroke);\n    background:rgba(255,255,255,.06); color:var(--ink); cursor:pointer;\n    transition:transform .06s ease, background .15s ease, box-shadow .15s ease;\n    box-shadow:0 6px 14px rgba(0,0,0,.18); font-weight:800\n  }\n  .iconbtn svg{width:22px; height:22px; display:block}\n  .iconbtn:hover{background:rgba(255,255,255,.10)}\n  .iconbtn:active{transform:translateY(1px)}\n  .iconbtn.primary{background:var(--brand); color:#001422}\n  .iconbtn.primary:hover{filter:brightness(1.05)}\n  .iconbtn.neutral{background:rgba(255,255,255,.06)}\n  .iconbtn.neutral:hover{background:rgba(255,255,255,.10)}\n  .visually-hidden{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n\n  \/* ===== cards ===== *\/\n  .maingrid{margin-top:14px;display:grid;grid-template-columns:1fr 1fr;gap:12px}\n  .card{background:var(--panel);border:1px solid var(--stroke);border-radius:14px;padding:12px;box-shadow:0 12px 24px rgba(0,0,0,.18)}\n  .hdr{display:flex;align-items:center;gap:10px}\n  .dot{width:10px;height:10px;border-radius:50%;background:#28d559}\n  .name{font-weight:800}\n  .mutegroup{margin-left:auto;display:flex;gap:8px}\n  .btnsm{border:1px solid var(--stroke);background:rgba(255,255,255,.06);color:var(--ink);padding:6px 10px;border-radius:10px;font-weight:700;cursor:pointer}\n\n  .row{display:flex;gap:12px;align-items:center;margin-top:10px}\n  .scope{\n    flex:1;height:160px;border-radius:12px;border:1px solid var(--stroke);\n    background:radial-gradient(1000px 300px at 50% 10%,rgba(18,110,168,.18),transparent),\n               linear-gradient(180deg,#0a2133,#081c2c)\n  }\n\n  \/* VU *\/\n  .vuPair{display:flex;gap:10px;align-items:flex-end;margin-right:2px}\n  .vuCol{display:flex;flex-direction:column;align-items:center;gap:6px}\n  .vuLabel{font-size:12px;color:var(--muted)}\n  .vuBar{width:12px;height:160px;border-radius:8px;background:#0a1f28;border:1px solid var(--stroke);overflow:hidden;position:relative}\n  .vuFill{position:absolute;bottom:0;left:0;right:0;height:0%;background:linear-gradient(180deg,var(--ok),var(--warn),var(--err))}\n\n  \/* v\u00eddeo frames *\/\n  .vframe{flex:1; height:260px; border-radius:12px; border:1px solid var(--stroke); overflow:hidden; background:#061728; position:relative}\n  video{width:100%; height:100%; object-fit:cover; display:block; background:#061728}\n  .vtag{position:absolute; left:8px; top:8px; font-size:12px; font-weight:800; background:rgba(0,0,0,.35); border:1px solid var(--stroke); padding:4px 8px; border-radius:8px}\n\n  .groups{display:grid;grid-template-columns:1.1fr 1fr 1fr;gap:10px;margin-top:10px}\n  .group{background:var(--panel2);border:1px solid var(--stroke);border-radius:12px;padding:10px}\n  .group .title2{font-size:12px;color:#a9c4da;margin-bottom:6px}\n  .gainLine{display:flex;align-items:center;gap:10px}\n  .gainVal{background:rgba(255,255,255,.06);border:1px solid var(--stroke);padding:2px 8px;border-radius:999px;font-size:12px}\n\n  .statgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:10px}\n  .stat{background:var(--panel2);border:1px solid var(--stroke);border-radius:10px;padding:8px}\n  .kv{font-size:12px;color:#a9c4da;text-transform:uppercase;letter-spacing:.05em}\n  .vv{font-weight:900;margin-top:4px}\n\n  .footer{margin-top:12px;display:flex;gap:8px;flex-wrap:wrap}\n  .badge{background:var(--panel2);border:1px solid var(--stroke);border-radius:999px;padding:6px 10px;font-size:12px}\n\n  @media(max-width:1200px){.toolbar{grid-template-columns:1fr 1fr 1fr 1fr 1fr auto}}\n  @media(max-width:980px){.maingrid{grid-template-columns:1fr}.toolbar{grid-template-columns:1fr 1fr;grid-auto-rows:auto}}\n<\/style>\n<\/head>\n<body>\n\n<!-- ===== Barra tipo macOS ===== -->\n<div class=\"macbar-wrap\">\n  <div class=\"macbar\">\n    <div class=\"mac-dots\">\n      <span class=\"mac-dot red\"><\/span>\n      <span class=\"mac-dot yellow\"><\/span>\n      <span class=\"mac-dot green\"><\/span>\n    <\/div>\n    <div class=\"mac-title\">GLTEC \u2014 Codec Web<\/div>\n    <span id=\"macChip\" class=\"mac-chip\">OFF<\/span>\n    <div class=\"mac-spacer\"><\/div>\n    <div class=\"mac-credit\">Desenvolvido por Gabriel L. Garcia \u2014 GLTEC Brasil<\/div>\n  <\/div>\n<\/div>\n\n<div class=\"wrap\">\n  <div class=\"toolbar\">\n    <div>\n      <label>C\u00f3digo \/ Sala<\/label>\n      <input id=\"roomCode\" class=\"inp\" placeholder=\"ex.: 5836\">\n      <div style=\"margin-top:6px;font-size:12px;color:var(--muted)\">Status: <b id=\"cardStatus\">OFF<\/b><\/div>\n    <\/div>\n\n    <div>\n      <label>C\u00e2mera<\/label>\n      <select id=\"camSel\" class=\"sel\"><\/select>\n    <\/div>\n    <div>\n      <label>Resolu\u00e7\u00e3o \/ FPS<\/label>\n      <select id=\"resSel\" class=\"sel\">\n        <option value=\"1280x720@30\">720p \u2022 30fps<\/option>\n        <option value=\"1920x1080@30\" selected>1080p \u2022 30fps<\/option>\n        <option value=\"1920x1080@60\">1080p \u2022 60fps<\/option>\n        <option value=\"3840x2160@30\">4K \u2022 30fps<\/option>\n        <option value=\"3840x2160@60\">4K \u2022 60fps<\/option>\n      <\/select>\n    <\/div>\n    <div>\n      <label>Bitrate de V\u00eddeo<\/label>\n      <select id=\"vBitSel\" class=\"sel\">\n        <option value=\"4000000\">4 Mbps<\/option>\n        <option value=\"8000000\">8 Mbps<\/option>\n        <option value=\"12000000\">12 Mbps<\/option>\n        <option value=\"20000000\" selected>20 Mbps<\/option>\n        <option value=\"30000000\">30 Mbps<\/option>\n      <\/select>\n    <\/div>\n\n    <div>\n      <label>Microfone<\/label>\n      <select id=\"micSel\" class=\"sel\"><\/select>\n    <\/div>\n    <div>\n      <label>Sa\u00edda de \u00c1udio<\/label>\n      <select id=\"outSel\" class=\"sel\"><\/select>\n    <\/div>\n\n    <div class=\"actions\">\n      <button id=\"btnConnect\" class=\"iconbtn primary\">Conectar<\/button>\n      <button id=\"btnClear\" class=\"iconbtn neutral\">Resetar<\/button>\n    <\/div>\n  <\/div>\n\n  <div class=\"maingrid\">\n    <!-- LOCAL -->\n    <div class=\"card\">\n      <div class=\"hdr\">\n        <span class=\"dot\" id=\"dotLocal\"><\/span>\n        <div class=\"name\">Est\u00fadio<\/div>\n        <div class=\"mutegroup\">\n          <button id=\"monitorBtn\" class=\"btnsm\">Escutar<\/button>\n          <button id=\"muteLocal\" class=\"btnsm\">MUTE<\/button>\n          <button id=\"muteVideo\" class=\"btnsm\">Pausar V\u00eddeo<\/button>\n          <button id=\"shareScreen\" class=\"btnsm\">Compartilhar Tela<\/button>\n        <\/div>\n      <\/div>\n\n      <div class=\"row\" style=\"align-items:stretch\">\n        <div class=\"vframe\">\n          <span class=\"vtag\">Local<\/span>\n          <video id=\"vLocal\" playsinline muted><\/video>\n        <\/div>\n\n        <div class=\"vuPair\">\n          <div class=\"vuCol\"><div class=\"vuLabel\">L<\/div><div class=\"vuBar\"><div id=\"vuL_IN\" class=\"vuFill\"><\/div><\/div><\/div>\n          <div class=\"vuCol\"><div class=\"vuLabel\">R<\/div><div class=\"vuBar\"><div id=\"vuR_IN\" class=\"vuFill\"><\/div><\/div><\/div>\n        <\/div>\n        <canvas id=\"scopeLocal\" class=\"scope\"><\/canvas>\n      <\/div>\n\n      <div class=\"groups\">\n        <div class=\"group\">\n          <div class=\"title2\">Transmiss\u00e3o (\u00e1udio)<\/div>\n          <select id=\"bitrateSel\" class=\"sel\">\n            <option value=\"64000\">Opus 64 kbps \u2014 CBR<\/option>\n            <option value=\"96000\">Opus 96 kbps \u2014 CBR<\/option>\n            <option value=\"128000\" selected>Opus 128 kbps \u2014 CBR<\/option>\n            <option value=\"192000\">Opus 192 kbps \u2014 CBR<\/option>\n            <option value=\"256000\">Opus 256 kbps \u2014 CBR<\/option>\n          <\/select>\n        <\/div>\n        <div class=\"group\">\n          <div class=\"title2\">Ganho (\u00e1udio)<\/div>\n          <div class=\"gainLine\">\n            <input id=\"gainRange\" type=\"range\" min=\"-12\" max=\"12\" step=\"0.1\" value=\"0\" style=\"width:100%\">\n            <span id=\"gainVal\" class=\"gainVal\">0 dB<\/span>\n          <\/div>\n        <\/div>\n        <div class=\"group\">\n          <div class=\"title2\">Delay (\u00e1udio)<\/div>\n          <select id=\"delaySel\" class=\"sel\">\n            <option value=\"0\" selected>Desativado<\/option>\n            <option value=\"50\">50 ms<\/option>\n            <option value=\"100\">100 ms<\/option>\n            <option value=\"150\">150 ms<\/option>\n          <\/select>\n        <\/div>\n      <\/div>\n\n      <div class=\"statgrid\">\n        <div class=\"stat\"><div class=\"kv\">Bitrate (\u00e1udio envio)<\/div><div class=\"vv\" id=\"brUp\">\u2014<\/div><\/div>\n        <div class=\"stat\"><div class=\"kv\">Bitrate (v\u00eddeo envio)<\/div><div class=\"vv\" id=\"brUpV\">\u2014<\/div><\/div>\n        <div class=\"stat\"><div class=\"kv\">FPS envio<\/div><div class=\"vv\" id=\"fpsUp\">\u2014<\/div><\/div>\n      <\/div>\n    <\/div>\n\n    <!-- REMOTO -->\n    <div class=\"card\">\n      <div class=\"hdr\">\n        <span class=\"dot\" id=\"dotRemote\"><\/span>\n        <div class=\"name\">Conex\u00e3o<\/div>\n        <div class=\"mutegroup\">\n          <button id=\"muteRemote\" class=\"btnsm\">MUTE<\/button>\n        <\/div>\n      <\/div>\n\n      <div class=\"row\" style=\"align-items:stretch\">\n        <div class=\"vframe\">\n          <span class=\"vtag\">Remoto<\/span>\n          <video id=\"vRemote\" playsinline autoplay><\/video>\n        <\/div>\n\n        <div class=\"vuPair\">\n          <div class=\"vuCol\"><div class=\"vuLabel\">L<\/div><div class=\"vuBar\"><div id=\"vuL_OUT\" class=\"vuFill\"><\/div><\/div><\/div>\n          <div class=\"vuCol\"><div class=\"vuLabel\">R<\/div><div class=\"vuBar\"><div id=\"vuR_OUT\" class=\"vuFill\"><\/div><\/div><\/div>\n        <\/div>\n        <canvas id=\"scopeRemote\" class=\"scope\"><\/canvas>\n      <\/div>\n\n      <div class=\"statgrid\">\n        <div class=\"stat\"><div class=\"kv\">Bitrate (\u00e1udio receb.)<\/div><div class=\"vv\" id=\"brDown\">\u2014<\/div><\/div>\n        <div class=\"stat\"><div class=\"kv\">Bitrate (v\u00eddeo receb.)<\/div><div class=\"vv\" id=\"brDownV\">\u2014<\/div><\/div>\n        <div class=\"stat\"><div class=\"kv\">Jitter \/ Perda<\/div><div class=\"vv\" id=\"netStats\">\u2014<\/div><\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <div class=\"footer\">\n    <label class=\"badge\"><input type=\"checkbox\" id=\"vpnOnly\"> For\u00e7ar VPN (apenas ICE host)<\/label>\n    <span class=\"badge\">Sala: <b id=\"cardRoom\">\u2014<\/b><\/span>\n    <span class=\"badge\">ICE: <b id=\"connState\">OFF<\/b><\/span>\n    <span class=\"badge\">Status: <b id=\"statusChip\">OFF<\/b><\/span>\n  <\/div>\n<\/div>\n\n<audio id=\"remoteAudio\" autoplay playsinline style=\"display:none\"><\/audio>\n\n<script>\n\/* ========= helpers\/config ========= *\/\nconst S=[\"https:\/\/gltecbrasil.com.br\/wp-json\/gltec\/v1\",\"\",\"stun:stun.l.google.com:19302\",\"stun:global.stun.twilio.com:3478\"];\nconst API=S[0];\nconst configuration={iceServers:[{urls:S[2]},{urls:S[3]}]};\nconst el=id=>document.getElementById(id);\nfunction fixCanvas(c){const dpr=window.devicePixelRatio||1;c.width=c.clientWidth*dpr;c.height=c.clientHeight*dpr}\nfunction pctFromDB(db){const c=Math.max(-60,Math.min(0,db));return Math.round((c+60)\/60*100)}\nfunction setStatus(on){\n  el(\"cardStatus\").textContent=on?\"ON\":\"OFF\";\n  el(\"statusChip\").textContent=on?\"ON\":\"OFF\";\n  const macChip=document.getElementById(\"macChip\");\n  if(macChip){ macChip.textContent=on?\"ON\":\"OFF\"; macChip.classList.toggle(\"on\", !!on); }\n  el(\"dotRemote\").style.background=on?\"#28d559\":\"#666\";\n  el(\"dotLocal\").style.background=on?\"#28d559\":\"#666\";\n}\n\n\/* ========= state ========= *\/\nlet pc=null, localStream=null, remoteStream=null, audioCtx=null;\nlet anLL=null,anLR=null,anRL=null,anRR=null; \/\/ analysers\nlet httpRoom=null,httpSince=0,pollTimer=null,statsTimer=null;\nlet bytesUpPrev=0,tsUpPrev=0,bytesDownPrev=0,tsDownPrev=0;\nlet vBytesUpPrev=0,vTsUpPrev=0,vBytesDownPrev=0,vTsDownPrev=0;\nlet monitorGain=null,delayNode=null,userGain=null;\nlet vpnOnly=false, videoPaused=false;\n\n\/* ========= audio ctx ========= *\/\nfunction getAudioCtx(){ if(!audioCtx) audioCtx=new (window.AudioContext||window.webkitAudioContext)(); if(audioCtx.state===\"suspended\"){try{audioCtx.resume()}catch(e){}} return audioCtx }\n\n\/* ========= signaling ========= *\/\nasync function httpJoin(room){const r=await fetch(`${API}\/join`,{method:\"POST\",headers:{\"Content-Type\":\"application\/json\"},body:JSON.stringify({room,password:room})});return r.json()}\nasync function httpLeave(room){fetch(`${API}\/leave`,{method:\"POST\",headers:{\"Content-Type\":\"application\/json\"},body:JSON.stringify({room}),keepalive:true}).catch(()=>{})}\nasync function httpPush(room,type,payload){await fetch(`${API}\/push`,{method:\"POST\",headers:{\"Content-Type\":\"application\/json\"},body:JSON.stringify({room,type,payload})})}\nasync function httpPull(room){\n  const r=await fetch(`${API}\/pull?room=${encodeURIComponent(room)}&since=${httpSince}`,{cache:\"no-store\"});\n  const j=await r.json(); if(j.ok){ httpSince=j.now; (j.items||[]).forEach(async it=>{\n    if(it.type===\"sdp\"){const msg=it.payload;\n      if(msg.type===\"offer\"){await ensurePeerAndLocal();if(pc.signalingState===\"have-local-offer\")return;\n        await pc.setRemoteDescription(msg);const ans=await pc.createAnswer({iceRestart:false});await pc.setLocalDescription(ans);await waitIce();await httpPush(room,\"sdp\",pc.localDescription)\n      }else if(msg.type===\"answer\"){if(pc&&pc.signalingState!==\"stable\"){await pc.setRemoteDescription(msg)}}\n    }else if(it.type===\"ice\"){if(pc)try{await pc.addIceCandidate(it.payload)}catch(e){}}\n  })}}\nasync function httpClear(room){const r=await fetch(`${API}\/clear`,{method:\"POST\",headers:{\"Content-Type\":\"application\/json\"},body:JSON.stringify({room,password:room})});return r.json()}\n\n\/* ========= devices ========= *\/\nasync function listDevices(){\n  const devs=await navigator.mediaDevices.enumerateDevices();\n  const cams=devs.filter(d=>d.kind===\"videoinput\");\n  const mics=devs.filter(d=>d.kind===\"audioinput\");\n  const outs=devs.filter(d=>d.kind===\"audiooutput\");\n\n  el(\"camSel\").innerHTML=\"\"; cams.forEach((d,i)=>{const o=document.createElement(\"option\");o.value=d.deviceId;o.text=d.label||(\"C\u00e2mera \"+(i+1));el(\"camSel\").appendChild(o)});\n  el(\"micSel\").innerHTML=\"\"; mics.forEach((d,i)=>{const o=document.createElement(\"option\");o.value=d.deviceId;o.text=d.label||(\"Microfone \"+(i+1));el(\"micSel\").appendChild(o)});\n  el(\"outSel\").innerHTML=\"\"; outs.forEach((d,i)=>{const o=document.createElement(\"option\");o.value=d.deviceId;o.text=d.label||(\"Sa\u00edda \"+(i+1));el(\"outSel\").appendChild(o)});\n}\n\n\/* ========= media capture ========= *\/\nfunction parseRes(sel){\n  const [wh,fpsStr]=sel.split(\"@\");\n  const [w,h]=wh.split(\"x\").map(v=>parseInt(v,10));\n  const fps=parseInt(fpsStr,10);\n  return {w,h,fps};\n}\n\nasync function getLocalStream(){\n  const mic=el(\"micSel\").value||undefined;\n  const cam=el(\"camSel\").value||undefined;\n  const {w,h,fps}=parseRes(el(\"resSel\").value);\n\n  \/\/ pede \u00e1udio + v\u00eddeo juntos\n  localStream=await navigator.mediaDevices.getUserMedia({\n    audio:{\n      deviceId:mic?{exact:mic}:undefined,\n      channelCount:2, echoCancellation:false, noiseSuppression:false, autoGainControl:false, sampleRate:48000\n    },\n    video:{\n      deviceId:cam?{exact:cam}:undefined,\n      width:{ideal:w}, height:{ideal:h}, frameRate:{ideal:fps, min:30}\n    }\n  });\n\n  \/\/ preview\n  const v=el(\"vLocal\"); v.srcObject=localStream; v.muted=true; await v.play().catch(()=>{});\n\n  \/\/ analisadores de \u00e1udio (como no seu original)\n  const ctx=getAudioCtx(); const src=ctx.createMediaStreamSource(localStream);\n  const split=ctx.createChannelSplitter(2); src.connect(split);\n  anLL=ctx.createAnalyser(); anLR=ctx.createAnalyser(); anLL.fftSize=2048; anLR.fftSize=2048;\n  split.connect(anLL,0); split.connect(anLR,1);\n  [anLL,anLR].forEach(an=>{const g=ctx.createGain();g.gain.value=0;an.connect(g);g.connect(ctx.destination)});\n  userGain=ctx.createGain(); delayNode=ctx.createDelay(1.0); delayNode.delayTime.value=0;\n  src.connect(userGain); userGain.connect(delayNode); monitorGain=ctx.createGain(); monitorGain.gain.value=0; delayNode.connect(monitorGain); monitorGain.connect(ctx.destination);\n  drawLocal();\n}\nfunction drawLocal(){\n  const bL=new Float32Array(2048), bR=new Float32Array(2048);\n  const c=el(\"scopeLocal\"), g=c.getContext(\"2d\"); fixCanvas(c);\n  (function loop(){\n    if(!anLL||!anLR){requestAnimationFrame(loop);return}\n    anLL.getFloatTimeDomainData(bL); anLR.getFloatTimeDomainData(bR);\n    const rmsL=Math.sqrt(bL.reduce((s,v)=>s+v*v,0)\/bL.length), rmsR=Math.sqrt(bR.reduce((s,v)=>s+v*v,0)\/bR.length);\n    el(\"vuL_IN\").style.height=pctFromDB(20*Math.log10(rmsL||1e-6))+\"%\";\n    el(\"vuR_IN\").style.height=pctFromDB(20*Math.log10(rmsR||1e-6))+\"%\";\n    g.clearRect(0,0,c.width,c.height); g.strokeStyle=\"#19d3a1\"; g.lineWidth=2*(window.devicePixelRatio||1); g.beginPath();\n    let x=0,step=c.width\/bL.length; for(let i=0;i<bL.length;i++){const v=(bL[i]+bR[i])*0.5; const y=c.height*0.5+v*(c.height*0.42); i?g.lineTo(x,y):g.moveTo(x,y); x+=step}\n    g.stroke(); requestAnimationFrame(loop)\n  })();\n}\n\n\/* ========= remoto analisador ========= *\/\nfunction setupAnalyserRemote(){\n  if(!remoteStream) return; const ctx=getAudioCtx(); const src=ctx.createMediaStreamSource(remoteStream);\n  const split=ctx.createChannelSplitter(2); src.connect(split);\n  anRL=ctx.createAnalyser(); anRR=ctx.createAnalyser(); anRL.fftSize=2048; anRR.fftSize=2048;\n  split.connect(anRL,0); split.connect(anRR,1);\n  [anRL,anRR].forEach(an=>{const g=ctx.createGain();g.gain.value=0;an.connect(g);g.connect(ctx.destination)});\n  drawRemote();\n}\nfunction drawRemote(){\n  const bL=new Float32Array(2048), bR=new Float32Array(2048);\n  const c=el(\"scopeRemote\"), g=c.getContext(\"2d\"); fixCanvas(c);\n  (function loop(){\n    if(!anRL||!anRR){requestAnimationFrame(loop);return}\n    anRL.getFloatTimeDomainData(bL); anRR.getFloatTimeDomainData(bR);\n    const rmsL=Math.sqrt(bL.reduce((s,v)=>s+v*v,0)\/bL.length), rmsR=Math.sqrt(bR.reduce((s,v)=>s+v*v,0)\/bR.length);\n    el(\"vuL_OUT\").style.height=pctFromDB(20*Math.log10(rmsL||1e-6))+\"%\";\n    el(\"vuR_OUT\").style.height=pctFromDB(20*Math.log10(rmsR||1e-6))+\"%\";\n    g.clearRect(0,0,c.width,c.height); g.strokeStyle=\"#39c0ff\"; g.lineWidth=2*(window.devicePixelRatio||1); g.beginPath();\n    let x=0,step=c.width\/bL.length; for(let i=0;i<bL.length;i++){const v=(bL[i]+bR[i])*0.5; const y=c.height*0.5+v*(c.height*0.42); i?g.lineTo(x,y):g.moveTo(x,y); x+=step}\n    g.stroke(); requestAnimationFrame(loop)\n  })();\n}\n\n\/* ========= peer ========= *\/\nasync function ensurePeerAndLocal(){\n  if(!localStream){await getLocalStream();await listDevices()}\n  if(!pc){\n    pc=new RTCPeerConnection(configuration);\n\n    \/\/ adiciona tracks\n    localStream.getTracks().forEach(t=>pc.addTrack(t,localStream));\n\n    \/\/ Prefer\u00eancia de codecs (AV1 -> VP9 -> H264)\n    try{\n      const trans = pc.getTransceivers().find(t=>t.sender?.track?.kind==='video');\n      if(trans){\n        const caps = RTCRtpSender.getCapabilities('video').codecs;\n        const pref = [\n          ...caps.filter(c=>\/AV1\/i.test(c.mimeType)),\n          ...caps.filter(c=>\/VP9\/i.test(c.mimeType)),\n          ...caps.filter(c=>\/H264\/i.test(c.mimeType)),\n        ];\n        if(pref.length) trans.setCodecPreferences(pref);\n      }\n    }catch(e){}\n\n    \/\/ par\u00e2metros de \u00e1udio\/v\u00eddeo (bitrate e SVC)\n    setTimeout(applyAVParams, 150);\n\n    pc.oniceconnectionstatechange=()=>{\n      const st=(pc.iceConnectionState||\"\").toUpperCase();\n      el(\"connState\").textContent=st; setStatus(st===\"CONNECTED\"||st===\"COMPLETED\");\n      try{const a=el(\"remoteAudio\"); if(a&&a.srcObject) a.play().catch(()=>{})}catch(e){}\n    };\n    pc.onicecandidate=async ev=>{\n      if(!ev.candidate||!httpRoom) return;\n      if(vpnOnly){\n        const c=ev.candidate.candidate||\"\";\n        if(!c.includes(\" typ host \")) return;\n      }\n      await httpPush(httpRoom,\"ice\",ev.candidate)\n    };\n    pc.ontrack=ev=>{\n      const stream=(ev.streams&&ev.streams[0])?ev.streams[0]:new MediaStream([ev.track]);\n      if(ev.track.kind==='audio'){\n        remoteStream=stream;\n        const aud=el(\"remoteAudio\"); aud.srcObject=stream; aud.muted=false; aud.volume=1.0;\n        aud.onloadedmetadata=()=>{const outId=el(\"outSel\").value; if(aud.setSinkId&&outId){aud.setSinkId(outId).catch(()=>{})} aud.play().catch(()=>{})};\n        setupAnalyserRemote();\n      }\n      if(ev.track.kind==='video'){\n        const v=el(\"vRemote\"); v.srcObject=stream; v.play().catch(()=>{});\n      }\n    };\n\n    if(statsTimer) clearInterval(statsTimer);\n    statsTimer=setInterval(async ()=>{\n      if(!pc) return;\n      const stats=await pc.getStats(null);\n      let inA=null,outA=null,inV=null,outV=null,pair=null,localC=null;\n      stats.forEach(r=>{\n        if(r.type==='inbound-rtp' && r.kind==='audio') inA=r;\n        if(r.type==='outbound-rtp' && r.kind==='audio') outA=r;\n        if(r.type==='inbound-rtp' && r.kind==='video') inV=r;\n        if(r.type==='outbound-rtp' && r.kind==='video') outV=r;\n        if(r.type==='candidate-pair'&&r.state==='succeeded'&&r.selected) pair=r;\n        if(r.type==='local-candidate') localC=r;\n      });\n\n      if(pair){\n        const rttMs=(pair.currentRoundTripTime||0)*1000;\n        const jitterMs=(inA?.jitter||0)*1000;\n        const lost=inA?.packetsLost||0, rec=inA?.packetsReceived||0;\n        el(\"netStats\").textContent = `${jitterMs.toFixed(0)} ms \/ ${rec?((lost\/(lost+rec))*100).toFixed(1):'0.0'}%`;\n      }\n\n      \/\/ \u00c1udio\n      if(inA){ const ts=inA.timestamp,b=inA.bytesReceived||0; if(ts&&tsDownPrev){const br=8*(b-bytesDownPrev)\/((ts-tsDownPrev)\/1000); el(\"brDown\").textContent=(br\/1000).toFixed(1)+\" kbps\"} tsDownPrev=ts; bytesDownPrev=b }\n      if(outA){ const ts=outA.timestamp,b=outA.bytesSent||0; if(ts&&tsUpPrev){const br=8*(b-bytesUpPrev)\/((ts-tsUpPrev)\/1000); el(\"brUp\").textContent=(br\/1000).toFixed(1)+\" kbps\"} tsUpPrev=ts; bytesUpPrev=b }\n\n      \/\/ V\u00eddeo\n      if(inV){ const ts=inV.timestamp,b=inV.bytesReceived||0; if(ts&&vTsDownPrev){const br=8*(b-vBytesDownPrev)\/((ts-vTsDownPrev)\/1000); el(\"brDownV\").textContent=(br\/1000).toFixed(1)+\" kbps\"} vTsDownPrev=ts; vBytesDownPrev=b }\n      if(outV){\n        const ts=outV.timestamp,b=outV.bytesSent||0; if(ts&&vTsUpPrev){const br=8*(b-vBytesUpPrev)\/((ts-vTsUpPrev)\/1000); el(\"brUpV\").textContent=(br\/1000).toFixed(1)+\" kbps\"} vTsUpPrev=ts; vBytesUpPrev=b;\n        if(outV.framesPerSecond) el(\"fpsUp\").textContent = `${outV.framesPerSecond} fps`;\n      }\n\n      el(\"cardRoom\").textContent=httpRoom||\"\u2014\";\n    },1000);\n  }\n}\n\nfunction applyAVParams(){\n  if(!pc) return;\n  \/\/ \u00c1udio bitrate\n  try{\n    const chosen=parseInt(el(\"bitrateSel\").value||\"128000\",10);\n    pc.getSenders().forEach(async s=>{\n      if(s.track&&s.track.kind===\"audio\"){\n        const p=s.getParameters(); if(!p.encodings)p.encodings=[{}];\n        p.encodings[0].maxBitrate=chosen; await s.setParameters(p);\n      }\n    });\n  }catch(e){}\n\n  \/\/ V\u00eddeo bitrate + SVC + dica de conte\u00fado\n  try{\n    const vChosen=parseInt(el(\"vBitSel\").value||\"20000000\",10);\n    const vSender = pc.getSenders().find(s=>s.track && s.track.kind==='video');\n    if(vSender){\n      const vp=vSender.getParameters(); if(!vp.encodings) vp.encodings=[{}];\n      vp.encodings[0].maxBitrate=vChosen;\n      vp.encodings[0].scalabilityMode='L3T3';\n      vSender.setParameters(vp).catch(()=>{});\n      if('contentHint' in vSender.track) vSender.track.contentHint='detail';\n    }\n  }catch(e){}\n}\n\nfunction waitIce(){return new Promise(res=>{if(pc.iceGatheringState===\"complete\")return res();const f=()=>{if(pc.iceGatheringState===\"complete\"){pc.removeEventListener(\"icegatheringstatechange\",f);res()}};pc.addEventListener(\"icegatheringstatechange\",f);setTimeout(res,4000)})}\n\n\/* ========= sess\u00e3o ========= *\/\nasync function startHttp(room){\n  const j=await httpJoin(room);\n  if(!j.ok){alert(j.err===\"room_full\"?\"Sala cheia. Use Resetar e tente de novo.\":\"Falha ao entrar.\");return}\n  httpRoom=room; httpSince=0; el(\"cardRoom\").textContent=room;\n\n  if(pollTimer) clearInterval(pollTimer);\n  pollTimer=setInterval(()=>httpPull(room),1200);\n\n  if(j.pos===1){\n    await ensurePeerAndLocal();\n    const off=await pc.createOffer({offerToReceiveAudio:true, offerToReceiveVideo:true, voiceActivityDetection:false});\n    await pc.setLocalDescription(off); await waitIce(); await httpPush(room,\"sdp\",pc.localDescription);\n  }\n}\nasync function leaveRoom(){\n  if(httpRoom){await httpLeave(httpRoom);httpRoom=null}\n  if(pollTimer){clearInterval(pollTimer);pollTimer=null}\n  if(statsTimer){clearInterval(statsTimer);statsTimer=null}\n  if(pc){try{pc.close()}catch(e){} pc=null}\n  setStatus(false); el(\"connState\").textContent=\"OFF\";\n  [\"brUp\",\"brDown\",\"brUpV\",\"brDownV\",\"fpsUp\",\"netStats\"].forEach(id=>el(id).textContent=\"\u2014\");\n  el(\"cardRoom\").textContent=\"\u2014\";\n}\n\n\/* ========= eventos ========= *\/\nwindow.addEventListener(\"resize\",()=>{fixCanvas(el(\"scopeLocal\"));fixCanvas(el(\"scopeRemote\"))});\n\nel(\"bitrateSel\").addEventListener(\"change\",applyAVParams);\nel(\"vBitSel\").addEventListener(\"change\",applyAVParams);\n\nel(\"gainRange\").addEventListener(\"input\",e=>{\n  const v=parseFloat(e.target.value||\"0\");\n  el(\"gainVal\").textContent=(v>=0?\"+\":\"\")+v.toFixed(1)+\" dB\";\n  if(userGain){userGain.gain.value=Math.pow(10,v\/20)}\n});\nel(\"delaySel\").addEventListener(\"change\",e=>{const ms=parseInt(e.target.value||\"0\",10)\/1000; if(delayNode) delayNode.delayTime.value=ms});\n\nel(\"monitorBtn\").onclick=()=>{ if(monitorGain){ const on=monitorGain.gain.value<0.5; monitorGain.gain.value=on?1:0; el(\"monitorBtn\").textContent=on?\"Parar\":\"Escutar\" }};\nel(\"vpnOnly\").addEventListener(\"change\",e=>{ vpnOnly=e.target.checked });\n\nel(\"outSel\").addEventListener(\"change\",()=>{\n  const a=el(\"remoteAudio\"), outId=el(\"outSel\").value;\n  if(a&&a.setSinkId&&outId){a.setSinkId(outId).catch(()=>{})}\n});\nel(\"camSel\").addEventListener(\"change\", async ()=>{\n  if(!localStream) return;\n  \/\/ troca de c\u00e2mera mantendo \u00e1udio\n  try{\n    const cam=el(\"camSel\").value||undefined;\n    const {w,h,fps}=parseRes(el(\"resSel\").value);\n    const vTrack = await navigator.mediaDevices.getUserMedia({\n      video:{deviceId:cam?{exact:cam}:undefined, width:{ideal:w}, height:{ideal:h}, frameRate:{ideal:fps,min:30}}\n    }).then(s=>s.getVideoTracks()[0]);\n    const sender = pc?.getSenders().find(s=>s.track && s.track.kind==='video');\n    if(sender){ await sender.replaceTrack(vTrack); applyAVParams(); }\n    \/\/ atualiza preview\n    const oldV = localStream.getVideoTracks()[0]; if(oldV) oldV.stop();\n    localStream.removeTrack(oldV); localStream.addTrack(vTrack);\n    el(\"vLocal\").srcObject = localStream;\n  }catch(e){}\n});\nel(\"resSel\").addEventListener(\"change\", async ()=>{ \/\/ re-aplica constraints\n  el(\"camSel\").dispatchEvent(new Event('change'));\n});\n\nel(\"muteVideo\").onclick=()=>{\n  videoPaused = !videoPaused;\n  if(localStream) localStream.getVideoTracks().forEach(t=>t.enabled = !videoPaused);\n  el(\"muteVideo\").textContent = videoPaused ? \"Retomar V\u00eddeo\" : \"Pausar V\u00eddeo\";\n};\nel(\"shareScreen\").onclick = async ()=>{\n  try{\n    const scr = await navigator.mediaDevices.getDisplayMedia({ video:true });\n    const track = scr.getVideoTracks()[0];\n    const sender = pc?.getSenders().find(s=>s.track && s.track.kind==='video');\n    if(sender){ await sender.replaceTrack(track); applyAVParams(); }\n    \/\/ preview muda para tela\n    const oldV = localStream.getVideoTracks()[0]; if(oldV) oldV.stop();\n    localStream.removeTrack(oldV); localStream.addTrack(track);\n    el(\"vLocal\").srcObject = localStream;\n    track.onended = ()=>{ \/\/ volta para camera ao parar compartilhamento\n      el(\"camSel\").dispatchEvent(new Event('change'));\n    }\n  }catch(e){}\n};\n\nel(\"btnConnect\").onclick=async ()=>{\n  try{await getAudioCtx().resume()}catch(e){}\n  try{await navigator.mediaDevices.getUserMedia({audio:true, video:true})}catch(e){}\n  const room=(el(\"roomCode\").value||\"\").trim();\n  if(!room){alert(\"Digite o c\u00f3digo\");return}\n  await startHttp(room);\n};\nel(\"btnClear\").onclick=async ()=>{\n  const room=(el(\"roomCode\").value||\"\").trim();\n  if(!room){alert(\"Digite o c\u00f3digo\");return}\n  await leaveRoom(); const r=await httpClear(room); if(r.ok) alert(\"Sala resetada.\")\n};\n\n\/* boot *\/\n(async function(){\n  try{await navigator.mediaDevices.getUserMedia({audio:true, video:true}); await listDevices()}catch(e){}\n  setStatus(false);\n  fixCanvas(el(\"scopeLocal\"));fixCanvas(el(\"scopeRemote\"));\n})();\n<\/script>\n<\/body>\n<\/html>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>GLTEC \u2014 Codec Web (\u00c1udio + V\u00eddeo) GLTEC \u2014 Codec Web OFF Desenvolvido por Gabriel L. Garcia \u2014 GLTEC Brasil C\u00f3digo \/ Sala Status: OFF C\u00e2mera Resolu\u00e7\u00e3o \/ FPS 720p \u2022 30fps1080p \u2022 30fps1080p \u2022 60fps4K \u2022 30fps4K \u2022 60fps Bitrate de V\u00eddeo 4 Mbps8 Mbps12 Mbps20 Mbps30 Mbps Microfone Sa\u00edda de \u00c1udio Conectar Resetar [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_canvas","meta":{"footnotes":""},"class_list":["post-425","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/pages\/425","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/comments?post=425"}],"version-history":[{"count":13,"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/pages\/425\/revisions"}],"predecessor-version":[{"id":438,"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/pages\/425\/revisions\/438"}],"wp:attachment":[{"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/media?parent=425"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}