nodeshiftmusicburmalda/hooks/useAudio.ts

107 lines
2.9 KiB
TypeScript
Raw Normal View History

2026-04-28 20:36:28 +05:00
import { useEffect } from 'react';
import TrackPlayer, {
State,
Capability,
AppKilledPlaybackBehavior,
usePlaybackState,
useProgress,
} from 'react-native-track-player';
2026-04-27 21:01:17 +05:00
import { usePlayer } from '../store';
2026-04-28 20:36:28 +05:00
import { coverUrl, Track } from '../services/api';
2026-04-27 21:01:17 +05:00
2026-04-28 20:36:28 +05:00
let _setupDone = false;
let _lastLoadedId: number | null = null;
2026-04-27 21:01:17 +05:00
2026-04-28 20:36:28 +05:00
async function ensureSetup() {
if (_setupDone) return;
_setupDone = true;
try {
await TrackPlayer.setupPlayer({ minBuffer: 15, maxBuffer: 50, playBuffer: 2.5 });
await TrackPlayer.updateOptions({
android: {
appKilledPlaybackBehavior:
AppKilledPlaybackBehavior.StopPlaybackAndRemoveNotification,
},
capabilities: [
Capability.Play,
Capability.Pause,
Capability.SkipToNext,
Capability.SkipToPrevious,
Capability.SeekTo,
],
compactCapabilities: [Capability.Play, Capability.Pause, Capability.SkipToNext],
2026-04-27 21:01:17 +05:00
});
2026-04-28 20:36:28 +05:00
} catch (e: any) {
// Player already set up on hot reload — update options only
if (e?.message?.toLowerCase().includes('already')) {
_setupDone = true;
return;
}
_setupDone = false;
throw e;
}
}
2026-04-27 21:01:17 +05:00
2026-04-28 20:36:28 +05:00
// Load a track and start playing. Mutex prevents double-load on the same ID.
export async function loadAndPlay(track: Track) {
if (_lastLoadedId === track.id) return;
_lastLoadedId = track.id;
try {
await TrackPlayer.reset();
await TrackPlayer.add({
id: String(track.id),
url: track.cdn_url2 || track.cdn_url,
title: track.title,
artist: track.artist,
artwork: coverUrl(track.cover_url) ?? undefined,
duration: track.duration,
});
await TrackPlayer.play();
} catch (e) {
_lastLoadedId = null;
throw e;
}
}
2026-04-27 21:01:17 +05:00
2026-04-28 20:36:28 +05:00
export async function togglePlay() {
const { state } = await TrackPlayer.getPlaybackState();
if (state === State.Playing) await TrackPlayer.pause();
else await TrackPlayer.play();
}
2026-04-27 21:01:17 +05:00
2026-04-28 20:36:28 +05:00
export async function seekTo(seconds: number) {
await TrackPlayer.seekTo(seconds);
}
2026-04-27 21:01:17 +05:00
2026-04-28 20:36:28 +05:00
// Call ONCE from the root layout only
export function useAudio() {
const { queue, index, setPlaying, setProgress, setDuration } = usePlayer();
const currentTrack = queue[index] ?? null;
const playbackState = usePlaybackState();
const progress = useProgress(500);
2026-04-27 21:01:17 +05:00
2026-04-28 20:36:28 +05:00
// One-time player setup
useEffect(() => {
ensureSetup().catch(console.error);
2026-04-27 21:01:17 +05:00
}, []);
2026-04-28 20:36:28 +05:00
// Sync TrackPlayer progress → store
useEffect(() => {
setProgress(progress.position);
setDuration(progress.duration);
}, [progress.position, progress.duration]);
2026-04-27 21:01:17 +05:00
2026-04-28 20:36:28 +05:00
// Sync TrackPlayer playing state → store
2026-04-27 21:01:17 +05:00
useEffect(() => {
2026-04-28 20:36:28 +05:00
setPlaying(playbackState.state === State.Playing);
}, [playbackState.state]);
// Load new track when queue index changes
useEffect(() => {
if (!currentTrack) return;
loadAndPlay(currentTrack).catch(console.error);
}, [currentTrack?.id]);
2026-04-27 21:01:17 +05:00
2026-04-28 20:36:28 +05:00
return { togglePlay, seek: seekTo };
2026-04-27 21:01:17 +05:00
}