diff --git a/package.json b/package.json index 0bb3198..f6fbaa9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sdh-gamethememusic", - "version": "1.3.1", + "version": "1.3.2", "description": "Play theme songs on your game pages", "scripts": { "build": "shx rm -rf dist && rollup -c", diff --git a/src/components/changeTheme/audioPlayer.tsx b/src/components/changeTheme/audioPlayer.tsx index 7774bc6..5d99d05 100644 --- a/src/components/changeTheme/audioPlayer.tsx +++ b/src/components/changeTheme/audioPlayer.tsx @@ -1,10 +1,11 @@ import { DialogButton, Focusable, ServerAPI } from 'decky-frontend-lib' -import React, { useEffect, useRef, useState } from 'react' +import React, { useEffect, useState } from 'react' import useTranslations from '../../hooks/useTranslations' import { getAudioUrlFromVideoId } from '../../actions/audio' import YouTubeVideo from '../../../types/YouTube' import { FaCheck } from 'react-icons/fa' import Spinner from '../spinner' +import useAudioPlayer from '../../hooks/useAudioPlayer' export default function AudioPlayer({ handlePlay, @@ -26,10 +27,11 @@ export default function AudioPlayer({ }) => void }) { const t = useTranslations() - const audioRef = useRef(null) const [loading, setLoading] = useState(true) const [audioUrl, setAudio] = useState() + const audioPlayer = useAudioPlayer(audioUrl) + useEffect(() => { async function getData() { setLoading(true) @@ -43,22 +45,19 @@ export default function AudioPlayer({ }, [video.id]) useEffect(() => { - if (audioRef.current) { - audioRef.current.volume = volume + if (audioPlayer.isReady) { + audioPlayer.setVolume(volume) } - }, [audioRef]) + }, [audioPlayer.isReady]) useEffect(() => { - if (audioRef.current) { - video.isPlaying ? audioRef.current.play() : audioRef.current.pause() + if (audioPlayer.isReady) { + video.isPlaying ? audioPlayer.play() : audioPlayer.stop() } }, [video.isPlaying]) function togglePlay() { - if (audioRef?.current) { - audioRef.current.currentTime = 0 - handlePlay(!video.isPlaying) - } + handlePlay(!video.isPlaying) } function selectAudio() { @@ -69,6 +68,7 @@ export default function AudioPlayer({ audioUrl: audioUrl }) } + if (!loading && !audioUrl) return <> return (
@@ -165,15 +165,6 @@ export default function AudioPlayer({
)} - ) } diff --git a/src/components/changeTheme/gameSettings.tsx b/src/components/changeTheme/gameSettings.tsx index 1123bb1..35202fc 100644 --- a/src/components/changeTheme/gameSettings.tsx +++ b/src/components/changeTheme/gameSettings.tsx @@ -6,7 +6,7 @@ import { PanelSectionRow, useParams } from 'decky-frontend-lib' -import React, { useEffect, useRef, useState } from 'react' +import React, { useEffect, useState } from 'react' import { getCache, updateCache } from '../../cache/musicCache' import { getAudioUrlFromVideoId, getAudio } from '../../actions/audio' @@ -14,10 +14,10 @@ import useTranslations from '../../hooks/useTranslations' import { useSettings } from '../../context/settingsContext' import { FaVolumeUp } from 'react-icons/fa' import Spinner from '../spinner' +import useAudioPlayer from '../../hooks/useAudioPlayer' export default function GameSettings({ serverAPI }: { serverAPI: ServerAPI }) { const t = useTranslations() - const audioRef = useRef(null) const { state: settingsState } = useSettings() const { appid } = useParams<{ appid: string }>() const appDetails = appStore.GetAppOverviewByGameID(parseInt(appid)) @@ -25,14 +25,15 @@ export default function GameSettings({ serverAPI }: { serverAPI: ServerAPI }) { const [currentAudio, setCurrentAudio] = useState() const [themeVolume, setThemeVolume] = useState(settingsState.volume) - const [isPlaying, setIsPlaying] = useState(false) const [loading, setLoading] = useState(true) + const audioPlayer = useAudioPlayer(currentAudio) + useEffect(() => { async function getData() { setLoading(true) const cache = await getCache(parseInt(appid)) - if (cache?.volume) { + if (typeof cache?.volume === 'number' && isFinite(cache.volume)) { setThemeVolume(cache.volume) } if (cache?.videoId?.length) { @@ -51,24 +52,12 @@ export default function GameSettings({ serverAPI }: { serverAPI: ServerAPI }) { getData() }, [appid]) - useEffect(() => { - if (audioRef.current) { - audioRef.current.volume = themeVolume - } - }, [audioRef, themeVolume]) - - function updateThemeVolume(newVol: number) { + function updateThemeVolume(newVol: number, reset?: boolean) { setThemeVolume(newVol) - updateCache(parseInt(appid), { volume: newVol }) + audioPlayer.setVolume(newVol) + updateCache(parseInt(appid), { volume: reset ? undefined : newVol }) } - useEffect(() => { - if (audioRef.current) { - audioRef.current.currentTime = 0 - isPlaying ? audioRef.current.play() : audioRef.current.pause() - } - }, [audioRef, isPlaying]) - return (

{appName}

@@ -103,28 +92,26 @@ export default function GameSettings({ serverAPI }: { serverAPI: ServerAPI }) {
setIsPlaying((v) => !v)} + onClick={audioPlayer.togglePlay} disabled={loading} focusable={!loading} style={{ height: 'max-content' }} > - {loading ? : isPlaying ? t('stop') : t('play')} + {loading ? ( + + ) : audioPlayer.isPlaying ? ( + t('stop') + ) : ( + t('play') + )} updateThemeVolume(settingsState.volume)} + onClick={() => updateThemeVolume(settingsState.volume, true)} style={{ height: 'max-content' }} > {t('resetVolume')} - - ) } diff --git a/src/components/themePlayer/index.tsx b/src/components/themePlayer/index.tsx index 85369bd..f001c74 100644 --- a/src/components/themePlayer/index.tsx +++ b/src/components/themePlayer/index.tsx @@ -1,9 +1,10 @@ import { ServerAPI, useParams } from 'decky-frontend-lib' -import React, { ReactElement, useEffect, useRef, useState } from 'react' +import React, { ReactElement, useEffect } from 'react' import useThemeMusic from '../../hooks/useThemeMusic' import { useSettings } from '../../context/settingsContext' import { getCache } from '../../cache/musicCache' +import useAudioPlayer from '../../hooks/useAudioPlayer' export default function ThemePlayer({ serverAPI @@ -12,44 +13,26 @@ export default function ThemePlayer({ }): ReactElement { const { state: settingsState } = useSettings() const { appid } = useParams<{ appid: string }>() - const audioRef = useRef(null) const { audio } = useThemeMusic(serverAPI, parseInt(appid)) - const [volume, setVolume] = useState(settingsState.volume) + const audioPlayer = useAudioPlayer(audio.audioUrl) useEffect(() => { async function getData() { const cache = await getCache(parseInt(appid)) if (typeof cache?.volume === 'number' && isFinite(cache.volume)) { - setVolume(cache.volume) + audioPlayer.setVolume(cache.volume) + } else { + audioPlayer.setVolume(settingsState.volume) } } getData() }, []) useEffect(() => { - if (audio?.audioUrl?.length && audioRef.current) { - audioRef.current.src = audio?.audioUrl - audioRef.current.volume = volume - audioRef.current.play() + if (audio?.audioUrl?.length && audioPlayer.isReady) { + audioPlayer.play() } - return () => { - if (audioRef.current) { - audioRef.current.pause() - audioRef.current.volume = 0 - audioRef.current.src = '' - } - } - }, [audio?.audioUrl, audioRef?.current, volume]) - - if (!audio?.audioUrl?.length) return <> + }, [audio?.audioUrl, audioPlayer.isReady]) - return ( - - ) + return <> } diff --git a/src/hooks/useAudioPlayer.ts b/src/hooks/useAudioPlayer.ts new file mode 100644 index 0000000..5aea2ba --- /dev/null +++ b/src/hooks/useAudioPlayer.ts @@ -0,0 +1,80 @@ +import { useEffect, useMemo, useState } from 'react' + +const useAudioPlayer = ( + audioUrl: string | undefined +): { + play: () => void + pause: () => void + stop: () => void + setVolume: (volume: number) => void + togglePlay: () => void + isPlaying: boolean + isReady: boolean +} => { + const audioPlayer: HTMLAudioElement = useMemo(() => { + return new Audio() + }, []) + + audioPlayer.oncanplay = () => { + setIsReady(true) + } + + const [isPlaying, setIsPlaying] = useState(false) + const [isReady, setIsReady] = useState(false) + + useEffect(() => { + if (audioUrl?.length) { + audioPlayer.src = audioUrl + audioPlayer.loop = true + } + }, [audioUrl]) + + useEffect(() => { + return () => { + unload() + } + }, []) + + function play() { + audioPlayer.play() + setIsPlaying(true) + } + + function pause() { + audioPlayer.pause() + setIsPlaying(false) + } + + function stop() { + audioPlayer.pause() + audioPlayer.currentTime = 0 + setIsPlaying(false) + } + + function togglePlay() { + isPlaying ? stop() : play() + } + + function setVolume(newVolume: number) { + audioPlayer.volume = newVolume + } + + function unload() { + stop() + audioPlayer.src = '' + setIsPlaying(false) + setIsReady(false) + } + + return { + play, + pause, + stop, + setVolume, + togglePlay, + isPlaying, + isReady + } +} + +export default useAudioPlayer diff --git a/src/hooks/useThemeMusic.ts b/src/hooks/useThemeMusic.ts index 9fb2ea4..caf929f 100644 --- a/src/hooks/useThemeMusic.ts +++ b/src/hooks/useThemeMusic.ts @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react' import { getAudio, getAudioUrlFromVideoId } from '../actions/audio' -import { getCache } from '../cache/musicCache' +import { getCache, updateCache } from '../cache/musicCache' import { useSettings } from '../context/settingsContext' const useThemeMusic = (serverAPI: ServerAPI, appId: number) => { @@ -39,6 +39,7 @@ const useThemeMusic = (serverAPI: ServerAPI, appId: number) => { if (!newAudio?.audioUrl?.length) { return setAudio({ videoId: '', audioUrl: '' }) } + await updateCache(appId, newAudio) return setAudio(newAudio) } }