bring back animated album covers + improvements
This commit is contained in:
parent
5f8cb6e947
commit
4497863667
4 changed files with 147 additions and 44 deletions
4
bun.lock
4
bun.lock
|
|
@ -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=="],
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
162
js/player.js
162
js/player.js
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
19
js/ui.js
19
js/ui.js
|
|
@ -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') {
|
||||
|
|
|
|||
Loading…
Reference in a new issue