import { Direction, Orientation, QAbstractSliderSignals, QIcon, QLabel } from "@nodegui/nodegui"; import { BoxView, GridColumn, GridRow, GridView, Slider, Text, useEventHandler } from "@nodegui/react-nodegui"; import React, { ReactElement, useContext, useEffect, useRef, useState } from "react"; import playerContext, { CurrentPlaylist } from "../context/playerContext"; import { shuffleArray } from "../helpers/shuffleArray"; import NodeMpv from "node-mpv"; import { getYoutubeTrack } from "../helpers/getYoutubeTrack"; import PlayerProgressBar from "./PlayerProgressBar"; import { random as shuffleIcon, play, pause, backward, forward, stop, heartRegular, heart } from "../icons"; import IconButton from "./shared/IconButton"; import showError from "../helpers/showError"; import useTrackReaction from "../hooks/useTrackReaction"; export const audioPlayer = new NodeMpv( { audio_only: true, auto_restart: true, time_update: 1, binary: process.env.MPV_EXECUTABLE ?? "/usr/bin/mpv", // debug: true, // verbose: true, }, ["--ytdl-raw-options-set=format=140,http-chunk-size=300000"] ); function Player(): ReactElement { const { currentTrack, currentPlaylist, setCurrentTrack, setCurrentPlaylist } = useContext(playerContext); const { reactToTrack, isFavorite } = useTrackReaction(); const [isPaused, setIsPaused] = useState(true); const [volume, setVolume] = useState(55); const [totalDuration, setTotalDuration] = useState(0); const [shuffle, setShuffle] = useState(false); const [realPlaylist, setRealPlaylist] = useState([]); const [isStopped, setIsStopped] = useState(false); const playlistTracksIds = currentPlaylist?.tracks.map((t) => t.track.id); const volumeHandler = useEventHandler( { sliderMoved: (value) => { setVolume(value); }, sliderReleased: () => { localStorage.setItem("volume", volume.toString()); }, }, [] ); const playerRunning = audioPlayer.isRunning(); const titleRef = useRef(); // initial Effect useEffect(() => { (async () => { try { if (!playerRunning) { await audioPlayer.start(); await audioPlayer.volume(volume); } } catch (error) { showError(error, "[Failed starting audio player]: "); } })(); return () => { if (playerRunning) { audioPlayer.quit().catch((e: any) => console.log(e)); } }; }, []); // track change effect useEffect(() => { (async () => { try { if (currentTrack && playerRunning) { const youtubeTrack = await getYoutubeTrack(currentTrack); await audioPlayer.load(youtubeTrack.youtube_uri, "replace"); await audioPlayer.play(); setIsPaused(false); } setIsStopped(false); } catch (error) { if (error.errcode !== 5) { setIsStopped(true); setIsPaused(true); } showError(error, "[Failure at track change]: "); } })(); }, [currentTrack]); // changing shuffle to default useEffect(() => { setShuffle(false); }, [currentPlaylist]) useEffect(() => { if (playerRunning) { audioPlayer.volume(volume); } }, [volume]); // for monitoring shuffle playlist useEffect(() => { if (currentPlaylist) { if (shuffle && realPlaylist.length === 0) { const shuffledTracks = shuffleArray(currentPlaylist.tracks); setRealPlaylist(currentPlaylist.tracks); setCurrentPlaylist({ ...currentPlaylist, tracks: shuffledTracks }); } else if (!shuffle && realPlaylist.length > 0) { setCurrentPlaylist({ ...currentPlaylist, tracks: realPlaylist }); } } }, [shuffle]); // live Effect useEffect(() => { if (playerRunning) { const statusListener = (status: { property: string; value: any }) => { if (status?.property === "duration") { setTotalDuration(status.value); } }; const stopListener = () => { setIsStopped(true); setIsPaused(true); // go to next track if (currentTrack && playlistTracksIds && currentPlaylist?.tracks.length !== 0) { const index = playlistTracksIds?.indexOf(currentTrack.id) + 1; setCurrentTrack(currentPlaylist?.tracks[index > playlistTracksIds.length - 1 ? 0 : index].track); } }; const pauseListener = () => { setIsPaused(true); }; const resumeListener = () => { setIsPaused(false); }; audioPlayer.on("status", statusListener); audioPlayer.on("stopped", stopListener); audioPlayer.on("paused", pauseListener); audioPlayer.on("resumed", resumeListener); return () => { audioPlayer.off("status", statusListener); audioPlayer.off("stopped", stopListener); audioPlayer.off("paused", pauseListener); audioPlayer.off("resumed", resumeListener); }; } }); const handlePlayPause = async () => { try { if ((await audioPlayer.isPaused()) && playerRunning) { await audioPlayer.play(); setIsStopped(false); setIsPaused(false); } else { await audioPlayer.pause(); setIsStopped(true); setIsPaused(true); } } catch (error) { showError(error, "[Track control failed]: "); } }; const prevOrNext = (constant: number) => { if (currentTrack && playlistTracksIds && currentPlaylist) { const index = playlistTracksIds.indexOf(currentTrack.id) + constant; setCurrentTrack(currentPlaylist.tracks[index > playlistTracksIds?.length - 1 ? 0 : index < 0 ? playlistTracksIds.length - 1 : index].track); } }; async function stopPlayback() { try { if (playerRunning) { setCurrentTrack(undefined); setCurrentPlaylist(undefined); await audioPlayer.stop(); } } catch (error) { showError(error, "[Failed at audio-player stop]: "); } } const artistsNames = currentTrack?.artists?.map((x) => x.name); return ( {artistsNames && currentTrack ? `

${currentTrack.name} - ${artistsNames[0]} ${artistsNames.length > 1 ? "feat. " + artistsNames.slice(1).join(", ") : ""}

` : `Oh, dear don't waste time`}
setShuffle(!shuffle) }} icon={new QIcon(shuffleIcon)} /> prevOrNext(-1) }} icon={new QIcon(backward)} /> prevOrNext(1) }} icon={new QIcon(forward)} />
); } export default Player;