refactor(music-api): convert to singleton
This commit is contained in:
parent
852baeaa17
commit
fb4b72bc39
2 changed files with 78 additions and 31 deletions
91
js/app.js
91
js/app.js
|
|
@ -389,8 +389,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
initAnalytics();
|
initAnalytics();
|
||||||
|
|
||||||
new ThemeStore();
|
new ThemeStore();
|
||||||
|
await MusicAPI.initialize(apiSettings);
|
||||||
|
|
||||||
const api = new MusicAPI(apiSettings);
|
|
||||||
const audioPlayer = document.getElementById('audio-player');
|
const audioPlayer = document.getElementById('audio-player');
|
||||||
|
|
||||||
// i love ios and macos!!!! webkit fucking SUCKS BULLSHIT sorry ios/macos heads yall getting lossless only playback
|
// i love ios and macos!!!! webkit fucking SUCKS BULLSHIT sorry ios/macos heads yall getting lossless only playback
|
||||||
|
|
@ -418,7 +418,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentQuality = localStorage.getItem('playback-quality') || 'HI_RES_LOSSLESS';
|
const currentQuality = localStorage.getItem('playback-quality') || 'HI_RES_LOSSLESS';
|
||||||
await Player.initialize(audioPlayer, api, currentQuality);
|
await Player.initialize(audioPlayer, MusicAPI.instance, currentQuality);
|
||||||
|
|
||||||
// Initialize tracker
|
// Initialize tracker
|
||||||
initTracker();
|
initTracker();
|
||||||
|
|
@ -497,7 +497,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const castBtn = document.getElementById('cast-btn');
|
const castBtn = document.getElementById('cast-btn');
|
||||||
initializeCasting(audioPlayer, castBtn);
|
initializeCasting(audioPlayer, castBtn);
|
||||||
|
|
||||||
const ui = new UIRenderer(api, Player.instance);
|
const ui = new UIRenderer(MusicAPI.instance, Player.instance);
|
||||||
window.monochromeUi = ui;
|
window.monochromeUi = ui;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -654,7 +654,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
const scrobbler = new MultiScrobbler();
|
const scrobbler = new MultiScrobbler();
|
||||||
window.monochromeScrobbler = scrobbler;
|
window.monochromeScrobbler = scrobbler;
|
||||||
const lyricsManager = new LyricsManager(api);
|
const lyricsManager = new LyricsManager(MusicAPI.instance);
|
||||||
ui.lyricsManager = lyricsManager;
|
ui.lyricsManager = lyricsManager;
|
||||||
managers.lyricsManager = lyricsManager;
|
managers.lyricsManager = lyricsManager;
|
||||||
|
|
||||||
|
|
@ -693,7 +693,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
// Load settings module and initialize
|
// Load settings module and initialize
|
||||||
const { initializeSettings } = await loadSettingsModule();
|
const { initializeSettings } = await loadSettingsModule();
|
||||||
await initializeSettings(scrobbler, Player.instance, api, ui);
|
await initializeSettings(scrobbler, Player.instance, MusicAPI.instance, ui);
|
||||||
|
|
||||||
// Track sidebar navigation clicks
|
// Track sidebar navigation clicks
|
||||||
document.querySelectorAll('.sidebar-nav a').forEach((link) => {
|
document.querySelectorAll('.sidebar-nav a').forEach((link) => {
|
||||||
|
|
@ -709,14 +709,14 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
initializePlayerEvents(Player.instance, audioPlayer, scrobbler, ui);
|
initializePlayerEvents(Player.instance, audioPlayer, scrobbler, ui);
|
||||||
initializeTrackInteractions(
|
initializeTrackInteractions(
|
||||||
Player.instance,
|
Player.instance,
|
||||||
api,
|
MusicAPI.instance,
|
||||||
document.querySelector('.main-content'),
|
document.querySelector('.main-content'),
|
||||||
document.getElementById('context-menu'),
|
document.getElementById('context-menu'),
|
||||||
lyricsManager,
|
lyricsManager,
|
||||||
ui,
|
ui,
|
||||||
scrobbler
|
scrobbler
|
||||||
);
|
);
|
||||||
initializeUIInteractions(Player.instance, api, ui);
|
initializeUIInteractions(Player.instance, MusicAPI.instance, ui);
|
||||||
initializeKeyboardShortcuts(Player.instance, audioPlayer);
|
initializeKeyboardShortcuts(Player.instance, audioPlayer);
|
||||||
|
|
||||||
// Restore UI state for the current track (like button, theme)
|
// Restore UI state for the current track (like button, theme)
|
||||||
|
|
@ -1073,7 +1073,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
'download',
|
'download',
|
||||||
Player.instance.currentTrack,
|
Player.instance.currentTrack,
|
||||||
Player.instance,
|
Player.instance,
|
||||||
api,
|
MusicAPI.instance,
|
||||||
lyricsManager,
|
lyricsManager,
|
||||||
'track',
|
'track',
|
||||||
ui
|
ui
|
||||||
|
|
@ -1147,7 +1147,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
if (!albumId) return;
|
if (!albumId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { tracks } = await api.getAlbum(albumId);
|
const { tracks } = await MusicAPI.instance.getAlbum(albumId);
|
||||||
if (tracks && tracks.length > 0) {
|
if (tracks && tracks.length > 0) {
|
||||||
// Sort tracks by disc and track number for consistent playback
|
// Sort tracks by disc and track number for consistent playback
|
||||||
const sortedTracks = [...tracks].sort((a, b) => {
|
const sortedTracks = [...tracks].sort((a, b) => {
|
||||||
|
|
@ -1185,7 +1185,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
if (!albumId) return;
|
if (!albumId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { tracks } = await api.getAlbum(albumId);
|
const { tracks } = await MusicAPI.instance.getAlbum(albumId);
|
||||||
if (tracks && tracks.length > 0) {
|
if (tracks && tracks.length > 0) {
|
||||||
const shuffledTracks = [...tracks].sort(() => Math.random() - 0.5);
|
const shuffledTracks = [...tracks].sort(() => Math.random() - 0.5);
|
||||||
Player.instance.setQueue(shuffledTracks, 0);
|
Player.instance.setQueue(shuffledTracks, 0);
|
||||||
|
|
@ -1215,7 +1215,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
btn.innerHTML = `${SVG_ANIMATE_SPIN(18)}<span>Shuffling...</span>`;
|
btn.innerHTML = `${SVG_ANIMATE_SPIN(18)}<span>Shuffling...</span>`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const artist = await api.getArtist(artistId);
|
const artist = await MusicAPI.instance.getArtist(artistId);
|
||||||
const allReleases = [...(artist.albums || []), ...(artist.eps || [])];
|
const allReleases = [...(artist.albums || []), ...(artist.eps || [])];
|
||||||
const trackSet = new Set();
|
const trackSet = new Set();
|
||||||
const allTracks = [];
|
const allTracks = [];
|
||||||
|
|
@ -1227,7 +1227,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
chunk.map(async (album) => {
|
chunk.map(async (album) => {
|
||||||
try {
|
try {
|
||||||
const { tracks } = await api.getAlbum(album.id);
|
const { tracks } = await MusicAPI.instance.getAlbum(album.id);
|
||||||
tracks.forEach((track) => {
|
tracks.forEach((track) => {
|
||||||
if (!trackSet.has(track.id)) {
|
if (!trackSet.has(track.id)) {
|
||||||
trackSet.add(track.id);
|
trackSet.add(track.id);
|
||||||
|
|
@ -1287,9 +1287,15 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
btn.innerHTML = `${SVG_ANIMATE_SPIN(20)}<span>Downloading...</span>`;
|
btn.innerHTML = `${SVG_ANIMATE_SPIN(20)}<span>Downloading...</span>`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { mix, tracks } = await api.getMix(mixId);
|
const { mix, tracks } = await MusicAPI.instance.getMix(mixId);
|
||||||
const { downloadPlaylistAsZip } = await loadDownloadsModule();
|
const { downloadPlaylistAsZip } = await loadDownloadsModule();
|
||||||
await downloadPlaylistAsZip(mix, tracks, api, downloadQualitySettings.getQuality(), lyricsManager);
|
await downloadPlaylistAsZip(
|
||||||
|
mix,
|
||||||
|
tracks,
|
||||||
|
MusicAPI.instance,
|
||||||
|
downloadQualitySettings.getQuality(),
|
||||||
|
lyricsManager
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Mix download failed:', error);
|
console.error('Mix download failed:', error);
|
||||||
alert('Failed to download mix: ' + error.message);
|
alert('Failed to download mix: ' + error.message);
|
||||||
|
|
@ -1326,13 +1332,19 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
playlist = { ...userPlaylist, title: userPlaylist.name || userPlaylist.title };
|
playlist = { ...userPlaylist, title: userPlaylist.name || userPlaylist.title };
|
||||||
tracks = userPlaylist.tracks || [];
|
tracks = userPlaylist.tracks || [];
|
||||||
} else {
|
} else {
|
||||||
const data = await api.getPlaylist(playlistId);
|
const data = await MusicAPI.instance.getPlaylist(playlistId);
|
||||||
playlist = data.playlist;
|
playlist = data.playlist;
|
||||||
tracks = data.tracks;
|
tracks = data.tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { downloadPlaylistAsZip } = await loadDownloadsModule();
|
const { downloadPlaylistAsZip } = await loadDownloadsModule();
|
||||||
await downloadPlaylistAsZip(playlist, tracks, api, downloadQualitySettings.getQuality(), lyricsManager);
|
await downloadPlaylistAsZip(
|
||||||
|
playlist,
|
||||||
|
tracks,
|
||||||
|
MusicAPI.instance,
|
||||||
|
downloadQualitySettings.getQuality(),
|
||||||
|
lyricsManager
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Playlist download failed:', error);
|
console.error('Playlist download failed:', error);
|
||||||
alert('Failed to download playlist: ' + error.message);
|
alert('Failed to download playlist: ' + error.message);
|
||||||
|
|
@ -1569,7 +1581,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
const result = await parseCSV(
|
const result = await parseCSV(
|
||||||
csvText,
|
csvText,
|
||||||
api,
|
MusicAPI.instance,
|
||||||
(progress) => {
|
(progress) => {
|
||||||
const percentage = totalTracks > 0 ? (progress.current / totalTracks) * 100 : 0;
|
const percentage = totalTracks > 0 ? (progress.current / totalTracks) * 100 : 0;
|
||||||
progressFill.style.width = `${Math.min(percentage, 100)}%`;
|
progressFill.style.width = `${Math.min(percentage, 100)}%`;
|
||||||
|
|
@ -1630,7 +1642,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
const jspfText = await file.text();
|
const jspfText = await file.text();
|
||||||
|
|
||||||
const result = await parseJSPF(jspfText, api, (progress) => {
|
const result = await parseJSPF(jspfText, MusicAPI.instance, (progress) => {
|
||||||
const percentage = progress.total > 0 ? (progress.current / progress.total) * 100 : 0;
|
const percentage = progress.total > 0 ? (progress.current / progress.total) * 100 : 0;
|
||||||
progressFill.style.width = `${Math.min(percentage, 100)}%`;
|
progressFill.style.width = `${Math.min(percentage, 100)}%`;
|
||||||
progressCurrent.textContent = progress.current.toString();
|
progressCurrent.textContent = progress.current.toString();
|
||||||
|
|
@ -1718,7 +1730,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
const result = await parseDynamicCSV(
|
const result = await parseDynamicCSV(
|
||||||
csvText,
|
csvText,
|
||||||
api,
|
MusicAPI.instance,
|
||||||
(progress) => {
|
(progress) => {
|
||||||
const percentage = totalItems > 0 ? (progress.current / totalItems) * 100 : 0;
|
const percentage = totalItems > 0 ? (progress.current / totalItems) * 100 : 0;
|
||||||
progressFill.style.width = `${Math.min(percentage, 100)}%`;
|
progressFill.style.width = `${Math.min(percentage, 100)}%`;
|
||||||
|
|
@ -1829,7 +1841,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
const xspfText = await file.text();
|
const xspfText = await file.text();
|
||||||
|
|
||||||
const result = await parseXSPF(xspfText, api, (progress) => {
|
const result = await parseXSPF(xspfText, MusicAPI.instance, (progress) => {
|
||||||
const percentage = progress.total > 0 ? (progress.current / progress.total) * 100 : 0;
|
const percentage = progress.total > 0 ? (progress.current / progress.total) * 100 : 0;
|
||||||
progressFill.style.width = `${Math.min(percentage, 100)}%`;
|
progressFill.style.width = `${Math.min(percentage, 100)}%`;
|
||||||
progressCurrent.textContent = progress.current.toString();
|
progressCurrent.textContent = progress.current.toString();
|
||||||
|
|
@ -1888,7 +1900,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
const xmlText = await file.text();
|
const xmlText = await file.text();
|
||||||
|
|
||||||
const result = await parseXML(xmlText, api, (progress) => {
|
const result = await parseXML(xmlText, MusicAPI.instance, (progress) => {
|
||||||
const percentage = progress.total > 0 ? (progress.current / progress.total) * 100 : 0;
|
const percentage = progress.total > 0 ? (progress.current / progress.total) * 100 : 0;
|
||||||
progressFill.style.width = `${Math.min(percentage, 100)}%`;
|
progressFill.style.width = `${Math.min(percentage, 100)}%`;
|
||||||
progressCurrent.textContent = progress.current.toString();
|
progressCurrent.textContent = progress.current.toString();
|
||||||
|
|
@ -1947,7 +1959,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
const m3uText = await file.text();
|
const m3uText = await file.text();
|
||||||
|
|
||||||
const result = await parseM3U(m3uText, api, (progress) => {
|
const result = await parseM3U(m3uText, MusicAPI.instance, (progress) => {
|
||||||
const percentage = progress.total > 0 ? (progress.current / progress.total) * 100 : 0;
|
const percentage = progress.total > 0 ? (progress.current / progress.total) * 100 : 0;
|
||||||
progressFill.style.width = `${Math.min(percentage, 100)}%`;
|
progressFill.style.width = `${Math.min(percentage, 100)}%`;
|
||||||
progressCurrent.textContent = progress.current.toString();
|
progressCurrent.textContent = progress.current.toString();
|
||||||
|
|
@ -2194,7 +2206,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
} else {
|
} else {
|
||||||
// Try API, if fail, try Public Pocketbase
|
// Try API, if fail, try Public Pocketbase
|
||||||
try {
|
try {
|
||||||
const { tracks: apiTracks } = await api.getPlaylist(playlistId);
|
const { tracks: apiTracks } = await MusicAPI.instance.getPlaylist(playlistId);
|
||||||
tracks = apiTracks;
|
tracks = apiTracks;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const publicPlaylist = await syncManager.getPublicPlaylist(playlistId);
|
const publicPlaylist = await syncManager.getPublicPlaylist(playlistId);
|
||||||
|
|
@ -2228,9 +2240,15 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
btn.innerHTML = `${SVG_ANIMATE_SPIN(20)}<span>Downloading...</span>`;
|
btn.innerHTML = `${SVG_ANIMATE_SPIN(20)}<span>Downloading...</span>`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { album, tracks } = await api.getAlbum(albumId);
|
const { album, tracks } = await MusicAPI.instance.getAlbum(albumId);
|
||||||
const { downloadAlbumAsZip } = await loadDownloadsModule();
|
const { downloadAlbumAsZip } = await loadDownloadsModule();
|
||||||
await downloadAlbumAsZip(album, tracks, api, downloadQualitySettings.getQuality(), lyricsManager);
|
await downloadAlbumAsZip(
|
||||||
|
album,
|
||||||
|
tracks,
|
||||||
|
MusicAPI.instance,
|
||||||
|
downloadQualitySettings.getQuality(),
|
||||||
|
lyricsManager
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Album download failed:', error);
|
console.error('Album download failed:', error);
|
||||||
alert('Failed to download album: ' + error.message);
|
alert('Failed to download album: ' + error.message);
|
||||||
|
|
@ -2248,7 +2266,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
if (!albumId) return;
|
if (!albumId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { tracks } = await api.getAlbum(albumId);
|
const { tracks } = await MusicAPI.instance.getAlbum(albumId);
|
||||||
|
|
||||||
if (!tracks || tracks.length === 0) {
|
if (!tracks || tracks.length === 0) {
|
||||||
const { showNotification } = await loadDownloadsModule();
|
const { showNotification } = await loadDownloadsModule();
|
||||||
|
|
@ -2351,7 +2369,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
btn.innerHTML = `${SVG_ANIMATE_SPIN(20)}<span>Loading...</span>`;
|
btn.innerHTML = `${SVG_ANIMATE_SPIN(20)}<span>Loading...</span>`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const artist = await api.getArtist(artistId);
|
const artist = await MusicAPI.instance.getArtist(artistId);
|
||||||
|
|
||||||
const allReleases = [...(artist.albums || []), ...(artist.eps || [])];
|
const allReleases = [...(artist.albums || []), ...(artist.eps || [])];
|
||||||
if (allReleases.length === 0) {
|
if (allReleases.length === 0) {
|
||||||
|
|
@ -2373,7 +2391,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
chunk.map(async (album) => {
|
chunk.map(async (album) => {
|
||||||
try {
|
try {
|
||||||
const { tracks } = await api.getAlbum(album.id);
|
const { tracks } = await MusicAPI.instance.getAlbum(album.id);
|
||||||
tracks.forEach((track) => {
|
tracks.forEach((track) => {
|
||||||
if (!trackSet.has(track.id)) {
|
if (!trackSet.has(track.id)) {
|
||||||
trackSet.add(track.id);
|
trackSet.add(track.id);
|
||||||
|
|
@ -2445,7 +2463,12 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { downloadLikedTracks } = await loadDownloadsModule();
|
const { downloadLikedTracks } = await loadDownloadsModule();
|
||||||
await downloadLikedTracks(likedTracks, api, downloadQualitySettings.getQuality(), lyricsManager);
|
await downloadLikedTracks(
|
||||||
|
likedTracks,
|
||||||
|
MusicAPI.instance,
|
||||||
|
downloadQualitySettings.getQuality(),
|
||||||
|
lyricsManager
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Liked tracks download failed:', error);
|
console.error('Liked tracks download failed:', error);
|
||||||
alert('Failed to download liked tracks: ' + error.message);
|
alert('Failed to download liked tracks: ' + error.message);
|
||||||
|
|
@ -2463,8 +2486,14 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
if (!artistId) return;
|
if (!artistId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const artist = await api.getArtist(artistId);
|
const artist = await MusicAPI.instance.getArtist(artistId);
|
||||||
showDiscographyDownloadModal(artist, api, downloadQualitySettings.getQuality(), lyricsManager, btn);
|
showDiscographyDownloadModal(
|
||||||
|
artist,
|
||||||
|
MusicAPI.instance,
|
||||||
|
downloadQualitySettings.getQuality(),
|
||||||
|
lyricsManager,
|
||||||
|
btn
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load artist for discography download:', error);
|
console.error('Failed to load artist for discography download:', error);
|
||||||
alert('Failed to load artist: ' + error.message);
|
alert('Failed to load artist: ' + error.message);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,15 @@ import { QobuzAPI } from './qobuz-api.js';
|
||||||
import { musicProviderSettings } from './storage.js';
|
import { musicProviderSettings } from './storage.js';
|
||||||
|
|
||||||
export class MusicAPI {
|
export class MusicAPI {
|
||||||
|
static #instance = null;
|
||||||
|
static get instance() {
|
||||||
|
if (!MusicAPI.#instance) {
|
||||||
|
throw new Error('MusicAPI not initialized. Call MusicAPI.initialize(settings) first.');
|
||||||
|
}
|
||||||
|
return MusicAPI.#instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @private */
|
||||||
constructor(settings) {
|
constructor(settings) {
|
||||||
this.tidalAPI = new LosslessAPI(settings);
|
this.tidalAPI = new LosslessAPI(settings);
|
||||||
this.qobuzAPI = new QobuzAPI();
|
this.qobuzAPI = new QobuzAPI();
|
||||||
|
|
@ -13,6 +22,15 @@ export class MusicAPI {
|
||||||
this.videoArtworkCache = new Map();
|
this.videoArtworkCache = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async initialize(settings) {
|
||||||
|
if (MusicAPI.#instance) {
|
||||||
|
throw new Error('MusicAPI is already initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = new MusicAPI(settings);
|
||||||
|
return (MusicAPI.#instance = api);
|
||||||
|
}
|
||||||
|
|
||||||
getCurrentProvider() {
|
getCurrentProvider() {
|
||||||
return musicProviderSettings.getProvider();
|
return musicProviderSettings.getProvider();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue