Files
CHOMPStation2/code/modules/media/mediamanager.dm
Leshana c63c68c9a8 Implements browser streaming media jukeboxes
Ports media code from vgstation, updates it for this codebase and modernizes it.
* Changes jukeboxes to load songs using an embedded browser instead of sending over BYOND's sound channels.  This means they load out of band without lagging the server.  Also songs can be resumed mid-song, so leaving and returning to an area doesn't start the music over.
* The old WMP and VLC player modes from /vg are still supported, but adds a new default mode using HTML5 audio to play the music.
  * WMP - The oldest, still works on IE on windows, but only there, and Microsoft could break it any second.
  * VLC - Works on all platforms, but requires user to have VLC pre-installed on their computer.  Uses a scary plugin.
  * HTML5 - New default, It is cross platform but doesn't require you to have VLC installed to work.  Also caches songs locally even between rounds.
* Changed jukebox.txt to be jukebox.json, now can include artist information as well.  Must include the duration of songs as well.
* For HTML5 audio compatibility, use only MP3 files, its the only format supported on all browsers.
* Jukebox itself is also upgraded, instead of just repeating the same song over and over it can actually advance to the next song when one is done playing.  Has a few modes including random, next, and single song.
* Jukeboxes have a UI improvement, and have a volume control.
* Three new settings are added to global settings in character setup
  * Jukebox music on/off toggles jukebox music independently of normal station ambience.  Now you can hear ambience but not music. (or vice versa if you wanted...)
  * Jukebox music volume.  Control the relative volume of jukebox music.   Actual volume is player's configured volume * jukebox's configured volume.
  * Media player type.  Choose between WMP, VLC, and HTML5
* Fixes a few bugs in the /vg code.
2017-03-23 21:10:11 -04:00

190 lines
6.3 KiB
Plaintext

/**********************
* 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)