From d19b35ec64e719539432c2d744d816e0536d2b27 Mon Sep 17 00:00:00 2001 From: beebls <102569435+beebls@users.noreply.github.com> Date: Sun, 18 Jun 2023 14:06:27 -0600 Subject: [PATCH 1/4] fix up user vars and implement global dfl --- audio_remoteinstall.py | 51 +++++++++++++++++++++--------------------- audio_utils.py | 31 ++++++++++++++++++++++++- main.py | 15 ++++++------- package-lock.json | 14 ++++++------ package.json | 2 +- rollup.config.js | 8 ++----- 6 files changed, 72 insertions(+), 49 deletions(-) diff --git a/audio_remoteinstall.py b/audio_remoteinstall.py index 87eff7a..8e19bf7 100644 --- a/audio_remoteinstall.py +++ b/audio_remoteinstall.py @@ -1,7 +1,5 @@ -import asyncio, json, tempfile, os -from audio_utils import Result, Log -from audio_utils import AUDIO_LOADER_VERSION, DECKY_HOME -import aiohttp +import asyncio, tempfile, os, zipfile, aiohttp +from audio_utils import Result, Log, get_pack_path, AUDIO_LOADER_VERSION async def run(command : str) -> str: proc = await asyncio.create_subprocess_shell(command, @@ -20,41 +18,42 @@ async def install(id : str, base_url : str) -> Result: url = f"{base_url}themes/{id}" - try: - async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False)) as session: + async with aiohttp.ClientSession(headers={"User-Agent": f"SDH-AudioLoader/{AUDIO_LOADER_VERSION}"}, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: + try: async with session.get(url) as resp: if resp.status != 200: raise Exception(f"Invalid status code {resp.status}") data = await resp.json() - except Exception as e: - return Result(False, str(e)) - - if (data["manifestVersion"] > AUDIO_LOADER_VERSION): - raise Exception("Manifest version of entry is unsupported by this version of Audio Loader") + except Exception as e: + return Result(False, str(e)) - download_url = f"{base_url}blobs/{data['download']['id']}" - tempDir = tempfile.TemporaryDirectory() + if (data["manifestVersion"] > AUDIO_LOADER_VERSION): + raise Exception("Manifest version of themedb entry is unsupported by this version of AudioLoader") - Log(f"Downloading {download_url} to {tempDir.name}...") - themeZipPath = os.path.join(tempDir.name, 'theme.zip') - try: - await run(f"curl \"{download_url}\" -L -o \"{themeZipPath}\"") - except Exception as e: - return Result(False, str(e)) + download_url = f"{base_url}blobs/{data['download']['id']}" + tempDir = tempfile.TemporaryDirectory() + + Log(f"Downloading {download_url} to {tempDir.name}...") + themeZipPath = os.path.join(tempDir.name, 'pack.zip') + try: + async with session.get(download_url) as resp: + if resp.status != 200: + raise Exception(f"Got {resp.status} code from '{download_url}'") + + with open(themeZipPath, "wb") as out: + out.write(await resp.read()) + + except Exception as e: + return Result(False, str(e)) Log(f"Unzipping {themeZipPath}") try: - await run(f"unzip -o \"{themeZipPath}\" -d \"{DECKY_HOME}/sounds\"") + with zipfile.ZipFile(themeZipPath, 'r') as zip: + zip.extractall(get_pack_path()) except Exception as e: return Result(False, str(e)) tempDir.cleanup() - # for x in data["dependencies"]: - # if x["name"] in local_themes: - # continue - - # await install(x["id"], base_url, local_themes) - return Result(True) \ No newline at end of file diff --git a/audio_utils.py b/audio_utils.py index 4c9cb93..5cf6c9f 100644 --- a/audio_utils.py +++ b/audio_utils.py @@ -1,14 +1,43 @@ import os from logging import getLogger +import platform Logger = getLogger("AUDIO_LOADER") AUDIO_LOADER_VERSION = 2 +HOME = os.getenv("HOME") +if not HOME: + HOME = os.path.expanduser("~") +PLATFORM_WIN = platform.system() == "Windows" DECKY_HOME = os.environ["DECKY_HOME"] # /home/user/homebrew DECKY_USER_HOME = os.environ["DECKY_USER_HOME"] # /home/user +DECKY_USER = os.getenv("DECKY_USER") def Log(text : str): Logger.info(f"[AUDIO_LOADER] {text}") +def get_user_home() -> str: + return HOME + +def get_pack_path() -> str: + return os.path.join(DECKY_HOME, "sounds") + +def get_steam_path() -> str: + if PLATFORM_WIN: + try: + import winreg + conn = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) + key = winreg.OpenKey(conn, "SOFTWARE\\Wow6432Node\\Valve\\Steam") + val, type = winreg.QueryValueEx(key, "InstallPath") + if type != winreg.REG_SZ: + raise Exception(f"Expected type {winreg.REG_SZ}, got {type}") + + Log(f"Got win steam install path: '{val}'") + return val + except Exception as e: + return "C:\\Program Files (x86)\\Steam" # Taking a guess here + else: + return f"{get_user_home()}/.local/share/Steam" + class Result: def __init__(self, success : bool, message : str = "Success"): self.success = success @@ -25,7 +54,7 @@ def to_dict(self): return {"success": self.success, "message": self.message} def store_path() -> str: - return os.path.join(DECKY_HOME, "sounds", "STORE") + return os.path.join(get_pack_path(), "STORE") def store_reads() -> dict: path = store_path() diff --git a/main.py b/main.py index 55aaf00..e352e4b 100644 --- a/main.py +++ b/main.py @@ -5,7 +5,7 @@ sys.path.append(os.path.dirname(__file__)) -from audio_utils import store_read as util_store_read, store_write as util_store_write, AUDIO_LOADER_VERSION, DECKY_HOME, DECKY_USER_HOME +from audio_utils import store_read as util_store_read, store_write as util_store_write, get_steam_path, get_pack_path, AUDIO_LOADER_VERSION, DECKY_HOME, DECKY_USER_HOME from audio_remoteinstall import install starter_config_data = { @@ -111,7 +111,7 @@ async def download_theme_from_url(self, id : str, url : str) -> dict: return (await install(id, url)).to_dict() async def get_config(self) -> object: - configPath = f"{DECKY_HOME}/sounds/config.json" + configPath = os.path.join(get_pack_path(), 'config.json') Log("Audio Loader - Fetching config file at {}".format(configPath)) if (os.path.exists(configPath)): @@ -122,7 +122,7 @@ async def get_config(self) -> object: return data async def set_config(self, configObj: object): - configPath = f"{DECKY_HOME}/sounds/config.json" + configPath = os.path.join(get_pack_path(), 'config.json') Log("Audio Loader - Setting config file at {}".format(configPath)) if (os.path.exists(configPath)): @@ -189,10 +189,9 @@ async def store_write(self, key : str, val : str) -> dict: return Result(True).to_dict() async def _load(self): - packsPath = f"{DECKY_HOME}/sounds" - symlinkPath = f"{DECKY_USER_HOME}/.local/share/Steam/steamui/sounds_custom" + packsPath = get_pack_path() - configPath = f"{DECKY_HOME}/sounds/config.json" + configPath = os.path.join(get_pack_path(), 'config.json') Log("Audio Loader - Finding sound packs...") self.soundPacks = [] @@ -200,8 +199,8 @@ async def _load(self): if (not os.path.exists(packsPath)): await create_folder(packsPath) - if (not os.path.exists(symlinkPath)): - await create_symlink(packsPath, symlinkPath) + if (not os.path.exists(os.path.join(get_steam_path(), 'steamui', 'sounds_custom'))): + await create_symlink(get_pack_path(), os.path.join(get_steam_path(), 'steamui', 'sounds_custom')) if (not os.path.exists(configPath)): await create_config(configPath) diff --git a/package-lock.json b/package-lock.json index b066cb7..26885aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "GPL-2.0-or-later", "dependencies": { "@types/lodash": "^4.14.191", - "decky-frontend-lib": "^3.21.0", + "decky-frontend-lib": "^3.21.1", "lodash": "^4.17.21", "react-icons": "^4.4.0" }, @@ -901,9 +901,9 @@ "dev": true }, "node_modules/decky-frontend-lib": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/decky-frontend-lib/-/decky-frontend-lib-3.21.0.tgz", - "integrity": "sha512-BZY+F9HKhjF0zthoBdQxbluZ/37UyjlzCCpcC6dQfOeAAPmjFDATs/FF/n9qo9B+NMepgWUXpStZR0becE9EOw==" + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/decky-frontend-lib/-/decky-frontend-lib-3.21.1.tgz", + "integrity": "sha512-30605ET9qqZ6St6I9WmMmLGgSrTIdMwo7xy85+lRaF1miUd2icOGEJjwnbVcZDdkal+1fJ3tNEDXlchVfG4TrA==" }, "node_modules/decode-uri-component": { "version": "0.2.2", @@ -3494,9 +3494,9 @@ "dev": true }, "decky-frontend-lib": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/decky-frontend-lib/-/decky-frontend-lib-3.21.0.tgz", - "integrity": "sha512-BZY+F9HKhjF0zthoBdQxbluZ/37UyjlzCCpcC6dQfOeAAPmjFDATs/FF/n9qo9B+NMepgWUXpStZR0becE9EOw==" + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/decky-frontend-lib/-/decky-frontend-lib-3.21.1.tgz", + "integrity": "sha512-30605ET9qqZ6St6I9WmMmLGgSrTIdMwo7xy85+lRaF1miUd2icOGEJjwnbVcZDdkal+1fJ3tNEDXlchVfG4TrA==" }, "decode-uri-component": { "version": "0.2.2", diff --git a/package.json b/package.json index 3f3ad2c..2ae1e8f 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@types/lodash": "^4.14.191", - "decky-frontend-lib": "^3.21.0", + "decky-frontend-lib": "^3.21.1", "lodash": "^4.17.21", "react-icons": "^4.4.0" }, diff --git a/rollup.config.js b/rollup.config.js index 41e68fe..68d7332 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -26,17 +26,13 @@ export default defineConfig({ styles(), ], context: "window", - external: [ - "react", - "react-dom", - // "decky-frontend-lib" - ], + external: ["react", "react-dom", "decky-frontend-lib"], output: { file: "dist/index.js", globals: { react: "SP_REACT", "react-dom": "SP_REACTDOM", - // "decky-frontend-lib": "DFL", + "decky-frontend-lib": "DFL", }, format: "iife", exports: "default", From dc227d760cf219d52cd0d1598aab67b7149f3b55 Mon Sep 17 00:00:00 2001 From: beebls <102569435+beebls@users.noreply.github.com> Date: Tue, 20 Jun 2023 14:32:42 -0600 Subject: [PATCH 2/4] Extract menu music to a single function Also now pauses/plays the music upon entering a game instead of killing and restarting --- src/audioPlayers/changeMenuMusic.ts | 48 +++++++++ src/audioPlayers/index.ts | 1 + src/index.tsx | 156 ++++++---------------------- 3 files changed, 80 insertions(+), 125 deletions(-) create mode 100644 src/audioPlayers/changeMenuMusic.ts create mode 100644 src/audioPlayers/index.ts diff --git a/src/audioPlayers/changeMenuMusic.ts b/src/audioPlayers/changeMenuMusic.ts new file mode 100644 index 0000000..d1c9782 --- /dev/null +++ b/src/audioPlayers/changeMenuMusic.ts @@ -0,0 +1,48 @@ +import { Pack } from "../classes"; + +export function changeMenuMusic( + newMusic: string, + menuMusic: HTMLAudioElement | null, + setGlobalState: (key: string, value: any) => void, + gamesRunning: any, + soundPacks: Pack[], + musicVolume: number +) { + setGlobalState("selectedMusic", newMusic); + if (menuMusic !== null) { + menuMusic.pause(); + menuMusic.currentTime = 0; + setGlobalState("menuMusic", null); + } + // This makes sure if you are in a game, music doesn't start playing + if (newMusic !== "None" && gamesRunning.length === 0) { + const currentPack = soundPacks.find((e) => e.name === newMusic); + let musicFileName = "menu_music.mp3"; + if (Object.keys(currentPack?.mappings || {}).includes("menu_music.mp3")) { + const randIndex = Math.trunc( + Math.random() * currentPack?.mappings["menu_music.mp3"].length + ); + musicFileName = currentPack?.mappings["menu_music.mp3"][randIndex]; + } + const newMenuMusic = new Audio( + `/sounds_custom/${ + currentPack?.truncatedPackPath || "error" + }/${musicFileName}` + ); + newMenuMusic.play(); + newMenuMusic.loop = true; + newMenuMusic.volume = musicVolume; + const setVolume = (value: number) => { + newMenuMusic.volume = value; + }; + // Update menuMusic in globalState after every change so that it reflects the changes the next time it checks + // @ts-ignore + window.AUDIOLOADER_MENUMUSIC = { + play: newMenuMusic.play.bind(newMenuMusic), + pause: newMenuMusic.pause.bind(newMenuMusic), + origVolume: newMenuMusic.volume, + setVolume: setVolume.bind(this), + }; + setGlobalState("menuMusic", newMenuMusic); + } +} diff --git a/src/audioPlayers/index.ts b/src/audioPlayers/index.ts new file mode 100644 index 0000000..5be28e6 --- /dev/null +++ b/src/audioPlayers/index.ts @@ -0,0 +1 @@ +export * from "./changeMenuMusic"; diff --git a/src/index.tsx b/src/index.tsx index a22eba2..4ee2d46 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -32,6 +32,7 @@ import { GlobalStateContextProvider, useGlobalState, } from "./state/GlobalState"; +import { changeMenuMusic } from "./audioPlayers"; const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { const { @@ -52,42 +53,14 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { }, []); function restartMusicPlayer(newMusic: string) { - if (menuMusic !== null) { - menuMusic.pause(); - menuMusic.currentTime = 0; - setGlobalState("menuMusic", null); - } - // This makes sure if you are in a game, music doesn't start playing - if (newMusic !== "None" && gamesRunning.length === 0) { - const currentPack = soundPacks.find((e) => e.name === newMusic); - let musicFileName = "menu_music.mp3"; - if (Object.keys(currentPack?.mappings || {}).includes("menu_music.mp3")) { - const randIndex = Math.trunc( - Math.random() * currentPack?.mappings["menu_music.mp3"].length - ); - musicFileName = currentPack?.mappings["menu_music.mp3"][randIndex]; - } - const newMenuMusic = new Audio( - `/sounds_custom/${ - currentPack?.truncatedPackPath || "error" - }/${musicFileName}` - ); - newMenuMusic.play(); - newMenuMusic.loop = true; - newMenuMusic.volume = musicVolume; - const setVolume = (value: number) => { - newMenuMusic.volume = value; - }; - // Update menuMusic in globalState after every change so that it reflects the changes the next time it checks - // @ts-ignore - window.AUDIOLOADER_MENUMUSIC = { - play: newMenuMusic.play.bind(newMenuMusic), - pause: newMenuMusic.pause.bind(newMenuMusic), - origVolume: newMenuMusic.volume, - setVolume: setVolume.bind(this), - }; - setGlobalState("menuMusic", newMenuMusic); - } + changeMenuMusic( + newMusic, + menuMusic, + (key, val) => setGlobalState(key, val), + gamesRunning, + soundPacks, + musicVolume + ); } function refetchLocalPacks() { @@ -202,8 +175,6 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { ?.data ?? -1 } onChange={async (option) => { - setGlobalState("selectedMusic", option.label as string); - const configObj = { selected_pack: activeSound, selected_music: option.label, @@ -225,7 +196,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { step={0.01} onChange={(value) => { setGlobalState("musicVolume", value); - menuMusic.origVolume = value; + menuMusic.volume = value; // @ts-ignore window.AUDIOLOADER_MENUMUSIC.volume = value; const configObj = { @@ -325,7 +296,6 @@ export default definePlugin((serverApi: ServerAPI) => { python.setStateClass(state); api.setServer(serverApi); api.setStateClass(state); - let menuMusic: any = null; python.resolve(python.storeRead("shortToken"), (token: string) => { if (token) { @@ -412,43 +382,17 @@ export default definePlugin((serverApi: ServerAPI) => { setGlobalState("soundVolume", configSoundVolume); setGlobalState("musicVolume", configMusicVolume); + const { soundPacks } = state.getPublicState(); + // Plays menu music initially - // TODO: Add check if game is currently running - if (configSelectedMusic !== "None") { - const { soundPacks } = state.getPublicState(); - const currentPack = soundPacks.find( - (e) => e.name === configSelectedMusic - ); - let musicFileName = "menu_music.mp3"; - if ( - Object.keys(currentPack?.mappings || {}).includes("menu_music.mp3") - ) { - const randIndex = Math.trunc( - Math.random() * currentPack?.mappings["menu_music.mp3"].length - ); - musicFileName = currentPack?.mappings["menu_music.mp3"][randIndex]; - } - menuMusic = new Audio( - `/sounds_custom/${ - currentPack?.truncatedPackPath || "error" - }/${musicFileName}` - ); - menuMusic.play(); - menuMusic.loop = true; - menuMusic.volume = configMusicVolume; - const setVolume = (value: number) => { - menuMusic.volume = value; - }; - // @ts-ignore - window.AUDIOLOADER_MENUMUSIC = { - play: menuMusic.play.bind(menuMusic), - pause: menuMusic.pause.bind(menuMusic), - origVolume: menuMusic.volume, - setVolume: setVolume.bind(this), - }; - console.log("play and loop ran", menuMusic); - setGlobalState("menuMusic", menuMusic); - } + changeMenuMusic( + configSelectedMusic, + null, + setGlobalState, + [], + soundPacks, + configMusicVolume + ); }); }); @@ -457,13 +401,8 @@ export default definePlugin((serverApi: ServerAPI) => { // Refer to the SteamClient.d.ts or just console.log(SteamClient) to see all of it's methods SteamClient.GameSessions.RegisterForAppLifetimeNotifications( (update: AppState) => { - const { - soundPacks, - menuMusic, - selectedMusic, - gamesRunning, - musicVolume, - } = state.getPublicState(); + const { menuMusic, selectedMusic, gamesRunning } = + state.getPublicState(); const setGlobalState = state.setGlobalState.bind(state); if (selectedMusic !== "None") { if (update.bRunning) { @@ -471,54 +410,21 @@ export default definePlugin((serverApi: ServerAPI) => { setGlobalState("gamesRunning", [...gamesRunning, update.unAppID]); if (menuMusic != null) { menuMusic.pause(); - menuMusic.currentTime = 0; - setGlobalState("menuMusic", null); + // menuMusic.currentTime = 0; + // setGlobalState("menuMusic", null); } } else { - // This happens when an app is closed - setGlobalState( - "gamesRunning", - gamesRunning.filter((e) => e !== update.unAppID) + const filteredGames = gamesRunning.filter( + (e) => e !== update.unAppID ); + // This happens when an app is closed + setGlobalState("gamesRunning", filteredGames); // I'm re-using the filter here because I don't believe the getPublicState() method will update the values if they are changed - if (gamesRunning.filter((e) => e !== update.unAppID).length === 0) { - const currentMusic = soundPacks.find( - (e) => e.name === selectedMusic - ); - let musicFileName = "menu_music.mp3"; - if ( - Object.keys(currentMusic?.mappings || {}).includes( - "menu_music.mp3" - ) - ) { - const randIndex = Math.trunc( - Math.random() * - currentMusic?.mappings["menu_music.mp3"].length - ); - musicFileName = - currentMusic?.mappings["menu_music.mp3"][randIndex]; + if (filteredGames.length === 0) { + if (menuMusic !== null) { + menuMusic.play(); } - const newMenuMusic = new Audio( - `/sounds_custom/${ - currentMusic?.truncatedPackPath || "error" - }/${musicFileName}` - ); - newMenuMusic.play(); - newMenuMusic.loop = true; - newMenuMusic.volume = musicVolume; - const setVolume = (value: number) => { - newMenuMusic.volume = value; - }; - // Update menuMusic in globalState after every change so that it reflects the changes the next time it checks - // @ts-ignore - window.AUDIOLOADER_MENUMUSIC = { - play: newMenuMusic.play.bind(newMenuMusic), - pause: newMenuMusic.pause.bind(newMenuMusic), - origVolume: newMenuMusic.volume, - setVolume: setVolume.bind(this), - }; - setGlobalState("menuMusic", newMenuMusic); } } } From 30bb9a651affcab5b70334f266e204818e6db682 Mon Sep 17 00:00:00 2001 From: beebls <102569435+beebls@users.noreply.github.com> Date: Tue, 20 Jun 2023 16:19:15 -0600 Subject: [PATCH 3/4] Add working intros to music packs music packs can now contain a `intro_music.mp3` or a mapped version of it, this intro will play once before the looping menu music. Additionally, this bumps the targeted manifest version to 3 --- .prettierrc | 1 + audio_utils.py | 2 +- main.py | 6 ++ src/audioPlayers/changeMenuMusic.ts | 101 ++++++++++++++++++++-------- src/classes.ts | 5 +- src/index.tsx | 90 +++++++++---------------- 6 files changed, 116 insertions(+), 89 deletions(-) diff --git a/.prettierrc b/.prettierrc index 2d08c14..c6015ef 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,7 @@ { "singleQuote": false, "tabWidth": 2, + "printWidth": 100, "semi": true, "trailingComma": "es5", "bracketSameLine": false diff --git a/audio_utils.py b/audio_utils.py index 5cf6c9f..c805bb5 100644 --- a/audio_utils.py +++ b/audio_utils.py @@ -3,7 +3,7 @@ import platform Logger = getLogger("AUDIO_LOADER") -AUDIO_LOADER_VERSION = 2 +AUDIO_LOADER_VERSION = 3 HOME = os.getenv("HOME") if not HOME: HOME = os.path.expanduser("~") diff --git a/main.py b/main.py index e352e4b..f843fdb 100644 --- a/main.py +++ b/main.py @@ -68,6 +68,7 @@ def __init__(self, packPath : str, json : dict, truncatedPackPath: str): self.mappings = json["mappings"] if ("mappings" in json) else {} self.music = bool(json["music"]) if ("music" in json) else False self.id = json["id"] if ("id" in json) else self.name + if (AUDIO_LOADER_VERSION < self.require): raise Exception("Audio Loader - {} requires Audio Loader version {} but only version {} is installed".format(self.name, self.require, AUDIO_LOADER_VERSION)) @@ -75,6 +76,10 @@ def __init__(self, packPath : str, json : dict, truncatedPackPath: str): self.packPath = packPath self.truncatedPackPath = truncatedPackPath + self.has_intro = False + if (self.music == True): + self.has_intro = os.path.exists(os.path.join(packPath, self.mappings["intro_music.mp3"] if "intro_music.mp3" in self.mappings else "intro_music.mp3")) + async def delete(self) -> Result: try: shutil.rmtree(self.packPath) @@ -92,6 +97,7 @@ def to_dict(self) -> dict: "ignore": self.ignore, "mappings": self.mappings, "music": self.music, + "hasIntro": self.has_intro, "packPath": self.packPath, "truncatedPackPath": self.truncatedPackPath, "id": self.id diff --git a/src/audioPlayers/changeMenuMusic.ts b/src/audioPlayers/changeMenuMusic.ts index d1c9782..1c77b0e 100644 --- a/src/audioPlayers/changeMenuMusic.ts +++ b/src/audioPlayers/changeMenuMusic.ts @@ -1,4 +1,16 @@ -import { Pack } from "../classes"; +import { Mappings, Pack } from "../classes"; + +function findMapping(origFileName: string, mappings: Mappings | undefined): string { + if (mappings && Object.keys(mappings || {}).includes(origFileName)) { + const randIndex = Math.trunc(Math.random() * mappings[origFileName].length); + return mappings[origFileName][randIndex]; + } + return origFileName; +} + +function createFullPath(fileName: string, truncatedPackPath: string | undefined) { + return `/sounds_custom/${truncatedPackPath || "error"}/${fileName}`; +} export function changeMenuMusic( newMusic: string, @@ -9,40 +21,73 @@ export function changeMenuMusic( musicVolume: number ) { setGlobalState("selectedMusic", newMusic); + + // Stops the old music if (menuMusic !== null) { menuMusic.pause(); menuMusic.currentTime = 0; setGlobalState("menuMusic", null); } - // This makes sure if you are in a game, music doesn't start playing + + // Start the new one, if the user selected a music at all if (newMusic !== "None" && gamesRunning.length === 0) { const currentPack = soundPacks.find((e) => e.name === newMusic); - let musicFileName = "menu_music.mp3"; - if (Object.keys(currentPack?.mappings || {}).includes("menu_music.mp3")) { - const randIndex = Math.trunc( - Math.random() * currentPack?.mappings["menu_music.mp3"].length - ); - musicFileName = currentPack?.mappings["menu_music.mp3"][randIndex]; - } - const newMenuMusic = new Audio( - `/sounds_custom/${ - currentPack?.truncatedPackPath || "error" - }/${musicFileName}` + + const musicFilePath = createFullPath( + findMapping("menu_music.mp3", currentPack?.mappings), + currentPack?.truncatedPackPath ); - newMenuMusic.play(); - newMenuMusic.loop = true; - newMenuMusic.volume = musicVolume; - const setVolume = (value: number) => { - newMenuMusic.volume = value; - }; - // Update menuMusic in globalState after every change so that it reflects the changes the next time it checks - // @ts-ignore - window.AUDIOLOADER_MENUMUSIC = { - play: newMenuMusic.play.bind(newMenuMusic), - pause: newMenuMusic.pause.bind(newMenuMusic), - origVolume: newMenuMusic.volume, - setVolume: setVolume.bind(this), - }; - setGlobalState("menuMusic", newMenuMusic); + const introFilePath = createFullPath( + findMapping("intro_music.mp3", currentPack?.mappings), + currentPack?.truncatedPackPath + ); + + let newMenuMusic: HTMLAudioElement; + + // If there is an intro, it must play that, and add an onended listener to change to the normal music + // If there's no intro, it can just go straight to playAndLoopMenuMusic() + if (currentPack?.hasIntro) { + function handleIntroEnd() { + newMenuMusic.currentTime = 0; + newMenuMusic.src = musicFilePath; + newMenuMusic.onended = null; + playAndLoopMenuMusic(); + } + newMenuMusic = new Audio(introFilePath); + newMenuMusic.onended = handleIntroEnd; + newMenuMusic.volume = musicVolume; + newMenuMusic.play(); + createWindowObject(newMenuMusic); + } else { + newMenuMusic = new Audio(musicFilePath); + playAndLoopMenuMusic(); + } + + function playAndLoopMenuMusic(wasFromIntro: boolean = false) { + newMenuMusic.play(); + newMenuMusic.loop = true; + // If someone has changed the volume before the intro ended, this would overwrite it with the original as this function does not have up to date data + if (!wasFromIntro) { + newMenuMusic.volume = musicVolume; + } + createWindowObject(newMenuMusic); + } + + // Self explanatory, just extracted it to a function so that I can run it once on the intro, and once on the menu music + // TODO: Not actually sure if it needs to be set the 2nd time + function createWindowObject(menuMusic: HTMLAudioElement) { + const setVolume = (value: number) => { + menuMusic.volume = value; + }; + // @ts-ignore + window.AUDIOLOADER_MENUMUSIC = { + play: menuMusic.play.bind(menuMusic), + pause: menuMusic.pause.bind(menuMusic), + origVolume: menuMusic.volume, + // @ts-ignore + setVolume: setVolume.bind(this), + }; + setGlobalState("menuMusic", menuMusic); + } } } diff --git a/src/classes.ts b/src/classes.ts index c6ed2cb..be11fbd 100644 --- a/src/classes.ts +++ b/src/classes.ts @@ -8,6 +8,7 @@ export interface Pack { packPath: string; // This contains the full path from root to the pack truncatedPackPath: string; // This is the relative path from ~/homebrew/sounds music: boolean; + hasIntro: boolean; id: string; } @@ -58,7 +59,9 @@ export type DeckSound = | "steam_chatroom_notification.m4a" | "ui_steam_message_old_smooth.m4a" | "ui_steam_smoother_friend_join.m4a" - | "ui_steam_smoother_friend_online.m4a"; + | "ui_steam_smoother_friend_online.m4a" + | "menu_music.mp3" + | "intro_music.mp3"; export type Mappings = | { diff --git a/src/index.tsx b/src/index.tsx index 4ee2d46..cb01f74 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -27,11 +27,7 @@ import { } from "./pack-manager"; import * as python from "./python"; import * as api from "./api"; -import { - GlobalState, - GlobalStateContextProvider, - useGlobalState, -} from "./state/GlobalState"; +import { GlobalState, GlobalStateContextProvider, useGlobalState } from "./state/GlobalState"; import { changeMenuMusic } from "./audioPlayers"; const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { @@ -95,8 +91,8 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { return ( - AudioLoader failed to initialize, try reloading, and if that doesn't - work, try restarting your deck. + AudioLoader failed to initialize, try reloading, and if that doesn't work, try restarting + your deck. ); @@ -122,8 +118,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { // activeSound now stores a string, this just finds the corresponding option for the label // the "?? -2" is there incase find returns undefined (the currently selected theme was deleted or something) // it NEEDS to be a nullish coalescing operator because 0 needs to be treated as true - SoundPackDropdownOptions.find((e) => e.label === activeSound) - ?.data ?? -1 + SoundPackDropdownOptions.find((e) => e.label === activeSound)?.data ?? -1 } onChange={async (option) => { setGlobalState("activeSound", option.label as string); @@ -147,10 +142,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { max={1} step={0.01} onChange={(value) => { - gainNode.gain.setValueAtTime( - value, - gainNode.context.currentTime + 0.01 - ); + gainNode.gain.setValueAtTime(value, gainNode.context.currentTime + 0.01); // gainNode.gain.value = value; setGlobalState("soundVolume", value); const configObj = { @@ -171,8 +163,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { menuLabel="Music Pack" rgOptions={MusicPackDropdownOptions} selectedOption={ - MusicPackDropdownOptions.find((e) => e.label === selectedMusic) - ?.data ?? -1 + MusicPackDropdownOptions.find((e) => e.label === selectedMusic)?.data ?? -1 } onChange={async (option) => { const configObj = { @@ -207,11 +198,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { }; python.setConfig(configObj); }} - icon={ - - } + icon={} /> @@ -348,9 +335,7 @@ export default definePlugin((serverApi: ServerAPI) => { } // Mapping check if (Object.keys(currentPack?.mappings || {}).includes(soundName)) { - const randIndex = Math.trunc( - Math.random() * currentPack?.mappings[soundName].length - ); + const randIndex = Math.trunc(Math.random() * currentPack?.mappings[soundName].length); const mappedFileName = currentPack?.mappings[soundName][randIndex]; newSoundURL = `/sounds_custom/${ currentPack?.truncatedPackPath || "/error" @@ -385,51 +370,39 @@ export default definePlugin((serverApi: ServerAPI) => { const { soundPacks } = state.getPublicState(); // Plays menu music initially - changeMenuMusic( - configSelectedMusic, - null, - setGlobalState, - [], - soundPacks, - configMusicVolume - ); + changeMenuMusic(configSelectedMusic, null, setGlobalState, [], soundPacks, configMusicVolume); }); }); const AppStateRegistrar = // SteamClient is something exposed by the SP tab of SteamUI, it's not a decky-frontend-lib thingy, but you can still call it normally // Refer to the SteamClient.d.ts or just console.log(SteamClient) to see all of it's methods - SteamClient.GameSessions.RegisterForAppLifetimeNotifications( - (update: AppState) => { - const { menuMusic, selectedMusic, gamesRunning } = - state.getPublicState(); - const setGlobalState = state.setGlobalState.bind(state); - if (selectedMusic !== "None") { - if (update.bRunning) { - // Because gamesRunning is in globalState, array methods like push and splice don't work - setGlobalState("gamesRunning", [...gamesRunning, update.unAppID]); - if (menuMusic != null) { - menuMusic.pause(); - // menuMusic.currentTime = 0; - // setGlobalState("menuMusic", null); - } - } else { - const filteredGames = gamesRunning.filter( - (e) => e !== update.unAppID - ); - // This happens when an app is closed - setGlobalState("gamesRunning", filteredGames); + SteamClient.GameSessions.RegisterForAppLifetimeNotifications((update: AppState) => { + const { menuMusic, selectedMusic, gamesRunning } = state.getPublicState(); + const setGlobalState = state.setGlobalState.bind(state); + if (selectedMusic !== "None") { + if (update.bRunning) { + // Because gamesRunning is in globalState, array methods like push and splice don't work + setGlobalState("gamesRunning", [...gamesRunning, update.unAppID]); + if (menuMusic != null) { + menuMusic.pause(); + // menuMusic.currentTime = 0; + // setGlobalState("menuMusic", null); + } + } else { + const filteredGames = gamesRunning.filter((e) => e !== update.unAppID); + // This happens when an app is closed + setGlobalState("gamesRunning", filteredGames); - // I'm re-using the filter here because I don't believe the getPublicState() method will update the values if they are changed - if (filteredGames.length === 0) { - if (menuMusic !== null) { - menuMusic.play(); - } + // I'm re-using the filter here because I don't believe the getPublicState() method will update the values if they are changed + if (filteredGames.length === 0) { + if (menuMusic !== null) { + menuMusic.play(); } } } } - ); + }); serverApi.routerHook.addRoute("/audiopack-manager", () => ( @@ -452,8 +425,7 @@ export default definePlugin((serverApi: ServerAPI) => { ), icon: , onDismount: () => { - const { menuMusic, soundPatchInstance, volumePatchInstance } = - state.getPublicState(); + const { menuMusic, soundPatchInstance, volumePatchInstance } = state.getPublicState(); const setGlobalState = state.setGlobalState.bind(state); if (menuMusic != null) { menuMusic.pause(); From 176d81e58c13352ad8489a7e3da28f3422128b07 Mon Sep 17 00:00:00 2001 From: beebls <102569435+beebls@users.noreply.github.com> Date: Thu, 22 Jun 2023 14:02:12 -0600 Subject: [PATCH 4/4] fix mappings with intro music --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index f843fdb..f60b020 100644 --- a/main.py +++ b/main.py @@ -78,7 +78,7 @@ def __init__(self, packPath : str, json : dict, truncatedPackPath: str): self.has_intro = False if (self.music == True): - self.has_intro = os.path.exists(os.path.join(packPath, self.mappings["intro_music.mp3"] if "intro_music.mp3" in self.mappings else "intro_music.mp3")) + self.has_intro = "intro_music.mp3" in self.mappings or os.path.exists(os.path.join(packPath, "intro_music.mp3")) async def delete(self) -> Result: try: