CC-4370: Transitioning between two webstreams a hiccup in the stream is audible
-some major refactoring before fixing this problem
This commit is contained in:
parent
6c8d602518
commit
d20c450226
6 changed files with 161 additions and 102 deletions
|
@ -336,7 +336,7 @@ class ApiController extends Zend_Controller_Action
|
||||||
$this->view->layout()->disableLayout();
|
$this->view->layout()->disableLayout();
|
||||||
$this->_helper->viewRenderer->setNoRender(true);
|
$this->_helper->viewRenderer->setNoRender(true);
|
||||||
|
|
||||||
$data = Application_Model_Schedule::getScheduledPlaylists();
|
$data = Application_Model_Schedule::getSchedule();
|
||||||
echo json_encode($data, JSON_FORCE_OBJECT);
|
echo json_encode($data, JSON_FORCE_OBJECT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ class RabbitMqPlugin extends Zend_Controller_Plugin_Abstract
|
||||||
public function dispatchLoopShutdown()
|
public function dispatchLoopShutdown()
|
||||||
{
|
{
|
||||||
if (Application_Model_RabbitMq::$doPush) {
|
if (Application_Model_RabbitMq::$doPush) {
|
||||||
$md = array('schedule' => Application_Model_Schedule::getScheduledPlaylists());
|
$md = array('schedule' => Application_Model_Schedule::getSchedule());
|
||||||
Application_Model_RabbitMq::SendMessageToPypo("update_schedule", $md);
|
Application_Model_RabbitMq::SendMessageToPypo("update_schedule", $md);
|
||||||
if (!isset($_SERVER['AIRTIME_SRV'])) {
|
if (!isset($_SERVER['AIRTIME_SRV'])) {
|
||||||
Application_Model_RabbitMq::SendMessageToShowRecorder("update_recorder_schedule");
|
Application_Model_RabbitMq::SendMessageToShowRecorder("update_recorder_schedule");
|
||||||
|
|
|
@ -570,7 +570,127 @@ SQL;
|
||||||
return $rows;
|
return $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getScheduledPlaylists($p_fromDateTime = null, $p_toDateTime = null)
|
private static function createInputHarborKickTimes(&$data, $range_start, $range_end)
|
||||||
|
{
|
||||||
|
$utcTimeZone = new DateTimeZone("UTC");
|
||||||
|
$kick_times = Application_Model_ShowInstance::GetEndTimeOfNextShowWithLiveDJ($range_start, $range_end);
|
||||||
|
foreach ($kick_times as $kick_time_info) {
|
||||||
|
$kick_time = $kick_time_info['ends'];
|
||||||
|
$temp = explode('.', Application_Model_Preference::GetDefaultTransitionFade());
|
||||||
|
// we round down transition time since PHP cannot handle millisecond. We need to
|
||||||
|
// handle this better in the future
|
||||||
|
$transition_time = intval($temp[0]);
|
||||||
|
$switchOffDataTime = new DateTime($kick_time, $utcTimeZone);
|
||||||
|
$switch_off_time = $switchOffDataTime->sub(new DateInterval('PT'.$transition_time.'S'));
|
||||||
|
$switch_off_time = $switch_off_time->format("Y-m-d H:i:s");
|
||||||
|
|
||||||
|
$kick_start = self::AirtimeTimeToPypoTime($kick_time);
|
||||||
|
$data["media"][$kick_start]['start'] = $kick_start;
|
||||||
|
$data["media"][$kick_start]['end'] = $kick_start;
|
||||||
|
$data["media"][$kick_start]['event_type'] = "kick_out";
|
||||||
|
$data["media"][$kick_start]['type'] = "event";
|
||||||
|
$data["media"][$kick_start]['independent_event'] = true;
|
||||||
|
|
||||||
|
if ($kick_time !== $switch_off_time) {
|
||||||
|
$switch_start = self::AirtimeTimeToPypoTime($switch_off_time);
|
||||||
|
$data["media"][$switch_start]['start'] = $switch_start;
|
||||||
|
$data["media"][$switch_start]['end'] = $switch_start;
|
||||||
|
$data["media"][$switch_start]['event_type'] = "switch_off";
|
||||||
|
$data["media"][$switch_start]['independent_event'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function createFileScheduleEvent(&$data, $item, $media_id)
|
||||||
|
{
|
||||||
|
$start = self::AirtimeTimeToPypoTime($item["start"]);
|
||||||
|
$end = self::AirtimeTimeToPypoTime($item["end"]);
|
||||||
|
|
||||||
|
$schedule_item = array(
|
||||||
|
'id' => $media_id,
|
||||||
|
'type' => 'file',
|
||||||
|
'row_id' => $item["id"],
|
||||||
|
'uri' => $uri,
|
||||||
|
'fade_in' => Application_Model_Schedule::WallTimeToMillisecs($item["fade_in"]),
|
||||||
|
'fade_out' => Application_Model_Schedule::WallTimeToMillisecs($item["fade_out"]),
|
||||||
|
'cue_in' => Application_Common_DateHelper::CalculateLengthInSeconds($item["cue_in"]),
|
||||||
|
'cue_out' => Application_Common_DateHelper::CalculateLengthInSeconds($item["cue_out"]),
|
||||||
|
'start' => $start,
|
||||||
|
'end' => $end,
|
||||||
|
'show_name' => $showName,
|
||||||
|
'replay_gain' => is_null($item["replay_gain"]) ? "0": $item["replay_gain"],
|
||||||
|
'independent_event' => true
|
||||||
|
);
|
||||||
|
$data["media"][$start] = $schedule_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function createStreamScheduleEvent(&$data, $item, $media_id)
|
||||||
|
{
|
||||||
|
$start = self::AirtimeTimeToPypoTime($item["start"]);
|
||||||
|
$end = self::AirtimeTimeToPypoTime($item["end"]);
|
||||||
|
|
||||||
|
//create an event to start stream buffering 5 seconds ahead of the streams actual time.
|
||||||
|
$buffer_start = new DateTime($item["start"], new DateTimeZone('UTC'));
|
||||||
|
$buffer_start->sub(new DateInterval("PT5S"));
|
||||||
|
|
||||||
|
$stream_buffer_start = self::AirtimeTimeToPypoTime($buffer_start->format("Y-m-d H:i:s"));
|
||||||
|
|
||||||
|
$schedule_item = array(
|
||||||
|
'start' => $stream_buffer_start,
|
||||||
|
'end' => $stream_buffer_start,
|
||||||
|
'uri' => $uri,
|
||||||
|
'row_id' => $item["id"],
|
||||||
|
'type' => 'stream_buffer_start',
|
||||||
|
'independent_event' => true
|
||||||
|
);
|
||||||
|
|
||||||
|
//TODO: Make sure no other media is being overwritten!
|
||||||
|
$data["media"][$stream_buffer_start] = $schedule_item;
|
||||||
|
$schedule_item = array(
|
||||||
|
'id' => $media_id,
|
||||||
|
'type' => 'stream_output_start',
|
||||||
|
'row_id' => $item["id"],
|
||||||
|
'uri' => $uri,
|
||||||
|
'start' => $start,
|
||||||
|
'end' => $end,
|
||||||
|
'show_name' => $showName,
|
||||||
|
'independent_event' => true
|
||||||
|
);
|
||||||
|
$data["media"][$start] = $schedule_item;
|
||||||
|
|
||||||
|
//since a stream never ends we have to insert an additional "kick stream" event. The "start"
|
||||||
|
//time of this event is the "end" time of the stream minus 1 second.
|
||||||
|
$dt = new DateTime($item["end"], new DateTimeZone('UTC'));
|
||||||
|
$dt->sub(new DateInterval("PT1S"));
|
||||||
|
|
||||||
|
//make sure the webstream doesn't play past the end time of the show
|
||||||
|
if ($dt->getTimestamp() > $showEndDateTime->getTimestamp()) {
|
||||||
|
$dt = $showEndDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stream_end = self::AirtimeTimeToPypoTime($dt->format("Y-m-d H:i:s"));
|
||||||
|
|
||||||
|
$schedule_item = array(
|
||||||
|
'start' => $stream_end,
|
||||||
|
'end' => $stream_end,
|
||||||
|
'uri' => $uri,
|
||||||
|
'type' => 'stream_buffer_end',
|
||||||
|
'independent_event' => true
|
||||||
|
);
|
||||||
|
$data["media"][$stream_end] = $schedule_item;
|
||||||
|
|
||||||
|
|
||||||
|
$schedule_item = array(
|
||||||
|
'start' => $stream_end,
|
||||||
|
'end' => $stream_end,
|
||||||
|
'uri' => $uri,
|
||||||
|
'type' => 'stream_output_end',
|
||||||
|
'independent_event' => true
|
||||||
|
);
|
||||||
|
$data["media"][$stream_end] = $schedule_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getRangeStartAndEnd($p_fromDateTime, $p_toDateTime)
|
||||||
{
|
{
|
||||||
global $CC_CONFIG;
|
global $CC_CONFIG;
|
||||||
|
|
||||||
|
@ -600,8 +720,13 @@ SQL;
|
||||||
$range_end = Application_Model_Schedule::PypoTimeToAirtimeTime($p_toDateTime);
|
$range_end = Application_Model_Schedule::PypoTimeToAirtimeTime($p_toDateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scheduler wants everything in a playlist
|
return array($range_start, $range_end);
|
||||||
$items = self::GetItems($range_start, $range_end);
|
}
|
||||||
|
|
||||||
|
public static function getSchedule($p_fromDateTime = null, $p_toDateTime = null)
|
||||||
|
{
|
||||||
|
|
||||||
|
list($range_start, $range_end) = self::getRangeStartAndEnd($p_fromDateTime, $p_toDateTime);
|
||||||
|
|
||||||
$data = array();
|
$data = array();
|
||||||
$utcTimeZone = new DateTimeZone("UTC");
|
$utcTimeZone = new DateTimeZone("UTC");
|
||||||
|
@ -609,33 +734,9 @@ SQL;
|
||||||
$data["status"] = array();
|
$data["status"] = array();
|
||||||
$data["media"] = array();
|
$data["media"] = array();
|
||||||
|
|
||||||
$kick_times = Application_Model_ShowInstance::GetEndTimeOfNextShowWithLiveDJ($range_start, $range_end);
|
self::createInputHarborKickTimes($data, $range_start, $range_end);
|
||||||
foreach ($kick_times as $kick_time_info) {
|
|
||||||
$kick_time = $kick_time_info['ends'];
|
|
||||||
$temp = explode('.', Application_Model_Preference::GetDefaultTransitionFade());
|
|
||||||
// we round down transition time since PHP cannot handle millisecond. We need to
|
|
||||||
// handle this better in the future
|
|
||||||
$transition_time = intval($temp[0]);
|
|
||||||
$switchOffDataTime = new DateTime($kick_time, $utcTimeZone);
|
|
||||||
$switch_off_time = $switchOffDataTime->sub(new DateInterval('PT'.$transition_time.'S'));
|
|
||||||
$switch_off_time = $switch_off_time->format("Y-m-d H:i:s");
|
|
||||||
|
|
||||||
$kick_start = Application_Model_Schedule::AirtimeTimeToPypoTime($kick_time);
|
|
||||||
$data["media"][$kick_start]['start'] = $kick_start;
|
|
||||||
$data["media"][$kick_start]['end'] = $kick_start;
|
|
||||||
$data["media"][$kick_start]['event_type'] = "kick_out";
|
|
||||||
$data["media"][$kick_start]['type'] = "event";
|
|
||||||
$data["media"][$kick_start]['independent_event'] = true;
|
|
||||||
|
|
||||||
if ($kick_time !== $switch_off_time) {
|
|
||||||
$switch_start = Application_Model_Schedule::AirtimeTimeToPypoTime($switch_off_time);
|
|
||||||
$data["media"][$switch_start]['start'] = $switch_start;
|
|
||||||
$data["media"][$switch_start]['end'] = $switch_start;
|
|
||||||
$data["media"][$switch_start]['event_type'] = "switch_off";
|
|
||||||
$data["media"][$switch_start]['independent_event'] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
$items = self::getItems($range_start, $range_end);
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
|
|
||||||
$showInstance = CcShowInstancesQuery::create()->findPK($item["instance_id"]);
|
$showInstance = CcShowInstancesQuery::create()->findPK($item["instance_id"]);
|
||||||
|
@ -648,11 +749,10 @@ SQL;
|
||||||
$trackEndDateTime = new DateTime($item["end"], $utcTimeZone);
|
$trackEndDateTime = new DateTime($item["end"], $utcTimeZone);
|
||||||
|
|
||||||
if ($trackStartDateTime->getTimestamp() > $showEndDateTime->getTimestamp()) {
|
if ($trackStartDateTime->getTimestamp() > $showEndDateTime->getTimestamp()) {
|
||||||
|
//do not send any tracks that start past their show's end time
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Note: cue_out and end are always the same. */
|
|
||||||
/* TODO: Not all tracks will have "show_end" */
|
|
||||||
if ($trackEndDateTime->getTimestamp() > $showEndDateTime->getTimestamp()) {
|
if ($trackEndDateTime->getTimestamp() > $showEndDateTime->getTimestamp()) {
|
||||||
$di = $trackStartDateTime->diff($showEndDateTime);
|
$di = $trackStartDateTime->diff($showEndDateTime);
|
||||||
|
|
||||||
|
@ -665,78 +765,37 @@ SQL;
|
||||||
$media_id = $item['file_id'];
|
$media_id = $item['file_id'];
|
||||||
$storedFile = Application_Model_StoredFile::Recall($media_id);
|
$storedFile = Application_Model_StoredFile::Recall($media_id);
|
||||||
$uri = $storedFile->getFilePath();
|
$uri = $storedFile->getFilePath();
|
||||||
$type = "file";
|
self::createFileScheduleEvent($data, $item, $media_id, $uri);
|
||||||
$independent_event = false;
|
|
||||||
} elseif (!is_null($item['stream_id'])) {
|
} elseif (!is_null($item['stream_id'])) {
|
||||||
//row is type "webstream"
|
//row is type "webstream"
|
||||||
$media_id = $item['stream_id'];
|
$media_id = $item['stream_id'];
|
||||||
$uri = $item['url'];
|
$uri = $item['url'];
|
||||||
$type = "stream";
|
self::createStreamScheduleEvent($data, $item, $media_id, $uri);
|
||||||
$independent_event = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$start = Application_Model_Schedule::AirtimeTimeToPypoTime($item["start"]);
|
|
||||||
$end = Application_Model_Schedule::AirtimeTimeToPypoTime($item["end"]);
|
|
||||||
|
|
||||||
$data["media"][$start] = array(
|
|
||||||
'id' => $media_id,
|
|
||||||
'type' => $type,
|
|
||||||
'row_id' => $item["id"],
|
|
||||||
'uri' => $uri,
|
|
||||||
'fade_in' => Application_Model_Schedule::WallTimeToMillisecs($item["fade_in"]),
|
|
||||||
'fade_out' => Application_Model_Schedule::WallTimeToMillisecs($item["fade_out"]),
|
|
||||||
'cue_in' => Application_Common_DateHelper::CalculateLengthInSeconds($item["cue_in"]),
|
|
||||||
'cue_out' => Application_Common_DateHelper::CalculateLengthInSeconds($item["cue_out"]),
|
|
||||||
'start' => $start,
|
|
||||||
'end' => $end,
|
|
||||||
'show_name' => $showName,
|
|
||||||
'replay_gain' => is_null($item["replay_gain"]) ? "0": $item["replay_gain"],
|
|
||||||
'independent_event' => $independent_event
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($type == "stream") {
|
|
||||||
//create an event to start stream buffering 5 seconds ahead of the streams actual time.
|
|
||||||
$buffer_start = new DateTime($item["start"], new DateTimeZone('UTC'));
|
|
||||||
$buffer_start->sub(new DateInterval("PT5S"));
|
|
||||||
|
|
||||||
$stream_buffer_start = Application_Model_Schedule::AirtimeTimeToPypoTime($buffer_start->format("Y-m-d H:i:s"));
|
|
||||||
|
|
||||||
//TODO: Make sure no other media is being overwritten!
|
|
||||||
$data["media"][$stream_buffer_start] = array(
|
|
||||||
'start' => $stream_buffer_start,
|
|
||||||
'end' => $stream_buffer_start,
|
|
||||||
'uri' => $uri,
|
|
||||||
'row_id' => $item["id"],
|
|
||||||
'type' => 'stream_buffer_start',
|
|
||||||
'independent_event' => true
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
//since a stream never ends we have to insert an additional "kick stream" event. The "start"
|
|
||||||
//time of this event is the "end" time of the stream minus 1 second.
|
|
||||||
$dt = new DateTime($item["end"], new DateTimeZone('UTC'));
|
|
||||||
$dt->sub(new DateInterval("PT1S"));
|
|
||||||
|
|
||||||
//make sure the webstream doesn't play past the end time of the show
|
|
||||||
if ($dt->getTimestamp() > $showEndDateTime->getTimestamp()) {
|
|
||||||
$dt = $showEndDateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
$stream_end = Application_Model_Schedule::AirtimeTimeToPypoTime($dt->format("Y-m-d H:i:s"));
|
|
||||||
|
|
||||||
$data["media"][$stream_end] = array(
|
|
||||||
'start' => $stream_end,
|
|
||||||
'end' => $stream_end,
|
|
||||||
'uri' => $uri,
|
|
||||||
'type' => 'stream_end',
|
|
||||||
'independent_event' => true
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
private static function collapseEvents($data)
|
||||||
|
{
|
||||||
|
$keys = array_keys($data);
|
||||||
|
|
||||||
|
for ($i = 0, $len = count($keys); $i < $len; $i++) {
|
||||||
|
$cur = $data[$keys[$i]];
|
||||||
|
$next = null;
|
||||||
|
if ($i+1 < $len) {
|
||||||
|
$next = $data[$keys[$i+1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cur['type'] == 'stream_buffer_end' && !is_null($next) && $next['type'] == 'stream_buffer_start') {
|
||||||
|
unset($data[$keys[$i]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
public static function deleteAll()
|
public static function deleteAll()
|
||||||
{
|
{
|
||||||
global $CC_CONFIG;
|
global $CC_CONFIG;
|
||||||
|
|
|
@ -99,7 +99,7 @@ while ($showTime < $endDate) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Application_Model_RabbitMq::$doPush) {
|
if (Application_Model_RabbitMq::$doPush) {
|
||||||
$md = array('schedule' => Application_Model_Schedule::getScheduledPlaylists());
|
$md = array('schedule' => Application_Model_Schedule::getSchedule());
|
||||||
Application_Model_RabbitMq::SendMessageToPypo("update_schedule", $md);
|
Application_Model_RabbitMq::SendMessageToPypo("update_schedule", $md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ except Exception, e:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
def is_stream(media_item):
|
def is_stream(media_item):
|
||||||
return media_item['type'] == 'stream'
|
return media_item['type'] == 'stream_output_start'
|
||||||
|
|
||||||
def is_file(media_item):
|
def is_file(media_item):
|
||||||
return media_item['type'] == 'file'
|
return media_item['type'] == 'file'
|
||||||
|
@ -211,7 +211,7 @@ class PypoPush(Thread):
|
||||||
queue.
|
queue.
|
||||||
"""
|
"""
|
||||||
file_chain = filter(lambda item: (item["type"] == "file"), current_event_chain)
|
file_chain = filter(lambda item: (item["type"] == "file"), current_event_chain)
|
||||||
stream_chain = filter(lambda item: (item["type"] == "stream"), current_event_chain)
|
stream_chain = filter(lambda item: (item["type"] == "stream_output_start"), current_event_chain)
|
||||||
|
|
||||||
self.logger.debug(self.current_stream_info)
|
self.logger.debug(self.current_stream_info)
|
||||||
self.logger.debug(current_event_chain)
|
self.logger.debug(current_event_chain)
|
||||||
|
@ -427,13 +427,13 @@ class PypoPush(Thread):
|
||||||
PypoFetch.switch_source(self.logger, self.telnet_lock, "live_dj", "off")
|
PypoFetch.switch_source(self.logger, self.telnet_lock, "live_dj", "off")
|
||||||
elif media_item['type'] == 'stream_buffer_start':
|
elif media_item['type'] == 'stream_buffer_start':
|
||||||
self.start_web_stream_buffer(media_item)
|
self.start_web_stream_buffer(media_item)
|
||||||
elif media_item['type'] == "stream":
|
elif media_item['type'] == "stream_output_start":
|
||||||
if media_item['row_id'] != self.current_prebuffering_stream_id:
|
if media_item['row_id'] != self.current_prebuffering_stream_id:
|
||||||
#this is called if the stream wasn't scheduled sufficiently ahead of time
|
#this is called if the stream wasn't scheduled sufficiently ahead of time
|
||||||
#so that the prebuffering stage could take effect. Let's do the prebuffering now.
|
#so that the prebuffering stage could take effect. Let's do the prebuffering now.
|
||||||
self.start_web_stream_buffer(media_item)
|
self.start_web_stream_buffer(media_item)
|
||||||
self.start_web_stream(media_item)
|
self.start_web_stream(media_item)
|
||||||
elif media_item['type'] == "stream_end":
|
elif media_item['type'] == "stream_buffer_end":
|
||||||
self.stop_web_stream(media_item)
|
self.stop_web_stream(media_item)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.logger.error('Pypo Push Exception: %s', e)
|
self.logger.error('Pypo Push Exception: %s', e)
|
||||||
|
|
|
@ -78,7 +78,7 @@ echo "Removing everything from the scheduler between $startTime and $endTime..."
|
||||||
$scheduleClear = Schedule::isScheduleEmptyInRange($startTime, "01:00:00");
|
$scheduleClear = Schedule::isScheduleEmptyInRange($startTime, "01:00:00");
|
||||||
if (!$scheduleClear) {
|
if (!$scheduleClear) {
|
||||||
echo "\nERROR: Schedule could not be cleared.\n\n";
|
echo "\nERROR: Schedule could not be cleared.\n\n";
|
||||||
var_dump(Schedule::GetItems($startTime, $endTime));
|
var_dump(Schedule::getItems($startTime, $endTime));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
echo "done.\n";
|
echo "done.\n";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue