From b5faf5261508b09de0204bb69fd7ab53ba8fb041 Mon Sep 17 00:00:00 2001
From: David Trattnig <david.trattnig@o94.at>
Date: Thu, 30 Jan 2020 20:25:27 +0100
Subject: [PATCH] Added cue-in capability.

---
 .../communication/liquidsoap/communicator.py  | 126 ++++++++++++------
 1 file changed, 83 insertions(+), 43 deletions(-)

diff --git a/modules/communication/liquidsoap/communicator.py b/modules/communication/liquidsoap/communicator.py
index 33869e4f..d8c4e67e 100644
--- a/modules/communication/liquidsoap/communicator.py
+++ b/modules/communication/liquidsoap/communicator.py
@@ -121,10 +121,12 @@ class LiquidSoapCommunicator(ExceptionLogger):
     # ------------------------------------------------------------------------------------------ #
     def get_active_channel(self):
         """
-        gets active channel from programme
-        :return:
+        Retrieves the active channel from programme.
+
+        Returns:
+            (String):   The channel type, empty string if no channel is active.
         """
-        (show, active_entry) = self.scheduler.get_active_entry()
+        active_entry = self.scheduler.get_active_entry()
         if active_entry is None:
             return ""
         return active_entry.type
@@ -286,45 +288,58 @@ class LiquidSoapCommunicator(ExceptionLogger):
         return True
 
     # ------------------------------------------------------------------------------------------ #
-    def activate(self, new_entry):
-        # grab the actual active entry
-        (show, old_entry) = self.scheduler.get_active_entry()
-        # determine its type
-        # TODO Move to <get_active_entry>
-        # FIXME No need to switch if current type = new type
-        old_type = ScheduleEntryType.FILESYSTEM # Set default if no previous track is available
-        if old_entry:
-            old_type = old_entry.type
+    def activate(self, new_entry, cue_in=0.0):
+        """
+        Activates a new Playlist Entry.
+
+        Args:
+            new_entry (PlaylistEntry): The track to be played
+        
+        Raises:
+            (LQConnectionError): In case connecting to LiquidSoap isn't possible
+        """
+        
+        # Grab the actual active entry
+        active_entry = self.scheduler.get_active_entry()
+        # Set default channel, if no previous track is available
+        current_channel = ScheduleEntryType.FILESYSTEM 
+        if active_entry:
+            current_channel = active_entry.type
 
         try:
-            # enable transaction
             self.enable_transaction()
-
-            if old_type == new_entry.type:
-                # push something to active channel
-                self.activate_same_channel(new_entry)
+            if current_channel == new_entry.type:
+                self.activate_same_channel(new_entry, cue_in)
             else:
-                # switch to another channel
-                self.activate_different_channel(new_entry, old_type)
-
-            # disable conn
+                self.activate_different_channel(new_entry, cue_in, current_channel)
             self.disable_transaction()
 
-            # insert playlist entry
-            self.logger.critical("Trackservice entry not written here anymore")
-#            self.insert_track_service_entry(new_entry)
+            # FIXME Implement TrackService bi-directionally, log fallbacks too.
+            self.logger.critical("FIXME: Implement TrackService")
+#           self.insert_track_service_entry(new_entry)
         except LQConnectionError:
-            # we already caught and handled this error in __send_lqc_command__, but we do not want to execute this function further and pass the exception
+            # we already caught and handled this error in __send_lqc_command__, 
+            # but we do not want to execute this function further and pass the exception
             pass
 
-    # ------------------------------------------------------------------------------------------ #
-    def activate_same_channel(self, entry, activate_different_channel=False):
+
+    def activate_same_channel(self, entry, cue_in=0.0, activate_different_channel=False):
+        """
+        Activates a playlist entry for the current channel.
+
+        Args:
+            entry (PlaylistEntry):  The entry to play.
+            cue_in (Float):         A value in seconds where to cue the start of the entry.
+        """
         if not activate_different_channel:
             self.logger.info(TerminalColors.PINK.value + entry.type.value + " already active!" + TerminalColors.ENDC.value)
 
-        # push to fs or stream
+        # Check if it needs to be pushed to a filesystem queue or stream
         if entry.type == ScheduleEntryType.FILESYSTEM:
-            self.playlist_push(entry.filename)
+            uri = entry.filename
+            if cue_in > 0.0:
+                uri = "annotate:liq_cue_in=\"%s\":%s" % (str(cue_in), entry.filename)
+            self.playlist_push(uri)
             self.active_channel = entry.type
 
         elif entry.type == ScheduleEntryType.STREAM:
@@ -333,27 +348,36 @@ class LiquidSoapCommunicator(ExceptionLogger):
             self.active_channel = entry.type
 
         # else: # live
-        # nothing to do when we are live => just leave it as is
+        # Nothing to do when we are live => just leave it as is
 
         self.active_channel = entry.type
 
-        # set active channel to wanted volume
+        # Set active channel to wanted volume
         if not activate_different_channel:
             self.channel_volume(entry.type.value, entry.volume)
 
-    # ------------------------------------------------------------------------------------------ #
-    def activate_different_channel(self, entry, active_type):
+
+    def activate_different_channel(self, entry, cue_in, active_type):
+        """
+        Activates a playlist entry for a channel other then the currently active one.
+
+        Args:
+            entry (PlaylistEntry):            The entry to play.
+            cue_in (Float):                   A value in seconds where to cue the start of the entry.
+            active_type (ScheduleEntryType):  The type of the currently active channel
+        """     
         self.logger.info(TerminalColors.PINK.value + "LiquidSoapCommunicator is activating " + entry.type.value + " & deactivating " + active_type.value + "!" + TerminalColors.ENDC.value)
 
-        # reuse of this function, because activate_same_channel and activate_different_channel are doing pretty the same except setting of the volume to zero
-        self.activate_same_channel(entry, True)
+        # Reuse of this function, because activate_same_channel and activate_different_channel 
+        # are doing pretty the same except setting of the volume to zero
+        self.activate_same_channel(entry, cue_in, True)
 
-        # set other channels to zero volume
+        # Set other channels to zero volume
         others = self.all_inputs_but(entry.getChannel())
         for o in others:
             self.channel_volume(o, 0)
 
-        # set active channel to wanted volume
+        # Set active channel to wanted volume
         self.channel_volume(entry.type.value, entry.volume)
 
     # ------------------------------------------------------------------------------------------ #
@@ -399,7 +423,13 @@ class LiquidSoapCommunicator(ExceptionLogger):
 
     # ------------------------------------------------------------------------------------------ #
     def init_player(self):
-        (_, active_entry) = self.scheduler.get_active_entry()
+        """
+        Initializes the LiquidSoap Player after startup of the engine.
+
+        Returns:
+            (String):   Message that the player is started.
+        """
+        active_entry = self.scheduler.get_active_entry()
 
         t = LiquidSoapInitThread(self, active_entry)
         t.start()
@@ -461,7 +491,7 @@ class LiquidSoapCommunicator(ExceptionLogger):
 
     # ------------------------------------------------------------------------------------------ #
     def liquidsoap_help(self):
-        data = self.__send_lqc_command__(self.client, 'help')
+        data = self.__send_lqc_command__(self.client, "help", "")
         if not data:
             self.logger.warning("Could not get Liquidsoap's help")
         else:
@@ -475,15 +505,24 @@ class LiquidSoapCommunicator(ExceptionLogger):
     # ------------------------------------------------------------------------------------------ #
     def playlist_push(self, uri):
         """
-        Eine Uri in die Playlist einfügen
-        @type   uri:   str
-        @param  uri:   Die Uri
+        Adds an filesystem URI to the playlist
+
+        Args:
+            uri (String):   The URI of the file
+        Returns:
+            LiquidSoap Response
         """
         return self.__send_lqc_command__(self.client, "fs", "push", uri)
 
     # ------------------------------------------------------------------------------------------ #
     def playlist_seek(self, seconds_to_seek):
-        return self.__send_lqc_command__(self.client, "fs", "seek", seconds_to_seek)
+        """
+        Forwards the player (n) seconds.
+
+        Args:
+            seconds_to_seeks (Float):   The seconds to skip
+        """
+        return self.__send_lqc_command__(self.client, "fs", "seek", str(seconds_to_seek))
 
     # ------------------------------------------------------------------------------------------ #
     def version(self):
@@ -566,6 +605,7 @@ class LiquidSoapCommunicator(ExceptionLogger):
                     raise e
             else:
                 # also store when was last admin mail sent with which content...
+                # FIXME implement admin mail sending
                 self.logger.critical("SEND ADMIN MAIL AT THIS POINT")
                 raise e
 
-- 
GitLab