mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-28 06:27:26 +01:00
Adds Play Internet Sound (#28183)
* death to the OGG * lint * adresses issues * nukes shells * sounds should still stop even if you muted someone * Update code/modules/admin/verbs/playsound.dm Co-authored-by: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com> Signed-off-by: Bm0n <92271472+Bm0n@users.noreply.github.com> * deconflict * as mentioned * minortypomoment * i prefer this wording * updates catches and moves admin logging.fixes span * fixes another weird edge case with muting admins * 516 support * linters? * further idiot proofing * typescript player update * more typescript player updates --------- Signed-off-by: Bm0n <92271472+Bm0n@users.noreply.github.com> Co-authored-by: Bmon <no@email.com> Co-authored-by: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
This commit is contained in:
co-authored by
AffectedArc07
Bmon
parent
8e6c2f3d8c
commit
8bec408c29
@@ -1,4 +1,5 @@
|
||||
GLOBAL_DATUM_INIT(filename_forbidden_chars, /regex, regex(@{""|[\\\n\t/?%*:|<>]|\.\."}, "g"))
|
||||
GLOBAL_DATUM_INIT(is_color, /regex, regex("^#\[0-9a-fA-F]{6}$"))
|
||||
GLOBAL_DATUM_INIT(regex_rgb_text, /regex, regex(@"^#?(([0-9a-fA-F]{8})|([0-9a-fA-F]{6})|([0-9a-fA-F]{3}))$"))
|
||||
GLOBAL_DATUM_INIT(is_http_protocol, /regex, regex("^https?://"))
|
||||
GLOBAL_PROTECT(filename_forbidden_chars)
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
var/list/region_map = list()
|
||||
/// Send a system toast on init completion?
|
||||
var/toast_on_init_complete = FALSE
|
||||
/// The URL for a ss13-yt-wrap server (https://github.com/Absolucy/ss13-yt-wrap) to use.
|
||||
var/ytdlp_url = null
|
||||
|
||||
/datum/configuration_section/system_configuration/load_data(list/data)
|
||||
// Use the load wrappers here. That way the default isnt made 'null' if you comment out the config line
|
||||
@@ -50,6 +52,7 @@
|
||||
CONFIG_LOAD_STR(internal_ip, data["internal_ip"])
|
||||
|
||||
CONFIG_LOAD_STR(override_map, data["override_map"])
|
||||
CONFIG_LOAD_STR(ytdlp_url, data["ytdlp_url"])
|
||||
|
||||
// Load region overrides
|
||||
if(islist(data["regional_servers"]))
|
||||
|
||||
@@ -81,7 +81,8 @@ GLOBAL_LIST_INIT(admin_verbs_sounds, list(
|
||||
/client/proc/play_server_sound,
|
||||
/client/proc/play_intercomm_sound,
|
||||
/client/proc/stop_global_admin_sounds,
|
||||
/client/proc/stop_sounds_global
|
||||
/client/proc/stop_sounds_global,
|
||||
/client/proc/play_web_sound
|
||||
))
|
||||
GLOBAL_LIST_INIT(admin_verbs_event, list(
|
||||
/client/proc/object_talk,
|
||||
|
||||
@@ -12,6 +12,8 @@ GLOBAL_LIST_EMPTY(sounds_cache)
|
||||
message_admins("[key_name_admin(src)] stopped admin sounds.", 1)
|
||||
for(var/mob/M in GLOB.player_list)
|
||||
M << awful_sound
|
||||
var/client/C = M.client
|
||||
C?.tgui_panel?.stop_music()
|
||||
|
||||
/client/proc/play_sound(S as sound)
|
||||
set category = "Event"
|
||||
@@ -127,3 +129,108 @@ GLOBAL_LIST_EMPTY(sounds_cache)
|
||||
SEND_SOUND(M, sound(null))
|
||||
var/client/C = M.client
|
||||
C?.tgui_panel?.stop_music()
|
||||
|
||||
/client/proc/play_web_sound()
|
||||
set category = "Event"
|
||||
set name = "Play Internet Sound"
|
||||
if(!check_rights(R_SOUNDS))
|
||||
return
|
||||
|
||||
if(!GLOB.configuration.system.ytdlp_url)
|
||||
to_chat(src, "<span class='boldwarning'>yt-dlp was not configured, action unavailable</span>") //Check config
|
||||
return
|
||||
|
||||
|
||||
var/web_sound_input = tgui_input_text(src, "Enter content URL (supported sites only, leave blank to stop playing)", "Play Internet Sound", null)
|
||||
if(istext(web_sound_input))
|
||||
var/web_sound_url = ""
|
||||
var/stop_web_sounds = FALSE
|
||||
var/list/music_extra_data = list()
|
||||
if(length(web_sound_input))
|
||||
web_sound_input = trim(web_sound_input)
|
||||
if(findtext(web_sound_input, ":") && !findtext(web_sound_input, GLOB.is_http_protocol))
|
||||
to_chat(src, "<span class='boldwarning'>Non-http(s) URIs are not allowed.</span>")
|
||||
to_chat(src, "<span class='warning'>For yt-dlp shortcuts like ytsearch: please use the appropriate full url from the website.</span>")
|
||||
return
|
||||
|
||||
// Prepare the body
|
||||
var/list/request_body = list("url" = web_sound_input)
|
||||
// Send the request off
|
||||
var/datum/http_request/media_poll_request = new()
|
||||
// The fact we are using GET with a body offends me
|
||||
media_poll_request.prepare(RUSTG_HTTP_METHOD_GET, GLOB.configuration.system.ytdlp_url, json_encode(request_body))
|
||||
// Start it off and wait
|
||||
media_poll_request.begin_async()
|
||||
UNTIL(media_poll_request.is_complete())
|
||||
var/datum/http_response/media_poll_response = media_poll_request.into_response()
|
||||
|
||||
if(media_poll_response.status_code == 200)
|
||||
var/list/data
|
||||
try
|
||||
data = json_decode(media_poll_response.body)
|
||||
catch(var/exception/e)
|
||||
to_chat(src, "<span class='boldwarning'>yt-dlp JSON parsing FAILED:</span>")
|
||||
to_chat(src, "<span class='warning'>[e]: [media_poll_response.body]</span>")
|
||||
return
|
||||
|
||||
if(data["sound_url"])
|
||||
web_sound_url = data["sound_url"]
|
||||
var/title = "[data["title"]]"
|
||||
var/webpage_url = title
|
||||
if(data["webpage_url"])
|
||||
webpage_url = "<a href=\"[data["webpage_url"]]\">[title]</a>"
|
||||
music_extra_data["start"] = data["start"]
|
||||
music_extra_data["end"] = data["end"]
|
||||
music_extra_data["link"] = data["webpage_url"]
|
||||
music_extra_data["title"] = data["title"]
|
||||
|
||||
var/res = tgui_alert(src, "Show the title of and link to this song to the players?\n[title]", "Show Info?", list("Yes", "No", "Cancel"))
|
||||
switch(res)
|
||||
if("Yes")
|
||||
log_admin("[key_name(src)] played web sound: [web_sound_input]")
|
||||
message_admins("[key_name(src)] played web sound: [web_sound_input]")
|
||||
to_chat(world, "<span class='boldannounceooc'>[src.ckey] played: [webpage_url]</span>")
|
||||
if("No")
|
||||
music_extra_data["link"] = "Song Link Hidden"
|
||||
music_extra_data["title"] = "Song Title Hidden"
|
||||
music_extra_data["artist"] = "Song Artist Hidden"
|
||||
log_admin("[key_name(src)] played web sound: [web_sound_input]")
|
||||
message_admins("[key_name(src)] played web sound: [web_sound_input]")
|
||||
to_chat(world, "<span class='boldannounceooc'>[src.ckey] played an internet sound</span>")
|
||||
if("Cancel")
|
||||
return
|
||||
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Internet Sound")
|
||||
|
||||
else
|
||||
to_chat(src, "<span class='boldwarning'>yt-dlp URL retrieval FAILED:</span>")
|
||||
to_chat(src, "<span class='warning'>[media_poll_response.body]</span>")
|
||||
|
||||
else //pressed ok with blank
|
||||
log_admin("[key_name(src)] stopped web sound")
|
||||
message_admins("[key_name(src)] stopped web sound")
|
||||
web_sound_url = null
|
||||
stop_web_sounds = TRUE
|
||||
|
||||
if(web_sound_url && !findtext(web_sound_url, GLOB.is_http_protocol))
|
||||
to_chat(src, "<span class='boldwarning'>BLOCKED: Content URL not using http(s) protocol</span>", confidential = TRUE)
|
||||
to_chat(src, "<span class='warning'>The media provider returned a content URL that isn't using the HTTP or HTTPS protocol</span>", confidential = TRUE)
|
||||
return
|
||||
|
||||
if(web_sound_url || stop_web_sounds)
|
||||
for(var/mob/M in GLOB.player_list)
|
||||
var/client/C = M.client
|
||||
var/this_uid = M.client.UID()
|
||||
if(stop_web_sounds)
|
||||
C.tgui_panel?.stop_music()
|
||||
continue
|
||||
if(C.prefs.sound & SOUND_MIDI)
|
||||
if(ckey in M.client.prefs.admin_sound_ckey_ignore)
|
||||
to_chat(C, "<span class='warning'>But [src.ckey] is muted locally in preferences!</span>")
|
||||
continue
|
||||
else
|
||||
C.tgui_panel?.play_music(web_sound_url, music_extra_data)
|
||||
to_chat(C, "<span class='warning'>(<a href='byond://?src=[this_uid];action=silenceSound'>SILENCE</a>) (<a href='byond://?src=[this_uid];action=muteAdmin&a=[ckey]'>ALWAYS SILENCE THIS ADMIN</a>)</span>")
|
||||
else
|
||||
to_chat(C, "<span class='warning'>But Admin MIDIs are disabled in preferences!</span>")
|
||||
return
|
||||
|
||||
@@ -192,10 +192,12 @@
|
||||
|
||||
if("silenceSound")
|
||||
usr.stop_sound_channel(CHANNEL_ADMIN)
|
||||
tgui_panel?.stop_music()
|
||||
return
|
||||
|
||||
if("muteAdmin")
|
||||
usr.stop_sound_channel(CHANNEL_ADMIN)
|
||||
tgui_panel?.stop_music()
|
||||
prefs.admin_sound_ckey_ignore |= href_list["a"]
|
||||
to_chat(usr, "You will no longer hear admin playsounds from <code>[href_list["a"]]</code>. To remove them, go to Preferences --> <code>Manage Admin Sound Mutes</code>.")
|
||||
prefs.save_preferences(src)
|
||||
|
||||
@@ -322,6 +322,7 @@
|
||||
set category = "Special Verbs"
|
||||
set desc = "Silence the current admin midi playing"
|
||||
usr.stop_sound_channel(CHANNEL_ADMIN)
|
||||
tgui_panel?.stop_music()
|
||||
to_chat(src, "The current admin midi has been silenced")
|
||||
|
||||
/datum/preference_toggle/toggle_runechat
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
/datum/tgui_panel/proc/play_music(url, extra_data)
|
||||
if(!is_ready())
|
||||
return
|
||||
if(!findtext(url, GLOB.is_http_protocol))
|
||||
return
|
||||
var/list/payload = list()
|
||||
if(length(extra_data) > 0)
|
||||
for(var/key in extra_data)
|
||||
|
||||
@@ -751,6 +751,8 @@ regional_servers = [
|
||||
]
|
||||
# Send a toast on server init completion. You probably dont need this on in production
|
||||
toast_on_init_complete = true
|
||||
# The URL for a ss13-yt-wrap server (https://github.com/Absolucy/ss13-yt-wrap) to use.
|
||||
#ytdlp_url = "http://ytdlpserverurlhere"
|
||||
|
||||
################################################################
|
||||
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { createLogger } from 'tgui/logging';
|
||||
|
||||
const logger = createLogger('AudioPlayer');
|
||||
|
||||
export class AudioPlayer {
|
||||
constructor() {
|
||||
// Set up the HTMLAudioElement node
|
||||
this.node = document.createElement('audio');
|
||||
this.node.style.setProperty('display', 'none');
|
||||
document.body.appendChild(this.node);
|
||||
// Set up other properties
|
||||
this.playing = false;
|
||||
this.volume = 1;
|
||||
this.options = {};
|
||||
this.onPlaySubscribers = [];
|
||||
this.onStopSubscribers = [];
|
||||
// Listen for playback start events
|
||||
this.node.addEventListener('canplaythrough', () => {
|
||||
logger.log('canplaythrough');
|
||||
this.playing = true;
|
||||
this.node.playbackRate = this.options.pitch || 1;
|
||||
this.node.currentTime = this.options.start || 0;
|
||||
this.node.volume = this.volume;
|
||||
this.node.play();
|
||||
for (let subscriber of this.onPlaySubscribers) {
|
||||
subscriber();
|
||||
}
|
||||
});
|
||||
// Listen for playback stop events
|
||||
this.node.addEventListener('ended', () => {
|
||||
logger.log('ended');
|
||||
this.stop();
|
||||
});
|
||||
// Listen for playback errors
|
||||
this.node.addEventListener('error', (e) => {
|
||||
if (this.playing) {
|
||||
logger.log('playback error', e.error);
|
||||
this.stop();
|
||||
}
|
||||
});
|
||||
// Check every second to stop the playback at the right time
|
||||
this.playbackInterval = setInterval(() => {
|
||||
if (!this.playing) {
|
||||
return;
|
||||
}
|
||||
const shouldStop = this.options.end > 0 && this.node.currentTime >= this.options.end;
|
||||
if (shouldStop) {
|
||||
this.stop();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (!this.node) {
|
||||
return;
|
||||
}
|
||||
this.node.stop();
|
||||
document.removeChild(this.node);
|
||||
clearInterval(this.playbackInterval);
|
||||
}
|
||||
|
||||
play(url, options = {}) {
|
||||
if (!this.node) {
|
||||
return;
|
||||
}
|
||||
logger.log('playing', url, options);
|
||||
this.options = options;
|
||||
this.node.src = url;
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (!this.node) {
|
||||
return;
|
||||
}
|
||||
if (this.playing) {
|
||||
for (let subscriber of this.onStopSubscribers) {
|
||||
subscriber();
|
||||
}
|
||||
}
|
||||
logger.log('stopping');
|
||||
this.playing = false;
|
||||
this.node.src = '';
|
||||
}
|
||||
|
||||
setVolume(volume) {
|
||||
if (!this.node) {
|
||||
return;
|
||||
}
|
||||
this.volume = volume;
|
||||
this.node.volume = volume;
|
||||
}
|
||||
|
||||
onPlay(subscriber) {
|
||||
if (!this.node) {
|
||||
return;
|
||||
}
|
||||
this.onPlaySubscribers.push(subscriber);
|
||||
}
|
||||
|
||||
onStop(subscriber) {
|
||||
if (!this.node) {
|
||||
return;
|
||||
}
|
||||
this.onStopSubscribers.push(subscriber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { createLogger } from 'tgui/logging';
|
||||
|
||||
const logger = createLogger('AudioPlayer');
|
||||
|
||||
type AudioOptions = {
|
||||
pitch?: number;
|
||||
start?: number;
|
||||
end?: number;
|
||||
};
|
||||
|
||||
export class AudioPlayer {
|
||||
element: HTMLAudioElement | null;
|
||||
options: AudioOptions;
|
||||
volume: number;
|
||||
|
||||
onPlaySubscribers: { (): void }[];
|
||||
onStopSubscribers: { (): void }[];
|
||||
|
||||
constructor() {
|
||||
this.element = null;
|
||||
|
||||
this.onPlaySubscribers = [];
|
||||
this.onStopSubscribers = [];
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.element = null;
|
||||
}
|
||||
|
||||
play(url: string, options: AudioOptions = {}) {
|
||||
if (this.element) {
|
||||
this.stop();
|
||||
}
|
||||
|
||||
this.options = options;
|
||||
|
||||
const audio = (this.element = new Audio(url));
|
||||
audio.volume = this.volume;
|
||||
audio.playbackRate = this.options.pitch || 1;
|
||||
|
||||
logger.log('playing', url, options);
|
||||
|
||||
audio.addEventListener('ended', () => {
|
||||
logger.log('ended');
|
||||
this.stop();
|
||||
});
|
||||
|
||||
audio.addEventListener('error', (error) => {
|
||||
logger.log('playback error', error);
|
||||
});
|
||||
|
||||
if (this.options.end) {
|
||||
audio.addEventListener('timeupdate', () => {
|
||||
if (this.options.end && this.options.end > 0 && audio.currentTime >= this.options.end) {
|
||||
this.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
audio.play();
|
||||
|
||||
this.onPlaySubscribers.forEach((subscriber) => subscriber());
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (!this.element) return;
|
||||
|
||||
logger.log('stopping');
|
||||
|
||||
this.element.pause();
|
||||
this.element = null;
|
||||
|
||||
this.onStopSubscribers.forEach((subscriber) => subscriber());
|
||||
}
|
||||
|
||||
setVolume(volume: number): void {
|
||||
this.volume = volume;
|
||||
|
||||
if (!this.element) return;
|
||||
|
||||
this.element.volume = volume;
|
||||
}
|
||||
|
||||
onPlay(subscriber: () => void): void {
|
||||
this.onPlaySubscribers.push(subscriber);
|
||||
}
|
||||
|
||||
onStop(subscriber: () => void): void {
|
||||
this.onStopSubscribers.push(subscriber);
|
||||
}
|
||||
}
|
||||
+129
-129
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user