Commit 94c38f18 authored by david's avatar david
Browse files

Default playlists for show and schedule-level. #52

parent e87be140
...@@ -374,41 +374,41 @@ class Player: ...@@ -374,41 +374,41 @@ class Player:
def start_fallback_playlist(self, entries): # def start_fallback_playlist(self, entries):
""" # """
Sets any scheduled fallback playlist and performs a fade-in. # Sets any scheduled fallback playlist and performs a fade-in.
Args: # Args:
entries ([Entry]): The playlist entries # entries ([Entry]): The playlist entries
""" # """
self.preload_group(entries, ChannelType.FALLBACK_QUEUE) # self.preload_group(entries, ChannelType.FALLBACK_QUEUE)
self.play(entries[0], TransitionType.FADE) # self.play(entries[0], TransitionType.FADE)
self.event_dispatcher.on_fallback_updated(entries) # self.event_dispatcher.on_fallback_updated(entries)
def stop_fallback_playlist(self): # def stop_fallback_playlist(self):
""" # """
Performs a fade-out and clears any scheduled fallback playlist. # Performs a fade-out and clears any scheduled fallback playlist.
""" # """
dirty_channel = self.channel_router.get_active(ChannelType.FALLBACK_QUEUE) # dirty_channel = self.channel_router.get_active(ChannelType.FALLBACK_QUEUE)
self.logger.info(f"Fading out channel '{dirty_channel}'") # self.logger.info(f"Fading out channel '{dirty_channel}'")
self.connector.enable_transaction() # self.connector.enable_transaction()
self.mixer_fallback.fade_out(dirty_channel) # self.mixer_fallback.fade_out(dirty_channel)
self.connector.disable_transaction() # self.connector.disable_transaction()
def clean_up(): # def clean_up():
# Wait a little, if there is some long fade-out. Note, this also means, # # Wait a little, if there is some long fade-out. Note, this also means,
# this channel should not be used for at least some seconds (including clearing time). # # this channel should not be used for at least some seconds (including clearing time).
time.sleep(2) # time.sleep(2)
self.connector.enable_transaction() # self.connector.enable_transaction()
self.mixer_fallback.channel_activate(dirty_channel.value, False) # self.mixer_fallback.channel_activate(dirty_channel.value, False)
res = self.queue_clear(dirty_channel) # res = self.queue_clear(dirty_channel)
self.logger.info("Clear Fallback Queue Response: " + res) # self.logger.info("Clear Fallback Queue Response: " + res)
self.connector.disable_transaction() # self.connector.disable_transaction()
self.event_dispatcher.on_fallback_cleaned(dirty_channel) # self.event_dispatcher.on_fallback_cleaned(dirty_channel)
Thread(target=clean_up).start() # Thread(target=clean_up).start()
......
...@@ -89,7 +89,7 @@ class EngineEventDispatcher(): ...@@ -89,7 +89,7 @@ class EngineEventDispatcher():
self.logger = logging.getLogger("AuraEngine") self.logger = logging.getLogger("AuraEngine")
self.config = AuraConfig.config() self.config = AuraConfig.config()
self.engine = engine self.engine = engine
binding = self.attach(AuraMailer) binding = self.attach(AuraMailer)
binding.subscribe("on_critical") binding.subscribe("on_critical")
binding.subscribe("on_sick") binding.subscribe("on_sick")
...@@ -183,33 +183,33 @@ class EngineEventDispatcher(): ...@@ -183,33 +183,33 @@ class EngineEventDispatcher():
""" """
Called when the engine has finished booting and is ready to play. Called when the engine has finished booting and is ready to play.
""" """
def func(self, param): def func(self, param):
self.logger.debug("on_ready(..)") self.logger.debug("on_ready(..)")
self.scheduler.on_ready() self.scheduler.on_ready()
self.call_event("on_ready", param) self.call_event("on_ready", param)
thread = Thread(target = func, args = (self, None)) thread = Thread(target = func, args = (self, None))
thread.start() thread.start()
def on_timeslot_start(self, timeslot): def on_timeslot_start(self, timeslot):
""" """
Called when a timeslot starts. Called when a timeslot starts.
""" """
def func(self, timeslot): def func(self, timeslot):
self.logger.debug("on_timeslot_start(..)") self.logger.debug("on_timeslot_start(..)")
self.fallback_manager.on_timeslot_start(timeslot) self.fallback_manager.on_timeslot_start(timeslot)
self.call_event("on_timeslot_start", timeslot) self.call_event("on_timeslot_start", timeslot)
thread = Thread(target = func, args = (self, timeslot)) thread = Thread(target = func, args = (self, timeslot))
thread.start() thread.start()
def on_timeslot_end(self, timeslot): def on_timeslot_end(self, timeslot):
""" """
Called when a timeslot ends. Called when a timeslot ends.
""" """
def func(self, timeslot): def func(self, timeslot):
self.logger.debug("on_timeslot_end(..)") self.logger.debug("on_timeslot_end(..)")
self.fallback_manager.on_timeslot_end(timeslot) self.fallback_manager.on_timeslot_end(timeslot)
self.call_event("on_timeslot_end", timeslot) self.call_event("on_timeslot_end", timeslot)
...@@ -227,7 +227,7 @@ class EngineEventDispatcher(): ...@@ -227,7 +227,7 @@ class EngineEventDispatcher():
Args: Args:
source (String): The `PlaylistEntry` object source (String): The `PlaylistEntry` object
""" """
def func(self, entry): def func(self, entry):
self.logger.debug("on_play(..)") self.logger.debug("on_play(..)")
# Assign timestamp indicating start play time. Use the actual playtime when possible. # Assign timestamp indicating start play time. Use the actual playtime when possible.
entry.entry_start_actual = datetime.datetime.now() entry.entry_start_actual = datetime.datetime.now()
...@@ -235,63 +235,63 @@ class EngineEventDispatcher(): ...@@ -235,63 +235,63 @@ class EngineEventDispatcher():
self.call_event("on_play", entry) self.call_event("on_play", entry)
thread = Thread(target = func, args = (self, entry)) thread = Thread(target = func, args = (self, entry))
thread.start() thread.start()
def on_metadata(self, data): def on_metadata(self, data):
""" """
Event called by the soundsystem implementation (i.e. Liquidsoap) when some entry is actually playing. Event called by the soundsystem implementation (i.e. Liquidsoap) when some entry is actually playing.
This does not include live or stream sources, since they ain't have metadata and are triggered from This does not include live or stream sources, since they ain't have metadata and are triggered from
engine core (see `on_play(..)`). engine core (see `on_play(..)`).
Args: Args:
data (dict): A collection of metadata related to the current track data (dict): A collection of metadata related to the current track
""" """
def func(self, data): def func(self, data):
self.logger.debug("on_metadata(..)") self.logger.debug("on_metadata(..)")
self.fallback_manager.on_metadata(data) self.fallback_manager.on_metadata(data)
self.call_event("on_metadata", data) self.call_event("on_metadata", data)
thread = Thread(target = func, args = (self, data)) thread = Thread(target = func, args = (self, data))
thread.start() thread.start()
def on_stop(self, entry): def on_stop(self, entry):
""" """
The entry on the assigned channel has been stopped playing. The entry on the assigned channel has been stopped playing.
""" """
def func(self, entry): def func(self, entry):
self.logger.debug("on_stop(..)") self.logger.debug("on_stop(..)")
self.call_event("on_stop", entry) self.call_event("on_stop", entry)
thread = Thread(target = func, args = (self, entry)) thread = Thread(target = func, args = (self, entry))
thread.start() thread.start()
def on_fallback_updated(self, playlist_uri): # def on_fallback_updated(self, playlist_uri):
""" # """
Called when the scheduled fallback playlist has been updated. # Called when the scheduled fallback playlist has been updated.
This event does not indicate that the fallback is actually playing. # This event does not indicate that the fallback is actually playing.
""" # """
def func(self, playlist_uri): # def func(self, playlist_uri):
self.logger.debug("on_fallback_updated(..)") # self.logger.debug("on_fallback_updated(..)")
self.call_event("on_fallback_updated", playlist_uri) # self.call_event("on_fallback_updated", playlist_uri)
thread = Thread(target = func, args = (self, playlist_uri)) # thread = Thread(target = func, args = (self, playlist_uri))
thread.start() # thread.start()
def on_fallback_cleaned(self, cleaned_channel): # def on_fallback_cleaned(self, cleaned_channel):
""" # """
Called when the scheduled fallback queue has been cleaned up. # Called when the scheduled fallback queue has been cleaned up.
This event does not indicate that some fallback is actually playing. # This event does not indicate that some fallback is actually playing.
""" # """
def func(self, cleaned_channel): # def func(self, cleaned_channel):
self.logger.debug("on_fallback_cleaned(..)") # self.logger.debug("on_fallback_cleaned(..)")
self.call_event("on_fallback_cleaned", cleaned_channel) # self.call_event("on_fallback_cleaned", cleaned_channel)
thread = Thread(target = func, args = (self, cleaned_channel)) # thread = Thread(target = func, args = (self, cleaned_channel))
thread.start() # thread.start()
def on_fallback_active(self, timeslot, fallback_type): def on_fallback_active(self, timeslot, fallback_type):
...@@ -299,57 +299,57 @@ class EngineEventDispatcher(): ...@@ -299,57 +299,57 @@ class EngineEventDispatcher():
Called when a fallback is activated for the given timeslot, Called when a fallback is activated for the given timeslot,
since no default playlist is available. since no default playlist is available.
""" """
def func(self, timeslot, fallback_type): def func(self, timeslot, fallback_type):
self.logger.debug("on_fallback_active(..)") self.logger.debug("on_fallback_active(..)")
self.call_event("on_fallback_active", timeslot, fallback_type) self.call_event("on_fallback_active", timeslot, fallback_type)
thread = Thread(target = func, args = (self, timeslot, fallback_type)) thread = Thread(target = func, args = (self, timeslot, fallback_type))
thread.start() thread.start()
def on_queue(self, entries): def on_queue(self, entries):
""" """
One or more entries have been queued and are currently pre-loaded. One or more entries have been queued and are currently pre-loaded.
""" """
def func(self, entries): def func(self, entries):
self.logger.debug("on_queue(..)") self.logger.debug("on_queue(..)")
self.call_event("on_queue", entries) self.call_event("on_queue", entries)
thread = Thread(target = func, args = (self, entries)) thread = Thread(target = func, args = (self, entries))
thread.start() thread.start()
def on_sick(self, data): def on_sick(self, data):
""" """
Called when the engine is in some unhealthy state. Called when the engine is in some unhealthy state.
""" """
def func(self, data): def func(self, data):
self.logger.debug("on_sick(..)") self.logger.debug("on_sick(..)")
self.call_event("on_sick", data) self.call_event("on_sick", data)
thread = Thread(target = func, args = (self, data)) thread = Thread(target = func, args = (self, data))
thread.start() thread.start()
def on_resurrect(self, data): def on_resurrect(self, data):
""" """
Called when the engine turned healthy again after being sick. Called when the engine turned healthy again after being sick.
""" """
def func(self, data): def func(self, data):
self.logger.debug("on_resurrect(..)") self.logger.debug("on_resurrect(..)")
self.call_event("on_resurrect", data) self.call_event("on_resurrect", data)
thread = Thread(target = func, args = (self, data)) thread = Thread(target = func, args = (self, data))
thread.start() thread.start()
def on_critical(self, subject, message, data=None): def on_critical(self, subject, message, data=None):
""" """
Callend when some critical event occurs Callend when some critical event occurs
""" """
def func(self, subject, message, data): def func(self, subject, message, data):
self.logger.debug("on_critical(..)") self.logger.debug("on_critical(..)")
self.call_event("on_critical", (subject, message, data)) self.call_event("on_critical", (subject, message, data))
thread = Thread(target = func, args = (self, subject, message, data)) thread = Thread(target = func, args = (self, subject, message, data))
thread.start() thread.start()
\ No newline at end of file \ No newline at end of file
...@@ -22,7 +22,6 @@ import logging ...@@ -22,7 +22,6 @@ import logging
import requests import requests
from collections import deque from collections import deque
from datetime import datetime, timedelta
from src.base.config import AuraConfig from src.base.config import AuraConfig
from src.base.utils import SimpleUtil as SU from src.base.utils import SimpleUtil as SU
...@@ -181,7 +180,7 @@ class TrackServiceHandler(): ...@@ -181,7 +180,7 @@ class TrackServiceHandler():
""" """
planned_playlist = None planned_playlist = None
if self.engine.scheduler: if self.engine.scheduler:
(fallback_type, planned_playlist) = self.engine.scheduler.get_active_playlist() (playlist_type, planned_playlist) = self.engine.scheduler.get_active_playlist()
(past_timeslot, current_timeslot, next_timeslot) = self.playlog.get_timeslots() (past_timeslot, current_timeslot, next_timeslot) = self.playlog.get_timeslots()
data = dict() data = dict()
...@@ -349,21 +348,29 @@ class Playlog: ...@@ -349,21 +348,29 @@ class Playlog:
data ({}): The dictionary holding the (virtual) timeslot data ({}): The dictionary holding the (virtual) timeslot
timeslot (Timeslot): The actual timeslot object to retrieve fallback info from timeslot (Timeslot): The actual timeslot object to retrieve fallback info from
""" """
fallback_type = None playlist_type = None
playlist = None playlist = None
if timeslot: if timeslot:
fallback_type, playlist = self.engine.scheduler.fallback.resolve_playlist(timeslot) playlist_type, playlist = self.engine.scheduler.resolve_playlist(timeslot)
if playlist: if playlist:
data["playlist_id"] = playlist.playlist_id data["playlist_id"] = playlist.playlist_id
else: else:
data["playlist_id"] = -1 data["playlist_id"] = -1
if fallback_type: #FIXME "fallback_type" should be a more generic "playout_state"? (compare meta#42)
data["fallback_type"] = fallback_type.id #FIXME Add field for "playlist_type", which now differs from playout-state
else: #FIXME Remove dependency to "scheduler" and "scheduler.fallback" module
data["fallback_type"] = FallbackType.STATION.id data["fallback_type"] = 0
if self.engine.scheduler:
playout_state = self.engine.scheduler.fallback.get_playout_state()
data["fallback_type"] = playout_state.id
# if playlist_type:
# data["fallback_type"] = playlist_type.id
# else:
# data["fallback_type"] = FallbackType.STATION.id
def get_timeslots(self): def get_timeslots(self):
......
...@@ -37,7 +37,7 @@ class ApiFetcher(threading.Thread): ...@@ -37,7 +37,7 @@ class ApiFetcher(threading.Thread):
""" """
config = None config = None
logging = None logging = None
queue = None queue = None
has_already_fetched = False has_already_fetched = False
fetched_timeslot_data = None fetched_timeslot_data = None
stop_event = None stop_event = None
...@@ -62,7 +62,7 @@ class ApiFetcher(threading.Thread): ...@@ -62,7 +62,7 @@ class ApiFetcher(threading.Thread):
self.tank_secret = self.config.get("api_tank_secret") self.tank_secret = self.config.get("api_tank_secret")
self.queue = queue.Queue() self.queue = queue.Queue()
self.stop_event = threading.Event() self.stop_event = threading.Event()
threading.Thread.__init__(self) threading.Thread.__init__(self)
...@@ -121,14 +121,13 @@ class ApiFetcher(threading.Thread): ...@@ -121,14 +121,13 @@ class ApiFetcher(threading.Thread):
for timeslot in self.fetched_timeslot_data: for timeslot in self.fetched_timeslot_data:
# FIXME Workaround until https://gitlab.servus.at/aura/steering/-/issues/54 is implemented if "schedule_default_playlist_id" in timeslot:
if "schedule_fallback_id" in timeslot: timeslot["default_schedule_playlist_id"] = timeslot["schedule_default_playlist_id"]
timeslot["default_schedule_playlist_id"] = timeslot["schedule_fallback_id"]
timeslot["schedule_fallback_id"] = None timeslot["schedule_fallback_id"] = None
if "show_fallback_id" in timeslot: if "show_default_playlist_id" in timeslot:
timeslot["default_show_playlist_id"] = timeslot["show_fallback_id"] timeslot["default_show_playlist_id"] = timeslot["show_default_playlist_id"]
timeslot["show_fallback_id"] = None timeslot["show_fallback_id"] = None
self.logger.debug("Fetching playlists from TANK") self.logger.debug("Fetching playlists from TANK")
self.fetch_playlists() self.fetch_playlists()
...@@ -163,15 +162,15 @@ class ApiFetcher(threading.Thread): ...@@ -163,15 +162,15 @@ class ApiFetcher(threading.Thread):
""" """
timeslots = None timeslots = None
headers = { "content-type": "application/json" } headers = { "content-type": "application/json" }
try: try:
self.logger.debug("Fetch timeslots from Steering API...") self.logger.debug("Fetch timeslots from Steering API...")
response = requests.get(self.steering_calendar_url, data=None, headers=headers) response = requests.get(self.steering_calendar_url, data=None, headers=headers)
if not response.status_code == 200: if not response.status_code == 200:
self.logger.critical(SU.red("HTTP Status: %s | Timeslots could not be fetched! Response: %s" % \ self.logger.critical(SU.red("HTTP Status: %s | Timeslots could not be fetched! Response: %s" % \
(str(response.status_code), response.text))) (str(response.status_code), response.text)))
return None return None
timeslots = response.json() timeslots = response.json()
except Exception as e: except Exception as e:
self.logger.critical(SU.red("Error while requesting timeslots from Steering!"), e) self.logger.critical(SU.red("Error while requesting timeslots from Steering!"), e)
...@@ -198,8 +197,8 @@ class ApiFetcher(threading.Thread): ...@@ -198,8 +197,8 @@ class ApiFetcher(threading.Thread):
# Get IDs of specific, default and fallback playlists # Get IDs of specific, default and fallback playlists
playlist_id = self.get_playlist_id(timeslot, "playlist_id") playlist_id = self.get_playlist_id(timeslot, "playlist_id")
default_schedule_playlist_id = self.get_playlist_id(timeslot, "default_schedule_playlist_id") default_schedule_playlist_id = self.get_playlist_id(timeslot, "default_schedule_playlist_id")
default_show_playlist_id = self.get_playlist_id(timeslot, "default_show_playlist_id") default_show_playlist_id = self.get_playlist_id(timeslot, "default_show_playlist_id")
schedule_fallback_id = self.get_playlist_id(timeslot, "schedule_fallback_id") schedule_fallback_id = self.get_playlist_id(timeslot, "schedule_fallback_id")
show_fallback_id = self.get_playlist_id(timeslot, "show_fallback_id") show_fallback_id = self.get_playlist_id(timeslot, "show_fallback_id")
station_fallback_id = self.get_playlist_id(timeslot, "station_fallback_id") station_fallback_id = self.get_playlist_id(timeslot, "station_fallback_id")
...@@ -208,7 +207,7 @@ class ApiFetcher(threading.Thread): ...@@ -208,7 +207,7 @@ class ApiFetcher(threading.Thread):
timeslot["playlist"] = self.fetch_playlist(playlist_id, fetched_entries) timeslot["playlist"] = self.fetch_playlist(playlist_id, fetched_entries)
timeslot["default_schedule_playlist"] = self.fetch_playlist(default_schedule_playlist_id, fetched_entries) timeslot["default_schedule_playlist"] = self.fetch_playlist(default_schedule_playlist_id, fetched_entries)
timeslot["default_show_playlist"] = self.fetch_playlist(default_show_playlist_id, fetched_entries) timeslot["default_show_playlist"] = self.fetch_playlist(default_show_playlist_id, fetched_entries)
timeslot["schedule_fallback"] = self.fetch_playlist(schedule_fallback_id, fetched_entries) timeslot["schedule_fallback"] = self.fetch_playlist(schedule_fallback_id, fetched_entries)
timeslot["show_fallback"] = self.fetch_playlist(show_fallback_id, fetched_entries) timeslot["show_fallback"] = self.fetch_playlist(show_fallback_id, fetched_entries)
timeslot["station_fallback"] = self.fetch_playlist(station_fallback_id, fetched_entries) timeslot["station_fallback"] = self.fetch_playlist(station_fallback_id, fetched_entries)
...@@ -232,9 +231,9 @@ class ApiFetcher(threading.Thread): ...@@ -232,9 +231,9 @@ class ApiFetcher(threading.Thread):
return None return None
playlist = None playlist = None
url = self.tank_playlist_url.replace("${ID}", playlist_id) url = self.tank_playlist_url.replace("${ID}", playlist_id)
headers = { headers = {
"Authorization": "Bearer %s:%s" % (self.tank_session, self.tank_secret), "Authorization": "Bearer %s:%s" % (self.tank_session, self.tank_secret),
"content-type": "application/json" "content-type": "application/json"
} }
...@@ -243,22 +242,22 @@ class ApiFetcher(threading.Thread): ...@@ -243,22 +242,22 @@ class ApiFetcher(threading.Thread):
if playlist["id"] == playlist_id: if playlist["id"] == playlist_id:
self.logger.debug("Playlist #%s already fetched" % playlist_id) self.logger.debug("Playlist #%s already fetched" % playlist_id)
return playlist return playlist
try: try:
self.logger.debug("Fetch playlist from Tank API...") self.logger.debug("Fetch playlist from Tank API...")
response = requests.get(url, data=None, headers=headers) response = requests.get(url, data=None, headers=headers)
if not response.status_code == 200: if not response.status_code == 200:
self.logger.critical(SU.red("HTTP Status: %s | Playlist #%s could not be fetched or is not available! Response: %s" % \ self.logger.critical(SU.red("HTTP Status: %s | Playlist #%s could not be fetched or is not available! Response: %s" % \
(str(response.status_code), str(playlist_id), response.text))) (str(response.status_code), str(playlist_id), response.text)))
return None return None
playlist = response.json() playlist = response.json()
except Exception as e: except Exception as e:
self.logger.critical(SU.red("Error while requesting playlist #%s from Tank" % str(playlist_id)), e) self.logger.critical(SU.red("Error while requesting playlist #%s from Tank" % str(playlist_id)), e)
return None return None
fetched_playlists.append(playlist) fetched_playlists.append(playlist)
return playlist