- New modern audio wave 'A' logo (192x192 and 512x512 icons) - PWA service worker for offline support and installability - Wake Lock API for background audio on FiiO/Android devices - Visibility change handling to prevent audio pause on screen off - Updated manifest.json with music categories and proper PWA config - Media Session API lock screen controls (already present) - Renamed app to 'Audiophile Web Player'
157 lines
5.1 KiB
TypeScript
157 lines
5.1 KiB
TypeScript
import { openDB, DBSchema } from 'idb';
|
|
import { Track, Playlist } from '@/types';
|
|
|
|
export type { Track, Playlist };
|
|
|
|
interface MyDB extends DBSchema {
|
|
playlists: {
|
|
key: string;
|
|
value: Playlist;
|
|
};
|
|
likedSongs: {
|
|
key: string; // trackId
|
|
value: Track;
|
|
};
|
|
}
|
|
|
|
const DB_NAME = 'audiophile-db';
|
|
const DB_VERSION = 2;
|
|
|
|
export const initDB = async () => {
|
|
return openDB<MyDB>(DB_NAME, DB_VERSION, {
|
|
upgrade(db, oldVersion, newVersion, transaction) {
|
|
// Re-create stores to clear old data
|
|
if (db.objectStoreNames.contains('playlists')) {
|
|
db.deleteObjectStore('playlists');
|
|
}
|
|
if (db.objectStoreNames.contains('likedSongs')) {
|
|
db.deleteObjectStore('likedSongs');
|
|
}
|
|
db.createObjectStore('playlists', { keyPath: 'id' });
|
|
db.createObjectStore('likedSongs', { keyPath: 'id' });
|
|
},
|
|
});
|
|
};
|
|
|
|
export const dbService = {
|
|
async getPlaylists() {
|
|
const db = await initDB();
|
|
const playlists = await db.getAll('playlists');
|
|
if (playlists.length === 0) {
|
|
return this.seedInitialData();
|
|
}
|
|
return playlists;
|
|
},
|
|
|
|
async seedInitialData() {
|
|
try {
|
|
// Fetch real data from backend to seed valid playlists
|
|
// We use the 'api' prefix assuming this runs in browser
|
|
const res = await fetch('/api/trending');
|
|
if (!res.ok) return [];
|
|
|
|
const data = await res.json();
|
|
const allTracks: Track[] = data.tracks || [];
|
|
|
|
if (allTracks.length === 0) return [];
|
|
|
|
const db = await initDB();
|
|
const newPlaylists: Playlist[] = [];
|
|
|
|
// 1. Starter Playlist
|
|
const favTracks = allTracks.slice(0, 8);
|
|
if (favTracks.length > 0) {
|
|
const p1: Playlist = {
|
|
id: crypto.randomUUID(),
|
|
title: "My Rotations",
|
|
tracks: favTracks,
|
|
createdAt: Date.now(),
|
|
cover_url: favTracks[0].cover_url
|
|
};
|
|
await db.put('playlists', p1);
|
|
newPlaylists.push(p1);
|
|
}
|
|
|
|
// 2. Vibes
|
|
const vibeTracks = allTracks.slice(8, 15);
|
|
if (vibeTracks.length > 0) {
|
|
const p2: Playlist = {
|
|
id: crypto.randomUUID(),
|
|
title: "Weekend Vibes",
|
|
tracks: vibeTracks,
|
|
createdAt: Date.now(),
|
|
cover_url: vibeTracks[0].cover_url
|
|
};
|
|
await db.put('playlists', p2);
|
|
newPlaylists.push(p2);
|
|
}
|
|
|
|
return newPlaylists;
|
|
|
|
} catch (e) {
|
|
console.error("Seeding failed", e);
|
|
return [];
|
|
}
|
|
},
|
|
|
|
async getPlaylist(id: string) {
|
|
const db = await initDB();
|
|
return db.get('playlists', id);
|
|
},
|
|
async createPlaylist(name: string) {
|
|
const db = await initDB();
|
|
const newPlaylist: Playlist = {
|
|
id: crypto.randomUUID(),
|
|
title: name,
|
|
tracks: [],
|
|
createdAt: Date.now(),
|
|
cover_url: "https://placehold.co/300/222/fff?text=" + encodeURIComponent(name)
|
|
};
|
|
await db.put('playlists', newPlaylist);
|
|
return newPlaylist;
|
|
},
|
|
async deletePlaylist(id: string) {
|
|
const db = await initDB();
|
|
await db.delete('playlists', id);
|
|
},
|
|
async addToPlaylist(playlistId: string, track: Track) {
|
|
const db = await initDB();
|
|
const playlist = await db.get('playlists', playlistId);
|
|
if (playlist) {
|
|
// Auto-update cover if it's the default or empty
|
|
if (playlist.tracks.length === 0 || playlist.cover_url?.includes("placehold")) {
|
|
playlist.cover_url = track.cover_url;
|
|
}
|
|
playlist.tracks.push(track);
|
|
await db.put('playlists', playlist);
|
|
}
|
|
},
|
|
async removeFromPlaylist(playlistId: string, trackId: string) {
|
|
const db = await initDB();
|
|
const playlist = await db.get('playlists', playlistId);
|
|
if (playlist) {
|
|
playlist.tracks = playlist.tracks.filter(t => t.id !== trackId);
|
|
await db.put('playlists', playlist);
|
|
}
|
|
},
|
|
async getLikedSongs() {
|
|
const db = await initDB();
|
|
return db.getAll('likedSongs');
|
|
},
|
|
async toggleLike(track: Track) {
|
|
const db = await initDB();
|
|
const existing = await db.get('likedSongs', track.id);
|
|
if (existing) {
|
|
await db.delete('likedSongs', track.id);
|
|
return false; // unliked
|
|
} else {
|
|
await db.put('likedSongs', track);
|
|
return true; // liked
|
|
}
|
|
},
|
|
async isLiked(trackId: string) {
|
|
const db = await initDB();
|
|
const existing = await db.get('likedSongs', trackId);
|
|
return !!existing;
|
|
}
|
|
};
|