From 94c38f18b2f0145d12546b745f1eeffb67069b6a Mon Sep 17 00:00:00 2001
From: David Trattnig <david.trattnig@o94.at>
Date: Thu, 29 Jul 2021 15:30:24 +0200
Subject: [PATCH] Default playlists for show and schedule-level. #52

---
 src/engine.py               |  70 +++++-----
 src/events.py               |  90 ++++++-------
 src/plugins/trackservice.py |  23 ++--
 src/scheduling/api.py       |  45 +++----
 src/scheduling/fallback.py  | 257 ++++++++++++++++++------------------
 src/scheduling/models.py    |  57 ++++----
 src/scheduling/programme.py |  71 +++++-----
 src/scheduling/scheduler.py |  34 +++--
 src/scheduling/utils.py     |  12 +-
 9 files changed, 348 insertions(+), 311 deletions(-)

diff --git a/src/engine.py b/src/engine.py
index b0f23aac..11a07222 100644
--- a/src/engine.py
+++ b/src/engine.py
@@ -374,41 +374,41 @@ class Player:
 
 
 
-    def start_fallback_playlist(self, entries):
-        """
-        Sets any scheduled fallback playlist and performs a fade-in.
-
-        Args:
-            entries ([Entry]):    The playlist entries
-        """
-        self.preload_group(entries, ChannelType.FALLBACK_QUEUE)
-        self.play(entries[0], TransitionType.FADE)
-        self.event_dispatcher.on_fallback_updated(entries)
-
-
-
-    def stop_fallback_playlist(self):
-        """
-        Performs a fade-out and clears any scheduled fallback playlist.
-        """
-        dirty_channel = self.channel_router.get_active(ChannelType.FALLBACK_QUEUE)
-
-        self.logger.info(f"Fading out channel '{dirty_channel}'")
-        self.connector.enable_transaction()
-        self.mixer_fallback.fade_out(dirty_channel)
-        self.connector.disable_transaction()
-
-        def clean_up():
-            # 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).
-            time.sleep(2)
-            self.connector.enable_transaction()
-            self.mixer_fallback.channel_activate(dirty_channel.value, False)
-            res = self.queue_clear(dirty_channel)
-            self.logger.info("Clear Fallback Queue Response: " + res)
-            self.connector.disable_transaction()
-            self.event_dispatcher.on_fallback_cleaned(dirty_channel)
-        Thread(target=clean_up).start()
+    # def start_fallback_playlist(self, entries):
+    #     """
+    #     Sets any scheduled fallback playlist and performs a fade-in.
+
+    #     Args:
+    #         entries ([Entry]):    The playlist entries
+    #     """
+    #     self.preload_group(entries, ChannelType.FALLBACK_QUEUE)
+    #     self.play(entries[0], TransitionType.FADE)
+    #     self.event_dispatcher.on_fallback_updated(entries)
+
+
+
+    # def stop_fallback_playlist(self):
+    #     """
+    #     Performs a fade-out and clears any scheduled fallback playlist.
+    #     """
+    #     dirty_channel = self.channel_router.get_active(ChannelType.FALLBACK_QUEUE)
+
+    #     self.logger.info(f"Fading out channel '{dirty_channel}'")
+    #     self.connector.enable_transaction()
+    #     self.mixer_fallback.fade_out(dirty_channel)
+    #     self.connector.disable_transaction()
+
+    #     def clean_up():
+    #         # 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).
+    #         time.sleep(2)
+    #         self.connector.enable_transaction()
+    #         self.mixer_fallback.channel_activate(dirty_channel.value, False)
+    #         res = self.queue_clear(dirty_channel)
+    #         self.logger.info("Clear Fallback Queue Response: " + res)
+    #         self.connector.disable_transaction()
+    #         self.event_dispatcher.on_fallback_cleaned(dirty_channel)
+    #     Thread(target=clean_up).start()
 
 
 
diff --git a/src/events.py b/src/events.py
index 74aca24b..c73ea27e 100644
--- a/src/events.py
+++ b/src/events.py
@@ -89,7 +89,7 @@ class EngineEventDispatcher():
         self.logger = logging.getLogger("AuraEngine")
         self.config = AuraConfig.config()
         self.engine = engine
-        
+
         binding = self.attach(AuraMailer)
         binding.subscribe("on_critical")
         binding.subscribe("on_sick")
@@ -183,33 +183,33 @@ class EngineEventDispatcher():
         """
         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.scheduler.on_ready()
             self.call_event("on_ready", param)
 
         thread = Thread(target = func, args = (self, None))
-        thread.start()             
+        thread.start()
 
 
     def on_timeslot_start(self, timeslot):
         """
         Called when a timeslot starts.
         """
-        def func(self, timeslot):        
+        def func(self, timeslot):
             self.logger.debug("on_timeslot_start(..)")
             self.fallback_manager.on_timeslot_start(timeslot)
             self.call_event("on_timeslot_start", timeslot)
 
         thread = Thread(target = func, args = (self, timeslot))
-        thread.start() 
+        thread.start()
 
 
     def on_timeslot_end(self, timeslot):
         """
         Called when a timeslot ends.
         """
-        def func(self, timeslot):        
+        def func(self, timeslot):
             self.logger.debug("on_timeslot_end(..)")
             self.fallback_manager.on_timeslot_end(timeslot)
             self.call_event("on_timeslot_end", timeslot)
@@ -227,7 +227,7 @@ class EngineEventDispatcher():
         Args:
             source (String):    The `PlaylistEntry` object
         """
-        def func(self, entry):        
+        def func(self, entry):
             self.logger.debug("on_play(..)")
             # Assign timestamp indicating start play time. Use the actual playtime when possible.
             entry.entry_start_actual = datetime.datetime.now()
@@ -235,63 +235,63 @@ class EngineEventDispatcher():
             self.call_event("on_play", entry)
 
         thread = Thread(target = func, args = (self, entry))
-        thread.start()    
+        thread.start()
 
 
     def on_metadata(self, data):
         """
-        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 
+        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
         engine core (see `on_play(..)`).
 
         Args:
             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.fallback_manager.on_metadata(data)
             self.call_event("on_metadata", data)
 
         thread = Thread(target = func, args = (self, data))
-        thread.start() 
+        thread.start()
 
 
     def on_stop(self, entry):
         """
         The entry on the assigned channel has been stopped playing.
         """
-        def func(self, entry):        
+        def func(self, entry):
             self.logger.debug("on_stop(..)")
             self.call_event("on_stop", entry)
-        
+
         thread = Thread(target = func, args = (self, entry))
-        thread.start() 
+        thread.start()
 
 
-    def on_fallback_updated(self, playlist_uri):
-        """
-        Called when the scheduled fallback playlist has been updated.
-        This event does not indicate that the fallback is actually playing.
-        """
-        def func(self, playlist_uri):        
-            self.logger.debug("on_fallback_updated(..)")
-            self.call_event("on_fallback_updated", playlist_uri)
+    # def on_fallback_updated(self, playlist_uri):
+    #     """
+    #     Called when the scheduled fallback playlist has been updated.
+    #     This event does not indicate that the fallback is actually playing.
+    #     """
+    #     def func(self, playlist_uri):
+    #         self.logger.debug("on_fallback_updated(..)")
+    #         self.call_event("on_fallback_updated", playlist_uri)
 
-        thread = Thread(target = func, args = (self, playlist_uri))
-        thread.start() 
+    #     thread = Thread(target = func, args = (self, playlist_uri))
+    #     thread.start()
 
 
-    def on_fallback_cleaned(self, cleaned_channel):
-        """
-        Called when the scheduled fallback queue has been cleaned up.
-        This event does not indicate that some fallback is actually playing.
-        """
-        def func(self, cleaned_channel):        
-            self.logger.debug("on_fallback_cleaned(..)")
-            self.call_event("on_fallback_cleaned", cleaned_channel)
+    # def on_fallback_cleaned(self, cleaned_channel):
+    #     """
+    #     Called when the scheduled fallback queue has been cleaned up.
+    #     This event does not indicate that some fallback is actually playing.
+    #     """
+    #     def func(self, cleaned_channel):
+    #         self.logger.debug("on_fallback_cleaned(..)")
+    #         self.call_event("on_fallback_cleaned", cleaned_channel)
 
-        thread = Thread(target = func, args = (self, cleaned_channel))
-        thread.start() 
+    #     thread = Thread(target = func, args = (self, cleaned_channel))
+    #     thread.start()
 
 
     def on_fallback_active(self, timeslot, fallback_type):
@@ -299,57 +299,57 @@ class EngineEventDispatcher():
         Called when a fallback is activated for the given timeslot,
         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.call_event("on_fallback_active", timeslot, fallback_type)
 
         thread = Thread(target = func, args = (self, timeslot, fallback_type))
-        thread.start() 
+        thread.start()
 
 
     def on_queue(self, entries):
         """
         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.call_event("on_queue", entries)
 
         thread = Thread(target = func, args = (self, entries))
-        thread.start() 
+        thread.start()
 
 
     def on_sick(self, data):
         """
         Called when the engine is in some unhealthy state.
         """
-        def func(self, data):        
+        def func(self, data):
             self.logger.debug("on_sick(..)")
             self.call_event("on_sick", data)
 
         thread = Thread(target = func, args = (self, data))
-        thread.start() 
+        thread.start()
 
 
     def on_resurrect(self, data):
         """
         Called when the engine turned healthy again after being sick.
         """
-        def func(self, data):        
+        def func(self, data):
             self.logger.debug("on_resurrect(..)")
             self.call_event("on_resurrect", data)
 
         thread = Thread(target = func, args = (self, data))
-        thread.start() 
+        thread.start()
 
 
     def on_critical(self, subject, message, data=None):
         """
         Callend when some critical event occurs
         """
-        def func(self, subject, message, data):        
+        def func(self, subject, message, data):
             self.logger.debug("on_critical(..)")
             self.call_event("on_critical", (subject, message, data))
 
         thread = Thread(target = func, args = (self, subject, message, data))
-        thread.start() 
\ No newline at end of file
+        thread.start()
\ No newline at end of file
diff --git a/src/plugins/trackservice.py b/src/plugins/trackservice.py
index 28d6d7f3..44d41504 100644
--- a/src/plugins/trackservice.py
+++ b/src/plugins/trackservice.py
@@ -22,7 +22,6 @@ import logging
 import requests
 
 from collections                import deque
-from datetime                   import datetime, timedelta
 
 from src.base.config            import AuraConfig
 from src.base.utils             import SimpleUtil as SU
@@ -181,7 +180,7 @@ class TrackServiceHandler():
         """
         planned_playlist = None
         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()
 
         data = dict()
@@ -349,21 +348,29 @@ class Playlog:
             data ({}):              The dictionary holding the (virtual) timeslot
             timeslot (Timeslot):    The actual timeslot object to retrieve fallback info from
         """
-        fallback_type = None
+        playlist_type = None
         playlist = None
 
         if timeslot:
-            fallback_type, playlist = self.engine.scheduler.fallback.resolve_playlist(timeslot)
+            playlist_type, playlist = self.engine.scheduler.resolve_playlist(timeslot)
 
         if playlist:
             data["playlist_id"] = playlist.playlist_id
         else:
             data["playlist_id"] = -1
 
-        if fallback_type:
-            data["fallback_type"] = fallback_type.id
-        else:
-            data["fallback_type"] = FallbackType.STATION.id
+        #FIXME "fallback_type" should be a more generic "playout_state"? (compare meta#42)
+        #FIXME Add field for "playlist_type", which now differs from playout-state
+        #FIXME Remove dependency to "scheduler" and "scheduler.fallback" module
+        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):
diff --git a/src/scheduling/api.py b/src/scheduling/api.py
index f8831408..c7e97657 100644
--- a/src/scheduling/api.py
+++ b/src/scheduling/api.py
@@ -37,7 +37,7 @@ class ApiFetcher(threading.Thread):
     """
     config = None
     logging = None
-    queue = None    
+    queue = None
     has_already_fetched = False
     fetched_timeslot_data = None
     stop_event = None
@@ -62,7 +62,7 @@ class ApiFetcher(threading.Thread):
         self.tank_secret = self.config.get("api_tank_secret")
         self.queue = queue.Queue()
         self.stop_event = threading.Event()
-        threading.Thread.__init__(self)        
+        threading.Thread.__init__(self)
 
 
 
@@ -121,14 +121,13 @@ class ApiFetcher(threading.Thread):
 
         for timeslot in self.fetched_timeslot_data:
 
-            # FIXME Workaround until https://gitlab.servus.at/aura/steering/-/issues/54 is implemented
-            if "schedule_fallback_id" in timeslot:
-                timeslot["default_schedule_playlist_id"] = timeslot["schedule_fallback_id"]
+            if "schedule_default_playlist_id" in timeslot:
+                timeslot["default_schedule_playlist_id"] = timeslot["schedule_default_playlist_id"]
                 timeslot["schedule_fallback_id"] = None
-            if "show_fallback_id" in timeslot:
-                timeslot["default_show_playlist_id"] = timeslot["show_fallback_id"]
+            if "show_default_playlist_id" in timeslot:
+                timeslot["default_show_playlist_id"] = timeslot["show_default_playlist_id"]
                 timeslot["show_fallback_id"] = None
-                
+
         self.logger.debug("Fetching playlists from TANK")
         self.fetch_playlists()
 
@@ -163,15 +162,15 @@ class ApiFetcher(threading.Thread):
         """
         timeslots = None
         headers = { "content-type": "application/json" }
-       
+
         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)
             if not response.status_code == 200:
                 self.logger.critical(SU.red("HTTP Status: %s | Timeslots could not be fetched! Response: %s" % \
                     (str(response.status_code), response.text)))
-                return None            
-            timeslots = response.json()            
+                return None
+            timeslots = response.json()
         except Exception as e:
             self.logger.critical(SU.red("Error while requesting timeslots from Steering!"), e)
 
@@ -198,8 +197,8 @@ class ApiFetcher(threading.Thread):
                 # Get IDs of specific, default and fallback playlists
                 playlist_id = self.get_playlist_id(timeslot, "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")                   
-                schedule_fallback_id = self.get_playlist_id(timeslot, "schedule_fallback_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")
                 show_fallback_id = self.get_playlist_id(timeslot, "show_fallback_id")
                 station_fallback_id = self.get_playlist_id(timeslot, "station_fallback_id")
 
@@ -208,7 +207,7 @@ class ApiFetcher(threading.Thread):
                 timeslot["playlist"] = self.fetch_playlist(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["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["station_fallback"]  = self.fetch_playlist(station_fallback_id, fetched_entries)
 
@@ -232,9 +231,9 @@ class ApiFetcher(threading.Thread):
             return None
 
         playlist = None
-        url = self.tank_playlist_url.replace("${ID}", playlist_id) 
+        url = self.tank_playlist_url.replace("${ID}", playlist_id)
         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"
         }
 
@@ -243,22 +242,22 @@ class ApiFetcher(threading.Thread):
             if playlist["id"] == playlist_id:
                 self.logger.debug("Playlist #%s already fetched" % playlist_id)
                 return playlist
-                   
+
         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)
             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" % \
                     (str(response.status_code), str(playlist_id), response.text)))
-                return None          
-            playlist = response.json()                  
+                return None
+            playlist = response.json()
         except Exception as e:
             self.logger.critical(SU.red("Error while requesting playlist #%s from Tank" % str(playlist_id)), e)
             return None
 
         fetched_playlists.append(playlist)
         return playlist
- 
+
 
 
     def get_playlist_id(self, timeslot, id_name):
@@ -279,7 +278,7 @@ class ApiFetcher(threading.Thread):
         if not playlist_id or playlist_id == "None":
             self.logger.debug("No value defined for '%s' in timeslot '#%s'" % (id_name, timeslot["id"]))
             return None
-        
+
         return playlist_id
 
 
diff --git a/src/scheduling/fallback.py b/src/scheduling/fallback.py
index b2425091..6a6133aa 100644
--- a/src/scheduling/fallback.py
+++ b/src/scheduling/fallback.py
@@ -36,14 +36,12 @@ class FallbackType(Enum):
     """
     Types of fallbacks.
 
-        NONE:       No fallback active, default playout
-        SCHEDULE:   The first played when some default playlist fails
-        SHOW:       The second played when the timeslot fallback fails
-        STATION:    The last played when everything else fails
+        NONE:       No fallback active, default playout as planned
+        STATION:    The station fallback is played when everything else fails
     """
     NONE        = { "id": 0, "name": "default", "channels": [ Channel.QUEUE_A, Channel.QUEUE_B ] }
-    SCHEDULE    = { "id": 1, "name": "schedule", "channels": [ Channel.FALLBACK_QUEUE_A, Channel.FALLBACK_QUEUE_B ] }   
-    SHOW        = { "id": 2, "name": "show", "channels": [ Channel.FALLBACK_QUEUE_A, Channel.FALLBACK_QUEUE_B ] }
+    # SCHEDULE    = { "id": 1, "name": "schedule", "channels": [ Channel.FALLBACK_QUEUE_A, Channel.FALLBACK_QUEUE_B ] }
+    # SHOW        = { "id": 2, "name": "show", "channels": [ Channel.FALLBACK_QUEUE_A, Channel.FALLBACK_QUEUE_B ] }
     STATION     = { "id": 3, "name": "station", "channels": [ Channel.FALLBACK_STATION_FOLDER, Channel.FALLBACK_STATION_PLAYLIST ] }
 
     @property
@@ -61,14 +59,13 @@ class FallbackType(Enum):
 
 class FallbackManager:
     """
-    Handles all types of fallbacks in case there is an outage or missing schedules
-    for the radio programme.
-    """    
+    Manages if engine is in normal or fallback play-state.
+    """
     config = None
     logger = None
     engine = None
     state = None
-    
+
 
     def __init__(self, engine):
         """
@@ -80,9 +77,9 @@ class FallbackManager:
         self.config = AuraConfig.config()
         self.logger = logging.getLogger("AuraEngine")
         self.engine = engine
-        self.state = {            
+        self.state = {
             "fallback_type": FallbackType.NONE,
-            "previous_fallback_type": None, 
+            "previous_fallback_type": None,
             "timeslot": None
         }
 
@@ -101,7 +98,7 @@ class FallbackManager:
 
     def on_timeslot_end(self, timeslot):
         """
-        The timeslot has ended and the state is updated. The method ensures that any intermediate state 
+        The timeslot has ended and the state is updated. The method ensures that any intermediate state
         update doesn't get overwritten.
         """
         if self.state["timeslot"] == timeslot:
@@ -110,11 +107,11 @@ class FallbackManager:
 
     def on_play(self, entry):
         """
-        Event Handler which is called by the engine when some entry is actually playing. 
+        Event Handler which is called by the engine when some entry is actually playing.
 
         Args:
             source (String):    The `PlaylistEntry` object
-        """     
+        """
         content_class = ResourceUtil.get_content_class(entry.get_content_type())
         if content_class == ResourceClass.FILE:
             # Files are handled by "on_metadata" called via Liquidsoap
@@ -125,8 +122,8 @@ class FallbackManager:
 
     def on_metadata(self, data):
         """
-        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 
+        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
         engine core (see `on_play(..)`).
 
         Args:
@@ -151,6 +148,13 @@ class FallbackManager:
     #
 
 
+    def get_playout_state(self):
+        """
+        Returns the current playout state, like normal or fallback.
+        """
+        return self.state["fallback_type"]
+
+
     def update_fallback_state(self, channel):
         """
         Update the current and previously active fallback state.
@@ -168,151 +172,150 @@ class FallbackManager:
         """
         Retrieves the matching fallback type for the given source.
         """
-        if source in [str(i) for i in FallbackType.SCHEDULE.channels]:
-            return FallbackType.SCHEDULE
-        if source in [str(i) for i in FallbackType.SHOW.channels]:
-            return FallbackType.SHOW
+        # if source in [str(i) for i in FallbackType.SCHEDULE.channels]:
+        #     return FallbackType.SCHEDULE
+        # if source in [str(i) for i in FallbackType.SHOW.channels]:
+        #     return FallbackType.SHOW
         if source in [str(i) for i in FallbackType.STATION.channels]:
             return FallbackType.STATION
         return FallbackType.NONE
 
 
-    def queue_fallback_playlist(self, timeslot):
-        """
-        Evaluates the scheduled fallback and queues it using a timed thread.
-        """
-        (fallback_type, playlist) = self.get_fallback_playlist(timeslot)
+    # def queue_fallback_playlist(self, timeslot):
+    #     """
+    #     Evaluates the scheduled fallback and queues it using a timed thread.
+    #     """
+    #     (fallback_type, playlist) = self.get_fallback_playlist(timeslot)
 
-        if playlist:
-            self.logger.info(f"Resolved {fallback_type.value} fallback")         
-            return FallbackCommand(timeslot, playlist.entries)
-        else:
-            msg = f"There is no timeslot- or show-fallback defined for timeslot#{timeslot.timeslot_id}. "
-            msg += f"The station fallback will be used automatically."
-            self.logger.info(msg)
+    #     if playlist:
+    #         self.logger.info(f"Resolved {fallback_type.value} fallback")
+    #         return FallbackCommand(timeslot, playlist.entries)
+    #     else:
+    #         msg = f"There is no fallback playlist on timeslot- or show-level defined for timeslot#{timeslot.timeslot_id}. "
+    #         msg += f"The station fallback will be used automatically."
+    #         self.logger.info(msg)
 
 
 
-    def resolve_playlist(self, timeslot):
-        """
-        Retrieves the currently planned (fallback) playlist. If a normal playlist is available,
-        this one is returned. In case of station fallback no playlist is returned.
+    # def resolve_playlist(self, timeslot):
+    #     """
+    #     Retrieves the currently planned, default or fallback playlist. If a normal playlist is available,
+    #     this one is returned. In case of a station fallback to be triggered, no playlist is returned.
 
-        Args:
-            timeslot (Timeslot)
-        
-        Returns:
-            (FallbackType, Playlist)
-        """
-        fallback_type = None
-        planned_playlist = self.engine.scheduler.programme.get_current_playlist(timeslot)
+    #     Args:
+    #         timeslot (Timeslot)
 
-        if planned_playlist:        
-            fallback_type = FallbackType.NONE
-        else:
-            (fallback_type, planned_playlist) = self.get_fallback_playlist(timeslot)            
+    #     Returns:
+    #         (FallbackType, Playlist)
+    #     """
+    #     (playlist_type, planned_playlist) = self.engine.scheduler.programme.get_current_playlist(timeslot)
 
-        return (fallback_type, planned_playlist)
+    #     if planned_playlist:
+    #         playlist_type = FallbackType.NONE
+    #     else:
+    #         (playlist_type, planned_playlist) = self.get_fallback_playlist(timeslot)
 
+    #     return (playlist_type, planned_playlist)
 
 
-    def get_fallback_playlist(self, timeslot):
-        """
-        Retrieves the playlist to be used in a fallback scenario.
 
-        Args: 
-            timeslot (Timeslot)
+    # def get_fallback_playlist(self, timeslot):
+    #     """
+    #     Retrieves the playlist to be used in a fallback scenario.
 
-        Returns:
-            (Playlist) 
-        """        
-        playlist = None
-        fallback_type = FallbackType.STATION
+    #     Args:
+    #         timeslot (Timeslot)
 
-        if self.validate_playlist(timeslot, "schedule_fallback"):
-            playlist = timeslot.schedule_fallback
-            fallback_type = FallbackType.SCHEDULE
-        elif self.validate_playlist(timeslot, "show_fallback"):
-            playlist = timeslot.show_fallback
-            fallback_type = FallbackType.SHOW
+    #     Returns:
+    #         (Playlist)
+    #     """
+    #     playlist = None
+    #     fallback_type = FallbackType.STATION
 
-        return (fallback_type, playlist)
+    #     if self.validate_playlist(timeslot, "schedule_fallback"):
+    #         playlist = timeslot.schedule_fallback
+    #         fallback_type = FallbackType.SCHEDULE
+    #     elif self.validate_playlist(timeslot, "show_fallback"):
+    #         playlist = timeslot.show_fallback
+    #         fallback_type = FallbackType.SHOW
 
+    #     return (fallback_type, playlist)
 
 
-    def validate_playlist(self, timeslot, playlist_type):
-        """
-        Checks if a playlist is valid for play-out.
 
-        Following checks are done for all playlists:
+    # def validate_playlist(self, timeslot, playlist_type):
+    #     """
+    #     Checks if a playlist is valid for play-out.
 
-            - has one or more entries
+    #     Following checks are done for all playlists:
 
-        Fallback playlists should either:
-                
-            - have filesystem entries only
-            - reference a recording of a previous playout of a show (also filesystem)
-        
-        Otherwise, if a fallback playlist contains Live or Stream entries,
-        the exact playout behaviour can hardly be predicted.
-        """
-        playlist = getattr(timeslot, playlist_type)
-        if playlist \
-            and playlist.entries \
-            and len(playlist.entries) > 0:
+    #         - has one or more entries
 
-            # Default playlist
-            if playlist_type == "playlist":
-                return True
+    #     Fallback playlists should either:
 
-            # Fallback playlist
-            elif playlist.entries:
-                is_fs_only = True
-                for entry in playlist.entries:
-                    if entry.get_content_type() not in ResourceClass.FILE.types:
-                        self.logger.error(SU.red("Fallback playlist of type '%s' contains not only file-system entries! \
-                            Skipping fallback level..." % playlist_type))
-                        is_fs_only = False
-                        break                
-                return is_fs_only
+    #         - have filesystem entries only
+    #         - reference a recording of a previous playout of a show (also filesystem)
 
-        return False
+    #     Otherwise, if a fallback playlist contains Live or Stream entries,
+    #     the exact playout behaviour can hardly be predicted.
+    #     """
+    #     playlist = getattr(timeslot, playlist_type)
+    #     if playlist \
+    #         and playlist.entries \
+    #         and len(playlist.entries) > 0:
 
+    #         # Default playlist
+    #         if playlist_type == "playlist":
+    #             return True
 
+    #         # Fallback playlist
+    #         elif playlist.entries:
+    #             is_fs_only = True
+    #             for entry in playlist.entries:
+    #                 if entry.get_content_type() not in ResourceClass.FILE.types:
+    #                     self.logger.error(SU.red("Fallback playlist of type '%s' contains not only file-system entries! \
+    #                         Skipping fallback level..." % playlist_type))
+    #                     is_fs_only = False
+    #                     break
+    #             return is_fs_only
 
+    #     return False
 
 
 
-class FallbackCommand(EngineExecutor):
-    """
-    Command composition for executing timed scheduling and unscheduling of fallback playlists.
 
-    Based on the `timeslot.start_date` and `timeslot.end_date` two `EngineExecutor commands
-    are created.
-    """
 
 
-    def __init__(self, timeslot, entries):
-        """
-        Constructor
+# class FallbackCommand(EngineExecutor):
+#     """
+#     Command composition for executing timed scheduling and unscheduling of fallback playlists.
 
-        Args:
-            timeslot (Timeslot):    The timeslot any fallback entries should be scheduled for
-            entries (List):         List of entries to be scheduled as fallback
-        """        
-        from src.engine import Engine
-
-        def do_play(entries):
-            self.logger.info(SU.cyan(f"=== start_fallback_playlist('{entries}') ==="))
-            Engine.get_instance().player.start_fallback_playlist(entries)
-
-        def do_stop():
-            self.logger.info(SU.cyan("=== stop_fallback_playlist() ==="))
-            Engine.get_instance().player.stop_fallback_playlist()
-
-        # Start fade-out 50% before the end of the timeslot for a more smooth transition
-        end_time_offset = int(float(AuraConfig.config().get("fade_out_time")) / 2 * 1000 * -1)
-        end_time = SU.timestamp(timeslot.timeslot_end + timedelta(milliseconds=end_time_offset))
-        self.logger.info(f"Starting fade-out of scheduled fallback with an offset of {end_time_offset} milliseconds at {end_time}")
-        super().__init__("FALLBACK", None, timeslot.start_unix, do_play, entries)
-        EngineExecutor("FALLBACK", self, end_time, do_stop, None)
\ No newline at end of file
+#     Based on the `timeslot.start_date` and `timeslot.end_date` two `EngineExecutor commands
+#     are created.
+#     """
+
+
+#     def __init__(self, timeslot, entries):
+#         """
+#         Constructor
+
+#         Args:
+#             timeslot (Timeslot):    The timeslot any fallback entries should be scheduled for
+#             entries (List):         List of entries to be scheduled as fallback
+#         """
+#         from src.engine import Engine
+
+#         def do_play(entries):
+#             self.logger.info(SU.cyan(f"=== start_fallback_playlist('{entries}') ==="))
+#             Engine.get_instance().player.start_fallback_playlist(entries)
+
+#         def do_stop():
+#             self.logger.info(SU.cyan("=== stop_fallback_playlist() ==="))
+#             Engine.get_instance().player.stop_fallback_playlist()
+
+#         # Start fade-out 50% before the end of the timeslot for a more smooth transition
+#         end_time_offset = int(float(AuraConfig.config().get("fade_out_time")) / 2 * 1000 * -1)
+#         end_time = SU.timestamp(timeslot.timeslot_end + timedelta(milliseconds=end_time_offset))
+#         self.logger.info(f"Starting fade-out of scheduled fallback with an offset of {end_time_offset} milliseconds at {end_time}")
+#         super().__init__("FALLBACK", None, timeslot.start_unix, do_play, entries)
+#         EngineExecutor("FALLBACK", self, end_time, do_stop, None)
\ No newline at end of file
diff --git a/src/scheduling/models.py b/src/scheduling/models.py
index f6a15987..f5a6b487 100644
--- a/src/scheduling/models.py
+++ b/src/scheduling/models.py
@@ -331,6 +331,11 @@ class Playlist(DB.Model, AuraDatabaseModel):
     """
     __tablename__ = 'playlist'
 
+    # Static Playlist Types
+    TYPE_TIMESLOT = { "id": 0, "name": "timeslot" }
+    TYPE_SCHEDULE = { "id": 1, "name": "schedule" }
+    TYPE_SHOW = { "id": 2, "name": "show" }
+
     # Primary and Foreign Key
     artificial_id = Column(Integer, primary_key=True)
     timeslot_start = Column(DateTime, ForeignKey("timeslot.timeslot_start"))
@@ -345,19 +350,19 @@ class Playlist(DB.Model, AuraDatabaseModel):
     entry_count = Column(Integer)
 
 
-    @staticmethod
-    def select_all():
-        """
-        Fetches all entries
-        """
-        all_entries = DB.session.query(Playlist).filter(Playlist.fallback_type == 0).all()
+    # @staticmethod
+    # def select_all():
+    #     """
+    #     Fetches all entries
+    #     """
+    #     all_entries = DB.session.query(Playlist).filter(Playlist.fallback_type == 0).all()
 
-        cnt = 0
-        for entry in all_entries:
-            entry.programme_index = cnt
-            cnt = cnt + 1
+    #     cnt = 0
+    #     for entry in all_entries:
+    #         entry.programme_index = cnt
+    #         cnt = cnt + 1
 
-        return all_entries
+    #     return all_entries
 
 
     @staticmethod
@@ -441,21 +446,21 @@ class Playlist(DB.Model, AuraDatabaseModel):
         return total
 
 
-    def as_dict(self):
-        """
-        Returns the playlist as a dictionary for serialization.
-        """
-        entries = []
-        for e in self.entries:
-            entries.append(e.as_dict())
-
-        playlist = {
-            "playlist_id": self.playlist_id,
-            "fallback_type": self.fallback_type,
-            "entry_count": self.entry_count,
-            "entries": entries
-        }
-        return playlist
+    # def as_dict(self):
+    #     """
+    #     Returns the playlist as a dictionary for serialization.
+    #     """
+    #     entries = []
+    #     for e in self.entries:
+    #         entries.append(e.as_dict())
+
+    #     playlist = {
+    #         "playlist_id": self.playlist_id,
+    #         "fallback_type": self.fallback_type,
+    #         "entry_count": self.entry_count,
+    #         "entries": entries
+    #     }
+    #     return playlist
 
 
     def __str__(self):
diff --git a/src/scheduling/programme.py b/src/scheduling/programme.py
index 195c901a..3c29ca13 100644
--- a/src/scheduling/programme.py
+++ b/src/scheduling/programme.py
@@ -51,7 +51,7 @@ class ProgrammeService():
         Constructor
         """
         self.config = AuraConfig.config()
-        self.logger = logging.getLogger("AuraEngine")    
+        self.logger = logging.getLogger("AuraEngine")
         self.programme_store = ProgrammeStore()
 
 
@@ -87,7 +87,7 @@ class ProgrammeService():
             msg = SU.red("Load programme from DB, because of an unknown response from ApiFetcher: " + response)
             self.logger.warning(msg)
 
-        # Load latest programme from the database        
+        # Load latest programme from the database
         if not self.timeslots:
             self.timeslots = self.programme_store.load_timeslots()
             self.logger.info(SU.green("Finished loading current programme from database (%s timeslots)" % str(len(self.timeslots))))
@@ -99,7 +99,7 @@ class ProgrammeService():
 
     def get_current_entry(self):
         """
-        Retrieves the current `PlaylistEntry` which should be played as per programme. 
+        Retrieves the current `PlaylistEntry` which should be played as per programme.
 
         Returns:
             (PlaylistEntry): The track which is (or should) currently being played
@@ -117,7 +117,7 @@ class ProgrammeService():
             return None
 
         # Check for scheduled playlist
-        current_playlist = self.get_current_playlist(current_timeslot)
+        playlist_type, current_playlist = self.get_current_playlist(current_timeslot)
         if not current_playlist:
             msg = "There's no (default) playlist assigned to the current timeslot. Most likely a fallback will make things okay again."
             self.logger.warning(SU.red(msg))
@@ -129,21 +129,21 @@ class ProgrammeService():
             if entry.start_unix <= now_unix and now_unix <= entry.end_unix:
                 current_entry = entry
                 break
-      
+
         if not current_entry:
             # Nothing playing ... fallback will kick-in
-            msg = "There's no entry scheduled for playlist '%s' at %s" % (str(current_playlist), SU.fmt_time(now_unix))
+            msg = f"There's no entry scheduled for '{playlist_type.get('name')}' playlist '{str(current_playlist)}' at {SU.fmt_time(now_unix)}"
             self.logger.warning(SU.red(msg))
             return None
 
         return current_entry
- 
+
 
 
     def get_current_timeslot(self):
         """
-        Retrieves the timeslot currently to be played. 
-        
+        Retrieves the timeslot currently to be played.
+
         Returns:
             (Timeslot): The current timeslot
         """
@@ -156,7 +156,7 @@ class ProgrammeService():
                 if timeslot.start_unix <= now_unix and now_unix < timeslot.end_unix:
                     current_timeslot = timeslot
                     break
-        
+
         return current_timeslot
 
 
@@ -169,14 +169,17 @@ class ProgrammeService():
 
         Returns:
             (FallbackType, Playlist): The currently assigned playlist
-        """        
+        """
+        playlist_type = Playlist.TYPE_TIMESLOT
         playlist = timeslot.playlist
         if not playlist:
+            playlist_type = Playlist.TYPE_SCHEDULE
             playlist = timeslot.default_schedule_playlist
             if not playlist:
+                playlist_type = Playlist.TYPE_SHOW
                 playlist = timeslot.default_show_playlist
 
-        return playlist
+        return (playlist_type, playlist)
 
 
 
@@ -202,7 +205,7 @@ class ProgrammeService():
                     next_timeslots.append(timeslot)
                 else:
                     break
-        
+
         return next_timeslots
 
 
@@ -235,9 +238,9 @@ class ProgrammeService():
 
 
 class ProgrammeStore():
-    """ 
+    """
     The `ProgrammeStore` service retrieves all current schedules and related
-    playlists including audio files from the configured API endpoints and stores 
+    playlists including audio files from the configured API endpoints and stores
     it in the local database.
 
     To perform the API queries it utilizes the ApiFetcher class.
@@ -277,7 +280,7 @@ class ProgrammeStore():
         # Check if existing timeslots have been deleted
         self.update_deleted_timeslots(fetched_timeslots)
 
-        # Process fetched timeslots    
+        # Process fetched timeslots
         for timeslot in fetched_timeslots:
 
             # Check timeslot for validity
@@ -303,7 +306,7 @@ class ProgrammeStore():
             if timeslot_db.show_fallback_id:
                 self.store_playlist(timeslot_db, timeslot_db.show_fallback_id, timeslot["show_fallback"])
             if timeslot_db.station_fallback_id:
-                self.store_playlist(timeslot_db, timeslot_db.station_fallback_id, timeslot["station_fallback"])                    
+                self.store_playlist(timeslot_db, timeslot_db.station_fallback_id, timeslot["station_fallback"])
 
         return timeslots
 
@@ -312,10 +315,12 @@ class ProgrammeStore():
     def update_deleted_timeslots(self, fetched_timeslots):
         """
         Checks if some timeslot has been deleted remotely, so delete it locally too.
-        
-        Attention: This method has no effect if only a single timeslot got deleted, 
-        because this could simply indicate a issue with the API/Steering, since that 
-        means no data got retrieved.
+
+        Attention: This method has no effect if only a single existing timeslot got
+        deleted, i.e. zero timeslots got returned, because this could simply indicate
+        an issue with the API/Steering, since that means no data got retrieved. This
+        should not be a problem in real life scenarios though, as there's practically
+        always something in the timetable.
 
         Args:
             fetched_timeslots ([dict]): List of timeslot dictionaries from the API
@@ -332,10 +337,10 @@ class ProgrammeStore():
                 # Filter the local timeslot from the fetched ones
                 existing_remotely = list(filter(lambda new_timeslot: \
                     new_timeslot["timeslot_id"] == local_timeslot.timeslot_id, fetched_timeslots))
-                
+
                 if not existing_remotely:
                     # Only allow deletion of timeslots which are deleted before the start of the scheduling window
-                    if (local_timeslot.start_unix - scheduling_window_start) > now_unix:                            
+                    if (local_timeslot.start_unix - scheduling_window_start) > now_unix:
                         self.logger.info("Timeslot #%s has been deleted remotely, hence also delete it locally too [%s]" % \
                             (local_timeslot.timeslot_id, str(local_timeslot)))
                         local_timeslot.delete(commit=True)
@@ -379,7 +384,7 @@ class ProgrammeStore():
         # Optional API properties
         if "default_schedule_playlist_id" in timeslot:
             timeslot_db.default_schedule_playlist_id = timeslot["default_schedule_playlist_id"]
-        if "default_show_playlist_id" in timeslot:            
+        if "default_show_playlist_id" in timeslot:
             timeslot_db.default_show_playlist_id = timeslot["default_show_playlist_id"]
         if "schedule_fallback_id" in timeslot:
             timeslot_db.schedule_fallback_id = timeslot["schedule_fallback_id"]
@@ -400,7 +405,7 @@ class ProgrammeStore():
         if not playlist_id or not fetched_playlist:
             self.logger.debug(f"Playlist ID#{playlist_id} is not available!")
             return
-        
+
         playlist_db = Playlist.select_playlist_for_timeslot(timeslot_db.timeslot_start, playlist_id)
         havetoadd = False
 
@@ -418,7 +423,7 @@ class ProgrammeStore():
             playlist_db.entry_count = 0
 
         playlist_db.store(havetoadd, commit=True)
-      
+
         if playlist_db.entry_count > 0:
             self.store_playlist_entries(timeslot_db, playlist_db, fetched_playlist)
 
@@ -439,8 +444,8 @@ class ProgrammeStore():
         # In the future this is to be replaced by generic music pool feature.
         entries = self.m3u_processor.spread(entries)
 
-        self.expand_entry_duration(timeslot_db, entries)        
-        self.delete_orphaned_entries(playlist_db, entries)  
+        self.expand_entry_duration(timeslot_db, entries)
+        self.delete_orphaned_entries(playlist_db, entries)
 
         for entry in entries:
             entry_db = PlaylistEntry.select_playlistentry_for_playlist(playlist_db.artificial_id, entry_num)
@@ -479,10 +484,10 @@ class ProgrammeStore():
         existing_last_idx = PlaylistEntry.count_entries(playlist_db.artificial_id)-1
 
         if existing_last_idx < new_last_idx:
-            return 
+            return
 
         for entry_num in range(new_last_idx, existing_last_idx+1, 1):
-            PlaylistEntry.delete_entry(playlist_db.artificial_id, entry_num)            
+            PlaylistEntry.delete_entry(playlist_db.artificial_id, entry_num)
             self.logger.info(SU.yellow("Deleted playlist entry %s:%s" % (playlist_db.artificial_id, entry_num)))
             entry_num += 1
 
@@ -496,7 +501,7 @@ class ProgrammeStore():
         """
         total_seconds = (timeslot_db.timeslot_end - timeslot_db.timeslot_start).total_seconds()
         total_duration = SU.seconds_to_nano(total_seconds)
-        actual_duration = 0        
+        actual_duration = 0
         missing_duration = []
         idx = 0
 
@@ -506,7 +511,7 @@ class ProgrammeStore():
             else:
                 actual_duration += entry["duration"]
             idx += 1
-                
+
         if len(missing_duration) == 1:
             entries[missing_duration[0]]["duration"] = total_duration - actual_duration
             self.logger.info(f"Expanded duration of playlist entry #{missing_duration[0]}")
@@ -536,7 +541,7 @@ class ProgrammeStore():
             metadata_db.artist = metadata["artist"]
         else:
             metadata_db.artist = ""
-        
+
         if "album" in metadata:
             metadata_db.album = metadata["album"]
         else:
diff --git a/src/scheduling/scheduler.py b/src/scheduling/scheduler.py
index b701ca7b..62cbfd5d 100644
--- a/src/scheduling/scheduler.py
+++ b/src/scheduling/scheduler.py
@@ -25,7 +25,7 @@ import time
 
 from src.base.config            import AuraConfig
 from src.base.utils             import SimpleUtil as SU
-from src.scheduling.models      import AuraDatabaseModel
+from src.scheduling.models      import AuraDatabaseModel, Playlist
 from src.base.exceptions        import NoActiveTimeslotException, LoadSourceException
 from src.control                import EngineExecutor
 from src.engine                 import Engine
@@ -184,7 +184,7 @@ class AuraScheduler(threading.Thread):
         if active_timeslot:
             # Create command timer to indicate the start of the timeslot
             TimeslotCommand(self.engine, active_timeslot)
-            self.fallback.queue_fallback_playlist(active_timeslot)
+            # self.fallback.queue_fallback_playlist(active_timeslot)
 
         active_entry = self.programme.get_current_entry()
         if not active_entry:
@@ -231,18 +231,36 @@ class AuraScheduler(threading.Thread):
 
     def get_active_playlist(self):
         """
-        Retrieves the currently playing playlist.
+        Retrieves the currently playing playlist. If there is no specific playlist for this timeslots,
+        then any "default playlist" available on the timeslot or show level, is returned.
 
         Returns:
-            (FallbackType, Playlist): The resolved playlist
+            (Dict, Playlist): A dictionary holding the playlist type and the resolved playlist
         """
         timeslot = self.programme.get_current_timeslot()
         if timeslot:
-            return self.fallback.resolve_playlist(timeslot)
+            return self.resolve_playlist(timeslot)
         return (None, None)
 
 
 
+    def resolve_playlist(self, timeslot):
+        """
+        Retrieves the planned or default playlist for the given timeslot.
+
+        Args:
+            timeslot (Timeslot)
+
+        Returns:
+            (Dict, Playlist): A dictionary holding the playlist type and the resolved playlist
+        """
+        playlist_type, playlist = self.programme.get_current_playlist(timeslot)
+        # if not playlist:
+        #     (playlist_type, playlist) = self.fallback.resolve_playlist(timeslot)
+        return (playlist_type, playlist)
+
+
+
     def queue_programme(self):
         """
         Queues the current programme (playlists as per timeslot) by creating
@@ -259,9 +277,9 @@ class AuraScheduler(threading.Thread):
                 # Create command timer to indicate the start of the timeslot
                 TimeslotCommand(self.engine, next_timeslot)
                 # Schedule any available fallback playlist
-                self.fallback.queue_fallback_playlist(next_timeslot)
+                #self.fallback.queue_fallback_playlist(next_timeslot)
 
-                playlist = self.programme.get_current_playlist(next_timeslot)
+                playlist_type, playlist = self.programme.get_current_playlist(next_timeslot)
                 if playlist:
                     self.queue_playlist_entries(next_timeslot, playlist.entries, False, True)
 
@@ -278,7 +296,7 @@ class AuraScheduler(threading.Thread):
 
         # Queue the (rest of the) currently playing timeslot upon startup
         if current_timeslot:
-            current_playlist = self.programme.get_current_playlist(current_timeslot)
+            playlist_type, current_playlist = self.programme.get_current_playlist(current_timeslot)
 
             if current_playlist:
                 active_entry = self.programme.get_current_entry()
diff --git a/src/scheduling/utils.py b/src/scheduling/utils.py
index 80c8c1e0..ed94f7e0 100644
--- a/src/scheduling/utils.py
+++ b/src/scheduling/utils.py
@@ -201,19 +201,19 @@ class TimeslotRenderer:
             if active_timeslot.playlist:
                 planned_playlist = active_timeslot.playlist
 
-            (fallback_type, resolved_playlist) = self.scheduler.fallback.resolve_playlist(active_timeslot)
+            (playlist_type, resolved_playlist) = self.scheduler.resolve_playlist(active_timeslot)
 
             s += "\n│   Playing timeslot %s         " % active_timeslot
             if planned_playlist:
                 if resolved_playlist and resolved_playlist.playlist_id != planned_playlist.playlist_id:
                     s += "\n│       └── Playlist %s         " % planned_playlist
                     s += "\n│       "
-                    s += SU.red("↑↑↑ That's the originally planned playlist.") + ("Instead playing the fallback playlist below ↓↓↓")
+                    s += SU.red("↑↑↑ That's the originally planned playlist.") + ("Instead playing the default playlist below ↓↓↓")
 
             if resolved_playlist:
                 if not planned_playlist:
-                    s += "\n│                         "
-                    s += SU.red("No playlist assigned to timeslot. Instead playing the `%s` playlist below ↓↓↓" % SU.cyan(str(fallback_type)))
+                    s += "\n│      "
+                    s += SU.red(f"No playlist assigned to timeslot. Instead playing the '{playlist_type.get('name')}' playlist below ↓↓↓")
 
                 s += "\n│       └── Playlist %s         " % resolved_playlist
 
@@ -250,11 +250,11 @@ class TimeslotRenderer:
             s += "\n│   Nothing.         "
         else:
             for timeslot in next_timeslots:
-                (fallback_type, resolved_playlist) = self.scheduler.fallback.resolve_playlist(timeslot)
+                (playlist_type, resolved_playlist) = self.scheduler.resolve_playlist(timeslot)
                 if resolved_playlist:
 
                     s += "\n│   Queued timeslot %s         " % timeslot
-                    s += "\n│      └── Playlist %s         (Type: %s)" % (resolved_playlist, SU.cyan(str(fallback_type)))
+                    s += "\n│      └── Playlist %s         (Type: %s)" % (resolved_playlist, SU.cyan(str(playlist_type)))
                     if resolved_playlist.end_unix > timeslot.end_unix:
                         s += "\n│          %s!              " % \
                         (SU.red("↑↑↑ Playlist #%s ends after timeslot #%s!" % (resolved_playlist.playlist_id, timeslot.timeslot_id)))
-- 
GitLab