![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/soundstudiopro.com/private_html/ |
<?php
session_start();
// ALWAYS check if user is logged in - no exceptions
if (!isset($_SESSION['user_id'])) {
header('Location: /auth/login.php');
exit;
}
// AJAX navigation removed - using normal page navigation
// Include database configuration
require_once 'config/database.php';
$user = getUserById($_SESSION['user_id']);
$user_name = $user['name'] ?? 'User';
$credits = $_SESSION['credits'] ?? 5;
// Get user stats
$pdo = getDBConnection();
$stmt = $pdo->prepare("
SELECT
COUNT(*) as total_tracks,
COUNT(CASE WHEN status = 'complete' THEN 1 END) as completed_tracks,
COUNT(CASE WHEN status = 'processing' THEN 1 END) as processing_tracks,
COUNT(CASE WHEN status = 'failed' THEN 1 END) as failed_tracks,
AVG(CASE WHEN status = 'complete' THEN duration END) as avg_duration,
SUM(CASE WHEN status = 'complete' THEN duration END) as total_duration
FROM music_tracks
WHERE user_id = ?
");
$stmt->execute([$_SESSION['user_id']]);
$user_stats = $stmt->fetch();
// Get user's music tracks with enhanced data and variations
$status_filter = $_GET['status'] ?? 'all';
$sort_filter = $_GET['sort'] ?? 'latest';
// Build WHERE clause for status filtering
$where_clause = "WHERE mt.user_id = ?";
$params = [$_SESSION['user_id']];
if ($status_filter !== 'all') {
$where_clause .= " AND mt.status = ?";
$params[] = $status_filter;
}
// Build ORDER BY clause for sorting (optimized to avoid subqueries)
$order_clause = "ORDER BY ";
switch ($sort_filter) {
case 'oldest':
$order_clause .= "mt.created_at ASC";
break;
case 'popular':
// Use LEFT JOIN instead of subquery for better performance
$order_clause = "LEFT JOIN (SELECT track_id, COUNT(*) as like_count FROM track_likes GROUP BY track_id) likes ON mt.id = likes.track_id ORDER BY likes.like_count DESC, mt.created_at DESC";
break;
case 'most-played':
// Use LEFT JOIN instead of subquery for better performance
$order_clause = "LEFT JOIN (SELECT track_id, COUNT(*) as play_count FROM track_plays GROUP BY track_id) plays ON mt.id = plays.track_id ORDER BY plays.play_count DESC, mt.created_at DESC";
break;
case 'latest':
default:
$order_clause .= "mt.created_at DESC";
break;
}
// Optimized query to avoid subqueries in SELECT
$stmt = $pdo->prepare("
SELECT
mt.*,
COALESCE(vars.variation_count, 0) as variation_count,
CASE
WHEN mt.created_at >= DATE_SUB(NOW(), INTERVAL 1 HOUR) THEN '🔥 Hot'
WHEN mt.created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR) THEN '⭐ New'
ELSE ''
END as badge
FROM music_tracks mt
LEFT JOIN (
SELECT track_id, COUNT(*) as variation_count
FROM audio_variations
GROUP BY track_id
) vars ON mt.id = vars.track_id
$where_clause
$order_clause
");
$stmt->execute($params);
$user_tracks = $stmt->fetchAll();
// Get variations for each track
$tracks_with_variations = [];
foreach ($user_tracks as $track) {
$track['variations'] = [];
if ($track['variation_count'] > 0) {
$stmt = $pdo->prepare("
SELECT
variation_index,
audio_url,
duration,
title,
tags,
image_url,
source_audio_url,
stream_audio_url
FROM audio_variations
WHERE track_id = ?
ORDER BY variation_index
");
$stmt->execute([$track['id']]);
$track['variations'] = $stmt->fetchAll();
}
$tracks_with_variations[] = $track;
}
$user_tracks = $tracks_with_variations;
// Set page variables for header
$page_title = 'My Music Library - SoundStudioPro';
$page_description = 'View and manage all your AI-generated music tracks. Play, download, and organize your music collection.';
$current_page = 'library_new';
// Include header (AJAX navigation removed - always include header)
include 'includes/header.php';
?>
<div class="main-content">
<style>
/* Main Content */
.main-content {
margin-top: 0;
padding: 0;
min-height: calc(100vh - 100px);
}
/* Hero Section */
.hero {
padding: 8rem 0 6rem;
text-align: center;
color: white;
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0a0a0a 100%);
position: relative;
overflow: hidden;
margin-bottom: 4rem;
margin-top: 0;
}
.hero::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse"><path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(102,126,234,0.1)" stroke-width="0.5"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>');
opacity: 0.3;
}
.hero-content {
max-width: 90rem;
margin: 0 auto;
position: relative;
z-index: 2;
}
.hero-badge {
display: inline-block;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.2), rgba(118, 75, 162, 0.2));
color: #667eea;
padding: 1.2rem 2.4rem;
border-radius: 50px;
font-size: 1.4rem;
font-weight: 600;
margin-bottom: 3rem;
backdrop-filter: blur(10px);
border: 1px solid rgba(102, 126, 234, 0.3);
}
.hero-title {
font-size: 5.6rem;
font-weight: 900;
line-height: 1.1;
margin-bottom: 2.4rem;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-subtitle {
font-size: 2rem;
color: #a0aec0;
margin-bottom: 4rem;
max-width: 60rem;
margin-left: auto;
margin-right: auto;
}
/* Library Stats */
.library-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 2rem;
margin-bottom: 4rem;
}
.stat-card {
background: rgba(255, 255, 255, 0.05);
padding: 3rem 2rem;
border-radius: 20px;
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
text-align: center;
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
border-color: rgba(102, 126, 234, 0.3);
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.1);
}
.stat-number {
font-size: 3.6rem;
font-weight: 900;
color: white;
margin-bottom: 1rem;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.stat-label {
font-size: 1.4rem;
color: #a0aec0;
font-weight: 500;
}
/* Library Content */
.library-content {
padding: 4rem 0;
}
.container {
max-width: 140rem;
margin: 0 auto;
padding: 0 2rem;
}
/* Section Headers */
.section-header {
text-align: center;
margin-bottom: 4rem;
}
.section-title-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 2rem;
}
.section-title {
font-size: 4.8rem;
font-weight: 700;
color: white;
margin-bottom: 1.5rem;
}
.section-subtitle {
font-size: 2rem;
color: #a0aec0;
margin-bottom: 4rem;
}
.filter-controls {
display: flex;
gap: 1rem;
align-items: center;
}
.filter-controls select {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
padding: 1rem 1.5rem;
color: white;
font-size: 1.4rem;
cursor: pointer;
transition: all 0.3s ease;
}
.filter-controls select:hover {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.3);
}
.filter-controls select:focus {
outline: none;
border-color: rgba(102, 126, 234, 0.5);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.filter-controls option {
background: #1a1a1a;
color: white;
}
/* Tracks Grid */
.tracks-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
gap: 3rem;
}
.track-card {
background: rgba(255, 255, 255, 0.05);
padding: 3rem;
border-radius: 20px;
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.track-card:hover {
transform: translateY(-5px);
border-color: rgba(102, 126, 234, 0.3);
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.1);
}
.track-card.has-badge {
border-color: rgba(255, 193, 7, 0.3);
box-shadow: 0 10px 30px rgba(255, 193, 7, 0.1);
}
.track-badge {
position: absolute;
top: 1rem;
right: 1rem;
background: linear-gradient(135deg, #ff6b6b, #ffa500);
color: white;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 1.2rem;
font-weight: 700;
z-index: 10;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.track-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 2rem;
position: relative;
}
.track-info {
display: flex;
align-items: center;
gap: 1.5rem;
flex: 1;
margin-right: 2rem;
min-width: 0;
overflow: hidden;
}
.artist-profile {
flex-shrink: 0;
}
.artist-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
object-fit: cover;
border: 3px solid rgba(102, 126, 234, 0.3);
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.artist-avatar:hover {
transform: scale(1.05);
border-color: rgba(102, 126, 234, 0.6);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
}
.clickable-avatar {
cursor: pointer;
position: relative;
}
.clickable-avatar::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 50%;
background: rgba(102, 126, 234, 0.1);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.clickable-avatar:hover::after {
opacity: 1;
}
.default-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 2.4rem;
font-weight: 700;
border: 3px solid rgba(102, 126, 234, 0.3);
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.default-avatar:hover {
transform: scale(1.05);
border-color: rgba(102, 126, 234, 0.6);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
}
.default-avatar.clickable-avatar {
cursor: pointer;
}
.track-details-info {
flex: 1;
}
.track-title {
font-size: 2rem;
font-weight: 700;
color: white;
margin-bottom: 0.5rem;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: 100%;
}
.track-artist {
font-size: 1.4rem;
color: #667eea;
font-weight: 500;
word-wrap: break-word;
overflow-wrap: break-word;
}
.track-prompt {
font-size: 1.4rem;
color: #a0aec0;
line-height: 1.6;
margin-bottom: 2rem;
background: rgba(255, 255, 255, 0.05);
padding: 1.5rem;
border-radius: 12px;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: 100%;
}
.track-details {
display: flex;
gap: 2rem;
margin-bottom: 2rem;
font-size: 1.2rem;
color: #a0aec0;
}
.track-details span {
display: flex;
align-items: center;
gap: 0.5rem;
}
.track-actions {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: flex-start;
}
.track-actions .btn {
flex: 0 0 auto;
min-width: 80px;
}
/* Status indicator for processing tracks */
.status-indicator {
position: fixed;
top: 20px;
right: 20px;
background: var(--bg-card);
border: 1px solid var(--border-medium);
border-radius: 8px;
padding: 12px 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
backdrop-filter: blur(10px);
}
.status-indicator-content {
display: flex;
align-items: center;
gap: 8px;
color: var(--text-primary);
font-size: 14px;
}
.status-indicator i {
color: var(--primary-color);
}
/* Variations Modal */
.variations-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 2000;
backdrop-filter: blur(5px);
}
.variations-modal.active {
display: flex;
align-items: center;
justify-content: center;
}
.variations-content {
background: #1a1a1a;
border-radius: 16px;
padding: 2rem;
max-width: 800px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
border: 1px solid #333;
}
.variations-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-medium);
}
.variations-title {
font-size: 2rem;
font-weight: 700;
color: #ffffff;
}
.close-variations {
background: none;
border: none;
color: #cccccc;
font-size: 2rem;
cursor: pointer;
padding: 0.5rem;
border-radius: 8px;
transition: all 0.3s ease;
}
.close-variations:hover {
color: #ffffff;
background: rgba(255, 255, 255, 0.1);
}
.variations-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.variation-card {
background: #2a2a2a;
border: 2px solid #444;
border-radius: 12px;
padding: 1.5rem;
transition: all 0.3s ease;
cursor: pointer;
}
.variation-card:hover {
border-color: #667eea;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.variation-card.selected {
border-color: #667eea;
background: rgba(102, 126, 234, 0.15);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.variation-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.variation-title {
font-size: 1.4rem;
font-weight: 600;
color: #ffffff;
}
.variation-index {
background: var(--primary-color);
color: white;
padding: 0.4rem 0.8rem;
border-radius: 20px;
font-size: 1.2rem;
font-weight: 600;
}
.variation-duration {
color: #cccccc;
font-size: 1.2rem;
margin-bottom: 1rem;
}
.variation-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1rem;
}
.variation-tag {
background: #444;
color: #cccccc;
padding: 0.3rem 0.8rem;
border-radius: 12px;
font-size: 1.1rem;
border: 1px solid #555;
}
.variation-actions {
display: flex;
gap: 0.8rem;
flex-wrap: wrap;
}
.variation-btn {
flex: 1;
min-width: 80px;
padding: 0.8rem;
border: none;
border-radius: 8px;
font-size: 1.2rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.variation-btn.play {
background: #667eea;
color: white;
border: 1px solid #667eea;
font-weight: 600;
}
.variation-btn.play:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
}
.variation-btn.download {
background: rgba(245, 158, 11, 0.3);
color: #fbbf24;
border: 1px solid rgba(245, 158, 11, 0.5);
font-weight: 600;
}
.variation-btn.download:hover {
background: rgba(245, 158, 11, 0.4);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
}
.variation-btn.select {
background: rgba(72, 187, 120, 0.3);
color: #68d391;
border: 1px solid rgba(72, 187, 120, 0.5);
font-weight: 600;
}
.variation-btn.select:hover {
background: rgba(72, 187, 120, 0.4);
box-shadow: 0 4px 12px rgba(72, 187, 120, 0.3);
}
.variation-btn.select.selected {
background: #48bb78;
color: white;
font-weight: 700;
}
.variations-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 1rem;
border-top: 1px solid #444;
}
.variations-info {
color: #cccccc;
font-size: 1.3rem;
}
.variations-actions {
display: flex;
gap: 1rem;
}
.variations-btn {
padding: 1rem 2rem;
border: none;
border-radius: 8px;
font-size: 1.4rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.variations-btn.cancel {
background: rgba(255, 255, 255, 0.15);
color: #cccccc;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.variations-btn.cancel:hover {
background: rgba(255, 255, 255, 0.25);
color: #ffffff;
}
.variations-btn.save {
background: #667eea;
color: white;
border: 1px solid #667eea;
}
.variations-btn.save:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
}
.variations-btn.save:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
/* Lyrics Modal */
.lyrics-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 2000;
backdrop-filter: blur(5px);
}
.lyrics-modal.active {
display: flex;
align-items: center;
justify-content: center;
}
.lyrics-content {
background: #1a1a1a;
border-radius: 16px;
padding: 2rem;
max-width: 800px;
width: 95%;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
border: 1px solid #333;
}
.lyrics-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-medium);
}
.lyrics-title {
font-size: 2rem;
font-weight: 700;
color: #ffffff;
}
.close-lyrics {
background: none;
border: none;
color: #cccccc;
font-size: 2rem;
cursor: pointer;
padding: 0.5rem;
border-radius: 8px;
transition: all 0.3s ease;
}
.close-lyrics:hover {
color: #ffffff;
background: rgba(255, 255, 255, 0.1);
}
.lyrics-body {
margin-bottom: 2rem;
}
.lyrics-track-info {
margin-bottom: 2rem;
text-align: center;
}
.lyrics-track-title {
font-size: 2.4rem;
font-weight: 700;
color: #ffffff;
margin-bottom: 0.5rem;
}
.lyrics-track-meta {
color: #a0aec0;
font-size: 1.4rem;
}
.lyrics-text-container {
background: #2a2a2a;
border-radius: 12px;
padding: 2rem;
border: 1px solid #444;
max-height: 60vh;
overflow-y: auto;
min-height: 300px;
}
.lyrics-text {
font-size: 1.6rem;
line-height: 1.8;
color: #ffffff;
white-space: pre-wrap;
font-family: 'Inter', sans-serif;
word-wrap: break-word;
overflow-wrap: break-word;
}
.lyrics-section {
margin-bottom: 2rem;
padding: 1.5rem;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
border-left: 3px solid #667eea;
}
.lyrics-section h4 {
font-size: 1.8rem;
font-weight: 600;
color: #667eea;
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.lyrics-section .lyrics-content {
background: rgba(0, 0, 0, 0.3);
border-radius: 6px;
padding: 1rem;
margin-top: 0.5rem;
}
.lyrics-section .lyrics-content pre {
font-family: 'Inter', sans-serif;
font-size: 1.4rem;
line-height: 1.6;
color: #ffffff;
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word;
margin: 0;
}
.lyrics-section {
margin-bottom: 2rem;
}
.lyrics-section h4 {
color: #667eea;
font-size: 1.6rem;
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.8rem;
}
.lyrics-section .lyrics-content {
background: rgba(255, 255, 255, 0.05);
padding: 1.2rem;
border-radius: 8px;
max-height: 200px;
overflow-y: auto;
}
.lyrics-section .lyrics-content pre {
background: none;
padding: 0;
margin: 0;
font-family: inherit;
color: inherit;
}
.lyrics-footer {
display: flex;
justify-content: center;
gap: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--border-medium);
}
.lyrics-actions {
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
}
.lyrics-btn {
padding: 1rem 2rem;
border: none;
border-radius: 12px;
font-size: 1.4rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 0.8rem;
}
.lyrics-btn.copy {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
}
.lyrics-btn.download {
background: linear-gradient(135deg, #48bb78, #38a169);
color: white;
}
.lyrics-btn.close {
background: rgba(255, 255, 255, 0.1);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.lyrics-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
}
.btn {
padding: 1rem 2rem;
border: none;
border-radius: 12px;
font-size: 1.4rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.8rem;
}
.btn-primary {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
}
.btn-secondary {
background: rgba(255, 255, 255, 0.1);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.btn-success {
background: linear-gradient(135deg, #48bb78, #38a169);
color: white;
}
.btn-danger {
background: linear-gradient(135deg, #f56565, #e53e3e);
color: white;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
}
.status-badge {
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 1.2rem;
font-weight: 600;
}
.status-complete {
background: rgba(72, 187, 120, 0.2);
color: #48bb78;
}
.status-processing {
background: rgba(255, 193, 7, 0.2);
color: #ffc107;
animation: pulse 2s infinite;
}
.status-failed {
background: rgba(245, 101, 101, 0.2);
color: #f56565;
}
.processing-status-container {
display: flex;
align-items: center;
gap: 0.5rem;
}
.refresh-status-btn {
padding: 0.3rem 0.6rem;
font-size: 1.2rem;
border-radius: 6px;
transition: all 0.3s ease;
}
.refresh-status-btn:hover {
transform: rotate(180deg);
background: rgba(255, 193, 7, 0.2);
border-color: #ffc107;
color: #ffc107;
}
.failed-status-container {
display: flex;
align-items: center;
gap: 0.5rem;
}
.retry-btn {
background: linear-gradient(135deg, #667eea, #764ba2);
border: none;
transition: all 0.3s ease;
}
.retry-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
}
.help-btn {
padding: 0.3rem 0.6rem;
font-size: 1.2rem;
border-radius: 6px;
transition: all 0.3s ease;
}
.help-btn:hover {
background: rgba(108, 117, 125, 0.2);
border-color: #6c757d;
color: #6c757d;
}
.track-card[data-status="failed"] {
opacity: 0.7;
border-color: rgba(245, 101, 101, 0.3);
}
.track-card[data-status="failed"]:hover {
opacity: 0.9;
border-color: rgba(245, 101, 101, 0.5);
}
.error-message {
margin: 0.5rem 0;
padding: 0.8rem;
background: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
border-radius: 8px;
color: #ef4444;
font-size: 0.9rem;
line-height: 1.4;
}
.failed-actions {
display: flex;
gap: 0.5rem;
margin-top: 0.5rem;
flex-wrap: wrap;
}
.delete-btn {
background: transparent;
border: 1px solid rgba(239, 68, 68, 0.5);
color: #ef4444;
transition: all 0.3s ease;
}
.delete-btn:hover {
background: rgba(239, 68, 68, 0.1);
border-color: #ef4444;
color: #ef4444;
transform: translateY(-1px);
}
.variations-badge {
background: rgba(102, 126, 234, 0.2);
color: #667eea;
padding: 0.3rem 0.8rem;
border-radius: 12px;
font-size: 1.2rem;
margin-left: 1rem;
display: inline-flex;
align-items: center;
gap: 0.3rem;
}
.playable-badge {
background: rgba(72, 187, 120, 0.2);
color: #48bb78;
padding: 0.3rem 0.8rem;
border-radius: 12px;
font-size: 1.2rem;
margin-left: 1rem;
display: inline-flex;
align-items: center;
gap: 0.3rem;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 4rem;
grid-column: 1 / -1;
}
.empty-icon {
font-size: 6rem;
margin-bottom: 2rem;
}
.empty-title {
font-size: 2.4rem;
color: white;
margin-bottom: 1rem;
}
.empty-description {
font-size: 1.6rem;
color: #a0aec0;
margin-bottom: 3rem;
}
/* Loading State */
.loading-tracks {
text-align: center;
color: #a0aec0;
font-size: 1.6rem;
padding: 4rem;
grid-column: 1 / -1;
}
/* Responsive */
@media (max-width: 768px) {
.hero-title {
font-size: 4rem;
}
.hero-subtitle {
font-size: 1.6rem;
}
.section-title {
font-size: 3.2rem;
}
.section-subtitle {
font-size: 1.4rem;
}
.library-stats {
grid-template-columns: repeat(2, 1fr);
gap: 2rem;
}
.stat-card {
padding: 2rem 1.5rem;
}
.stat-number {
font-size: 2.8rem;
}
.stat-label {
font-size: 1.2rem;
}
.tracks-grid {
grid-template-columns: 1fr;
gap: 2rem;
}
.track-card {
padding: 2rem;
}
.track-title {
font-size: 2rem;
}
.track-artist {
font-size: 1.4rem;
}
.track-prompt {
font-size: 1.4rem;
line-height: 1.5;
}
.track-lyrics-preview {
color: #667eea;
font-size: 1.4rem;
line-height: 1.5;
padding: 0.8rem;
background: rgba(102, 126, 234, 0.1);
border-radius: 8px;
border-left: 3px solid #667eea;
margin: 0.5rem 0 1rem 0;
}
/* Lyrics Section */
.lyrics-section {
margin: 1.5rem 0;
padding: 1.5rem;
background: rgba(255, 255, 255, 0.03);
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.05);
}
.lyrics-section h4 {
color: #667eea;
font-size: 1.4rem;
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.8rem;
}
.lyrics-content {
max-height: 200px;
overflow-y: auto;
}
.lyrics-content pre {
background: rgba(255, 255, 255, 0.05);
padding: 1.2rem;
border-radius: 8px;
font-size: 1.2rem;
line-height: 1.6;
color: #e2e8f0;
white-space: pre-wrap;
word-wrap: break-word;
margin: 0;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
}
.lyrics-content::-webkit-scrollbar {
width: 6px;
}
.lyrics-content::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 3px;
}
.lyrics-content::-webkit-scrollbar-thumb {
background: rgba(102, 126, 234, 0.5);
border-radius: 3px;
}
.lyrics-content::-webkit-scrollbar-thumb:hover {
background: rgba(102, 126, 234, 0.7);
}
.lyrics-toggle {
background: rgba(102, 126, 234, 0.1);
border: 1px solid rgba(102, 126, 234, 0.3);
color: #667eea;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
font-size: 1.2rem;
transition: all 0.3s ease;
margin-top: 1rem;
}
.lyrics-toggle:hover {
background: rgba(102, 126, 234, 0.2);
border-color: rgba(102, 126, 234, 0.5);
}
/* Responsive lyrics */
@media (max-width: 768px) {
.lyrics-section {
margin: 1rem 0;
padding: 1rem;
}
.lyrics-section h4 {
font-size: 1.2rem;
}
.lyrics-content pre {
font-size: 1rem;
padding: 1rem;
}
.lyrics-toggle {
font-size: 1rem;
padding: 0.8rem 1.5rem;
}
}
.track-details {
flex-wrap: wrap;
gap: 1rem;
}
.track-details span {
font-size: 1.2rem;
}
.track-actions {
flex-direction: column;
gap: 1rem;
}
.track-actions .btn {
width: 100%;
justify-content: center;
padding: 1.2rem 2rem;
font-size: 1.4rem;
}
.container {
padding: 0 1.5rem;
}
.library-content {
padding: 4rem 0;
}
.section-title-row {
flex-direction: column;
gap: 1.5rem;
}
.filter-controls {
flex-wrap: wrap;
gap: 0.8rem;
}
.filter-controls select {
padding: 0.8rem 1.2rem;
font-size: 1.3rem;
}
.filter-summary {
display: flex;
align-items: center;
gap: 1rem;
margin-top: 1rem;
}
.filter-badge {
background: rgba(102, 126, 234, 0.2);
color: #667eea;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 1.2rem;
font-weight: 500;
}
.clear-filters {
color: #a0aec0;
text-decoration: none;
font-size: 1.2rem;
transition: color 0.3s ease;
}
.clear-filters:hover {
color: #667eea;
}
.track-info {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.artist-avatar {
width: 50px;
height: 50px;
}
.default-avatar {
width: 50px;
height: 50px;
font-size: 2rem;
}
}
@media (max-width: 480px) {
.hero-title {
font-size: 3.2rem;
}
.hero-subtitle {
font-size: 1.4rem;
}
.section-title {
font-size: 2.8rem;
}
.library-stats {
grid-template-columns: 1fr;
}
.track-card {
padding: 1.5rem;
}
.track-title {
font-size: 1.8rem;
}
.track-actions .btn {
padding: 1rem 1.5rem;
font-size: 1.3rem;
}
}
</style>
<!-- Hero Section -->
<section class="hero">
<div class="container">
<div class="hero-content">
<div class="hero-badge">🎵 My Library</div>
<h1 class="hero-title">Your Music Collection</h1>
<p class="hero-subtitle">Manage, play, and organize all your AI-generated music tracks. Your creative journey starts here.</p>
</div>
</div>
</section>
<!-- Library Content -->
<section class="library-content">
<div class="container">
<!-- Library Stats -->
<div class="library-stats">
<div class="stat-card">
<div class="stat-number"><?= $user_stats['total_tracks'] ?></div>
<div class="stat-label">Total Tracks</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= $user_stats['completed_tracks'] ?></div>
<div class="stat-label">Completed</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= $user_stats['processing_tracks'] ?></div>
<div class="stat-label">Processing</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= $user_stats['failed_tracks'] ?></div>
<div class="stat-label">Failed</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= $user_stats['avg_duration'] ? round($user_stats['avg_duration'] / 60, 1) : 0 ?>m</div>
<div class="stat-label">Avg Duration</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= $credits ?></div>
<div class="stat-label">Credits Left</div>
</div>
</div>
<!-- Tracks Section -->
<div class="tracks-section">
<div class="section-header">
<div class="section-title-row">
<div>
<h2 class="section-title">Your Music Tracks</h2>
<p class="section-subtitle">All your AI-generated music in one place</p>
</div>
<div class="filter-controls">
<select id="status-filter" onchange="filterTracks()">
<option value="all" <?= $status_filter === 'all' ? 'selected' : '' ?>>All Status</option>
<option value="complete" <?= $status_filter === 'complete' ? 'selected' : '' ?>>✅ Complete</option>
<option value="processing" <?= $status_filter === 'processing' ? 'selected' : '' ?>>⏳ Processing</option>
<option value="failed" <?= $status_filter === 'failed' ? 'selected' : '' ?>>❌ Failed</option>
</select>
<select id="sort-filter" onchange="filterTracks()">
<option value="latest" <?= $sort_filter === 'latest' ? 'selected' : '' ?>>🕒 Latest</option>
<option value="oldest" <?= $sort_filter === 'oldest' ? 'selected' : '' ?>>📅 Oldest</option>
<option value="popular" <?= $sort_filter === 'popular' ? 'selected' : '' ?>>💎 Most Popular</option>
<option value="most-played" <?= $sort_filter === 'most-played' ? 'selected' : '' ?>>▶️ Most Played</option>
</select>
<?php if ($status_filter !== 'all' || $sort_filter !== 'latest'): ?>
<div class="filter-summary">
<span class="filter-badge">
<?php if ($status_filter !== 'all'): ?>
Status: <?= ucfirst($status_filter) ?>
<?php endif; ?>
<?php if ($status_filter !== 'all' && $sort_filter !== 'latest'): ?> • <?php endif; ?>
<?php if ($sort_filter !== 'latest'): ?>
Sort: <?= ucfirst(str_replace('-', ' ', $sort_filter)) ?>
<?php endif; ?>
</span>
<a href="library.php" class="clear-filters">Clear Filters</a>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="tracks-grid">
<?php
// Debug info
if (isset($_GET['debug'])) {
echo "<div style='background: rgba(255,255,255,0.1); padding: 1rem; margin: 1rem 0; border-radius: 8px;'>";
echo "<strong>Debug Info:</strong><br>";
echo "User ID: " . $_SESSION['user_id'] . "<br>";
echo "User Name: " . $user_name . "<br>";
echo "Track Count: " . count($user_tracks) . "<br>";
echo "Status Filter: " . $status_filter . "<br>";
echo "Sort Filter: " . $sort_filter . "<br>";
echo "</div>";
}
?>
<?php if (empty($user_tracks)): ?>
<div class="empty-state">
<div class="empty-icon">🎵</div>
<h3 class="empty-title">No tracks yet</h3>
<p class="empty-description">Start creating amazing AI music to build your library!</p>
<a href="/#create" class="btn btn-primary">
<i class="fas fa-plus"></i> Create Your First Track
</a>
<br><br>
<a href="/debug_stephane_tracks.php" class="btn btn-secondary" style="background: rgba(255,255,255,0.1);">
<i class="fas fa-bug"></i> Debug Tracks
</a>
</div>
<?php else: ?>
<?php foreach ($user_tracks as $track): ?>
<div class="track-card <?= !empty($track['badge']) ? 'has-badge' : '' ?>"
data-track-id="<?= $track['id'] ?>"
data-status="<?= $track['status'] ?>"
data-selected-variation="<?= $track['selected_variation'] ?? 0 ?>"
data-variations="<?= htmlspecialchars(json_encode($track['variations'] ?? [])) ?>">
<?php if (!empty($track['badge'])): ?>
<div class="track-badge"><?= $track['badge'] ?></div>
<?php endif; ?>
<div class="track-header">
<div class="track-info">
<div class="artist-profile">
<?php if (!empty($user['profile_image'])): ?>
<img src="<?= htmlspecialchars($user['profile_image']) ?>"
alt="<?= htmlspecialchars($user_name) ?>"
class="artist-avatar clickable-avatar"
title="Your profile"
onerror="this.parentElement.innerHTML='<div class=\'default-avatar clickable-avatar\' title=\'Your profile\'><?= substr(htmlspecialchars(ucwords(strtolower($user_name))), 0, 1) ?></div>'">
<?php else: ?>
<div class="default-avatar clickable-avatar" title="Your profile"><?= substr(htmlspecialchars(ucwords(strtolower($user_name))), 0, 1) ?></div>
<?php endif; ?>
</div>
<div class="track-details-info">
<div class="track-title">
<?php
$displayTitle = $track['title'];
if (empty($displayTitle)) {
// Generate title from prompt if available
if (!empty($track['prompt'])) {
$displayTitle = substr($track['prompt'], 0, 50);
if (strlen($track['prompt']) > 50) {
$displayTitle .= '...';
}
} else {
$displayTitle = 'Untitled Track';
}
}
?>
<?= htmlspecialchars($displayTitle) ?>
<?php if ($track['status'] === 'complete' && !empty($track['audio_url'])): ?>
<span class="playable-badge" title="Available for playback">
<i class="fas fa-play-circle"></i> Playable
</span>
<?php endif; ?>
<?php if ($track['variation_count'] > 0): ?>
<span class="variations-badge" title="<?= $track['variation_count'] ?> variations available">
<i class="fas fa-layer-group"></i> <?= $track['variation_count'] ?>
</span>
<?php endif; ?>
</div>
<div class="track-artist">by <?= htmlspecialchars($user_name) ?></div>
</div>
</div>
</div>
<?php
// Extract lyrics from metadata if available
$original_prompt = $track['prompt'];
$generated_lyrics = '';
if ($track['status'] === 'complete' && !empty($track['metadata'])) {
$metadata = json_decode($track['metadata'], true);
if ($metadata && isset($metadata['data']['data'][0]['prompt'])) {
$generated_lyrics = $metadata['data']['data'][0]['prompt'];
}
}
?>
<!-- Original Prompt -->
<div class="track-prompt">
<strong>Original Prompt:</strong> <?= htmlspecialchars(substr($original_prompt, 0, 150)) ?>...
</div>
<!-- Full Lyrics Section -->
<?php if (!empty($generated_lyrics) && $generated_lyrics !== $original_prompt): ?>
<div class="lyrics-section" id="lyrics-section-<?= $track['id'] ?>" style="display: none;">
<h4><i class="fas fa-music"></i> Generated Lyrics</h4>
<div class="lyrics-content">
<pre><?= htmlspecialchars($generated_lyrics) ?></pre>
</div>
</div>
<button class="lyrics-toggle" onclick="toggleLyrics(<?= $track['id'] ?>)">
<i class="fas fa-music"></i> Show Full Lyrics
</button>
<?php endif; ?>
<div class="track-details">
<span><i class="fas fa-clock"></i> <?= floor($track['duration'] / 60) ?>m <?= $track['duration'] % 60 ?>s</span>
<span><i class="fas fa-calendar"></i> <?= date('M j, Y', strtotime($track['created_at'])) ?></span>
<span class="status-badge status-<?= $track['status'] ?>">
<?= ucfirst($track['status']) ?>
</span>
</div>
<div class="track-actions">
<?php if ($track['status'] === 'complete' && !empty($track['audio_url'])): ?>
<?php
// Use selected variation's audio URL if available
$audioUrl = $track['audio_url'];
if (!empty($track['variations']) && isset($track['selected_variation'])) {
$selectedIndex = $track['selected_variation'] ?? 0;
if (isset($track['variations'][$selectedIndex])) {
$audioUrl = $track['variations'][$selectedIndex]['audio_url'];
}
}
?>
<button class="btn btn-primary play-track-btn"
data-audio-url="<?= htmlspecialchars($audioUrl) ?>"
data-title="<?= htmlspecialchars($displayTitle) ?>"
data-artist="<?= htmlspecialchars($user_name) ?>"
data-track-id="<?= $track['id'] ?>">
<i class="fas fa-play"></i> Play
</button>
<a href="<?= htmlspecialchars($audioUrl) ?>" class="btn btn-secondary" download>
<i class="fas fa-download"></i> Download
</a>
<?php if ($track['variation_count'] > 0): ?>
<button class="btn btn-secondary" onclick="showVariations(<?= $track['id'] ?>)">
<i class="fas fa-layer-group"></i> Variations
</button>
<?php endif; ?>
<?php
// Check for lyrics in metadata
$has_lyrics = false;
$lyrics_content = '';
if ($track['status'] === 'complete' && !empty($track['metadata'])) {
$metadata = json_decode($track['metadata'], true);
if ($metadata && isset($metadata['data']['data'][0]['prompt'])) {
$generated_lyrics = $metadata['data']['data'][0]['prompt'];
if (!empty($generated_lyrics)) {
$has_lyrics = true;
$lyrics_content = $generated_lyrics;
}
}
}
// Also check for separate lyrics field
if (!$has_lyrics && !empty($track['lyrics'])) {
$has_lyrics = true;
$lyrics_content = $track['lyrics'];
}
?>
<?php if ($has_lyrics): ?>
<button class="btn btn-secondary" onclick="showLyrics(<?= $track['id'] ?>, <?= json_encode($displayTitle) ?>, <?= json_encode($lyrics_content) ?>, <?= json_encode($track['prompt']) ?>)">
<i class="fas fa-align-left"></i> Lyrics
</button>
<?php elseif (!empty($generated_lyrics) && $generated_lyrics !== $original_prompt): ?>
<button class="btn btn-secondary" onclick="showLyrics(<?= $track['id'] ?>, <?= json_encode($displayTitle) ?>, <?= json_encode($generated_lyrics) ?>, <?= json_encode($original_prompt) ?>)">
<i class="fas fa-music"></i> Lyrics
</button>
<?php else: ?>
<!-- Debug: No lyrics found for track <?= $track['id'] ?> -->
<?php endif; ?>
<?php elseif ($track['status'] === 'processing'): ?>
<div class="processing-status-container">
<span class="btn btn-secondary">
<i class="fas fa-spinner fa-spin"></i> Processing...
</span>
<button onclick="checkTrackStatus(<?= $track['id'] ?>)" class="btn btn-sm btn-outline-secondary refresh-status-btn" title="Check Status">
<i class="fas fa-sync-alt"></i>
</button>
</div>
<?php elseif ($track['status'] === 'failed'): ?>
<?php
// Extract error message from metadata
$error_message = '';
$error_type = '';
if (!empty($track['metadata'])) {
$metadata = json_decode($track['metadata'], true);
if ($metadata && isset($metadata['msg'])) {
$error_message = $metadata['msg'];
}
if ($metadata && isset($metadata['code'])) {
$error_type = $metadata['code'] == 400 ? 'content_violation' : 'technical_error';
}
}
?>
<div class="failed-status-container">
<span class="btn btn-danger">
<i class="fas fa-exclamation-triangle"></i> Failed
</span>
<?php if ($error_message): ?>
<div class="error-message" style="margin: 0.5rem 0; padding: 0.5rem; background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.3); border-radius: 8px; color: #ef4444; font-size: 0.9rem;">
<strong>Error:</strong> <?= htmlspecialchars($error_message) ?>
</div>
<?php endif; ?>
<div class="failed-actions" style="display: flex; gap: 0.5rem; margin-top: 0.5rem;">
<button class="btn btn-primary retry-btn" onclick="retryTrack(<?= $track['id'] ?>)" title="Create a new version with the same prompt">
<i class="fas fa-redo"></i> Retry
</button>
<button class="btn btn-outline-secondary help-btn" onclick="showFailureHelp(<?= $track['id'] ?>)" title="Why did this fail?">
<i class="fas fa-question-circle"></i>
</button>
<button class="btn btn-outline-danger delete-btn" onclick="deleteFailedTrack(<?= $track['id'] ?>)" title="Delete this failed track">
<i class="fas fa-trash"></i> Delete
</button>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div>
</section>
<!-- Lyrics Modal -->
<div id="lyricsModal" class="lyrics-modal">
<div class="lyrics-content">
<div class="lyrics-header">
<h2 class="lyrics-title">Track Lyrics</h2>
<button class="close-lyrics" onclick="closeLyrics()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="lyrics-body">
<div class="lyrics-track-info">
<h3 id="lyricsTrackTitle" class="lyrics-track-title"></h3>
<div class="lyrics-track-meta">
<span class="lyrics-artist">by <?= htmlspecialchars($user_name) ?></span>
</div>
</div>
<div class="lyrics-text-container">
<div id="lyricsText" class="lyrics-text">
<!-- Lyrics will be loaded here -->
</div>
</div>
</div>
<div class="lyrics-footer">
<div class="lyrics-actions">
<button class="lyrics-btn copy" onclick="copyLyrics()">
<i class="fas fa-copy"></i> Copy Lyrics
</button>
<button class="lyrics-btn download" onclick="downloadLyrics()">
<i class="fas fa-download"></i> Download
</button>
<button class="lyrics-btn close" onclick="closeLyrics()">
Close
</button>
</div>
</div>
</div>
</div>
<!-- Variations Modal -->
<div id="variationsModal" class="variations-modal">
<div class="variations-content">
<div class="variations-header">
<h2 class="variations-title">Choose Your Track Variation</h2>
<button class="close-variations" onclick="closeVariations()">
<i class="fas fa-times"></i>
</button>
</div>
<div id="variationsGrid" class="variations-grid">
<!-- Variations will be loaded here -->
</div>
<div class="variations-footer">
<div class="variations-info">
<i class="fas fa-info-circle"></i>
Select which variation will be your main track for sale and public display. You can also download individual variations or all versions at once.
</div>
<div class="variations-actions">
<button class="variations-btn cancel" onclick="closeVariations()">
Cancel
</button>
<button id="saveVariationBtn" class="variations-btn save" onclick="saveVariationSelection()" disabled>
Save Selection
</button>
</div>
</div>
</div>
</div>
</div>
<script>
// Library page functionality using global player
// Variations functionality variables
let trackVariations = [];
let currentTrackId = null;
let selectedVariationIndex = null;
// Check if global player is loaded
window.addEventListener('load', function() {
console.log('🎵 Library page loaded');
console.log('🎵 Global player status:', typeof window.globalPlayer !== 'undefined');
console.log('🎵 Global player element exists:', !!document.getElementById('globalMusicPlayer'));
console.log('🎵 playTrackWithGlobalPlayer function exists:', typeof window.playTrackWithGlobalPlayer === 'function');
if (typeof window.globalPlayer === 'undefined') {
console.error('🎵 Global player not loaded!');
setTimeout(() => {
if (typeof window.globalPlayer !== 'undefined') {
console.log('🎵 Global player loaded after delay');
} else {
console.error('🎵 Global player still not available after delay');
}
}, 1000);
} else {
console.log('🎵 Global player loaded successfully');
window.globalPlayer.showPlayer();
}
// Check if playTrackWithGlobalPlayer is available
if (typeof window.playTrackWithGlobalPlayer === 'function') {
console.log('🎵 playTrackWithGlobalPlayer function is available');
} else {
console.error('🎵 playTrackWithGlobalPlayer function is missing!');
}
// Start status checking for processing tracks
checkProcessingTracks();
});
// Check processing tracks status periodically
function checkProcessingTracks() {
const processingTracks = document.querySelectorAll('.track-card[data-status="processing"]');
if (processingTracks.length > 0) {
console.log('🎵 Found processing tracks, checking status...');
// Show status indicator
showStatusIndicator(`Checking ${processingTracks.length} processing track(s)...`);
processingTracks.forEach(trackCard => {
const trackId = trackCard.getAttribute('data-track-id');
checkTrackStatus(trackId);
});
// Check again in 30 seconds
setTimeout(checkProcessingTracks, 30000);
} else {
hideStatusIndicator();
}
}
// Show status indicator
function showStatusIndicator(message) {
let indicator = document.getElementById('status-indicator');
if (!indicator) {
indicator = document.createElement('div');
indicator.id = 'status-indicator';
indicator.className = 'status-indicator';
indicator.innerHTML = `
<div class="status-indicator-content">
<i class="fas fa-spinner fa-spin"></i>
<span>${message}</span>
</div>
`;
document.body.appendChild(indicator);
} else {
indicator.querySelector('span').textContent = message;
}
indicator.style.display = 'block';
}
// Hide status indicator
function hideStatusIndicator() {
const indicator = document.getElementById('status-indicator');
if (indicator) {
indicator.style.display = 'none';
}
}
// Check individual track status
async function checkTrackStatus(trackId) {
try {
console.log(`🎵 Checking status for track ${trackId}...`);
// Show loading state on the refresh button
const refreshBtn = document.querySelector(`[onclick="checkTrackStatus(${trackId})"]`);
if (refreshBtn) {
const originalContent = refreshBtn.innerHTML;
refreshBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
refreshBtn.disabled = true;
}
const response = await fetch(`/api/check_track_status.php?track_id=${trackId}`);
const result = await response.json();
if (result.success && result.data) {
const status = result.data.status;
const trackCard = document.querySelector(`[data-track-id="${trackId}"]`);
console.log(`🎵 Track ${trackId} status: ${status}`);
if (trackCard && status !== 'processing') {
console.log(`🎵 Track ${trackId} status changed to: ${status}`);
// Update track card status
trackCard.setAttribute('data-status', status);
// Show success notification
if (typeof window.showNotification === 'function') {
window.showNotification(`Track status updated: ${status}`, 'success');
}
// Refresh the page to show updated status
setTimeout(() => {
window.location.reload();
}, 2000);
} else if (status === 'processing') {
// Show notification that it's still processing
if (typeof window.showNotification === 'function') {
window.showNotification('Track is still processing...', 'info');
}
}
} else {
console.error('🎵 Track status check failed:', result.message);
if (typeof window.showNotification === 'function') {
window.showNotification('Failed to check track status', 'error');
}
}
} catch (error) {
console.error('🎵 Error checking track status:', error);
// Show error notification if available
if (typeof window.showNotification === 'function') {
window.showNotification('Error checking track status', 'error');
}
} finally {
// Restore refresh button
if (refreshBtn) {
refreshBtn.innerHTML = '<i class="fas fa-sync-alt"></i>';
refreshBtn.disabled = false;
}
}
}
// Retry failed track
async function retryTrack(trackId) {
if (!confirm('🔄 Are you sure you want to retry this track? This will create a new version using your original prompt.')) {
return;
}
try {
console.log(`🎵 Retrying track ${trackId}...`);
// Show loading state
const retryBtn = document.querySelector(`[onclick="retryTrack(${trackId})"]`);
if (retryBtn) {
const originalContent = retryBtn.innerHTML;
retryBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Retrying...';
retryBtn.disabled = true;
}
const response = await fetch('/api_retry.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({ track_id: trackId })
});
const result = await response.json();
if (result.success) {
console.log('🎵 Track retry initiated successfully');
if (typeof window.showNotification === 'function') {
window.showNotification('Track retry initiated! Check your library for the new version.', 'success');
}
// Refresh the page to show the new track
setTimeout(() => {
window.location.reload();
}, 2000);
} else {
console.error('🎵 Track retry failed:', result.error);
if (typeof window.showNotification === 'function') {
window.showNotification('Failed to retry track: ' + (result.error || 'Unknown error'), 'error');
} else {
alert('❌ Failed to retry track: ' + (result.error || 'Unknown error'));
}
}
} catch (error) {
console.error('🎵 Error retrying track:', error);
if (typeof window.showNotification === 'function') {
window.showNotification('Network error retrying track. Please try again.', 'error');
} else {
alert('❌ Network error retrying track. Please check your connection and try again.');
}
} finally {
// Restore retry button
if (retryBtn) {
retryBtn.innerHTML = '<i class="fas fa-redo"></i> Retry';
retryBtn.disabled = false;
}
}
}
// Delete failed track
async function deleteFailedTrack(trackId) {
if (!confirm('🗑️ Are you sure you want to delete this failed track? This action cannot be undone.')) {
return;
}
try {
console.log(`🎵 Deleting failed track ${trackId}...`);
// Show loading state
const deleteBtn = document.querySelector(`[onclick="deleteFailedTrack(${trackId})"]`);
if (deleteBtn) {
const originalContent = deleteBtn.innerHTML;
deleteBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Deleting...';
deleteBtn.disabled = true;
}
const response = await fetch('/api_delete_track.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({ track_id: trackId })
});
const result = await response.json();
if (result.success) {
console.log('🎵 Failed track deleted successfully');
if (typeof window.showNotification === 'function') {
window.showNotification('Failed track deleted successfully!', 'success');
}
// Remove the track card from the DOM
const trackCard = document.querySelector(`[data-track-id="${trackId}"]`);
if (trackCard) {
trackCard.remove();
} else {
// Fallback: refresh the page
setTimeout(() => {
window.location.reload();
}, 1000);
}
} else {
console.error('🎵 Failed to delete track:', result.error);
if (typeof window.showNotification === 'function') {
window.showNotification('Failed to delete track: ' + (result.error || 'Unknown error'), 'error');
} else {
alert('❌ Failed to delete track: ' + (result.error || 'Unknown error'));
}
}
} catch (error) {
console.error('🎵 Error deleting track:', error);
if (typeof window.showNotification === 'function') {
window.showNotification('Network error deleting track. Please try again.', 'error');
} else {
alert('❌ Network error deleting track. Please check your connection and try again.');
}
} finally {
// Restore delete button
if (deleteBtn) {
deleteBtn.innerHTML = '<i class="fas fa-trash"></i> Delete';
deleteBtn.disabled = false;
}
}
}
// Show failure help modal
function showFailureHelp(trackId) {
const helpContent = `
<div class="failure-help-modal">
<h3>🤔 Why did my track fail?</h3>
<p>Track generation can fail for several reasons:</p>
<ul>
<li><strong>🔧 Technical Issues:</strong> API service temporarily unavailable</li>
<li><strong>📝 Content Violations:</strong> Prompt contains restricted content (artist names, copyrighted material)</li>
<li><strong>⏱️ Timeout:</strong> Generation took too long</li>
<li><strong>🎵 Audio Issues:</strong> Problems with audio processing</li>
</ul>
<p><strong>🚫 Content Restrictions:</strong></p>
<ul>
<li><strong>Artist names:</strong> "gorillaz", "beatles", "michael jackson", etc.</li>
<li><strong>Copyrighted material:</strong> Song titles, lyrics, or melodies</li>
<li><strong>Explicit content:</strong> Inappropriate or offensive material</li>
<li><strong>Trademarks:</strong> Brand names, company names</li>
</ul>
<p><strong>💡 For Charles's case:</strong> The error "Song Description contained artist name: gorillaz" means you mentioned the band "Gorillaz" in your prompt. Try using generic descriptions like "electronic band" or "alternative rock group" instead.</p>
<p><strong>💡 Tips for success:</strong></p>
<ul>
<li>Keep prompts clear and specific</li>
<li>Avoid mentioning existing artists or bands</li>
<li>Use generic descriptions instead of specific names</li>
<li>Try different music styles or genres</li>
<li>Use the retry button to try again with a modified prompt</li>
</ul>
<div class="help-actions">
<button onclick="closeFailureHelp()" class="btn btn-primary">Got it!</button>
<button onclick="retryTrack(${trackId})" class="btn btn-secondary">Retry Now</button>
<button onclick="deleteFailedTrack(${trackId})" class="btn btn-danger">Delete Track</button>
</div>
</div>
`;
// Create modal
const modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.innerHTML = helpContent;
document.body.appendChild(modal);
// Add modal styles
const style = document.createElement('style');
style.textContent = `
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
backdrop-filter: blur(10px);
}
.failure-help-modal {
background: #1a1a1a;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 2rem;
max-width: 500px;
color: white;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5);
}
.failure-help-modal h3 {
color: #ffc107;
margin-bottom: 1rem;
}
.failure-help-modal ul {
margin: 1rem 0;
padding-left: 1.5rem;
}
.failure-help-modal li {
margin: 0.5rem 0;
}
.help-actions {
display: flex;
gap: 1rem;
margin-top: 1.5rem;
}
`;
document.head.appendChild(style);
}
// Close failure help modal
function closeFailureHelp() {
const modal = document.querySelector('.modal-overlay');
if (modal) {
modal.remove();
}
}
// Use global player for track playback
async function playTrack(audioUrl, title, artist) {
console.log('🎵 Library playTrack called:', { audioUrl, title, artist });
// Validate audio URL
if (!audioUrl || audioUrl === 'NULL' || audioUrl === 'null') {
console.error('🎵 INVALID AUDIO URL:', audioUrl);
return;
}
// Use the global player function directly
if (typeof window.playTrackWithGlobalPlayer === 'function') {
console.log('🎵 Using playTrackWithGlobalPlayer function');
try {
window.playTrackWithGlobalPlayer(audioUrl, title, artist, <?= $_SESSION['user_id'] ?>);
return;
} catch (error) {
console.error('🎵 playTrackWithGlobalPlayer failed:', error);
}
}
// Fallback to direct global player
if (typeof window.globalPlayer !== 'undefined' && typeof window.globalPlayer.playTrack === 'function') {
console.log('🎵 Using direct global player fallback');
try {
window.globalPlayer.wasPlaying = true;
window.globalPlayer.playTrack(audioUrl, title, artist, <?= $_SESSION['user_id'] ?>);
return;
} catch (error) {
console.error('🎵 Direct global player failed:', error);
}
}
console.error('🎵 No global player available for playback');
}
// Track play when play button is clicked
function trackPlay(trackId) {
fetch('/api_social.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'play', track_id: trackId })
});
}
// Use global player function from global_player.php
// Local function removed to prevent conflicts
// Play track from button and record play
async function playTrackFromButton(button) {
const audioUrl = button.getAttribute('data-audio-url');
const title = button.getAttribute('data-title');
const artist = button.getAttribute('data-artist');
const trackId = button.closest('.track-card').getAttribute('data-track-id');
console.log('🎵 Play button clicked:', { audioUrl, title, artist, trackId });
// Validate audio URL
if (!audioUrl || audioUrl === 'NULL' || audioUrl === 'null') {
console.error('🎵 INVALID AUDIO URL:', audioUrl);
return;
}
// Record the play
trackPlay(trackId);
// Play the track using the global player
playTrackWithGlobalPlayer(audioUrl, title, artist, <?= $_SESSION['user_id'] ?>);
}
// Show variations modal
function showVariations(trackId) {
console.log('🎵 Showing variations for track:', trackId);
// Get track data from PHP
const trackCard = document.querySelector(`[data-track-id="${trackId}"]`);
if (!trackCard) {
console.error('🎵 Track card not found for track ID:', trackId);
alert('Track not found. Please refresh the page and try again.');
return;
}
console.log('🎵 Track card found:', trackCard);
// Get variations data from PHP
const variationsData = trackCard.getAttribute('data-variations');
console.log('🎵 Raw variations data:', variationsData);
if (!variationsData || variationsData === '[]') {
console.error('🎵 No variations data found');
alert('No variations available for this track.');
return;
}
try {
trackVariations = JSON.parse(variationsData);
console.log('🎵 Parsed variations:', trackVariations);
if (!Array.isArray(trackVariations) || trackVariations.length === 0) {
console.error('🎵 No variations in array');
alert('No variations available for this track.');
return;
}
currentTrackId = trackId;
// Get current selection
const currentSelection = trackCard.getAttribute('data-selected-variation') || '0';
selectedVariationIndex = parseInt(currentSelection);
console.log('🎵 About to populate variations grid');
// Populate variations grid
populateVariationsGrid();
console.log('🎵 About to show modal');
// Show modal
const modal = document.getElementById('variationsModal');
if (!modal) {
console.error('🎵 Variations modal not found');
alert('Modal not found. Please refresh the page and try again.');
return;
}
modal.classList.add('active');
console.log('🎵 Modal should now be visible');
} catch (error) {
console.error('🎵 Error parsing variations data:', error);
alert('Error loading variations. Please refresh the page and try again.');
}
}
// Populate variations grid
function populateVariationsGrid() {
const grid = document.getElementById('variationsGrid');
grid.innerHTML = '';
trackVariations.forEach((variation, index) => {
const card = document.createElement('div');
card.className = `variation-card ${index === selectedVariationIndex ? 'selected' : ''}`;
card.onclick = () => selectVariation(index);
const duration = Math.floor(variation.duration / 60) + 'm ' + Math.floor(variation.duration % 60) + 's';
const tags = variation.tags ? variation.tags.split(',').slice(0, 3) : [];
card.innerHTML = `
<div class="variation-header">
<div class="variation-title">${variation.title || 'Variation ' + (index + 1)}</div>
<div class="variation-index">${index + 1}</div>
</div>
<div class="variation-duration">
<i class="fas fa-clock"></i> ${duration}
</div>
${tags.length > 0 ? `
<div class="variation-tags">
${tags.map(tag => `<span class="variation-tag">${tag.trim()}</span>`).join('')}
</div>
` : ''}
<div class="variation-actions">
<button class="variation-btn play" onclick="playVariation(${index})">
<i class="fas fa-play"></i> Play
</button>
<button class="variation-btn download" onclick="downloadVariation(${index})">
<i class="fas fa-download"></i> Download
</button>
<button class="variation-btn select ${index === selectedVariationIndex ? 'selected' : ''}" onclick="selectVariation(${index})">
<i class="fas fa-check"></i> ${index === selectedVariationIndex ? 'Selected' : 'Select'}
</button>
</div>
`;
grid.appendChild(card);
});
// Update save button state
updateSaveButton();
}
// Select variation
function selectVariation(index) {
selectedVariationIndex = index;
// Update visual selection
document.querySelectorAll('.variation-card').forEach((card, i) => {
card.classList.toggle('selected', i === index);
});
document.querySelectorAll('.variation-btn.select').forEach((btn, i) => {
btn.classList.toggle('selected', i === index);
btn.innerHTML = `<i class="fas fa-check"></i> ${i === index ? 'Selected' : 'Select'}`;
});
updateSaveButton();
}
// Play variation
async function playVariation(index) {
const variation = trackVariations[index];
if (!variation) return;
console.log('🎵 Playing variation:', variation);
// Use the same playTrack function as the main library
playTrack(variation.audio_url, variation.title || 'Variation ' + (index + 1), '<?= htmlspecialchars($user_name) ?>');
}
// Download variation
function downloadVariation(index) {
const variation = trackVariations[index];
if (!variation) return;
console.log('🎵 Downloading variation:', variation);
// Create a temporary link element to trigger download
const link = document.createElement('a');
link.href = variation.audio_url;
// Create a meaningful filename
const trackTitle = variation.title || 'Variation ' + (index + 1);
const artistName = '<?= htmlspecialchars($user_name) ?>';
const filename = `${artistName} - ${trackTitle} (Variation ${index + 1}).mp3`;
link.download = filename;
link.target = '_blank';
// Add to DOM, click, and remove
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Show success message
const toast = document.createElement('div');
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(72, 187, 120, 0.9);
color: white;
padding: 1rem 2rem;
border-radius: 8px;
z-index: 3000;
font-size: 1.4rem;
font-weight: 600;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
`;
toast.textContent = `✅ Downloading: ${filename}`;
document.body.appendChild(toast);
// Remove toast after 3 seconds
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 3000);
}
// Lyrics functionality
let currentLyrics = '';
let currentLyricsTitle = '';
// Show lyrics modal
function showLyrics(trackId, title, lyrics, originalPrompt) {
console.log('🎵 Showing lyrics for track:', trackId, title);
console.log('🎵 Lyrics length:', lyrics ? lyrics.length : 0);
console.log('🎵 Original prompt length:', originalPrompt ? originalPrompt.length : 0);
console.log('🎵 Lyrics preview:', lyrics ? lyrics.substring(0, 100) + '...' : 'No lyrics');
currentLyrics = lyrics;
currentLyricsTitle = title;
// Create content with both original prompt and generated lyrics
let content = '';
if (originalPrompt && originalPrompt !== lyrics) {
content += '<div class="lyrics-section">';
content += '<h4><i class="fas fa-edit"></i> Original Prompt</h4>';
content += '<div class="lyrics-content">';
content += '<pre>' + originalPrompt + '</pre>';
content += '</div>';
content += '</div>';
}
content += '<div class="lyrics-section">';
content += '<h4><i class="fas fa-music"></i> Generated Lyrics</h4>';
content += '<div class="lyrics-content">';
content += '<pre>' + lyrics + '</pre>';
content += '</div>';
content += '</div>';
// Update modal content
document.getElementById('lyricsTrackTitle').textContent = title;
document.getElementById('lyricsText').innerHTML = content;
// Show modal
const modal = document.getElementById('lyricsModal');
console.log('🎵 Modal element found:', !!modal);
modal.classList.add('active');
console.log('🎵 Modal active class added');
// Prevent body scroll
document.body.style.overflow = 'hidden';
console.log('🎵 Body scroll disabled');
}
// Close lyrics modal
function closeLyrics() {
console.log('🎵 Closing lyrics modal');
const modal = document.getElementById('lyricsModal');
modal.classList.remove('active');
// Restore body scroll
document.body.style.overflow = '';
// Clear current lyrics
currentLyrics = '';
currentLyricsTitle = '';
}
// Toggle inline lyrics display
function toggleLyrics(trackId) {
const lyricsSection = document.getElementById(`lyrics-section-${trackId}`);
const toggleButton = lyricsSection.nextElementSibling;
if (lyricsSection.style.display === 'none') {
lyricsSection.style.display = 'block';
toggleButton.innerHTML = '<i class="fas fa-music"></i> Hide Lyrics';
} else {
lyricsSection.style.display = 'none';
toggleButton.innerHTML = '<i class="fas fa-music"></i> Show Full Lyrics';
}
}
// Copy lyrics to clipboard
function copyLyrics() {
if (!currentLyrics) {
console.error('🎵 No lyrics to copy');
return;
}
// Create formatted lyrics text
const formattedLyrics = `${currentLyricsTitle}\nby <?= htmlspecialchars($user_name) ?>\n\n${currentLyrics}`;
// Copy to clipboard
navigator.clipboard.writeText(formattedLyrics).then(() => {
console.log('🎵 Lyrics copied to clipboard');
// Show success message
const toast = document.createElement('div');
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(102, 126, 234, 0.9);
color: white;
padding: 1rem 2rem;
border-radius: 8px;
z-index: 3000;
font-size: 1.4rem;
font-weight: 600;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
`;
toast.textContent = '✅ Lyrics copied to clipboard!';
document.body.appendChild(toast);
// Remove toast after 3 seconds
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 3000);
}).catch(err => {
console.error('🎵 Failed to copy lyrics:', err);
// Fallback: select text and show copy instruction
const lyricsText = document.getElementById('lyricsText');
const range = document.createRange();
range.selectNodeContents(lyricsText);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
// Show instruction
const toast = document.createElement('div');
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(255, 193, 7, 0.9);
color: white;
padding: 1rem 2rem;
border-radius: 8px;
z-index: 3000;
font-size: 1.4rem;
font-weight: 600;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
`;
toast.textContent = '📋 Text selected - press Ctrl+C to copy';
document.body.appendChild(toast);
// Remove toast after 5 seconds
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 5000);
});
}
// Download lyrics as text file
function downloadLyrics() {
if (!currentLyrics) {
console.error('🎵 No lyrics to download');
return;
}
// Create formatted lyrics text
const formattedLyrics = `${currentLyricsTitle}\nby <?= htmlspecialchars($user_name) ?>\n\n${currentLyrics}`;
// Create blob and download
const blob = new Blob([formattedLyrics], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `${currentLyricsTitle} - Lyrics.txt`;
// Add to DOM, click, and remove
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Clean up URL
URL.revokeObjectURL(url);
// Show success message
const toast = document.createElement('div');
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(72, 187, 120, 0.9);
color: white;
padding: 1rem 2rem;
border-radius: 8px;
z-index: 3000;
font-size: 1.4rem;
font-weight: 600;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
`;
toast.textContent = `✅ Downloading: ${currentLyricsTitle} - Lyrics.txt`;
document.body.appendChild(toast);
// Remove toast after 3 seconds
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 3000);
}
// Close modal when clicking outside
document.addEventListener('click', function(event) {
const lyricsModal = document.getElementById('lyricsModal');
const lyricsContent = document.querySelector('.lyrics-content');
if (lyricsModal && lyricsModal.classList.contains('active')) {
if (event.target === lyricsModal) {
closeLyrics();
}
}
});
// Close modal on escape key
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
const lyricsModal = document.getElementById('lyricsModal');
if (lyricsModal && lyricsModal.classList.contains('active')) {
closeLyrics();
}
}
});
// Update save button state
function updateSaveButton() {
const saveBtn = document.getElementById('saveVariationBtn');
if (!saveBtn) {
console.error('🎵 Save button not found');
return;
}
if (currentTrackId === null) {
saveBtn.disabled = true;
saveBtn.textContent = 'No Track Selected';
return;
}
const trackCard = document.querySelector(`[data-track-id="${currentTrackId}"]`);
if (!trackCard) {
saveBtn.disabled = true;
saveBtn.textContent = 'Track Not Found';
return;
}
const currentSelection = trackCard.getAttribute('data-selected-variation') || '0';
if (selectedVariationIndex !== parseInt(currentSelection)) {
saveBtn.disabled = false;
saveBtn.textContent = 'Save Selection';
} else {
saveBtn.disabled = true;
saveBtn.textContent = 'No Changes';
}
}
// Save variation selection
function saveVariationSelection() {
if (selectedVariationIndex === null || currentTrackId === null) {
console.error('🎵 No variation or track selected');
return;
}
console.log('🎵 Saving variation selection:', { trackId: currentTrackId, variationIndex: selectedVariationIndex });
// Disable save button during request
const saveBtn = document.getElementById('saveVariationBtn');
saveBtn.disabled = true;
saveBtn.textContent = 'Saving...';
fetch('/api_select_variation.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
track_id: currentTrackId,
variation_index: selectedVariationIndex
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
console.log('🎵 Variation selection saved:', data);
// Update the track card
const trackCard = document.querySelector(`[data-track-id="${currentTrackId}"]`);
if (trackCard) {
trackCard.setAttribute('data-selected-variation', selectedVariationIndex);
// Update the main audio URL and duration
const variation = trackVariations[selectedVariationIndex];
if (variation) {
// Update play button
const playBtn = trackCard.querySelector('.play-track-btn');
if (playBtn) {
playBtn.setAttribute('data-audio-url', variation.audio_url);
}
// Update duration display
const durationSpan = trackCard.querySelector('.track-details span:first-child');
if (durationSpan) {
const duration = Math.floor(variation.duration / 60) + 'm ' + Math.floor(variation.duration % 60) + 's';
durationSpan.innerHTML = `<i class="fas fa-clock"></i> ${duration}`;
}
}
}
// Show success message
alert('✅ Variation selected successfully! This will be your main track for sale and public display.');
// Close modal
closeVariations();
} else {
console.error('🎵 Failed to save variation selection:', data.error);
let errorMessage = data.error || 'Unknown error';
if (errorMessage === 'Internal server error') {
errorMessage = 'Server error. Please try again in a moment.';
}
alert('❌ Failed to save selection: ' + errorMessage);
}
})
.catch(error => {
console.error('🎵 Error saving variation selection:', error);
alert('❌ Network error saving selection. Please check your connection and try again.');
})
.finally(() => {
// Re-enable save button
saveBtn.disabled = false;
updateSaveButton();
});
}
// Close variations modal
function closeVariations() {
const modal = document.getElementById('variationsModal');
modal.classList.remove('active');
// Reset state
currentTrackId = null;
selectedVariationIndex = null;
trackVariations = [];
}
// Close modal when clicking outside
document.getElementById('variationsModal').addEventListener('click', function(e) {
if (e.target === this) {
closeVariations();
}
});
// Filter and sort tracks
function filterTracks() {
const statusFilter = document.getElementById('status-filter').value;
const sortFilter = document.getElementById('sort-filter').value;
console.log('🎵 Filtering tracks:', { statusFilter, sortFilter });
// Build URL with filter parameters
const url = new URL(window.location);
url.searchParams.set('status', statusFilter);
url.searchParams.set('sort', sortFilter);
// Reload page with new filters
window.location.href = url.toString();
}
// Setup play button event listeners with AJAX interference prevention
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.play-track-btn').forEach(button => {
button.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
const audioUrl = this.getAttribute('data-audio-url');
const title = this.getAttribute('data-title');
const artist = this.getAttribute('data-artist');
const trackId = this.getAttribute('data-track-id');
console.log('🎵 Play button clicked:', { audioUrl, title, artist, trackId });
// Validate audio URL
if (!audioUrl || audioUrl === 'NULL' || audioUrl === 'null') {
alert('This track is not available for playback.');
return;
}
// Record the play
if (trackId) {
trackPlay(trackId);
}
// Play the track using the global player
playTrack(audioUrl, title, artist);
});
});
});
</script>
<?php
// Include footer (AJAX navigation removed - always include footer)
include 'includes/footer.php';
?>