Spaces:
Running
Running
| <html lang="pt-BR"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Microphone Checker | Teste seu Microfone</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .waveform { | |
| height: 80px; | |
| background: linear-gradient(90deg, #3b82f6, #8b5cf6); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .waveform::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 80' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 40 Q 25 10, 50 40 T 100 40 T 150 40 T 200 40' stroke='%23ffffff10' fill='none' stroke-width='2'/%3E%3C/svg%3E"); | |
| opacity: 0.3; | |
| } | |
| .audio-level { | |
| height: 100%; | |
| width: 0; | |
| background-color: rgba(255, 255, 255, 0.2); | |
| transition: width 0.05s ease-out; | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| 100% { transform: scale(1); } | |
| } | |
| .pulse { | |
| animation: pulse 2s infinite; | |
| } | |
| .volume-slider { | |
| -webkit-appearance: none; | |
| width: 100%; | |
| height: 8px; | |
| border-radius: 4px; | |
| background: #e2e8f0; | |
| outline: none; | |
| } | |
| .volume-slider::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: #3b82f6; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .volume-slider::-webkit-slider-thumb:hover { | |
| background: #2563eb; | |
| transform: scale(1.1); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gradient-to-br from-blue-50 to-purple-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-12"> | |
| <div class="max-w-3xl mx-auto"> | |
| <!-- Header --> | |
| <div class="text-center mb-12"> | |
| <h1 class="text-4xl font-bold text-gray-800 mb-2">Microphone Checker</h1> | |
| <p class="text-lg text-gray-600">Verifique e teste seus dispositivos de microfone</p> | |
| </div> | |
| <!-- Main Card --> | |
| <div class="bg-white rounded-xl shadow-xl overflow-hidden transition-all duration-300 hover:shadow-2xl"> | |
| <!-- Status Section --> | |
| <div class="p-8 border-b border-gray-100"> | |
| <div class="flex items-center justify-between"> | |
| <div> | |
| <h2 class="text-2xl font-semibold text-gray-800">Status do Microfone</h2> | |
| <p class="text-gray-500" id="status-text">Verificando dispositivos...</p> | |
| </div> | |
| <div id="status-icon" class="text-4xl text-gray-400"> | |
| <i class="fas fa-microphone-slash"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Device Selection --> | |
| <div class="p-8 border-b border-gray-100" id="device-section" style="display: none;"> | |
| <h3 class="text-lg font-medium text-gray-700 mb-4">Selecione seu microfone</h3> | |
| <select id="device-select" class="w-full p-3 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> | |
| <option value="">Carregando dispositivos...</option> | |
| </select> | |
| </div> | |
| <!-- Visualizer --> | |
| <div class="waveform" id="visualizer"> | |
| <div class="audio-level" id="audio-level"></div> | |
| </div> | |
| <!-- Controls --> | |
| <div class="p-8"> | |
| <div class="flex flex-col sm:flex-row gap-4 mb-6"> | |
| <button id="test-btn" class="flex-1 bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg transition-all flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
| <i class="fas fa-play"></i> Testar Microfone | |
| </button> | |
| <button id="stop-btn" class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-3 px-6 rounded-lg transition-all flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
| <i class="fas fa-stop"></i> Parar Teste | |
| </button> | |
| </div> | |
| <!-- Monitor Section --> | |
| <div class="bg-purple-50 border border-purple-100 rounded-lg p-4 mb-6" id="monitor-section" style="display: none;"> | |
| <div class="flex items-center justify-between mb-3"> | |
| <h3 class="font-medium text-purple-800 flex items-center gap-2"> | |
| <i class="fas fa-headphones"></i> Monitorar Áudio | |
| </h3> | |
| <div class="flex items-center gap-2"> | |
| <span id="volume-value" class="text-sm text-purple-700">50%</span> | |
| <i class="fas fa-volume-up text-purple-600"></i> | |
| </div> | |
| </div> | |
| <div class="flex items-center gap-4"> | |
| <button id="monitor-btn" class="bg-purple-600 hover:bg-purple-700 text-white font-medium py-2 px-4 rounded-lg transition-all flex items-center justify-center gap-2"> | |
| <i class="fas fa-play"></i> Ouvir | |
| </button> | |
| <input type="range" min="0" max="100" value="50" class="volume-slider flex-1" id="volume-slider"> | |
| </div> | |
| <div class="mt-3 flex items-center gap-2 text-sm text-purple-700"> | |
| <div class="h-3 w-3 rounded-full bg-purple-400" id="monitor-indicator"></div> | |
| <span id="monitor-status">Pronto para monitorar</span> | |
| </div> | |
| </div> | |
| <div class="mt-8 bg-blue-50 border border-blue-100 rounded-lg p-4" id="instructions"> | |
| <h3 class="font-medium text-blue-800 mb-2 flex items-center gap-2"> | |
| <i class="fas fa-info-circle"></i> Instruções | |
| </h3> | |
| <ol class="list-decimal list-inside text-blue-700 space-y-1"> | |
| <li>Permita o acesso ao microfone quando solicitado</li> | |
| <li>Selecione seu dispositivo de microfone</li> | |
| <li>Clique em "Testar Microfone" e comece a falar</li> | |
| <li>Use o botão "Ouvir" para monitorar seu microfone em tempo real</li> | |
| <li>O visualizador mostrará o nível de entrada de áudio</li> | |
| </ol> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <div class="mt-12 text-center text-gray-500 text-sm"> | |
| <p>© 2023 Microphone Checker | Desenvolvido com <i class="fas fa-heart text-red-500"></i> para testar seus dispositivos de áudio</p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const statusText = document.getElementById('status-text'); | |
| const statusIcon = document.getElementById('status-icon'); | |
| const deviceSection = document.getElementById('device-section'); | |
| const deviceSelect = document.getElementById('device-select'); | |
| const testBtn = document.getElementById('test-btn'); | |
| const stopBtn = document.getElementById('stop-btn'); | |
| const audioLevel = document.getElementById('audio-level'); | |
| const visualizer = document.getElementById('visualizer'); | |
| const instructions = document.getElementById('instructions'); | |
| const monitorSection = document.getElementById('monitor-section'); | |
| const monitorBtn = document.getElementById('monitor-btn'); | |
| const volumeSlider = document.getElementById('volume-slider'); | |
| const volumeValue = document.getElementById('volume-value'); | |
| const monitorStatus = document.getElementById('monitor-status'); | |
| const monitorIndicator = document.getElementById('monitor-indicator'); | |
| let audioContext; | |
| let microphone; | |
| let analyser; | |
| let isTesting = false; | |
| let devices = []; | |
| let monitorStream = null; | |
| let monitorGain = null; | |
| let isMonitoring = false; | |
| let volume = 0.5; | |
| // Check microphone availability | |
| checkMicrophoneAvailability(); | |
| async function checkMicrophoneAvailability() { | |
| try { | |
| // Request permission to list devices | |
| const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); | |
| stream.getTracks().forEach(track => track.stop()); | |
| // List audio devices | |
| devices = await navigator.mediaDevices.enumerateDevices(); | |
| const audioDevices = devices.filter(device => device.kind === 'audioinput'); | |
| if (audioDevices.length > 0) { | |
| // Update status | |
| statusText.textContent = `${audioDevices.length} microfone(s) disponível(is)`; | |
| statusIcon.innerHTML = '<i class="fas fa-microphone text-green-500 pulse"></i>'; | |
| // Show device selection and monitor section | |
| deviceSection.style.display = 'block'; | |
| monitorSection.style.display = 'block'; | |
| populateDeviceSelect(audioDevices); | |
| // Enable test button | |
| testBtn.disabled = false; | |
| } else { | |
| statusText.textContent = 'Nenhum microfone encontrado'; | |
| statusIcon.innerHTML = '<i class="fas fa-microphone-slash text-red-500"></i>'; | |
| } | |
| } catch (error) { | |
| console.error('Error accessing microphone:', error); | |
| statusText.textContent = 'Acesso ao microfone negado ou erro ao acessar'; | |
| statusIcon.innerHTML = '<i class="fas fa-microphone-slash text-red-500"></i>'; | |
| // Update instructions | |
| instructions.querySelector('ol').innerHTML = ` | |
| <li class="text-red-500">Permissão para acessar o microfone foi negada</li> | |
| <li>Atualize a página e permita o acesso ao microfone</li> | |
| <li>Verifique as configurações de privacidade do seu navegador</li> | |
| `; | |
| } | |
| } | |
| function populateDeviceSelect(audioDevices) { | |
| deviceSelect.innerHTML = ''; | |
| if (audioDevices.length === 0) { | |
| deviceSelect.innerHTML = '<option value="">Nenhum microfone encontrado</option>'; | |
| return; | |
| } | |
| // Add default option | |
| const defaultOption = document.createElement('option'); | |
| defaultOption.value = ''; | |
| defaultOption.textContent = 'Selecione um microfone...'; | |
| deviceSelect.appendChild(defaultOption); | |
| // Add devices | |
| audioDevices.forEach(device => { | |
| const option = document.createElement('option'); | |
| option.value = device.deviceId; | |
| option.textContent = device.label || `Microfone ${deviceSelect.options.length}`; | |
| deviceSelect.appendChild(option); | |
| }); | |
| } | |
| testBtn.addEventListener('click', startTest); | |
| stopBtn.addEventListener('click', stopTest); | |
| monitorBtn.addEventListener('click', toggleMonitor); | |
| volumeSlider.addEventListener('input', updateVolume); | |
| async function startTest() { | |
| if (isTesting) return; | |
| const deviceId = deviceSelect.value; | |
| if (!deviceId) { | |
| alert('Por favor, selecione um microfone primeiro'); | |
| return; | |
| } | |
| try { | |
| isTesting = true; | |
| testBtn.disabled = true; | |
| stopBtn.disabled = false; | |
| visualizer.style.background = 'linear-gradient(90deg, #10b981, #3b82f6)'; | |
| // Initialize audio context | |
| audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| analyser = audioContext.createAnalyser(); | |
| analyser.fftSize = 32; | |
| // Get microphone stream | |
| const constraints = { | |
| audio: { | |
| deviceId: deviceId ? { exact: deviceId } : undefined, | |
| echoCancellation: false, | |
| noiseSuppression: false, | |
| autoGainControl: false | |
| } | |
| }; | |
| const stream = await navigator.mediaDevices.getUserMedia(constraints); | |
| microphone = audioContext.createMediaStreamSource(stream); | |
| microphone.connect(analyser); | |
| // Update UI | |
| statusText.textContent = 'Testando microfone... Fale agora'; | |
| statusIcon.innerHTML = '<i class="fas fa-microphone text-green-500 animate-pulse"></i>'; | |
| // Start visualization | |
| visualize(); | |
| } catch (error) { | |
| console.error('Error starting test:', error); | |
| statusText.textContent = 'Erro ao iniciar o teste do microfone'; | |
| statusIcon.innerHTML = '<i class="fas fa-microphone-slash text-red-500"></i>'; | |
| stopTest(); | |
| } | |
| } | |
| function stopTest() { | |
| if (!isTesting) return; | |
| isTesting = false; | |
| testBtn.disabled = false; | |
| stopBtn.disabled = true; | |
| visualizer.style.background = 'linear-gradient(90deg, #3b82f6, #8b5cf6)'; | |
| // Stop all tracks | |
| if (microphone && microphone.mediaStream) { | |
| microphone.mediaStream.getTracks().forEach(track => track.stop()); | |
| } | |
| // Disconnect audio nodes | |
| if (microphone && analyser) { | |
| microphone.disconnect(); | |
| } | |
| // Update UI | |
| statusText.textContent = 'Teste concluído'; | |
| statusIcon.innerHTML = '<i class="fas fa-microphone text-green-500"></i>'; | |
| audioLevel.style.width = '0'; | |
| } | |
| function visualize() { | |
| if (!isTesting) return; | |
| const dataArray = new Uint8Array(analyser.frequencyBinCount); | |
| analyser.getByteFrequencyData(dataArray); | |
| // Calculate average volume | |
| let sum = 0; | |
| for (let i = 0; i < dataArray.length; i++) { | |
| sum += dataArray[i]; | |
| } | |
| const average = sum / dataArray.length; | |
| // Update visualizer | |
| audioLevel.style.width = `${average}%`; | |
| // Color intensity based on volume | |
| const intensity = Math.min(average / 100, 1); | |
| const r = Math.floor(16 + (255 - 16) * intensity); | |
| const g = Math.floor(182 + (255 - 182) * intensity); | |
| const b = Math.floor(255 * intensity); | |
| audioLevel.style.backgroundColor = `rgba(${r}, ${g}, ${b}, 0.5)`; | |
| // Continue animation | |
| requestAnimationFrame(visualize); | |
| } | |
| async function toggleMonitor() { | |
| if (isMonitoring) { | |
| stopMonitor(); | |
| } else { | |
| await startMonitor(); | |
| } | |
| } | |
| async function startMonitor() { | |
| const deviceId = deviceSelect.value; | |
| if (!deviceId) { | |
| alert('Por favor, selecione um microfone primeiro'); | |
| return; | |
| } | |
| try { | |
| // Initialize audio context if not already done | |
| if (!audioContext) { | |
| audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| } | |
| // Get microphone stream | |
| const constraints = { | |
| audio: { | |
| deviceId: deviceId ? { exact: deviceId } : undefined, | |
| echoCancellation: false, | |
| noiseSuppression: false, | |
| autoGainControl: false | |
| } | |
| }; | |
| monitorStream = await navigator.mediaDevices.getUserMedia(constraints); | |
| const source = audioContext.createMediaStreamSource(monitorStream); | |
| // Create gain node for volume control | |
| monitorGain = audioContext.createGain(); | |
| monitorGain.gain.value = volume; | |
| // Connect to destination | |
| source.connect(monitorGain); | |
| monitorGain.connect(audioContext.destination); | |
| // Update UI | |
| isMonitoring = true; | |
| monitorBtn.innerHTML = '<i class="fas fa-stop"></i> Parar'; | |
| monitorBtn.classList.remove('bg-purple-600', 'hover:bg-purple-700'); | |
| monitorBtn.classList.add('bg-red-500', 'hover:bg-red-600'); | |
| monitorStatus.textContent = 'Monitorando ativamente'; | |
| monitorIndicator.classList.remove('bg-purple-400'); | |
| monitorIndicator.classList.add('bg-green-500', 'animate-pulse'); | |
| } catch (error) { | |
| console.error('Error starting monitor:', error); | |
| alert('Erro ao iniciar o monitoramento: ' + error.message); | |
| } | |
| } | |
| function stopMonitor() { | |
| if (!isMonitoring) return; | |
| // Stop all tracks | |
| if (monitorStream) { | |
| monitorStream.getTracks().forEach(track => track.stop()); | |
| } | |
| // Disconnect audio nodes | |
| if (monitorGain) { | |
| monitorGain.disconnect(); | |
| } | |
| // Update UI | |
| isMonitoring = false; | |
| monitorBtn.innerHTML = '<i class="fas fa-play"></i> Ouvir'; | |
| monitorBtn.classList.remove('bg-red-500', 'hover:bg-red-600'); | |
| monitorBtn.classList.add('bg-purple-600', 'hover:bg-purple-700'); | |
| monitorStatus.textContent = 'Pronto para monitorar'; | |
| monitorIndicator.classList.remove('bg-green-500', 'animate-pulse'); | |
| monitorIndicator.classList.add('bg-purple-400'); | |
| } | |
| function updateVolume() { | |
| volume = volumeSlider.value / 100; | |
| volumeValue.textContent = `${volumeSlider.value}%`; | |
| if (monitorGain) { | |
| monitorGain.gain.value = volume; | |
| } | |
| } | |
| // Handle device changes | |
| navigator.mediaDevices.addEventListener('devicechange', async () => { | |
| devices = await navigator.mediaDevices.enumerateDevices(); | |
| const audioDevices = devices.filter(device => device.kind === 'audioinput'); | |
| populateDeviceSelect(audioDevices); | |
| if (audioDevices.length > 0) { | |
| statusText.textContent = `${audioDevices.length} microfone(s) disponível(is)`; | |
| statusIcon.innerHTML = '<i class="fas fa-microphone text-green-500 pulse"></i>'; | |
| monitorSection.style.display = 'block'; | |
| } else { | |
| statusText.textContent = 'Nenhum microfone encontrado'; | |
| statusIcon.innerHTML = '<i class="fas fa-microphone-slash text-red-500"></i>'; | |
| monitorSection.style.display = 'none'; | |
| } | |
| // Stop monitoring if device list changes | |
| if (isMonitoring) { | |
| stopMonitor(); | |
| } | |
| }); | |
| }); | |
| </script> | |
| </html> |