From c0206e31ec43de4eddb63abc253e8a28592cc857 Mon Sep 17 00:00:00 2001 From: Jeremiah <42397676+jlsnow301@users.noreply.github.com> Date: Sun, 4 May 2025 09:39:43 -0700 Subject: [PATCH] Prevents protected audio from crashing the server with no survivors (#90975) ## About The Pull Request Fixes #90694 I believe the heart of this issue is that the audio player was trying to log an error object, but logs only take strings. This should prevent the blue screen and provide admin feedback ## Why It's Good For The Game - Everyone survives your fatal attempt to play king gizzard the lizard wizard - Better admin feedback why you cannot play aforementioned song ## Changelog :cl: fix: We're no strangers to bugs: Playing protected audio shouldn't crash the server anymore! /:cl: --- code/modules/tgui_panel/tgui_panel.dm | 11 +++++++++ tgui/packages/tgui-panel/audio/player.ts | 30 ++++++++++++++++++++---- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm index 2d270ac72aa..24b1e865ffc 100644 --- a/code/modules/tgui_panel/tgui_panel.dm +++ b/code/modules/tgui_panel/tgui_panel.dm @@ -12,6 +12,8 @@ var/datum/tgui_window/window var/broken = FALSE var/initialized_at + /// Each client notifies on protected playback, so this prevents spamming admins. + var/static/admins_warned = FALSE /datum/tgui_panel/New(client/client, id) src.client = client @@ -86,9 +88,18 @@ ), )) return TRUE + if(type == "audio/setAdminMusicVolume") client.admin_music_volume = payload["volume"] return TRUE + + if(type == "audio/protected") + if(!admins_warned) + message_admins(span_notice("Audio returned a protected playback error, likely due to being copyrighted.")) + admins_warned = TRUE + addtimer(VARSET_CALLBACK(src, admins_warned, FALSE), 10 SECONDS) + return TRUE + if(type == "telemetry") analyze_telemetry(payload) return TRUE diff --git a/tgui/packages/tgui-panel/audio/player.ts b/tgui/packages/tgui-panel/audio/player.ts index a916576c5b5..9f5f35987d2 100644 --- a/tgui/packages/tgui-panel/audio/player.ts +++ b/tgui/packages/tgui-panel/audio/player.ts @@ -14,6 +14,15 @@ type AudioOptions = { end?: number; }; +function isProtectedError(error: ErrorEvent): boolean { + return ( + typeof error === 'object' && + error !== null && + 'isTrusted' in error && + error.isTrusted + ); +} + export class AudioPlayer { element: HTMLAudioElement | null; options: AudioOptions; @@ -40,7 +49,13 @@ export class AudioPlayer { this.options = options; - const audio = (this.element = new Audio(url)); + const audio = new Audio(url); + if (!audio) { + logger.log('failed to create audio element'); + return; + } + this.element = audio; + audio.volume = this.volume; audio.playbackRate = this.options.pitch || 1; @@ -52,7 +67,11 @@ export class AudioPlayer { }); audio.addEventListener('error', (error) => { - logger.log('playback error', error); + if (isProtectedError(error)) { + Byond.sendMessage('audio/protected'); + } + logger.log('playback error:', JSON.stringify(error)); + this.stop(); }); if (this.options.end) { @@ -67,7 +86,10 @@ export class AudioPlayer { }); } - audio.play()?.catch((error) => logger.log('playback error', error)); + audio.play()?.catch(() => { + // no error is passed here, it's sent to the event listener + logger.log('playback failed'); + }); this.onPlaySubscribers.forEach((subscriber) => subscriber()); } @@ -78,7 +100,7 @@ export class AudioPlayer { logger.log('stopping'); this.element.pause(); - this.element = null; + this.destroy(); this.onStopSubscribers.forEach((subscriber) => subscriber()); }