fix: visualizer bugs and better mobile support (#509)
* Refine fullscreen player to look more like apple music
* fix: buttons when in visualizer only mode
* fix: mobile sizing
* Update styles.css
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update js/ui.js
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* feat: refine fullscreen apple player
* fix: add lyrics toggle for mobile
* add lyrics toggle for mobile
* wrong branch oops 😭
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
7df10b0f5e
commit
f2135cc455
6 changed files with 752 additions and 361 deletions
|
|
@ -19,4 +19,3 @@ new_func = """def download_and_process_cover(cover_uuid):
|
||||||
content = re.sub(r"def download_and_process_cover\(cover_uuid\):[\s\S]*?(?=def process_cover)", new_func + "\n\n", content)
|
content = re.sub(r"def download_and_process_cover\(cover_uuid\):[\s\S]*?(?=def process_cover)", new_func + "\n\n", content)
|
||||||
|
|
||||||
with open("gen-editors-picks.py", "w") as f: f.write(content)
|
with open("gen-editors-picks.py", "w") as f: f.write(content)
|
||||||
|
|
||||||
|
|
|
||||||
18
index.html
18
index.html
|
|
@ -205,13 +205,17 @@
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
"
|
"
|
||||||
></div>
|
></div>
|
||||||
|
<button id="fullscreen-dismiss-handle" type="button" aria-label="Dismiss fullscreen"></button>
|
||||||
|
<button id="toggle-fullscreen-lyrics-mobile-btn" class="fullscreen-mobile-lyrics-toggle" title="Hide Lyrics">
|
||||||
|
<use svg="!lucide/mic-vocal.svg" size="18" />
|
||||||
|
</button>
|
||||||
<button id="toggle-ui-btn" class="fullscreen-ui-toggle" title="Toggle UI">
|
<button id="toggle-ui-btn" class="fullscreen-ui-toggle" title="Toggle UI">
|
||||||
<use svg="!lucide/eye-off.svg" size="24" />
|
<use svg="!lucide/eye-off.svg" size="24" />
|
||||||
</button>
|
</button>
|
||||||
<button id="toggle-fullscreen-lyrics-btn" class="fullscreen-lyrics-toggle" title="Toggle Lyrics">
|
|
||||||
<use svg="!lucide/mic-vocal.svg" size="24" />
|
|
||||||
</button>
|
|
||||||
<div class="fullscreen-top-actions">
|
<div class="fullscreen-top-actions">
|
||||||
|
<button id="toggle-fullscreen-lyrics-btn" class="fullscreen-lyrics-toggle" title="Hide Lyrics">
|
||||||
|
<use svg="!lucide/mic-vocal.svg" size="20" />
|
||||||
|
</button>
|
||||||
<button id="fs-visualizer-btn" class="fs-visualizer-btn" title="Disable Visualizer">
|
<button id="fs-visualizer-btn" class="fs-visualizer-btn" title="Disable Visualizer">
|
||||||
<use svg="!lucide/audio-lines.svg" size="20" />
|
<use svg="!lucide/audio-lines.svg" size="20" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -257,6 +261,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fullscreen-controls">
|
<div class="fullscreen-controls">
|
||||||
|
<div id="fullscreen-mobile-quality" class="fullscreen-mobile-quality" aria-hidden="true"></div>
|
||||||
<div class="fullscreen-progress-container">
|
<div class="fullscreen-progress-container">
|
||||||
<span id="fs-current-time">0:00</span>
|
<span id="fs-current-time">0:00</span>
|
||||||
<div id="fs-progress-bar" class="progress-bar">
|
<div id="fs-progress-bar" class="progress-bar">
|
||||||
|
|
@ -280,12 +285,7 @@
|
||||||
<button id="fs-repeat-btn" title="Repeat">
|
<button id="fs-repeat-btn" title="Repeat">
|
||||||
<use svg="!lucide/repeat.svg" size="24" />
|
<use svg="!lucide/repeat.svg" size="24" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button id="fs-quality-btn" class="fs-quality-btn" title="Quality" style="display: none">
|
||||||
id="fs-quality-btn"
|
|
||||||
class="fs-quality-btn"
|
|
||||||
title="Quality"
|
|
||||||
style="display: none"
|
|
||||||
>
|
|
||||||
<use svg="!lucide/pencil-line.svg" size="20" />
|
<use svg="!lucide/pencil-line.svg" size="20" />
|
||||||
<span class="fs-quality-label">Auto</span>
|
<span class="fs-quality-label">Auto</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1013,12 +1013,16 @@ function applyFullscreenLyricsShadowTweaks(amLyrics, container) {
|
||||||
}
|
}
|
||||||
|
|
||||||
.lyrics-line {
|
.lyrics-line {
|
||||||
|
transform-origin: left center;
|
||||||
transition:
|
transition:
|
||||||
opacity 0.42s ease,
|
opacity 0.42s ease,
|
||||||
transform 0.55s cubic-bezier(0.22, 1, 0.36, 1) var(--lyrics-line-delay, 0ms),
|
transform 0.55s cubic-bezier(0.22, 1, 0.36, 1) var(--lyrics-line-delay, 0ms),
|
||||||
filter 0.48s cubic-bezier(0.22, 1, 0.36, 1) !important;
|
filter 0.48s cubic-bezier(0.22, 1, 0.36, 1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lyrics-line:not(.active):not(.pre-active) {
|
||||||
|
opacity: 0.44;
|
||||||
|
}
|
||||||
.lyrics-line-container {
|
.lyrics-line-container {
|
||||||
transition:
|
transition:
|
||||||
transform 0.72s cubic-bezier(0.22, 1, 0.36, 1),
|
transform 0.72s cubic-bezier(0.22, 1, 0.36, 1),
|
||||||
|
|
@ -1033,6 +1037,10 @@ function applyFullscreenLyricsShadowTweaks(amLyrics, container) {
|
||||||
background-color 0.22s ease,
|
background-color 0.22s ease,
|
||||||
color 0.22s ease !important;
|
color 0.22s ease !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lyrics-line.active .lyrics-line-container {
|
||||||
|
transform: scale(1.015);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
298
js/ui.js
298
js/ui.js
|
|
@ -93,6 +93,7 @@ const setFullscreenUIToggleIcon = (button, visualizerOnlyMode) => {
|
||||||
button.innerHTML = visualizerOnlyMode ? SVG_EYE(24) : SVG_EYE_OFF(24);
|
button.innerHTML = visualizerOnlyMode ? SVG_EYE(24) : SVG_EYE_OFF(24);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isMobileFullscreenViewport = () => window.matchMedia('(max-width: 768px)').matches;
|
||||||
function sortTracks(tracks, sortType) {
|
function sortTracks(tracks, sortType) {
|
||||||
if (sortType === 'custom') return [...tracks];
|
if (sortType === 'custom') return [...tracks];
|
||||||
const sorted = [...tracks];
|
const sorted = [...tracks];
|
||||||
|
|
@ -155,6 +156,10 @@ export class UIRenderer {
|
||||||
this.renderLock = false;
|
this.renderLock = false;
|
||||||
this.lastRecommendedTracks = [];
|
this.lastRecommendedTracks = [];
|
||||||
this.currentArtistId = null;
|
this.currentArtistId = null;
|
||||||
|
this.fullscreenLyricsVisible = true;
|
||||||
|
this.fullscreenPlaybackStateCleanup = null;
|
||||||
|
this.fullscreenDismissHandleCleanup = null;
|
||||||
|
this.fullscreenLyricsToggleCleanup = null;
|
||||||
|
|
||||||
// Listen for dynamic color reset events
|
// Listen for dynamic color reset events
|
||||||
window.addEventListener('reset-dynamic-color', () => {
|
window.addEventListener('reset-dynamic-color', () => {
|
||||||
|
|
@ -1095,9 +1100,13 @@ export class UIRenderer {
|
||||||
let r = parseInt(hex.substr(0, 2), 16);
|
let r = parseInt(hex.substr(0, 2), 16);
|
||||||
let g = parseInt(hex.substr(2, 2), 16);
|
let g = parseInt(hex.substr(2, 2), 16);
|
||||||
let b = parseInt(hex.substr(4, 2), 16);
|
let b = parseInt(hex.substr(4, 2), 16);
|
||||||
|
let fullscreenR = r;
|
||||||
|
let fullscreenG = g;
|
||||||
|
let fullscreenB = b;
|
||||||
|
|
||||||
// Calculate perceived brightness
|
// Calculate perceived brightness
|
||||||
let brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
let brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
||||||
|
let fullscreenBrightness = brightness;
|
||||||
|
|
||||||
if (isLightMode) {
|
if (isLightMode) {
|
||||||
// In light mode, the background is white.
|
// In light mode, the background is white.
|
||||||
|
|
@ -1124,6 +1133,23 @@ export class UIRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
const adjustedColor = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
const adjustedColor = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
||||||
|
while (fullscreenBrightness < 105) {
|
||||||
|
fullscreenR = Math.min(255, Math.max(fullscreenR + 1, Math.floor(fullscreenR * 1.08)));
|
||||||
|
fullscreenG = Math.min(255, Math.max(fullscreenG + 1, Math.floor(fullscreenG * 1.08)));
|
||||||
|
fullscreenB = Math.min(255, Math.max(fullscreenB + 1, Math.floor(fullscreenB * 1.08)));
|
||||||
|
fullscreenBrightness = (fullscreenR * 299 + fullscreenG * 587 + fullscreenB * 114) / 1000;
|
||||||
|
if (fullscreenR >= 255 && fullscreenG >= 255 && fullscreenB >= 255) break;
|
||||||
|
}
|
||||||
|
while (fullscreenBrightness > 185) {
|
||||||
|
fullscreenR = Math.floor(fullscreenR * 0.92);
|
||||||
|
fullscreenG = Math.floor(fullscreenG * 0.92);
|
||||||
|
fullscreenB = Math.floor(fullscreenB * 0.92);
|
||||||
|
fullscreenBrightness = (fullscreenR * 299 + fullscreenG * 587 + fullscreenB * 114) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullscreenAdjustedColor = `#${fullscreenR.toString(16).padStart(2, '0')}${fullscreenG
|
||||||
|
.toString(16)
|
||||||
|
.padStart(2, '0')}${fullscreenB.toString(16).padStart(2, '0')}`;
|
||||||
|
|
||||||
// Calculate contrast text color for buttons (text on top of the vibrant color)
|
// Calculate contrast text color for buttons (text on top of the vibrant color)
|
||||||
const foreground = brightness > 128 ? '#000000' : '#ffffff';
|
const foreground = brightness > 128 ? '#000000' : '#ffffff';
|
||||||
|
|
@ -1135,6 +1161,8 @@ export class UIRenderer {
|
||||||
root.style.setProperty('--highlight-rgb', `${r}, ${g}, ${b}`);
|
root.style.setProperty('--highlight-rgb', `${r}, ${g}, ${b}`);
|
||||||
root.style.setProperty('--active-highlight', adjustedColor);
|
root.style.setProperty('--active-highlight', adjustedColor);
|
||||||
root.style.setProperty('--ring', adjustedColor);
|
root.style.setProperty('--ring', adjustedColor);
|
||||||
|
root.style.setProperty('--fs-accent', fullscreenAdjustedColor);
|
||||||
|
root.style.setProperty('--fs-accent-rgb', `${fullscreenR}, ${fullscreenG}, ${fullscreenB}`);
|
||||||
|
|
||||||
// Calculate a safe hover color
|
// Calculate a safe hover color
|
||||||
let hoverColor;
|
let hoverColor;
|
||||||
|
|
@ -1157,6 +1185,8 @@ export class UIRenderer {
|
||||||
root.style.removeProperty('--highlight-rgb');
|
root.style.removeProperty('--highlight-rgb');
|
||||||
root.style.removeProperty('--active-highlight');
|
root.style.removeProperty('--active-highlight');
|
||||||
root.style.removeProperty('--ring');
|
root.style.removeProperty('--ring');
|
||||||
|
root.style.removeProperty('--fs-accent');
|
||||||
|
root.style.removeProperty('--fs-accent-rgb');
|
||||||
root.style.removeProperty('--track-hover-bg');
|
root.style.removeProperty('--track-hover-bg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1270,12 +1300,10 @@ export class UIRenderer {
|
||||||
currentImage.src = coverUrl;
|
currentImage.src = coverUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
overlay.style.setProperty('--bg-image', `url('${this.api.getCoverUrl(track.album?.cover, '1280')}')`);
|
|
||||||
await this.extractAndApplyColor(this.api.getCoverUrl(track.album?.cover, '80'));
|
await this.extractAndApplyColor(this.api.getCoverUrl(track.album?.cover, '80'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const qualityBadge = this.getFullscreenQualityBadgeHTML(track);
|
this.updateFullscreenQualityBadgePlacement(track, overlay);
|
||||||
title.innerHTML = `${escapeHtml(track.title)} ${qualityBadge}`;
|
|
||||||
artist.textContent = getTrackArtists(track);
|
artist.textContent = getTrackArtists(track);
|
||||||
|
|
||||||
if (nextTrack) {
|
if (nextTrack) {
|
||||||
|
|
@ -1288,7 +1316,7 @@ export class UIRenderer {
|
||||||
|
|
||||||
async showFullscreenCover(track, nextTrack, lyricsManager, activeElement) {
|
async showFullscreenCover(track, nextTrack, lyricsManager, activeElement) {
|
||||||
if (!track) return;
|
if (!track) return;
|
||||||
this.fullscreenVisualizerSuppressed = true;
|
this.fullscreenVisualizerSuppressed = isMobileFullscreenViewport();
|
||||||
if (window.location.hash !== '#fullscreen') {
|
if (window.location.hash !== '#fullscreen') {
|
||||||
window.history.pushState({ fullscreen: true }, '', '#fullscreen');
|
window.history.pushState({ fullscreen: true }, '', '#fullscreen');
|
||||||
}
|
}
|
||||||
|
|
@ -1308,23 +1336,23 @@ export class UIRenderer {
|
||||||
nextTrackEl.classList.remove('animate-in');
|
nextTrackEl.classList.remove('animate-in');
|
||||||
}
|
}
|
||||||
|
|
||||||
const canRenderLyrics = Boolean(
|
const canRenderLyrics = Boolean(lyricsManager && activeElement && lyricsPane && lyricsContent && track.type !== 'video');
|
||||||
lyricsManager && activeElement && lyricsPane && lyricsContent && track.type !== 'video'
|
|
||||||
);
|
|
||||||
if (canRenderLyrics) {
|
if (canRenderLyrics) {
|
||||||
lyricsToggleBtn.style.display = 'none';
|
this.fullscreenLyricsVisible = true;
|
||||||
|
if (lyricsToggleBtn) lyricsToggleBtn.style.removeProperty('display');
|
||||||
overlay.classList.remove('lyrics-unavailable');
|
overlay.classList.remove('lyrics-unavailable');
|
||||||
clearFullscreenLyricsSync(lyricsContent);
|
clearFullscreenLyricsSync(lyricsContent);
|
||||||
await renderLyricsInFullscreen(track, activeElement, lyricsManager, lyricsContent);
|
await renderLyricsInFullscreen(track, activeElement, lyricsManager, lyricsContent);
|
||||||
} else {
|
} else {
|
||||||
lyricsToggleBtn.style.display = 'none';
|
this.fullscreenLyricsVisible = false;
|
||||||
|
if (lyricsToggleBtn) lyricsToggleBtn.style.display = 'none';
|
||||||
overlay.classList.add('lyrics-unavailable');
|
overlay.classList.add('lyrics-unavailable');
|
||||||
if (lyricsContent) {
|
if (lyricsContent) {
|
||||||
clearFullscreenLyricsSync(lyricsContent);
|
clearFullscreenLyricsSync(lyricsContent);
|
||||||
lyricsContent.innerHTML =
|
lyricsContent.innerHTML = '<div class="fullscreen-lyrics-empty">Lyrics are not available for this track.</div>';
|
||||||
'<div class="fullscreen-lyrics-empty">Lyrics are not available for this track.</div>';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.updateFullscreenLyricsVisibility(overlay);
|
||||||
|
|
||||||
const playerBar = document.querySelector('.now-playing-bar');
|
const playerBar = document.querySelector('.now-playing-bar');
|
||||||
if (playerBar) playerBar.style.display = 'none';
|
if (playerBar) playerBar.style.display = 'none';
|
||||||
|
|
@ -1355,9 +1383,84 @@ export class UIRenderer {
|
||||||
this.setupUIToggleButton(overlay);
|
this.setupUIToggleButton(overlay);
|
||||||
this.setupControlsAutoHide(overlay);
|
this.setupControlsAutoHide(overlay);
|
||||||
this.setupFullscreenSidePanelSync(overlay);
|
this.setupFullscreenSidePanelSync(overlay);
|
||||||
|
this.setupFullscreenDismissHandle(overlay);
|
||||||
|
this.setupFullscreenLyricsToggle(overlay);
|
||||||
await this.refreshFullscreenVisualizerState(activeElement);
|
await this.refreshFullscreenVisualizerState(activeElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFullscreenLyricsVisibility(overlay = document.getElementById('fullscreen-cover-overlay')) {
|
||||||
|
if (!overlay) return;
|
||||||
|
|
||||||
|
const lyricsToggleButtons = [
|
||||||
|
document.getElementById('toggle-fullscreen-lyrics-btn'),
|
||||||
|
document.getElementById('toggle-fullscreen-lyrics-mobile-btn'),
|
||||||
|
].filter(Boolean);
|
||||||
|
const lyricsUnavailable = overlay.classList.contains('lyrics-unavailable');
|
||||||
|
const shouldShowLyrics = this.fullscreenLyricsVisible && !lyricsUnavailable;
|
||||||
|
|
||||||
|
overlay.classList.toggle('lyrics-hidden', !shouldShowLyrics);
|
||||||
|
this.updateFullscreenQualityBadgePlacement(this.player?.currentTrack, overlay);
|
||||||
|
|
||||||
|
lyricsToggleButtons.forEach((lyricsToggleBtn) => {
|
||||||
|
lyricsToggleBtn.classList.toggle('active', shouldShowLyrics);
|
||||||
|
lyricsToggleBtn.title = shouldShowLyrics ? 'Hide Lyrics' : 'Show Lyrics';
|
||||||
|
lyricsToggleBtn.setAttribute('aria-pressed', shouldShowLyrics ? 'true' : 'false');
|
||||||
|
if (lyricsUnavailable) {
|
||||||
|
lyricsToggleBtn.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
lyricsToggleBtn.style.removeProperty('display');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFullscreenQualityBadgePlacement(track, overlay = document.getElementById('fullscreen-cover-overlay')) {
|
||||||
|
if (!track || !overlay) return;
|
||||||
|
|
||||||
|
const title = document.getElementById('fullscreen-track-title');
|
||||||
|
const mobileQuality = document.getElementById('fullscreen-mobile-quality');
|
||||||
|
if (!title) return;
|
||||||
|
|
||||||
|
const qualityBadge = this.getFullscreenQualityBadgeHTML(track);
|
||||||
|
const useMobileBadgeOnly = window.matchMedia('(max-width: 768px)').matches && overlay.classList.contains('lyrics-hidden');
|
||||||
|
|
||||||
|
title.innerHTML = useMobileBadgeOnly ? escapeHtml(track.title) : `${escapeHtml(track.title)} ${qualityBadge}`;
|
||||||
|
if (mobileQuality) {
|
||||||
|
mobileQuality.innerHTML = useMobileBadgeOnly ? qualityBadge : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async dismissFullscreenCover({ animate = true } = {}) {
|
||||||
|
const overlay = document.getElementById('fullscreen-cover-overlay');
|
||||||
|
if (!overlay || overlay.style.display === 'none') return;
|
||||||
|
|
||||||
|
if (animate) {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
const finish = () => {
|
||||||
|
overlay.removeEventListener('transitionend', handleTransitionEnd);
|
||||||
|
overlay.classList.remove('fullscreen-dragging', 'fullscreen-dismissing');
|
||||||
|
overlay.style.removeProperty('--fullscreen-drag-offset');
|
||||||
|
overlay.style.removeProperty('--fullscreen-drag-progress');
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTransitionEnd = (event) => {
|
||||||
|
if (event.target !== overlay.querySelector('.fullscreen-cover-content')) return;
|
||||||
|
finish();
|
||||||
|
};
|
||||||
|
|
||||||
|
overlay.addEventListener('transitionend', handleTransitionEnd);
|
||||||
|
overlay.classList.add('fullscreen-dismissing');
|
||||||
|
window.setTimeout(finish, 280);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.closeFullscreenCover();
|
||||||
|
|
||||||
|
if (window.location.hash === '#fullscreen') {
|
||||||
|
window.history.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
closeFullscreenCover() {
|
closeFullscreenCover() {
|
||||||
const overlay = document.getElementById('fullscreen-cover-overlay');
|
const overlay = document.getElementById('fullscreen-cover-overlay');
|
||||||
const coverImage = document.getElementById('fullscreen-cover-image');
|
const coverImage = document.getElementById('fullscreen-cover-image');
|
||||||
|
|
@ -1370,16 +1473,22 @@ export class UIRenderer {
|
||||||
lyricsContent.innerHTML = '<div class="fullscreen-lyrics-empty">Lyrics appear here.</div>';
|
lyricsContent.innerHTML = '<div class="fullscreen-lyrics-empty">Lyrics appear here.</div>';
|
||||||
}
|
}
|
||||||
overlay.style.display = 'none';
|
overlay.style.display = 'none';
|
||||||
overlay.classList.remove('visualizer-active', 'ui-hidden', 'fullscreen-cover-no-round', 'fullscreen-paused');
|
overlay.classList.remove(
|
||||||
|
'visualizer-active',
|
||||||
|
'ui-hidden',
|
||||||
|
'fullscreen-cover-no-round',
|
||||||
|
'fullscreen-paused',
|
||||||
|
'fullscreen-dragging',
|
||||||
|
'fullscreen-dismissing'
|
||||||
|
);
|
||||||
|
overlay.style.removeProperty('--fullscreen-drag-offset');
|
||||||
|
overlay.style.removeProperty('--fullscreen-drag-progress');
|
||||||
|
|
||||||
const playerBar = document.querySelector('.now-playing-bar');
|
const playerBar = document.querySelector('.now-playing-bar');
|
||||||
if (playerBar) playerBar.style.removeProperty('display');
|
if (playerBar) playerBar.style.removeProperty('display');
|
||||||
const mainContent = document.querySelector('.main-content');
|
const mainContent = document.querySelector('.main-content');
|
||||||
if (mainContent instanceof HTMLElement) {
|
if (mainContent instanceof HTMLElement) {
|
||||||
if (
|
if (typeof this.fullscreenMainContentOverflow === 'string' && this.fullscreenMainContentOverflow.length > 0) {
|
||||||
typeof this.fullscreenMainContentOverflow === 'string' &&
|
|
||||||
this.fullscreenMainContentOverflow.length > 0
|
|
||||||
) {
|
|
||||||
mainContent.style.overflowY = this.fullscreenMainContentOverflow;
|
mainContent.style.overflowY = this.fullscreenMainContentOverflow;
|
||||||
} else {
|
} else {
|
||||||
mainContent.style.removeProperty('overflow-y');
|
mainContent.style.removeProperty('overflow-y');
|
||||||
|
|
@ -1434,6 +1543,16 @@ export class UIRenderer {
|
||||||
this.fullscreenSidePanelSyncCleanup();
|
this.fullscreenSidePanelSyncCleanup();
|
||||||
this.fullscreenSidePanelSyncCleanup = null;
|
this.fullscreenSidePanelSyncCleanup = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.fullscreenDismissHandleCleanup) {
|
||||||
|
this.fullscreenDismissHandleCleanup();
|
||||||
|
this.fullscreenDismissHandleCleanup = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.fullscreenLyricsToggleCleanup) {
|
||||||
|
this.fullscreenLyricsToggleCleanup();
|
||||||
|
this.fullscreenLyricsToggleCleanup = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async startFullscreenVisualizer(activeElement, overlay) {
|
async startFullscreenVisualizer(activeElement, overlay) {
|
||||||
|
|
@ -1448,6 +1567,7 @@ export class UIRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.visualizer) {
|
if (this.visualizer) {
|
||||||
|
this.visualizer.applyPresetOverride('kawarp');
|
||||||
await this.visualizer.start();
|
await this.visualizer.start();
|
||||||
overlay.classList.add('visualizer-active');
|
overlay.classList.add('visualizer-active');
|
||||||
}
|
}
|
||||||
|
|
@ -1493,7 +1613,7 @@ export class UIRenderer {
|
||||||
const visualizerBtn = document.getElementById('fs-visualizer-btn');
|
const visualizerBtn = document.getElementById('fs-visualizer-btn');
|
||||||
const toggleBtn = document.getElementById('toggle-ui-btn');
|
const toggleBtn = document.getElementById('toggle-ui-btn');
|
||||||
const isVideoTrack = this.player?.currentTrack?.type === 'video';
|
const isVideoTrack = this.player?.currentTrack?.type === 'video';
|
||||||
const enabled = visualizerSettings.isEnabled() && !isVideoTrack && !this.fullscreenVisualizerSuppressed;
|
const enabled = !isVideoTrack && !this.fullscreenVisualizerSuppressed && !isMobileFullscreenViewport();
|
||||||
|
|
||||||
if (!overlay) return;
|
if (!overlay) return;
|
||||||
|
|
||||||
|
|
@ -1578,7 +1698,6 @@ export class UIRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fullscreenVisualizerSuppressed = false;
|
this.fullscreenVisualizerSuppressed = false;
|
||||||
visualizerSettings.setEnabled(true);
|
|
||||||
await this.refreshFullscreenVisualizerState(this.player?.activeElement);
|
await this.refreshFullscreenVisualizerState(this.player?.activeElement);
|
||||||
|
|
||||||
if (!overlay.classList.contains('visualizer-active')) {
|
if (!overlay.classList.contains('visualizer-active')) {
|
||||||
|
|
@ -1659,6 +1778,138 @@ export class UIRenderer {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupFullscreenDismissHandle(overlay) {
|
||||||
|
if (this.fullscreenDismissHandleCleanup) {
|
||||||
|
this.fullscreenDismissHandleCleanup();
|
||||||
|
this.fullscreenDismissHandleCleanup = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handle = document.getElementById('fullscreen-dismiss-handle');
|
||||||
|
if (!handle) return;
|
||||||
|
|
||||||
|
let activePointerId = null;
|
||||||
|
let startY = 0;
|
||||||
|
let startX = 0;
|
||||||
|
let lastY = 0;
|
||||||
|
let lastTimestamp = 0;
|
||||||
|
let velocityY = 0;
|
||||||
|
let hasDragged = false;
|
||||||
|
|
||||||
|
const resetDragState = () => {
|
||||||
|
activePointerId = null;
|
||||||
|
hasDragged = false;
|
||||||
|
overlay.classList.remove('fullscreen-dragging');
|
||||||
|
overlay.style.removeProperty('--fullscreen-drag-offset');
|
||||||
|
overlay.style.removeProperty('--fullscreen-drag-progress');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPointerDown = (event) => {
|
||||||
|
if (!isMobileFullscreenViewport()) return;
|
||||||
|
|
||||||
|
activePointerId = event.pointerId;
|
||||||
|
startY = event.clientY;
|
||||||
|
startX = event.clientX;
|
||||||
|
lastY = event.clientY;
|
||||||
|
lastTimestamp = event.timeStamp;
|
||||||
|
velocityY = 0;
|
||||||
|
hasDragged = false;
|
||||||
|
overlay.classList.add('fullscreen-dragging');
|
||||||
|
handle.setPointerCapture(event.pointerId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPointerMove = (event) => {
|
||||||
|
if (event.pointerId !== activePointerId) return;
|
||||||
|
|
||||||
|
const deltaY = Math.max(0, event.clientY - startY);
|
||||||
|
const deltaX = Math.abs(event.clientX - startX);
|
||||||
|
|
||||||
|
if (!hasDragged && deltaX > deltaY) {
|
||||||
|
resetDragState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasDragged = true;
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const elapsed = Math.max(1, event.timeStamp - lastTimestamp);
|
||||||
|
velocityY = (event.clientY - lastY) / elapsed;
|
||||||
|
lastY = event.clientY;
|
||||||
|
lastTimestamp = event.timeStamp;
|
||||||
|
|
||||||
|
const progress = Math.min(deltaY / Math.max(window.innerHeight * 0.32, 1), 1);
|
||||||
|
overlay.style.setProperty('--fullscreen-drag-offset', `${deltaY}px`);
|
||||||
|
overlay.style.setProperty('--fullscreen-drag-progress', progress.toFixed(3));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPointerEnd = async (event) => {
|
||||||
|
if (event.pointerId !== activePointerId) return;
|
||||||
|
|
||||||
|
const deltaY = Math.max(0, event.clientY - startY);
|
||||||
|
const shouldDismiss = hasDragged && (deltaY > 96 || velocityY > 0.55);
|
||||||
|
|
||||||
|
if (handle.hasPointerCapture(event.pointerId)) {
|
||||||
|
handle.releasePointerCapture(event.pointerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldDismiss) {
|
||||||
|
await this.dismissFullscreenCover();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetDragState();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClick = async (event) => {
|
||||||
|
if (!isMobileFullscreenViewport() || hasDragged) return;
|
||||||
|
event.preventDefault();
|
||||||
|
await this.dismissFullscreenCover();
|
||||||
|
};
|
||||||
|
|
||||||
|
handle.addEventListener('pointerdown', onPointerDown);
|
||||||
|
handle.addEventListener('pointermove', onPointerMove);
|
||||||
|
handle.addEventListener('pointerup', onPointerEnd);
|
||||||
|
handle.addEventListener('pointercancel', onPointerEnd);
|
||||||
|
handle.addEventListener('click', onClick);
|
||||||
|
|
||||||
|
this.fullscreenDismissHandleCleanup = () => {
|
||||||
|
handle.removeEventListener('pointerdown', onPointerDown);
|
||||||
|
handle.removeEventListener('pointermove', onPointerMove);
|
||||||
|
handle.removeEventListener('pointerup', onPointerEnd);
|
||||||
|
handle.removeEventListener('pointercancel', onPointerEnd);
|
||||||
|
handle.removeEventListener('click', onClick);
|
||||||
|
overlay.classList.remove('fullscreen-dragging');
|
||||||
|
overlay.style.removeProperty('--fullscreen-drag-offset');
|
||||||
|
overlay.style.removeProperty('--fullscreen-drag-progress');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setupFullscreenLyricsToggle(overlay) {
|
||||||
|
if (this.fullscreenLyricsToggleCleanup) {
|
||||||
|
this.fullscreenLyricsToggleCleanup();
|
||||||
|
this.fullscreenLyricsToggleCleanup = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleButtons = [
|
||||||
|
document.getElementById('toggle-fullscreen-lyrics-btn'),
|
||||||
|
document.getElementById('toggle-fullscreen-lyrics-mobile-btn'),
|
||||||
|
].filter(Boolean);
|
||||||
|
if (toggleButtons.length === 0) return;
|
||||||
|
|
||||||
|
const handleToggle = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
if (overlay.classList.contains('lyrics-unavailable')) return;
|
||||||
|
this.fullscreenLyricsVisible = !this.fullscreenLyricsVisible;
|
||||||
|
this.updateFullscreenLyricsVisibility(overlay);
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleButtons.forEach((toggleBtn) => toggleBtn.addEventListener('click', handleToggle));
|
||||||
|
this.updateFullscreenLyricsVisibility(overlay);
|
||||||
|
|
||||||
|
this.fullscreenLyricsToggleCleanup = () => {
|
||||||
|
toggleButtons.forEach((toggleBtn) => toggleBtn.removeEventListener('click', handleToggle));
|
||||||
|
};
|
||||||
|
}
|
||||||
setupFullscreenControls() {
|
setupFullscreenControls() {
|
||||||
const playBtn = document.getElementById('fs-play-pause-btn');
|
const playBtn = document.getElementById('fs-play-pause-btn');
|
||||||
const prevBtn = document.getElementById('fs-prev-btn');
|
const prevBtn = document.getElementById('fs-prev-btn');
|
||||||
|
|
@ -1728,16 +1979,7 @@ export class UIRenderer {
|
||||||
|
|
||||||
if (visualizerBtn) {
|
if (visualizerBtn) {
|
||||||
visualizerBtn.onclick = async () => {
|
visualizerBtn.onclick = async () => {
|
||||||
if (this.fullscreenVisualizerSuppressed) {
|
this.fullscreenVisualizerSuppressed = !this.fullscreenVisualizerSuppressed;
|
||||||
this.fullscreenVisualizerSuppressed = false;
|
|
||||||
visualizerSettings.setEnabled(true);
|
|
||||||
} else if (visualizerSettings.isEnabled()) {
|
|
||||||
visualizerSettings.setEnabled(false);
|
|
||||||
this.fullscreenVisualizerSuppressed = false;
|
|
||||||
} else {
|
|
||||||
this.fullscreenVisualizerSuppressed = false;
|
|
||||||
visualizerSettings.setEnabled(true);
|
|
||||||
}
|
|
||||||
await this.refreshFullscreenVisualizerState(this.player.activeElement);
|
await this.refreshFullscreenVisualizerState(this.player.activeElement);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -337,4 +337,16 @@ export class Visualizer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyPresetOverride(key) {
|
||||||
|
if (!this.presets?.[key] || this.activePresetKey === key) return;
|
||||||
|
|
||||||
|
if (this.activePreset?.destroy) {
|
||||||
|
this.activePreset.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._currentContextType = undefined;
|
||||||
|
this.ctx = null;
|
||||||
|
this.activePresetKey = key;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
776
styles.css
776
styles.css
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue