Skip to main content

Overview

Your Library stores all your liked songs and recently played tracks locally in your browser using IndexedDB. This data persists across sessions and is private to your device.

Accessing Your Library

Navigate to the Library page to view:
  • Liked Songs collection
  • Recently Played history
export default function LibraryPage() {
  const [likedSongs, setLikedSongs] = useState([]);
  const [recentPlays, setRecentPlays] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function loadData() {
      setLoading(true);
      const [liked, recent] = await Promise.all([
        getLikedSongs(),
        getRecentPlays(30),
      ]);
      setLikedSongs(liked);
      setRecentPlays(recent);
      setLoading(false);
    }
    loadData();
  }, []);
}
The Library page loads both liked songs and recent plays in parallel for faster performance.

Liked Songs

Your Liked Songs collection displays all tracks you’ve favorited.

Liking a Track

Click the heart icon in the player bar to like the currently playing track:
const handleToggleLike = async () => {
  if (!currentTrack) return;
  const result = await toggleLike(currentTrack);
  setLiked(result);
};

<IconButton onClick={handleToggleLike}>
  {liked ? (
    <FavoriteRounded sx={{ color: "#ec4899" }} />
  ) : (
    <FavoriteBorderRounded />
  )}
</IconButton>
The icon turns pink when a track is liked.

Like Status Detection

The player automatically checks if the current track is liked:
useEffect(() => {
  if (currentTrack?.trackId) {
    isLiked(currentTrack.trackId).then(setLiked).catch(() => {});
  }
}, [currentTrack?.trackId]);
Like status is checked every time a new track starts playing, ensuring the heart icon always reflects the current state.

Liked Songs Display

The Liked Songs section features a gradient header with playback controls:
<Box
  sx={{
    display: "flex",
    alignItems: "center",
    gap: 2,
    p: 3,
    borderRadius: 3,
    background: "linear-gradient(135deg, rgba(124,58,237,0.15), rgba(6,182,212,0.1))",
  }}
>
  <Box
    sx={{
      width: 64,
      height: 64,
      borderRadius: 2,
      background: "linear-gradient(135deg, #7c3aed, #06b6d4)",
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
    }}
  >
    <FavoriteRoundedIcon sx={{ color: "#fff", fontSize: 32 }} />
  </Box>
  <Box sx={{ flexGrow: 1 }}>
    <Typography variant="h6" sx={{ fontWeight: 700 }}>
      Liked Songs
    </Typography>
    <Typography variant="body2" color="text.secondary">
      {likedSongs.length} {likedSongs.length === 1 ? "song" : "songs"}
    </Typography>
  </Box>
  {likedSongs.length > 0 && (
    <IconButton onClick={playAllLiked}>
      <PlayArrowRoundedIcon />
    </IconButton>
  )}
</Box>

Playing All Liked Songs

Click the play button to play all your liked songs in order:
const playAllLiked = () => {
  if (likedSongs.length > 0) {
    playerActions.playTrack(likedSongs[0], likedSongs);
  }
};
This sets all liked songs as your queue and starts playing from the first track.

Recently Played

The Recently Played section shows your last 30 played tracks:
{recentPlays.length > 0 && (
  <Box sx={{ mb: 4 }}>
    <Box sx={{ display: "flex", alignItems: "center", gap: 1, mb: 2 }}>
      <HistoryRoundedIcon sx={{ color: "text.secondary" }} />
      <Typography variant="h6" sx={{ fontWeight: 700 }}>
        Recently Played
      </Typography>
    </Box>
    <TrackList tracks={recentPlays} />
  </Box>
)}

Automatic Tracking

Every track you play is automatically added to recent plays:
// In Player component
useEffect(() => {
  if (currentTrack?.trackId) {
    addRecentPlay(currentTrack).catch(() => {});
  }
}, [currentTrack?.trackId]);
Recent plays are ordered by most recent first, so your latest listening always appears at the top.

IndexedDB Storage

Beat App uses IndexedDB for local storage (src/lib/idb.js:7):
const DB_NAME = 'beat-app-db';
const DB_VERSION = 1;

function openDB() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(DB_NAME, DB_VERSION);

    request.onupgradeneeded = (event) => {
      const db = event.target.result;

      if (!db.objectStoreNames.contains('recentlyPlayed')) {
        const store = db.createObjectStore('recentlyPlayed', { keyPath: 'trackId' });
        store.createIndex('playedAt', 'playedAt', { unique: false });
      }

      if (!db.objectStoreNames.contains('likedSongs')) {
        db.createObjectStore('likedSongs', { keyPath: 'trackId' });
      }
    };

    request.onsuccess = () => resolve(request.result);
  });
}

Database Operations

export async function toggleLike(track) {
  const db = await openDB();
  const tx = db.transaction('likedSongs', 'readwrite');
  const store = tx.objectStore('likedSongs');

  return new Promise((resolve, reject) => {
    const getReq = store.get(track.trackId);
    getReq.onsuccess = () => {
      if (getReq.result) {
        store.delete(track.trackId);
        tx.oncomplete = () => resolve(false); // unliked
      } else {
        store.put({ ...track, likedAt: Date.now() });
        tx.oncomplete = () => resolve(true); // liked
      }
    };
  });
}

Data Persistence

Your library data is:
  • Stored locally in your browser
  • Persists across sessions
  • Not synced to any server
  • Private to your device
  • Survives browser restarts
Clearing your browser data will delete your library. To preserve your data, avoid clearing site data for Beat App.

Empty States

When you haven’t liked any songs yet:
{likedSongs.length === 0 && (
  <Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
    Songs you like will appear here. Tap the heart icon on the player to save songs.
  </Typography>
)}
This helpful message guides new users to start building their library.

Track Metadata Storage

Each stored track includes:
  • trackId - Unique identifier
  • title - Track name
  • artists - Artist information
  • album - Album details
  • thumbnailUrl - Album artwork URL
  • duration - Track length
  • likedAt or playedAt - Timestamp
// Example liked song object
{
  trackId: "abc123",
  title: "Song Title",
  artists: [{ name: "Artist Name", id: "xyz" }],
  album: { title: "Album Title", albumId: "def456" },
  thumbnailUrl: "/path/to/image.jpg",
  duration: { label: "3:45" },
  likedAt: 1709482800000
}

Checking Like Status

Check if a specific track is liked:
export async function isLiked(trackId) {
  const db = await openDB();
  const tx = db.transaction('likedSongs', 'readonly');
  const store = tx.objectStore('likedSongs');

  return new Promise((resolve) => {
    const request = store.get(trackId);
    request.onsuccess = () => resolve(!!request.result);
  });
}
This returns true if the track is in your liked songs, false otherwise.

Local Playlists Support

The database also includes infrastructure for local playlists (future feature):
if (!db.objectStoreNames.contains('localPlaylists')) {
  db.createObjectStore('localPlaylists', { 
    keyPath: 'id', 
    autoIncrement: true 
  });
}
Playlist operations include:
  • createPlaylist(name) - Create new playlist
  • getLocalPlaylists() - Get all playlists
  • addToPlaylist(playlistId, track) - Add track to playlist
  • removeFromPlaylist(playlistId, trackId) - Remove track
  • deletePlaylist(playlistId) - Delete playlist
Local playlists functionality is implemented in the database layer but not yet exposed in the UI.

Storage Limits

IndexedDB storage limits vary by browser:
  • Chrome: ~60% of total disk space
  • Firefox: Up to 2GB per origin
  • Safari: ~1GB with user prompt for more
For typical music metadata, these limits allow for thousands of tracks.

Privacy Considerations

Local Storage

All library data stays on your device. Nothing is sent to external servers.

No Tracking

Your listening history and preferences are private and not tracked.

Next Steps

Music Playback

Learn how to play your liked songs

Search

Find more music to add to your library

Build docs developers (and LLMs) love