🎵 Audio Cast

Upload audio to the server and play it on the Android device

Target Device

Upload Audio

Drag & drop an audio file here, or click to browse

Playback

idle No audio uploaded yet
// ── File selection ───────────────────────────────────────────────────────── function onFileChosen(file) { if (!file || !file.type.startsWith('audio/')) { showToast('Please select an audio file'); return; } selectedFile = file; fileNameDisp.textContent = file.name + ' (' + (file.size / 1024 / 1024).toFixed(2) + ' MB)'; fileNameDisp.style.display = 'block'; dropLabel.style.display = 'none'; dropZone.classList.add('has-file'); btnUpload.disabled = false; } dropZone.addEventListener('click', () => fileInput.click()); fileInput.addEventListener('change', () => { if (fileInput.files[0]) onFileChosen(fileInput.files[0]); }); dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('over'); }); dropZone.addEventListener('dragleave', () => dropZone.classList.remove('over')); dropZone.addEventListener('drop', e => { e.preventDefault(); dropZone.classList.remove('over'); if (e.dataTransfer.files[0]) onFileChosen(e.dataTransfer.files[0]); }); // ── Upload ───────────────────────────────────────────────────────────────── btnUpload.addEventListener('click', () => { if (!selectedFile) return; const formData = new FormData(); formData.append('audio', selectedFile, selectedFile.name); const xhr = new XMLHttpRequest(); progressBar.style.display = 'block'; progressFill.style.width = '0%'; btnUpload.disabled = true; xhr.upload.onprogress = e => { if (e.lengthComputable) progressFill.style.width = ((e.loaded / e.total) * 100).toFixed(1) + '%'; }; xhr.onload = () => { progressBar.style.display = 'none'; try { const res = JSON.parse(xhr.responseText); if (res.ok) { currentAudioId = res.audioId; currentAudioName = res.name; audioLabel.textContent = res.name; btnPlay.disabled = !streamSelect.value; btnStop.disabled = false; setStatus('idle', 'ready'); showToast('✅ Uploaded: ' + res.name); addHistory({ audioId: res.audioId, name: res.name, downloadUrl: res.downloadUrl }); } else { showToast('Upload failed: ' + (res.error || xhr.status)); btnUpload.disabled = false; } } catch { showToast('Upload failed (parse error)'); btnUpload.disabled = false; } }; xhr.onerror = () => { progressBar.style.display = 'none'; showToast('Network error during upload'); btnUpload.disabled = false; }; xhr.open('POST', '/audio-upload'); xhr.send(formData); }); streamSelect.addEventListener('change', () => { btnPlay.disabled = !streamSelect.value || !currentAudioId; }); // ── Play / Stop ──────────────────────────────────────────────────────────── async function sendCommand(action, audioId) { const streamId = streamSelect.value; if (!streamId) { showToast('Select a stream first'); return; } try { const url = action === 'play' ? `/audio-play?id=${encodeURIComponent(streamId)}` : `/audio-stop?id=${encodeURIComponent(streamId)}`; const body = action === 'play' ? JSON.stringify({ audioId }) : '{}'; const r = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body }); const data = await r.json(); if (data.ok) { if (action === 'play') { setStatus('playing', 'playing'); showToast('▶ Playing: ' + (currentAudioName || audioId)); } else { setStatus('stopped', 'stopped'); showToast('⏹ Stopped'); } } else { setStatus('error', 'error'); showToast('Error: ' + (data.error || r.status)); } } catch (e) { setStatus('error', 'error'); showToast('Network error: ' + e.message); } } btnPlay.addEventListener('click', () => sendCommand('play', currentAudioId)); btnStop.addEventListener('click', () => sendCommand('stop', null)); // ── History ──────────────────────────────────────────────────────────────── function addHistory(entry) { history.unshift(entry); if (history.length > 5) history.pop(); renderHistory(); } function renderHistory() { if (!history.length) { historyCard.style.display = 'none'; return; } historyCard.style.display = 'block'; historyList.innerHTML = ''; for (const h of history) { const li = document.createElement('li'); li.className = 'history-item'; li.innerHTML = `${h.name} `; li.querySelector('.btn-sm').addEventListener('click', () => { currentAudioId = h.audioId; currentAudioName = h.name; audioLabel.textContent = h.name; btnPlay.disabled = !streamSelect.value; setStatus('idle', 'ready'); sendCommand('play', h.audioId); }); historyList.appendChild(li); } } })();