Project started 🥂
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { useAppDispatch, useAppSelector } from './useAppDispatch';
|
||||
import {
|
||||
pause, resume, setPosition, setDuration,
|
||||
setVolume as setVolumeAction,
|
||||
} from '../store/slices/player';
|
||||
import { nextTrack, prevTrack } from '../store/slices/queue';
|
||||
import { play } from '../store/slices/player';
|
||||
import { getStreamUrl, getCoverUrl } from '../api/endpoints/streaming';
|
||||
|
||||
let audioElement: HTMLAudioElement | null = null;
|
||||
|
||||
function getAudio(): HTMLAudioElement {
|
||||
if (!audioElement) {
|
||||
audioElement = new Audio();
|
||||
audioElement.preload = 'metadata';
|
||||
}
|
||||
return audioElement;
|
||||
}
|
||||
|
||||
export function useAudioPlayer() {
|
||||
const dispatch = useAppDispatch();
|
||||
const player = useAppSelector((s) => s.player);
|
||||
const queue = useAppSelector((s) => s.queue);
|
||||
const accessToken = useAppSelector((s) => s.auth.accessToken);
|
||||
const isSetup = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isSetup.current) return;
|
||||
isSetup.current = true;
|
||||
|
||||
const audio = getAudio();
|
||||
|
||||
audio.addEventListener('timeupdate', () => {
|
||||
dispatch(setPosition(audio.currentTime));
|
||||
});
|
||||
audio.addEventListener('durationchange', () => {
|
||||
dispatch(setDuration(audio.duration || 0));
|
||||
});
|
||||
audio.addEventListener('ended', () => {
|
||||
dispatch(nextTrack());
|
||||
});
|
||||
audio.addEventListener('pause', () => {
|
||||
dispatch(pause());
|
||||
});
|
||||
audio.addEventListener('play', () => {
|
||||
dispatch(resume());
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!player.currentTrackId || !accessToken) return;
|
||||
const audio = getAudio();
|
||||
const url = getStreamUrl(player.currentTrackId, accessToken);
|
||||
if (audio.src !== url) {
|
||||
audio.src = url;
|
||||
audio.load();
|
||||
}
|
||||
if (player.isPlaying) {
|
||||
void audio.play();
|
||||
} else {
|
||||
audio.pause();
|
||||
}
|
||||
}, [player.currentTrackId, player.isPlaying, accessToken]);
|
||||
|
||||
useEffect(() => {
|
||||
const audio = getAudio();
|
||||
audio.volume = player.muted ? 0 : player.volume;
|
||||
}, [player.volume, player.muted]);
|
||||
|
||||
// MediaSession: system media controls + metadata (lock screen, OS, headset keys)
|
||||
useEffect(() => {
|
||||
if (!('mediaSession' in navigator)) return;
|
||||
const entry = queue.entries[queue.currentIndex];
|
||||
if (!entry) {
|
||||
navigator.mediaSession.metadata = null;
|
||||
return;
|
||||
}
|
||||
const artUrl = getCoverUrl(entry.albumArtUrl);
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
title: entry.title,
|
||||
artist: entry.artistName,
|
||||
album: entry.albumTitle,
|
||||
artwork: artUrl ? [{ src: artUrl }] : [],
|
||||
});
|
||||
}, [queue.entries, queue.currentIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!('mediaSession' in navigator)) return;
|
||||
navigator.mediaSession.playbackState = player.isPlaying ? 'playing' : 'paused';
|
||||
}, [player.isPlaying]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!('mediaSession' in navigator)) return;
|
||||
const ms = navigator.mediaSession;
|
||||
ms.setActionHandler('play', () => dispatch(resume()));
|
||||
ms.setActionHandler('pause', () => dispatch(pause()));
|
||||
ms.setActionHandler('previoustrack', () => dispatch(prevTrack()));
|
||||
ms.setActionHandler('nexttrack', () => dispatch(nextTrack()));
|
||||
ms.setActionHandler('seekto', (details) => {
|
||||
if (typeof details.seekTime === 'number') {
|
||||
getAudio().currentTime = details.seekTime;
|
||||
dispatch(setPosition(details.seekTime));
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
ms.setActionHandler('play', null);
|
||||
ms.setActionHandler('pause', null);
|
||||
ms.setActionHandler('previoustrack', null);
|
||||
ms.setActionHandler('nexttrack', null);
|
||||
ms.setActionHandler('seekto', null);
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentEntry = queue.entries[queue.currentIndex];
|
||||
if (!currentEntry) return;
|
||||
if (currentEntry.trackId !== player.currentTrackId) {
|
||||
dispatch(play(currentEntry.trackId));
|
||||
}
|
||||
}, [queue.currentIndex, queue.entries, player.currentTrackId, dispatch]);
|
||||
|
||||
const seek = useCallback((seconds: number) => {
|
||||
const audio = getAudio();
|
||||
audio.currentTime = seconds;
|
||||
dispatch(setPosition(seconds));
|
||||
}, [dispatch]);
|
||||
|
||||
const setPlayerVolume = useCallback((vol: number) => {
|
||||
dispatch(setVolumeAction(vol));
|
||||
}, [dispatch]);
|
||||
|
||||
const playNext = useCallback(() => { dispatch(nextTrack()); }, [dispatch]);
|
||||
const playPrev = useCallback(() => { dispatch(prevTrack()); }, [dispatch]);
|
||||
|
||||
return { seek, setVolume: setPlayerVolume, playNext, playPrev };
|
||||
}
|
||||
Reference in New Issue
Block a user