bring back animated album covers + improvements

This commit is contained in:
Samidy 2026-04-13 22:01:54 +03:00
parent 5f8cb6e947
commit 4497863667
4 changed files with 147 additions and 44 deletions

View file

@ -19,7 +19,7 @@
"@svta/common-media-library": "^0.18.1",
"@types/wicg-file-system-access": "^2023.10.7",
"@typescript-eslint/eslint-plugin": "^8.57.2",
"@uimaxbai/am-lyrics": "^1.2.1",
"@uimaxbai/am-lyrics": "^1.2.8",
"@vitest/web-worker": "^4.1.2",
"appwrite": "^23.0.0",
"butterchurn": "^2.6.7",
@ -678,7 +678,7 @@
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="],
"@uimaxbai/am-lyrics": ["@uimaxbai/am-lyrics@1.2.1", "", { "dependencies": { "@babel/runtime": "^7.27.6", "lit": "^3.1.4" }, "peerDependencies": { "@lit/react": "^1.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@lit/react", "react"] }, "sha512-DbzIeQS3bNAiQ+T35EYnFgCSEn6caTefyhtycb4DJr2+iLf8bi8DRP8Dd2cwY5bxAl3ZG6MKdM+Vma3fnN2ruw=="],
"@uimaxbai/am-lyrics": ["@uimaxbai/am-lyrics@1.2.8", "", { "dependencies": { "@babel/runtime": "^7.27.6", "lit": "^3.1.4" }, "peerDependencies": { "@lit/react": "^1.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@lit/react", "react"] }, "sha512-aR8kxqIYcVlsMCH6bbH8ANG+bN/2OAw66ZFjYD1a25hkMTyxtULWgWwAZlUfreP9V47bFvNgXIKvOqhO5JFpeg=="],
"@vitest/browser": ["@vitest/browser@4.1.2", "", { "dependencies": { "@blazediff/core": "1.9.1", "@vitest/mocker": "4.1.2", "@vitest/utils": "4.1.2", "magic-string": "^0.30.21", "pngjs": "^7.0.0", "sirv": "^3.0.2", "tinyrainbow": "^3.1.0", "ws": "^8.19.0" }, "peerDependencies": { "vitest": "4.1.2" } }, "sha512-CwdIf90LNf1Zitgqy63ciMAzmyb4oIGs8WZ40VGYrWkssQKeEKr32EzO8MKUrDPPcPVHFI9oQ5ni2Hp24NaNRQ=="],

View file

@ -247,10 +247,6 @@ export class MusicAPI {
}
try {
/*
Maintainer of artwork.boidu.dev has asked for his API to be removed for the time being due to spam
*/
/*
const url = `https://artwork.boidu.dev/?s=${encodeURIComponent(title)}&a=${encodeURIComponent(artist)}`;
const response = await fetch(url);
if (!response.ok) return null;
@ -261,8 +257,6 @@ export class MusicAPI {
};
this.videoArtworkCache.set(cacheKey, result);
return result;
*/
throw new Error('Video artwork is disabled for now.');
} catch (error) {
console.warn('Failed to fetch video artwork:', error);
return null;

View file

@ -189,6 +189,29 @@ export class Player {
});
this._setupVideoSync();
this._setupAnimatedCoverSync();
}
_setupAnimatedCoverSync() {
const syncPlayPause = () => {
const isPaused = this.activeElement.paused;
document.querySelectorAll('.cover, #fullscreen-cover-image').forEach((el) => {
if (el.tagName === 'VIDEO' && el !== this.video) {
if (isPaused) {
el.pause();
} else {
el.play().catch(() => {});
}
}
});
};
this.audio.addEventListener('play', syncPlayPause);
this.audio.addEventListener('pause', syncPlayPause);
if (this.video) {
this.video.addEventListener('play', syncPlayPause);
this.video.addEventListener('pause', syncPlayPause);
}
}
_setupVideoSync() {
@ -779,6 +802,46 @@ export class Player {
await this.playTrackFromQueue();
}
async updateVideoCovers(videoUrl) {
if (!videoUrl) return;
const syncCover = async (el) => {
if (!el) return;
const isPaused = this.activeElement.paused;
let videoEl;
if (el.tagName === 'IMG') {
videoEl = document.createElement('video');
videoEl.autoplay = !isPaused;
videoEl.loop = true;
videoEl.muted = true;
videoEl.playsInline = true;
videoEl.className = el.className;
videoEl.id = el.id;
videoEl.style.objectFit = 'cover';
el.replaceWith(videoEl);
} else if (el.tagName === 'VIDEO') {
videoEl = el;
} else {
return;
}
if (UIRenderer.instance) {
await UIRenderer.instance.setupHlsVideo(videoEl, videoUrl, null);
if (isPaused) {
videoEl.pause();
} else {
videoEl.play().catch(() => {});
}
}
};
const playerBarCover = document.querySelector('.now-playing-bar .cover');
if (playerBarCover) await syncCover(playerBarCover);
const fullscreenCover = document.getElementById('fullscreen-cover-image');
if (fullscreenCover) await syncCover(fullscreenCover);
}
async playTrackFromQueue(startTime = 0, recursiveCount = 0, isRetry = false) {
if (!isRetry) {
this.isFallbackRetry = false;
@ -837,9 +900,26 @@ export class Player {
this.currentTrack = track;
const trackTitle = getTrackTitle(track);
const artistName = getTrackArtists(track);
const trackArtistsHTML = getTrackArtistsHTML(track);
const yearDisplay = getTrackYearDisplay(track);
if (!track.videoUrl && !track.videoCoverUrl && !track.album?.videoCoverUrl) {
this.api.getVideoArtwork(trackTitle, artistName).then((result) => {
if (this.currentTrack?.id === track.id && result && (result.videoUrl || result.hlsUrl)) {
track.videoCoverUrl = result.videoUrl || result.hlsUrl;
this.updateVideoCovers(track.videoCoverUrl);
if (
UIRenderer.instance &&
document.getElementById('fullscreen-cover-overlay')?.style.display === 'flex'
) {
UIRenderer.instance.updateFullscreenMetadata(track, this.getNextTrack());
}
}
});
}
const trackInfo = document.querySelector('.now-playing-bar .track-info');
const coverEl = trackInfo?.querySelector('.cover:not(#audio-player):not(#video-player)');
@ -904,17 +984,31 @@ export class Player {
} else {
if (coverEl) {
coverEl.style.display = 'block';
const videoCoverUrl = track.videoUrl || track.videoCoverUrl || track.album?.videoCoverUrl || null;
const coverId = track.image || track.cover || track.album?.cover;
const coverUrl = this.api.getCoverUrl(coverId);
const coverSrcset = this.api.getCoverSrcset(coverId);
if (coverEl.getAttribute('src') !== coverUrl) {
coverEl.src = coverUrl;
if (coverSrcset) {
coverEl.setAttribute('srcset', coverSrcset);
coverEl.setAttribute('sizes', '(max-width: 640px) 160px, (max-width: 1024px) 320px, 640px');
} else {
coverEl.removeAttribute('srcset');
coverEl.removeAttribute('sizes');
const coverUrl = videoCoverUrl || this.api.getCoverUrl(coverId);
const coverSrcset = videoCoverUrl ? null : this.api.getCoverSrcset(coverId);
if (videoCoverUrl) {
this.updateVideoCovers(videoCoverUrl);
} else {
let imgEl = coverEl;
if (coverEl.tagName === 'VIDEO') {
imgEl = document.createElement('img');
imgEl.className = coverEl.className;
imgEl.id = coverEl.id;
coverEl.replaceWith(imgEl);
}
if (imgEl.getAttribute('src') !== coverUrl) {
imgEl.src = coverUrl;
if (coverSrcset) {
imgEl.setAttribute('srcset', coverSrcset);
imgEl.setAttribute('sizes', '(max-width: 640px) 160px, (max-width: 1024px) 320px, 640px');
} else {
imgEl.removeAttribute('srcset');
imgEl.removeAttribute('sizes');
}
}
}
}
@ -2090,33 +2184,32 @@ export class Player {
}
updateMediaSession(track) {
const coverId = track.album?.cover;
const trackTitle = getTrackTitle(track);
// Force a refresh for picky Bluetooth systems by clearing metadata first
MediaSession.setMetadata({})
.finally(() =>
MediaSession.setMetadata({
title: trackTitle || 'Unknown Title',
artist: getTrackArtists(track) || 'Unknown Artist',
album: track.album?.title || 'Unknown Album',
artwork: coverId
? [
{
src: this.api.getCoverUrl(coverId, '1280'),
sizes: '1280x1280',
type: 'image/jpeg',
},
]
: undefined,
})
)
.catch(() => {})
.finally(() => {
this.updateMediaSessionPlaybackState();
this.updateMediaSessionPositionState();
});
.finally(() =>
MediaSession.setMetadata({
title: trackTitle || 'Unknown Title',
artist: getTrackArtists(track) || 'Unknown Artist',
album: track.album?.title || 'Unknown Album',
artwork: coverId
? [
{
src: this.api.getCoverUrl(coverId, '1280'),
sizes: '1280x1280',
type: 'image/jpeg',
},
]
: undefined,
})
)
.catch(() => {})
.finally(() => {
this.updateMediaSessionPlaybackState();
this.updateMediaSessionPositionState();
});
}
updateMediaSessionPlaybackState() {
@ -2169,9 +2262,8 @@ export class Player {
duration: duration,
playbackRate: el.playbackRate || 1,
position: Math.min(el.currentTime, duration),
})
.catch((error) => {
console.log('Failed to update Media Session position:', error);
}).catch((error) => {
console.log('Failed to update Media Session position:', error);
});
}

View file

@ -1279,18 +1279,35 @@ export class UIRenderer {
const currentImage = document.getElementById('fullscreen-cover-image');
if (videoCoverUrl) {
const isPaused = this.player?.activeElement?.paused ?? true;
if (currentImage.tagName === 'IMG') {
const video = document.createElement('video');
video.src = videoCoverUrl;
video.autoplay = true;
video.autoplay = !isPaused;
video.loop = true;
video.muted = true;
video.playsInline = true;
video.preload = 'auto';
video.className = currentImage.className;
video.id = currentImage.id;
video.style.objectFit = 'cover';
currentImage.replaceWith(video);
if (!isPaused) {
video.play().catch(() => {});
}
} else if (currentImage.src !== videoCoverUrl) {
currentImage.src = videoCoverUrl;
if (!isPaused) {
currentImage.play().catch(() => {});
} else {
currentImage.pause();
}
} else {
if (!isPaused) {
currentImage.play().catch(() => {});
} else {
currentImage.pause();
}
}
} else {
if (currentImage.tagName === 'VIDEO') {