![]() 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/public_html/ |
<?php
// New Enhanced Global Music Player - Based on test_enhanced_player.php
// Clean, modern SoundCloud-style bottom player
// Prevent multiple includes
if (defined('GLOBAL_PLAYER_INCLUDED')) {
return;
}
define('GLOBAL_PLAYER_INCLUDED', true);
?>
<!-- Enhanced Global Music Player -->
<div id="enhancedGlobalPlayer" class="global-bottom-player">
<!-- Track Info Section -->
<div class="player-track-info">
<div class="track-details">
<div class="track-title clickable" id="playerTrackTitle" title="Click to view track details">Loading playlists...</div>
<div class="track-artist clickable" id="playerTrackArtist" title="Click to view artist profile">Discovering great music for you</div>
</div>
</div>
<!-- Playlist Selector -->
<div class="playlist-selector" id="playlistSelector">
<select id="playlistDropdown" class="playlist-dropdown" onchange="switchPlaylist(this.value)">
<option value="vip">🎵 VIP Samples</option>
<option value="featured">⭐ Featured</option>
<option value="community">🌟 Community</option>
</select>
</div>
<!-- Main Player Controls -->
<div class="player-main-controls">
<button class="player-btn previous-btn" id="playerPrevBtn" title="Previous">
<i class="fas fa-step-backward"></i>
</button>
<button class="player-btn skip-back-btn" id="playerSkipBackBtn" title="Skip Back 10s">
<i class="fas fa-backward"></i>
<span class="skip-label">10s</span>
</button>
<button class="player-btn play-pause-btn" id="playerPlayBtn" title="Play">
<i class="fas fa-play" id="playerPlayIcon"></i>
</button>
<button class="player-btn skip-forward-btn" id="playerSkipForwardBtn" title="Skip Forward 10s">
<i class="fas fa-forward"></i>
<span class="skip-label">10s</span>
</button>
<button class="player-btn next-btn" id="playerNextBtn" title="Next">
<i class="fas fa-step-forward"></i>
</button>
</div>
<!-- Progress Section with Interactive Waveform -->
<div class="player-progress-section">
<span class="time-current" id="playerTimeDisplay">0:00</span>
<div class="progress-container" id="playerProgressContainer">
<!-- Traditional Progress Bar (fallback) -->
<div class="progress-bar-background"></div>
<div class="progress-bar" id="playerProgressBar"></div>
<!-- Interactive Waveform representing the actual song -->
<div class="waveform-progress-container" id="waveformProgressContainer">
<div class="waveform-progress-bars" id="waveformProgressBars">
<!-- Waveform bars representing actual song amplitude -->
</div>
<div class="waveform-progress-line" id="waveformProgressLine"></div>
</div>
<div class="progress-handle" id="playerProgressHandle"></div>
</div>
<span class="time-duration" id="playerDurationDisplay">0:00</span>
</div>
<!-- Advanced Controls -->
<div class="player-advanced-controls">
<button class="player-btn volume-btn" id="playerVolumeBtn" title="Volume">
<i class="fas fa-volume-up" id="playerVolumeIcon"></i>
</button>
<div class="volume-slider-container" id="volumeSliderContainer">
<input type="range" class="volume-slider" id="playerVolumeSlider" min="0" max="100" value="100">
</div>
<button class="player-btn speed-btn" id="playerSpeedBtn" title="Speed: 1x">
<span class="speed-text">1x</span>
</button>
<button class="player-btn auto-play-btn" id="playerAutoPlayBtn" title="Auto-play: On">
<i class="fas fa-infinity" id="playerAutoPlayIcon"></i>
</button>
<button class="player-btn minimize-btn" id="playerMinimizeBtn" title="Minimize">
<i class="fas fa-chevron-down"></i>
</button>
</div>
<!-- Waveform Visualizer -->
<div class="waveform-container" id="playerWaveformContainer">
<div class="waveform-bars" id="playerWaveformBars">
<!-- Animated waveform bars will be generated dynamically -->
</div>
</div>
<!-- Hidden Audio Element -->
<audio id="globalAudioElement" preload="metadata" playsinline webkit-playsinline></audio>
</div>
<style>
/* Enhanced CSS Variables from test_enhanced_player.php */
:root {
--primary: #667eea;
--primary-dark: #5a67d8;
--primary-light: #7c3aed;
--secondary: #764ba2;
--secondary-dark: #6b46c1;
--secondary-light: #8b5cf6;
--accent: #4facfe;
--accent-dark: #3b82f6;
--accent-light: #60a5fa;
--gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--gradient-secondary: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
--gradient-accent: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
--gradient-divine: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--bg-primary: #0a0a0a;
--bg-secondary: #1a1a1a;
--bg-tertiary: #2d2d2d;
--bg-quaternary: #3a3a3a;
--bg-card: rgba(26, 26, 26, 0.9);
--bg-overlay: rgba(0, 0, 0, 0.8);
--bg-glass: rgba(255, 255, 255, 0.05);
--text-primary: #ffffff;
--text-secondary: #a0aec0;
--text-muted: #718096;
--text-light: #4a5568;
--text-accent: #4facfe;
--border-light: rgba(255, 255, 255, 0.1);
--border-medium: rgba(255, 255, 255, 0.2);
--border-accent: rgba(102, 126, 234, 0.3);
--shadow-light: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
--shadow-medium: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
--shadow-heavy: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
--shadow-glow: 0 0 20px rgba(102, 126, 234, 0.3);
}
/* Enhanced SoundCloud-Style Global Player */
.global-bottom-player {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(135deg, rgba(26, 26, 26, 0.98) 0%, rgba(45, 45, 45, 0.98) 100%);
backdrop-filter: blur(20px);
border-top: 2px solid var(--border-accent);
padding: 8px 16px;
display: none;
align-items: center;
gap: 16px;
z-index: 10000;
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.6), 0 -2px 8px rgba(102, 126, 234, 0.2);
height: 60px;
min-height: 60px;
max-height: 60px;
transition: all 0.4s ease;
overflow: hidden;
}
/* Mobile Responsive Design */
@media (max-width: 768px) {
.global-bottom-player {
height: auto;
min-height: 80px;
max-height: 120px;
padding: 12px 16px;
flex-wrap: wrap;
gap: 12px;
}
.global-bottom-player.playing {
height: auto;
min-height: 80px;
max-height: 120px;
}
.global-bottom-player.minimized {
height: 50px;
min-height: 50px;
max-height: 50px;
padding: 8px 12px;
}
/* Mobile Track Info */
.player-track-info {
min-width: 0;
flex: 1;
order: 1;
width: 100%;
margin-bottom: 8px;
}
.track-details {
min-width: 0;
width: 100%;
}
.track-title {
font-size: 13px;
line-height: 1.1;
}
.track-artist {
font-size: 11px;
line-height: 1.1;
}
/* Mobile Controls Layout */
.player-main-controls {
order: 2;
gap: 8px;
justify-content: center;
width: 100%;
margin-bottom: 8px;
flex-wrap: wrap;
}
.player-btn {
width: 36px;
height: 36px;
min-width: 36px;
flex-shrink: 0;
}
.play-pause-btn {
width: 48px;
height: 48px;
min-width: 48px;
}
/* Skip buttons - visible on mobile */
.skip-back-btn,
.skip-forward-btn {
display: flex !important;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4px;
font-size: 10px;
}
.skip-label {
font-size: 8px;
line-height: 1;
margin-top: 2px;
font-weight: 600;
}
/* Mobile Progress Section */
.player-progress-section {
order: 3;
max-width: 100%;
margin: 0;
gap: 10px;
width: 100%;
display: flex !important;
visibility: visible !important;
opacity: 1 !important;
flex-shrink: 0;
}
.time-current,
.time-duration {
font-size: 11px;
min-width: 25px;
flex-shrink: 0;
}
/* Make progress container taller and more touch-friendly on mobile */
.progress-container {
height: 50px !important;
min-height: 50px !important;
padding: 12px 0;
touch-action: pan-y;
-webkit-tap-highlight-color: rgba(102, 126, 234, 0.3);
position: relative;
z-index: 10;
}
.progress-bar-background {
height: 8px !important;
top: auto;
bottom: 12px;
border-radius: 4px;
}
.progress-bar {
height: 8px !important;
bottom: 12px;
border-radius: 4px;
}
.waveform-progress-container {
bottom: 14px !important;
top: 8px !important;
}
.progress-handle {
bottom: 5px !important;
width: 18px !important;
height: 18px !important;
opacity: 1 !important;
transform: translateX(-50%);
}
.waveform-progress-line {
bottom: 14px !important;
top: 8px !important;
}
.waveform-progress-bar {
min-height: 12px !important;
cursor: pointer;
touch-action: pan-y;
-webkit-tap-highlight-color: rgba(102, 126, 234, 0.5);
position: relative;
z-index: 5;
user-select: none;
-webkit-user-select: none;
}
/* Mobile Advanced Controls */
.player-advanced-controls {
order: 4;
display: flex;
align-items: center;
gap: 8px;
justify-content: center;
width: 100%;
flex-wrap: wrap;
}
.volume-slider-container {
width: 60px;
}
.volume-slider {
width: 100%;
}
/* Mobile Playlist Selector */
.playlist-selector {
order: 5;
margin: 0;
width: 100%;
text-align: center;
}
.playlist-dropdown {
font-size: 11px;
padding: 4px 8px;
background: var(--bg-glass);
border: 1px solid var(--border-light);
border-radius: 4px;
}
/* Mobile Waveform */
.waveform-container {
order: 6;
width: 100%;
height: 20px;
margin-top: 8px;
}
/* Hide some elements on very small screens */
@media (max-width: 480px) {
.player-advanced-controls {
gap: 6px;
}
.player-btn {
width: 28px;
height: 28px;
}
.play-pause-btn {
width: 36px;
height: 36px;
}
.track-title {
font-size: 12px;
}
.track-artist {
font-size: 10px;
}
}
/* Landscape orientation adjustments */
@media (max-width: 768px) and (orientation: landscape) {
.global-bottom-player {
height: auto;
min-height: 60px;
max-height: 80px;
flex-wrap: nowrap;
}
.player-track-info {
order: 1;
width: auto;
margin-bottom: 0;
}
.player-main-controls {
order: 2;
width: auto;
margin-bottom: 0;
}
.player-progress-section {
order: 3;
width: auto;
flex: 1;
}
.player-advanced-controls {
order: 4;
width: auto;
}
.playlist-selector {
order: 5;
width: auto;
}
}
}
.global-bottom-player::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 100%;
background: linear-gradient(90deg,
transparent 0%,
rgba(102, 126, 234, 0.05) 25%,
rgba(118, 75, 162, 0.05) 75%,
transparent 100%);
pointer-events: none;
animation: ambientGlow 8s ease-in-out infinite;
}
@keyframes ambientGlow {
0%, 100% { opacity: 0.3; transform: translateX(-100%); }
50% { opacity: 0.7; transform: translateX(100%); }
}
.global-bottom-player.playing {
display: flex;
animation: slideUp 0.5s cubic-bezier(0.4, 0, 0.2, 1);
background: linear-gradient(135deg, rgba(26, 26, 26, 0.98) 0%, rgba(45, 45, 45, 0.98) 50%, rgba(102, 126, 234, 0.15) 100%);
box-shadow: 0 -8px 40px rgba(102, 126, 234, 0.4), 0 -4px 20px rgba(118, 75, 162, 0.3);
border-top-color: rgba(102, 126, 234, 0.6);
}
.global-bottom-player.minimized {
height: 40px;
min-height: 40px;
max-height: 40px;
padding: 6px 12px;
}
@keyframes slideUp {
from {
transform: translateY(100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
/* Enhanced Track Info */
.player-track-info {
display: flex !important;
align-items: center;
gap: 8px;
flex-shrink: 0;
position: relative;
min-width: 250px;
visibility: visible !important;
opacity: 1 !important;
background: rgba(45, 45, 55, 0.6);
padding: 6px 12px;
border-radius: 8px;
backdrop-filter: blur(10px);
}
.track-details {
min-width: 200px;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.track-title {
color: rgba(255, 255, 255, 0.95);
font-weight: 700;
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 2px;
text-shadow: 0 1px 3px rgba(102, 126, 234, 0.3);
line-height: 1.2;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
.track-artist {
color: rgba(200, 180, 255, 0.9);
font-size: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
line-height: 1.2;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
/* Clickable track title and artist styles */
.track-title.clickable,
.track-artist.clickable {
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.track-title.clickable:hover,
.track-artist.clickable:hover {
color: var(--primary);
text-shadow: 0 0 8px rgba(102, 126, 234, 0.5);
transform: translateY(-1px);
}
.track-title.clickable::after {
content: "🔗";
position: absolute;
right: -20px;
top: 50%;
transform: translateY(-50%);
font-size: 12px;
opacity: 0;
transition: opacity 0.3s ease;
}
.track-title.clickable:hover::after {
opacity: 1;
}
.track-artist.clickable::after {
content: "👤";
position: absolute;
right: -20px;
top: 50%;
transform: translateY(-50%);
font-size: 12px;
opacity: 0;
transition: opacity 0.3s ease;
}
.track-artist.clickable:hover::after {
opacity: 1;
}
.track-title-link {
color: inherit;
text-decoration: none;
transition: all 0.3s ease;
cursor: pointer;
display: block;
width: 100%;
}
.track-title-link:hover {
color: var(--primary);
text-decoration: underline;
text-shadow: 0 0 8px rgba(102, 126, 234, 0.5);
}
.track-artist-link {
color: inherit;
text-decoration: none;
transition: all 0.3s ease;
cursor: pointer;
display: block;
width: 100%;
}
.track-artist-link:hover {
color: var(--primary);
text-decoration: underline;
text-shadow: 0 0 8px rgba(102, 126, 234, 0.5);
}
/* Playlist Selector */
.playlist-selector {
margin-left: 8px;
}
.playlist-dropdown {
background: transparent;
border: none;
color: var(--text-secondary);
padding: 0;
font-size: 10px;
cursor: pointer;
outline: none;
transition: all 0.2s ease;
}
.playlist-dropdown:hover {
color: var(--text-primary);
}
.playlist-dropdown:focus {
color: var(--text-primary);
}
.playlist-dropdown option {
background: var(--bg-secondary);
color: var(--text-primary);
}
/* Main Controls */
.player-main-controls {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.player-btn {
background: var(--bg-glass);
border: 1px solid var(--border-light);
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-primary);
cursor: pointer;
transition: all 0.2s ease;
backdrop-filter: blur(10px);
}
.player-btn:hover {
background: var(--border-medium);
border-color: var(--border-medium);
transform: scale(1.05);
box-shadow: var(--shadow-glow);
}
.play-pause-btn {
width: 30px;
height: 30px;
background: linear-gradient(135deg, var(--primary), var(--secondary));
border: none;
box-shadow: var(--shadow-light);
}
.play-pause-btn:hover {
transform: scale(1.1);
box-shadow: var(--shadow-glow);
}
/* Skip buttons - visible on all devices */
.skip-back-btn,
.skip-forward-btn {
display: flex !important;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2px;
font-size: 8px;
}
.skip-label {
font-size: 7px;
line-height: 1;
margin-top: 1px;
font-weight: 600;
}
.auto-play-btn {
position: relative;
}
.auto-play-btn.active {
background: var(--gradient-divine);
border-color: var(--primary);
box-shadow: var(--shadow-glow);
}
.auto-play-btn.active:hover {
transform: scale(1.05);
box-shadow: 0 0 15px rgba(102, 126, 234, 0.6);
}
/* Progress Section */
.player-progress-section {
flex: 1;
display: flex;
align-items: center;
gap: 15px;
max-width: 600px;
margin: 0 20px;
}
.time-current,
.time-duration {
font-size: 12px;
color: var(--text-secondary);
font-weight: 500;
min-width: 30px;
text-align: center;
}
.progress-container {
flex: 1;
height: 20px;
background: transparent;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.progress-container:hover {
transform: scaleY(1.05);
}
.progress-bar-background {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 4px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
}
.progress-bar {
position: absolute;
bottom: 0;
left: 0;
height: 4px;
background: var(--gradient-divine);
border-radius: 2px;
width: 0%;
transition: width 0.1s ease;
box-shadow: 0 0 10px rgba(102, 126, 234, 0.5);
z-index: 3;
}
/* Interactive Waveform Progress */
.waveform-progress-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 6px;
display: flex;
align-items: end;
overflow: hidden;
}
.waveform-progress-bars {
display: flex;
align-items: end;
height: 100%;
width: 100%;
gap: 1px;
}
.waveform-progress-bar {
background: rgba(255, 255, 255, 0.3);
flex: 1;
min-height: 2px;
max-height: 32px;
border-radius: 1px;
transition: all 0.2s ease;
cursor: pointer;
}
.waveform-progress-bar:hover {
background: rgba(255, 255, 255, 0.6);
transform: scaleY(1.2);
}
.waveform-progress-bar.played {
background: var(--gradient-divine);
box-shadow: 0 0 3px rgba(102, 126, 234, 0.5);
}
.waveform-progress-bar.playing {
background: var(--accent);
box-shadow: 0 0 8px rgba(79, 172, 254, 0.8);
animation: currentBarPulse 0.5s ease-in-out infinite alternate;
}
@keyframes currentBarPulse {
0% { transform: scaleY(1); opacity: 0.8; }
100% { transform: scaleY(1.3); opacity: 1; }
}
.waveform-progress-line {
position: absolute;
top: 0;
bottom: 6px;
width: 2px;
background: var(--text-primary);
box-shadow: 0 0 8px rgba(255, 255, 255, 0.8);
border-radius: 1px;
left: 0%;
transition: left 0.1s ease;
z-index: 4;
}
.progress-handle {
position: absolute;
bottom: -4px;
width: 14px;
height: 14px;
background: var(--text-primary);
border-radius: 50%;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4), 0 0 0 2px var(--primary);
left: 0%;
transform: translateX(-50%);
opacity: 0;
transition: all 0.3s ease;
z-index: 5;
}
.progress-container:hover .progress-handle {
opacity: 1;
transform: translateX(-50%) scale(1.2);
}
/* Advanced Controls */
.player-advanced-controls {
display: flex;
align-items: center;
gap: 10px;
position: relative;
z-index: 10000;
flex-shrink: 0;
}
/* Mobile-friendly touch targets */
@media (max-width: 768px) {
.player-btn {
min-width: 44px;
min-height: 44px;
touch-action: manipulation;
}
.play-pause-btn {
min-width: 48px;
min-height: 48px;
}
.volume-slider {
min-height: 44px;
touch-action: manipulation;
}
.playlist-dropdown {
min-height: 44px;
touch-action: manipulation;
}
.volume-slider-container {
position: fixed;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
min-width: 60px;
min-height: 120px;
z-index: 10002;
}
}
.volume-btn,
.speed-btn,
.minimize-btn {
width: 24px;
height: 24px;
position: relative;
}
.volume-btn {
background: var(--bg-glass);
border: 1px solid var(--border-light);
border-radius: 50%;
transition: all 0.2s ease;
}
.volume-btn:hover {
background: var(--border-medium);
border-color: var(--border-medium);
transform: scale(1.05);
box-shadow: var(--shadow-glow);
}
.speed-btn {
border-radius: 6px;
width: 30px;
}
.speed-text {
font-size: 10px;
font-weight: 600;
}
.volume-slider-container {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%) translateY(10px);
background: var(--bg-tertiary);
border: 1px solid var(--border-light);
border-radius: 8px;
padding: 10px 8px;
backdrop-filter: blur(20px);
box-shadow: var(--shadow-heavy);
opacity: 0;
visibility: hidden;
transition: all 0.2s ease;
z-index: 10001;
min-width: 40px;
min-height: 100px;
}
.volume-slider-container.active {
opacity: 1;
visibility: visible;
transform: translateX(-50%) translateY(-10px);
z-index: 10001;
}
.volume-slider {
writing-mode: bt-lr; /* IE */
-webkit-appearance: slider-vertical; /* WebKit */
width: 16px;
height: 60px;
background: var(--bg-glass);
outline: none;
cursor: pointer;
border-radius: 8px;
-webkit-appearance: none;
appearance: none;
}
.volume-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
background: var(--primary);
border-radius: 50%;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.volume-slider::-moz-range-thumb {
width: 16px;
height: 16px;
background: var(--primary);
border-radius: 50%;
cursor: pointer;
border: none;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
/* Responsive Design */
@media (max-width: 768px) {
.global-bottom-player {
padding: 0 15px;
gap: 15px;
height: 60px;
min-height: 60px;
max-height: 60px;
}
.player-track-info {
min-width: 150px;
max-width: 200px;
}
.player-progress-section {
max-width: 300px;
}
.track-title {
font-size: 13px;
}
.track-artist {
font-size: 11px;
}
}
@media (max-width: 480px) {
.global-bottom-player {
height: 50px;
min-height: 50px;
max-height: 50px;
padding: 0 5px;
gap: 5px;
}
.player-btn {
width: 20px;
height: 20px;
}
.play-pause-btn {
width: 25px;
height: 25px;
}
}
/* Waveform Visualizer */
.waveform-container {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 4px;
overflow: hidden;
opacity: 0.6;
pointer-events: none;
}
.waveform-bars {
display: flex;
align-items: end;
height: 100%;
gap: 1px;
justify-content: space-between;
}
.waveform-bar {
background: var(--gradient-divine);
flex: 1;
min-height: 1px;
max-width: 3px;
border-radius: 1px;
transition: all 0.3s ease;
animation: waveAnimation 1.5s ease-in-out infinite alternate;
}
@keyframes waveAnimation {
0% {
height: 20%;
opacity: 0.6;
filter: hue-rotate(0deg);
}
100% {
height: 100%;
opacity: 1;
filter: hue-rotate(10deg);
}
}
.waveform-bar:nth-child(even) {
animation-delay: 0.1s;
}
.waveform-bar:nth-child(3n) {
animation-delay: 0.2s;
}
.waveform-bar:nth-child(4n) {
animation-delay: 0.3s;
}
.waveform-bar:nth-child(5n) {
animation-delay: 0.4s;
}
/* Enhanced Playing State */
.global-bottom-player.playing .waveform-bar {
animation: wavePlayback 0.8s ease-in-out infinite alternate;
background: linear-gradient(45deg, var(--gradient-divine), var(--accent));
box-shadow: 0 0 5px rgba(102, 126, 234, 0.5);
}
@keyframes wavePlayback {
0% {
transform: scaleY(0.4);
opacity: 0.7;
filter: hue-rotate(0deg) brightness(1);
}
100% {
transform: scaleY(1.3);
opacity: 1;
filter: hue-rotate(20deg) brightness(1.2);
}
}
.global-bottom-player.playing .play-pause-btn {
animation: playButtonGlow 3s ease-in-out infinite alternate;
}
@keyframes playButtonGlow {
0% {
box-shadow: var(--shadow-glow), var(--shadow-medium);
filter: brightness(1);
}
100% {
box-shadow: 0 0 30px rgba(102, 126, 234, 0.8), 0 8px 30px rgba(118, 75, 162, 0.6);
filter: brightness(1.2);
}
}
</style>
<script>
// Enhanced Global Music Player JavaScript
(function() {
'use strict';
// Helper function to decode HTML entities (e.g., ' → ')
function decodeHtmlEntities(str) {
if (!str) return str;
const textarea = document.createElement('textarea');
textarea.innerHTML = str;
return textarea.value;
}
// Player state
let currentTrack = null;
let isPlaying = false;
let currentTime = 0;
let duration = 0;
let volume = 1.0;
let previousVolume = 1.0; // Store volume before muting
let playbackSpeed = 1.0; // Always start at normal speed (1.0x)
let isMinimized = false;
// Playlist management
let currentPlaylist = [];
let currentPlaylistType = 'vip'; // Default to VIP samples
let currentTrackIndex = 0;
// Auto-play state
let autoPlayEnabled = true; // Default to enabled
// Detect mobile device (must be defined early)
function isMobileDevice() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
(window.matchMedia && window.matchMedia('(max-width: 768px)').matches && 'ontouchstart' in window);
}
// Toggle auto-play
function toggleAutoPlay() {
autoPlayEnabled = !autoPlayEnabled;
const autoPlayBtn = document.getElementById('playerAutoPlayBtn');
const autoPlayIcon = document.getElementById('playerAutoPlayIcon');
if (autoPlayBtn && autoPlayIcon) {
if (autoPlayEnabled) {
autoPlayBtn.title = 'Auto-play: On';
autoPlayIcon.className = 'fas fa-infinity';
autoPlayBtn.classList.add('active');
} else {
autoPlayBtn.title = 'Auto-play: Off';
autoPlayIcon.className = 'fas fa-times';
autoPlayBtn.classList.remove('active');
}
}
console.log('🎵 Auto-play toggled:', autoPlayEnabled);
}
// DOM elements
let playerElement = null;
let audioElement = null;
let trackTitle = null;
let trackArtist = null;
let playIcon = null;
let progressBar = null;
let progressHandle = null;
let timeDisplay = null;
let durationDisplay = null;
let volumeSlider = null;
let volumeIcon = null;
let speedBtn = null;
let volumeSliderContainer = null;
// Generate interactive waveform representing song structure
// Optimized: Reduced DOM elements and improved performance
function generateWaveform() {
// Generate ambient background waveform (decorative)
const barsContainer = document.getElementById('playerWaveformBars');
if (!barsContainer) return;
// Reduced from 60 to 30 bars (50% reduction) to decrease DOM size
const numberOfBars = 30;
// Use DocumentFragment for better performance (faster than innerHTML)
const fragment = document.createDocumentFragment();
for (let i = 0; i < numberOfBars; i++) {
const height = Math.random() * 80 + 20;
const animationDelay = Math.random() * 2;
const bar = document.createElement('div');
bar.className = 'waveform-bar';
bar.style.height = `${height}%`;
bar.style.animationDelay = `${animationDelay}s`;
fragment.appendChild(bar);
}
barsContainer.innerHTML = ''; // Clear first
barsContainer.appendChild(fragment);
console.log('🌊 Ambient waveform visualizer generated (optimized: 30 bars)');
}
// Generate interactive waveform for current track
function generateInteractiveWaveform(duration = 180) {
const progressBarsContainer = document.getElementById('waveformProgressBars');
if (!progressBarsContainer) return;
// Clear existing bars
progressBarsContainer.innerHTML = '';
// Calculate number of bars based on song duration (1 bar per 3 seconds, min 50, max 200)
const barsPerSecond = 1/3;
const numberOfBars = Math.max(50, Math.min(200, Math.floor(duration * barsPerSecond)));
// Generate waveform data simulating audio amplitude
const waveformData = generateWaveformData(numberOfBars);
let barsHtml = '';
for (let i = 0; i < numberOfBars; i++) {
const height = waveformData[i];
const percentage = (i / numberOfBars) * 100;
barsHtml += `<div class="waveform-progress-bar"
data-index="${i}"
data-time="${(duration * i / numberOfBars).toFixed(2)}"
data-percentage="${percentage}"
style="height: ${height}%;">
</div>`;
}
progressBarsContainer.innerHTML = barsHtml;
// Add touch and click event listeners to waveform bars for mobile support
const waveformBars = progressBarsContainer.querySelectorAll('.waveform-progress-bar');
waveformBars.forEach(bar => {
const percentage = parseFloat(bar.getAttribute('data-percentage'));
// Click event (desktop)
bar.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
seekToWaveformPosition(percentage);
});
// Touch events (mobile)
bar.addEventListener('touchstart', function(e) {
e.preventDefault();
e.stopPropagation();
seekToWaveformPosition(percentage);
}, { passive: false });
bar.addEventListener('touchend', function(e) {
e.preventDefault();
e.stopPropagation();
}, { passive: false });
});
// Store waveform data for updates
window.currentWaveformBars = numberOfBars;
window.currentWaveformDuration = duration;
console.log('🌊 Interactive waveform generated:', numberOfBars, 'bars for', duration, 'seconds');
}
// Generate realistic waveform data
function generateWaveformData(numberOfBars) {
const data = [];
for (let i = 0; i < numberOfBars; i++) {
const position = i / numberOfBars;
// Create realistic audio amplitude pattern
let amplitude = 0;
// Base wave pattern
amplitude += Math.sin(position * Math.PI * 8) * 0.3;
// Add some randomness for realism
amplitude += (Math.random() - 0.5) * 0.4;
// Add crescendos and diminuendos
amplitude += Math.sin(position * Math.PI * 2) * 0.2;
// Normalize to 20-100% range
amplitude = Math.max(0.2, Math.min(1, (amplitude + 1) / 2));
const height = amplitude * 80 + 20; // 20% to 100%
data.push(height);
}
return data;
}
// Update waveform progress as song plays
function updateWaveformProgress(currentTime, duration) {
const progressBars = document.querySelectorAll('.waveform-progress-bar');
const progressLine = document.getElementById('waveformProgressLine');
const progressHandle = document.getElementById('playerProgressHandle');
if (!progressBars.length || !duration) return;
const progress = currentTime / duration;
const progressPercentage = progress * 100;
// Update progress line position
if (progressLine) {
progressLine.style.left = `${progressPercentage}%`;
}
// Update progress handle position
if (progressHandle) {
progressHandle.style.left = `${progressPercentage}%`;
}
// Update waveform bar states
progressBars.forEach((bar, index) => {
const barProgress = (index / progressBars.length);
bar.classList.remove('played', 'playing');
if (barProgress < progress) {
bar.classList.add('played');
} else if (Math.abs(barProgress - progress) < (1 / progressBars.length)) {
bar.classList.add('playing');
}
});
}
// Seek to waveform position when clicked
function seekToWaveformPosition(percentage) {
if (!audioElement || !audioElement.src) {
console.warn('🎯 Cannot seek waveform: no audio element or source');
return;
}
const trackDuration = audioElement.duration;
if (!trackDuration || !isFinite(trackDuration) || trackDuration <= 0) {
console.warn('🎯 Cannot seek waveform: invalid duration', trackDuration);
return;
}
const seekTime = (percentage / 100) * trackDuration;
if (!isNaN(seekTime) && isFinite(seekTime) && seekTime >= 0 && seekTime <= trackDuration) {
audioElement.currentTime = seekTime;
currentTime = seekTime; // Update our state variable
console.log('🎯 Waveform seeked to', formatTime(seekTime), '(', percentage.toFixed(1) + '%)');
} else {
console.warn('🎯 Invalid waveform seek time:', seekTime);
}
}
// Initialize player when DOM is ready
// Clear cached track state to prevent stale data issues
function clearCachedTrackState() {
console.log('🎵 Clearing cached track state');
// Store current track data before clearing
const savedTrack = currentTrack && currentTrack.trackId ? { ...currentTrack } : null;
// Reset currentTrack
currentTrack = null;
// Restore track data if it was valid
if (savedTrack) {
currentTrack = savedTrack;
console.log('🎵 Restored current track data after initialization:', currentTrack);
}
currentPlaylist = [];
currentTrackIndex = 0;
currentPlaylistType = 'vip';
isPlaying = false;
currentTime = 0;
duration = 0;
// Clear any stored track references
if (typeof localStorage !== 'undefined') {
localStorage.removeItem('globalPlayerCurrentTrack');
localStorage.removeItem('globalPlayerCurrentIndex');
localStorage.removeItem('globalPlayerPlaylistType');
}
}
function initializePlayer() {
console.log('🎵 Initializing Enhanced Global Player');
// Clear any cached track state to prevent stale data issues
clearCachedTrackState();
// Get DOM elements
playerElement = document.getElementById('enhancedGlobalPlayer');
audioElement = document.getElementById('globalAudioElement');
trackTitle = document.getElementById('playerTrackTitle');
trackArtist = document.getElementById('playerTrackArtist');
playIcon = document.getElementById('playerPlayIcon');
progressBar = document.getElementById('playerProgressBar');
progressHandle = document.getElementById('playerProgressHandle');
timeDisplay = document.getElementById('playerTimeDisplay');
durationDisplay = document.getElementById('playerDurationDisplay');
volumeSlider = document.getElementById('playerVolumeSlider');
volumeIcon = document.getElementById('playerVolumeIcon');
speedBtn = document.getElementById('playerSpeedBtn');
volumeSliderContainer = document.getElementById('volumeSliderContainer');
if (!playerElement || !audioElement) {
console.error('🎵 Global player elements not found');
console.error('🎵 playerElement:', !!playerElement);
console.error('🎵 audioElement:', !!audioElement);
// Don't return - continue with initialization even if elements are missing
// The player will be available but may not work until elements are found
}
setupEventListeners();
setupAudioEvents();
// Defer waveform generation to reduce initial page load blocking
// Use requestIdleCallback if available, otherwise defer with setTimeout
if ('requestIdleCallback' in window) {
requestIdleCallback(generateWaveform, { timeout: 2000 });
} else {
setTimeout(generateWaveform, 100);
}
// Set initial volume
audioElement.volume = volume;
volumeSlider.value = volume * 100;
// Mobile-specific audio setup
if (isMobileDevice()) {
// Enable inline playback on iOS
audioElement.setAttribute('playsinline', 'true');
audioElement.setAttribute('webkit-playsinline', 'true');
console.log('🎵 Mobile device detected - configured for inline playback');
}
// CRITICAL: Set initial playback rate to 1.0 (normal speed)
// This ensures tracks play at normal speed unless user changes it
if (audioElement) {
audioElement.playbackRate = 1.0;
playbackSpeed = 1.0;
}
if (speedBtn) {
speedBtn.querySelector('.speed-text').textContent = '1x';
speedBtn.title = 'Speed: 1x';
}
// CRITICAL: Aggressively monitor and enforce playbackRate = 1.0
if (audioElement) {
// Override the playbackRate property setter to intercept ALL changes
let _internalPlaybackRate = 1.0;
const originalDescriptor = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate');
try {
Object.defineProperty(audioElement, 'playbackRate', {
get: function() {
return playbackSpeed === 1.0 ? 1.0 : _internalPlaybackRate;
},
set: function(value) {
// If user hasn't changed speed, force to 1.0
if (playbackSpeed === 1.0 && value !== 1.0) {
console.warn('🚨 BLOCKED: Attempt to set playbackRate to', value, '- forcing to 1.0');
value = 1.0;
}
_internalPlaybackRate = value;
// Set the actual property
if (originalDescriptor && originalDescriptor.set) {
originalDescriptor.set.call(this, value);
} else {
// Fallback if descriptor doesn't have setter
Object.getPrototypeOf(this).playbackRate = value;
}
},
configurable: true,
enumerable: true
});
console.log('🎵 Successfully overrode playbackRate property setter');
} catch (e) {
console.warn('🎵 Could not override playbackRate property:', e);
}
// Start monitoring playbackRate to catch any unauthorized changes
let lastPlaybackRate = 1.0;
const monitorInterval = setInterval(() => {
if (audioElement) {
const currentRate = audioElement.playbackRate;
if (currentRate !== lastPlaybackRate) {
console.warn('🚨 ALERT: playbackRate changed from', lastPlaybackRate, 'to', currentRate);
console.trace('Stack trace:');
lastPlaybackRate = currentRate;
}
// If it's not 1.0 and user hasn't changed speed, force it back IMMEDIATELY
if (currentRate !== 1.0 && playbackSpeed === 1.0) {
console.warn('🚨 FORCING playbackRate back to 1.0 (was', currentRate, ')');
_internalPlaybackRate = 1.0;
if (originalDescriptor && originalDescriptor.set) {
originalDescriptor.set.call(audioElement, 1.0);
}
lastPlaybackRate = 1.0;
}
}
}, 25); // Check every 25ms for even faster detection
// Store interval ID so we can clear it if needed
window._playbackRateMonitor = monitorInterval;
}
// Initialize auto-play button state
const autoPlayBtn = document.getElementById('playerAutoPlayBtn');
if (autoPlayBtn) {
autoPlayBtn.classList.toggle('active', autoPlayEnabled);
}
// Expose diagnostic function to console
window.checkPlaybackRate = function() {
const audioEl = document.getElementById('globalAudioElement');
if (audioEl) {
console.log('📊 Current playbackRate:', audioEl.playbackRate);
console.log('📊 playbackSpeed variable:', playbackSpeed);
console.log('📊 Audio element:', audioEl);
console.log('📊 Is playing:', !audioEl.paused);
console.log('📊 Current time:', audioEl.currentTime);
console.log('📊 Duration:', audioEl.duration);
return {
playbackRate: audioEl.playbackRate,
playbackSpeed: playbackSpeed,
isPlaying: !audioEl.paused,
currentTime: audioEl.currentTime,
duration: audioEl.duration
};
} else {
console.error('❌ Audio element not found');
return null;
}
};
console.log('🎵 Enhanced Global Player initialized with playbackRate monitoring');
console.log('💡 Run checkPlaybackRate() in console to check current playback rate');
// If elements weren't found, retry after a short delay
if (!playerElement || !audioElement) {
console.log('🎵 Elements missing, retrying initialization in 500ms...');
setTimeout(() => {
initializePlayer();
}, 500);
return;
}
// Note: Playlist loading is handled by index.php to avoid duplicate calls
// Removed auto-load here to prevent duplicate API requests
}
// Setup event listeners
function setupEventListeners() {
// Play/Pause button - use both click and touchstart for mobile
const playBtn = document.getElementById('playerPlayBtn');
if (playBtn) {
playBtn.addEventListener('click', togglePlayPause);
playBtn.addEventListener('touchend', function(e) {
e.preventDefault();
togglePlayPause();
}, { passive: false });
}
// Previous/Next buttons - support touch events
const prevBtn = document.getElementById('playerPrevBtn');
const nextBtn = document.getElementById('playerNextBtn');
if (prevBtn) {
prevBtn.addEventListener('click', playPrevious);
prevBtn.addEventListener('touchend', function(e) {
e.preventDefault();
playPrevious();
}, { passive: false });
}
if (nextBtn) {
nextBtn.addEventListener('click', playNext);
nextBtn.addEventListener('touchend', function(e) {
e.preventDefault();
playNext();
}, { passive: false });
}
// Skip forward/back buttons - for mobile seeking
const skipBackBtn = document.getElementById('playerSkipBackBtn');
const skipForwardBtn = document.getElementById('playerSkipForwardBtn');
if (skipBackBtn) {
skipBackBtn.addEventListener('click', skipBackward);
skipBackBtn.addEventListener('touchend', function(e) {
e.preventDefault();
skipBackward();
}, { passive: false });
}
if (skipForwardBtn) {
skipForwardBtn.addEventListener('click', skipForward);
skipForwardBtn.addEventListener('touchend', function(e) {
e.preventDefault();
skipForward();
}, { passive: false });
}
// Progress bar - support both mouse and touch events with improved mobile handling
const progressContainer = document.getElementById('playerProgressContainer');
if (progressContainer) {
let isDragging = false;
// Mouse events
progressContainer.addEventListener('click', seekToPosition);
// Touch events - improved for mobile
progressContainer.addEventListener('touchstart', function(e) {
isDragging = true;
e.preventDefault();
e.stopPropagation();
seekToPosition(e);
}, { passive: false });
progressContainer.addEventListener('touchmove', function(e) {
if (isDragging) {
e.preventDefault();
e.stopPropagation();
seekToPosition(e);
}
}, { passive: false });
progressContainer.addEventListener('touchend', function(e) {
if (isDragging) {
e.preventDefault();
e.stopPropagation();
seekToPosition(e);
isDragging = false;
}
}, { passive: false });
progressContainer.addEventListener('touchcancel', function(e) {
isDragging = false;
}, { passive: true });
}
// Volume controls
const volumeBtn = document.getElementById('playerVolumeBtn');
volumeBtn.addEventListener('click', toggleVolumeSlider);
// Show slider on hover
volumeBtn.addEventListener('mouseenter', function() {
volumeSliderContainer.classList.add('active');
});
// Keep slider visible when hovering over it
volumeSliderContainer.addEventListener('mouseenter', function() {
volumeSliderContainer.classList.add('active');
});
// Hide slider when mouse leaves the volume control area
const volumeControlArea = volumeBtn.parentElement;
volumeControlArea.addEventListener('mouseleave', function() {
volumeSliderContainer.classList.remove('active');
});
volumeSlider.addEventListener('input', updateVolume);
// Speed button
speedBtn.addEventListener('click', cyclePlaybackSpeed);
// Auto-play button
document.getElementById('playerAutoPlayBtn').addEventListener('click', toggleAutoPlay);
// Minimize button
document.getElementById('playerMinimizeBtn').addEventListener('click', toggleMinimize);
// Track title click handler
document.getElementById('playerTrackTitle').addEventListener('click', handleTrackTitleClick);
// Track artist click handler
document.getElementById('playerTrackArtist').addEventListener('click', handleTrackArtistClick);
// Close volume slider when clicking outside
document.addEventListener('click', function(e) {
if (!e.target.closest('.player-advanced-controls')) {
volumeSliderContainer.classList.remove('active');
}
});
}
// Setup audio element events
function setupAudioEvents() {
audioElement.addEventListener('timeupdate', updateProgress);
audioElement.addEventListener('loadedmetadata', updateDuration);
audioElement.addEventListener('ended', onTrackEnded);
audioElement.addEventListener('error', onAudioError);
audioElement.addEventListener('canplay', onCanPlay);
// Update play button when audio actually starts playing
audioElement.addEventListener('play', function() {
// CRITICAL: Force playbackRate to 1.0 every time audio plays
// This ensures tracks always play at normal speed
if (audioElement.playbackRate !== 1.0) {
console.warn('🎵 WARNING: playbackRate was', audioElement.playbackRate, '- forcing to 1.0');
audioElement.playbackRate = 1.0;
playbackSpeed = 1.0;
if (speedBtn) {
speedBtn.querySelector('.speed-text').textContent = '1x';
speedBtn.title = 'Speed: 1x';
}
}
isPlaying = true;
updatePlayButton();
console.log('🎵 Audio play event - button updated to pause, playbackRate:', audioElement.playbackRate);
});
// Update play button when audio is paused
audioElement.addEventListener('pause', function() {
const currentTime = audioElement.currentTime;
const duration = audioElement.duration;
// DEBUG: Log if pause happens around 44 seconds
if (currentTime >= 43 && currentTime <= 45 && duration > 60) {
console.warn('⚠️ WARNING: Audio paused at 44 seconds! CurrentTime:', currentTime, 'Duration:', duration);
console.trace('Pause call stack:');
}
isPlaying = false;
updatePlayButton();
console.log('🎵 Audio pause event - button updated to play (time:', currentTime, 'duration:', duration, ')');
});
}
// Playlist management functions
async function loadPlaylist(type, autoPlay = false) {
console.log('🎵 Loading playlist:', type, 'autoPlay:', autoPlay);
try {
let url;
switch(type) {
case 'vip':
url = '/api/get_vip_sample_tracks.php?per_page=50&_t=' + Date.now();
break;
case 'featured':
url = '/api/get_featured_tracks.php?per_page=20&_t=' + Date.now();
break;
case 'community':
url = '/api/get_community_tracks.php?per_page=50&_t=' + Date.now();
break;
default:
url = '/api/get_vip_sample_tracks.php?per_page=50&_t=' + Date.now();
}
const response = await fetch(url);
const data = await response.json();
if (data.success && data.tracks) {
console.log('🎵 Playlist data received:', {
type: type,
trackCount: data.tracks.length,
tracks: data.tracks.map(t => ({ id: t.id, title: t.title, artist: t.artist_name }))
});
currentPlaylist = data.tracks;
currentPlaylistType = type;
currentTrackIndex = 0;
updatePlayerDisplay(data.playlist_info);
// Only auto-play if explicitly requested
if (autoPlay && currentPlaylist.length > 0) {
const firstTrack = currentPlaylist[0];
playTrack(firstTrack.audio_url, firstTrack.title, firstTrack.artist_name, firstTrack.id, firstTrack.user_id);
console.log('🎵 Auto-playing first track from', type, 'playlist');
} else if (currentPlaylist.length > 0) {
console.log('🎵 Playlist loaded - ready for user interaction');
}
console.log('🎵 Loaded', currentPlaylist.length, 'tracks from', type, 'playlist');
} else {
console.error('🎵 Failed to load playlist:', data.error);
fallbackToSampleTrack();
}
} catch (error) {
console.error('🎵 Playlist loading error:', error);
fallbackToSampleTrack();
}
}
function updatePlayerDisplay(playlistInfo) {
const trackTitleLink = document.getElementById('playerTrackTitleLink');
const trackArtistLink = document.getElementById('playerTrackArtistLink');
// Show actual track info when playing, or playlist info when not playing
if (currentTrack && currentTrack.title) {
// Show the actual track title and artist
if (trackTitleLink) {
trackTitleLink.textContent = currentTrack.title;
trackTitleLink.href = '#';
trackTitleLink.style.cursor = 'default';
} else {
trackTitle.textContent = currentTrack.title;
}
if (trackArtistLink) {
trackArtistLink.textContent = currentTrack.artist;
trackArtistLink.href = '#';
trackArtistLink.style.cursor = 'default';
} else {
trackArtist.textContent = currentTrack.artist;
}
} else if (currentPlaylist.length > 0) {
// Show playlist info when no track is currently playing
if (trackTitleLink) {
trackTitleLink.textContent = playlistInfo.name;
trackTitleLink.href = '#';
trackTitleLink.style.cursor = 'default';
} else {
trackTitle.textContent = playlistInfo.name;
}
if (trackArtistLink) {
trackArtistLink.textContent = `${currentPlaylist.length} tracks • ${playlistInfo.description}`;
trackArtistLink.href = '#';
trackArtistLink.style.cursor = 'default';
} else {
trackArtist.textContent = `${currentPlaylist.length} tracks • ${playlistInfo.description}`;
}
} else {
if (trackTitleLink) {
trackTitleLink.textContent = 'No tracks available';
trackTitleLink.href = '#';
trackTitleLink.style.cursor = 'default';
} else {
trackTitle.textContent = 'No tracks available';
}
if (trackArtistLink) {
trackArtistLink.textContent = 'Try switching to a different playlist';
trackArtistLink.href = '#';
trackArtistLink.style.cursor = 'default';
} else {
trackArtist.textContent = 'Try switching to a different playlist';
}
}
}
function fallbackToSampleTrack() {
trackTitle.textContent = 'Sample Track - SoundStudioPro Demo';
trackArtist.textContent = 'Click to listen to our demo';
currentPlaylist = [{
audio_url: 'https://www.soundjay.com/misc/sounds/bell-ringing-05.wav',
title: 'Sample Track - SoundStudioPro Demo',
artist_name: 'SoundStudioPro'
}];
}
// Global function for playlist switching
window.switchPlaylist = function(type) {
console.log('🎵 Switching to playlist:', type);
// Clear cached state before loading new playlist
clearCachedTrackState();
loadPlaylist(type);
};
// Force refresh current playlist (for debugging track ownership issues)
window.refreshCurrentPlaylist = function() {
console.log('🎵 Force refreshing current playlist:', currentPlaylistType);
clearCachedTrackState();
loadPlaylist(currentPlaylistType);
};
// Force clear all caches and reload playlist
window.forceRefreshPlaylist = function() {
console.log('🎵 Force clearing all caches and reloading playlist');
clearCachedTrackState();
// Clear browser cache for API requests
if ('caches' in window) {
caches.keys().then(function(names) {
for (let name of names) {
caches.delete(name);
}
});
}
// Force reload with cache busting
setTimeout(() => {
loadPlaylist(currentPlaylistType);
}, 100);
};
// Load crate as custom playlist (from library crates) - plays all variations
window.loadCratePlaylist = function(tracks, crateName) {
console.log('🎵 Loading crate playlist:', crateName, 'with', tracks.length, 'tracks');
if (!tracks || tracks.length === 0) {
console.error('🎵 No tracks provided for crate playlist');
return false;
}
// Clear cached state
clearCachedTrackState();
// Set playlist type to custom crate
currentPlaylistType = 'crate';
// Set the playlist directly (use currentPlaylist, not playlist)
currentPlaylist = tracks.map((track, index) => ({
id: track.id,
audio_url: track.audio_url,
title: track.title,
artist_name: track.artist_name || 'Unknown Artist',
duration: track.duration || 0
}));
currentTrackIndex = 0;
// Update playlist dropdown to show crate name
const dropdown = document.getElementById('playlistDropdown');
if (dropdown) {
// Add or update crate option
let crateOption = dropdown.querySelector('option[value="crate"]');
if (!crateOption) {
crateOption = document.createElement('option');
crateOption.value = 'crate';
dropdown.appendChild(crateOption);
}
crateOption.textContent = '📦 ' + (crateName || 'Crate');
dropdown.value = 'crate';
}
// Show player if hidden
const player = document.getElementById('enhancedGlobalPlayer');
if (player) {
player.style.display = 'flex';
}
// Play first track
if (currentPlaylist.length > 0) {
const firstTrack = currentPlaylist[0];
playTrack(firstTrack.audio_url, firstTrack.title, firstTrack.artist_name, firstTrack.id, null, true);
console.log('🎵 Started crate playlist:', crateName);
// Show notification
if (typeof window.showNotification === 'function') {
window.showNotification(`Playing: ${crateName} (${currentPlaylist.length} tracks with variations)`, 'success');
}
}
return true;
};
// Public API - Play track
// NOTE: This function is only called from user interactions (button clicks),
// so we can safely attempt to play on mobile devices
function playTrack(audioUrl, title, artist, trackId = null, artistId = null, skipIndexSearch = false) {
console.log('🎵 Global Player playTrack called:', { audioUrl, title, artist, skipIndexSearch });
// Validate inputs
if (!audioUrl || audioUrl === 'NULL' || audioUrl === 'null') {
console.error('🎵 Invalid audio URL:', audioUrl);
return false;
}
// Find track index in current playlist if it exists
if (currentPlaylist.length > 0 && !skipIndexSearch) {
// BUG FIX: First check if track at currentTrackIndex already matches (for navigation)
// This prevents index from being reset incorrectly when navigating
if (currentTrackIndex >= 0 && currentTrackIndex < currentPlaylist.length) {
const trackAtCurrentIndex = currentPlaylist[currentTrackIndex];
if (trackAtCurrentIndex && (
trackAtCurrentIndex.audio_url === audioUrl ||
trackAtCurrentIndex.id == trackId || // Use == for type coercion (string vs number)
(trackAtCurrentIndex.title === title && trackAtCurrentIndex.artist_name === artist)
)) {
// Track at current index matches - keep the index (don't search)
console.log('🎵 Track matches current index:', currentTrackIndex, '- keeping index');
// currentTrackIndex is already correct, no need to update
} else {
// Track doesn't match current index - search for it
let trackIndex = currentPlaylist.findIndex(track =>
track.audio_url === audioUrl ||
track.id == trackId || // Use == for type coercion
(track.title === title && track.artist_name === artist)
);
// If still not found, try more flexible matching
if (trackIndex === -1) {
trackIndex = currentPlaylist.findIndex(track => {
const trackAudio = (track.audio_url || '').toLowerCase();
const providedAudio = (audioUrl || '').toLowerCase();
return trackAudio === providedAudio ||
trackAudio.includes(providedAudio) ||
providedAudio.includes(trackAudio);
});
}
if (trackIndex !== -1) {
currentTrackIndex = trackIndex;
console.log('🎵 Found track in playlist at index:', currentTrackIndex, 'of', currentPlaylist.length);
} else {
console.warn('🎵 Track not found in playlist after search, but keeping current index:', currentTrackIndex);
// Don't reset index - it might be correct from playNext/playPrevious
}
}
} else {
// Invalid current index - search for track
let trackIndex = currentPlaylist.findIndex(track =>
track.audio_url === audioUrl ||
track.id == trackId || // Use == for type coercion
(track.title === title && track.artist_name === artist)
);
if (trackIndex === -1) {
trackIndex = currentPlaylist.findIndex(track => {
const trackAudio = (track.audio_url || '').toLowerCase();
const providedAudio = (audioUrl || '').toLowerCase();
return trackAudio === providedAudio ||
trackAudio.includes(providedAudio) ||
providedAudio.includes(trackAudio);
});
}
if (trackIndex !== -1) {
currentTrackIndex = trackIndex;
console.log('🎵 Found track in playlist at index:', currentTrackIndex);
}
}
} else if (skipIndexSearch) {
// Skip index search - we already know the index is correct (called from playNext/playPrevious)
console.log('🎵 Skipping index search - using current index:', currentTrackIndex);
} else {
// Check if there's a page playlist stored on window (fallback for community/library pages)
if (window._communityPlaylist && window._communityPlaylist.length > 0) {
console.log('🎵 Found window._communityPlaylist with', window._communityPlaylist.length, 'tracks - using it');
currentPlaylist = window._communityPlaylist;
currentPlaylistType = window._communityPlaylistType || 'community_page';
// Find track index
const trackIndex = currentPlaylist.findIndex(t =>
t.id == trackId ||
t.audio_url === audioUrl ||
(t.title === title && t.artist_name === artist)
);
if (trackIndex !== -1) {
currentTrackIndex = trackIndex;
console.log('🎵 Set track index to', trackIndex, 'from window playlist');
}
} else if (!currentPlaylistType || currentPlaylistType === '' || currentPlaylistType === 'vip') {
console.log('🎵 No playlist loaded - single track mode (no auto-advance)');
currentPlaylistType = '';
} else {
console.log('🎵 Keeping external playlist type:', currentPlaylistType, 'with', currentPlaylist.length, 'tracks');
}
}
// Sanitize and validate track info - decode HTML entities for proper display
const sanitizedTitle = decodeHtmlEntities((title || 'Unknown Track').trim());
const sanitizedArtist = decodeHtmlEntities((artist || 'Unknown Artist').trim());
console.log('🎵 Sanitized track info:', {
originalTitle: title,
sanitizedTitle,
originalArtist: artist,
sanitizedArtist
});
// Update track info
currentTrack = {
audioUrl,
title: sanitizedTitle,
artist: sanitizedArtist,
trackId: trackId,
artistId: artistId
};
// Store track data in localStorage for persistence
try {
localStorage.setItem('globalPlayerCurrentTrack', JSON.stringify(currentTrack));
console.log('🎵 Stored track data in localStorage');
} catch (e) {
console.log('🎵 Could not store track data in localStorage:', e);
}
// Debug: Log what's being stored
console.log('🎵 DEBUG: currentTrack object created:', {
audioUrl: currentTrack.audioUrl,
title: currentTrack.title,
artist: currentTrack.artist,
trackId: currentTrack.trackId,
artistId: currentTrack.artistId,
trackIdType: typeof currentTrack.trackId,
trackIdValue: currentTrack.trackId
});
// Also log the function call parameters for debugging
console.log('🎵 DEBUG: playTrack called with:', {
audioUrl,
title,
artist,
trackId,
artistId,
trackIdType: typeof trackId,
trackIdValue: trackId
});
// Log which page is calling this
console.log('🎵 DEBUG: Called from page:', window.location.pathname);
console.log('🎵 DEBUG: trackId parameter received:', trackId);
console.log('🎵 DEBUG: trackId is truthy:', !!trackId);
console.log('🎵 DEBUG: trackId === null:', trackId === null);
console.log('🎵 DEBUG: trackId === undefined:', trackId === undefined);
// Update UI with proper escaping
if (trackTitle) {
trackTitle.textContent = sanitizedTitle;
console.log('🎵 Updated track title:', sanitizedTitle);
}
if (trackArtist) {
trackArtist.textContent = sanitizedArtist;
console.log('🎵 Updated track artist:', sanitizedArtist);
}
// Stop current audio before loading new track
if (!audioElement.paused) {
console.log('🎵 Stopping current track before switching');
audioElement.pause();
audioElement.currentTime = 0;
isPlaying = false;
updatePlayButton();
}
// Load and play audio
audioElement.src = audioUrl;
// CRITICAL: Set playbackRate BEFORE load() to prevent any default behavior
playbackSpeed = 1.0; // Reset to normal speed for each new track
audioElement.playbackRate = 1.0;
console.log('🎵 Set playbackRate to 1.0 BEFORE load()');
audioElement.load();
// CRITICAL: Set it again AFTER load() in case browser reset it
audioElement.playbackRate = 1.0;
playbackSpeed = 1.0;
if (speedBtn) {
speedBtn.querySelector('.speed-text').textContent = '1x';
speedBtn.title = 'Speed: 1x';
}
console.log('🎵 Set playback rate to 1.0x (normal speed) AFTER load()');
// Show player
showPlayer();
// Play when ready - but only if not on mobile (mobile requires user interaction)
const playWhenReady = function() {
// Remove this listener immediately to prevent multiple calls
audioElement.removeEventListener('canplay', playWhenReady);
// CRITICAL: Force playbackRate to 1.0 multiple times to ensure it sticks
// Some browsers reset it on load, so we enforce it aggressively
audioElement.playbackRate = 1.0;
playbackSpeed = 1.0; // Keep in sync
// Set it again after a tiny delay to catch any browser resets
setTimeout(() => {
if (audioElement.playbackRate !== 1.0) {
console.warn('🎵 Browser reset playbackRate to', audioElement.playbackRate, '- forcing back to 1.0');
audioElement.playbackRate = 1.0;
playbackSpeed = 1.0;
}
}, 10);
// Update UI
if (speedBtn) {
speedBtn.querySelector('.speed-text').textContent = '1x';
speedBtn.title = 'Speed: 1x';
}
// Try to play - if this was called from a user click (button), we can play on mobile too
// The playTrack function is only called from user interactions (button clicks)
audioElement.play().then(() => {
// Force it one more time after play starts
if (audioElement.playbackRate !== 1.0) {
console.warn('🎵 playbackRate changed during play() to', audioElement.playbackRate, '- forcing to 1.0');
audioElement.playbackRate = 1.0;
playbackSpeed = 1.0;
}
isPlaying = true;
updatePlayButton();
console.log('🎵 Playback started successfully at', audioElement.playbackRate, 'x speed');
}).catch(error => {
console.error('🎵 Playback failed:', error);
// If autoplay is blocked, that's okay - user can click play button
if (error.name === 'NotAllowedError') {
console.log('🎵 Autoplay blocked - user must click play button on player');
isPlaying = false;
updatePlayButton();
} else {
onAudioError(error);
}
});
};
audioElement.addEventListener('canplay', playWhenReady);
return true;
}
// Show player
function showPlayer() {
if (playerElement) {
playerElement.classList.add('playing');
console.log('🎵 Player shown');
}
}
// Hide player
function hidePlayer() {
if (playerElement) {
playerElement.classList.remove('playing');
console.log('🎵 Player hidden');
}
}
// Toggle play/pause
function togglePlayPause() {
console.log('🎵 Toggle play/pause called', {
hasAudioSrc: !!audioElement.src,
isPlaying: isPlaying,
playlistLength: currentPlaylist.length,
currentTrackIndex: currentTrackIndex,
isMobile: isMobileDevice()
});
// If no audio source is set, try to load the first track from playlist
if (!audioElement.src) {
if (currentPlaylist.length > 0) {
const firstTrack = currentPlaylist[0];
console.log('🎵 No track loaded, playing first track from playlist:', firstTrack.title);
playTrack(firstTrack.audio_url, firstTrack.title, firstTrack.artist_name, firstTrack.id, firstTrack.artist_id || firstTrack.user_id);
return;
} else {
console.log('🎵 No playlist loaded, loading VIP samples');
loadPlaylist('vip', false); // Don't auto-play on mobile
return;
}
}
if (isPlaying) {
console.log('🎵 Pausing playback');
audioElement.pause();
isPlaying = false;
} else {
console.log('🎵 Starting playback (user interaction)');
// This is a direct user interaction, so it should work on mobile
const playPromise = audioElement.play();
if (playPromise !== undefined) {
playPromise.then(() => {
isPlaying = true;
updatePlayButton();
console.log('🎵 Playback started successfully');
}).catch(error => {
console.error('🎵 Play failed:', error);
if (error.name === 'NotAllowedError') {
console.error('🎵 Autoplay blocked - this should not happen on user click');
alert('Please enable audio playback in your browser settings.');
} else {
onAudioError(error);
}
});
}
}
updatePlayButton();
}
// Update play button icon
function updatePlayButton() {
if (playIcon) {
playIcon.className = isPlaying ? 'fas fa-pause' : 'fas fa-play';
}
}
// Skip backward 10 seconds
function skipBackward() {
if (!audioElement || !audioElement.src) {
console.warn('⏪ Cannot skip: no audio element or source');
return;
}
const current = audioElement.currentTime || 0;
const newTime = Math.max(0, current - 10);
audioElement.currentTime = newTime;
currentTime = newTime;
console.log('⏪ Skipped back 10s to', formatTime(newTime));
}
// Skip forward 10 seconds
function skipForward() {
if (!audioElement || !audioElement.src) {
console.warn('⏩ Cannot skip: no audio element or source');
return;
}
const duration = audioElement.duration || 0;
const current = audioElement.currentTime || 0;
const newTime = Math.min(duration, current + 10);
audioElement.currentTime = newTime;
currentTime = newTime;
console.log('⏩ Skipped forward 10s to', formatTime(newTime));
}
// Seek to position (handles both mouse and touch events with improved mobile support)
function seekToPosition(e) {
if (!audioElement || !audioElement.src) {
console.warn('🎯 Cannot seek: no audio element or source');
return;
}
// Get duration from audio element directly
const trackDuration = audioElement.duration;
if (!trackDuration || !isFinite(trackDuration) || trackDuration <= 0) {
console.warn('🎯 Cannot seek: invalid duration', trackDuration);
return;
}
// Prevent default to avoid scrolling on mobile
if (e && e.preventDefault) {
e.preventDefault();
}
if (e && e.stopPropagation) {
e.stopPropagation();
}
// Get the progress container element
const progressContainer = document.getElementById('playerProgressContainer');
if (!progressContainer) {
console.warn('🎯 Cannot seek: progress container not found');
return;
}
const rect = progressContainer.getBoundingClientRect();
// Support both mouse and touch events - improved touch handling
let clientX = null;
if (e) {
if (e.touches && e.touches.length > 0) {
clientX = e.touches[0].clientX;
} else if (e.changedTouches && e.changedTouches.length > 0) {
clientX = e.changedTouches[0].clientX;
} else if (e.clientX !== undefined) {
clientX = e.clientX;
}
}
if (clientX === null) {
console.warn('🎯 Cannot seek: no valid clientX');
return;
}
const percent = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
const newTime = percent * trackDuration;
if (!isNaN(newTime) && isFinite(newTime) && newTime >= 0 && newTime <= trackDuration) {
audioElement.currentTime = newTime;
currentTime = newTime; // Update our state variable
console.log('🎯 Seeked to', formatTime(newTime), '(', (percent * 100).toFixed(1) + '%)');
} else {
console.warn('🎯 Invalid seek time:', newTime);
}
}
// Update progress
function updateProgress() {
if (!audioElement.src) return;
// CRITICAL: Continuously enforce playbackRate = 1.0 during playback
// This catches any code that might be changing it
if (audioElement.playbackRate !== 1.0 && playbackSpeed === 1.0) {
// Only fix it if user hasn't manually changed speed
console.warn('🎵 updateProgress: playbackRate was', audioElement.playbackRate, '- forcing to 1.0');
audioElement.playbackRate = 1.0;
}
currentTime = audioElement.currentTime;
// DEBUG: Log if we're at 44 seconds to detect premature stopping
if (currentTime >= 43.5 && currentTime <= 44.5) {
console.log('🎵 DEBUG: At 44 seconds - currentTime:', currentTime, 'duration:', duration, 'paused:', audioElement.paused);
}
const percent = (currentTime / duration) * 100;
if (progressBar) {
progressBar.style.width = `${percent}%`;
}
if (timeDisplay) {
timeDisplay.textContent = formatTime(currentTime);
}
// Update interactive waveform progress
updateWaveformProgress(currentTime, duration);
}
// Update duration
function updateDuration() {
duration = audioElement.duration;
if (durationDisplay) {
durationDisplay.textContent = formatTime(duration);
}
// Generate interactive waveform based on actual song duration
if (duration > 0) {
generateInteractiveWaveform(duration);
}
}
// Format time
function formatTime(seconds) {
if (isNaN(seconds)) return '0:00';
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
// Volume controls
function toggleVolumeSlider() {
// Toggle mute/unmute instead of just showing slider
if (audioElement.volume > 0) {
// Mute: save current volume and set to 0
previousVolume = audioElement.volume;
volume = 0;
audioElement.volume = 0;
volumeSlider.value = 0;
volumeIcon.className = 'fas fa-volume-mute';
} else {
// Unmute: restore previous volume
volume = previousVolume > 0 ? previousVolume : 1.0;
audioElement.volume = volume;
volumeSlider.value = volume * 100;
updateVolumeIcon();
}
}
function updateVolumeIcon() {
// Update volume icon based on current volume
if (volume === 0) {
volumeIcon.className = 'fas fa-volume-mute';
} else if (volume < 0.5) {
volumeIcon.className = 'fas fa-volume-down';
} else {
volumeIcon.className = 'fas fa-volume-up';
}
}
function updateVolume() {
volume = volumeSlider.value / 100;
audioElement.volume = volume;
// Update previous volume if not muted
if (volume > 0) {
previousVolume = volume;
}
// Update volume icon
updateVolumeIcon();
}
// Playback speed
function cyclePlaybackSpeed() {
const speeds = [0.5, 0.75, 1, 1.25, 1.5, 2];
const currentIndex = speeds.indexOf(playbackSpeed);
playbackSpeed = speeds[(currentIndex + 1) % speeds.length];
// Update playback rate - the override will allow it since we're updating playbackSpeed first
if (audioElement) {
audioElement.playbackRate = playbackSpeed;
}
if (speedBtn) {
speedBtn.querySelector('.speed-text').textContent = `${playbackSpeed}x`;
speedBtn.title = `Speed: ${playbackSpeed}x`;
}
console.log('🎵 User changed playback speed to', playbackSpeed, 'x');
}
// Minimize/restore
function toggleMinimize() {
isMinimized = !isMinimized;
playerElement.classList.toggle('minimized', isMinimized);
const icon = document.getElementById('playerMinimizeBtn').querySelector('i');
icon.className = isMinimized ? 'fas fa-chevron-up' : 'fas fa-chevron-down';
}
// Playlist navigation functions
// These now fetch fresh tokens with duration-based expiry for each track
// This prevents token expiration during playlist autoplay
async function playPrevious() {
if (currentPlaylist.length === 0) {
console.log('🎵 No playlist loaded');
return;
}
currentTrackIndex = (currentTrackIndex - 1 + currentPlaylist.length) % currentPlaylist.length;
const track = currentPlaylist[currentTrackIndex];
// Fetch fresh token with duration-based expiry
await playTrackWithFreshToken(track);
console.log('🎵 Playing previous track:', track.title);
}
async function playNext() {
if (currentPlaylist.length === 0) {
console.log('🎵 No playlist loaded');
return;
}
currentTrackIndex = (currentTrackIndex + 1) % currentPlaylist.length;
const track = currentPlaylist[currentTrackIndex];
// Fetch fresh token with duration-based expiry
await playTrackWithFreshToken(track);
console.log('🎵 Playing next track:', track.title);
}
// Helper function to fetch fresh token and play track
// Token expiry is based on track duration + 60s buffer
async function playTrackWithFreshToken(track) {
// Get track duration in seconds (parse from various formats)
let durationSeconds = 300; // Default 5 min if unknown
if (track.duration) {
if (typeof track.duration === 'number') {
durationSeconds = Math.ceil(track.duration);
} else if (typeof track.duration === 'string') {
// Parse "MM:SS" or "HH:MM:SS" format
const parts = track.duration.split(':').map(Number);
if (parts.length === 2) {
durationSeconds = parts[0] * 60 + parts[1];
} else if (parts.length === 3) {
durationSeconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
}
}
}
console.log('🎵 Fetching fresh token for track:', track.id, 'duration:', durationSeconds, 'seconds');
try {
// Build token request URL with duration
let tokenUrl = `/api/get_audio_token.php?track_id=${track.id}&duration=${durationSeconds}`;
if (track.selected_variation_index !== undefined && track.selected_variation_index !== null) {
tokenUrl += `&variation=${track.selected_variation_index}`;
}
const response = await fetch(tokenUrl);
const data = await response.json();
if (data.success && data.url) {
console.log('🎵 Got fresh token, expires in:', data.expires_in, 'seconds');
// Play with fresh signed URL
playTrack(data.url, track.title, track.artist_name, track.id, track.artist_id || track.user_id, true);
} else {
console.error('🎵 Failed to get fresh token:', data.error);
// Fallback to original URL (may fail if expired, but worth a try)
playTrack(track.audio_url, track.title, track.artist_name, track.id, track.artist_id || track.user_id, true);
}
} catch (error) {
console.error('🎵 Token fetch error:', error);
// Fallback to original URL
playTrack(track.audio_url, track.title, track.artist_name, track.id, track.artist_id || track.user_id, true);
}
}
// Event handlers
function onTrackEnded() {
isPlaying = false;
updatePlayButton();
const actualDuration = audioElement.duration;
const playedTime = audioElement.currentTime;
console.log('🎵 === TRACK ENDED ===');
console.log('🎵 Duration:', actualDuration, 'Played:', playedTime);
console.log('🎵 Internal playlist length:', currentPlaylist.length);
console.log('🎵 Internal playlist type:', currentPlaylistType);
console.log('🎵 Internal track index:', currentTrackIndex);
console.log('🎵 Window playlist:', window._communityPlaylist ? window._communityPlaylist.length : 'undefined');
console.log('🎵 AutoPlay enabled:', autoPlayEnabled);
// ALWAYS try to load from window if internal is empty
if (currentPlaylist.length === 0) {
if (window._communityPlaylist && window._communityPlaylist.length > 0) {
currentPlaylist = window._communityPlaylist;
currentPlaylistType = window._communityPlaylistType || 'community_page';
if (typeof window._communityTrackIndex === 'number') {
currentTrackIndex = window._communityTrackIndex;
}
console.log('🎵 LOADED from window._communityPlaylist:', currentPlaylist.length, 'tracks, index:', currentTrackIndex);
} else {
console.error('🎵 ERROR: No playlist available - internal empty, window empty');
}
}
// Auto-advance if we have a playlist
if (autoPlayEnabled && currentPlaylist.length > 0) {
console.log('🎵 ✅ Auto-advancing to next track...');
playNext();
} else {
console.error('🎵 ❌ NOT auto-advancing - autoPlayEnabled:', autoPlayEnabled, 'playlist length:', currentPlaylist.length);
}
}
function onAudioError(error) {
console.error('🎵 Audio error:', error);
isPlaying = false;
updatePlayButton();
// If it's a 404 or network error, the track might have changed ownership
if (error.message && (error.message.includes('404') || error.message.includes('Failed to fetch'))) {
console.warn('🎵 Track not found - may have changed ownership, refreshing playlist...');
// Force refresh the playlist after a short delay
setTimeout(() => {
refreshCurrentPlaylist();
}, 1000);
}
}
function onCanPlay() {
// CRITICAL: Ensure playbackRate is 1.0 whenever audio is ready
if (audioElement && audioElement.playbackRate !== 1.0) {
console.warn('🎵 onCanPlay: playbackRate was', audioElement.playbackRate, '- forcing to 1.0');
audioElement.playbackRate = 1.0;
playbackSpeed = 1.0;
if (speedBtn) {
speedBtn.querySelector('.speed-text').textContent = '1x';
speedBtn.title = 'Speed: 1x';
}
}
console.log('🎵 Audio ready to play at', audioElement ? audioElement.playbackRate : 'unknown', 'x speed');
}
// First API exposure (will be overridden below)
// Global function to enable play buttons when player is ready
window.enablePlayButtons = function() {
console.log('🎵 Enabling play buttons - global player is ready');
// This function can be called by other pages to enable their play buttons
document.querySelectorAll('.preview-btn.play-track-btn, .action-btn.play-btn').forEach(btn => {
btn.classList.add('ready');
btn.disabled = false;
// Remove loading state if present
const icon = btn.querySelector('i');
if (icon && icon.className.includes('fa-spinner')) {
icon.className = 'fas fa-play';
}
});
};
// Global function to wait for player to be ready
window.waitForGlobalPlayer = function(callback, maxAttempts = 20) {
if (window.globalPlayerReady && window.enhancedGlobalPlayer && typeof window.enhancedGlobalPlayer.playTrack === 'function') {
callback();
return;
}
if (maxAttempts > 0) {
setTimeout(() => waitForGlobalPlayer(callback, maxAttempts - 1), 250);
} else {
console.error('🎵 Global player not available after maximum attempts');
}
};
// Track title click handler
function handleTrackTitleClick() {
console.log('🎵 Track title clicked');
// Debug: Log what's in currentTrack when clicked
console.log('🎵 DEBUG: currentTrack when clicked:', {
currentTrack: currentTrack,
hasCurrentTrack: !!currentTrack,
trackId: currentTrack?.trackId,
trackIdType: typeof currentTrack?.trackId,
trackIdValue: currentTrack?.trackId,
title: currentTrack?.title,
artist: currentTrack?.artist
});
// Use the currentTrack object that was set when playTrack was called
console.log('🎵 DEBUG: Click handler - currentTrack object:', currentTrack);
console.log('🎵 DEBUG: Click handler - currentTrack.trackId:', currentTrack?.trackId);
console.log('🎵 DEBUG: Click handler - currentTrack.trackId type:', typeof currentTrack?.trackId);
if (currentTrack && currentTrack.trackId) {
// Navigate to track page using the current track's ID
console.log('🎵 SUCCESS: Navigating to track page:', currentTrack.trackId, 'for track:', currentTrack.title);
window.open(`/track.php?id=${currentTrack.trackId}`, '_blank');
return;
} else {
console.error('🎵 ERROR: Cannot navigate - missing track data:', {
hasCurrentTrack: !!currentTrack,
currentTrack: currentTrack,
trackId: currentTrack?.trackId
});
}
}
// Track artist click handler
function handleTrackArtistClick() {
console.log('🎵 Track artist clicked');
// Use the currentTrack object that was set when playTrack was called
if (currentTrack && currentTrack.artistId) {
// Navigate to artist profile page using ID (preferred method)
console.log('🎵 Navigating to artist profile:', currentTrack.artistId);
window.open(`/artist_profile.php?id=${currentTrack.artistId}`, '_blank');
return;
}
// Fallback: try using artist name if ID is not available
const trackArtist = document.getElementById('playerTrackArtist');
if (trackArtist) {
const artist = trackArtist.textContent;
if (artist && artist !== 'Discovering great music for you' && artist !== 'Unknown Artist') {
console.log('🎵 Fallback: Navigating to artist profile by name:', artist);
window.open(`/artist_profile.php?name=${encodeURIComponent(artist)}`, '_blank');
return;
}
}
console.error('🎵 ERROR: Artist ID not available - CLICK IGNORED');
alert('Artist information not available. Please play a track first.');
}
// Load community/library playlist (same pattern as artist playlist)
function loadPagePlaylist(tracks, playlistType, startIndex = 0) {
console.log('🎵 === LOADING PAGE PLAYLIST ===');
console.log('🎵 Type:', playlistType, 'Tracks:', tracks ? tracks.length : 0, 'StartIndex:', startIndex);
if (!tracks || tracks.length === 0) {
console.error('🎵 No tracks provided for page playlist');
return false;
}
// Set current playlist directly (no getters/setters)
currentPlaylist = tracks;
currentPlaylistType = playlistType;
currentTrackIndex = startIndex;
// Also store on window for redundancy
window._communityPlaylist = tracks;
window._communityPlaylistType = playlistType;
window._communityTrackIndex = startIndex;
console.log('🎵 ✅ Playlist STORED - Internal:', currentPlaylist.length, 'Window:', window._communityPlaylist.length);
console.log('🎵 First track:', tracks[0]?.title, 'Last track:', tracks[tracks.length-1]?.title);
return true;
}
// Load artist playlist (tracks array directly)
function loadArtistPlaylist(tracks, artistName, autoPlay = false) {
console.log('🎵 Loading artist playlist:', artistName, 'tracks:', tracks.length);
if (!tracks || tracks.length === 0) {
console.error('🎵 No tracks provided for artist playlist');
return;
}
// Set current playlist
currentPlaylist = tracks;
currentPlaylistType = 'artist_' + (tracks[0]?.user_id || 'unknown');
currentTrackIndex = 0;
// Update player display with artist info
updatePlayerDisplay({
name: artistName + ' Radio',
description: tracks.length + ' tracks',
type: 'artist'
});
// Auto-play first track if requested
if (autoPlay && tracks.length > 0) {
const firstTrack = tracks[0];
playTrack(firstTrack.audio_url, firstTrack.title, firstTrack.artist_name || artistName, firstTrack.id, firstTrack.user_id);
console.log('🎵 Auto-playing first track from', artistName, 'playlist');
} else {
console.log('🎵 Artist playlist loaded - ready for user interaction');
}
console.log('🎵 Loaded', tracks.length, 'tracks from', artistName, 'playlist');
}
// Expose global API
window.enhancedGlobalPlayer = {
playTrack: playTrack,
showPlayer: showPlayer,
hidePlayer: hidePlayer,
togglePlayPause: togglePlayPause,
loadPlaylist: loadPlaylist,
loadArtistPlaylist: loadArtistPlaylist,
loadPagePlaylist: loadPagePlaylist,
init: initializePlayer,
// Expose playlist state for external access via getters/setters
get currentPlaylist() { return currentPlaylist; },
set currentPlaylist(value) {
console.log('🎵 Setting currentPlaylist with', value ? value.length : 0, 'tracks');
currentPlaylist = value;
},
get currentPlaylistType() { return currentPlaylistType; },
set currentPlaylistType(value) {
console.log('🎵 Setting currentPlaylistType to:', value);
currentPlaylistType = value;
},
get currentTrackIndex() { return currentTrackIndex; },
set currentTrackIndex(value) {
console.log('🎵 Setting currentTrackIndex to:', value);
currentTrackIndex = value;
},
// Explicit methods for setting playlist (more reliable than setters in some browsers)
setPagePlaylist: function(playlist, type, index) {
console.log('🎵 setPagePlaylist called with', playlist.length, 'tracks, type:', type, 'index:', index);
currentPlaylist = playlist;
currentPlaylistType = type || 'page';
currentTrackIndex = index || 0;
},
getPlaylistInfo: function() {
return {
length: currentPlaylist.length,
type: currentPlaylistType,
index: currentTrackIndex
};
}
};
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
initializePlayer();
// Signal that global player is ready AFTER initialization
window.globalPlayerReady = true;
console.log('🎵 Enhanced Global Player initialized and ready');
});
} else {
initializePlayer();
// Signal that global player is ready AFTER initialization
window.globalPlayerReady = true;
console.log('🎵 Enhanced Global Player initialized and ready');
}
// Emergency fallback - ensure global player is always available
setTimeout(() => {
if (!window.enhancedGlobalPlayer) {
console.error('🎵 EMERGENCY: Global player not available, creating fallback');
window.enhancedGlobalPlayer = {
playTrack: function(audioUrl, title, artist, trackId) {
console.log('🎵 Fallback playTrack called:', { audioUrl, title, artist, trackId });
alert('Global player not ready. Please refresh the page.');
},
loadArtistPlaylist: function(tracks, artistName, autoPlay) {
console.log('🎵 Fallback loadArtistPlaylist called');
alert('Global player not ready. Please refresh the page.');
},
showPlayer: function() {
console.log('🎵 Fallback showPlayer called');
},
hidePlayer: function() {
console.log('🎵 Fallback hidePlayer called');
},
togglePlayPause: function() {
console.log('🎵 Fallback togglePlayPause called');
},
loadPlaylist: function(playlist, autoPlay) {
console.log('🎵 Fallback loadPlaylist called:', { playlist, autoPlay });
},
init: function() {
console.log('🎵 Fallback init called');
}
};
window.globalPlayerReady = true;
}
}, 3000);
})();
</script>