'use client'; import { useCallback } from 'react'; import { useAudioPlayer, Track } from '@/app/components/AudioPlayerContext'; import { useOfflineDownloads } from '@/hooks/use-offline-downloads'; import { useOfflineLibrary } from '@/hooks/use-offline-library'; import { Album, Song } from '@/lib/navidrome'; import { getNavidromeAPI } from '@/lib/navidrome'; export interface OfflineTrack extends Track { isOffline?: boolean; offlineUrl?: string; } export function useOfflineAudioPlayer() { const { playTrack, addToQueue, currentTrack, ...audioPlayerProps } = useAudioPlayer(); const { isSupported: isOfflineSupported, checkOfflineStatus } = useOfflineDownloads(); const { isOnline, scrobbleOffline } = useOfflineLibrary(); const api = getNavidromeAPI(); // Convert song to track with offline awareness const songToTrack = useCallback(async (song: Song): Promise => { let track: OfflineTrack = { id: song.id, name: song.title, url: api?.getStreamUrl(song.id) || '', artist: song.artist, album: song.album || '', duration: song.duration, coverArt: song.coverArt ? api?.getCoverArtUrl(song.coverArt, 1200) : undefined, albumId: song.albumId, artistId: song.artistId, starred: !!song.starred }; // Check if song is available offline if (isOfflineSupported) { const offlineStatus = await checkOfflineStatus(song.id, 'song'); if (offlineStatus) { track.isOffline = true; track.offlineUrl = `offline-song-${song.id}`; // Prefer offline cached URL to avoid re-streaming even when online track.url = track.offlineUrl; } } return track; }, [api, isOfflineSupported, checkOfflineStatus]); // Play track with offline fallback const playTrackOffline = useCallback(async (song: Song | OfflineTrack) => { try { let track: OfflineTrack; if ('url' in song) { // Already a track track = song as OfflineTrack; } else { // Convert song to track track = await songToTrack(song); } // If offline and track has offline URL, use that if (!isOnline && track.isOffline && track.offlineUrl) { track.url = track.offlineUrl; } playTrack(track); // Scrobble with offline support scrobbleOffline(track.id); } catch (error) { console.error('Failed to play track:', error); throw error; } }, [songToTrack, playTrack, scrobbleOffline, isOnline]); // Play album with offline awareness const playAlbumOffline = useCallback(async (album: Album, songs: Song[], startIndex: number = 0) => { try { if (songs.length === 0) return; // Convert all songs to tracks with offline awareness const tracks = await Promise.all(songs.map(songToTrack)); // Filter to only available tracks (online or offline) const availableTracks = tracks.filter((track: OfflineTrack) => { if (isOnline) return true; // All tracks available when online return track.isOffline; // Only offline tracks when offline }); if (availableTracks.length === 0) { throw new Error('No tracks available for playback'); } // Adjust start index if needed const safeStartIndex = Math.min(startIndex, availableTracks.length - 1); // Play first track playTrack(availableTracks[safeStartIndex]); // Add remaining tracks to queue const remainingTracks = [ ...availableTracks.slice(safeStartIndex + 1), ...availableTracks.slice(0, safeStartIndex) ]; remainingTracks.forEach(track => addToQueue(track)); // Scrobble first track scrobbleOffline(availableTracks[safeStartIndex].id); } catch (error) { console.error('Failed to play album offline:', error); throw error; } }, [songToTrack, playTrack, addToQueue, scrobbleOffline, isOnline]); // Add track to queue with offline awareness const addToQueueOffline = useCallback(async (song: Song | OfflineTrack) => { try { let track: OfflineTrack; if ('url' in song) { track = song as OfflineTrack; } else { track = await songToTrack(song); } // Check if track is available if (!isOnline && !track.isOffline) { throw new Error('Track not available offline'); } // If offline and track has offline URL, use that if (!isOnline && track.isOffline && track.offlineUrl) { track.url = track.offlineUrl; } addToQueue(track); } catch (error) { console.error('Failed to add track to queue:', error); throw error; } }, [songToTrack, addToQueue, isOnline]); // Shuffle play with offline awareness const shufflePlayOffline = useCallback(async (songs: Song[]) => { try { if (songs.length === 0) return; // Convert all songs to tracks const tracks = await Promise.all(songs.map(songToTrack)); // Filter available tracks const availableTracks = tracks.filter((track: OfflineTrack) => { if (isOnline) return true; return track.isOffline; }); if (availableTracks.length === 0) { throw new Error('No tracks available for shuffle play'); } // Shuffle the available tracks const shuffledTracks = [...availableTracks].sort(() => Math.random() - 0.5); // Play first track playTrack(shuffledTracks[0]); // Add remaining tracks to queue shuffledTracks.slice(1).forEach(track => addToQueue(track)); // Scrobble first track scrobbleOffline(shuffledTracks[0].id); } catch (error) { console.error('Failed to shuffle play offline:', error); throw error; } }, [songToTrack, playTrack, addToQueue, scrobbleOffline, isOnline]); // Get availability info for a song const getTrackAvailability = useCallback(async (song: Song): Promise<{ isAvailable: boolean; isOffline: boolean; requiresConnection: boolean; }> => { try { const track = await songToTrack(song); return { isAvailable: isOnline || !!track.isOffline, isOffline: !!track.isOffline, requiresConnection: !track.isOffline }; } catch (error) { console.error('Failed to check track availability:', error); return { isAvailable: false, isOffline: false, requiresConnection: true }; } }, [songToTrack, isOnline]); // Get album availability info const getAlbumAvailability = useCallback(async (songs: Song[]): Promise<{ totalTracks: number; availableTracks: number; offlineTracks: number; onlineOnlyTracks: number; }> => { try { const tracks = await Promise.all(songs.map(songToTrack)); const offlineTracks = tracks.filter((t: OfflineTrack) => t.isOffline).length; const onlineOnlyTracks = tracks.filter((t: OfflineTrack) => !t.isOffline).length; const availableTracks = isOnline ? tracks.length : offlineTracks; return { totalTracks: tracks.length, availableTracks, offlineTracks, onlineOnlyTracks }; } catch (error) { console.error('Failed to check album availability:', error); return { totalTracks: songs.length, availableTracks: 0, offlineTracks: 0, onlineOnlyTracks: songs.length }; } }, [songToTrack, isOnline]); // Enhanced track info with offline status const getCurrentTrackInfo = useCallback(() => { if (!currentTrack) return null; const offlineTrack = currentTrack as OfflineTrack; return { ...currentTrack, isAvailableOffline: offlineTrack.isOffline || false, isPlayingOffline: !isOnline && !!offlineTrack.isOffline }; }, [currentTrack, isOnline]); return { // Original audio player props ...audioPlayerProps, currentTrack, // Enhanced offline methods playTrackOffline, playAlbumOffline, addToQueueOffline, shufflePlayOffline, // Utility methods songToTrack, getTrackAvailability, getAlbumAvailability, getCurrentTrackInfo, // State isOnline, isOfflineSupported }; }