diff --git a/public/index.html b/public/index.html index 3d77839..0987cc3 100644 --- a/public/index.html +++ b/public/index.html @@ -45,8 +45,21 @@

Media

Idle
-
-
+
+ +
+ + +
+
+
+
+
+
+
No media queued
@@ -76,11 +89,12 @@ userTimelines: new Map(), applyingServerState: false, media: { playing: false, current: null, queue: [] }, + mediaMode: 'music', }; const SAMPLE_RATE = 24000; const CHANNELS = 1; const el = { - wsDot: document.getElementById('wsDot'), wsStatusText: document.getElementById('wsStatusText'), activeTabLabel: document.getElementById('activeTabLabel'), errorBox: document.getElementById('errorBox'), voiceGuildSelect: document.getElementById('voiceGuildSelect'), textGuildSelect: document.getElementById('textGuildSelect'), channelSelect: document.getElementById('channelSelect'), channelFilter: document.getElementById('channelFilter'), joinVoiceBtn: document.getElementById('joinVoiceBtn'), disconnectVoiceBtn: document.getElementById('disconnectVoiceBtn'), voiceStatusText: document.getElementById('voiceStatusText'), voiceStatusNote: document.getElementById('voiceStatusNote'), toggleBtn: document.getElementById('toggleBtn'), listenBtn: document.getElementById('listenBtn'), listenStatus: document.getElementById('listenStatus'), visualizer: document.getElementById('visualizer'), userList: document.getElementById('userList'), textList: document.getElementById('textList'), reviewList: document.getElementById('reviewList'), mediaSourceInput: document.getElementById('mediaSourceInput'), mediaStatus: document.getElementById('mediaStatus'), queueMediaBtn: document.getElementById('queueMediaBtn'), skipMediaBtn: document.getElementById('skipMediaBtn'), stopMediaBtn: document.getElementById('stopMediaBtn'), mediaQueueList: document.getElementById('mediaQueueList') + wsDot: document.getElementById('wsDot'), wsStatusText: document.getElementById('wsStatusText'), activeTabLabel: document.getElementById('activeTabLabel'), errorBox: document.getElementById('errorBox'), voiceGuildSelect: document.getElementById('voiceGuildSelect'), textGuildSelect: document.getElementById('textGuildSelect'), channelSelect: document.getElementById('channelSelect'), channelFilter: document.getElementById('channelFilter'), joinVoiceBtn: document.getElementById('joinVoiceBtn'), disconnectVoiceBtn: document.getElementById('disconnectVoiceBtn'), voiceStatusText: document.getElementById('voiceStatusText'), voiceStatusNote: document.getElementById('voiceStatusNote'), toggleBtn: document.getElementById('toggleBtn'), listenBtn: document.getElementById('listenBtn'), listenStatus: document.getElementById('listenStatus'), visualizer: document.getElementById('visualizer'), userList: document.getElementById('userList'), textList: document.getElementById('textList'), reviewList: document.getElementById('reviewList'), mediaSourceInput: document.getElementById('mediaSourceInput'), screenSourceInput: document.getElementById('screenSourceInput'), mediaStatus: document.getElementById('mediaStatus'), queueMediaBtn: document.getElementById('queueMediaBtn'), skipMediaBtn: document.getElementById('skipMediaBtn'), stopMediaBtn: document.getElementById('stopMediaBtn'), queueScreenBtn: document.getElementById('queueScreenBtn'), skipScreenBtn: document.getElementById('skipScreenBtn'), stopScreenBtn: document.getElementById('stopScreenBtn'), musicModeBtn: document.getElementById('musicModeBtn'), screenModeBtn: document.getElementById('screenModeBtn'), musicPanel: document.getElementById('musicPanel'), screenPanel: document.getElementById('screenPanel'), mediaQueueList: document.getElementById('mediaQueueList') }; for (let i = 0; i < 32; i++) { const bar = document.createElement('div'); bar.className = 'bar'; el.visualizer.appendChild(bar); } const bars = [...document.querySelectorAll('.bar')]; @@ -159,6 +173,20 @@ function updateVisualizer(level) { bars.forEach((bar, index) => { const wave = Math.sin(index * 0.55 + Date.now() / 140) * 0.35 + 0.65; bar.style.height = `${Math.max(3, level * 190 * wave)}px`; }); } document.querySelectorAll('.tab-btn').forEach((button) => { button.addEventListener('click', () => postUIState({ activeTab: button.dataset.tab }).catch((error) => showError(error.message))); }); + el.musicModeBtn.addEventListener('click', () => { + state.mediaMode = 'music'; + el.musicModeBtn.classList.add('active'); + el.screenModeBtn.classList.remove('active'); + el.musicPanel.style.display = ''; + el.screenPanel.style.display = 'none'; + }); + el.screenModeBtn.addEventListener('click', () => { + state.mediaMode = 'screen'; + el.screenModeBtn.classList.add('active'); + el.musicModeBtn.classList.remove('active'); + el.musicPanel.style.display = 'none'; + el.screenPanel.style.display = ''; + }); el.voiceGuildSelect.addEventListener('change', () => postUIState({ selectedVoiceGuild: el.voiceGuildSelect.value, selectedVoiceChannel: '' }).catch((error) => showError(error.message))); el.textGuildSelect.addEventListener('change', () => postUIState({ selectedTextGuild: el.textGuildSelect.value, selectedTextChannel: '' }).catch((error) => showError(error.message))); el.channelSelect.addEventListener('change', () => postUIState({ selectedVoiceChannel: el.channelSelect.value }).catch((error) => showError(error.message))); @@ -169,7 +197,8 @@ el.channelFilter.addEventListener('change', () => { const selectedTextChannel = el.channelFilter.value; const url = new URL(location.href); if (selectedTextChannel) url.searchParams.set('channel', selectedTextChannel); else url.searchParams.delete('channel'); if (el.textGuildSelect.value) url.searchParams.set('guild', el.textGuildSelect.value); history.replaceState({}, '', url); postUIState({ selectedTextChannel }).catch((error) => showError(error.message)); }); async function fetchMediaStatus() { state.media = await apiRequest('/api/media/status'); renderMedia(); } - async function queueMedia() { const source = el.mediaSourceInput.value.trim(); if (!source) return showError('Enter a music URL or local file path'); if (state.isStreaming || state.localStreaming) await postUIState({ isStreaming: false }); state.media = await apiRequest('/api/media/queue', { method: 'POST', body: JSON.stringify({ source }) }); el.mediaSourceInput.value = ''; await reconcileDynamicAudio(); renderMedia(); } + async function queueMedia() { const source = el.mediaSourceInput.value.trim(); if (!source) return showError('Enter a music URL or local file path'); if (state.isStreaming || state.localStreaming) await postUIState({ isStreaming: false }); state.media = await apiRequest('/api/media/queue', { method: 'POST', body: JSON.stringify({ source, mode: 'music' }) }); el.mediaSourceInput.value = ''; await reconcileDynamicAudio(); renderMedia(); } + async function queueScreen() { const source = el.screenSourceInput.value.trim(); if (!source) return showError('Enter a screen share URL or file path'); if (state.isStreaming || state.localStreaming) await postUIState({ isStreaming: false }); state.media = await apiRequest('/api/media/queue', { method: 'POST', body: JSON.stringify({ source, mode: 'screen' }) }); el.screenSourceInput.value = ''; await reconcileDynamicAudio(); renderMedia(); } async function skipMedia() { state.media = await apiRequest('/api/media/skip', { method: 'POST' }); await reconcileDynamicAudio(); renderMedia(); } async function stopMedia() { state.media = await apiRequest('/api/media/stop', { method: 'POST' }); await reconcileDynamicAudio(); renderMedia(); } function renderMedia() { el.mediaQueueList.replaceChildren(); const current = state.media.current; el.mediaStatus.textContent = current ? `Playing ${current.title}` : 'Idle'; if (current) { const item = document.createElement('div'); item.className = 'event-card'; item.textContent = `Now: ${current.title}`; el.mediaQueueList.appendChild(item); } for (const queued of state.media.queue || []) { const item = document.createElement('div'); item.className = 'event-card'; item.textContent = queued.title; el.mediaQueueList.appendChild(item); } if (!current && (!state.media.queue || state.media.queue.length === 0)) appendEmpty(el.mediaQueueList, 'No media queued'); } @@ -177,6 +206,9 @@ el.queueMediaBtn.addEventListener('click', () => queueMedia().catch((error) => showError(error.message))); el.skipMediaBtn.addEventListener('click', () => skipMedia().catch((error) => showError(error.message))); el.stopMediaBtn.addEventListener('click', () => stopMedia().catch((error) => showError(error.message))); + el.queueScreenBtn.addEventListener('click', () => queueScreen().catch((error) => showError(error.message))); + el.skipScreenBtn.addEventListener('click', () => skipMedia().catch((error) => showError(error.message))); + el.stopScreenBtn.addEventListener('click', () => stopMedia().catch((error) => showError(error.message))); connectWebSocket(); apiRequest('/api/ui-state').then(applyServerState).then(() => loadGuilds()).then(refreshStatus).then(fetchMediaStatus).catch((error) => showError(error.message));