discover progress
This commit is contained in:
parent
f75ffdf6cb
commit
86fe7dde1f
5 changed files with 206 additions and 64 deletions
|
|
@ -29,32 +29,13 @@ class PersonalizedPlaylistsService:
|
||||||
Get recently added tracks from library.
|
Get recently added tracks from library.
|
||||||
|
|
||||||
Returns tracks ordered by date_added DESC
|
Returns tracks ordered by date_added DESC
|
||||||
|
|
||||||
|
NOTE: This requires library tracks to have Spotify metadata which may not be available.
|
||||||
|
Returns empty list if schema incompatible.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
with self.database._get_connection() as conn:
|
logger.warning("Recently Added requires Spotify-linked library tracks - returning empty")
|
||||||
cursor = conn.cursor()
|
return []
|
||||||
|
|
||||||
cursor.execute("""
|
|
||||||
SELECT
|
|
||||||
t.id,
|
|
||||||
t.spotify_track_id,
|
|
||||||
t.title as track_name,
|
|
||||||
t.duration_ms,
|
|
||||||
ar.name as artist_name,
|
|
||||||
al.title as album_name,
|
|
||||||
al.cover_url as album_cover_url,
|
|
||||||
t.popularity,
|
|
||||||
t.date_added
|
|
||||||
FROM tracks t
|
|
||||||
LEFT JOIN artists ar ON t.artist_id = ar.id
|
|
||||||
LEFT JOIN albums al ON t.album_id = al.id
|
|
||||||
WHERE t.spotify_track_id IS NOT NULL
|
|
||||||
ORDER BY t.date_added DESC
|
|
||||||
LIMIT ?
|
|
||||||
""", (limit,))
|
|
||||||
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
return [dict(row) for row in rows]
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting recently added tracks: {e}")
|
logger.error(f"Error getting recently added tracks: {e}")
|
||||||
|
|
@ -268,11 +249,12 @@ class PersonalizedPlaylistsService:
|
||||||
# ========================================
|
# ========================================
|
||||||
|
|
||||||
def get_popular_picks(self, limit: int = 50) -> List[Dict]:
|
def get_popular_picks(self, limit: int = 50) -> List[Dict]:
|
||||||
"""Get high popularity tracks from discovery pool"""
|
"""Get high popularity tracks from discovery pool with diversity (max 2 tracks per album/artist)"""
|
||||||
try:
|
try:
|
||||||
with self.database._get_connection() as conn:
|
with self.database._get_connection() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Get more tracks than needed to allow for filtering
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
SELECT
|
SELECT
|
||||||
spotify_track_id,
|
spotify_track_id,
|
||||||
|
|
@ -286,10 +268,35 @@ class PersonalizedPlaylistsService:
|
||||||
WHERE popularity >= 60
|
WHERE popularity >= 60
|
||||||
ORDER BY popularity DESC, RANDOM()
|
ORDER BY popularity DESC, RANDOM()
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
""", (limit,))
|
""", (limit * 3,)) # Get 3x more for diversity filtering
|
||||||
|
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
return [dict(row) for row in rows]
|
all_tracks = [dict(row) for row in rows]
|
||||||
|
|
||||||
|
# Apply diversity constraint: max 2 tracks per album, max 3 per artist
|
||||||
|
tracks_by_album = {}
|
||||||
|
tracks_by_artist = {}
|
||||||
|
diverse_tracks = []
|
||||||
|
|
||||||
|
for track in all_tracks:
|
||||||
|
album = track['album_name']
|
||||||
|
artist = track['artist_name']
|
||||||
|
|
||||||
|
# Count current tracks for this album/artist
|
||||||
|
album_count = tracks_by_album.get(album, 0)
|
||||||
|
artist_count = tracks_by_artist.get(artist, 0)
|
||||||
|
|
||||||
|
# Apply limits: max 2 per album, max 3 per artist
|
||||||
|
if album_count < 2 and artist_count < 3:
|
||||||
|
diverse_tracks.append(track)
|
||||||
|
tracks_by_album[album] = album_count + 1
|
||||||
|
tracks_by_artist[artist] = artist_count + 1
|
||||||
|
|
||||||
|
if len(diverse_tracks) >= limit:
|
||||||
|
break
|
||||||
|
|
||||||
|
logger.info(f"Popular Picks: Selected {len(diverse_tracks)} tracks with diversity")
|
||||||
|
return diverse_tracks[:limit]
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting popular picks: {e}")
|
logger.error(f"Error getting popular picks: {e}")
|
||||||
|
|
@ -660,20 +667,36 @@ class PersonalizedPlaylistsService:
|
||||||
|
|
||||||
logger.info(f"Building custom playlist from {len(seed_artist_ids)} seed artists")
|
logger.info(f"Building custom playlist from {len(seed_artist_ids)} seed artists")
|
||||||
|
|
||||||
# Step 1: Get similar artists for each seed
|
# Step 1: Get similar artists for each seed from database
|
||||||
all_similar_artists = []
|
all_similar_artists = []
|
||||||
seen_artist_ids = set(seed_artist_ids) # Don't include seed artists themselves
|
seen_artist_ids = set(seed_artist_ids) # Don't include seed artists themselves
|
||||||
|
|
||||||
for seed_artist_id in seed_artist_ids:
|
for seed_artist_id in seed_artist_ids:
|
||||||
try:
|
try:
|
||||||
# Get similar artists from Spotify
|
# Get similar artists from database (cached from MusicMap)
|
||||||
similar = self.spotify_client.get_similar_artists(seed_artist_id)
|
with self.database._get_connection() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT similar_artist_spotify_id, similar_artist_name
|
||||||
|
FROM similar_artists
|
||||||
|
WHERE source_artist_id = ?
|
||||||
|
ORDER BY similarity_rank ASC
|
||||||
|
LIMIT 10
|
||||||
|
""", (seed_artist_id,))
|
||||||
|
|
||||||
if similar:
|
rows = cursor.fetchall()
|
||||||
for artist in similar[:10]: # Max 10 per seed
|
|
||||||
if artist.id not in seen_artist_ids:
|
for row in rows:
|
||||||
all_similar_artists.append(artist)
|
artist_id = row['similar_artist_spotify_id']
|
||||||
seen_artist_ids.add(artist.id)
|
artist_name = row['similar_artist_name']
|
||||||
|
|
||||||
|
if artist_id not in seen_artist_ids:
|
||||||
|
# Create artist-like object
|
||||||
|
all_similar_artists.append({
|
||||||
|
'id': artist_id,
|
||||||
|
'name': artist_name
|
||||||
|
})
|
||||||
|
seen_artist_ids.add(artist_id)
|
||||||
|
|
||||||
if len(all_similar_artists) >= 25:
|
if len(all_similar_artists) >= 25:
|
||||||
break
|
break
|
||||||
|
|
@ -685,7 +708,7 @@ class PersonalizedPlaylistsService:
|
||||||
logger.warning(f"Error getting similar artists for {seed_artist_id}: {e}")
|
logger.warning(f"Error getting similar artists for {seed_artist_id}: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logger.info(f"Found {len(all_similar_artists)} similar artists")
|
logger.info(f"Found {len(all_similar_artists)} similar artists from database")
|
||||||
|
|
||||||
if not all_similar_artists:
|
if not all_similar_artists:
|
||||||
return {'tracks': [], 'error': 'No similar artists found'}
|
return {'tracks': [], 'error': 'No similar artists found'}
|
||||||
|
|
@ -698,7 +721,7 @@ class PersonalizedPlaylistsService:
|
||||||
for artist in similar_artists_to_use:
|
for artist in similar_artists_to_use:
|
||||||
try:
|
try:
|
||||||
albums = self.spotify_client.get_artist_albums(
|
albums = self.spotify_client.get_artist_albums(
|
||||||
artist.id,
|
artist['id'],
|
||||||
album_type='album,single',
|
album_type='album,single',
|
||||||
limit=10
|
limit=10
|
||||||
)
|
)
|
||||||
|
|
@ -710,7 +733,7 @@ class PersonalizedPlaylistsService:
|
||||||
time.sleep(0.3) # Rate limiting
|
time.sleep(0.3) # Rate limiting
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error getting albums for {artist.name}: {e}")
|
logger.warning(f"Error getting albums for {artist['name']}: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logger.info(f"Found {len(all_albums)} total albums")
|
logger.info(f"Found {len(all_albums)} total albums")
|
||||||
|
|
@ -768,8 +791,11 @@ class PersonalizedPlaylistsService:
|
||||||
'description': f'Built from {len(seed_artist_ids)} seed artists',
|
'description': f'Built from {len(seed_artist_ids)} seed artists',
|
||||||
'track_count': len(final_tracks),
|
'track_count': len(final_tracks),
|
||||||
'tracks': final_tracks,
|
'tracks': final_tracks,
|
||||||
'similar_artists_count': len(similar_artists_to_use),
|
'metadata': {
|
||||||
'albums_used': len(selected_albums)
|
'total_tracks': len(final_tracks),
|
||||||
|
'similar_artists_count': len(similar_artists_to_use),
|
||||||
|
'albums_count': len(selected_albums)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -624,13 +624,21 @@ class SeasonalDiscoveryService:
|
||||||
for track in album_data['tracks'].get('items', []):
|
for track in album_data['tracks'].get('items', []):
|
||||||
# Use track's actual artist, not album artist
|
# Use track's actual artist, not album artist
|
||||||
track_artist = track['artists'][0]['name'] if track.get('artists') else album['artist_name']
|
track_artist = track['artists'][0]['name'] if track.get('artists') else album['artist_name']
|
||||||
all_tracks.append({
|
|
||||||
|
track_data = {
|
||||||
'spotify_track_id': track['id'],
|
'spotify_track_id': track['id'],
|
||||||
'track_name': track['name'],
|
'track_name': track['name'],
|
||||||
'artist_name': track_artist,
|
'artist_name': track_artist,
|
||||||
'album_name': album['album_name'],
|
'album_name': album['album_name'],
|
||||||
'popularity': album.get('popularity', 50)
|
'popularity': album.get('popularity', 50),
|
||||||
})
|
'album_cover_url': album.get('album_cover_url'),
|
||||||
|
'duration_ms': track.get('duration_ms', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
all_tracks.append(track_data)
|
||||||
|
|
||||||
|
# Also save track to seasonal_tracks table for later retrieval
|
||||||
|
self._add_seasonal_track(season_key, track_data)
|
||||||
|
|
||||||
import time
|
import time
|
||||||
time.sleep(0.3) # Rate limiting
|
time.sleep(0.3) # Rate limiting
|
||||||
|
|
|
||||||
|
|
@ -871,8 +871,8 @@ class WatchlistScanner:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
SELECT DISTINCT a.title, ar.name as artist_name
|
SELECT DISTINCT a.title, ar.name as artist_name
|
||||||
FROM albums_new a
|
FROM albums a
|
||||||
JOIN artists_new ar ON a.artist_id = ar.id
|
JOIN artists ar ON a.artist_id = ar.id
|
||||||
ORDER BY RANDOM()
|
ORDER BY RANDOM()
|
||||||
LIMIT 5
|
LIMIT 5
|
||||||
""")
|
""")
|
||||||
|
|
@ -1301,12 +1301,16 @@ class WatchlistScanner:
|
||||||
balanced_tracks.append(track['id'])
|
balanced_tracks.append(track['id'])
|
||||||
balanced_track_data.append(track)
|
balanced_track_data.append(track)
|
||||||
|
|
||||||
# IMPROVED: Sort by score first, then shuffle within score tiers for variety
|
# IMPROVED: Sort by score first, then shuffle for variety
|
||||||
balanced_track_data.sort(key=lambda t: t['score'], reverse=True)
|
balanced_track_data.sort(key=lambda t: t['score'], reverse=True)
|
||||||
|
|
||||||
# Take top 50
|
# Take top 75, then shuffle for final randomization (prevents album grouping)
|
||||||
release_radar_tracks = [track['id'] for track in balanced_track_data[:50]]
|
top_tracks = balanced_track_data[:75]
|
||||||
release_radar_track_data = balanced_track_data[:50]
|
random.shuffle(top_tracks)
|
||||||
|
|
||||||
|
# Take final 50 tracks
|
||||||
|
release_radar_tracks = [track['id'] for track in top_tracks[:50]]
|
||||||
|
release_radar_track_data = top_tracks[:50]
|
||||||
|
|
||||||
# Add Release Radar tracks to discovery pool so they're available for fast lookup
|
# Add Release Radar tracks to discovery pool so they're available for fast lookup
|
||||||
logger.info(f"Adding {len(release_radar_track_data)} Release Radar tracks to discovery pool...")
|
logger.info(f"Adding {len(release_radar_track_data)} Release Radar tracks to discovery pool...")
|
||||||
|
|
|
||||||
|
|
@ -2024,8 +2024,32 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Generated Playlist Results -->
|
<!-- Generated Playlist Results -->
|
||||||
<div id="build-playlist-results" class="discover-playlist-container compact">
|
<div id="build-playlist-results-wrapper" style="display: none;">
|
||||||
<!-- Generated playlist will appear here -->
|
<!-- Playlist Header with Actions -->
|
||||||
|
<div class="discover-section-header" style="margin-top: 20px;">
|
||||||
|
<div>
|
||||||
|
<h3 id="build-playlist-results-title" style="margin: 0; color: #fff; font-size: 18px;">Generated Playlist</h3>
|
||||||
|
<p id="build-playlist-results-subtitle" style="margin: 4px 0 0 0; color: #999; font-size: 13px;"></p>
|
||||||
|
</div>
|
||||||
|
<div class="discover-section-actions">
|
||||||
|
<button class="action-button secondary" onclick="openDownloadModalForBuildPlaylist()" title="Download missing tracks">
|
||||||
|
<span class="button-icon">↓</span>
|
||||||
|
<span class="button-text">Download</span>
|
||||||
|
</button>
|
||||||
|
<button class="action-button primary" id="build-playlist-sync-btn" onclick="startDiscoverPlaylistSync('build_playlist', 'Custom Playlist')" title="Sync to media server">
|
||||||
|
<span class="button-icon">⟳</span>
|
||||||
|
<span class="button-text">Sync</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Metadata Display -->
|
||||||
|
<div id="build-playlist-metadata-display"></div>
|
||||||
|
|
||||||
|
<!-- Track List -->
|
||||||
|
<div id="build-playlist-results" class="discover-playlist-container compact">
|
||||||
|
<!-- Generated playlist will appear here -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -25507,15 +25507,62 @@ function hideSeasonalSections() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openDownloadModalForSeasonalAlbum(index) {
|
async function openDownloadModalForSeasonalAlbum(albumIndex) {
|
||||||
const album = discoverSeasonalAlbums[index];
|
const album = discoverSeasonalAlbums[albumIndex];
|
||||||
if (!album) return;
|
if (!album) {
|
||||||
|
showToast('Album data not found', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch album tracks
|
console.log(`📥 Opening Download Missing Tracks modal for album: ${album.album_name}`);
|
||||||
const albumDetails = await fetchAlbumTracks(album.spotify_album_id);
|
showLoadingOverlay(`Loading tracks for ${album.album_name}...`);
|
||||||
if (!albumDetails) return;
|
|
||||||
|
|
||||||
openDownloadMissingModal(albumDetails.tracks, albumDetails.name);
|
try {
|
||||||
|
// Fetch album tracks from Spotify API via backend
|
||||||
|
const response = await fetch(`/api/spotify/album/${album.spotify_album_id}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch album tracks');
|
||||||
|
}
|
||||||
|
|
||||||
|
const albumData = await response.json();
|
||||||
|
if (!albumData.tracks || albumData.tracks.length === 0) {
|
||||||
|
throw new Error('No tracks found in album');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to expected format
|
||||||
|
const spotifyTracks = albumData.tracks.map(track => {
|
||||||
|
// Normalize artists to array of strings
|
||||||
|
let artists = track.artists || [{ name: album.artist_name }];
|
||||||
|
if (Array.isArray(artists)) {
|
||||||
|
artists = artists.map(a => a.name || a);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: track.id,
|
||||||
|
name: track.name,
|
||||||
|
artists: artists,
|
||||||
|
album: {
|
||||||
|
name: album.album_name,
|
||||||
|
images: album.album_cover_url ? [{ url: album.album_cover_url }] : []
|
||||||
|
},
|
||||||
|
duration_ms: track.duration_ms || 0
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create virtual playlist ID
|
||||||
|
const virtualPlaylistId = `seasonal_album_${album.spotify_album_id}`;
|
||||||
|
const playlistName = `${album.album_name} - ${album.artist_name}`;
|
||||||
|
|
||||||
|
// Open download modal (same as Recent Releases)
|
||||||
|
await openDownloadMissingModalForYouTube(virtualPlaylistId, playlistName, spotifyTracks);
|
||||||
|
|
||||||
|
hideLoadingOverlay();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error loading seasonal album: ${error.message}`);
|
||||||
|
hideLoadingOverlay();
|
||||||
|
showToast(`Failed to load album tracks: ${error.message}`, 'error');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openDownloadModalForSeasonalPlaylist() {
|
async function openDownloadModalForSeasonalPlaylist() {
|
||||||
|
|
@ -25915,6 +25962,8 @@ function renderBuildPlaylistSelectedArtists() {
|
||||||
generateBtn.style.opacity = '1';
|
generateBtn.style.opacity = '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let buildPlaylistTracks = [];
|
||||||
|
|
||||||
async function generateBuildPlaylist() {
|
async function generateBuildPlaylist() {
|
||||||
if (buildPlaylistSelectedArtists.length === 0) {
|
if (buildPlaylistSelectedArtists.length === 0) {
|
||||||
alert('Please select at least 1 artist');
|
alert('Please select at least 1 artist');
|
||||||
|
|
@ -25923,12 +25972,17 @@ async function generateBuildPlaylist() {
|
||||||
|
|
||||||
const generateBtn = document.getElementById('build-playlist-generate-btn');
|
const generateBtn = document.getElementById('build-playlist-generate-btn');
|
||||||
const resultsContainer = document.getElementById('build-playlist-results');
|
const resultsContainer = document.getElementById('build-playlist-results');
|
||||||
|
const resultsWrapper = document.getElementById('build-playlist-results-wrapper');
|
||||||
const loadingIndicator = document.getElementById('build-playlist-loading');
|
const loadingIndicator = document.getElementById('build-playlist-loading');
|
||||||
|
const metadataDisplay = document.getElementById('build-playlist-metadata-display');
|
||||||
|
const titleEl = document.getElementById('build-playlist-results-title');
|
||||||
|
const subtitleEl = document.getElementById('build-playlist-results-subtitle');
|
||||||
|
|
||||||
// Show loading
|
// Show loading
|
||||||
generateBtn.disabled = true;
|
generateBtn.disabled = true;
|
||||||
generateBtn.style.opacity = '0.5';
|
generateBtn.style.opacity = '0.5';
|
||||||
loadingIndicator.style.display = 'block';
|
loadingIndicator.style.display = 'flex';
|
||||||
|
resultsWrapper.style.display = 'none';
|
||||||
resultsContainer.innerHTML = '';
|
resultsContainer.innerHTML = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -25951,19 +26005,29 @@ async function generateBuildPlaylist() {
|
||||||
throw new Error('Invalid playlist data');
|
throw new Error('Invalid playlist data');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render playlist
|
// Store tracks globally
|
||||||
renderCompactPlaylist(resultsContainer, data.playlist.tracks);
|
buildPlaylistTracks = data.playlist.tracks;
|
||||||
|
|
||||||
// Show metadata
|
// Update title and subtitle
|
||||||
|
const artistNames = buildPlaylistSelectedArtists.map(a => a.name).join(', ');
|
||||||
|
titleEl.textContent = 'Custom Playlist';
|
||||||
|
subtitleEl.textContent = `Based on: ${artistNames}`;
|
||||||
|
|
||||||
|
// Render metadata
|
||||||
const metadata = data.playlist.metadata;
|
const metadata = data.playlist.metadata;
|
||||||
const metadataHtml = `
|
metadataDisplay.innerHTML = `
|
||||||
<div class="build-playlist-metadata">
|
<div class="build-playlist-metadata">
|
||||||
<p><strong>Total Tracks:</strong> ${metadata.total_tracks}</p>
|
<p><strong>Total Tracks:</strong> ${metadata.total_tracks}</p>
|
||||||
<p><strong>Similar Artists Used:</strong> ${metadata.similar_artists_count}</p>
|
<p><strong>Similar Artists Used:</strong> ${metadata.similar_artists_count}</p>
|
||||||
<p><strong>Albums Sampled:</strong> ${metadata.albums_count}</p>
|
<p><strong>Albums Sampled:</strong> ${metadata.albums_count}</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
resultsContainer.insertAdjacentHTML('beforebegin', metadataHtml);
|
|
||||||
|
// Render playlist
|
||||||
|
renderCompactPlaylist(resultsContainer, data.playlist.tracks);
|
||||||
|
|
||||||
|
// Show results wrapper
|
||||||
|
resultsWrapper.style.display = 'block';
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error generating playlist:', error);
|
console.error('Error generating playlist:', error);
|
||||||
|
|
@ -25975,6 +26039,20 @@ async function generateBuildPlaylist() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openDownloadModalForBuildPlaylist() {
|
||||||
|
if (!buildPlaylistTracks || buildPlaylistTracks.length === 0) {
|
||||||
|
showToast('No playlist tracks available', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const artistNames = buildPlaylistSelectedArtists.map(a => a.name).join(', ');
|
||||||
|
const playlistName = `Custom Playlist - ${artistNames}`;
|
||||||
|
const virtualPlaylistId = 'build_playlist_custom';
|
||||||
|
|
||||||
|
// Open download modal
|
||||||
|
await openDownloadMissingModalForYouTube(virtualPlaylistId, playlistName, buildPlaylistTracks);
|
||||||
|
}
|
||||||
|
|
||||||
function openDailyMix(mixIndex) {
|
function openDailyMix(mixIndex) {
|
||||||
const mix = personalizedDailyMixes[mixIndex];
|
const mix = personalizedDailyMixes[mixIndex];
|
||||||
if (!mix || !mix.tracks) return;
|
if (!mix || !mix.tracks) return;
|
||||||
|
|
@ -26013,6 +26091,8 @@ async function openDownloadModalForDiscoverPlaylist(playlistType, playlistName)
|
||||||
tracks = personalizedTopTracks;
|
tracks = personalizedTopTracks;
|
||||||
} else if (playlistType === 'forgotten_favorites') {
|
} else if (playlistType === 'forgotten_favorites') {
|
||||||
tracks = personalizedForgottenFavorites;
|
tracks = personalizedForgottenFavorites;
|
||||||
|
} else if (playlistType === 'build_playlist') {
|
||||||
|
tracks = buildPlaylistTracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tracks || tracks.length === 0) {
|
if (!tracks || tracks.length === 0) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue