Move media player from sidebar to floating bottom-right mini player
The sidebar player was a poor use of vertical real estate and created the collapsed-state layout issues. The mini player is now a fixed 360px widget at bottom-right (above the bell/help buttons), matching the convention of most streaming apps. Changes: - Removed media player from sidebar; sidebar spacer now pushes support/version section to bottom as before - New .mini-player-body horizontal layout: album art | track info | controls - Added prev/next skip buttons (mini-nav-btn) with same skip logic as the Now Playing modal; updateNpPrevNextButtons() now syncs both sets - .media-player.idle now display:none (widget hides entirely when no track) - Progress bar is a flush full-width line at the top of the widget - Volume slider kept as hidden DOM element for JS compatibility; volume is set via the Now Playing modal - Toast container moved up to bottom:174px to stay above the mini player - expand-hint button updated to four-corner expand icon, opens NP modal - All volumeSlider and click-exclusion references updated for null-safety
This commit is contained in:
parent
276f70ab9a
commit
76ff98261b
3 changed files with 141 additions and 117 deletions
124
webui/index.html
124
webui/index.html
|
|
@ -255,73 +255,6 @@
|
|||
<!-- Spacer -->
|
||||
<div class="sidebar-spacer"></div>
|
||||
|
||||
<!-- Media Player Section -->
|
||||
<div class="media-player" id="media-player">
|
||||
<!-- Progress bar at top of player -->
|
||||
<div class="player-top-progress">
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-track">
|
||||
<div class="progress-fill" id="progress-fill"></div>
|
||||
</div>
|
||||
<input type="range" class="progress-bar" id="progress-bar" min="0" max="100" value="0" step="0.1">
|
||||
</div>
|
||||
<div class="time-display">
|
||||
<span class="current-time" id="current-time">0:00</span>
|
||||
<span class="total-time" id="total-time">0:00</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading Animation -->
|
||||
<div class="loading-animation hidden" id="loading-animation">
|
||||
<div class="loading-bar">
|
||||
<div class="loading-progress"></div>
|
||||
</div>
|
||||
<div class="loading-text">0%</div>
|
||||
</div>
|
||||
|
||||
<!-- Track info row -->
|
||||
<div class="media-header">
|
||||
<img class="sidebar-album-art" id="sidebar-album-art" src="/static/trans2.png" alt="" onerror="this.src='/static/trans2.png'">
|
||||
<div class="media-info">
|
||||
<div class="track-title" id="track-title">No track</div>
|
||||
<div class="artist-name" id="artist-name">Unknown Artist</div>
|
||||
<div class="album-name header-album" id="album-name">Unknown Album</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Controls row (always visible) -->
|
||||
<div class="media-controls-row">
|
||||
<button class="play-button" id="play-button" disabled>
|
||||
<svg class="play-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
|
||||
<svg class="pause-icon" viewBox="0 0 24 24" fill="currentColor" style="display:none"><path d="M6 4h4v16H6zm8 0h4v16h-4z"/></svg>
|
||||
</button>
|
||||
<div class="volume-control">
|
||||
<span class="volume-icon">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.16c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.16c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/></svg>
|
||||
</span>
|
||||
<input type="range" class="volume-slider" id="volume-slider" min="0" max="100" value="70">
|
||||
</div>
|
||||
<button class="stop-button" id="stop-button" disabled>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" width="12" height="12"><rect x="6" y="6" width="12" height="12" rx="1.5"/></svg>
|
||||
</button>
|
||||
<button class="expand-hint" aria-label="Expand player">
|
||||
<svg viewBox="0 0 16 16" fill="currentColor" width="12" height="12"><path d="M3 10l5 4 5-4" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Expanded Content (kept for backward compat, hidden) -->
|
||||
<div class="media-expanded hidden" id="media-expanded"></div>
|
||||
|
||||
<!-- No Track Message -->
|
||||
<div class="no-track-message" id="no-track-message">
|
||||
<svg class="no-track-icon" viewBox="0 0 48 48" fill="none" width="28" height="28">
|
||||
<circle cx="24" cy="24" r="22" stroke="currentColor" stroke-width="1.5" opacity="0.25"/>
|
||||
<path d="M20 16l12 8-12 8z" fill="currentColor" opacity="0.2"/>
|
||||
</svg>
|
||||
<span>Play a track to get started</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Support Button -->
|
||||
<div class="support-section">
|
||||
<button class="support-button" onclick="showSupportModal()">Support SoulSync</button>
|
||||
|
|
@ -7455,6 +7388,63 @@
|
|||
</div>
|
||||
<div class="gsearch-results" id="gsearch-results"></div>
|
||||
|
||||
<!-- Floating Mini Media Player — fixed bottom-right, above bell/help -->
|
||||
<div class="media-player idle" id="media-player">
|
||||
<!-- Thin interactive progress bar spanning full width at top -->
|
||||
<div class="player-top-progress">
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-track">
|
||||
<div class="progress-fill" id="progress-fill"></div>
|
||||
</div>
|
||||
<input type="range" class="progress-bar" id="progress-bar" min="0" max="100" value="0" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading animation (shown while buffering/downloading) -->
|
||||
<div class="loading-animation hidden" id="loading-animation">
|
||||
<div class="loading-bar"><div class="loading-progress"></div></div>
|
||||
<div class="loading-text">0%</div>
|
||||
</div>
|
||||
|
||||
<!-- Main body: album art + track info + controls -->
|
||||
<div class="mini-player-body">
|
||||
<img class="sidebar-album-art" id="sidebar-album-art" src="/static/trans2.png" alt="" onerror="this.src='/static/trans2.png'">
|
||||
<div class="media-info">
|
||||
<div class="track-title" id="track-title"><span class="title-text">No track</span></div>
|
||||
<div class="artist-name" id="artist-name">Unknown Artist</div>
|
||||
</div>
|
||||
<div class="mini-player-controls">
|
||||
<button class="mini-nav-btn" id="mini-prev-btn" title="Previous" disabled>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M6 6h2v12H6zm3.5 6l8.5 6V6z"/></svg>
|
||||
</button>
|
||||
<button class="play-button" id="play-button" disabled>
|
||||
<svg class="play-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
|
||||
<svg class="pause-icon" viewBox="0 0 24 24" fill="currentColor" style="display:none"><path d="M6 4h4v16H6zm8 0h4v16h-4z"/></svg>
|
||||
</button>
|
||||
<button class="mini-nav-btn" id="mini-next-btn" title="Next" disabled>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/></svg>
|
||||
</button>
|
||||
<button class="stop-button" id="stop-button" disabled>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" width="10" height="10"><rect x="6" y="6" width="12" height="12" rx="1.5"/></svg>
|
||||
</button>
|
||||
<button class="expand-hint" aria-label="Expand player" title="Now Playing">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="13" height="13"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Elements kept in DOM for JS compatibility — not visible in mini player -->
|
||||
<div class="volume-control" style="display:none">
|
||||
<span class="volume-icon"></span>
|
||||
<input type="range" class="volume-slider" id="volume-slider" min="0" max="100" value="70">
|
||||
</div>
|
||||
<span id="current-time" style="display:none">0:00</span>
|
||||
<span id="total-time" style="display:none">0:00</span>
|
||||
<div id="album-name" style="display:none"></div>
|
||||
<div class="media-expanded hidden" id="media-expanded"></div>
|
||||
<div class="no-track-message hidden" id="no-track-message"><span>Play a track to get started</span></div>
|
||||
</div>
|
||||
|
||||
<button class="notif-bell-btn" id="notif-bell-btn" onclick="toggleNotifPanel()" title="Notifications">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
|
||||
<span class="notif-bell-badge" id="notif-bell-badge" style="display:none">0</span>
|
||||
|
|
|
|||
|
|
@ -3059,7 +3059,7 @@ function initializeMediaPlayer() {
|
|||
|
||||
// Set initial volume
|
||||
audioPlayer.volume = 0.7; // 70%
|
||||
volumeSlider.value = 70;
|
||||
if (volumeSlider) volumeSlider.value = 70;
|
||||
}
|
||||
|
||||
// Track title click handled by initExpandedPlayer's media-player click handler
|
||||
|
|
@ -3067,7 +3067,7 @@ function initializeMediaPlayer() {
|
|||
// Media controls
|
||||
playButton.addEventListener('click', handlePlayPause);
|
||||
stopButton.addEventListener('click', handleStop);
|
||||
volumeSlider.addEventListener('input', handleVolumeChange);
|
||||
if (volumeSlider) volumeSlider.addEventListener('input', handleVolumeChange);
|
||||
|
||||
// Progress bar controls
|
||||
const progressBar = document.getElementById('progress-bar');
|
||||
|
|
@ -3083,7 +3083,13 @@ function initializeMediaPlayer() {
|
|||
}
|
||||
|
||||
// Update volume slider styling
|
||||
volumeSlider.addEventListener('input', updateVolumeSliderAppearance);
|
||||
if (volumeSlider) volumeSlider.addEventListener('input', updateVolumeSliderAppearance);
|
||||
|
||||
// Mini player prev / next buttons
|
||||
const miniPrevBtn = document.getElementById('mini-prev-btn');
|
||||
const miniNextBtn = document.getElementById('mini-next-btn');
|
||||
if (miniPrevBtn) miniPrevBtn.addEventListener('click', (e) => { e.stopPropagation(); playPreviousInQueue(); });
|
||||
if (miniNextBtn) miniNextBtn.addEventListener('click', (e) => { e.stopPropagation(); playNextInQueue(); });
|
||||
}
|
||||
|
||||
function toggleMediaPlayerExpansion() {
|
||||
|
|
@ -3325,6 +3331,7 @@ function handleProgressBarChange(event) {
|
|||
|
||||
function updateVolumeSliderAppearance() {
|
||||
const slider = document.getElementById('volume-slider');
|
||||
if (!slider) return;
|
||||
const value = slider.value;
|
||||
slider.style.setProperty('--volume-percent', `${value}%`);
|
||||
}
|
||||
|
|
@ -4169,7 +4176,7 @@ function initExpandedPlayer() {
|
|||
mediaPlayer.style.cursor = 'pointer';
|
||||
mediaPlayer.addEventListener('click', (e) => {
|
||||
// Don't open modal when clicking controls (let expand-hint through)
|
||||
if (e.target.closest('.play-button, .stop-button, .volume-slider, .volume-control, .progress-bar, .volume-icon') && !e.target.closest('.expand-hint')) return;
|
||||
if (e.target.closest('.play-button, .stop-button, .volume-slider, .volume-control, .progress-bar, .volume-icon, .mini-nav-btn') && !e.target.closest('.expand-hint')) return;
|
||||
if (currentTrack) openNowPlayingModal();
|
||||
});
|
||||
}
|
||||
|
|
@ -4821,16 +4828,20 @@ function renderNpQueue() {
|
|||
}
|
||||
|
||||
function updateNpPrevNextButtons() {
|
||||
const canPrev = npQueueIndex > 0 || (audioPlayer && audioPlayer.currentTime > 3);
|
||||
const canNext = npQueue.length > 0 && (npShuffleOn ? npQueue.length > 1 : (npQueueIndex < npQueue.length - 1 || npRepeatMode === 'all'));
|
||||
|
||||
// Full Now Playing modal buttons
|
||||
const prevBtn = document.getElementById('np-prev-btn');
|
||||
const nextBtn = document.getElementById('np-next-btn');
|
||||
if (prevBtn) {
|
||||
const canPrev = npQueueIndex > 0 || (audioPlayer && audioPlayer.currentTime > 3);
|
||||
prevBtn.disabled = !canPrev;
|
||||
}
|
||||
if (nextBtn) {
|
||||
const canNext = npQueue.length > 0 && (npShuffleOn ? npQueue.length > 1 : (npQueueIndex < npQueue.length - 1 || npRepeatMode === 'all'));
|
||||
nextBtn.disabled = !canNext;
|
||||
}
|
||||
if (prevBtn) prevBtn.disabled = !canPrev;
|
||||
if (nextBtn) nextBtn.disabled = !canNext;
|
||||
|
||||
// Mini player buttons
|
||||
const miniPrevBtn = document.getElementById('mini-prev-btn');
|
||||
const miniNextBtn = document.getElementById('mini-next-btn');
|
||||
if (miniPrevBtn) miniPrevBtn.disabled = !canPrev;
|
||||
if (miniNextBtn) miniNextBtn.disabled = !canNext;
|
||||
}
|
||||
|
||||
function handlePlayerKeyboardShortcuts(event) {
|
||||
|
|
|
|||
|
|
@ -506,67 +506,90 @@ body {
|
|||
);
|
||||
}
|
||||
|
||||
/* Media Player Section - Premium Compact Design */
|
||||
/* ── Floating Mini Media Player — fixed bottom-right ─────────────────────── */
|
||||
.media-player {
|
||||
position: fixed;
|
||||
bottom: 88px;
|
||||
right: 16px;
|
||||
width: 360px;
|
||||
background: linear-gradient(180deg,
|
||||
rgba(24, 24, 24, 0.97) 0%,
|
||||
rgba(16, 16, 16, 0.99) 50%,
|
||||
rgba(10, 10, 10, 1.0) 100%);
|
||||
backdrop-filter: blur(16px) saturate(1.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(20px) saturate(1.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.07);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 14px;
|
||||
margin: 8px 14px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition: opacity 0.25s ease, transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.25s ease;
|
||||
box-shadow:
|
||||
0 8px 24px rgba(0, 0, 0, 0.45),
|
||||
0 2px 8px rgba(0, 0, 0, 0.3),
|
||||
0 12px 40px rgba(0, 0, 0, 0.55),
|
||||
0 4px 12px rgba(0, 0, 0, 0.35),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.07);
|
||||
position: relative;
|
||||
min-height: 120px;
|
||||
flex-shrink: 0;
|
||||
z-index: 99998;
|
||||
}
|
||||
|
||||
.media-player:hover {
|
||||
border-color: rgba(var(--accent-rgb), 0.18);
|
||||
border-top-color: rgba(var(--accent-rgb), 0.22);
|
||||
border-color: rgba(var(--accent-rgb), 0.2);
|
||||
border-top-color: rgba(var(--accent-rgb), 0.25);
|
||||
box-shadow:
|
||||
0 10px 28px rgba(0, 0, 0, 0.5),
|
||||
0 4px 14px rgba(var(--accent-rgb), 0.1),
|
||||
0 16px 48px rgba(0, 0, 0, 0.6),
|
||||
0 4px 16px rgba(var(--accent-rgb), 0.12),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Compact idle state — collapsed when nothing is playing */
|
||||
/* Hidden when nothing is playing — widget disappears entirely */
|
||||
.media-player.idle {
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
margin: 4px 14px;
|
||||
}
|
||||
|
||||
.media-player.idle .player-top-progress,
|
||||
.media-player.idle .media-header,
|
||||
.media-player.idle .media-controls-row,
|
||||
.media-player.idle .media-expanded {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.media-player.idle .no-track-message {
|
||||
padding: 10px 14px;
|
||||
font-size: 0.75rem;
|
||||
gap: 8px;
|
||||
/* Mini player body — horizontal: art | info | controls */
|
||||
.mini-player-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 12px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.media-player.idle .no-track-message svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
/* Compact prev/next nav buttons */
|
||||
.mini-nav-btn {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: color 0.18s ease, background 0.18s ease;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Top progress bar - thin line across full width */
|
||||
.mini-nav-btn:hover:not(:disabled) {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.mini-nav-btn:disabled {
|
||||
opacity: 0.22;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Controls cluster in the mini player */
|
||||
.mini-player-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Top progress bar - flush full-width line at the very top of the mini player */
|
||||
.player-top-progress {
|
||||
padding: 0 14px;
|
||||
padding-top: 10px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.player-top-progress .progress-bar-container {
|
||||
|
|
@ -5034,10 +5057,10 @@ body.helper-mode-active #dashboard-activity-feed:hover {
|
|||
NOTIFICATION SYSTEM — Compact toasts + bell button + notification panel
|
||||
================================================================================== */
|
||||
|
||||
/* Toast container — bottom-right, above the bell/helper buttons */
|
||||
/* Toast container — bottom-right, above the mini player */
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
bottom: 84px;
|
||||
bottom: 174px;
|
||||
right: 24px;
|
||||
z-index: 99999;
|
||||
pointer-events: none;
|
||||
|
|
|
|||
Loading…
Reference in a new issue