pahvalentines/waiting.php
2026-02-04 18:09:40 +05:30

374 lines
No EOL
17 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">
<?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.png" alt="Jukebox" />
<img class="jukebox-banner-dt" src="assets/images/jukebox-banner-dt.png" alt="Jukebox" />
<div class="title">Your Pet Love Song <br/>will be ready in <span id="timer">30</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">Unleashed</textPath>
</text>
<text class="bottom-text">
<textPath href="#arc-bottom" startOffset="50%" text-anchor="middle">Melody</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 = 30;
const COUNTDOWN_DURATION = 36; // seconds
const TEXT_SWITCH_INTERVAL = 10; // seconds
const PHRASES = [
{ top: "Brown Eyed", bottom: "Gerbil" },
{ top: "Cat it", bottom: "Be Love" },
{ top: "How Squeaky", bottom: "is Your Love" },
{ top: "I Just Called", bottom: "To Say I Wuf You" },
{ top: "I'll Sit Stay", bottom: "And Stand By You" },
{ top: "Kiss Me", bottom: "Meow" },
{ top: "My Bark", bottom: "Will Go On" },
{ top: "Something The", bottom: "Way You Purr" },
{ top: "Unfor-Pet-able", bottom: "" },
{ top: "Unleashed", bottom: "Melody" }
];
// State
let sessionId = null;
const startTime = Date.now();
let pollTimer = 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') {
// Generation complete, fade out audio and redirect to result page
clearInterval(pollTimer);
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);
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 count = COUNTDOWN_START;
const timerElement = document.getElementById("timer");
// Total duration = 36 seconds, so each step = 36 / 30 = 1.2 seconds
// Calculate interval time based on countdown duration
const intervalTime = (COUNTDOWN_DURATION / COUNTDOWN_START) * 1000; // milliseconds
const interval = setInterval(() => {
timerElement.textContent = `${count}`;
count--;
if (count < 0) {
clearInterval(interval);
timerElement.textContent = "";
}
}, intervalTime);
}
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`;
setInterval(() => {
overlay.classList.add('text-hidden');
setTimeout(() => {
recordTextIndex = (recordTextIndex + 1) % PHRASES.length;
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>