HEX
Server: Apache
System: Linux srv.kreative-web.pt 4.18.0-553.8.1.lve.el8.x86_64 #1 SMP Thu Jul 4 16:24:39 UTC 2024 x86_64
User: kevinefranco (1040)
PHP: 8.2.30
Disabled: mail,system,passthru,exec,popen,proc_close,proc_get_status,proc_nice,proc_open,proc_terminate,shell_exec,ini_restore
Upload Files
File: //home/kevinefranco/public_html/zoommeeting/audio/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
<title>Zoom Mimic — Live Demo v2</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
  html,body{height:100%}
  body{margin:0;background:linear-gradient(180deg,#071329,#041022);color:white;font-family:Inter,system-ui,-apple-system,Segoe UI}
  .card{background:rgba(20,27,38,0.55);backdrop-filter:blur(6px);border-radius:14px}
  .slide-up{transform:translateY(12px);opacity:0;transition:transform 420ms cubic-bezier(.2,.9,.2,1),opacity 420ms}
  .slide-up.show{transform:translateY(0);opacity:1}
  .speaking-ring{box-shadow:0 0 0 0 rgba(96,165,250,0);transition:box-shadow 160ms}
  .speaking-ring.on{box-shadow:0 0 0 12px rgba(96,165,250,0.12)}
  #waveCanvas{width:100%;height:56px;display:block}
  .participant.card-active{outline:3px solid rgba(99,102,241,0.12);transform:scale(1.02);transition:transform .25s,outline .25s}
  .mic-dot{width:10px;height:10px;border-radius:50%;background:#06b6d4;opacity:0;transform:scale(0.6);transition:opacity 120ms,transform 120ms}
  .mic-dot.on{opacity:1;transform:scale(1)}
  .fit-screen{min-height:calc(100vh - 12px)}
</style>
</head>
<body class="fit-screen">

<div class="max-w-md mx-auto p-4 h-full flex flex-col">

  <!-- Lobby -->
  <div id="lobby" class="card p-4 mb-4">
    <div class="flex items-start gap-3">
      <div class="w-14 h-14 rounded-xl bg-[#0b1220] flex items-center justify-center">
        <span class="text-blue-400 font-bold">zoom</span>
      </div>
      <div class="flex-1">
        <h2 class="text-2xl font-extrabold text-blue-400">Zoom Meeting</h2>
        <p class="text-sm text-slate-300">Ready to join</p>
      </div>
    </div>

    <div class="mt-6 rounded-xl p-4 bg-[#051124]">
      <h3 class="text-lg font-bold">Zoom Meeting - <span id="meetingId">1764987494729</span></h3>

      <div class="mt-4 rounded-lg p-3 flex items-center gap-3 bg-[#071829]">
        <img id="hostImg" src="https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&w=800&q=80" class="w-12 h-12 rounded-full object-cover ring-2 ring-slate-700" alt="host" />
        <div>
          <div class="font-semibold">Adelheid Schmidt</div>
          <div class="text-xs text-slate-400">Meeting Host • Project Manager</div>
        </div>
      </div>

      <div class="mt-4 flex justify-between items-center text-slate-300">
        <div>Today</div>
        <div>45 min</div>
      </div>

      <div class="mt-4">
        <div class="text-slate-300 mb-2">4 people are expected to join</div>
        <button id="joinBtn" class="w-full py-3 rounded-xl bg-blue-600 hover:bg-blue-500 font-semibold">Join Meeting</button>
      </div>

      <p class="text-xs text-slate-500 mt-3">Camera and microphone will be activated based on your permissions</p>
      <div class="mt-4 text-center text-xs text-slate-600 bg-[#0a1320] py-2 rounded">ID: meeting-1764987494729</div>
    </div>
  </div>

  <!-- Preparing overlay -->
  <div id="preparing" class="card p-4 mb-4 hidden">
    <div class="flex flex-col items-center text-center">
      <div class="w-16 h-16 rounded-2xl bg-[#071329] flex items-center justify-center mb-3 text-blue-400 font-bold">zoom</div>
      <h2 class="text-xl font-bold">Zoom Meeting</h2>
      <p class="text-slate-300 mt-2">Preparing your meeting experience...</p>
      <div class="mt-3 text-slate-400">Meeting ID: <span id="prepId">meeting-1764987494729</span></div>
    </div>
  </div>

  <!-- Meeting UI -->
  <div id="meeting" class="hidden flex-1 flex flex-col justify-between">
    <div class="card p-3 mb-3">
      <div class="flex items-center justify-between">
        <div>
          <div class="text-sm text-slate-300">Zoom Meeting</div>
          <div class="text-xs text-slate-400">Host: Adelheid Schmidt</div>
        </div>
        <div class="text-xs text-slate-400">Meeting ID: meeting-1764987494729</div>
      </div>
    </div>

    <div class="card rounded-2xl overflow-hidden flex-1 flex flex-col">
      <div class="relative flex-1 bg-black flex items-center justify-center">
        <video id="localVideo" autoplay playsinline muted class="w-full h-full object-cover"></video>
        <div id="coverFallback" class="absolute inset-0 flex items-center justify-center p-4">
          <img id="mainCover" src="https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&w=800&q=80" class="w-28 h-28 rounded-md object-cover shadow-md" alt="cover" />
          <div class="absolute left-4 bottom-4 text-white">
            <div class="text-lg font-bold">Adelheid Schmidt</div>
            <div class="text-sm text-slate-300">Meeting Host • Project Manager</div>
          </div>
        </div>

        <div id="micIndicator" class="absolute right-4 top-4 mic-ring speaking-ring p-2 rounded-full bg-black/40 flex items-center gap-2">
          <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M12 14a3 3 0 0 0 3-3V5a3 3 0 0 0-6 0v6a3 3 0 0 0 3 3z"/><path d="M19 11a1 1 0 0 1 2 0 7 7 0 0 1-7 7v2h-2v-2a7 7 0 0 1-7-7 1 1 0 0 1 2 0 5 5 0 0 0 5 5h2a5 5 0 0 0 5-5z"/></svg>
          <div class="mic-dot" id="micDot"></div>
        </div>
      </div>

      <div class="p-3">
        <canvas id="waveCanvas"></canvas>
        <div class="mt-3 flex items-center justify-between">
          <div class="text-slate-300 font-semibold">Others</div>
          <div class="text-slate-400 text-sm" id="peopleCount">4 people</div>
        </div>
        <div id="participantsList" class="mt-3 grid grid-cols-3 gap-3">
        </div>
      </div>

      <div class="bg-[#071329] p-3 flex items-center justify-between">
        <div class="flex gap-2">
          <button id="muteBtn" class="control-btn px-3 py-2 rounded-lg bg-red-600 text-white font-semibold">Unmute</button>
          <button id="videoToggleBtn" class="control-btn px-3 py-2 rounded-lg pill">Video</button>
          <button id="recordBtn" class="control-btn px-3 py-2 rounded-lg pill">Record</button>
        </div>
        <div class="flex gap-2">
          <button id="simulateTalkBtn" class="control-btn px-3 py-2 rounded-lg pill">Simulate Talk</button>
          <button id="leaveBtn" class="control-btn px-3 py-2 rounded-lg bg-red-600 text-white font-semibold">Leave</button>
        </div>
      </div>
    </div>
  </div>

</div>

<!-- audio elements -->
<audio id="conversation-1" src="conversation-1.mp3"></audio>\n<audio id="conversation-2" src="conversation-2.mp3"></audio>\n<audio id="conversation-3" src="conversation-3.mp3"></audio>\n<audio id="mute" src="mute.mp3"></audio>\n<audio id="unmute" src="unmute.mp3"></audio>\n<audio id="recording-start" src="recording-start.mp3"></audio>\n<audio id="recording-stop" src="recording-stop.mp3"></audio>\n<audio id="new-speaker" src="new-speaker.mp3"></audio>\n<audio id="user-joined" src="user-joined.mp3"></audio>\n<audio id="user-left" src="user-left.mp3"></audio>\n
<script>
document.addEventListener('DOMContentLoaded', function(){
  const joinBtn = document.getElementById('joinBtn');
  const lobby = document.getElementById('lobby');
  const preparing = document.getElementById('preparing');
  const meeting = document.getElementById('meeting');
  const localVideo = document.getElementById('localVideo');
  const coverFallback = document.getElementById('coverFallback');
  const participantsList = document.getElementById('participantsList');
  const muteBtn = document.getElementById('muteBtn');
  const videoToggleBtn = document.getElementById('videoToggleBtn');
  const recordBtn = document.getElementById('recordBtn');
  const simulateTalkBtn = document.getElementById('simulateTalkBtn');
  const leaveBtn = document.getElementById('leaveBtn');
  const micIndicator = document.getElementById('micIndicator');
  const micDot = document.getElementById('micDot');
  const waveCanvas = document.getElementById('waveCanvas');
  const peopleCount = document.getElementById('peopleCount');

  const otherImages = ['https://i.pravatar.cc/300?img=12','https://i.pravatar.cc/300?img=47','https://i.pravatar.cc/300?img=15','https://i.pravatar.cc/300?img=56','https://i.pravatar.cc/300?img=32'];
  const namePool = ['James Whitmore','Marcus Freitag','Olivia Chen','Noah Bennett','Lucas Ruiz','Sofia Moreno','Amina Yusuf','Kareem Adewale','Lucas Ruiz'];

  // helper to play audio safely
  function play(id){ try{ const el = document.getElementById(id); if(el){ el.currentTime = 0; el.play().catch(()=>{}); } } catch(e){ console.warn('play err', e); } }

  // ensure we use the uploaded audio names if present
  const sounds = {
    conversation1: document.getElementById('conversation-1'),
    conversation2: document.getElementById('conversation-2'),
    conversation3: document.getElementById('conversation-3'),
    mute: document.getElementById('mute'),
    unmute: document.getElementById('unmute'),
    recStart: document.getElementById('recording-start'),
    recStop: document.getElementById('recording-stop'),
    newSpeaker: document.getElementById('new-speaker'),
    joined: document.getElementById('user-joined'),
    left: document.getElementById('user-left')
  };

  function safePlayAudio(el){ try{ if(!el) return; el.currentTime = 0; el.play().catch(()=>{}); }catch(e){ } }

  function pickRandom(arr){ return arr[Math.floor(Math.random()*arr.length)]; }

  // populate participants (including host)
  const participants = [
    {id:'p_host', name:'Adelheid Schmidt', title:'Meeting Host • Project Manager', img:'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&w=800&q=80', local:true},
  ];

  // helper to create participant element
  function createParticipant(p){
    const div = document.createElement('div');
    div.className = 'bg-[#071829] p-2 rounded-lg flex flex-col items-center slide-up';
    div.dataset.id = p.id;
    div.innerHTML = `<img src="${p.img}" class="w-20 h-20 rounded-lg object-cover mb-2"><div class="text-sm font-semibold">${p.name}</div><div class="text-xs text-slate-400">${p.title}</div><div class="mt-2 mic-dot" id="dot-${p.id}"></div>`;
    return div;
  }

  // simulate join/leave sequence: join, someone leaves, rejoins, etc.
  function simulateJoinLeaveSequence(){
    const randomNames = [ 'Lucas Ruiz', 'Noah Bennett', 'Sofia Moreno', 'Kareem Adewale' ];
    const chosen = [];
    // first two join quickly, then one leaves, then one rejoins
    for(let i=0;i<2;i++){
      const name = pickRandom(randomNames);
      const id = 'p_'+Math.random().toString(36).slice(2,7);
      const img = pickRandom(otherImages);
      const p = {id:id, name:name, title: i===0 ? 'Product Manager' : 'Sales Lead', img:img};
      chosen.push(p);
      setTimeout(()=>{
        const el = createParticipant(p);
        participantsList.appendChild(el);
        // reveal animation
        setTimeout(()=> el.classList.add('show'), 80);
        safePlayAudio('user-joined');
      }, 600 + i*1000);
    }
    // one leaves after 2500ms
    setTimeout(()=>{
      const first = participantsList.querySelector('[data-id]');
      if(first){ safePlayAudio('user-left'); first.remove(); }
    }, 2500);
    // someone rejoins after 3800ms
    setTimeout(()=>{
      const p = {id:'p_'+Math.random().toString(36).slice(2,7), name: pickRandom(randomNames), title:'Sales Lead', img: pickRandom(otherImages)};
      const el = createParticipant(p);
      participantsList.appendChild(el);
      setTimeout(()=> el.classList.add('show'), 80);
      safePlayAudio('user-joined');
    }, 3800);
    // final stable join of two participants after 4800ms
    setTimeout(()=>{
      // ensure at least two participants present (besides host)
      while(participantsList.querySelectorAll('[data-id]').length < 2){
        const p = {id:'p_'+Math.random().toString(36).slice(2,7), name: pickRandom(randomNames), title:'Engineer', img: pickRandom(otherImages)};
        const el = createParticipant(p);
        participantsList.appendChild(el);
        setTimeout(()=> el.classList.add('show'), 80);
        safePlayAudio('user-joined');
      }
      // update people count
      peopleCount.innerText = 1 + participantsList.querySelectorAll('[data-id]').length + ' people';
    }, 4800);
  }

  // speaking indicator animation for a participant element
  function setSpeaking(participantId, on){
    // highlight element and dot
    const el = participantsList.querySelector('[data-id="'+participantId+'"]');
    if(!el) return;
    if(on){ el.classList.add('card-active'); const dot = document.getElementById('dot-'+participantId); if(dot) dot.classList.add('on'); }
    else { el.classList.remove('card-active'); const dot = document.getElementById('dot-'+participantId); if(dot) dot.classList.remove('on'); }
  }

  // randomly pick a participant to "speak" and show animated effect; play new-speaker and conversation clips
  function randomSpeakerTurn(){
    const items = participantsList.querySelectorAll('[data-id]');
    if(!items || items.length===0) return;
    const idx = Math.floor(Math.random()*items.length);
    const el = items[idx];
    const pid = el.dataset.id;
    // play new speaker sound
    safePlayAudio('new-speaker');
    // mark speaking on
    setSpeaking(pid, true);
    // play a short conversation clip randomly
    const conv = Math.random();
    if(conv < 0.4) safePlayAudio('conversation-1');
    else if(conv < 0.8) safePlayAudio('conversation-2');
    else safePlayAudio('conversation-3');
    // after a short duration, clear speaking
    setTimeout(()=> setSpeaking(pid, false), 1200 + Math.random()*900);
  }

  // simulate a longer conversation with turns
  function startSimulatedConversation(){
    // initial host speaks
    setTimeout(()=>{ safePlayAudio('conversation-1'); }, 900);
    // then random speaker turns repeatedly
    const iv = setInterval(()=>{
      randomSpeakerTurn();
    }, 2200 + Math.random()*1200);
    // stop after 30s to avoid runaway in demo; clear interval later if needed
    setTimeout(()=> clearInterval(iv), 30000);
  }

  // mic visual: pulse ring when local mic sees audio (using analyser if available)
  let audioCtx, analyser, source, dataArray;
  async function setupLocalMicVisual(stream){
    try{
      audioCtx = audioCtx || new (window.AudioContext || window.webkitAudioContext)();
      source = audioCtx.createMediaStreamSource(stream);
      analyser = audioCtx.createAnalyser();
      analyser.fftSize = 256;
      analyser.smoothingTimeConstant = 0.8;
      source.connect(analyser);
      dataArray = new Uint8Array(analyser.fftSize);
      monitorLocalMic();
    }catch(e){ console.warn('mic visual failed', e); }
  }
  function monitorLocalMic(){
    try{
      if(!analyser) return;
      analyser.getByteTimeDomainData(dataArray);
      let sum = 0;
      for(let i=0;i<dataArray.length;i++){ const v = (dataArray[i]-128)/128; sum += v*v; }
      const rms = Math.sqrt(sum / dataArray.length);
      const speaking = rms > 0.02;
      if(micIndicator){ if(speaking) micIndicator.classList.add('on'); else micIndicator.classList.remove('on'); }
      if(micDot){ if(speaking) micDot.classList.add('on'); else micDot.classList.remove('on'); }
    }catch(e){}
    setTimeout(monitorLocalMic, 120);
  }

  // main: startMedia, show UI sequence
  async function startMediaAndShow(){
    try{
      const stream = await navigator.mediaDevices.getUserMedia({video:{width:640,height:360}, audio:true});
      // attach stream to video element
      localVideo.srcObject = stream;
      localVideo.muted = true;
      try{ await localVideo.play(); }catch(e){}
      coverFallback.style.display = 'none';
      // setup mic visual
      setupLocalMicVisual(stream);
    }catch(e){
      console.warn('media blocked or failed', e);
      coverFallback.style.display = 'flex';
    }
  }

  // join click handler: show preparing, then show meeting after ~3s and simulate join/leave and conversation
  joinBtn && joinBtn.addEventListener('click', async function(){
    // hide lobby, show preparing
    lobby.style.display = 'none';
    preparing.classList.remove('hidden');
    void preparing.offsetWidth;
    preparing.classList.add('show');
    // wait 1s then request media, then wait 2s more (total ~3s)
    setTimeout(()=> startMediaAndShow(), 1000);
    setTimeout(()=>{
      preparing.classList.add('hidden');
      meeting.classList.remove('hidden');
      // ensure cover hidden if camera started
      // simulate join/leave sequence
      simulateJoinLeaveSequence();
      // start simulated conversation turns and speaker highlights
      startSimulatedConversation();
    }, 3000);
  });

  // Mute/unmute toggles local audio tracks and plays sound
  muteBtn && muteBtn.addEventListener('click', function(){
    const currentlyMuted = muteBtn.dataset.muted === '1';
    const newMuted = !currentlyMuted;
    muteBtn.dataset.muted = newMuted ? '1' : '0';
    muteBtn.innerText = newMuted ? 'Unmute' : 'Mute';
    safePlayAudio(newMuted ? 'mute' : 'unmute');
    // toggle tracks
    const tracks = localVideo && localVideo.srcObject ? localVideo.srcObject.getAudioTracks() : [];
    tracks && tracks.forEach(t=> t.enabled = !newMuted);
  });

  // Record button plays start and stop sounds and toggles a small visual
  recordBtn && recordBtn.addEventListener('click', function(){
    safePlayAudio('recording-start');
    // small visual cue on micIndicator
    micIndicator.classList.add('recording');
    setTimeout(()=>{ safePlayAudio('recording-stop'); micIndicator.classList.remove('recording'); }, 4000);
  });

  // Simulate Talk button triggers immediate random speaker turn (for demo)
  simulateTalkBtn && simulateTalkBtn.addEventListener('click', function(){ randomSpeakerTurn(); });

  // Leave handler
  leaveBtn && leaveBtn.addEventListener('click', function(){ safePlayAudio('user-left'); setTimeout(()=> location.reload(), 600); });

  // start: create host element in participants list
  const host = {id:'p_host', name:'Adelheid Schmidt', title:'Meeting Host', img:'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&w=800&q=80'};
  const hostEl = createParticipant(host);
  participantsList.appendChild(hostEl);
  setTimeout(()=> hostEl.classList.add('show'), 80);

  // Expose for debugging
  window._zoomDemo = { simulateJoinLeaveSequence, randomSpeakerTurn, startSimulatedConversation };

}); // DOMContentLoaded
</script>
</body>
</html>