{"id":188,"date":"2025-10-05T03:23:25","date_gmt":"2025-10-05T03:23:25","guid":{"rendered":"https:\/\/gltecbrasil.com.br\/?page_id=188"},"modified":"2025-12-09T13:12:36","modified_gmt":"2025-12-09T13:12:36","slug":"codec-ip-web","status":"publish","type":"page","link":"https:\/\/gltecbrasil.com.br\/index.php\/codec-ip-web\/","title":{"rendered":"codec ip web"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"188\" class=\"elementor elementor-188\">\n\t\t\t\t<div class=\"elementor-element elementor-element-5dbdacb e-flex e-con-boxed e-con e-parent\" data-id=\"5dbdacb\" 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-0f4bde2 elementor-widget elementor-widget-html\" data-id=\"0f4bde2\" 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<\/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 \/ inputs ===== *\/\n  .toolbar{margin-top:14px;display:grid;grid-template-columns:1.2fr 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  \/* bot\u00f5es ic\u00f4nicos *\/\n  .actions{display:flex; gap:10px; align-items:end}\n  .iconbtn{\n    display:inline-flex; align-items:center; justify-content:center;\n    width:44px; height:44px; 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);\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  \/* ===== VU L | R lado a lado ===== *\/\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  .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  \/* grupos alinhados *\/\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: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    <div>\n      <label>Canal de sa\u00edda do som<\/label>\n      <select id=\"outSel\" class=\"sel\"><\/select>\n    <\/div>\n    <div>\n      <label>Microfone<\/label>\n      <select id=\"micSel\" class=\"sel\"><\/select>\n    <\/div>\n\n    <!-- Bot\u00f5es ic\u00f4nicos (SVG) -->\n    <div class=\"actions\">\n      <!-- Conectar -->\n      <button id=\"btnConnect\" class=\"iconbtn primary\" aria-label=\"Conectar\" title=\"Conectar\">\n        <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n          <path d=\"M8 8l8 8\"><\/path><path d=\"M16 8l-8 8\"><\/path>\n          <path d=\"M3 12h3\"><\/path><path d=\"M18 12h3\"><\/path>\n          <circle cx=\"12\" cy=\"12\" r=\"9\" opacity=\".2\"><\/circle>\n        <\/svg>\n        <span class=\"visually-hidden\">Conectar<\/span>\n      <\/button>\n\n      <!-- Resetar -->\n      <button id=\"btnClear\" class=\"iconbtn neutral\" aria-label=\"Resetar\" title=\"Resetar\">\n        <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n          <path d=\"M21 12a9 9 0 1 1-2.64-6.36\"><\/path>\n          <path d=\"M21 3v6h-6\"><\/path>\n        <\/svg>\n        <span class=\"visually-hidden\">Resetar<\/span>\n      <\/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        <\/div>\n      <\/div>\n\n      <div class=\"row\">\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<\/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<\/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<\/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 (envio)<\/div><div class=\"vv\" id=\"brUp\">\u2014<\/div><\/div>\n        <div class=\"stat\"><div class=\"kv\">Protocolo envio<\/div><div class=\"vv\" id=\"protoUp\">\u2014<\/div><\/div>\n        <div class=\"stat\"><div class=\"kv\">Lat\u00eancia (one-way)<\/div><div class=\"vv\" id=\"latOneWay\">\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\">\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 (recebendo)<\/div><div class=\"vv\" id=\"brDown\">\u2014<\/div><\/div>\n        <div class=\"stat\"><div class=\"kv\">Jitter<\/div><div class=\"vv\" id=\"jitterVal\">\u2014<\/div><\/div>\n        <div class=\"stat\"><div class=\"kv\">Pacotes perdidos<\/div><div class=\"vv\" id=\"pktLoss\">\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]; const 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; \/\/ L\/R IN e OUT\nlet httpRoom=null,httpSince=0,pollTimer=null,statsTimer=null;\nlet bytesUpPrev=0,tsUpPrev=0,bytesDownPrev=0,tsDownPrev=0;\nlet monitorGain=null,delayNode=null,userGain=null;\nlet vpnOnly=false;\n\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 mics=devs.filter(d=>d.kind===\"audioinput\"); const outs=devs.filter(d=>d.kind===\"audiooutput\");\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\/* ===== local L\/R ===== *\/\nasync function getLocalStream(){\n  const mic=el(\"micSel\").value||undefined;\n  localStream=await navigator.mediaDevices.getUserMedia({audio:{deviceId:mic?{exact:mic}:undefined,channelCount:2,echoCancellation:false,noiseSuppression:false,autoGainControl:false,sampleRate:48000},video:false});\n  const ctx=getAudioCtx(); const src=ctx.createMediaStreamSource(localStream);\n  const split=ctx.createChannelSplitter(2);\n  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 L\/R ===== *\/\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 & stats ===== *\/\nasync function ensurePeerAndLocal(){\n  if(!localStream){await getLocalStream();await listDevices()}\n  if(!pc){\n    pc=new RTCPeerConnection(configuration);\n    localStream.getTracks().forEach(t=>pc.addTrack(t,localStream));\n    setTimeout(async ()=>{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){}},120);\n\n    pc.oniceconnectionstatechange=()=>{const st=(pc.iceConnectionState||\"\").toUpperCase(); el(\"connState\").textContent=st; setStatus(st===\"CONNECTED\"||st===\"COMPLETED\"); try{const a=el(\"remoteAudio\"); if(a&&a.srcObject) a.play().catch(()=>{})}catch(e){}};\n    pc.onicecandidate=async ev=>{if(!ev.candidate||!httpRoom)return;if(vpnOnly){const c=ev.candidate.candidate||\"\";if(!c.includes(\" typ host \"))return}await httpPush(httpRoom,\"ice\",ev.candidate)};\n    pc.ontrack=ev=>{\n      const stream=(ev.streams&&ev.streams[0])?ev.streams[0]:new MediaStream([ev.track]); 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\n    if(statsTimer) clearInterval(statsTimer);\n    statsTimer=setInterval(async ()=>{\n      if(!pc) return; const stats=await pc.getStats(null);\n      let inRtp=null,outRtp=null,pair=null,localC=null;\n      stats.forEach(r=>{ if(r.type===\"inbound-rtp\"&&r.kind===\"audio\") inRtp=r; if(r.type===\"outbound-rtp\"&&r.kind===\"audio\") outRtp=r; if(r.type===\"candidate-pair\"&&r.state===\"succeeded\"&&r.selected) pair=r; if(r.type===\"local-candidate\") localC=r; });\n      if(pair){ const rttMs=(pair.currentRoundTripTime||0)*1000; el(\"latOneWay\").textContent=Math.max(0, rttMs\/2).toFixed(0)+\" ms\"; el(\"protoUp\").textContent=(localC?.candidateType||\"\u2014\")+\"\/\"+(localC?.protocol||\"\u2014\"); }\n      if(inRtp){ const jitterMs=(inRtp.jitter||0)*1000; el(\"jitterVal\").textContent=jitterMs.toFixed(0)+\" ms\"; const lost=inRtp.packetsLost||0, rec=inRtp.packetsReceived||0; el(\"pktLoss\").textContent=rec?((lost\/(lost+rec))*100).toFixed(1)+\"%\":\"0%\";\n        const ts=inRtp.timestamp,b=inRtp.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(outRtp){ const ts=outRtp.timestamp,b=outRtp.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      el(\"cardRoom\").textContent=httpRoom||\"\u2014\";\n    },1000);\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); 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  if(pollTimer) clearInterval(pollTimer); pollTimer=setInterval(()=>httpPull(room),1200);\n  if(j.pos===1){await ensurePeerAndLocal();const off=await pc.createOffer({offerToReceiveAudio:true,voiceActivityDetection:false});await pc.setLocalDescription(off);await waitIce();await httpPush(room,\"sdp\",pc.localDescription)}\n}\nasync function leaveRoom(){ if(httpRoom){await httpLeave(httpRoom);httpRoom=null} if(pollTimer){clearInterval(pollTimer);pollTimer=null} if(statsTimer){clearInterval(statsTimer);statsTimer=null} if(pc){try{pc.close()}catch(e){} pc=null} setStatus(false); el(\"connState\").textContent=\"OFF\"; el(\"brUp\").textContent=el(\"brDown\").textContent=\"\u2014\"; el(\"jitterVal\").textContent=\"\u2014\"; el(\"latOneWay\").textContent=\"\u2014\"; el(\"cardRoom\").textContent=\"\u2014\" }\n\n\/* ===== eventos ===== *\/\nwindow.addEventListener(\"resize\",()=>{fixCanvas(el(\"scopeLocal\"));fixCanvas(el(\"scopeRemote\"))});\nel(\"bitrateSel\").addEventListener(\"change\",async ()=>{if(!pc)return;const chosen=parseInt(el(\"bitrateSel\").value||\"128000\",10);try{pc.getSenders().forEach(async s=>{if(s.track&&s.track.kind===\"audio\"){const p=s.getParameters();if(!p.encodings)p.encodings=[{}];p.encodings[0].maxBitrate=chosen;await s.setParameters(p)}})}catch(e){}});\nel(\"gainRange\").addEventListener(\"input\",e=>{const v=parseFloat(e.target.value||\"0\");el(\"gainVal\").textContent=(v>=0?\"+\":\"\")+v.toFixed(1)+\" dB\";if(userGain){userGain.gain.value=Math.pow(10,v\/20)}});\nel(\"delaySel\").addEventListener(\"change\",e=>{const ms=parseInt(e.target.value||\"0\",10)\/1000; if(delayNode) delayNode.delayTime.value=ms});\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 });\nel(\"outSel\").addEventListener(\"change\",()=>{ const a=el(\"remoteAudio\"), outId=el(\"outSel\").value; if(a&&a.setSinkId&&outId){a.setSinkId(outId).catch(()=>{})}});\nel(\"btnConnect\").onclick=async ()=>{try{await getAudioCtx().resume()}catch(e){}try{await navigator.mediaDevices.getUserMedia({audio:true})}catch(e){}const room=(el(\"roomCode\").value||\"\").trim();if(!room){alert(\"Digite o c\u00f3digo\");return}await startHttp(room)};\nel(\"btnClear\").onclick=async ()=>{const room=(el(\"roomCode\").value||\"\").trim();if(!room){alert(\"Digite o c\u00f3digo\");return}await leaveRoom();const r=await httpClear(room);if(r.ok) alert(\"Sala resetada.\")};\n\n\/* boot *\/\n(async function(){try{await navigator.mediaDevices.getUserMedia({audio:true});await listDevices()}catch(e){} setStatus(false); fixCanvas(el(\"scopeLocal\"));fixCanvas(el(\"scopeRemote\"))})();\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 GLTEC \u2014 Codec Web OFF Desenvolvido por Gabriel L. Garcia \u2014 GLTEC Brasil C\u00f3digo \/ Sala Status: OFF Canal de sa\u00edda do som Microfone Conectar Resetar Est\u00fadio Escutar MUTE L R Transmiss\u00e3o Opus 64 kbps \u2014 CBROpus 96 kbps \u2014 CBROpus 128 kbps \u2014 CBROpus 192 kbps \u2014 CBROpus 256 kbps [&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-188","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/pages\/188","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=188"}],"version-history":[{"count":77,"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/pages\/188\/revisions"}],"predecessor-version":[{"id":299,"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/pages\/188\/revisions\/299"}],"wp:attachment":[{"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/media?parent=188"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}