diff --git a/airtime_mvc/application/models/Schedule.php b/airtime_mvc/application/models/Schedule.php index 718ab5895..e65774c18 100644 --- a/airtime_mvc/application/models/Schedule.php +++ b/airtime_mvc/application/models/Schedule.php @@ -676,102 +676,6 @@ class Application_Model_Schedule { return $data; } - /** - * Export the schedule in json formatted for pypo (the liquidsoap scheduler) - * - * @param string $p_fromDateTime - * In the format "YYYY-MM-DD-HH-mm-SS" - * @param string $p_toDateTime - * In the format "YYYY-MM-DD-HH-mm-SS" - */ - public static function GetScheduledPlaylistsOld($p_fromDateTime = null, $p_toDateTime = null) - { - global $CC_CONFIG, $CC_DBC; - - if (is_null($p_fromDateTime)) { - $t1 = new DateTime("@".time()); - $range_start = $t1->format("Y-m-d H:i:s"); - } else { - $range_start = Application_Model_Schedule::PypoTimeToAirtimeTime($p_fromDateTime); - } - if (is_null($p_fromDateTime)) { - $t2 = new DateTime("@".time()); - $t2->add(new DateInterval("PT24H")); - $range_end = $t2->format("Y-m-d H:i:s"); - } else { - $range_end = Application_Model_Schedule::PypoTimeToAirtimeTime($p_toDateTime); - } - - // Scheduler wants everything in a playlist - $data = Application_Model_Schedule::GetItems($range_start, $range_end, true); - $playlists = array(); - - if (is_array($data)){ - foreach ($data as $dx){ - $start = $dx['start']; - - //chop off subseconds - $start = substr($start, 0, 19); - - //Start time is the array key, needs to be in the format "YYYY-MM-DD-HH-mm-ss" - $pkey = Application_Model_Schedule::AirtimeTimeToPypoTime($start); - $timestamp = strtotime($start); - $playlists[$pkey]['source'] = "PLAYLIST"; - $playlists[$pkey]['x_ident'] = $dx['group_id']; - $playlists[$pkey]['timestamp'] = $timestamp; - $playlists[$pkey]['duration'] = $dx['clip_length']; - $playlists[$pkey]['played'] = '0'; - $playlists[$pkey]['schedule_id'] = $dx['group_id']; - $playlists[$pkey]['show_name'] = $dx['show_name']; - $playlists[$pkey]['show_start'] = Application_Model_Schedule::AirtimeTimeToPypoTime($dx['show_start']); - $playlists[$pkey]['show_end'] = Application_Model_Schedule::AirtimeTimeToPypoTime($dx['show_end']); - $playlists[$pkey]['user_id'] = 0; - $playlists[$pkey]['id'] = $dx['group_id']; - $playlists[$pkey]['start'] = Application_Model_Schedule::AirtimeTimeToPypoTime($dx["start"]); - $playlists[$pkey]['end'] = Application_Model_Schedule::AirtimeTimeToPypoTime($dx["end"]); - } - } - ksort($playlists); - - foreach ($playlists as &$playlist) - { - $scheduleGroup = new Application_Model_ScheduleGroup($playlist["schedule_id"]); - $items = $scheduleGroup->getItems(); - $medias = array(); - foreach ($items as $item) - { - $storedFile = Application_Model_StoredFile::Recall($item["file_id"]); - $uri = $storedFile->getFileUrlUsingConfigAddress(); - - $starts = Application_Model_Schedule::AirtimeTimeToPypoTime($item["starts"]); - $medias[$starts] = array( - 'id' => $storedFile->getGunid(), - 'uri' => $uri, - 'fade_in' => Application_Model_Schedule::WallTimeToMillisecs($item["fade_in"]), - 'fade_out' => Application_Model_Schedule::WallTimeToMillisecs($item["fade_out"]), - 'fade_cross' => 0, - 'cue_in' => Application_Model_DateHelper::CalculateLengthInSeconds($item["cue_in"]), - 'cue_out' => Application_Model_DateHelper::CalculateLengthInSeconds($item["cue_out"]), - 'start' => $starts, - 'end' => Application_Model_Schedule::AirtimeTimeToPypoTime($item["ends"]) - ); - } - ksort($medias); - $playlist['medias'] = $medias; - } - - $result = array(); - $result['status'] = array('range' => array('start' => $range_start, 'end' => $range_end), - 'version' => AIRTIME_REST_VERSION); - $result['playlists'] = $playlists; - $result['check'] = 1; - $result['stream_metadata'] = array(); - $result['stream_metadata']['format'] = Application_Model_Preference::GetStreamLabelFormat(); - $result['stream_metadata']['station_name'] = Application_Model_Preference::GetStationName(); - - return $result; - } - public static function deleteAll() { global $CC_CONFIG, $CC_DBC; diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index 7a6b0bd92..c6c9c23a1 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -65,21 +65,31 @@ class PypoPush(Thread): media_schedule = self.queue.get(block=True) else: media_schedule = self.queue.get(block=True, timeout=time_until_next_play) - + #We get to the following lines only if a schedule was received. liquidsoap_queue_approx = self.get_queue_items_from_liquidsoap() - self.handle_new_media_schedule(media_schedule, liquidsoap_queue_approx) - next_media_item_chain = self.get_next_schedule_chain(media_schedule) - self.logger.debug("Next schedule chain: %s", next_media_item_chain) + - if next_media_item_chain is not None: - tnow = datetime.utcnow() - chain_start = datetime.strptime(next_media_item_chain[0]['start'], "%Y-%m-%d-%H-%M-%S") - time_until_next_play = self.date_interval_to_seconds(chain_start - tnow) - self.logger.debug("Blocking %s seconds until show start", time_until_next_play) + chains = self.get_all_chains(media_schedule) + current_chain = self.get_current_chain(chains) + if len(current_chain) > 0 and len(liquidsoap_queue_approx) == 0: + #Something is scheduled but Liquidsoap is not playing anything! + #Need to schedule it immediately + modify_cue_point_of_first_link(current_chain) + next_media_item_chain = current_chain + time_until_next_play = 0 else: - self.logger.debug("Blocking indefinitely since no show scheduled next") - time_until_next_play = None + self.handle_new_media_schedule(media_schedule, liquidsoap_queue_approx) + next_media_item_chain = self.get_next_schedule_chain(media_schedule) + self.logger.debug("Next schedule chain: %s", next_media_item_chain) + if next_media_item_chain is not None: + tnow = datetime.utcnow() + chain_start = datetime.strptime(next_media_item_chain[0]['start'], "%Y-%m-%d-%H-%M-%S") + time_until_next_play = self.date_interval_to_seconds(chain_start - tnow) + self.logger.debug("Blocking %s seconds until show start", time_until_next_play) + else: + self.logger.debug("Blocking indefinitely since no show scheduled") + time_until_next_play = None except Empty, e: #We only get here when a new chain of tracks are ready to be played. self.push_to_liquidsoap(next_media_item_chain) @@ -87,7 +97,8 @@ class PypoPush(Thread): #TODO time.sleep(2) - next_media_item_chain = self.get_next_schedule_chain(media_schedule) + chains = self.get_all_chains(media_schedule) + next_media_item_chain = self.get_next_schedule_chain(chains) if next_media_item_chain is not None: tnow = datetime.utcnow() chain_start = datetime.strptime(next_media_item_chain[0]['start'], "%Y-%m-%d-%H-%M-%S") @@ -142,7 +153,7 @@ class PypoPush(Thread): return liquidsoap_queue_approx - def handle_new_media_schedule(self, media, liquidsoap_queue_approx): + def handle_new_media_schedule(self, media_schedule, liquidsoap_queue_approx): """ This function's purpose is to gracefully handle situations where Liquidsoap already has a track in its queue, but the schedule @@ -156,8 +167,9 @@ class PypoPush(Thread): iteration = 0 problem_at_iteration = None for queue_item in liquidsoap_queue_approx: - if queue_item['start'] in media.keys(): - if queue_item['id'] == media['start']['id']: + + if queue_item['start'] in media_schedule.keys(): + if queue_item['id'] == media_schedule[queue_item['start']]['id']: #Everything OK for this iteration. pass else: @@ -178,21 +190,18 @@ class PypoPush(Thread): #The first item in the Liquidsoap queue (the one that is currently playing) #has changed or been removed from the schedule. We need to clear the entire #queue, and push the new schedule + self.logger.debug("Problem at iteration %s", problem_at_iteration) self.remove_from_liquidsoap_queue(problem_at_iteration, liquidsoap_queue_approx) - - """ - The purpose of this function is to take a look at the last received schedule from - pypo-fetch and return the next chain of media_items. A chain is defined as a sequence - of media_items where the end time of media_item 'n' is the start time of media_item - 'n+1' - """ - def get_next_schedule_chain(self, media_schedule): + def get_all_chains(self, media_schedule): chains = [] current_chain = [] - for mkey in media_schedule: + + sorted_keys = sorted(media_schedule.keys()) + + for mkey in sorted_keys: media_item = media_schedule[mkey] if len(current_chain) == 0: current_chain.append(media_item) @@ -206,10 +215,49 @@ class PypoPush(Thread): if len(current_chain) > 0: chains.append(current_chain) - - self.logger.debug('media_schedule %s', media_schedule) - self.logger.debug("chains %s", chains) + + return chains + + def modify_cue_point_of_first_link(self, chain): + tnow = datetime.utcnow() + link = chain[0] + link_start = datetime.strptime(link['start'], "%Y-%m-%d-%H-%M-%S") + + diff = tnow - link_start + + self.logger.debug("media item was supposed to start %s ago. Preparing to start..", diff) + link['cue_in'] = link['cue_in'] + diff + + + + + def get_current_chain(self, chains): + tnow = datetime.utcnow() + current_chain = [] + + + for chain in chains: + iteration = 0 + for link in chain: + link_start = datetime.strptime(link['start'], "%Y-%m-%d-%H-%M-%S") + link_end = datetime.strptime(link['end'], "%Y-%m-%d-%H-%M-%S") + + self.logger.debug("tnow %s, chain_start %s", tnow, link_start) + if link_start <= tnow and tnow < link_end: + current_chain = chain[iteration:] + break + iteration += 1 + + return current_chain + + """ + The purpose of this function is to take a look at the last received schedule from + pypo-fetch and return the next chain of media_items. A chain is defined as a sequence + of media_items where the end time of media_item 'n' is the start time of media_item + 'n+1' + """ + def get_next_schedule_chain(self, chains): #all media_items are now divided into chains. Let's find the one that #starts closest in the future.