Update media browser UI with fade-in animations and improvements
- Add fade-in animation for thumbnail loading to prevent layout shifts - Add loading state indicators for thumbnails - Improve media browser CSS styling - Enhance JavaScript for smoother thumbnail loading experience Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -240,6 +240,72 @@
|
||||
setTimeout(() => { isUserAdjustingVolume = false; }, 500);
|
||||
});
|
||||
|
||||
// Mini Player: Intersection Observer to show/hide when main player scrolls out of view
|
||||
const playerContainer = document.querySelector('.player-container');
|
||||
const miniPlayer = document.getElementById('mini-player');
|
||||
|
||||
const observerOptions = {
|
||||
root: null, // viewport
|
||||
threshold: 0.1, // trigger when 10% visible
|
||||
rootMargin: '0px'
|
||||
};
|
||||
|
||||
const observerCallback = (entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
// Main player is visible - hide mini player
|
||||
miniPlayer.classList.add('hidden');
|
||||
} else {
|
||||
// Main player is scrolled out of view - show mini player
|
||||
miniPlayer.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(observerCallback, observerOptions);
|
||||
observer.observe(playerContainer);
|
||||
|
||||
// Mini player progress bar click to seek
|
||||
const miniProgressBar = document.getElementById('mini-progress-bar');
|
||||
miniProgressBar.addEventListener('click', (e) => {
|
||||
const rect = miniProgressBar.getBoundingClientRect();
|
||||
const percent = (e.clientX - rect.left) / rect.width;
|
||||
const position = percent * currentDuration;
|
||||
seek(position);
|
||||
});
|
||||
|
||||
// Mini player volume slider
|
||||
const miniVolumeSlider = document.getElementById('mini-volume-slider');
|
||||
miniVolumeSlider.addEventListener('input', (e) => {
|
||||
isUserAdjustingVolume = true;
|
||||
const volume = parseInt(e.target.value);
|
||||
document.getElementById('mini-volume-display').textContent = `${volume}%`;
|
||||
document.getElementById('volume-display').textContent = `${volume}%`;
|
||||
document.getElementById('volume-slider').value = volume;
|
||||
|
||||
// Throttle volume updates while dragging
|
||||
if (volumeUpdateTimer) {
|
||||
clearTimeout(volumeUpdateTimer);
|
||||
}
|
||||
volumeUpdateTimer = setTimeout(() => {
|
||||
setVolume(volume);
|
||||
volumeUpdateTimer = null;
|
||||
}, 50);
|
||||
});
|
||||
|
||||
miniVolumeSlider.addEventListener('change', (e) => {
|
||||
// Clear any pending throttled update
|
||||
if (volumeUpdateTimer) {
|
||||
clearTimeout(volumeUpdateTimer);
|
||||
volumeUpdateTimer = null;
|
||||
}
|
||||
|
||||
// Send final volume update immediately
|
||||
const volume = parseInt(e.target.value);
|
||||
setVolume(volume);
|
||||
setTimeout(() => { isUserAdjustingVolume = false; }, 500);
|
||||
});
|
||||
|
||||
// Progress bar click to seek
|
||||
const progressBar = document.getElementById('progress-bar');
|
||||
progressBar.addEventListener('click', (e) => {
|
||||
@@ -417,6 +483,10 @@
|
||||
document.getElementById('artist').textContent = status.artist || '';
|
||||
document.getElementById('album').textContent = status.album || '';
|
||||
|
||||
// Update mini player info
|
||||
document.getElementById('mini-track-title').textContent = status.title || t('player.no_media');
|
||||
document.getElementById('mini-artist').textContent = status.artist || '';
|
||||
|
||||
// Update state
|
||||
const previousState = currentState;
|
||||
currentState = status.state;
|
||||
@@ -424,12 +494,13 @@
|
||||
|
||||
// Update album art
|
||||
const artImg = document.getElementById('album-art');
|
||||
if (status.album_art_url) {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
artImg.src = `/api/media/artwork?token=${encodeURIComponent(token)}&_=${Date.now()}`;
|
||||
} else {
|
||||
artImg.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Crect fill='%23282828' width='300' height='300'/%3E%3Cpath fill='%236a6a6a' d='M150 80c-38.66 0-70 31.34-70 70s31.34 70 70 70 70-31.34 70-70-31.34-70-70-70zm0 20c27.614 0 50 22.386 50 50s-22.386 50-50 50-50-22.386-50-50 22.386-50 50-50zm0 30a20 20 0 100 40 20 20 0 000-40z'/%3E%3C/svg%3E";
|
||||
}
|
||||
const miniArtImg = document.getElementById('mini-album-art');
|
||||
const artworkUrl = status.album_art_url
|
||||
? `/api/media/artwork?token=${encodeURIComponent(localStorage.getItem('media_server_token'))}&_=${Date.now()}`
|
||||
: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Crect fill='%23282828' width='300' height='300'/%3E%3Cpath fill='%236a6a6a' d='M150 80c-38.66 0-70 31.34-70 70s31.34 70 70 70 70-31.34 70-70-31.34-70-70-70zm0 20c27.614 0 50 22.386 50 50s-22.386 50-50 50-50-22.386-50-50 22.386-50 50-50zm0 30a20 20 0 100 40 20 20 0 000-40z'/%3E%3C/svg%3E";
|
||||
|
||||
artImg.src = artworkUrl;
|
||||
miniArtImg.src = artworkUrl;
|
||||
|
||||
// Update progress
|
||||
if (status.duration && status.position !== null) {
|
||||
@@ -447,6 +518,8 @@
|
||||
if (!isUserAdjustingVolume) {
|
||||
document.getElementById('volume-slider').value = status.volume;
|
||||
document.getElementById('volume-display').textContent = `${status.volume}%`;
|
||||
document.getElementById('mini-volume-slider').value = status.volume;
|
||||
document.getElementById('mini-volume-display').textContent = `${status.volume}%`;
|
||||
}
|
||||
|
||||
// Update mute state
|
||||
@@ -460,6 +533,7 @@
|
||||
document.getElementById('btn-play-pause').disabled = !hasMedia;
|
||||
document.getElementById('btn-next').disabled = !hasMedia;
|
||||
document.getElementById('btn-previous').disabled = !hasMedia;
|
||||
document.getElementById('mini-btn-play-pause').disabled = !hasMedia;
|
||||
|
||||
// Start/stop position interpolation based on playback state
|
||||
if (status.state === 'playing' && previousState !== 'playing') {
|
||||
@@ -473,27 +547,32 @@
|
||||
const stateText = document.getElementById('playback-state');
|
||||
const stateIcon = document.getElementById('state-icon');
|
||||
const playPauseIcon = document.getElementById('play-pause-icon');
|
||||
const miniPlayPauseIcon = document.getElementById('mini-play-pause-icon');
|
||||
|
||||
switch(state) {
|
||||
case 'playing':
|
||||
stateText.textContent = t('state.playing');
|
||||
stateIcon.innerHTML = '<path d="M8 5v14l11-7z"/>';
|
||||
playPauseIcon.innerHTML = '<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>';
|
||||
miniPlayPauseIcon.innerHTML = '<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>';
|
||||
break;
|
||||
case 'paused':
|
||||
stateText.textContent = t('state.paused');
|
||||
stateIcon.innerHTML = '<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>';
|
||||
playPauseIcon.innerHTML = '<path d="M8 5v14l11-7z"/>';
|
||||
miniPlayPauseIcon.innerHTML = '<path d="M8 5v14l11-7z"/>';
|
||||
break;
|
||||
case 'stopped':
|
||||
stateText.textContent = t('state.stopped');
|
||||
stateIcon.innerHTML = '<path d="M6 6h12v12H6z"/>';
|
||||
playPauseIcon.innerHTML = '<path d="M8 5v14l11-7z"/>';
|
||||
miniPlayPauseIcon.innerHTML = '<path d="M8 5v14l11-7z"/>';
|
||||
break;
|
||||
default:
|
||||
stateText.textContent = t('state.idle');
|
||||
stateIcon.innerHTML = '<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5v-9l6 4.5-6 4.5z"/>';
|
||||
playPauseIcon.innerHTML = '<path d="M8 5v14l11-7z"/>';
|
||||
miniPlayPauseIcon.innerHTML = '<path d="M8 5v14l11-7z"/>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,6 +582,11 @@
|
||||
document.getElementById('current-time').textContent = formatTime(position);
|
||||
document.getElementById('total-time').textContent = formatTime(duration);
|
||||
document.getElementById('progress-bar').dataset.duration = duration;
|
||||
|
||||
// Update mini player progress
|
||||
document.getElementById('mini-progress-fill').style.width = `${percent}%`;
|
||||
document.getElementById('mini-current-time').textContent = formatTime(position);
|
||||
document.getElementById('mini-total-time').textContent = formatTime(duration);
|
||||
}
|
||||
|
||||
function startPositionInterpolation() {
|
||||
@@ -533,10 +617,16 @@
|
||||
|
||||
function updateMuteIcon(muted) {
|
||||
const muteIcon = document.getElementById('mute-icon');
|
||||
const miniMuteIcon = document.getElementById('mini-mute-icon');
|
||||
const mutedPath = '<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>';
|
||||
const unmutedPath = '<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.02z"/>';
|
||||
|
||||
if (muted) {
|
||||
muteIcon.innerHTML = '<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>';
|
||||
muteIcon.innerHTML = mutedPath;
|
||||
miniMuteIcon.innerHTML = mutedPath;
|
||||
} else {
|
||||
muteIcon.innerHTML = '<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.02z"/>';
|
||||
muteIcon.innerHTML = unmutedPath;
|
||||
miniMuteIcon.innerHTML = unmutedPath;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user