415 lines
13 KiB
JavaScript
415 lines
13 KiB
JavaScript
// app.js — Frontend upload + minimal native player logic with slide-in and pulse effect
|
|
|
|
function getCookie(name) {
|
|
const value = `; ${document.cookie}`;
|
|
const parts = value.split(`; ${name}=`);
|
|
if (parts.length === 2) return parts.pop().split(';').shift();
|
|
return null;
|
|
}
|
|
|
|
import { playBeep } from "./sound.js";
|
|
import { showToast } from "./toast.js";
|
|
|
|
|
|
// Log debug messages to server
|
|
export function logToServer(msg) {
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open("POST", "/log", true);
|
|
xhr.setRequestHeader("Content-Type", "application/json");
|
|
xhr.send(JSON.stringify({ msg }));
|
|
}
|
|
|
|
// Expose for debugging
|
|
window.logToServer = logToServer;
|
|
|
|
// Handle magic link login redirect
|
|
(function handleMagicLoginRedirect() {
|
|
const params = new URLSearchParams(window.location.search);
|
|
if (params.get('login') === 'success' && params.get('confirmed_uid')) {
|
|
const username = params.get('confirmed_uid');
|
|
localStorage.setItem('uid', username);
|
|
logToServer(`[DEBUG] localStorage.setItem('uid', '${username}')`);
|
|
localStorage.setItem('confirmed_uid', username);
|
|
logToServer(`[DEBUG] localStorage.setItem('confirmed_uid', '${username}')`);
|
|
const uidTime = Date.now().toString();
|
|
localStorage.setItem('uid_time', uidTime);
|
|
logToServer(`[DEBUG] localStorage.setItem('uid_time', '${uidTime}')`);
|
|
// Set uid as cookie for backend authentication
|
|
document.cookie = "uid=" + encodeURIComponent(username) + "; path=/";
|
|
// Remove query params from URL
|
|
window.history.replaceState({}, document.title, window.location.pathname);
|
|
// Reload to show dashboard as logged in
|
|
location.reload();
|
|
return;
|
|
}
|
|
})();
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
// (Removed duplicate logToServer definition)
|
|
|
|
// Guest vs. logged-in toggling is now handled by dashboard.js
|
|
// --- Public profile view logic ---
|
|
function showProfilePlayerFromUrl() {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const profileUid = params.get("profile");
|
|
if (profileUid) {
|
|
const mePage = document.getElementById("me-page");
|
|
if (mePage) {
|
|
document.querySelectorAll("main > section").forEach(sec => sec.hidden = sec.id !== "me-page");
|
|
// Hide upload/delete/copy-url controls for guest view
|
|
const uploadArea = document.getElementById("upload-area");
|
|
if (uploadArea) uploadArea.hidden = true;
|
|
const copyUrlBtn = document.getElementById("copy-url");
|
|
if (copyUrlBtn) copyUrlBtn.style.display = "none";
|
|
const deleteBtn = document.getElementById("delete-account");
|
|
if (deleteBtn) deleteBtn.style.display = "none";
|
|
// Update heading and description for guest view
|
|
const meHeading = document.querySelector("#me-page h2");
|
|
if (meHeading) meHeading.textContent = `${profileUid}'s Stream 🎙️`;
|
|
const meDesc = document.querySelector("#me-page p");
|
|
if (meDesc) meDesc.textContent = `This is ${profileUid}'s public stream.`;
|
|
// Show a Play Stream button for explicit user action
|
|
const streamInfo = document.getElementById("stream-info");
|
|
if (streamInfo) {
|
|
streamInfo.innerHTML = "";
|
|
const playBtn = document.createElement('button');
|
|
playBtn.textContent = "▶ Play Stream";
|
|
playBtn.onclick = () => {
|
|
loadProfileStream(profileUid);
|
|
playBtn.disabled = true;
|
|
};
|
|
streamInfo.appendChild(playBtn);
|
|
streamInfo.hidden = false;
|
|
}
|
|
// Do NOT call loadProfileStream(profileUid) automatically!
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Only run showProfilePlayerFromUrl after session/profile checks are complete ---
|
|
function runProfilePlayerIfSessionValid() {
|
|
if (typeof checkSessionValidity === "function" && !checkSessionValidity()) return;
|
|
showProfilePlayerFromUrl();
|
|
}
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
setTimeout(runProfilePlayerIfSessionValid, 200);
|
|
});
|
|
window.addEventListener('popstate', () => {
|
|
setTimeout(runProfilePlayerIfSessionValid, 200);
|
|
});
|
|
window.showProfilePlayerFromUrl = showProfilePlayerFromUrl;
|
|
|
|
// Global audio state
|
|
let globalAudio = null;
|
|
let currentStreamUid = null;
|
|
let audioPlaying = false;
|
|
let lastPosition = 0;
|
|
|
|
// Expose main audio element for other scripts
|
|
window.getMainAudio = () => globalAudio;
|
|
window.stopMainAudio = () => {
|
|
if (globalAudio) {
|
|
globalAudio.pause();
|
|
audioPlaying = false;
|
|
updatePlayPauseButton();
|
|
}
|
|
};
|
|
|
|
function getOrCreateAudioElement() {
|
|
if (!globalAudio) {
|
|
globalAudio = document.getElementById('me-audio');
|
|
if (!globalAudio) {
|
|
console.error('Audio element not found');
|
|
return null;
|
|
}
|
|
// Set up audio element properties
|
|
globalAudio.preload = 'metadata'; // Preload metadata for better performance
|
|
globalAudio.crossOrigin = 'use-credentials'; // Use credentials for authenticated requests
|
|
globalAudio.setAttribute('crossorigin', 'use-credentials'); // Explicitly set the attribute
|
|
|
|
// Set up event listeners
|
|
globalAudio.addEventListener('play', () => {
|
|
audioPlaying = true;
|
|
updatePlayPauseButton();
|
|
});
|
|
globalAudio.addEventListener('pause', () => {
|
|
audioPlaying = false;
|
|
updatePlayPauseButton();
|
|
});
|
|
globalAudio.addEventListener('timeupdate', () => lastPosition = globalAudio.currentTime);
|
|
|
|
// Add error handling
|
|
globalAudio.addEventListener('error', (e) => {
|
|
console.error('Audio error:', e);
|
|
showToast('❌ Audio playback error');
|
|
});
|
|
}
|
|
return globalAudio;
|
|
}
|
|
|
|
// Function to update play/pause button state
|
|
function updatePlayPauseButton() {
|
|
const audio = getOrCreateAudioElement();
|
|
if (playPauseButton && audio) {
|
|
playPauseButton.textContent = audio.paused ? '▶' : '⏸️';
|
|
}
|
|
}
|
|
|
|
// Initialize play/pause button
|
|
const playPauseButton = document.getElementById('play-pause');
|
|
if (playPauseButton) {
|
|
// Set initial state
|
|
updatePlayPauseButton();
|
|
|
|
// Add click handler
|
|
playPauseButton.addEventListener('click', () => {
|
|
const audio = getOrCreateAudioElement();
|
|
if (audio) {
|
|
if (audio.paused) {
|
|
// Stop any playing public streams first
|
|
const publicPlayers = document.querySelectorAll('.stream-player audio');
|
|
publicPlayers.forEach(player => {
|
|
if (!player.paused) {
|
|
player.pause();
|
|
const button = player.closest('.stream-player').querySelector('.play-pause');
|
|
if (button) {
|
|
button.textContent = '▶';
|
|
}
|
|
}
|
|
});
|
|
|
|
audio.play().catch(e => {
|
|
console.error('Play failed:', e);
|
|
audioPlaying = false;
|
|
});
|
|
} else {
|
|
audio.pause();
|
|
}
|
|
updatePlayPauseButton();
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
|
|
// Preload audio without playing it
|
|
function preloadAudio(src) {
|
|
return new Promise((resolve) => {
|
|
const audio = new Audio();
|
|
audio.preload = 'auto';
|
|
audio.crossOrigin = 'anonymous';
|
|
audio.src = src;
|
|
audio.load();
|
|
audio.oncanplaythrough = () => resolve(audio);
|
|
});
|
|
}
|
|
|
|
// Load and play a stream
|
|
async function loadProfileStream(uid) {
|
|
const audio = getOrCreateAudioElement();
|
|
if (!audio) return null;
|
|
|
|
// Always reset current stream and update audio source
|
|
currentStreamUid = uid;
|
|
audio.pause();
|
|
audio.src = '';
|
|
|
|
// Wait a moment to ensure the previous source is cleared
|
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
|
|
// Set new source with cache-busting timestamp
|
|
audio.src = `/audio/${encodeURIComponent(uid)}/stream.opus?t=${Date.now()}`;
|
|
|
|
// Try to play immediately
|
|
try {
|
|
await audio.play();
|
|
audioPlaying = true;
|
|
} catch (e) {
|
|
console.error('Play failed:', e);
|
|
audioPlaying = false;
|
|
}
|
|
|
|
// Show stream info
|
|
const streamInfo = document.getElementById("stream-info");
|
|
if (streamInfo) streamInfo.hidden = false;
|
|
|
|
// Update button state
|
|
updatePlayPauseButton();
|
|
|
|
return audio;
|
|
}
|
|
|
|
// Load and play a stream
|
|
async function loadProfileStream(uid) {
|
|
const audio = getOrCreateAudioElement();
|
|
if (!audio) return null;
|
|
|
|
// Hide playlist controls
|
|
const mePrevBtn = document.getElementById("me-prev");
|
|
if (mePrevBtn) mePrevBtn.style.display = "none";
|
|
const meNextBtn = document.getElementById("me-next");
|
|
if (meNextBtn) meNextBtn.style.display = "none";
|
|
|
|
// Handle navigation to "Your Stream"
|
|
const mePageLink = document.getElementById("show-me");
|
|
if (mePageLink) {
|
|
mePageLink.addEventListener("click", async (e) => {
|
|
e.preventDefault();
|
|
const uid = localStorage.getItem("uid");
|
|
if (!uid) return;
|
|
|
|
// Show loading state
|
|
const streamInfo = document.getElementById("stream-info");
|
|
if (streamInfo) {
|
|
streamInfo.hidden = false;
|
|
streamInfo.innerHTML = '<p>Loading stream...</p>';
|
|
}
|
|
|
|
try {
|
|
// Load the stream but don't autoplay
|
|
await loadProfileStream(uid);
|
|
|
|
// Update URL without triggering a full page reload
|
|
if (window.location.pathname !== '/') {
|
|
window.history.pushState({}, '', '/');
|
|
}
|
|
|
|
// Show the me-page section
|
|
const mePage = document.getElementById('me-page');
|
|
if (mePage) {
|
|
document.querySelectorAll('main > section').forEach(s => s.hidden = s.id !== 'me-page');
|
|
}
|
|
|
|
// Clear loading state
|
|
const streamInfo = document.getElementById('stream-info');
|
|
if (streamInfo) {
|
|
streamInfo.innerHTML = '';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading stream:', error);
|
|
const streamInfo = document.getElementById('stream-info');
|
|
if (streamInfo) {
|
|
streamInfo.innerHTML = '<p>Error loading stream. Please try again.</p>';
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Always reset current stream and update audio source
|
|
currentStreamUid = uid;
|
|
audio.pause();
|
|
audio.src = '';
|
|
|
|
// Wait a moment to ensure the previous source is cleared
|
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
|
|
// Set new source with cache-busting timestamp
|
|
audio.src = `/audio/${encodeURIComponent(uid)}/stream.opus?t=${Date.now()}`;
|
|
|
|
// Try to play immediately
|
|
try {
|
|
await audio.play();
|
|
audioPlaying = true;
|
|
} catch (e) {
|
|
console.error('Play failed:', e);
|
|
audioPlaying = false;
|
|
}
|
|
|
|
// Show stream info
|
|
const streamInfo = document.getElementById("stream-info");
|
|
if (streamInfo) streamInfo.hidden = false;
|
|
|
|
// Update button state
|
|
updatePlayPauseButton();
|
|
|
|
return audio;
|
|
}
|
|
|
|
// Export the function for use in other modules
|
|
window.loadProfileStream = loadProfileStream;
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
// Initialize play/pause button
|
|
const playPauseButton = document.getElementById('play-pause');
|
|
if (playPauseButton) {
|
|
// Set initial state
|
|
audioPlaying = false;
|
|
updatePlayPauseButton();
|
|
|
|
// Add event listener
|
|
playPauseButton.addEventListener('click', () => {
|
|
const audio = getMainAudio();
|
|
if (audio) {
|
|
if (audio.paused) {
|
|
audio.play();
|
|
} else {
|
|
audio.pause();
|
|
}
|
|
updatePlayPauseButton();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Add bot protection for registration form
|
|
const registerForm = document.getElementById('register-form');
|
|
if (registerForm) {
|
|
registerForm.addEventListener('submit', (e) => {
|
|
const botTrap = e.target.elements.bot_trap;
|
|
if (botTrap && botTrap.value) {
|
|
e.preventDefault();
|
|
showToast('❌ Bot detected! Please try again.');
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
// Initialize navigation
|
|
document.querySelectorAll('#links a[data-target]').forEach(link => {
|
|
link.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const target = link.getAttribute('data-target');
|
|
// Only hide other sections when not opening #me-page
|
|
if (target !== 'me-page') fadeAllSections();
|
|
const section = document.getElementById(target);
|
|
if (section) {
|
|
section.hidden = false;
|
|
section.classList.add("slide-in");
|
|
section.scrollIntoView({ behavior: "smooth" });
|
|
}
|
|
const burger = document.getElementById('burger-toggle');
|
|
if (burger && burger.checked) burger.checked = false;
|
|
});
|
|
});
|
|
|
|
// Initialize profile player if valid session
|
|
setTimeout(runProfilePlayerIfSessionValid, 200);
|
|
window.addEventListener('popstate', () => {
|
|
setTimeout(runProfilePlayerIfSessionValid, 200);
|
|
});
|
|
});
|
|
// Initialize navigation
|
|
document.querySelectorAll('#links a[data-target]').forEach(link => {
|
|
link.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const target = link.getAttribute('data-target');
|
|
// Only hide other sections when not opening #me-page
|
|
if (target !== 'me-page') fadeAllSections();
|
|
const section = document.getElementById(target);
|
|
if (section) {
|
|
section.hidden = false;
|
|
section.classList.add("slide-in");
|
|
section.scrollIntoView({ behavior: "smooth" });
|
|
}
|
|
const burger = document.getElementById('burger-toggle');
|
|
if (burger && burger.checked) burger.checked = false;
|
|
});
|
|
});
|
|
|
|
// Initialize profile player if valid session
|
|
setTimeout(runProfilePlayerIfSessionValid, 200);
|
|
window.addEventListener('popstate', () => {
|
|
setTimeout(runProfilePlayerIfSessionValid, 200);
|
|
});
|
|
});
|