mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 02:09:41 +00:00
Merge pull request #1235 from VOREStation/mediamanager
Media Manager - HTML5 Antilag Jukebox
This commit is contained in:
@@ -11,8 +11,7 @@
|
||||
switch(alert("Travel back to ss13?",,"Yes","No"))
|
||||
if("Yes")
|
||||
if(user.z != src.z) return
|
||||
user.loc.loc.Exited(user)
|
||||
user.loc = pick(latejoin)
|
||||
user.forceMove(pick(latejoin))
|
||||
if("No")
|
||||
return
|
||||
|
||||
|
||||
@@ -416,13 +416,13 @@ var/global/datum/controller/occupations/job_master
|
||||
if(!S)
|
||||
S = locate("start*[rank]") // use old stype
|
||||
if(istype(S, /obj/effect/landmark/start) && istype(S.loc, /turf))
|
||||
H.loc = S.loc
|
||||
H.forceMove(S.loc)
|
||||
else
|
||||
LateSpawn(H, rank)
|
||||
|
||||
// Moving wheelchair if they have one
|
||||
if(H.buckled && istype(H.buckled, /obj/structure/bed/chair/wheelchair))
|
||||
H.buckled.loc = H.loc
|
||||
H.buckled.forceMove(H.loc)
|
||||
H.buckled.set_dir(H.dir)
|
||||
|
||||
// If they're head, give them the account info for their department
|
||||
@@ -627,12 +627,12 @@ var/global/datum/controller/occupations/job_master
|
||||
|
||||
if(spawnpos && istype(spawnpos))
|
||||
if(spawnpos.check_job_spawning(rank))
|
||||
H.loc = pick(spawnpos.turfs)
|
||||
H.forceMove(pick(spawnpos.turfs))
|
||||
. = spawnpos.msg
|
||||
else
|
||||
H << "Your chosen spawnpoint ([spawnpos.display_name]) is unavailable for your chosen job. Spawning you at the Arrivals shuttle instead."
|
||||
H.loc = pick(latejoin)
|
||||
H.forceMove(pick(latejoin))
|
||||
. = "has arrived on the station"
|
||||
else
|
||||
H.loc = pick(latejoin)
|
||||
H.forceMove(pick(latejoin))
|
||||
. = "has arrived on the station"
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
datum/track
|
||||
var/title
|
||||
var/sound
|
||||
//
|
||||
// Media Player Jukebox
|
||||
// Rewritten by Leshana from existing Polaris code, merging in D2K5 and N3X15 work
|
||||
//
|
||||
|
||||
datum/track/New(var/title_name, var/audio)
|
||||
title = title_name
|
||||
sound = audio
|
||||
#define JUKEMODE_NEXT 1 // Advance to next song in the track list
|
||||
#define JUKEMODE_RANDOM 2 // Not shuffle, randomly picks next each time.
|
||||
#define JUKEMODE_REPEAT_SONG 3 // Play the same song over and over
|
||||
#define JUKEMODE_PLAY_ONCE 4 // Play, then stop.
|
||||
|
||||
/obj/machinery/media/jukebox/
|
||||
name = "space jukebox"
|
||||
@@ -19,33 +21,17 @@ datum/track/New(var/title_name, var/audio)
|
||||
active_power_usage = 100
|
||||
circuit = /obj/item/weapon/circuitboard/jukebox
|
||||
|
||||
var/playing = 0
|
||||
|
||||
// Vars for hacking
|
||||
var/datum/wires/jukebox/wires = null
|
||||
var/hacked = 0 // Whether to show the hidden songs or not
|
||||
var/freq = 0
|
||||
var/freq = 0 // Currently no effect, will return in phase II of mediamanager.
|
||||
|
||||
var/datum/track/current_track
|
||||
var/list/datum/track/tracks = list(
|
||||
new/datum/track("Beyond", 'sound/ambience/ambispace.ogg'),
|
||||
new/datum/track("Clouds of Fire", 'sound/music/clouds.s3m'),
|
||||
new/datum/track("D`Bert", 'sound/music/title2.ogg'),
|
||||
new/datum/track("D`Fort", 'sound/ambience/song_game.ogg'),
|
||||
new/datum/track("Floating", 'sound/music/main.ogg'),
|
||||
new/datum/track("Endless Space", 'sound/music/space.ogg'),
|
||||
new/datum/track("Part A", 'sound/misc/TestLoop1.ogg'),
|
||||
new/datum/track("Scratch", 'sound/music/title1.ogg'),
|
||||
new/datum/track("Trai`Tor", 'sound/music/traitor.ogg'),
|
||||
)
|
||||
|
||||
// Only visible if hacked
|
||||
var/list/datum/track/secret_tracks = list(
|
||||
new/datum/track("Clown", 'sound/music/clown.ogg'),
|
||||
new/datum/track("Space Asshole", 'sound/music/space_asshole.ogg'),
|
||||
new/datum/track("Thunderdome", 'sound/music/THUNDERDOME.ogg'),
|
||||
new/datum/track("Russkiy rep Diskoteka", 'sound/music/russianrapdisco.ogg')
|
||||
)
|
||||
var/loop_mode = JUKEMODE_PLAY_ONCE // Behavior when finished playing a song
|
||||
var/max_queue_len = 3 // How many songs are we allowed to queue up?
|
||||
var/datum/track/current_track // Currently playing song
|
||||
var/list/datum/track/queue = list() // Queued songs
|
||||
var/list/datum/track/tracks = list() // Available tracks
|
||||
var/list/datum/track/secret_tracks = list() // Only visible if hacked
|
||||
|
||||
/obj/machinery/media/jukebox/New()
|
||||
..()
|
||||
@@ -54,11 +40,70 @@ datum/track/New(var/title_name, var/audio)
|
||||
update_icon()
|
||||
|
||||
/obj/machinery/media/jukebox/Destroy()
|
||||
StopPlaying()
|
||||
qdel(wires)
|
||||
wires = null
|
||||
..()
|
||||
|
||||
// On initialization, copy our tracks from the global list
|
||||
/obj/machinery/media/jukebox/initialize()
|
||||
..()
|
||||
if(all_jukebox_tracks.len < 1)
|
||||
stat |= BROKEN // No tracks configured this round!
|
||||
return
|
||||
// Ootherwise load from the global list!
|
||||
for(var/datum/track/T in all_jukebox_tracks)
|
||||
if(T.secret)
|
||||
secret_tracks |= T
|
||||
else
|
||||
tracks |= T
|
||||
return
|
||||
|
||||
/obj/machinery/media/jukebox/process()
|
||||
if(!playing)
|
||||
return
|
||||
if(inoperable())
|
||||
disconnect_media_source()
|
||||
playing = 0
|
||||
return
|
||||
// If the current track isn't finished playing, let it keep going
|
||||
if(current_track && world.time < media_start_time + current_track.duration)
|
||||
return
|
||||
// Otherwise time to pick a new one!
|
||||
if(queue.len > 0)
|
||||
current_track = queue[1]
|
||||
queue.Cut(1, 2) // Remove the item we just took off the list
|
||||
else
|
||||
// Oh... nothing in queue? Well then pick next according to our rules
|
||||
switch(loop_mode)
|
||||
if(JUKEMODE_NEXT)
|
||||
var/curTrackIndex = max(1, tracks.Find(current_track))
|
||||
var/newTrackIndex = (curTrackIndex % tracks.len) + 1 // Loop back around if past end
|
||||
current_track = tracks[newTrackIndex]
|
||||
if(JUKEMODE_RANDOM)
|
||||
var/previous_track = current_track
|
||||
do
|
||||
current_track = pick(tracks)
|
||||
while(current_track == previous_track && tracks.len > 1)
|
||||
if(JUKEMODE_REPEAT_SONG)
|
||||
current_track = current_track
|
||||
if(JUKEMODE_PLAY_ONCE)
|
||||
current_track = null
|
||||
playing = 0
|
||||
update_icon()
|
||||
updateDialog()
|
||||
start_stop_song()
|
||||
|
||||
// Tells the media manager to start or stop playing based on current settings.
|
||||
/obj/machinery/media/jukebox/proc/start_stop_song()
|
||||
if(current_track && playing)
|
||||
media_url = current_track.url
|
||||
media_start_time = world.time
|
||||
visible_message("<span class='notice'>\The [src] begins to play [current_track.display()].</span>")
|
||||
else
|
||||
media_url = ""
|
||||
media_start_time = 0
|
||||
update_music()
|
||||
|
||||
/obj/machinery/media/jukebox/proc/set_hacked(var/newhacked)
|
||||
if (hacked == newhacked) return
|
||||
hacked = newhacked
|
||||
@@ -80,13 +125,16 @@ datum/track/New(var/title_name, var/audio)
|
||||
if(istype(W, /obj/item/device/multitool))
|
||||
return wires.Interact(user)
|
||||
if(istype(W, /obj/item/weapon/wrench))
|
||||
if(playing)
|
||||
StopPlaying()
|
||||
user.visible_message("<span class='warning'>[user] has [anchored ? "un" : ""]secured \the [src].</span>", "<span class='notice'>You [anchored ? "un" : ""]secure \the [src].</span>")
|
||||
anchored = !anchored
|
||||
playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
|
||||
power_change()
|
||||
update_icon()
|
||||
if(!anchored)
|
||||
playing = 0
|
||||
disconnect_media_source()
|
||||
else
|
||||
update_media_source()
|
||||
return
|
||||
return ..()
|
||||
|
||||
@@ -125,16 +173,23 @@ datum/track/New(var/title_name, var/audio)
|
||||
usr << "<span class='warning'>You must secure \the [src] first.</span>"
|
||||
return
|
||||
|
||||
if(stat & (NOPOWER|BROKEN))
|
||||
if(inoperable())
|
||||
usr << "\The [src] doesn't appear to function."
|
||||
return
|
||||
|
||||
if(href_list["change_track"])
|
||||
for(var/datum/track/T in tracks)
|
||||
if(T.title == href_list["title"])
|
||||
current_track = T
|
||||
StartPlaying()
|
||||
break
|
||||
var/datum/track/T = locate(href_list["change_track"]) in tracks
|
||||
if(istype(T))
|
||||
current_track = T
|
||||
StartPlaying()
|
||||
else if(href_list["loopmode"])
|
||||
var/newval = text2num(href_list["loopmode"])
|
||||
loop_mode = sanitize_inlist(newval, list(JUKEMODE_NEXT, JUKEMODE_RANDOM, JUKEMODE_REPEAT_SONG, JUKEMODE_PLAY_ONCE), loop_mode)
|
||||
else if(href_list["volume"])
|
||||
var/newval = input("Choose Jukebox volume (0-100%)", "Jukebox volume", round(volume * 100.0))
|
||||
newval = sanitize_integer(text2num(newval), min = 0, max = 100, default = volume * 100.0)
|
||||
volume = newval / 100.0
|
||||
update_music() // To broadcast volume change without restarting song
|
||||
else if(href_list["stop"])
|
||||
StopPlaying()
|
||||
else if(href_list["play"])
|
||||
@@ -162,36 +217,39 @@ datum/track/New(var/title_name, var/audio)
|
||||
return 1
|
||||
|
||||
/obj/machinery/media/jukebox/interact(mob/user)
|
||||
if(stat & (NOPOWER|BROKEN))
|
||||
if(inoperable())
|
||||
usr << "\The [src] doesn't appear to function."
|
||||
return
|
||||
|
||||
ui_interact(user)
|
||||
|
||||
/obj/machinery/media/jukebox/ui_interact(mob/user, ui_key = "jukebox", var/datum/nanoui/ui = null, var/force_open = 1)
|
||||
var/title = "RetroBox - Space Style"
|
||||
var/data[0]
|
||||
|
||||
if(!(stat & (NOPOWER|BROKEN)))
|
||||
data["current_track"] = current_track != null ? current_track.title : ""
|
||||
if(operable())
|
||||
data["playing"] = playing
|
||||
data["hacked"] = hacked
|
||||
data["max_queue_len"] = max_queue_len
|
||||
data["media_start_time"] = media_start_time
|
||||
data["loop_mode"] = loop_mode
|
||||
data["volume"] = volume
|
||||
if(current_track)
|
||||
data["current_track_ref"] = "\ref[current_track]" // Convenient shortcut
|
||||
data["current_track"] = current_track.toNanoList()
|
||||
data["percent"] = playing ? min(100, round(world.time - media_start_time) / current_track.duration) : 0;
|
||||
|
||||
var/list/nano_tracks = new
|
||||
for(var/datum/track/T in tracks)
|
||||
nano_tracks[++nano_tracks.len] = list("track" = T.title)
|
||||
|
||||
nano_tracks[++nano_tracks.len] = T.toNanoList()
|
||||
data["tracks"] = nano_tracks
|
||||
|
||||
// update the ui if it exists, returns null if no ui is passed/found
|
||||
ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
|
||||
if (!ui)
|
||||
// the ui does not exist, so we'll create a new() one
|
||||
// for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
|
||||
ui = new(user, src, ui_key, "jukebox.tmpl", title, 450, 600)
|
||||
// when the ui is first opened this is the data it will use
|
||||
ui.set_initial_data(data)
|
||||
// open the new ui window
|
||||
ui.open()
|
||||
ui.set_auto_update(playing)
|
||||
|
||||
/obj/machinery/media/jukebox/attack_ai(mob/user as mob)
|
||||
return src.attack_hand(user)
|
||||
@@ -212,24 +270,6 @@ datum/track/New(var/title_name, var/audio)
|
||||
new /obj/effect/decal/cleanable/blood/oil(src.loc)
|
||||
qdel(src)
|
||||
|
||||
/obj/machinery/media/jukebox/attackby(obj/item/W as obj, mob/user as mob)
|
||||
src.add_fingerprint(user)
|
||||
|
||||
if(default_deconstruction_screwdriver(user, W))
|
||||
return
|
||||
if(default_deconstruction_crowbar(user, W))
|
||||
return
|
||||
if(istype(W, /obj/item/weapon/wrench))
|
||||
if(playing)
|
||||
StopPlaying()
|
||||
user.visible_message("<span class='warning'>[user] has [anchored ? "un" : ""]secured \the [src].</span>", "<span class='notice'>You [anchored ? "un" : ""]secure \the [src].</span>")
|
||||
anchored = !anchored
|
||||
playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
|
||||
power_change()
|
||||
update_icon()
|
||||
return
|
||||
return ..()
|
||||
|
||||
/obj/machinery/media/jukebox/emag_act(var/remaining_charges, var/mob/user)
|
||||
if(!emagged)
|
||||
emagged = 1
|
||||
@@ -239,37 +279,19 @@ datum/track/New(var/title_name, var/audio)
|
||||
return 1
|
||||
|
||||
/obj/machinery/media/jukebox/proc/StopPlaying()
|
||||
var/area/main_area = get_area(src)
|
||||
// Always kill the current sound
|
||||
for(var/mob/living/M in mobs_in_area(main_area))
|
||||
M << sound(null, channel = 1)
|
||||
|
||||
main_area.forced_ambience = null
|
||||
playing = 0
|
||||
update_use_power(1)
|
||||
update_icon()
|
||||
|
||||
start_stop_song()
|
||||
|
||||
/obj/machinery/media/jukebox/proc/StartPlaying()
|
||||
StopPlaying()
|
||||
if(!current_track)
|
||||
return
|
||||
|
||||
var/area/main_area = get_area(src)
|
||||
if(freq)
|
||||
var/sound/new_song = sound(current_track.sound, channel = 1, repeat = 1, volume = 25)
|
||||
new_song.frequency = freq
|
||||
main_area.forced_ambience = list(new_song)
|
||||
else
|
||||
main_area.forced_ambience = list(current_track.sound)
|
||||
|
||||
for(var/mob/living/M in mobs_in_area(main_area))
|
||||
if(M.mind)
|
||||
main_area.play_ambience(M)
|
||||
|
||||
playing = 1
|
||||
update_use_power(2)
|
||||
update_icon()
|
||||
start_stop_song()
|
||||
updateDialog()
|
||||
|
||||
// Advance to the next track - Don't start playing it unless we were already playing
|
||||
/obj/machinery/media/jukebox/proc/NextTrack()
|
||||
@@ -278,7 +300,7 @@ datum/track/New(var/title_name, var/audio)
|
||||
var/newTrackIndex = (curTrackIndex % tracks.len) + 1 // Loop back around if past end
|
||||
current_track = tracks[newTrackIndex]
|
||||
if(playing)
|
||||
StartPlaying()
|
||||
start_stop_song()
|
||||
updateDialog()
|
||||
|
||||
// Advance to the next track - Don't start playing it unless we were already playing
|
||||
@@ -288,5 +310,5 @@ datum/track/New(var/title_name, var/audio)
|
||||
var/newTrackIndex = curTrackIndex == 1 ? tracks.len : curTrackIndex - 1
|
||||
current_track = tracks[newTrackIndex]
|
||||
if(playing)
|
||||
StartPlaying()
|
||||
start_stop_song()
|
||||
updateDialog()
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
//
|
||||
// VOREStation Custom - Configurable Jukebox!
|
||||
//
|
||||
|
||||
/datum/track
|
||||
var/secret = 0 // Whether or not this is a SECRET TRACK OOOOOH
|
||||
|
||||
// On initialization, copy our tracks from the global list
|
||||
/obj/machinery/media/jukebox/initialize()
|
||||
..()
|
||||
if(all_jukebox_tracks.len)
|
||||
tracks.Cut()
|
||||
secret_tracks.Cut()
|
||||
for(var/datum/track/T in all_jukebox_tracks)
|
||||
if(T.secret)
|
||||
secret_tracks += T
|
||||
else
|
||||
tracks += T
|
||||
return
|
||||
|
||||
// Global list holding all configured jukebox tracks
|
||||
var/global/list/all_jukebox_tracks = list()
|
||||
|
||||
// Read the jukebox configuration file on system startup.
|
||||
/hook/startup/proc/load_jukebox_tracks()
|
||||
var/jukebox_track_file = "config/jukebox.txt"
|
||||
if(!fexists(jukebox_track_file))
|
||||
warning("File not found: [jukebox_track_file]")
|
||||
return
|
||||
// Helpful regex that ignores comments and parses our file format
|
||||
var/regex/lineSplitter = regex("^(?!#)(.+?)\\|(.+?)\\|(.+)$")
|
||||
var/list/Lines = file2list(jukebox_track_file)
|
||||
for(var/t in Lines)
|
||||
if(!t) continue
|
||||
if(!lineSplitter.Find(t)) continue
|
||||
var/file = trim(lineSplitter.group[1])
|
||||
var/title = trim(lineSplitter.group[2])
|
||||
var/isSecret = text2num(trim(lineSplitter.group[3]))
|
||||
if(!fexists(file))
|
||||
warning("In [jukebox_track_file], sound file file not found: [file]")
|
||||
continue
|
||||
var/datum/track/T = new(title, file(file))
|
||||
T.secret = isSecret ? 1 : 0
|
||||
all_jukebox_tracks += T
|
||||
return 1
|
||||
53
code/modules/client/preference_setup/global/06_media.dm
Normal file
53
code/modules/client/preference_setup/global/06_media.dm
Normal file
@@ -0,0 +1,53 @@
|
||||
/datum/preferences
|
||||
var/media_volume = 1
|
||||
var/media_player = 2 // 0 = VLC, 1 = WMP, 2 = HTML5, 3+ = unassigned
|
||||
|
||||
/datum/category_item/player_setup_item/player_global/media
|
||||
name = "Media"
|
||||
sort_order = 6
|
||||
|
||||
/datum/category_item/player_setup_item/player_global/media/load_preferences(var/savefile/S)
|
||||
S["media_volume"] >> pref.media_volume
|
||||
S["media_player"] >> pref.media_player
|
||||
|
||||
/datum/category_item/player_setup_item/player_global/media/save_preferences(var/savefile/S)
|
||||
S["media_volume"] << pref.media_volume
|
||||
S["media_player"] << pref.media_player
|
||||
|
||||
/datum/category_item/player_setup_item/player_global/media/sanitize_preferences()
|
||||
pref.media_volume = isnum(pref.media_volume) ? Clamp(pref.media_volume, 0, 1) : initial(pref.media_volume)
|
||||
pref.media_player = sanitize_inlist(pref.media_player, list(0, 1, 2), initial(pref.media_player))
|
||||
|
||||
/datum/category_item/player_setup_item/player_global/media/content(var/mob/user)
|
||||
. += "<b>Jukebox Volume:</b>"
|
||||
. += "<a href='?src=\ref[src];change_media_volume=1'><b>[round(pref.media_volume * 100)]%</b></a><br>"
|
||||
. += "<b>Media Player Type:</b> Depending on you operating system, one of these might work better. "
|
||||
. += "Use HTML5 if it works for you. If neither HTML5 nor WMP work, you'll have to fall back to using VLC, "
|
||||
. += "but this requires you have the VLC client installed on your comptuer."
|
||||
. += "Try the others if you want but you'll probably just get no music.<br>"
|
||||
. += (pref.media_player == 2) ? "<span class='linkOn'><b>HTML5</b></span> " : "<a href='?src=\ref[src];set_media_player=2'>HTML5</a> "
|
||||
. += (pref.media_player == 1) ? "<span class='linkOn'><b>WMP</b></span> " : "<a href='?src=\ref[src];set_media_player=1'>WMP</a> "
|
||||
. += (pref.media_player == 0) ? "<span class='linkOn'><b>VLC</b></span> " : "<a href='?src=\ref[src];set_media_player=0'>VLC</a> "
|
||||
. += "<br>"
|
||||
|
||||
/datum/category_item/player_setup_item/player_global/media/OnTopic(var/href, var/list/href_list, var/mob/user)
|
||||
if(href_list["change_media_volume"])
|
||||
if(CanUseTopic(user))
|
||||
var/value = input("Choose your Jukebox volume (0-100%)", "Jukebox volume", round(pref.media_volume * 100))
|
||||
if(isnum(value))
|
||||
value = Clamp(value, 0, 100)
|
||||
pref.media_volume = value/100.0
|
||||
if(user.client && user.client.media)
|
||||
user.client.media.update_volume(pref.media_volume)
|
||||
return TOPIC_REFRESH
|
||||
else if(href_list["set_media_player"])
|
||||
if(CanUseTopic(user))
|
||||
var/newval = sanitize_inlist(text2num(href_list["set_media_player"]), list(0, 1, 2), pref.media_player)
|
||||
if(newval != pref.media_player)
|
||||
pref.media_player = newval
|
||||
if(user.client && user.client.media)
|
||||
user.client.media.open()
|
||||
spawn(10)
|
||||
user.update_music()
|
||||
return TOPIC_REFRESH
|
||||
return ..()
|
||||
@@ -74,6 +74,16 @@ var/list/_client_preferences_by_type
|
||||
preference_mob << sound(null, repeat = 0, wait = 0, volume = 0, channel = 1)
|
||||
preference_mob << sound(null, repeat = 0, wait = 0, volume = 0, channel = 2)
|
||||
|
||||
/datum/client_preference/play_jukebox
|
||||
description ="Play jukebox music"
|
||||
key = "SOUND_JUKEBOX"
|
||||
|
||||
/datum/client_preference/play_jukebox/toggled(var/mob/preference_mob, var/enabled)
|
||||
if(!enabled)
|
||||
preference_mob.stop_all_music()
|
||||
else
|
||||
preference_mob.update_music()
|
||||
|
||||
/datum/client_preference/ghost_ears
|
||||
description ="Ghost ears"
|
||||
key = "CHAT_GHOSTEARS"
|
||||
|
||||
74
code/modules/media/media_machinery.dm
Normal file
74
code/modules/media/media_machinery.dm
Normal file
@@ -0,0 +1,74 @@
|
||||
// Machinery serving as a media source.
|
||||
/obj/machinery/media
|
||||
var/playing = 0 // Am I playing right now?
|
||||
var/media_url = "" // URL of media I am playing
|
||||
var/media_start_time = 0 // world.time when it started playing
|
||||
var/volume = 1 // 0 - 1 for ease of coding.
|
||||
|
||||
var/area/master_area // My area
|
||||
|
||||
// ~Leshana - Transmitters unimplemented
|
||||
|
||||
// Notify everyone in the area of new music.
|
||||
// YOU MUST SET MEDIA_URL AND MEDIA_START_TIME YOURSELF!
|
||||
/obj/machinery/media/proc/update_music()
|
||||
update_media_source()
|
||||
// Bail if we lost connection to master.
|
||||
if(!master_area)
|
||||
return
|
||||
// Send update to clients.
|
||||
for(var/mob/M in mobs_in_area(master_area))
|
||||
if(M && M.client)
|
||||
M.update_music()
|
||||
|
||||
/obj/machinery/media/proc/update_media_source()
|
||||
var/area/A = get_area_master(src)
|
||||
if(!A)
|
||||
return
|
||||
// Check if there's a media source already.
|
||||
if(A.media_source && A.media_source != src) // If it does, the new media source replaces it. basically, the last media source arrived gets played on top.
|
||||
A.media_source.disconnect_media_source() // You can turn a media source off and on for it to come back on top.
|
||||
A.media_source = src
|
||||
master_area = A
|
||||
return
|
||||
else
|
||||
A.media_source = src
|
||||
master_area = A
|
||||
|
||||
/obj/machinery/media/proc/disconnect_media_source()
|
||||
var/area/A = get_area_master(src)
|
||||
// Sanity
|
||||
if(!A)
|
||||
master_area = null
|
||||
return
|
||||
// Check if there's a media source already.
|
||||
if(A && A.media_source && A.media_source != src)
|
||||
master_area = null
|
||||
return
|
||||
// Update Media Source.
|
||||
A.media_source = null
|
||||
// Clients
|
||||
for(var/mob/M in mobs_in_area(A))
|
||||
if(M && M.client)
|
||||
M.update_music()
|
||||
master_area = null
|
||||
|
||||
/obj/machinery/media/Move()
|
||||
..()
|
||||
if(anchored)
|
||||
update_music()
|
||||
|
||||
/obj/machinery/media/forceMove(var/atom/destination)
|
||||
disconnect_media_source()
|
||||
..()
|
||||
if(anchored)
|
||||
update_music()
|
||||
|
||||
/obj/machinery/media/initialize()
|
||||
..()
|
||||
update_media_source()
|
||||
|
||||
/obj/machinery/media/Destroy()
|
||||
disconnect_media_source()
|
||||
..()
|
||||
|
||||
27
code/modules/media/media_player_html5.dm
Normal file
27
code/modules/media/media_player_html5.dm
Normal file
@@ -0,0 +1,27 @@
|
||||
// IT IS FINALLY TIME. IT IS HERE. Converted to HTML5 <audio> - Leshana
|
||||
var/const/PLAYER_HTML5_HTML={"<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=11">
|
||||
<script type="text/javascript">
|
||||
function noErrorMessages () { return true; }
|
||||
window.onerror = noErrorMessages;
|
||||
function SetMusic(url, time, volume) {
|
||||
var player = document.getElementById('player');
|
||||
// IE can't handle us setting the time before it loads, so we must wait for asychronous load
|
||||
var setTime = function () {
|
||||
player.removeEventListener("canplay", setTime); // One time only!
|
||||
player.volume = volume;
|
||||
player.currentTime = time;
|
||||
player.play();
|
||||
}
|
||||
if(url != "") player.addEventListener("canplay", setTime, false);
|
||||
player.src = url;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<audio id="player"></audio>
|
||||
</body>
|
||||
</html>
|
||||
"}
|
||||
27
code/modules/media/media_player_vlc.dm
Normal file
27
code/modules/media/media_player_vlc.dm
Normal file
@@ -0,0 +1,27 @@
|
||||
// Open up VLC and play musique.
|
||||
// Converted to VLC for cross-platform and ogg support. - N3X
|
||||
var/const/PLAYER_VLC_HTML={"
|
||||
<object classid="clsid:9BE31822-FDAD-461B-AD51-BE1D1C159921" codebase="http://download.videolan.org/pub/videolan/vlc/last/win32/axvlc.cab" id="player"></object>
|
||||
<script>
|
||||
function noErrorMessages () { return true; }
|
||||
window.onerror = noErrorMessages;
|
||||
function SetMusic(url, time, volume) {
|
||||
var vlc = document.getElementById('player');
|
||||
|
||||
// Stop playing
|
||||
vlc.playlist.stop();
|
||||
|
||||
// Clear playlist
|
||||
vlc.playlist.items.clear();
|
||||
|
||||
// Add new playlist item.
|
||||
var id = vlc.playlist.add(url);
|
||||
|
||||
// Play playlist item
|
||||
vlc.playlist.playItem(id);
|
||||
|
||||
vlc.input.time = time*1000; // VLC takes milliseconds.
|
||||
vlc.audio.volume = volume*100; // \[0-200]
|
||||
}
|
||||
</script>
|
||||
"}
|
||||
14
code/modules/media/media_player_wmp.dm
Normal file
14
code/modules/media/media_player_wmp.dm
Normal file
@@ -0,0 +1,14 @@
|
||||
// Legacy player using Windows Media Player OLE object.
|
||||
// I guess it will work in IE on windows, and BYOND uses IE on windows, so alright!
|
||||
var/const/PLAYER_WMP_HTML={"
|
||||
<OBJECT id='player' CLASSID='CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6' type='application/x-oleobject'></OBJECT>
|
||||
<script>
|
||||
function noErrorMessages () { return true; }
|
||||
window.onerror = noErrorMessages;
|
||||
function SetMusic(url, time, volume) {
|
||||
var player = document.getElementById('player');
|
||||
player.URL = url;
|
||||
player.Controls.currentPosition = +time;
|
||||
player.Settings.volume = +volume;
|
||||
}
|
||||
</script>"}
|
||||
57
code/modules/media/media_tracks.dm
Normal file
57
code/modules/media/media_tracks.dm
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Load the list of available music tracks for the jukebox (or other things that use music)
|
||||
//
|
||||
|
||||
// Music track available for playing in a media machine.
|
||||
/datum/track
|
||||
var/url // URL to load song from
|
||||
var/title // Song title
|
||||
var/artist // Song's creator
|
||||
var/duration // Song length in deciseconds
|
||||
var/secret // Show up in regular playlist or secret playlist?
|
||||
|
||||
/datum/track/New(var/url, var/title, var/duration, var/artist = "", var/secret = 0)
|
||||
src.url = url
|
||||
src.title = title
|
||||
src.artist = artist
|
||||
src.duration = duration
|
||||
src.secret = secret
|
||||
|
||||
/datum/track/proc/display()
|
||||
var str = "\"[title]\""
|
||||
if(artist)
|
||||
str += " by [artist]"
|
||||
return str
|
||||
|
||||
/datum/track/proc/toNanoList()
|
||||
return list("ref" = "\ref[src]", "title" = title, "artist" = artist, "duration" = duration)
|
||||
|
||||
|
||||
// Global list holding all configured jukebox tracks
|
||||
var/global/list/all_jukebox_tracks = list()
|
||||
|
||||
// Read the jukebox configuration file on system startup.
|
||||
/hook/startup/proc/load_jukebox_tracks()
|
||||
var/jukebox_track_file = "config/jukebox.json"
|
||||
if(!fexists(jukebox_track_file))
|
||||
warning("File not found: [jukebox_track_file]")
|
||||
return
|
||||
var/list/jsonData = json_decode(file2text(jukebox_track_file))
|
||||
if(!istype(jsonData))
|
||||
warning("Failed to read tracks from [jukebox_track_file], json_decode failed.")
|
||||
for(var/entry in jsonData)
|
||||
if(!istext(entry["url"]))
|
||||
warning("[jukebox_track_file] entry [entry]: bad or missing 'url'")
|
||||
continue
|
||||
if(!istext(entry["title"]))
|
||||
warning("[jukebox_track_file] entry [entry]: bad or missingg 'title'")
|
||||
continue
|
||||
if(!isnum(entry["duration"]))
|
||||
warning("[jukebox_track_file] entry [entry]: bad or missing 'duration'")
|
||||
continue
|
||||
var/datum/track/T = new(entry["url"], entry["title"], entry["duration"])
|
||||
if(istext(entry["artist"]))
|
||||
T.artist = entry["artist"]
|
||||
T.secret = entry["secret"] ? 1 : 0
|
||||
all_jukebox_tracks += T
|
||||
return 1
|
||||
189
code/modules/media/mediamanager.dm
Normal file
189
code/modules/media/mediamanager.dm
Normal file
@@ -0,0 +1,189 @@
|
||||
/**********************
|
||||
* AWW SHIT IT'S TIME FOR RADIO
|
||||
*
|
||||
* Concept stolen from D2K5
|
||||
* Rewritten by N3X15 for vgstation
|
||||
* Adapted by Leshana for VOREStation
|
||||
***********************/
|
||||
|
||||
// Uncomment to test the mediaplayer
|
||||
// #define DEBUG_MEDIAPLAYER
|
||||
|
||||
#ifdef DEBUG_MEDIAPLAYER
|
||||
#define MP_DEBUG(x) owner << x
|
||||
#warning Please comment out #define DEBUG_MEDIAPLAYER before committing.
|
||||
#else
|
||||
#define MP_DEBUG(x)
|
||||
#endif
|
||||
|
||||
/proc/stop_all_media()
|
||||
for(var/mob/M in mob_list)
|
||||
if(M && M.client)
|
||||
M.stop_all_music()
|
||||
|
||||
// Set up player on login to a mob.
|
||||
// This means they get a new media manager every time they switch mobs!
|
||||
// Is this wasteful? Granted switching mobs doesn't happen very often so maybe its fine.
|
||||
// TODO - While this direct override might technically be faster, probably better code to use observer or hooks ~Leshana
|
||||
/mob/Login()
|
||||
. = ..()
|
||||
ASSERT(src.client)
|
||||
src.client.media = new /datum/media_manager(src.client)
|
||||
src.client.media.open()
|
||||
src.client.media.update_music()
|
||||
|
||||
// Stop media when the round ends. I guess so it doesn't play forever or something (for some reason?)
|
||||
/hook/roundend/proc/stop_all_media()
|
||||
log_debug("Stopping all playing media...")
|
||||
// Stop all music.
|
||||
stop_all_media()
|
||||
// SHITTY HACK TO AVOID RACE CONDITION WITH SERVER REBOOT.
|
||||
sleep(10) // TODO - Leshana - see if this is needed
|
||||
|
||||
// Update when moving between areas.
|
||||
// TODO - While this direct override might technically be faster, probably better code to use observer or hooks ~Leshana
|
||||
/area/Entered(var/mob/living/M)
|
||||
// Note, we cannot call ..() first, because it would update lastarea.
|
||||
if(!istype(M))
|
||||
return ..()
|
||||
// Optimization, no need to call update_music() if both are null (or same instance, strange as that would be)
|
||||
if(M.lastarea && M.lastarea.media_source == src.media_source)
|
||||
return ..()
|
||||
if(M.client && M.client.media && !M.client.media.forced)
|
||||
M.update_music()
|
||||
return ..()
|
||||
|
||||
//
|
||||
// ### Media variable on /client ###
|
||||
/client
|
||||
// Set on Login
|
||||
var/datum/media_manager/media = null
|
||||
|
||||
/client/verb/change_volume()
|
||||
set name = "Set Volume"
|
||||
set category = "OOC"
|
||||
set desc = "Set jukebox volume"
|
||||
set_new_volume(usr)
|
||||
|
||||
/client/proc/set_new_volume(var/mob/user)
|
||||
if(deleted(src.media) || !istype(src.media))
|
||||
to_chat(user, "<span class='warning'>You have no media datum to change, if you're not in the lobby tell an admin.</span>")
|
||||
return
|
||||
var/value = input("Choose your Jukebox volume.", "Jukebox volume", media.volume)
|
||||
value = round(max(0, min(100, value)))
|
||||
media.update_volume(value)
|
||||
|
||||
//
|
||||
// ### Media procs on mobs ###
|
||||
// These are all convenience functions, simple delegations to the media datum on mob.
|
||||
// But their presense and null checks make other coder's life much easier.
|
||||
//
|
||||
|
||||
/mob/proc/update_music()
|
||||
if (client && client.media && !client.media.forced)
|
||||
client.media.update_music()
|
||||
|
||||
/mob/proc/stop_all_music()
|
||||
if (client && client.media)
|
||||
client.media.stop_music()
|
||||
|
||||
/mob/proc/force_music(var/url, var/start, var/volume=1)
|
||||
if (client && client.media)
|
||||
if(url == "")
|
||||
client.media.forced = 0
|
||||
client.media.update_music()
|
||||
else
|
||||
client.media.forced = 1
|
||||
client.media.push_music(url, start, volume)
|
||||
return
|
||||
|
||||
//
|
||||
// ### Define media source to areas ###
|
||||
// Each area may have at most one media source that plays songs into that area.
|
||||
// We keep track of that source so any mob entering the area can lookup what to play.
|
||||
//
|
||||
/area
|
||||
// For now, only one media source per area allowed
|
||||
// Possible Future: turn into a list, then only play the first one that's playing.
|
||||
var/obj/machinery/media/media_source = null
|
||||
|
||||
//
|
||||
// ### Media Manager Datum
|
||||
//
|
||||
|
||||
/datum/media_manager
|
||||
var/url = "" // URL of currently playing media
|
||||
var/start_time = 0 // world.time when it started playing *in the source* (Not when started playing for us)
|
||||
var/source_volume = 1 // Volume as set by source. Actual volume = "volume * source_volume"
|
||||
var/rate = 1 // Playback speed. For Fun(tm)
|
||||
var/volume = 50 // Client's volume modifier. Actual volume = "volume * source_volume"
|
||||
var/client/owner // Client this is actually running in
|
||||
var/forced=0 // If true, current url overrides area media sources
|
||||
var/playerstyle // Choice of which player plugin to use
|
||||
var/const/WINDOW_ID = "rpane.mediapanel" // Which elem in skin.dmf to use
|
||||
|
||||
/datum/media_manager/New(var/client/C)
|
||||
ASSERT(istype(C))
|
||||
src.owner = C
|
||||
|
||||
// Actually pop open the player in the background.
|
||||
/datum/media_manager/proc/open()
|
||||
if(!owner.prefs)
|
||||
return
|
||||
if(isnum(owner.prefs.media_volume))
|
||||
volume = owner.prefs.media_volume
|
||||
switch(owner.prefs.media_player)
|
||||
if(0)
|
||||
playerstyle = PLAYER_VLC_HTML
|
||||
if(1)
|
||||
playerstyle = PLAYER_WMP_HTML
|
||||
if(2)
|
||||
playerstyle = PLAYER_HTML5_HTML
|
||||
owner << browse(null, "window=[WINDOW_ID]")
|
||||
owner << browse(playerstyle, "window=[WINDOW_ID]")
|
||||
send_update()
|
||||
|
||||
// Tell the player to play something via JS.
|
||||
/datum/media_manager/proc/send_update()
|
||||
if(!(owner.prefs))
|
||||
return
|
||||
if(!owner.is_preference_enabled(/datum/client_preference/play_jukebox) && url != "")
|
||||
return // Don't send anything other than a cancel to people with SOUND_STREAMING pref disabled
|
||||
MP_DEBUG("<span class='good'>Sending update to mediapanel ([url], [(world.time - start_time) / 10], [volume * source_volume])...</span>")
|
||||
owner << output(list2params(list(url, (world.time - start_time) / 10, volume * source_volume)), "[WINDOW_ID]:SetMusic")
|
||||
|
||||
/datum/media_manager/proc/push_music(var/targetURL, var/targetStartTime, var/targetVolume)
|
||||
if (url != targetURL || abs(targetStartTime - start_time) > 1 || abs(targetVolume - source_volume) > 0.1 /* 10% */)
|
||||
url = targetURL
|
||||
start_time = targetStartTime
|
||||
source_volume = Clamp(targetVolume, 0, 1)
|
||||
send_update()
|
||||
|
||||
/datum/media_manager/proc/stop_music()
|
||||
push_music("", 0, 1)
|
||||
|
||||
/datum/media_manager/proc/update_volume(var/value)
|
||||
volume = value
|
||||
send_update()
|
||||
|
||||
// Scan for media sources and use them.
|
||||
/datum/media_manager/proc/update_music()
|
||||
var/targetURL = ""
|
||||
var/targetStartTime = 0
|
||||
var/targetVolume = 0
|
||||
|
||||
if (forced || !owner || !owner.mob)
|
||||
return
|
||||
|
||||
var/area/A = get_area_master(owner.mob)
|
||||
if(!A)
|
||||
MP_DEBUG("client=[owner], mob=[owner.mob] not in an area! loc=[owner.mob.loc]. Aborting.")
|
||||
stop_music()
|
||||
return
|
||||
var/obj/machinery/media/M = A.media_source
|
||||
if(M && M.playing)
|
||||
targetURL = M.media_url
|
||||
targetStartTime = M.media_start_time
|
||||
targetVolume = M.volume
|
||||
//MP_DEBUG("Found audio source: [M.media_url] @ [(world.time - start_time) / 10]s.")
|
||||
push_music(targetURL, targetStartTime, targetVolume)
|
||||
@@ -2282,6 +2282,32 @@ window "rpane"
|
||||
is-checked = false
|
||||
group = "rpanemode"
|
||||
button-type = pushbox
|
||||
elem "mediapanel"
|
||||
type = BROWSER
|
||||
pos = 392,25
|
||||
size = 1x1
|
||||
anchor1 = none
|
||||
anchor2 = none
|
||||
font-family = ""
|
||||
font-size = 0
|
||||
font-style = ""
|
||||
text-color = #000000
|
||||
background-color = none
|
||||
is-visible = true
|
||||
is-disabled = false
|
||||
is-transparent = false
|
||||
is-default = false
|
||||
border = none
|
||||
drop-zone = false
|
||||
right-click = false
|
||||
saved-params = ""
|
||||
on-size = ""
|
||||
show-history = false
|
||||
show-url = false
|
||||
auto-format = true
|
||||
use-title = false
|
||||
on-show = ""
|
||||
on-hide = ""
|
||||
|
||||
window "browserwindow"
|
||||
elem "browserwindow"
|
||||
|
||||
@@ -3,17 +3,47 @@ Title: Jukebox UI
|
||||
Used In File(s): \code\game\machinery\jukebox.dm
|
||||
-->
|
||||
|
||||
<H3><span class="white">Current track:</span> <span class="average">{{:data.current_track}}</span></H3>
|
||||
<div>
|
||||
{{:helper.link('Play' , 'play', {'play' : 1}, data.playing == 1 ? 'disabled' : null, null)}}
|
||||
{{:helper.link('Stop' , 'stop', {'stop' : 1}, data.playing == 0 ? 'disabled' : null, null)}}
|
||||
<div class='statusDisplay'>
|
||||
<div class="item">
|
||||
<div class="itemLabel">Currently Playing:</div>
|
||||
{{if data.playing && data.current_track }}
|
||||
<div class="itemContent"><b>{{:data.current_track.title}}</b> by <i>{{:data.current_track.artist ? data.current_track.artist : "Unknown"}}</i></div>
|
||||
{{else}}
|
||||
<div class="itemContent">Stopped</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="itemContent">
|
||||
{{:helper.displayBar(data.percent, 0, 1, 'good')}}
|
||||
</div>
|
||||
<div class="itemLabel">
|
||||
{{:helper.link('Play' , 'play', {'play' : 1}, data.playing == 1 ? 'disabled' : null, null)}}
|
||||
{{:helper.link('Stop' , 'stop', {'stop' : 1}, data.playing == 0 ? 'disabled' : null, null)}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="itemLabel">{{:helper.link('Volume', 'volume-on', {'volume' : 1})}}</div>
|
||||
<div class="itemContent">
|
||||
{{:helper.displayBar(data.volume, 0, 1, (data.volume < .25) ? 'bad' : (data.volume < .75) ? 'average' : 'good')}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item">
|
||||
<div class="itemLabel">Loop Mode:</div>
|
||||
<div class="itemContent">
|
||||
{{:helper.link('Next', 'triangle-1-e', {'loopmode' : 1}, data.loop_mode == 1 ? 'disabled' : null, null)}}
|
||||
{{:helper.link('Random', 'shuffle', {'loopmode' : 2}, data.loop_mode == 2 ? 'disabled' : null, null)}}
|
||||
{{:helper.link('Repeat', 'arrowrefresh-1-w', {'loopmode' : 3}, data.loop_mode == 3 ? 'disabled' : null, null)}}
|
||||
{{:helper.link('Once', 'arrowstop-1-e', {'loopmode' : 4}, data.loop_mode == 4 ? 'disabled' : null, null)}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<H3><span class="white">Available tracks:</span></H3>
|
||||
<div class="itemContent">
|
||||
{{for data.tracks}}
|
||||
<div class="item">
|
||||
{{:helper.link( value.track, 'gear', {'change_track' : 1, 'title' : value.track}, value.track == data.current_track ? 'disabled' : null, null)}}
|
||||
{{:helper.link(value.title, 'gear', {'change_track' : value.ref}, data.current_track_ref == value.ref ? 'disabled' : null, null)}}
|
||||
</div>
|
||||
{{/for}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -565,7 +565,6 @@
|
||||
#include "code\game\machinery\igniter.dm"
|
||||
#include "code\game\machinery\iv_drip.dm"
|
||||
#include "code\game\machinery\jukebox.dm"
|
||||
#include "code\game\machinery\jukebox_vr.dm"
|
||||
#include "code\game\machinery\lightswitch.dm"
|
||||
#include "code\game\machinery\machinery.dm"
|
||||
#include "code\game\machinery\magnet.dm"
|
||||
@@ -1242,6 +1241,7 @@
|
||||
#include "code\modules\client\preference_setup\global\03_pai.dm"
|
||||
#include "code\modules\client\preference_setup\global\04_communicators.dm"
|
||||
#include "code\modules\client\preference_setup\global\05_ooc.dm"
|
||||
#include "code\modules\client\preference_setup\global\06_media.dm"
|
||||
#include "code\modules\client\preference_setup\global\setting_datums.dm"
|
||||
#include "code\modules\client\preference_setup\loadout\gear_tweaks.dm"
|
||||
#include "code\modules\client\preference_setup\loadout\loadout.dm"
|
||||
@@ -1551,6 +1551,12 @@
|
||||
#include "code\modules\materials\material_synth.dm"
|
||||
#include "code\modules\materials\materials.dm"
|
||||
#include "code\modules\materials\materials_vr.dm"
|
||||
#include "code\modules\media\media_machinery.dm"
|
||||
#include "code\modules\media\media_player_html5.dm"
|
||||
#include "code\modules\media\media_player_vlc.dm"
|
||||
#include "code\modules\media\media_player_wmp.dm"
|
||||
#include "code\modules\media\media_tracks.dm"
|
||||
#include "code\modules\media\mediamanager.dm"
|
||||
#include "code\modules\metric\activity.dm"
|
||||
#include "code\modules\metric\department.dm"
|
||||
#include "code\modules\metric\metric.dm"
|
||||
|
||||
Reference in New Issue
Block a user