Skip to content

Commit

Permalink
Merge pull request #19 from OMGDuke/18/per-game-volume
Browse files Browse the repository at this point in the history
Add per game volume, add a default to mute button, fix no music not saving
  • Loading branch information
OMGDuke authored Apr 7, 2023
2 parents e03f87e + d8d7957 commit a4c1cf4
Show file tree
Hide file tree
Showing 14 changed files with 382 additions and 131 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Localisation is now available via [Crowdin](https://crowdin.com/project/sdh-game

[![fr translation](https://img.shields.io/badge/dynamic/json?color=blue&label=French&style=flat&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLWZyIiB2aWV3Qm94PSIwIDAgNjQwIDQ4MCI+CiAgPGcgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2Utd2lkdGg9IjFwdCI+CiAgICA8cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAwaDY0MHY0ODBIMHoiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDI2NTQiIGQ9Ik0wIDBoMjEzLjN2NDgwSDB6Ii8+CiAgICA8cGF0aCBmaWxsPSIjY2UxMTI2IiBkPSJNNDI2LjcgMEg2NDB2NDgwSDQyNi43eiIvPgogIDwvZz4KPC9zdmc+Cg==&query=%24.progress.8.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15707857-578443.json)](https://crowdin.com/project/sdh-gamethememusic/fr)

[![de translation](https://img.shields.io/badge/dynamic/json?color=blue&label=German&style=flat&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLWRlIiB2aWV3Qm94PSIwIDAgNjQwIDQ4MCI+CiAgPHBhdGggZmlsbD0iI2ZmY2UwMCIgZD0iTTAgMzIwaDY0MHYxNjBIMHoiLz4KICA8cGF0aCBkPSJNMCAwaDY0MHYxNjBIMHoiLz4KICA8cGF0aCBmaWxsPSIjZDAwIiBkPSJNMCAxNjBoNjQwdjE2MEgweiIvPgo8L3N2Zz4K&query=%24.progress.3.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15707857-578443.json)](https://crowdin.com/project/sdh-gamethememusic/de)
[![de translation](https://img.shields.io/badge/dynamic/json?color=blue&label=German&style=flat&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLWRlIiB2aWV3Qm94PSIwIDAgNjQwIDQ4MCI+CiAgPHBhdGggZmlsbD0iI2ZmY2UwMCIgZD0iTTAgMzIwaDY0MHYxNjBIMHoiLz4KICA8cGF0aCBkPSJNMCAwaDY0MHYxNjBIMHoiLz4KICA8cGF0aCBmaWxsPSIjZDAwIiBkPSJNMCAxNjBoNjQwdjE2MEgweiIvPgo8L3N2Zz4K&query=%24.progress.3.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15707857-578443.json)](https://crowdin.com/project/sdh-gamethememusic/de) Provided by DarkSide1305

[![el translation](https://img.shields.io/badge/dynamic/json?color=blue&label=Greek&style=flat&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLWdyIiB2aWV3Qm94PSIwIDAgNjQwIDQ4MCI+CiAgPHBhdGggZmlsbD0iIzBkNWVhZiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMCAwaDY0MHY1My4zSDB6Ii8+CiAgPHBhdGggZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMCA1My4zaDY0MHY1My40SDB6Ii8+CiAgPHBhdGggZmlsbD0iIzBkNWVhZiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMCAxMDYuN2g2NDBWMTYwSDB6Ii8+CiAgPHBhdGggZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMCAxNjBoNjQwdjUzLjNIMHoiLz4KICA8cGF0aCBmaWxsPSIjMGQ1ZWFmIiBkPSJNMCAwaDI2Ni43djI2Ni43SDB6Ii8+CiAgPHBhdGggZmlsbD0iIzBkNWVhZiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMCAyMTMuM2g2NDB2NTMuNEgweiIvPgogIDxwYXRoIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTAgMjY2LjdoNjQwVjMyMEgweiIvPgogIDxwYXRoIGZpbGw9IiMwZDVlYWYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTAgMzIwaDY0MHY1My4zSDB6Ii8+CiAgPHBhdGggZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMCAzNzMuM2g2NDB2NTMuNEgweiIvPgogIDxnIGZpbGw9IiNmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlLXdpZHRoPSIxLjMiPgogICAgPHBhdGggZD0iTTEwNi43IDBIMTYwdjI2Ni43aC01My4zeiIvPgogICAgPHBhdGggZD0iTTAgMTA2LjdoMjY2LjdWMTYwSDB6Ii8+CiAgPC9nPgogIDxwYXRoIGZpbGw9IiMwZDVlYWYiIGQ9Ik0wIDQyNi43aDY0MFY0ODBIMHoiLz4KPC9zdmc+Cg==&query=%24.progress.4.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15707857-578443.json)](https://crowdin.com/project/sdh-gamethememusic/el)

Expand All @@ -87,7 +87,7 @@ Localisation is now available via [Crowdin](https://crowdin.com/project/sdh-game

[![ru translation](https://img.shields.io/badge/dynamic/json?color=blue&label=Russian&style=flat&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLXJ1IiB2aWV3Qm94PSIwIDAgNjQwIDQ4MCI+CiAgPGcgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2Utd2lkdGg9IjFwdCI+CiAgICA8cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAwaDY0MHY0ODBIMHoiLz4KICAgIDxwYXRoIGZpbGw9IiMwMDM5YTYiIGQ9Ik0wIDE2MGg2NDB2MzIwSDB6Ii8+CiAgICA8cGF0aCBmaWxsPSIjZDUyYjFlIiBkPSJNMCAzMjBoNjQwdjE2MEgweiIvPgogIDwvZz4KPC9zdmc+Cg==&query=%24.progress.19.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15707857-578443.json)](https://crowdin.com/project/sdh-gamethememusic/ru)

[![es-ES translation](https://img.shields.io/badge/dynamic/json?color=blue&label=Spanish&style=flat&logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iNjAwIj4KPHJlY3Qgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIGZpbGw9IiNjNjBiMWUiLz4KPHJlY3Qgd2lkdGg9IjkwMCIgaGVpZ2h0PSIzMDAiIHk9IjE1MCIgZmlsbD0iI2ZmYzQwMCIvPgo8L3N2Zz4=&query=%24.progress.6.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15707857-578443.json)](https://crowdin.com/project/sdh-gamethememusic/es-ES)
[![es-ES translation](https://img.shields.io/badge/dynamic/json?color=blue&label=Spanish&style=flat&logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iNjAwIj4KPHJlY3Qgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIGZpbGw9IiNjNjBiMWUiLz4KPHJlY3Qgd2lkdGg9IjkwMCIgaGVpZ2h0PSIzMDAiIHk9IjE1MCIgZmlsbD0iI2ZmYzQwMCIvPgo8L3N2Zz4=&query=%24.progress.6.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15707857-578443.json)](https://crowdin.com/project/sdh-gamethememusic/es-ES) Provided by OuterWinnie

[![es-419 translation](https://img.shields.io/badge/dynamic/json?color=blue&label=Spanish,%20Latin%20America&style=flat&logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iNjAwIj4KPHJlY3Qgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIGZpbGw9IiNjNjBiMWUiLz4KPHJlY3Qgd2lkdGg9IjkwMCIgaGVpZ2h0PSIzMDAiIHk9IjE1MCIgZmlsbD0iI2ZmYzQwMCIvPgo8L3N2Zz4=&query=%24.progress.5.data.translationProgress&url=https%3A%2F%2Fbadges.awesome-crowdin.com%2Fstats-15707857-578443.json)](https://crowdin.com/project/sdh-gamethememusic/es-419)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sdh-gamethememusic",
"version": "1.2.2",
"version": "1.3.0",
"description": "Play theme songs on your game pages",
"scripts": {
"build": "shx rm -rf dist && rollup -c",
Expand Down
11 changes: 9 additions & 2 deletions src/cache/musicCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ localforage.config({
})

type GameThemeMusicCache = {
videoId: string | undefined
videoId?: string | undefined
volume?: number
}

export async function updateCache(appId: number, newData: GameThemeMusicCache) {
const newCache = await localforage.setItem(appId.toString(), newData)
const oldCache = (await localforage.getItem(
appId.toString()
)) as GameThemeMusicCache | null
const newCache = await localforage.setItem(appId.toString(), {
...(oldCache || {}),
...newData
})
return newCache
}

Expand Down
91 changes: 63 additions & 28 deletions src/components/changeTheme/audioPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import React, { useEffect, useRef, 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'

export default function AudioPlayer({
handlePlay,
Expand All @@ -19,7 +21,7 @@ export default function AudioPlayer({
selected: boolean
selectNewAudio: (audio: {
title: string
videoId: string | undefined
videoId: string
audioUrl: string
}) => void
}) {
Expand Down Expand Up @@ -72,9 +74,7 @@ export default function AudioPlayer({
<div>
<Focusable
style={{
background: selected
? 'var(--main-light-blue-background)'
: 'var(--main-editor-bg-color)',
background: 'var(--main-editor-bg-color)',
borderRadius: '6px',
display: 'grid',
gridTemplateRows: '129px max-content max-content',
Expand All @@ -95,9 +95,7 @@ export default function AudioPlayer({
/>
<p
style={{
color: selected
? 'var(--main-editor-bg-color)'
: 'var(--main-editor-text-color)',
color: 'var(--main-editor-text-color)',
overflow: 'hidden',
textOverflow: 'ellipsis',
width: '230px',
Expand All @@ -107,29 +105,65 @@ export default function AudioPlayer({
{video.title}
</p>

<div
style={{
display: 'flex',
flexDirection: 'column',
gap: '6px',
width: '230px'
}}
>
<DialogButton
onClick={togglePlay}
disabled={loading}
focusable={!loading}
{loading ? (
<div
style={{
height: '85px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
{video.isPlaying ? t('stop') : t('play')}
</DialogButton>
<DialogButton
disabled={selected || loading}
focusable={!selected && !loading}
onClick={selectAudio}
<Spinner />
</div>
) : (
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: '6px',
width: '230px'
}}
>
{selected ? t('selected') : t('select')}
</DialogButton>
</div>
<DialogButton
onClick={togglePlay}
disabled={loading}
focusable={!loading}
>
{video.isPlaying ? t('stop') : t('play')}
</DialogButton>
<div style={{ position: 'relative' }}>
<DialogButton
disabled={selected || loading}
focusable={!selected && !loading}
onClick={selectAudio}
>
{selected ? t('selected') : t('select')}
</DialogButton>
{selected ? (
<div
style={{
height: '20px',
width: '20px',
position: 'absolute',
bottom: '-6px',
right: '-6px',
background: '#59bf40',
borderRadius: '50%',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<FaCheck />
</div>
) : (
''
)}
</div>
</div>
)}
</Focusable>
<audio
ref={audioRef}
Expand All @@ -138,6 +172,7 @@ export default function AudioPlayer({
controlsList="nodownload"
onPlay={() => audioRef.current?.play()}
onPause={() => audioRef.current?.pause()}
style={{ display: 'none' }}
></audio>
</div>
)
Expand Down
11 changes: 7 additions & 4 deletions src/components/changeTheme/changePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default function ChangePage({

function selectNewAudio(audio: {
title: string
videoId: string | undefined
videoId: string
audioUrl: string
}) {
setSelected(audio.videoId)
Expand All @@ -56,15 +56,18 @@ export default function ChangePage({

return (
<div>
<h2 style={{ margin: '0' }}>{appName}</h2>
<PanelSection title={t('search')}>
<h2 style={{ margin: '20px 0' }}>{appName}</h2>
<PanelSection>
<PanelSectionRow>
<Focusable
style={{
display: 'grid',
gap: '6px',
gridTemplateColumns: '2fr max-content max-content',
height: 'max-content'
height: 'max-content',
background: 'var(--main-editor-bg-color)',
borderRadius: '6px',
padding: '10px'
}}
>
<form
Expand Down
130 changes: 130 additions & 0 deletions src/components/changeTheme/gameSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import {
DialogButton,
Focusable,
ServerAPI,
SliderField,
PanelSectionRow,
useParams
} from 'decky-frontend-lib'
import React, { useEffect, useRef, useState } from 'react'
import { getCache, updateCache } from '../../cache/musicCache'

import { getAudioUrlFromVideoId, getAudio } from '../../actions/audio'
import useTranslations from '../../hooks/useTranslations'
import { useSettings } from '../../context/settingsContext'
import { FaVolumeUp } from 'react-icons/fa'
import Spinner from '../spinner'

export default function GameSettings({ serverAPI }: { serverAPI: ServerAPI }) {
const t = useTranslations()
const audioRef = useRef<HTMLAudioElement>(null)
const { state: settingsState } = useSettings()
const { appid } = useParams<{ appid: string }>()
const appDetails = appStore.GetAppOverviewByGameID(parseInt(appid))
const appName = appDetails?.display_name

const [currentAudio, setCurrentAudio] = useState<string>()
const [themeVolume, setThemeVolume] = useState(settingsState.volume)
const [isPlaying, setIsPlaying] = useState(false)
const [loading, setLoading] = useState(true)

useEffect(() => {
async function getData() {
setLoading(true)
const cache = await getCache(parseInt(appid))
if (cache?.volume) {
setThemeVolume(cache.volume)
}
if (cache?.videoId?.length) {
const newAudio = await getAudioUrlFromVideoId(serverAPI, {
title: '',
id: cache?.videoId
})
setCurrentAudio(newAudio)
} else {
const newAudio = await getAudio(serverAPI, appName as string)
setCurrentAudio(newAudio?.audioUrl)
}
setLoading(false)
}

getData()
}, [appid])

useEffect(() => {
if (audioRef.current) {
audioRef.current.volume = themeVolume
}
}, [audioRef, themeVolume])

function updateThemeVolume(newVol: number) {
setThemeVolume(newVol)
updateCache(parseInt(appid), { volume: newVol })
}

useEffect(() => {
if (audioRef.current) {
audioRef.current.currentTime = 0
isPlaying ? audioRef.current.play() : audioRef.current.pause()
}
}, [audioRef, isPlaying])

return (
<div>
<h2 style={{ margin: '20px 0' }}>{appName}</h2>

<Focusable
style={{
background: 'var(--main-editor-bg-color)',
borderRadius: '6px',
display: 'grid',
gridGap: '16px',
gridTemplateColumns: '2fr max-content max-content',
height: 'max-content',
padding: '10px 10px 10px 16px',
alignItems: 'center'
}}
>
<div style={{ padding: '0 10px' }}>
<PanelSectionRow>
<SliderField
layout="below"
bottomSeparator="none"
label={t('volume')}
description={t('gameVolumeDescription')}
value={themeVolume * 100}
onChange={(newVal) => updateThemeVolume(newVal / 100)}
min={0}
max={100}
step={1}
icon={<FaVolumeUp />}
editableValue
/>
</PanelSectionRow>
</div>
<DialogButton
onClick={() => setIsPlaying((v) => !v)}
disabled={loading}
focusable={!loading}
style={{ height: 'max-content' }}
>
{loading ? <Spinner /> : isPlaying ? t('stop') : t('play')}
</DialogButton>
<DialogButton
onClick={() => updateThemeVolume(settingsState.volume)}
style={{ height: 'max-content' }}
>
{t('resetVolume')}
</DialogButton>
</Focusable>

<audio
ref={audioRef}
src={currentAudio}
controlsList="nodownload"
loop
style={{ display: 'none' }}
></audio>
</div>
)
}
10 changes: 10 additions & 0 deletions src/components/changeTheme/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import AboutPage from './aboutPage'
import { SettingsProvider } from '../../context/settingsContext'
import { getYouTubeSearchResults } from '../../actions/audio'
import YouTubeVideo from '../../../types/YouTube'
import GameSettings from './gameSettings'

export default function ChangeTheme({ serverAPI }: { serverAPI: ServerAPI }) {
const [currentTab, setCurrentTab] = useState<string>()
Expand Down Expand Up @@ -82,6 +83,15 @@ export default function ChangeTheme({ serverAPI }: { serverAPI: ServerAPI }) {
),
id: 'change-music-tab'
},
{
title: t('gameSettings'),
content: (
<SettingsProvider>
<GameSettings serverAPI={serverAPI} />
</SettingsProvider>
),
id: 'game-settings-tab'
},
{ title: t('about'), content: <AboutPage />, id: 'about-tab' }
]}
/>
Expand Down
Loading

0 comments on commit a4c1cf4

Please sign in to comment.