Files
beatconnect_client/src/App/Providers/AudioPlayer/Audioplayer.re
T
2021-04-18 19:41:39 +02:00

271 lines
6.8 KiB
ReasonML

open AudioPlayerProvider;
[@bs.val] external alert: string => unit = "alert";
let audio = Audio.make();
let _play = () => {
Audio.play(audio);
};
let _updateMetadata = (song: song) => {
MediaMetadata.make({
title: song.title,
artist: song.artist,
album: "Beatconnect",
artwork: [|MediaMetadata.makeArtwork(song.id)|],
})
->MediaSession.setMediaSessionMetadata;
};
let _setPreviewAudio = (beatmapSetId: int) => {
Audio.setSrc(audio, {j|https://b.ppy.sh/preview/$beatmapSetId.mp3|j});
};
let _setAudioSrc = (~audioFilePath, ~previewOffset=?, ()) => {
Audio.setSrc(audio, audioFilePath);
switch (previewOffset) {
| Some(offset) => Audio.setCurrentTime(audio, offset)
| None => ()
};
};
let pause = () => {
Audio.pause(audio);
};
let togglePlayPause = () => Audio.paused(audio) ? _play() : pause();
let setVolume = Audio.setVolume(audio);
[@react.component]
let make = (~children) => {
let playlistErrorCount = React.useRef(0);
let (playingState, setPlayingState) = React.useState(() => initialState);
let (playlist: playlist, setPlaylist) = React.useState(() => [||]);
let (playlistID: string, setPlaylistID) = React.useState(() => "");
let _canPlay = (offset: int) =>
if (playlist->Belt_Array.length > 0) {
let currentSongIndex =
playlist->Belt_Array.getIndexBy(item =>
item.id == playingState.beatmapSetId
);
switch (currentSongIndex) {
| None => None
| Some(index) =>
index + offset < playlist->Js_array.length && index + offset > (-1)
? Some(index + offset) : None
};
} else {
None;
};
let _canPlayNextSong = () => _canPlay(1);
let _canPlayPrevSong = () => _canPlay(-1);
let setPlaylist = (~beatmapPlaylist: playlist, ~playlistID=?, ()) => {
setPlaylist(_ => beatmapPlaylist);
switch (playlistID) {
| Some(id) => setPlaylistID(_ => id)
| None => ()
};
};
let _stop = () => {
pause();
setPlaylist(~beatmapPlaylist=[||], ~playlistID="", ());
};
let playFromPlaylist = (playlistindex: int) => {
let nextSong = playlist[playlistindex];
Audio.setSrc(audio, nextSong.path);
_updateMetadata({
id: nextSong.id,
title: nextSong.title,
artist: nextSong.artist,
});
_play();
setPlayingState(oldState =>
{
...oldState,
isPlaying: false,
beatmapSetId: nextSong.id,
title: nextSong.title,
artist: nextSong.artist,
}
);
};
let playNext = () => {
switch (_canPlayNextSong()) {
| Some(nextSong) => playFromPlaylist(nextSong)
| None => ()
};
};
let playPrevious = () => {
switch (_canPlayPrevSong()) {
| Some(prevSong) => playFromPlaylist(prevSong)
| None => ()
};
};
React.useEffect3(
() => {
IPCRenderer.send(
UPDATE_THUMB_BAR({
isPlaying: playingState.isPlaying,
canPlayNext: playingState.hasNext,
canPlayPrev: playingState.hasPrev,
}),
);
IPCRenderer.on("EXEC_PREV", playPrevious);
IPCRenderer.on("EXEC_NEXT", playNext);
Some(
() => {
IPCRenderer.removeListener("EXEC_PREV", playPrevious);
IPCRenderer.removeListener("EXEC_NEXT", playNext);
},
);
},
(playingState.isPlaying, playingState.hasNext, playingState.hasPrev),
);
React.useEffect0(() => {
MediaSession.setActionHandler(`play, Some(_play));
MediaSession.setActionHandler(`pause, Some(pause));
MediaSession.setActionHandler(`stop, Some(_stop));
IPCRenderer.on("EXEC_PLAY_PAUSE", togglePlayPause);
None;
});
let updateMediaHandlers = () => {
let next =
switch (_canPlayNextSong()) {
| Some(nextSongindex) =>
setPlayingState(prevState => {...prevState, hasNext: true});
Some(() => playFromPlaylist(nextSongindex));
| None =>
setPlayingState(prevState => {...prevState, hasNext: false});
None;
};
MediaSession.setActionHandler(`nexttrack, next);
let previous =
switch (_canPlayPrevSong()) {
| Some(prevSongindex) =>
setPlayingState(prevState => {...prevState, hasPrev: true});
Some(() => playFromPlaylist(prevSongindex));
| None =>
setPlayingState(prevState => {...prevState, hasPrev: false});
None;
};
MediaSession.setActionHandler(`previoustrack, previous);
};
React.useEffect2(
() => {
updateMediaHandlers();
None;
},
(playingState.beatmapSetId, playlist),
);
let setAudio =
(
~song: song,
~audioFilePath: option(string),
~previewOffset: option(int),
) => {
setPlaylist(~beatmapPlaylist=[||], ~playlistID="", ());
_updateMetadata(song);
switch (audioFilePath, previewOffset) {
| (None, None) => _setPreviewAudio(song.id)
| (None, Some(_)) => _setPreviewAudio(song.id)
| (Some(audioFilePath), None) => _setAudioSrc(~audioFilePath, ())
| (Some(audioFilePath), Some(previewOffset)) =>
_setAudioSrc(~audioFilePath, ~previewOffset, ())
};
_play();
setPlayingState(oldState =>
{
...oldState,
isPlaying: false,
beatmapSetId: song.id,
artist: song.artist,
title: song.title,
}
);
};
let setMuted = muted => {
Audio.setMuted(audio, muted);
setPlayingState(oldState => {...oldState, muted});
};
Audio.onended(audio, _e => {
switch (_canPlayNextSong()) {
| Some(nextSongindex) => playFromPlaylist(nextSongindex)
| None => DiscordRPC.clearActivity()
}
});
Audio.onpause(
audio,
_e => {
Js.log("PAUSEEEE");
setPlayingState(oldState => {...oldState, isPlaying: false});
},
);
Audio.onplay(audio, _e => {
setPlayingState(oldState => {...oldState, isPlaying: true})
});
Audio.oncanplay(audio, _e =>
setPlayingState(oldState => {...oldState, isPlaying: true})
);
Audio.onvolumechange(audio, e => {
setPlayingState(oldState => {...oldState, volume: e.target.volume})
});
Audio.onerror(
audio,
_e => {
setPlayingState(oldState => {...oldState, isPlaying: false});
let skipCount = playlistErrorCount->React.Ref.current;
if (skipCount > 12 || skipCount >= playlist->Js_array.length - 1) {
playlistErrorCount->React.Ref.setCurrent(0);
setPlaylist(~beatmapPlaylist=[||], ~playlistID="", ());
alert(
"Failed to play a song one or multiple times, please check your osu songs folder setting in the Settings section",
);
} else {
playNext();
playlistErrorCount->React.Ref.setCurrent(skipCount + 1);
};
},
);
let value = {
playingState,
pause,
setAudio,
setPlaylist,
setVolume,
togglePlayPause,
setMuted,
playlist,
playNext,
playPrevious,
playlistID,
};
<Provider value> children </Provider>;
};