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
🆑
fix: We're no strangers to bugs: Playing protected audio shouldn't crash
the server anymore!
/🆑
This commit is contained in:
Jeremiah
2025-05-04 16:39:43 +00:00
committed by GitHub
parent 417c9b1f4c
commit c0206e31ec
2 changed files with 37 additions and 4 deletions
+11
View File
@@ -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
+26 -4
View File
@@ -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());
}