Add files via upload
This commit is contained in:
commit
675493bafc
28 changed files with 2176 additions and 0 deletions
96
hooks/useAudio.ts
Normal file
96
hooks/useAudio.ts
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { Audio, AVPlaybackStatus } from 'expo-av';
|
||||
import { usePlayer } from '../store';
|
||||
|
||||
let _sound: Audio.Sound | null = null;
|
||||
|
||||
// Module-level singleton so the sound survives re-renders
|
||||
export async function getSound() { return _sound; }
|
||||
|
||||
export function useAudio() {
|
||||
const initialized = useRef(false);
|
||||
const {
|
||||
queue, index, playing,
|
||||
setPlaying, setProgress, setDuration,
|
||||
nextTrack, setIndex, repeat,
|
||||
} = usePlayer();
|
||||
|
||||
const currentTrack = queue[index] ?? null;
|
||||
|
||||
// Init audio session once
|
||||
useEffect(() => {
|
||||
if (initialized.current) return;
|
||||
initialized.current = true;
|
||||
Audio.setAudioModeAsync({
|
||||
staysActiveInBackground: true,
|
||||
playsInSilentModeIOS: true,
|
||||
interruptionModeIOS: 1,
|
||||
shouldDuckAndroid: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Load new track whenever index/id changes
|
||||
useEffect(() => {
|
||||
if (!currentTrack) return;
|
||||
|
||||
const load = async () => {
|
||||
// Unload previous
|
||||
if (_sound) {
|
||||
await _sound.stopAsync().catch(() => {});
|
||||
await _sound.unloadAsync().catch(() => {});
|
||||
_sound = null;
|
||||
}
|
||||
|
||||
const { sound } = await Audio.Sound.createAsync(
|
||||
{ uri: currentTrack.stream_url },
|
||||
{ shouldPlay: true, progressUpdateIntervalMillis: 500 },
|
||||
(status: AVPlaybackStatus) => {
|
||||
if (!status.isLoaded) return;
|
||||
setProgress(status.positionMillis / 1000);
|
||||
setDuration((status.durationMillis ?? 0) / 1000);
|
||||
if (status.isPlaying !== usePlayer.getState().playing) {
|
||||
setPlaying(status.isPlaying);
|
||||
}
|
||||
if (status.didJustFinish) {
|
||||
const state = usePlayer.getState();
|
||||
const ni = state.nextTrack();
|
||||
if (ni !== state.index || state.repeat === 2) {
|
||||
setIndex(ni);
|
||||
} else {
|
||||
setPlaying(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
_sound = sound;
|
||||
};
|
||||
|
||||
load().catch(console.error);
|
||||
}, [currentTrack?.id]);
|
||||
|
||||
const togglePlay = useCallback(async () => {
|
||||
if (!_sound) return;
|
||||
const status = await _sound.getStatusAsync();
|
||||
if (!status.isLoaded) return;
|
||||
if (status.isPlaying) {
|
||||
await _sound.pauseAsync();
|
||||
} else {
|
||||
await _sound.playAsync();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const seek = useCallback(async (seconds: number) => {
|
||||
if (!_sound) return;
|
||||
await _sound.setPositionAsync(seconds * 1000);
|
||||
}, []);
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
_sound?.unloadAsync().catch(() => {});
|
||||
_sound = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { togglePlay, seek };
|
||||
}
|
||||
27
hooks/useAuth.ts
Normal file
27
hooks/useAuth.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { useEffect } from 'react';
|
||||
import { getAccessToken, getMe, clearTokens } from '../services/api';
|
||||
import { useAuth } from '../store';
|
||||
|
||||
export function useBootAuth() {
|
||||
const { setUser, setReady } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const token = await getAccessToken();
|
||||
if (!token) { setReady(true); return; }
|
||||
const me = await getMe();
|
||||
setUser(me);
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
setReady(true);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
}
|
||||
|
||||
export async function logout() {
|
||||
await clearTokens();
|
||||
useAuth.getState().setUser(null);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue