{"id":300,"date":"2025-10-12T03:40:38","date_gmt":"2025-10-12T03:40:38","guid":{"rendered":"https:\/\/gltecbrasil.com.br\/?page_id=300"},"modified":"2025-10-12T03:55:39","modified_gmt":"2025-10-12T03:55:39","slug":"gltec-codec","status":"publish","type":"page","link":"https:\/\/gltecbrasil.com.br\/index.php\/gltec-codec\/","title":{"rendered":"GLTEC \u2014 Codec"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"300\" class=\"elementor elementor-300\">\n\t\t\t\t<div class=\"elementor-element elementor-element-5a78b0a e-flex e-con-boxed e-con e-parent\" data-id=\"5a78b0a\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-df4cfe2 e-flex e-con-boxed e-con e-parent\" data-id=\"df4cfe2\" 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-5e7c0e7 elementor-widget elementor-widget-html\" data-id=\"5e7c0e7\" 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 (Multiponto)<\/title>\n<meta name=\"robots\" content=\"noindex,nofollow\"\/>\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:#15d9a2;--warn:#f4b942;--err:#ff6b6b;\n}\nhtml,body{height:100%}\nbody{margin:0;background:linear-gradient(180deg,var(--bg),var(--bg2));color:var(--ink);font-family:Inter,system-ui,Segoe UI,Roboto,Arial,sans-serif}\n.main{min-height:100%;display:flex;flex-direction:column}\n.gltec-wrap{max-width:1200px;margin:24px auto;padding:0 16px;flex:1 0 auto}\n.gltec-window{background:linear-gradient(180deg,#0b1b2a,#0c2234);border:1px solid var(--stroke);border-radius:14px;box-shadow:0 16px 40px rgba(0,0,0,.25);overflow:hidden;color:var(--ink)}\n.gltec-topbar{display:flex;align-items:center;gap:10px;padding:10px 12px;background:rgba(255,255,255,.06);border-bottom:1px solid var(--stroke)}\n.gltec-dots{display:flex;gap:8px;margin-right:6px}\n.gltec-dot{width:12px;height:12px;border-radius:50%}\n.gltec-red{background:#ff5f56}.gltec-yellow{background:#ffbd2e}.gltec-green{background:#27c93f}\n.gltec-title{font-weight:800}\n.gltec-body{padding:16px}\n.footer{padding:12px 16px;color:#93a8bb; font-size:12px; text-align:center}\n\n.toolbar{display:grid;grid-template-columns:1fr 1fr 1fr 1fr auto;gap:10px}\nlabel{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.actions{display:flex;gap:10px;align-items:end}\n.iconbtn{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:12px;border:1px solid var(--stroke);background:rgba(255,255,255,.06);color:var(--ink);cursor:pointer}\n.iconbtn.primary{background:var(--brand);color:#001422}\n\n.cards{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:14px}\n.card{background:var(--panel);border:1px solid var(--stroke);border-radius:14px;padding:12px}\n.hdr{display:flex;align-items:center;gap:10px}\n.name{font-weight:800}\n.row{display:flex;gap:12px;align-items:center;margin-top:10px}\n.vuPair{display:flex;gap:10px;align-items:flex-end}\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(--err) 0%, var(--warn) 50%, var(--ok) 100%)}\n.scope{flex:1;height:160px;border-radius:12px;border:1px solid var(--stroke);background:radial-gradient(1000px 300px at 50% 10%,rgba(18,110,168,.18),transparent),linear-gradient(180deg,#0a2133,#081c2c)}\n.badge{background:var(--panel2);border:1px solid var(--stroke);border-radius:999px;padding:6px 10px;font-size:12px;margin-top:10px;display:inline-block}\n@media(max-width:980px){.cards{grid-template-columns:1fr}.toolbar{grid-template-columns:1fr 1fr;grid-auto-rows:auto}}\n<\/style>\n<\/head>\n<body>\n<main class=\"main\">\n  <div class=\"gltec-wrap\">\n    <div class=\"gltec-window\">\n      <div class=\"gltec-topbar\">\n        <div class=\"gltec-dots\"><span class=\"gltec-dot gltec-red\"><\/span><span class=\"gltec-dot gltec-yellow\"><\/span><span class=\"gltec-dot gltec-green\"><\/span><\/div>\n        <div class=\"gltec-title\">GLTEC \u2014 Codec (Multiponto) \u2022 CBR<\/div>\n      <\/div>\n      <div class=\"gltec-body\" id=\"gltec-app\">\n        <div class=\"toolbar\">\n          <div><label>C\u00f3digo \/ Sala<\/label><input id=\"roomCode\" class=\"inp\" placeholder=\"ex.: garcia30\"><\/div>\n          <div><label>Taxa (CBR)<\/label>\n            <select id=\"bitrateSel\" class=\"sel\">\n              <option value=\"64000\">Opus 64 kbps<\/option>\n              <option value=\"96000\">Opus 96 kbps<\/option>\n              <option value=\"128000\" selected>Opus 128 kbps<\/option>\n              <option value=\"192000\">Opus 192 kbps<\/option>\n              <option value=\"256000\">Opus 256 kbps<\/option>\n              <option value=\"320000\">Opus 320 kbps<\/option>\n              <option value=\"512000\">Opus 512 kbps<\/option>\n              <option value=\"650000\">Opus 650 kbps<\/option>\n            <\/select>\n          <\/div>\n          <div><label>Sa\u00edda<\/label><select id=\"outSel\" class=\"sel\"><\/select><\/div>\n          <div><label>Microfone<\/label><select id=\"micSel\" class=\"sel\"><\/select><\/div>\n          <div class=\"actions\">\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\"><path d=\"M8 8l8 8\"><\/path><path d=\"M16 8l-8 8\"><\/path><path d=\"M3 12h3\"><\/path><path d=\"M18 12h3\"><\/path><circle cx=\"12\" cy=\"12\" r=\"9\" opacity=\".2\"><\/circle><\/svg>\n            <\/button>\n            <button id=\"btnClear\" class=\"iconbtn\" aria-label=\"Sair\/Resetar\" title=\"Sair\/Resetar\">\n              <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 12a9 9 0 1 1-2.64-6.36\"><\/path><path d=\"M21 3v6h-6\"><\/path><\/svg>\n            <\/button>\n          <\/div>\n        <\/div>\n\n        <div class=\"cards\">\n          <div class=\"card\">\n            <div class=\"hdr\"><div class=\"name\">Est\u00fadio (Voc\u00ea)<\/div><\/div>\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          <\/div>\n\n          <div class=\"card\">\n            <div class=\"hdr\"><div class=\"name\">Conex\u00e3o (Mix Remoto)<\/div><\/div>\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          <\/div>\n        <\/div>\n\n        <div class=\"badge\">Sala: <b id=\"cardRoom\">\u2014<\/b><\/div>\n        <div class=\"badge\">ICE: <b id=\"connState\">OFF<\/b><\/div>\n        <div class=\"badge\">Status: <b id=\"statusChip\">OFF<\/b><\/div>\n\n        <audio id=\"remoteAudio\" autoplay playsinline style=\"display:none\"><\/audio>\n      <\/div>\n    <\/div>\n  <\/div>\n  <footer class=\"footer\">\u00a9 GLTEC Brasil \u2014 Codec  Multiponto  <\/footer>\n<\/main>\n\n<script>\n\/\/ === API do seu WP (plugin mesh ativo) ===\nconst API_BASE = 'https:\/\/gltecbrasil.com.br';\nconst API = (p)=> API_BASE + '\/wp-json\/gltec\/v1' + p;\n\nconst configuration = { iceServers: [{urls:'stun:stun.l.google.com:19302'},{urls:'stun:global.stun.twilio.com:3478'}] };\nconst el = (id)=> document.getElementById(id);\nconst uuid = ()=> 'c'+Math.random().toString(36).slice(2)+Date.now().toString(36);\nconst pctFromDB = (db)=>{ const c=Math.max(-60,Math.min(0,db)); return Math.round((c+60)\/60*100); };\nconst fixCanvas = (c)=>{ const dpr=window.devicePixelRatio||1; c.width=c.clientWidth*dpr; c.height=c.clientHeight*dpr; };\nconst chosenBitrate = ()=> parseInt(el('bitrateSel').value || '128000', 10); \/\/ bps\n\nconst state = {\n  clientId: uuid(),\n  room:null, since:0, pollTimer:null,\n  pcs:new Map(), stats:new Map(),\n  localStream:null, ac:null, anLL:null, anLR:null,\n  mix: new MediaStream(), acR:null, anRL:null, anRR:null,\n  vpnOnly:false,\n};\nconst getAC  = ()=> state.ac  ||= new (window.AudioContext||window.webkitAudioContext)();\nconst getACR = ()=> state.acR ||= new (window.AudioContext||window.webkitAudioContext)();\n\n\/\/ ---- SDP munging para for\u00e7ar CBR + maxaveragebitrate (Opus) ----\nfunction forceOpusCBR(sdp, maxAvg){\n  try{\n    const m = \/a=rtpmap:(\\d+)\\s+opus\\\/48000\\\/?\\d*\/i.exec(sdp);\n    if(!m) return sdp;\n    const pt = m[1];\n    const reFmtp = new RegExp(`a=fmtp:${pt} ([^\\\\r\\\\n]*)`, 'i');\n    if(reFmtp.test(sdp)){\n      sdp = sdp.replace(reFmtp, (line, params)=>{\n        const map = {};\n        params.split(';').map(p=>p.trim()).filter(Boolean).forEach(kv=>{\n          const [k,v] = kv.split('=');\n          map[k.trim()] = (v||'').trim();\n        });\n        map['stereo'] = map['stereo'] || '1';\n        map['sprop-stereo'] = map['sprop-stereo'] || '1';\n        map['cbr'] = '1';\n        map['maxaveragebitrate'] = String(maxAvg);\n        map['useinbandfec'] = map['useinbandfec'] || '1';\n        const merged = Object.entries(map).map(([k,v])=> v?`${k}=${v}`:k).join(';');\n        return `a=fmtp:${pt} ${merged}`;\n      });\n    }else{\n      const add = `a=fmtp:${pt} stereo=1; sprop-stereo=1; cbr=1; maxaveragebitrate=${maxAvg}; useinbandfec=1`;\n      sdp = sdp.replace(new RegExp(`a=rtpmap:${pt}.*`, 'i'), (line)=> line + `\\\\r\\\\n${add}`);\n    }\n    return sdp;\n  }catch(e){ return sdp; }\n}\n\n\/\/ ---- aplica bitrate CBR nos senders ----\nasync function applyBitrateToPC(pc, bps){\n  try{\n    pc.getSenders().forEach(async (s)=>{\n      if(s.track && s.track.kind==='audio'){\n        const p = s.getParameters();\n        if(!p.encodings) p.encodings = [{}];\n        p.encodings[0].maxBitrate = bps; \/\/ bps\n        await s.setParameters(p).catch(()=>{});\n      }\n    });\n  }catch(e){}\n}\n\nconst http = {\n  join : async (room)=> (await fetch(API('\/join'), {method:'POST',headers:{'Content-Type':'application\/json'},body:JSON.stringify({room,peerId:state.clientId})})).json(),\n  pull : async (room)=> (await fetch(API('\/pull')+`?room=${encodeURIComponent(room)}&since=${state.since}`,{cache:'no-store'})).json(),\n  push : async (room,type,payload,to=null)=> (await fetch(API('\/push'),{method:'POST',headers:{'Content-Type':'application\/json'},body:JSON.stringify({room,type,payload,from:state.clientId,to})})).json(),\n  leave: async (room)=> fetch(API('\/leave'), {method:'POST',headers:{'Content-Type':'application\/json'},body:JSON.stringify({room})}),\n  clear: async (room)=> (await fetch(API('\/clear'), {method:'POST',headers:{'Content-Type':'application\/json'},body:JSON.stringify({room})})).json()\n};\n\nasync function listDevices(){\n  const devs=await navigator.mediaDevices.enumerateDevices();\n  const mics=devs.filter(d=>d.kind==='audioinput'), outs=devs.filter(d=>d.kind==='audiooutput');\n  const micSel=el('micSel'); micSel.innerHTML='';\n  mics.forEach((d,i)=>{const o=document.createElement('option'); o.value=d.deviceId; o.text=d.label||('Mic '+(i+1)); micSel.appendChild(o);});\n  const outSel=el('outSel'); outSel.innerHTML='';\n  outs.forEach((d,i)=>{const o=document.createElement('option'); o.value=d.deviceId; o.text=d.label||('Sa\u00edda '+(i+1)); outSel.appendChild(o);});\n}\n\nasync function ensureLocal(){\n  if(state.localStream) return;\n  const mic=el('micSel').value || undefined;\n  state.localStream = await navigator.mediaDevices.getUserMedia({\n    audio:{deviceId:mic?{exact:mic}:undefined,channelCount:2,echoCancellation:false,noiseSuppression:false,autoGainControl:false,sampleRate:48000},\n    video:false\n  });\n  const ac=getAC(), src=ac.createMediaStreamSource(state.localStream), split=ac.createChannelSplitter(2);\n  src.connect(split);\n  state.anLL=ac.createAnalyser(); state.anLR=ac.createAnalyser(); state.anLL.fftSize=2048; state.anLR.fftSize=2048;\n  split.connect(state.anLL,0); split.connect(state.anLR,1);\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(!state.anLL||!state.anLR){requestAnimationFrame(loop);return}\n    state.anLL.getFloatTimeDomainData(bL); state.anLR.getFloatTimeDomainData(bR);\n    const rmsL=Math.sqrt(bL.reduce((s,v)=>s+v*v,0)\/bL.length)||1e-6, rmsR=Math.sqrt(bR.reduce((s,v)=>s+v*v,0)\/bR.length)||1e-6;\n    el('vuL_IN').style.height = pctFromDB(20*Math.log10(rmsL))+'%';\n    el('vuR_IN').style.height = pctFromDB(20*Math.log10(rmsR))+'%';\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\nfunction ensureRemoteAnalyser(){\n  const ac=getACR();\n  const src=ac.createMediaStreamSource(state.mix);\n  const split=ac.createChannelSplitter(2);\n  src.connect(split);\n  state.anRL=ac.createAnalyser(); state.anRR=ac.createAnalyser(); state.anRL.fftSize=2048; state.anRR.fftSize=2048;\n  split.connect(state.anRL,0); split.connect(state.anRR,1);\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(!state.anRL||!state.anRR){requestAnimationFrame(loop);return}\n    state.anRL.getFloatTimeDomainData(bL); state.anRR.getFloatTimeDomainData(bR);\n    const rmsL=Math.sqrt(bL.reduce((s,v)=>s+v*v,0)\/bL.length)||1e-6, rmsR=Math.sqrt(bR.reduce((s,v)=>s+v*v,0)\/bR.length)||1e-6;\n    el('vuL_OUT').style.height = pctFromDB(20*Math.log10(rmsL))+'%';\n    el('vuR_OUT').style.height = pctFromDB(20*Math.log10(rmsR))+'%';\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\nasync function applyBitrateNow(){\n  const bps = chosenBitrate();\n  state.pcs.forEach(pc=> applyBitrateToPC(pc, bps));\n}\n\nfunction getOrCreatePC(peerId){\n  if(state.pcs.has(peerId)) return state.pcs.get(peerId);\n  const pc = new RTCPeerConnection(configuration);\n  \/\/ adiciona trilhas locais\n  state.localStream.getTracks().forEach(t=>pc.addTrack(t, state.localStream));\n  \/\/ aplica bitrate CBR assim que poss\u00edvel\n  applyBitrateToPC(pc, chosenBitrate());\n  pc.onicecandidate = async ev=>{\n    if(!ev.candidate || !state.room) return;\n    if(state.vpnOnly){ const c=ev.candidate.candidate||''; if(!c.includes(' typ host ')) return; }\n    await http.push(state.room, 'ice', {candidate: ev.candidate}, peerId);\n  };\n  pc.ontrack = ev=>{\n    ev.streams[0].getAudioTracks().forEach(t=> state.mix.addTrack(t));\n    const a = el('remoteAudio'); a.srcObject = state.mix;\n    const outId=el('outSel').value; if(a.setSinkId && outId){ a.setSinkId(outId).catch(()=>{}); }\n    a.muted=false; a.play().catch(()=>{});\n    ensureRemoteAnalyser();\n    el('connState').textContent='CONNECTED'; el('statusChip').textContent='ON';\n  };\n  \/\/ patch de createOffer\/createAnswer para injetar CBR no SDP\n  const _createOffer = pc.createOffer.bind(pc);\n  pc.createOffer = async (opts)=>{\n    const desc = await _createOffer(opts || {offerToReceiveAudio:true, voiceActivityDetection:false});\n    desc.sdp = forceOpusCBR(desc.sdp || '', chosenBitrate());\n    return desc;\n  };\n  const _createAnswer = pc.createAnswer.bind(pc);\n  pc.createAnswer = async (opts)=>{\n    const desc = await _createAnswer(opts || {voiceActivityDetection:false});\n    desc.sdp = forceOpusCBR(desc.sdp || '', chosenBitrate());\n    return desc;\n  };\n  state.pcs.set(peerId, pc);\n  state.stats.set(peerId, {});\n  return pc;\n}\n\nasync function handleSignal(it){\n  const {type, payload, from, to} = it;\n  if(to && to!==state.clientId) return;\n  if(from===state.clientId) return;\n\n  if(type==='hello'){\n    if(state.clientId < from){\n      const pc = getOrCreatePC(from);\n      const off = await pc.createOffer({offerToReceiveAudio:true, voiceActivityDetection:false});\n      await pc.setLocalDescription(off);\n      await waitIce(pc);\n      await http.push(state.room, 'sdp', pc.localDescription, from);\n    }\n  } else if(type==='sdp'){\n    const pc = getOrCreatePC(from);\n    if(payload.type==='offer'){\n      await pc.setRemoteDescription(payload);\n      const ans = await pc.createAnswer({iceRestart:false});\n      await pc.setLocalDescription(ans);\n      await waitIce(pc);\n      await http.push(state.room, 'sdp', pc.localDescription, from);\n    } else if(payload.type==='answer'){\n      if(pc.signalingState!=='stable'){ await pc.setRemoteDescription(payload); }\n    }\n  } else if(type==='ice'){\n    const pc = getOrCreatePC(from);\n    try { await pc.addIceCandidate(payload.candidate); } catch(e){}\n  }\n}\nfunction waitIce(pc){\n  return new Promise(res=>{\n    if(pc.iceGatheringState==='complete') return res();\n    const f=()=>{ if(pc.iceGatheringState==='complete'){ pc.removeEventListener('icegatheringstatechange',f); res(); } };\n    pc.addEventListener('icegatheringstatechange', f);\n    setTimeout(res, 4000);\n  });\n}\n\nfunction startPolling(){\n  if(state.pollTimer) clearInterval(state.pollTimer);\n  state.pollTimer = setInterval(async ()=>{\n    const j = await http.pull(state.room);\n    if(!j.ok) return;\n    state.since = j.now || state.since;\n    (j.items || []).forEach(handleSignal);\n  }, 900);\n}\n\nasync function startRoom(room){\n  const j = await http.join(room);\n  if(!j.ok){ alert('Falha ao entrar na sala'); return; }\n  state.room = room; state.since = 0; el('cardRoom').textContent = room;\n  await ensureLocal(); await listDevices();\n  startPolling();\n  await http.push(room, 'hello', {hi:true});\n}\nasync function leaveRoom(){\n  if(state.room){ await http.leave(state.room); }\n  if(state.pollTimer){ clearInterval(state.pollTimer); state.pollTimer=null; }\n  state.pcs.forEach(pc=>{ try{pc.close()}catch(e){} });\n  state.pcs.clear(); state.stats.clear();\n  state.mix.getAudioTracks().forEach(t=>t.stop());\n  state.mix = new MediaStream();\n  el('remoteAudio').srcObject = null;\n  el('connState').textContent='OFF'; el('statusChip').textContent='OFF'; el('cardRoom').textContent='\u2014';\n  state.room = null;\n}\n\n\/\/ eventos UI\nwindow.addEventListener('resize', ()=>{\n  const L=document.getElementById('scopeLocal'); const R=document.getElementById('scopeRemote');\n  if(L){const d=window.devicePixelRatio||1;L.width=L.clientWidth*d;L.height=L.clientHeight*d;}\n  if(R){const d=window.devicePixelRatio||1;R.width=R.clientWidth*d;R.height=R.clientHeight*d;}\n});\ndocument.getElementById('outSel').addEventListener('change', ()=>{\n  const a = document.getElementById('remoteAudio'), outId=document.getElementById('outSel').value;\n  if(a.setSinkId && outId){ a.setSinkId(outId).catch(()=>{}); }\n});\ndocument.getElementById('bitrateSel').addEventListener('change', applyBitrateNow);\n\ndocument.getElementById('btnConnect').onclick = async ()=>{\n  try{ await (new (window.AudioContext||window.webkitAudioContext)()).resume(); }catch(e){}\n  try{ await navigator.mediaDevices.getUserMedia({audio:true}); }catch(e){}\n  const room = (document.getElementById('roomCode').value||'').trim();\n  if(!room){ alert('Digite a sala'); return; }\n  await startRoom(room);\n};\ndocument.getElementById('btnClear').onclick = async ()=>{\n  const room = (document.getElementById('roomCode').value||'').trim();\n  if(!room){ alert('Digite a sala'); return; }\n  await leaveRoom();\n  const r = await http.clear(room);\n  if(r.ok) alert('Sala resetada.');\n};\n\n\/\/ boot\n(async function(){\n  try{ await navigator.mediaDevices.getUserMedia({audio:true}); await listDevices(); }catch(e){ await listDevices(); }\n  const L=document.getElementById('scopeLocal'); const R=document.getElementById('scopeRemote');\n  if(L){const d=window.devicePixelRatio||1;L.width=L.clientWidth*d;L.height=L.clientHeight*d;}\n  if(R){const d=window.devicePixelRatio||1;R.width=R.clientWidth*d;R.height=R.clientHeight*d;}\n})();\n<\/script>\n<\/body>\n<\/html>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-757a6b9 e-flex e-con-boxed e-con e-parent\" data-id=\"757a6b9\" 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-3dc9941 elementor-widget elementor-widget-shortcode\" data-id=\"3dc9941\" data-element_type=\"widget\" data-widget_type=\"shortcode.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-shortcode\">      <div id=\"gltec-app\" data-room=\"garcia30\"><\/div>\n    <\/div>\n\t\t\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 (Multiponto) GLTEC \u2014 Codec (Multiponto) \u2022 CBR C\u00f3digo \/ Sala Taxa (CBR) Opus 64 kbpsOpus 96 kbpsOpus 128 kbpsOpus 192 kbpsOpus 256 kbpsOpus 320 kbpsOpus 512 kbpsOpus 650 kbps Sa\u00edda Microfone Est\u00fadio (Voc\u00ea) L R Conex\u00e3o (Mix Remoto) L R Sala: \u2014 ICE: OFF Status: OFF \u00a9 GLTEC Brasil \u2014 Codec Multiponto<\/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-300","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/pages\/300","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=300"}],"version-history":[{"count":13,"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/pages\/300\/revisions"}],"predecessor-version":[{"id":314,"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/pages\/300\/revisions\/314"}],"wp:attachment":[{"href":"https:\/\/gltecbrasil.com.br\/index.php\/wp-json\/wp\/v2\/media?parent=300"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}