feat(covers): animated covers
This commit is contained in:
parent
4762dea607
commit
e3f781d588
5 changed files with 370 additions and 15 deletions
3
bun.lock
3
bun.lock
|
|
@ -15,6 +15,7 @@
|
|||
"cookie-session": "^2.1.1",
|
||||
"dashjs": "^5.1.1",
|
||||
"fuse.js": "^7.1.0",
|
||||
"hls.js": "^1.6.15",
|
||||
"jose": "^6.1.3",
|
||||
"npm": "^11.11.0",
|
||||
"pocketbase": "^0.26.8",
|
||||
|
|
@ -843,6 +844,8 @@
|
|||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"hls.js": ["hls.js@1.6.15", "", {}, "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA=="],
|
||||
|
||||
"hookified": ["hookified@1.15.0", "", {}, "sha512-51w+ZZGt7Zw5q7rM3nC4t3aLn/xvKDETsXqMczndvwyVQhAHfUmUuFBRFcos8Iyebtk7OAE9dL26wFNzZVVOkw=="],
|
||||
|
||||
"html-entities": ["html-entities@2.6.0", "", {}, "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ=="],
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export class MusicAPI {
|
|||
this.tidalAPI = new LosslessAPI(settings);
|
||||
this.qobuzAPI = new QobuzAPI();
|
||||
this._settings = settings;
|
||||
this.videoArtworkCache = new Map();
|
||||
}
|
||||
|
||||
getCurrentProvider() {
|
||||
|
|
@ -139,6 +140,29 @@ export class MusicAPI {
|
|||
return this.getCoverUrl(fallbackCoverId, size);
|
||||
}
|
||||
|
||||
async getVideoArtwork(title, artist) {
|
||||
const cacheKey = `${title}-${artist}`.toLowerCase();
|
||||
if (this.videoArtworkCache.has(cacheKey)) {
|
||||
return this.videoArtworkCache.get(cacheKey);
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `https://artwork.boidu.dev/?s=${encodeURIComponent(title)}&a=${encodeURIComponent(artist)}`;
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) return null;
|
||||
const data = await response.json();
|
||||
const result = {
|
||||
videoUrl: data.videoUrl || null,
|
||||
hlsUrl: data.animated || null
|
||||
};
|
||||
this.videoArtworkCache.set(cacheKey, result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.warn('Failed to fetch video artwork:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
getArtistPictureUrl(id, size = '320') {
|
||||
if (typeof id === 'string' && id.startsWith('q:')) {
|
||||
return this.qobuzAPI.getArtistPictureUrl(id.slice(2), size);
|
||||
|
|
|
|||
86
js/player.js
86
js/player.js
|
|
@ -18,6 +18,7 @@ import {
|
|||
audioEffectsSettings,
|
||||
} from './storage.js';
|
||||
import { audioContextManager } from './audio-context.js';
|
||||
import Hls from 'hls.js';
|
||||
|
||||
export class Player {
|
||||
constructor(audioElement, api, quality = 'HI_RES_LOSSLESS') {
|
||||
|
|
@ -402,6 +403,42 @@ export class Player {
|
|||
}
|
||||
}
|
||||
|
||||
setupHlsVideo(video, result, fallbackImg) {
|
||||
const url = result.videoUrl || result.hlsUrl;
|
||||
if (!url) return;
|
||||
|
||||
if (url.endsWith('.m3u8')) {
|
||||
if (Hls.isSupported()) {
|
||||
const hls = new Hls();
|
||||
hls.loadSource(url);
|
||||
hls.attachMedia(video);
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||
video.play().catch(() => {});
|
||||
});
|
||||
hls.on(Hls.Events.ERROR, (event, data) => {
|
||||
if (data.fatal) {
|
||||
console.warn('HLS fatal error:', data.type);
|
||||
video.replaceWith(fallbackImg);
|
||||
hls.destroy();
|
||||
}
|
||||
});
|
||||
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
video.src = url;
|
||||
} else {
|
||||
video.replaceWith(fallbackImg);
|
||||
}
|
||||
} else {
|
||||
video.src = url;
|
||||
video.onerror = () => {
|
||||
if (result.hlsUrl) {
|
||||
this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, fallbackImg);
|
||||
} else {
|
||||
video.replaceWith(fallbackImg);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async playTrackFromQueue(startTime = 0, recursiveCount = 0) {
|
||||
const currentQueue = this.shuffleActive ? this.shuffledQueue : this.queue;
|
||||
if (this.currentQueueIndex < 0 || this.currentQueueIndex >= currentQueue.length) {
|
||||
|
|
@ -433,22 +470,67 @@ export class Player {
|
|||
|
||||
const coverEl = document.querySelector('.now-playing-bar .cover');
|
||||
if (coverEl) {
|
||||
const videoCoverUrl = track.album?.videoCover
|
||||
let videoCoverUrl = track.album?.videoCover
|
||||
? this.api.tidalAPI.getVideoCoverUrl(track.album.videoCover)
|
||||
: null;
|
||||
: track.album?.videoCoverUrl || null;
|
||||
|
||||
if (!videoCoverUrl && track.album) {
|
||||
this.api.getVideoArtwork(track.title, getTrackArtists(track)).then((result) => {
|
||||
if (result && this.currentTrack?.id === track.id) {
|
||||
const url = result.videoUrl || result.hlsUrl;
|
||||
if (!url) return;
|
||||
|
||||
track.album.videoCoverUrl = url;
|
||||
const currentCoverEl = document.querySelector('.now-playing-bar .cover');
|
||||
if (currentCoverEl && currentCoverEl.tagName !== 'VIDEO') {
|
||||
const video = document.createElement('video');
|
||||
video.autoplay = true;
|
||||
video.loop = true;
|
||||
video.muted = true;
|
||||
video.playsInline = true;
|
||||
video.preload = 'auto';
|
||||
video.className = currentCoverEl.className;
|
||||
video.id = currentCoverEl.id;
|
||||
video.style.opacity = '1';
|
||||
video.style.zIndex = '1';
|
||||
video.style.objectFit = 'cover';
|
||||
video.poster = currentCoverEl.src;
|
||||
|
||||
this.setupHlsVideo(video, result, currentCoverEl);
|
||||
currentCoverEl.replaceWith(video);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const coverUrl = videoCoverUrl || this.api.getCoverUrl(track.album?.cover);
|
||||
|
||||
if (videoCoverUrl) {
|
||||
if (coverEl.tagName === 'IMG') {
|
||||
const video = document.createElement('video');
|
||||
video.src = videoCoverUrl;
|
||||
video.poster = this.api.getCoverUrl(track.album?.cover);
|
||||
video.autoplay = true;
|
||||
video.loop = true;
|
||||
video.muted = true;
|
||||
video.playsInline = true;
|
||||
video.preload = 'auto';
|
||||
video.className = coverEl.className;
|
||||
video.id = coverEl.id;
|
||||
video.style.objectFit = 'cover';
|
||||
video.style.gridArea = '1 / 1';
|
||||
video.onerror = () => {
|
||||
const img = document.createElement('img');
|
||||
img.src = this.api.getCoverUrl(track.album?.cover);
|
||||
img.className = video.className;
|
||||
img.id = video.id;
|
||||
video.replaceWith(img);
|
||||
};
|
||||
coverEl.replaceWith(video);
|
||||
} else if (coverEl.tagName === 'VIDEO' && coverEl.src !== videoCoverUrl) {
|
||||
coverEl.src = videoCoverUrl;
|
||||
coverEl.poster = this.api.getCoverUrl(track.album?.cover);
|
||||
coverEl.style.objectFit = 'cover';
|
||||
}
|
||||
} else {
|
||||
if (coverEl.tagName === 'VIDEO') {
|
||||
|
|
|
|||
271
js/ui.js
271
js/ui.js
|
|
@ -49,6 +49,7 @@ import {
|
|||
createTrackFromSong,
|
||||
} from './tracker.js';
|
||||
import { trackSearch, trackChangeSort } from './analytics.js';
|
||||
import Hls from 'hls.js';
|
||||
|
||||
fontSettings.applyFont();
|
||||
fontSettings.applyFontSize();
|
||||
|
|
@ -412,12 +413,13 @@ export class UIRenderer {
|
|||
`;
|
||||
}
|
||||
|
||||
getCoverHTML(videoCover, cover, alt, className = 'card-image', loading = 'lazy') {
|
||||
const videoUrl = videoCover ? this.api.tidalAPI.getVideoCoverUrl(videoCover) : null;
|
||||
getCoverHTML(videoCover, cover, alt, className = 'card-image', loading = 'lazy', videoCoverUrl = null) {
|
||||
const videoUrl = (videoCover ? this.api.tidalAPI.getVideoCoverUrl(videoCover) : null) || videoCoverUrl;
|
||||
const imageUrl = this.api.getCoverUrl(cover);
|
||||
if (videoUrl) {
|
||||
return `<video src="${videoUrl}" class="${className}" alt="${alt}" autoplay loop muted playsinline></video>`;
|
||||
return `<video src="${videoUrl}" poster="${imageUrl}" class="${className}" alt="${alt}" autoplay loop muted playsinline preload="auto" onerror="this.onerror=null; this.outerHTML='<img src="${imageUrl}" class="${className}" alt="${alt}" loading="${loading}">';"></video>`;
|
||||
}
|
||||
return `<img src="${this.api.getCoverUrl(cover)}" class="${className}" alt="${alt}" loading="${loading}">`;
|
||||
return `<img src="${imageUrl}" class="${className}" alt="${alt}" loading="${loading}">`;
|
||||
}
|
||||
|
||||
createBaseCardHTML({
|
||||
|
|
@ -623,7 +625,7 @@ export class UIRenderer {
|
|||
href: `/album/${album.id}`,
|
||||
title: `${escapeHtml(album.title)} ${explicitBadge} ${qualityBadge}`,
|
||||
subtitle: `${escapeHtml(artistName)} • ${yearDisplay}${typeLabel}`,
|
||||
imageHTML: this.getCoverHTML(album.videoCover, album.cover, escapeHtml(album.title)),
|
||||
imageHTML: this.getCoverHTML(album.videoCover, album.cover, escapeHtml(album.title), 'card-image', 'lazy', album.videoCoverUrl),
|
||||
actionButtonsHTML: `
|
||||
<button class="like-btn card-like-btn" data-action="toggle-like" data-type="album" title="Add to Liked">
|
||||
${this.createHeartIcon(false)}
|
||||
|
|
@ -910,7 +912,7 @@ export class UIRenderer {
|
|||
|
||||
const videoCoverUrl = track.album?.videoCover
|
||||
? this.api.tidalAPI.getVideoCoverUrl(track.album.videoCover, '1280')
|
||||
: null;
|
||||
: (track.album?.videoCoverUrl || null);
|
||||
const coverUrl = videoCoverUrl || this.api.getCoverUrl(track.album?.cover, '1280');
|
||||
|
||||
const fsLikeBtn = document.getElementById('fs-like-btn');
|
||||
|
|
@ -1434,6 +1436,7 @@ export class UIRenderer {
|
|||
}
|
||||
|
||||
showPage(pageId) {
|
||||
this.currentPage = pageId;
|
||||
document.querySelectorAll('.page').forEach((page) => {
|
||||
page.classList.toggle('active', page.id === `page-${pageId}`);
|
||||
});
|
||||
|
|
@ -1524,6 +1527,38 @@ export class UIRenderer {
|
|||
const likedPlaylists = await db.getFavorites('playlist');
|
||||
const likedMixes = await db.getFavorites('mix');
|
||||
|
||||
|
||||
if (likedTracks.length > 0) {
|
||||
likedTracks.slice(0, 2).forEach((track) => {
|
||||
if (!track.album?.videoCover && !track.album?.videoCoverUrl) {
|
||||
this.api.getVideoArtwork(track.title, getTrackArtists(track)).then((result) => {
|
||||
if (result && this.currentPage === 'library') {
|
||||
const url = result.videoUrl || result.hlsUrl;
|
||||
if (!url) return;
|
||||
track.album = track.album || {};
|
||||
track.album.videoCoverUrl = url;
|
||||
this.replaceVideoArtwork(tracksContainer, 'track', track.id, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (likedAlbums.length > 0) {
|
||||
likedAlbums.slice(0, 2).forEach((album) => {
|
||||
if (!album.videoCover && !album.videoCoverUrl) {
|
||||
this.api.getVideoArtwork(album.title, typeof album.artist === 'string' ? album.artist : (album.artist?.name || '')).then((result) => {
|
||||
if (result && this.currentPage === 'library') {
|
||||
const url = result.videoUrl || result.hlsUrl;
|
||||
if (!url) return;
|
||||
album.videoCoverUrl = url;
|
||||
this.replaceVideoArtwork(albumsContainer, 'album', album.id, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mixedContent = [];
|
||||
if (likedPlaylists.length) mixedContent.push(...likedPlaylists.map((p) => ({ ...p, _type: 'playlist' })));
|
||||
if (likedMixes.length) mixedContent.push(...likedMixes.map((m) => ({ ...m, _type: 'mix' })));
|
||||
|
|
@ -2388,6 +2423,88 @@ export class UIRenderer {
|
|||
return items.filter((item) => !favoriteIds.has(item.id));
|
||||
}
|
||||
|
||||
setupHlsVideo(video, result, fallbackImg) {
|
||||
const url = result.videoUrl || result.hlsUrl;
|
||||
if (!url) return;
|
||||
|
||||
if (url.endsWith('.m3u8')) {
|
||||
if (Hls.isSupported()) {
|
||||
const hls = new Hls();
|
||||
hls.loadSource(url);
|
||||
hls.attachMedia(video);
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||
video.play().catch(() => {});
|
||||
});
|
||||
hls.on(Hls.Events.ERROR, (event, data) => {
|
||||
if (data.fatal) {
|
||||
console.warn('HLS fatal error:', data.type);
|
||||
video.replaceWith(fallbackImg);
|
||||
hls.destroy();
|
||||
}
|
||||
});
|
||||
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
// i heard safari supports HLS natively
|
||||
video.src = url;
|
||||
} else {
|
||||
video.replaceWith(fallbackImg);
|
||||
}
|
||||
} else {
|
||||
// MP4
|
||||
video.src = url;
|
||||
video.onerror = () => {
|
||||
if (result.hlsUrl) {
|
||||
// HLS fallback (for some reason alot of animated covers js dont work on MP4 lol)
|
||||
this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, fallbackImg);
|
||||
} else {
|
||||
video.replaceWith(fallbackImg);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
replaceVideoArtwork(container, type, id, result) {
|
||||
const url = result.videoUrl || result.hlsUrl;
|
||||
if (!url) return;
|
||||
|
||||
const card = container.querySelector(`[data-${type}-id="${id}"]`);
|
||||
if (!card) return;
|
||||
const img = card.querySelector('.card-image');
|
||||
if (img && img.tagName !== 'VIDEO') {
|
||||
const video = document.createElement('video');
|
||||
video.autoplay = true;
|
||||
video.loop = true;
|
||||
video.muted = true;
|
||||
video.playsInline = true;
|
||||
video.preload = 'auto';
|
||||
video.className = img.className;
|
||||
video.id = img.id;
|
||||
video.style.objectFit = 'cover';
|
||||
|
||||
video.poster = img.src;
|
||||
|
||||
video.onerror = () => {
|
||||
if (video.src === result.videoUrl && result.hlsUrl) {
|
||||
this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, img);
|
||||
return;
|
||||
}
|
||||
video.replaceWith(img);
|
||||
};
|
||||
|
||||
video.addEventListener('error', (e) => {
|
||||
if (video.src === result.videoUrl && result.hlsUrl) {
|
||||
this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, img);
|
||||
return;
|
||||
}
|
||||
console.warn('Video decoding error:', e);
|
||||
video.replaceWith(img);
|
||||
}, true);
|
||||
|
||||
img.replaceWith(video);
|
||||
|
||||
this.setupHlsVideo(video, result, img);
|
||||
}
|
||||
}
|
||||
|
||||
async renderSearchPage(query) {
|
||||
this.showPage('search');
|
||||
document.getElementById('search-results-title').textContent = `Search Results for "${query}"`;
|
||||
|
|
@ -2494,6 +2611,35 @@ export class UIRenderer {
|
|||
this.updateLikeState(el, 'playlist', playlist.uuid);
|
||||
}
|
||||
});
|
||||
|
||||
if (finalTracks.length > 0) {
|
||||
const track = finalTracks[0];
|
||||
if (!track.album?.videoCover && !track.album?.videoCoverUrl) {
|
||||
this.api.getVideoArtwork(track.title, getTrackArtists(track)).then((result) => {
|
||||
if (result && this.currentPage === 'search') {
|
||||
const url = result.videoUrl || result.hlsUrl;
|
||||
if (!url) return;
|
||||
track.album = track.album || {};
|
||||
track.album.videoCoverUrl = url;
|
||||
this.replaceVideoArtwork(tracksContainer, 'track', track.id, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (finalAlbums.length > 0) {
|
||||
const album = finalAlbums[0];
|
||||
if (!album.videoCover && !album.videoCoverUrl) {
|
||||
this.api.getVideoArtwork(album.title, typeof album.artist === 'string' ? album.artist : (album.artist?.name || '')).then((result) => {
|
||||
if (result && this.currentPage === 'search') {
|
||||
const url = result.videoUrl || result.hlsUrl;
|
||||
if (!url) return;
|
||||
album.videoCoverUrl = url;
|
||||
this.replaceVideoArtwork(albumsContainer, 'album', album.id, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.name === 'AbortError') return;
|
||||
console.error('Search failed:', error);
|
||||
|
|
@ -2613,8 +2759,39 @@ export class UIRenderer {
|
|||
|
||||
try {
|
||||
const { album, tracks } = await this.api.getAlbum(albumId, provider);
|
||||
this.currentAlbumId = albumId;
|
||||
|
||||
let videoCoverUrl = album.videoCover
|
||||
? this.api.tidalAPI.getVideoCoverUrl(album.videoCover)
|
||||
: (album.videoCoverUrl || null);
|
||||
|
||||
if (!videoCoverUrl && tracks.length > 0) {
|
||||
const firstTrack = tracks[0];
|
||||
this.api.getVideoArtwork(firstTrack.title, getTrackArtists(firstTrack)).then((result) => {
|
||||
if (result && this.currentPage === 'album' && this.currentAlbumId === albumId) {
|
||||
const url = result.videoUrl || result.hlsUrl;
|
||||
if (!url) return;
|
||||
album.videoCoverUrl = url;
|
||||
const currentImageEl = document.getElementById('album-detail-image');
|
||||
if (currentImageEl && currentImageEl.tagName !== 'VIDEO') {
|
||||
const video = document.createElement('video');
|
||||
video.autoplay = true;
|
||||
video.loop = true;
|
||||
video.muted = true;
|
||||
video.playsInline = true;
|
||||
video.preload = 'auto';
|
||||
video.className = currentImageEl.className;
|
||||
video.id = currentImageEl.id;
|
||||
video.style.objectFit = 'cover';
|
||||
video.poster = currentImageEl.src;
|
||||
|
||||
this.setupHlsVideo(video, result, currentImageEl);
|
||||
currentImageEl.replaceWith(video);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const videoCoverUrl = album.videoCover ? this.api.tidalAPI.getVideoCoverUrl(album.videoCover) : null;
|
||||
const coverUrl = videoCoverUrl || this.api.getCoverUrl(album.cover);
|
||||
|
||||
if (videoCoverUrl) {
|
||||
|
|
@ -3364,6 +3541,7 @@ export class UIRenderer {
|
|||
|
||||
try {
|
||||
const { mix, tracks } = await this.api.getMix(mixId, provider);
|
||||
this.currentMixId = mixId;
|
||||
|
||||
if (mix.cover) {
|
||||
imageEl.src = mix.cover;
|
||||
|
|
@ -3372,10 +3550,47 @@ export class UIRenderer {
|
|||
} else {
|
||||
// Try to get cover from first track album
|
||||
if (tracks.length > 0 && tracks[0].album?.cover) {
|
||||
const videoCoverUrl = tracks[0].album?.videoCover
|
||||
? this.api.tidalAPI.getVideoCoverUrl(tracks[0].album.videoCover)
|
||||
: null;
|
||||
const coverUrl = videoCoverUrl || this.api.getCoverUrl(tracks[0].album.cover);
|
||||
const firstTrack = tracks[0];
|
||||
let videoCoverUrl = firstTrack.album?.videoCover
|
||||
? this.api.tidalAPI.getVideoCoverUrl(firstTrack.album.videoCover)
|
||||
: (firstTrack.album?.videoCoverUrl || null);
|
||||
|
||||
if (!videoCoverUrl && firstTrack.album) {
|
||||
this.api.getVideoArtwork(firstTrack.title, getTrackArtists(firstTrack)).then((result) => {
|
||||
if (result && this.currentPage === 'mix' && this.currentMixId === mixId) {
|
||||
const url = result.videoUrl || result.hlsUrl;
|
||||
if (!url) return;
|
||||
firstTrack.album.videoCoverUrl = url;
|
||||
const currentImageEl = document.getElementById('mix-detail-image');
|
||||
if (currentImageEl && currentImageEl.tagName !== 'VIDEO') {
|
||||
const video = document.createElement('video');
|
||||
video.autoplay = true;
|
||||
video.loop = true;
|
||||
video.muted = true;
|
||||
video.playsInline = true;
|
||||
video.preload = 'auto';
|
||||
video.className = currentImageEl.className;
|
||||
video.id = currentImageEl.id;
|
||||
video.style.opacity = '0';
|
||||
video.style.transition = 'opacity 0.3s ease-in-out';
|
||||
|
||||
video.oncanplay = () => {
|
||||
video.style.opacity = '1';
|
||||
setTimeout(() => {
|
||||
if (currentImageEl.parentNode) {
|
||||
currentImageEl.style.display = 'none';
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
|
||||
this.setupHlsVideo(video, result, currentImageEl);
|
||||
currentImageEl.parentNode.insertBefore(video, currentImageEl);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const coverUrl = videoCoverUrl || this.api.getCoverUrl(firstTrack.album.cover);
|
||||
|
||||
if (videoCoverUrl) {
|
||||
if (imageEl.tagName === 'IMG') {
|
||||
|
|
@ -4416,10 +4631,40 @@ export class UIRenderer {
|
|||
console.warn('getTrack failed, trying getTrackMetadata', e);
|
||||
track = await this.api.getTrackMetadata(trackId, provider);
|
||||
}
|
||||
this.currentTrackPageId = track.id;
|
||||
|
||||
const videoCoverUrl = track.album?.videoCover
|
||||
let videoCoverUrl = track.album?.videoCover
|
||||
? this.api.tidalAPI.getVideoCoverUrl(track.album.videoCover)
|
||||
: null;
|
||||
: (track.album?.videoCoverUrl || null);
|
||||
|
||||
if (!videoCoverUrl && track.album) {
|
||||
this.api.getVideoArtwork(track.title, getTrackArtists(track)).then((result) => {
|
||||
if (result && this.currentPage === 'track' && this.currentTrackPageId === track.id) {
|
||||
const url = result.videoUrl || result.hlsUrl;
|
||||
if (!url) return;
|
||||
track.album.videoCoverUrl = url;
|
||||
const currentImageEl = document.getElementById('track-detail-image');
|
||||
if (currentImageEl && currentImageEl.tagName !== 'VIDEO') {
|
||||
const video = document.createElement('video');
|
||||
video.autoplay = true;
|
||||
video.loop = true;
|
||||
video.muted = true;
|
||||
video.playsInline = true;
|
||||
video.preload = 'auto';
|
||||
video.className = currentImageEl.className;
|
||||
video.id = currentImageEl.id;
|
||||
video.style.opacity = '0';
|
||||
video.style.transition = 'opacity 0.3s ease-in-out';
|
||||
|
||||
video.poster = currentImageEl.src;
|
||||
|
||||
this.setupHlsVideo(video, result, currentImageEl);
|
||||
currentImageEl.replaceWith(video);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const coverUrl = videoCoverUrl || this.api.getCoverUrl(track.album?.cover);
|
||||
|
||||
if (videoCoverUrl) {
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
"cookie-session": "^2.1.1",
|
||||
"dashjs": "^5.1.1",
|
||||
"fuse.js": "^7.1.0",
|
||||
"hls.js": "^1.6.15",
|
||||
"jose": "^6.1.3",
|
||||
"npm": "^11.11.0",
|
||||
"pocketbase": "^0.26.8"
|
||||
|
|
|
|||
Loading…
Reference in a new issue