From 2e1367e5c27dba554982b5e8334d5588fffd0d96 Mon Sep 17 00:00:00 2001 From: edideaur Date: Mon, 9 Mar 2026 21:54:32 +0000 Subject: [PATCH] video covers --- index.html | 26 +++++++++++-- js/api.js | 16 ++++++++ js/music-api.js | 13 +++++++ js/player.js | 81 +++++++++++++++++++++++++++++++++++++- js/ui-interactions.js | 8 +++- js/ui.js | 64 +++++++++++++----------------- styles.css | 91 +++++++++++++++++++++++++++++++++++++++++-- 7 files changed, 250 insertions(+), 49 deletions(-) diff --git a/index.html b/index.html index 9954827..6b0a084 100644 --- a/index.html +++ b/index.html @@ -376,12 +376,30 @@ stroke-linejoin="round" class="lucide lucide-repeat-icon lucide-repeat" > - - - - + + + + + +
`; + }) + .join(''); + + qualityMenu.querySelectorAll('.fs-quality-option').forEach((btn) => { + btn.onclick = (e) => { + e.stopPropagation(); + const level = parseInt(btn.dataset.level); + this.hls.currentLevel = level; + const labelSpan = qualityBtn.querySelector('.fs-quality-label'); + if (labelSpan) labelSpan.textContent = level === -1 ? 'Auto' : qualityLabels[level + 1] || 'Auto'; + qualityMenu.style.display = 'none'; + }; + }); + }; + + qualityBtn.style.display = 'flex'; + qualityBtn.onclick = (e) => { + e.stopPropagation(); + const isVisible = qualityMenu.style.display === 'block'; + qualityMenu.style.display = isVisible ? 'none' : 'block'; + if (!isVisible) { + updateQualityMenu(); + } + }; + + this.hls.on(Hls.Events.LEVEL_SWITCHED, () => { + updateQualityMenu(); + const labelSpan = qualityBtn.querySelector('.fs-quality-label'); + if (labelSpan) { + const currentLevel = this.hls.currentLevel; + labelSpan.textContent = currentLevel === -1 ? 'Auto' : qualityLabels[currentLevel + 1] || 'Auto'; + } + }); + + document.addEventListener('click', () => { + qualityMenu.style.display = 'none'; + }); + + qualityMenu.onclick = (e) => e.stopPropagation(); + } + async playVideo(video) { if (!video) return; const videoTrack = { diff --git a/js/ui-interactions.js b/js/ui-interactions.js index 6b7e08c..95cd7bc 100644 --- a/js/ui-interactions.js +++ b/js/ui-interactions.js @@ -251,6 +251,12 @@ export function initializeUIInteractions(player, api, ui) { ? `title="Blocked: ${contentBlockingSettings.isTrackBlocked(track.id) ? 'Track blocked' : contentBlockingSettings.isArtistBlocked(track.artist?.id) ? 'Artist blocked' : 'Album blocked'}"` : ''; + const isVideo = track.type === 'video'; + const coverUrl = + isVideo && track.imageId + ? api.getVideoCoverUrl(track.imageId) + : api.getCoverUrl(track.album?.cover); + return `
@@ -260,7 +266,7 @@ export function initializeUIInteractions(player, api, ui) {
-
${escapeHtml(trackTitle)} ${qualityBadge}
diff --git a/js/ui.js b/js/ui.js index 0b6ef28..891ff56 100644 --- a/js/ui.js +++ b/js/ui.js @@ -348,9 +348,19 @@ export class UIRenderer { let trackImageHTML = ''; if (showCover) { if (isVideo && this.currentPage === 'playlist') { - trackImageHTML = `
`; + const videoCoverUrl = this.api.getVideoCoverUrl(track.imageId); + if (videoCoverUrl) { + trackImageHTML = ``; + } else { + trackImageHTML = `
`; + } } else if (isVideo && (this.currentPage === 'search' || this.currentPage === 'library')) { - trackImageHTML = `
`; + const videoCoverUrl = this.api.getVideoCoverUrl(track.imageId); + if (videoCoverUrl) { + trackImageHTML = ``; + } else { + trackImageHTML = `
`; + } } else { trackImageHTML = this.getCoverHTML( track.image || track.cover || track.album?.cover, @@ -670,10 +680,13 @@ export class UIRenderer { const duration = formatTime(video.duration); const artistName = getTrackArtists(video); + const videoCoverUrl = this.api.getVideoCoverUrl(video.imageId); const cover = video.image || video.cover; let imageHTML; - if (cover) { + if (videoCoverUrl) { + imageHTML = `${escapeHtml(video.title)}`; + } else if (cover) { imageHTML = this.getCoverHTML(cover, escapeHtml(video.title)); } else { imageHTML = `
`; @@ -1012,6 +1025,11 @@ export class UIRenderer { if (image) image.style.display = 'block'; if (visualizerContainer) visualizerContainer.style.display = 'block'; + const qualityBtn = document.getElementById('fs-quality-btn'); + const qualityMenu = document.getElementById('fs-quality-menu'); + if (qualityBtn) qualityBtn.style.display = 'none'; + if (qualityMenu) qualityMenu.style.display = 'none'; + const videoCoverUrl = track.videoUrl || track.videoCoverUrl || track.album?.videoCoverUrl || null; const coverUrl = videoCoverUrl || this.api.getCoverUrl(track.album?.cover, '1280'); @@ -1227,50 +1245,20 @@ export class UIRenderer { // Mouse move handler const handleMouseMove = (e) => { const rect = overlay.getBoundingClientRect(); - // Check if mouse is near the top-right corner (within 150px from right, 100px from top) const isNearTopRight = e.clientY < 100 && e.clientX > rect.width - 150; if (isUIHidden) { if (overlay.classList.contains('is-video-mode')) { - toggleUI(); + if (isNearTopRight) { + showButton(); + } else { + hideButton(); + } } else if (isNearTopRight) { showButton(); } else { hideButton(); } - } else if (overlay.classList.contains('is-video-mode')) { - resetVideoHideTimer(); - } - }; - - let videoHideTimer = null; - const resetVideoHideTimer = () => { - if (videoHideTimer) clearTimeout(videoHideTimer); - if (!overlay.classList.contains('is-video-mode') || isUIHidden) return; - - videoHideTimer = setTimeout(() => { - if (!isUIHidden && overlay.classList.contains('is-video-mode')) { - toggleUI(); - } - }, 3000); - }; - - resetVideoHideTimer(); - - // Toggle UI visibility - const toggleUI = () => { - isUIHidden = !isUIHidden; - overlay.classList.toggle('ui-hidden', isUIHidden); - toggleBtn.classList.toggle('active', isUIHidden); - toggleBtn.title = isUIHidden ? 'Show UI' : 'Hide UI'; - - if (isUIHidden) { - // When UI is hidden, immediately hide the button - // It will reappear when mouse nears top-right - hideButton(); - } else { - // When UI is shown, keep button visible - showButton(); } }; diff --git a/styles.css b/styles.css index dfc3a65..d0c3942 100644 --- a/styles.css +++ b/styles.css @@ -2113,6 +2113,10 @@ input[type='search']::-webkit-search-cancel-button { color: var(--highlight); } +.track-item.video-track-item { + gap: var(--spacing-xl); +} + .track-item.unavailable { opacity: 0.5; cursor: not-allowed; @@ -5436,12 +5440,12 @@ img[src=''] { right: 2rem; max-width: 500px; margin: 0 auto; - background: rgb(15, 15, 15, 0.5); - backdrop-filter: blur(12px); + background: transparent; + backdrop-filter: none; padding: 0.6rem 1rem; border-radius: 10px; - border: 1px solid rgb(255, 255, 255, 0.05); - box-shadow: 0 4px 20px rgb(0, 0, 0, 0.4); + border: none; + box-shadow: none; transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s ease; @@ -5478,6 +5482,84 @@ img[src=''] { display: none; } +#fullscreen-cover-overlay.is-video-mode .fs-quality-btn { + display: flex !important; + width: 28px; + height: 28px; + padding: 0.25rem; +} + +#fullscreen-cover-overlay.is-video-mode .fs-quality-btn svg { + width: 18px; + height: 18px; +} + +#fullscreen-cover-overlay.is-video-mode .fs-quality-label { + display: none; +} + +.fs-quality-btn { + background: transparent; + border: none; + color: white; + padding: 4px 8px; + border-radius: 4px; + cursor: pointer; + font-size: 0.75rem; + display: flex; + align-items: center; + gap: 4px; + opacity: 0.7; + transition: opacity 0.2s; + position: relative; +} + +.fs-quality-btn:hover { + opacity: 1; + background: rgba(255, 255, 255, 0.1); +} + +.fullscreen-volume-container { + position: relative; +} + +.fs-quality-menu { + position: absolute; + bottom: 100%; + right: 0; + background: rgb(20, 20, 20); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + padding: 4px; + min-width: 120px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); + z-index: 1000; + margin-bottom: 8px; +} + +.fs-quality-option { + display: block; + width: 100%; + padding: 8px 12px; + border: none; + background: transparent; + color: white; + text-align: left; + cursor: pointer; + font-size: 0.85rem; + border-radius: 4px; + transition: background 0.2s; +} + +.fs-quality-option:hover { + background: rgba(255, 255, 255, 0.1); +} + +.fs-quality-option.active { + background: var(--primary); + color: white; +} + #fullscreen-cover-overlay.is-video-mode .fullscreen-volume-container { margin-top: 0.5rem; } @@ -8193,6 +8275,7 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { .video-card .card-image-container { aspect-ratio: 16 / 9 !important; + margin-bottom: var(--spacing-sm); } .video-card .card-image {