IMP: mixes using new API

This commit is contained in:
Julien Maille 2026-01-04 18:48:49 +01:00
parent 4fc36f63e4
commit 6ee3c57bc5
4 changed files with 29 additions and 56 deletions

View file

@ -487,27 +487,23 @@ export class LosslessAPI {
const response = await this.fetchWithRetry(`/mix/?id=${id}`, { type: 'api' }); const response = await this.fetchWithRetry(`/mix/?id=${id}`, { type: 'api' });
const data = await response.json(); const data = await response.json();
let mix = null; const mixData = data.mix;
let tracks = []; const items = data.items || [];
// Mix response structure might vary, handle likely cases if (!mixData) {
const items = data.items || data.tracks || (Array.isArray(data) ? data : []); throw new Error('Mix metadata not found');
// If data has mix info, use it. Otherwise, fabricate one or look for it.
if (data.mix) {
mix = data.mix;
} else if (!Array.isArray(data) && data.id) {
mix = data;
} }
if (!mix) { const tracks = items.map(i => this.prepareTrack(i.item || i));
// Basic placeholder if mix metadata isn't explicitly separated
mix = { id, title: 'Mix', description: 'Generated Mix' };
}
if (items.length > 0) { const mix = {
tracks = items.map(i => this.prepareTrack(i.item || i)); id: mixData.id,
} title: mixData.title,
subTitle: mixData.subTitle,
description: mixData.description,
mixType: mixData.mixType,
cover: mixData.images?.LARGE?.url || mixData.images?.MEDIUM?.url || mixData.images?.SMALL?.url || null
};
const result = { mix, tracks }; const result = { mix, tracks };
await this.cache.set('mix', id, result); await this.cache.set('mix', id, result);

View file

@ -329,9 +329,8 @@ document.addEventListener('DOMContentLoaded', async () => {
const btn = e.target.closest('#download-mix-btn'); const btn = e.target.closest('#download-mix-btn');
if (btn.disabled) return; if (btn.disabled) return;
const param = window.location.hash.split('#mix/')[1]; const mixId = window.location.hash.split('#mix/')[1];
if (!param) return; if (!mixId) return;
const [mixId] = param.split('?');
btn.disabled = true; btn.disabled = true;
const originalHTML = btn.innerHTML; const originalHTML = btn.innerHTML;

View file

@ -361,7 +361,7 @@ export async function handleTrackAction(action, item, player, api, lyricsManager
showNotification(`Playing next: ${item.title}`); showNotification(`Playing next: ${item.title}`);
} else if (action === 'track-mix') { } else if (action === 'track-mix') {
if (item.mixes && item.mixes.TRACK_MIX) { if (item.mixes && item.mixes.TRACK_MIX) {
window.location.hash = `#mix/${item.mixes.TRACK_MIX}?type=track&name=${encodeURIComponent(item.title)}`; window.location.hash = `#mix/${item.mixes.TRACK_MIX}`;
} }
} else if (action === 'play-card') { } else if (action === 'play-card') {
try { try {

View file

@ -933,7 +933,7 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) {
const mixBtn = document.getElementById('album-mix-btn'); const mixBtn = document.getElementById('album-mix-btn');
if (mixBtn && artistData.mixes && artistData.mixes.ARTIST_MIX) { if (mixBtn && artistData.mixes && artistData.mixes.ARTIST_MIX) {
mixBtn.style.display = 'flex'; mixBtn.style.display = 'flex';
mixBtn.onclick = () => window.location.hash = `#mix/${artistData.mixes.ARTIST_MIX}?type=artist&name=${encodeURIComponent(artistData.name)}`; mixBtn.onclick = () => window.location.hash = `#mix/${artistData.mixes.ARTIST_MIX}`;
} }
const renderSection = (items, container, section, titleEl, titleText) => { const renderSection = (items, container, section, titleEl, titleText) => {
@ -1171,12 +1171,8 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) {
} }
} }
async renderMixPage(param) { async renderMixPage(mixId) {
this.showPage('mix'); this.showPage('mix');
const [mixId, query] = param.split('?');
const urlParams = new URLSearchParams(query);
const type = urlParams.get('type');
const name = urlParams.get('name');
const imageEl = document.getElementById('mix-detail-image'); const imageEl = document.getElementById('mix-detail-image');
const titleEl = document.getElementById('mix-detail-title'); const titleEl = document.getElementById('mix-detail-title');
@ -1206,16 +1202,10 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) {
try { try {
const { mix, tracks } = await this.api.getMix(mixId); const { mix, tracks } = await this.api.getMix(mixId);
// Mixes usually have covers from Tidal resources, similar to playlists if (mix.cover) {
const imageId = mix.images?.medium?.source || mix.image || mix.id; imageEl.src = mix.cover;
// Fallback for cover: if mix.id matches a pattern or we can just try generic mix cover this.setPageBackground(mix.cover);
// Often mix ID isn't directly an image ID. this.extractAndApplyColor(mix.cover);
// If API returns explicit image URL/ID use it.
// For now assume standard playlist-like cover or placeholder.
if (imageId && imageId !== mix.id) {
imageEl.src = this.api.getCoverUrl(imageId);
this.setPageBackground(imageEl.src);
this.extractAndApplyColor(this.api.getCoverUrl(imageId, '160'));
} else { } else {
// Try to get cover from first track album // Try to get cover from first track album
if (tracks.length > 0 && tracks[0].album?.cover) { if (tracks.length > 0 && tracks[0].album?.cover) {
@ -1231,26 +1221,14 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) {
imageEl.style.backgroundColor = ''; imageEl.style.backgroundColor = '';
let displayTitle; // Use title and subtitle from API directly
if (type === 'artist' && name) { const displayTitle = mix.title || 'Mix';
const decodedName = decodeURIComponent(name); titleEl.textContent = displayTitle;
titleEl.innerHTML = `<span style="color: var(--muted-foreground)">Mix for artist</span> ${decodedName}`; this.adjustTitleFontSize(titleEl, displayTitle);
this.adjustTitleFontSize(titleEl, `Mix for artist ${decodedName}`);
} else if (type === 'track' && name) {
const decodedName = decodeURIComponent(name);
titleEl.innerHTML = `<span style="color: var(--muted-foreground)">Mix for track</span> ${decodedName}`;
this.adjustTitleFontSize(titleEl, `Mix for track ${decodedName}`);
} else {
const firstTrackArtist = tracks.length > 0 ? tracks[0].artist?.name : '';
displayTitle = mix.title || (firstTrackArtist ? `${firstTrackArtist} Mix` : 'Mix');
titleEl.textContent = displayTitle;
this.adjustTitleFontSize(titleEl, displayTitle);
}
const totalDuration = calculateTotalDuration(tracks); const totalDuration = calculateTotalDuration(tracks);
metaEl.textContent = `${tracks.length} tracks • ${formatDuration(totalDuration)}`; metaEl.textContent = `${tracks.length} tracks • ${formatDuration(totalDuration)}`;
descEl.textContent = mix.subTitle || mix.description || ''; descEl.innerHTML = `${mix.subTitle}`;
tracklistContainer.innerHTML = ` tracklistContainer.innerHTML = `
<div class="track-list-header"> <div class="track-list-header">
@ -1309,7 +1287,7 @@ async showFullscreenCover(track, nextTrack, lyricsManager, audioPlayer) {
if (mixBtn) { if (mixBtn) {
if (artist.mixes && artist.mixes.ARTIST_MIX) { if (artist.mixes && artist.mixes.ARTIST_MIX) {
mixBtn.style.display = 'flex'; mixBtn.style.display = 'flex';
mixBtn.onclick = () => window.location.hash = `#mix/${artist.mixes.ARTIST_MIX}?type=artist&name=${encodeURIComponent(artist.name)}`; mixBtn.onclick = () => window.location.hash = `#mix/${artist.mixes.ARTIST_MIX}`;
} else { } else {
mixBtn.style.display = 'none'; mixBtn.style.display = 'none';
} }