pahvalentines/frontend/waiting.php

401 lines
No EOL
19 KiB
PHP

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png">
<title>Pets at Home</title>
<meta name="description" content="Create a personalized love song for your pet with our fun and easy tool.">
<link rel="stylesheet" href="assets/css/style.css">
<link rel="preload" href="./assets/images/jukebox-banner-dt.gif" as="image">
<link rel="preload" href="./assets/images/jukebox-banner-mb.gif" as="image">
<?php include('opengraph.php'); ?>
</head>
<body>
<?php include('header.php'); ?>
<div class="container">
<div class="body-container">
<img class="jukebox-banner-mb" src="./assets/images/jukebox-banner-mb.gif" alt="Jukebox" />
<img class="jukebox-banner-dt" src="./assets/images/jukebox-banner-dt.gif" alt="Jukebox" />
<div class="title">Your Pet Love Song <br/>will be ready in <span id="timer"></span></div>
<div class="melody-record-container">
<img class="melody-record-needle" src="./assets/images/needle.png" alt="record player needle">
<img class="melody-record" src="./assets/images/blank-record.png"
alt="Melody Record" />
<svg class="record-text-overlay" viewBox="0 0 100 100">
<path id="arc-top" d="M 38,50 A 12,12 0 0,1 62,50" fill="transparent" />
<path id="arc-bottom" d="M 62,50 A 12,12 0 0,1 38,50" fill="transparent" />
<text class="top-text">
<textPath href="#arc-top" startOffset="50%" text-anchor="middle">Melody</textPath>
</text>
<text class="bottom-text">
<textPath href="#arc-bottom" startOffset="50%" text-anchor="middle">unleashed</textPath>
</text>
</svg>
</div>
<!-- Audio player (hidden until streaming is ready) -->
<!-- <div id="audio-player-container" class="audio-player-container" style="display: none;">
<p class="audio-player-label">Listen while your video is being created...</p>
<audio id="audio-player" controls autoplay preload="none">
Your browser does not support the audio element.
</audio>
<p id="audio-tap-message" class="audio-tap-message" style="display: none;">Tap to play</p>
</div> -->
</div>
</div>
<?php include('footer.php'); ?>
<script>
(function() {
// Configuration
const API_BASE_URL = 'https://valentinesong.oliver.digital/back';
const POLL_INTERVAL = 10 * 1000; // 10 seconds
const MAX_POLL_TIME = 10 * 60 * 1000; // 10 minutes
const COUNTDOWN_START_SECONDS = 2 * 60; // 5 minutes
const TIMER_SPEED_FACTOR = 1.2; // 1.0 = real time, 1.2 = 20% slower
const TEXT_SWITCH_INTERVAL = 10; // seconds
const PHRASES = [
{ top: "My bark", bottom: "will go on" },
{ top: "Cat it", bottom: "be love" },
{ top: "Brown eyed", bottom: "gerbil" },
{ top: "How squeak", bottom: "is your love" },
{ top: "Unfor-pet-able", bottom: "" },
{ top: "Kiss me", bottom: "meow" },
//{ top: "Unleashed", bottom: "melody" },
{ top: "Paws in the", bottom: "name of love" },
{ top: "Bark for", bottom: "good" },
{ top: "All you need", bottom: "are treats" },
{ top: "This is", bottom: "wuf" },
{ top: "Truly, madly,", bottom: "wheekly" }
];
// State
let sessionId = null;
const startTime = Date.now();
let pollTimer = null;
let countdownTimer = null;
let streamingStarted = false;
let audioPlayer = null;
let recordTextIndex = 0;
// Session Management
function getSessionId() {
// Get session_id from URL query parameter
const urlParams = new URLSearchParams(window.location.search);
let sessionId = urlParams.get('session_id');
if (!sessionId) {
// No session_id in URL, check localStorage
try {
const submissionData = localStorage.getItem('submission_data');
if (submissionData) {
const data = JSON.parse(submissionData);
if (data.entries && data.entries.length > 0) {
// Sort entries by timestamp (newest first) and get the latest
const sortedEntries = data.entries.sort((a, b) => {
return new Date(b.timestamp) - new Date(a.timestamp);
});
const latestEntry = sortedEntries[0];
sessionId = latestEntry.session_id;
console.log(`Loading session from localStorage: ${sessionId} from ${latestEntry.timestamp}`);
}
}
} catch (error) {
console.error(`Error reading from localStorage: ${error}`);
}
// If still no session_id, redirect to home
if (!sessionId) {
window.location.href = '/';
return null;
}
}
return sessionId;
}
// Audio Player Functions
function showAudioPlayer(taskId) {
if (streamingStarted) return;
streamingStarted = true;
const container = document.getElementById('audio-player-container');
audioPlayer = document.getElementById('audio-player');
const tapMessage = document.getElementById('audio-tap-message');
if (!container || !audioPlayer) return;
// Set up event listeners before loading
audioPlayer.addEventListener('play', () => {
console.log('Audio playing');
tapMessage.style.display = 'none';
});
audioPlayer.addEventListener('error', (e) => {
console.error('Audio error:', e.target.error);
});
audioPlayer.addEventListener('stalled', () => {
console.log('Audio stalled - waiting for data');
});
audioPlayer.addEventListener('waiting', () => {
console.log('Audio waiting for data');
});
// Set the stream source directly to Sonauto's streaming endpoint
audioPlayer.src = `https://api-stream.sonauto.ai/stream/${taskId}`;
audioPlayer.load();
container.style.display = 'block';
// Wait for enough data before attempting autoplay
audioPlayer.addEventListener('canplay', function onCanPlay() {
audioPlayer.removeEventListener('canplay', onCanPlay);
// Attempt autoplay
const playPromise = audioPlayer.play();
if (playPromise !== undefined) {
playPromise.then(() => {
console.log('Audio autoplay started');
}).catch((error) => {
// Autoplay blocked - show tap to play message
console.log('Autoplay blocked:', error.message);
tapMessage.style.display = 'block';
// Tapping container or using native controls will both work
// The 'play' event listener above will hide the message
container.addEventListener('click', () => {
audioPlayer.play().catch(e => console.error('Play failed:', e));
}, { once: true });
});
}
}, { once: true });
// Fallback: if canplay doesn't fire within 5 seconds, show player anyway
setTimeout(() => {
if (tapMessage.style.display !== 'block' && audioPlayer.paused) {
console.log('Canplay timeout - showing tap message');
tapMessage.style.display = 'block';
}
}, 5000);
}
function fadeOutAudio(callback) {
if (!audioPlayer || audioPlayer.paused) {
callback();
return;
}
const fadeInterval = 50; // ms
const fadeDuration = 1000; // 1 second fade
const volumeStep = audioPlayer.volume / (fadeDuration / fadeInterval);
const fadeTimer = setInterval(() => {
if (audioPlayer.volume > volumeStep) {
audioPlayer.volume -= volumeStep;
} else {
audioPlayer.volume = 0;
audioPlayer.pause();
clearInterval(fadeTimer);
callback();
}
}, fadeInterval);
}
// Polling Functions
async function pollStatus() {
// Check if we've exceeded max poll time
if (Date.now() - startTime > MAX_POLL_TIME) {
clearInterval(pollTimer);
showTimeoutError();
return;
}
try {
const response = await fetch(`${API_BASE_URL}/api/submissions/${sessionId}/status`);
if (!response.ok) {
if (response.status === 404) {
// Session not found, redirect to home
window.location.href = '/';
console.log(`polling - no session found in API -> ${sessionId}`)
return;
}
console.error(`Status check failed: ${response.status}`);
return;
}
const data = await response.json();
// Check if streaming is ready and show audio player
// if (data.streaming_ready && data.task_id && !streamingStarted) {
// showAudioPlayer(data.task_id);
// }
if (data.status === 'success' || data.streaming_ready) {
// Generation complete, fade out audio and redirect to result page
clearInterval(pollTimer);
clearInterval(countdownTimer);
fadeOutAudio(() => {
window.location.href = `result.php?session_id=${sessionId}`;
});
} else if (data.status === 'fail') {
// Generation failed, fade out audio and redirect to result page
clearInterval(pollTimer);
clearInterval(countdownTimer);
fadeOutAudio(() => {
window.location.href = `result.php?session_id=${sessionId}`;
});
}
// For 'pending' or 'processing', continue polling
} catch (error) {
console.error(`Error polling status: ${error}`);
// Continue polling on network errors
}
}
function startPolling() {
// Start polling immediately and then every 10 seconds
pollStatus();
pollTimer = setInterval(pollStatus, POLL_INTERVAL);
}
// UI Functions
function showTimeoutError() {
// Find and REMOVE the record container
const recordContainer = document.querySelector('.melody-record-container');
if (recordContainer) {
recordContainer.remove();
}
// Select the existing title elements
const bodyTitle = document.querySelector('.body-container div.title');
const bodySubTitle = document.querySelector('.body-container div.sub-title');
// Update the text
if (bodyTitle) {
bodyTitle.textContent = 'Taking longer than expected...';
}
if (bodySubTitle) {
bodySubTitle.textContent = 'Your song is still being created. Please check back in a few minutes.';
}
// Create the NEW buttons div
const newDiv = document.createElement('div');
newDiv.className = 'action-btns';
newDiv.innerHTML = `
<button class="make-another-song-btn" onclick="window.location.reload()">
Check Again
</button>
<button class="make-another-song-btn" onclick="window.location.href='/'">
Create New Song
</button>
`;
// Place buttons after the sub-title
if (bodySubTitle) {
bodySubTitle.after(newDiv);
} else if (bodyTitle) {
bodyTitle.after(newDiv);
}
}
function startCountdown() {
let secondsLeft = COUNTDOWN_START_SECONDS;
const timerElement = document.getElementById("timer");
// Initial render so the user doesn't see a blank span or old value
updateTimerDisplay(secondsLeft);
function updateTimerDisplay(totalSeconds) {
const mins = Math.floor(totalSeconds / 60);
const secs = totalSeconds % 60;
timerElement.textContent = `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
}
const tickRate = 1000 * TIMER_SPEED_FACTOR;
countdownTimer = setInterval(() => {
secondsLeft--;
updateTimerDisplay(secondsLeft);
if (secondsLeft <= 0) {
clearInterval(countdownTimer);
timerElement.textContent = "00:00";
window.location.href = `result.php?session_id=${sessionId}`;
}
}, tickRate);
}
function startRecordTextAnimation() {
const intervalMs = TEXT_SWITCH_INTERVAL * 1000;
const fadeMs = intervalMs * 0.06; // Relative fade timing (e.g., 600ms for 10s)
const overlay = document.querySelector('.record-text-overlay');
const topText = document.querySelector('.top-text textPath');
const bottomText = document.querySelector('.bottom-text textPath');
if (overlay && topText && bottomText) {
overlay.style.transition = `opacity ${fadeMs}ms ease-in-out`;
// Set initial random text
recordTextIndex = Math.floor(Math.random() * PHRASES.length);
topText.textContent = PHRASES[recordTextIndex].top;
bottomText.textContent = PHRASES[recordTextIndex].bottom;
setInterval(() => {
overlay.classList.add('text-hidden');
setTimeout(() => {
// Pick a new random index that is different from the current one
let newIndex;
do {
newIndex = Math.floor(Math.random() * PHRASES.length);
} while (newIndex === recordTextIndex && PHRASES.length > 1);
recordTextIndex = newIndex;
topText.textContent = PHRASES[recordTextIndex].top;
bottomText.textContent = PHRASES[recordTextIndex].bottom;
overlay.classList.remove('text-hidden');
}, fadeMs + 100); // Wait for fade out before swap
}, intervalMs);
}
}
// Main function
function main() {
sessionId = getSessionId();
if (!sessionId) return;
startCountdown();
startRecordTextAnimation();
startPolling();
}
// Initialize
main();
})();
</script>
</body>
</html>