soundwave/docs/OFFLINE_PLAYLISTS_GUIDE.md
Iulian 51679d1943 Initial commit - SoundWave v1.0
- Full PWA support with offline capabilities
- Comprehensive search across songs, playlists, and channels
- Offline playlist manager with download tracking
- Pre-built frontend for zero-build deployment
- Docker-based deployment with docker compose
- Material-UI dark theme interface
- YouTube audio download and management
- Multi-user authentication support
2025-12-16 23:43:07 +00:00

8.3 KiB

Quick Start: Offline Playlist Features

🎯 For Users

Download a Playlist for Offline Use (PWA UI)

When you're online, you can download any playlist to use offline:

  1. Open a playlist in the Soundwave app
  2. Click the "Download for Offline" button (⬇️)
  3. Wait for download to complete
  4. The playlist will now work even without internet

Use Offline Playlists

  • Downloaded playlists appear with an offline badge (📶)
  • Audio plays directly from cache (no buffering!)
  • Metadata loads instantly from IndexedDB

Remove Offline Playlist

  1. Open the downloaded playlist
  2. Click "Remove Offline Data" (🗑️)
  3. Frees up storage space

💻 For Developers

Check if Playlist is Cached

import { offlineStorage } from '../utils/offlineStorage';

const playlist = await offlineStorage.getPlaylist(playlistId);
if (playlist && playlist.offline) {
  console.log('Playlist available offline!');
}

Cache a Playlist

import { usePWA } from '../context/PWAContext';
import { offlineStorage } from '../utils/offlineStorage';

function DownloadButton({ playlist }) {
  const { cachePlaylist, isOnline } = usePWA();

  const handleDownload = async () => {
    if (!isOnline) {
      alert('Must be online to download');
      return;
    }

    // Get all audio URLs from playlist
    const audioUrls = playlist.items.map(item => item.audio_url);
    
    // Cache in Service Worker
    const cached = await cachePlaylist(playlist.id, audioUrls);
    
    if (cached) {
      // Save metadata to IndexedDB
      await offlineStorage.savePlaylist({
        id: playlist.id,
        title: playlist.title,
        description: playlist.description,
        items: playlist.items,
        offline: true,
        lastSync: Date.now(),
      });
      
      alert('Playlist downloaded for offline use!');
    }
  };

  return (
    <button onClick={handleDownload}>
      Download Offline
    </button>
  );
}

Get All Offline Playlists

const offlinePlaylists = await offlineStorage.getOfflinePlaylists();
console.log(`You have ${offlinePlaylists.length} playlists available offline`);

Remove Cached Playlist

import { usePWA } from '../context/PWAContext';
import { offlineStorage } from '../utils/offlineStorage';

async function removeOffline(playlist) {
  const { removePlaylistCache } = usePWA();
  
  const audioUrls = playlist.items.map(item => item.audio_url);
  
  // Remove from Service Worker cache
  await removePlaylistCache(playlist.id, audioUrls);
  
  // Remove from IndexedDB
  await offlineStorage.removePlaylist(playlist.id);
}

Check Storage Usage

const { cacheSize } = usePWA();

if (cacheSize) {
  const usedMB = (cacheSize.usage / 1024 / 1024).toFixed(2);
  const quotaMB = (cacheSize.quota / 1024 / 1024).toFixed(2);
  const percent = ((cacheSize.usage / cacheSize.quota) * 100).toFixed(1);
  
  console.log(`Storage: ${usedMB} MB / ${quotaMB} MB (${percent}%)`);
}

🔄 Sync Strategy

When Online

  • User downloads playlist → cached in Service Worker + IndexedDB
  • Audio files stored in browser cache
  • Metadata stored in IndexedDB

When Offline

  • Service Worker serves cached audio files
  • IndexedDB provides playlist metadata
  • No network requests needed

When Back Online

  • Check for playlist updates
  • Sync any pending changes
  • Update cache if needed

🎨 UI Integration Example

import { usePWA } from '../context/PWAContext';
import { offlineStorage } from '../utils/offlineStorage';
import { useState, useEffect } from 'react';

function PlaylistCard({ playlist }) {
  const { cachePlaylist, removePlaylistCache, isOnline } = usePWA();
  const [isOffline, setIsOffline] = useState(false);
  const [downloading, setDownloading] = useState(false);

  useEffect(() => {
    // Check if cached
    offlineStorage.getPlaylist(playlist.id).then(cached => {
      setIsOffline(cached?.offline || false);
    });
  }, [playlist.id]);

  const handleDownload = async () => {
    setDownloading(true);
    try {
      const audioUrls = playlist.items.map(i => i.audio_url);
      await cachePlaylist(playlist.id, audioUrls);
      await offlineStorage.savePlaylist({
        ...playlist,
        offline: true,
        lastSync: Date.now(),
      });
      setIsOffline(true);
    } catch (error) {
      console.error('Download failed:', error);
    } finally {
      setDownloading(false);
    }
  };

  const handleRemove = async () => {
    try {
      const audioUrls = playlist.items.map(i => i.audio_url);
      await removePlaylistCache(playlist.id, audioUrls);
      await offlineStorage.removePlaylist(playlist.id);
      setIsOffline(false);
    } catch (error) {
      console.error('Remove failed:', error);
    }
  };

  return (
    <div className="playlist-card">
      <h3>{playlist.title}</h3>
      {isOffline && <span className="badge">📶 Offline</span>}
      
      {!isOffline ? (
        <button 
          onClick={handleDownload}
          disabled={!isOnline || downloading}
        >
          {downloading ? 'Downloading...' : 'Download Offline'}
        </button>
      ) : (
        <button onClick={handleRemove}>
          Remove Offline Data
        </button>
      )}
    </div>
  );
}

📱 PWA Context API

All available PWA functions:

const {
  isOnline,              // Boolean: network status
  canInstall,            // Boolean: can show install prompt
  isInstalled,           // Boolean: is installed as PWA
  isUpdateAvailable,     // Boolean: new version available
  cacheSize,             // { usage, quota }: storage info
  
  showInstallPrompt,     // () => Promise<boolean>
  updateApp,             // () => Promise<void>
  clearCache,            // () => Promise<boolean>
  cacheAudio,            // (url) => Promise<boolean>
  cachePlaylist,         // (id, urls) => Promise<boolean>
  removePlaylistCache,   // (id, urls) => Promise<boolean>
  requestNotifications,  // () => Promise<NotificationPermission>
} = usePWA();

🗄️ IndexedDB Storage API

All available storage functions:

// Playlists
await offlineStorage.savePlaylist(playlist);
await offlineStorage.getPlaylist(id);
await offlineStorage.getPlaylists();
await offlineStorage.getOfflinePlaylists();
await offlineStorage.removePlaylist(id);
await offlineStorage.updatePlaylistSyncStatus(id, 'synced');

// Audio Queue
await offlineStorage.saveAudioQueue(queue);
await offlineStorage.getAudioQueue();

// Favorites
await offlineStorage.addFavorite(item);
await offlineStorage.removeFavorite(id);
await offlineStorage.getFavorites();

// Settings
await offlineStorage.saveSetting('key', value);
await offlineStorage.getSetting('key');

// Cleanup
await offlineStorage.clearAllData();

🧪 Testing Offline Functionality

Browser DevTools

  1. Open DevTools → Application tab
  2. Service Workers → Check registration status
  3. Cache Storage → Verify cached audio files
  4. IndexedDB → Check soundwave-offline database

Simulate Offline

  1. DevTools → Network tab
  2. Change throttling to "Offline"
  3. Try playing a downloaded playlist
  4. Should work without any network requests!

Clear Everything

// Clear Service Worker caches
await clearCache();

// Clear IndexedDB
await offlineStorage.clearAllData();

// Unregister Service Worker
navigator.serviceWorker.getRegistrations().then(regs => {
  regs.forEach(reg => reg.unregister());
});

Performance Tips

  1. Batch Downloads

    • Download multiple playlists during idle time
    • Use background sync when available
  2. Smart Caching

    • Only cache frequently accessed playlists
    • Remove old cached content periodically
  3. Monitor Storage

    • Check cacheSize regularly
    • Warn user when approaching quota
  4. Progressive Enhancement

    • App works without offline features
    • Enhanced experience when cached

🔐 Security Notes

  • Cached data is user-specific (token-based)
  • Offline playlists only accessible by owner
  • Cache is browser-specific (not shared)
  • Service Worker respects CORS policies

📊 Storage Estimates

Typical sizes:

  • Audio file: 3-5 MB (compressed)
  • Playlist metadata: 50-100 KB
  • API responses: 10-50 KB

Example playlist (20 songs):

  • Audio files: ~80 MB
  • Metadata: ~2 MB
  • Total: ~82 MB

Browser quota (typical):

  • Desktop: 2-10 GB
  • Mobile: 500 MB - 2 GB