diff --git a/airtime_mvc/application/controllers/ApiController.php b/airtime_mvc/application/controllers/ApiController.php index 128782ec0..d32f2ad30 100644 --- a/airtime_mvc/application/controllers/ApiController.php +++ b/airtime_mvc/application/controllers/ApiController.php @@ -28,6 +28,9 @@ class ApiController extends Zend_Controller_Action ->addActionContext('update-file-system-mount', 'json') ->addActionContext('handle-watched-dir-missing', 'json') ->addActionContext('rabbitmq-do-push', 'json') + ->addActionContext('check-live-stream-auth', 'json') + ->addActionContext('update-source-status', 'json') + ->addActionContext('get-switch-status', 'json') ->initContext(); } @@ -804,8 +807,9 @@ class ApiController extends Zend_Controller_Action print 'You are not allowed to access this resource.'; exit; } - - $this->view->msg = Application_Model_StreamSetting::getStreamSetting(); + + $info = Application_Model_StreamSetting::getStreamSetting(); + $this->view->msg = $info; } public function statusAction() { @@ -860,6 +864,22 @@ class ApiController extends Zend_Controller_Action Application_Model_StreamSetting::setLiquidsoapError($stream_id, $msg, $boot_time); } + + public function updateSourceStatusAction(){ + $request = $this->getRequest(); + + $msg = $request->getParam('msg'); + $sourcename = $request->getParam('sourcename'); + $status = $request->getParam('status'); + + // on source disconnection sent msg to pypo to turn off the switch + if($status == "false"){ + $data = array("sourcename"=>$sourcename, "status"=>"off"); + Application_Model_RabbitMq::SendMessageToPypo("switch_source", $data); + Application_Model_Preference::SetSourceSwitchStatus($sourcename, "off"); + } + Application_Model_Preference::SetSourceStatus($sourcename, $status); + } // handles addition/deletion of mount point which watched dirs reside public function updateFileSystemMountAction(){ @@ -958,7 +978,6 @@ class ApiController extends Zend_Controller_Action Application_Model_MusicDir::removeWatchedDir($dir, false); } - /* This action is for use by our dev scripts, that make * a change to the database and we want rabbitmq to send * out a message to pypo that a potential change has been made. */ @@ -973,10 +992,87 @@ class ApiController extends Zend_Controller_Action print 'You are not allowed to access this resource.'; exit; } - Logging::log("Notifying RabbitMQ to send message to pypo"); Application_Model_RabbitMq::PushSchedule(); } + + public function getSwitchStatusAction(){ + $live_dj = Application_Model_Preference::GetSourceSwitchStatus('live_dj'); + $master_dj = Application_Model_Preference::GetSourceSwitchStatus('master_dj'); + $scheduled_play = Application_Model_Preference::GetSourceSwitchStatus('scheduled_play'); + + $res = array("live_dj"=>$live_dj, "master_dj"=>$master_dj, "scheduled_play"=>$scheduled_play); + $this->view->status = $res; + } + + /* This is used but Liquidsoap to check authentication of live streams*/ + public function checkLiveStreamAuthAction(){ + global $CC_CONFIG; + + $request = $this->getRequest(); + $api_key = $request->getParam('api_key'); + + $username = $request->getParam('username'); + $password = $request->getParam('password'); + $djtype = $request->getParam('djtype'); + + if (!in_array($api_key, $CC_CONFIG["apiKey"])) + { + header('HTTP/1.0 401 Unauthorized'); + print 'You are not allowed to access this resource.'; + exit; + } + + if($djtype == 'master'){ + //check against master + if($username == Application_Model_Preference::GetLiveSteamMasterUsername() && $password == Application_Model_Preference::GetLiveSteamMasterPassword()){ + $this->view->msg = true; + }else{ + $this->view->msg = false; + } + }elseif($djtype == "dj"){ + //check against show dj auth + $showInfo = Application_Model_Show::GetCurrentShow(); + // there is current playing show + if(isset($showInfo[0]['id'])){ + $current_show_id = $showInfo[0]['id']; + $CcShow = CcShowQuery::create()->findPK($current_show_id); + + // get custom pass info from the show + $custom_user = $CcShow->getDbLiveStreamUser(); + $custom_pass = $CcShow->getDbLiveStreamPass(); + + // get hosts ids + $show = new Application_Model_Show($current_show_id); + $hosts_ids = $show->getHostsIds(); + + // check against hosts auth + if($CcShow->getDbLiveStreamUsingAirtimeAuth()){ + foreach( $hosts_ids as $host){ + $h = new Application_Model_User($host['subjs_id']); + if($username == $h->getLogin() && md5($password) == $h->getPassword()){ + $this->view->msg = true; + return; + } + } + } + // check against custom auth + if($CcShow->getDbLiveStreamUsingCustomAuth()){ + if($username == $custom_user && $password == $custom_pass){ + $this->view->msg = true; + }else{ + $this->view->msg = false; + } + } + else{ + $this->view->msg = false; + } + }else{ + // no show is currently playing + $this->view->msg = false; + } + } + } } diff --git a/airtime_mvc/application/controllers/DashboardController.php b/airtime_mvc/application/controllers/DashboardController.php index 284e810b6..9a30348a2 100644 --- a/airtime_mvc/application/controllers/DashboardController.php +++ b/airtime_mvc/application/controllers/DashboardController.php @@ -5,14 +5,86 @@ class DashboardController extends Zend_Controller_Action public function init() { - + $ajaxContext = $this->_helper->getHelper('AjaxContext'); + $ajaxContext->addActionContext('switch-source', 'json') + ->addActionContext('disconnect-source', 'json') + ->initContext(); } public function indexAction() { // action body } - + + public function disconnectSourceAction(){ + $request = $this->getRequest(); + $sourcename = $request->getParam('sourcename'); + + $userInfo = Zend_Auth::getInstance()->getStorage()->read(); + $user = new Application_Model_User($userInfo->id); + + $show = Application_Model_Show::GetCurrentShow(); + + $show_id = isset($show['id'])?$show['id']:0; + + $source_connected = Application_Model_Preference::GetSourceStatus($sourcename); + + if($user->canSchedule($show_id) && $source_connected){ + $data = array("sourcename"=>$sourcename); + Application_Model_RabbitMq::SendMessageToPypo("disconnect_source", $data); + }else{ + if($source_connected){ + $this->view->error = "You don't have permission to disconnect source."; + }else{ + $this->view->error = "There is no source connected to this input."; + } + } + } + + public function switchSourceAction(){ + $request = $this->getRequest(); + $sourcename = $this->_getParam('sourcename'); + $current_status = $this->_getParam('status'); + + $userInfo = Zend_Auth::getInstance()->getStorage()->read(); + $user = new Application_Model_User($userInfo->id); + + $show = Application_Model_Show::GetCurrentShow(); + $show_id = isset($show['id'])?$show['id']:0; + + $source_connected = Application_Model_Preference::GetSourceStatus($sourcename); + + if($user->canSchedule($show_id) && ($source_connected || $sourcename == 'scheduled_play')){ + + $change_status_to = "on"; + + if(strtolower($current_status) == "on"){ + $change_status_to = "off"; + } + + $data = array("sourcename"=>$sourcename, "status"=>$change_status_to); + Application_Model_RabbitMq::SendMessageToPypo("switch_source", $data); + if(strtolower($current_status) == "on"){ + Application_Model_Preference::SetSourceSwitchStatus($sourcename, "off"); + $this->view->status = "OFF"; + }else{ + Application_Model_Preference::SetSourceSwitchStatus($sourcename, "on"); + $this->view->status = "ON"; + } + } + else{ + if($source_connected){ + $this->view->error = "You don't have permission to switch source."; + }else{ + $this->view->error = "There is no source connected to this input."; + } + } + } + + public function switchOffSource(){ + + } + public function streamPlayerAction() { global $CC_CONFIG; diff --git a/airtime_mvc/application/controllers/IndexController.php b/airtime_mvc/application/controllers/IndexController.php index 7580891db..c1129ae36 100644 --- a/airtime_mvc/application/controllers/IndexController.php +++ b/airtime_mvc/application/controllers/IndexController.php @@ -10,7 +10,7 @@ class IndexController extends Zend_Controller_Action public function indexAction() { - $this->_forward('index', 'login'); + $this->_forward('index', 'showbuilder'); } public function mainAction() diff --git a/airtime_mvc/application/controllers/LibraryController.php b/airtime_mvc/application/controllers/LibraryController.php index aa6b15bb6..80f90fc90 100644 --- a/airtime_mvc/application/controllers/LibraryController.php +++ b/airtime_mvc/application/controllers/LibraryController.php @@ -73,8 +73,6 @@ class LibraryController extends Zend_Controller_Action public function contextMenuAction() { - global $CC_CONFIG; - $id = $this->_getParam('id'); $type = $this->_getParam('type'); //playlist||timeline diff --git a/airtime_mvc/application/controllers/PreferenceController.php b/airtime_mvc/application/controllers/PreferenceController.php index 6c59c4eca..d13cd678d 100644 --- a/airtime_mvc/application/controllers/PreferenceController.php +++ b/airtime_mvc/application/controllers/PreferenceController.php @@ -14,6 +14,7 @@ class PreferenceController extends Zend_Controller_Action ->addActionContext('is-import-in-progress', 'json') ->addActionContext('change-stream-setting', 'json') ->addActionContext('get-liquidsoap-status', 'json') + ->addActionContext('set-source-connection-url', 'json') ->initContext(); } @@ -47,7 +48,6 @@ class PreferenceController extends Zend_Controller_Action Application_Model_Preference::SetSoundCloudTags($values["preferences_soundcloud"]["SoundCloudTags"]); Application_Model_Preference::SetSoundCloudGenre($values["preferences_soundcloud"]["SoundCloudGenre"]); Application_Model_Preference::SetSoundCloudTrackType($values["preferences_soundcloud"]["SoundCloudTrackType"]); - Application_Model_Preference::SetSoundCloudLicense($values["preferences_soundcloud"]["SoundCloudLicense"]); $this->view->statusMsg = "
Preferences updated.
"; @@ -176,6 +176,9 @@ class PreferenceController extends Zend_Controller_Action $form->setSetting($setting); $form->startFrom(); + + $live_stream_subform = new Application_Form_LiveStreamingPreferences(); + $form->addSubForm($live_stream_subform, "live_stream_subform"); for($i=1; $i<=$num_of_stream; $i++){ $subform = new Application_Form_StreamSettingSubForm(); @@ -190,17 +193,8 @@ class PreferenceController extends Zend_Controller_Action $post_data = $request->getPost(); $error = false; - $values = array(); - for($i=1; $i<=$num_of_stream; $i++){ - if(!$form->getSubForm("s".$i."_subform")->isValid($post_data["s".$i."_data"])){ - $error = true; - }else{ - // getValues returne array of size 1, so reorganized it - foreach($form->getSubForm("s".$i."_subform")->getValues() as $key => $d){ - $values[$key] = $d; - } - } - } + $values = $post_data; + if($form->isValid($post_data)){ if(Application_Model_Preference::GetPlanLevel() == 'disabled'){ $values['output_sound_device'] = $form->getValue('output_sound_device'); @@ -211,18 +205,29 @@ class PreferenceController extends Zend_Controller_Action $values['output_sound_device_type'] = $form->getValue('output_sound_device_type'); $values['streamFormat'] = $form->getValue('streamFormat'); - } - if(!$error){ Application_Model_StreamSetting::setStreamSetting($values); + + // this goes into cc_pref table + Application_Model_Preference::SetStreamLabelFormat($values['streamFormat']); + Application_Model_Preference::SetLiveSteamMasterUsername($values["master_username"]); + Application_Model_Preference::SetLiveSteamMasterPassword($values["master_password"]); + + // extra info that goes into cc_stream_setting + Application_Model_StreamSetting::SetMasterLiveSteamPort($values["master_harbor_input_port"]); + Application_Model_StreamSetting::SetMasterLiveSteamMountPoint($values["master_harbor_input_mount_point"]); + Application_Model_StreamSetting::SetDJLiveSteamPort($values["dj_harbor_input_port"]); + Application_Model_StreamSetting::SetDJLiveSteamMountPoint($values["dj_harbor_input_mount_point"]); + + // store stream update timestamp + Application_Model_Preference::SetStreamUpdateTimestamp(); + $data = array(); - $data['setting'] = Application_Model_StreamSetting::getStreamSetting(); + $info = Application_Model_StreamSetting::getStreamSetting(); + $data['setting'] = $info; for($i=1;$i<=$num_of_stream;$i++){ Application_Model_StreamSetting::setLiquidsoapError($i, "waiting"); } - // this goes into cc_pref table - Application_Model_Preference::SetStreamLabelFormat($values['streamFormat']); - // store stream update timestamp - Application_Model_Preference::SetStreamUpdateTimestamp(); + Application_Model_RabbitMq::SendMessageToPypo("update_stream_setting", $data); $this->view->statusMsg = "
Stream Setting Updated.
"; } @@ -331,6 +336,19 @@ class PreferenceController extends Zend_Controller_Action } die(json_encode($out)); } + + public function setSourceConnectionUrlAction(){ + $request = $this->getRequest(); + $type = $request->getParam("type", null); + $url = urldecode($request->getParam("url", null)); + + if($type == 'masterdj'){ + Application_Model_Preference::SetMasterDJSourceConnectionURL($url); + }elseif($type == 'livedj'){ + Application_Model_Preference::SetLiveDJSourceConnectionURL($url); + } + die(); + } } diff --git a/airtime_mvc/application/controllers/ScheduleController.php b/airtime_mvc/application/controllers/ScheduleController.php index a97471759..57c285835 100644 --- a/airtime_mvc/application/controllers/ScheduleController.php +++ b/airtime_mvc/application/controllers/ScheduleController.php @@ -321,8 +321,28 @@ class ScheduleController extends Zend_Controller_Action Application_Model_Show::ConvertToLocalTimeZone($range["currentShow"], array("starts", "ends", "start_timestamp", "end_timestamp")); Application_Model_Show::ConvertToLocalTimeZone($range["nextShow"], array("starts", "ends", "start_timestamp", "end_timestamp")); - + + $source_status = array(); + $switch_status = array(); + $live_dj = Application_Model_Preference::GetSourceStatus("live_dj"); + $master_dj = Application_Model_Preference::GetSourceStatus("master_dj"); + + $scheduled_play_switch = Application_Model_Preference::GetSourceSwitchStatus("scheduled_play"); + $live_dj_switch = Application_Model_Preference::GetSourceSwitchStatus("live_dj"); + $master_dj_switch = Application_Model_Preference::GetSourceSwitchStatus("master_dj"); + + //might not be the correct place to implement this but for now let's just do it here + $source_status['live_dj_source'] = $live_dj; + $source_status['master_dj_source'] = $master_dj; + $this->view->source_status = $source_status; + + $switch_status['live_dj_source'] = $live_dj_switch; + $switch_status['master_dj_source'] = $master_dj_switch; + $switch_status['scheduled_play'] = $scheduled_play_switch; + $this->view->switch_status = $switch_status; + $this->view->entries = $range; + } public function removeGroupAction() @@ -411,6 +431,7 @@ class ScheduleController extends Zend_Controller_Action $formWhen = new Application_Form_AddShowWhen(); $formRepeats = new Application_Form_AddShowRepeats(); $formStyle = new Application_Form_AddShowStyle(); + $formLive = new Application_Form_AddShowLiveStream(); $formWhat->removeDecorator('DtDdWrapper'); $formWho->removeDecorator('DtDdWrapper'); @@ -423,6 +444,7 @@ class ScheduleController extends Zend_Controller_Action $this->view->repeats = $formRepeats; $this->view->who = $formWho; $this->view->style = $formStyle; + $this->view->live = $formLive; $this->view->addNewShow = false; $show = new Application_Model_Show($showInstance->getShowId()); @@ -476,6 +498,8 @@ class ScheduleController extends Zend_Controller_Action $formWho->populate(array('add_show_hosts' => $hosts)); $formStyle->populate(array('add_show_background_color' => $show->getBackgroundColor(), 'add_show_color' => $show->getColor())); + + $formLive->populate($show->getLiveStreamInfo()); if(!$isSaas){ $formRecord = new Application_Form_AddShowRR(); @@ -564,12 +588,14 @@ class ScheduleController extends Zend_Controller_Action $formWhen = new Application_Form_AddShowWhen(); $formRepeats = new Application_Form_AddShowRepeats(); $formStyle = new Application_Form_AddShowStyle(); + $formLive = new Application_Form_AddShowLiveStream(); $formWhat->removeDecorator('DtDdWrapper'); $formWho->removeDecorator('DtDdWrapper'); $formWhen->removeDecorator('DtDdWrapper'); $formRepeats->removeDecorator('DtDdWrapper'); $formStyle->removeDecorator('DtDdWrapper'); + $formLive->removeDecorator('DtDdWrapper'); $what = $formWhat->isValid($data); $when = $formWhen->isValid($data); @@ -686,6 +712,7 @@ class ScheduleController extends Zend_Controller_Action $this->view->rr = $formRecord; $this->view->absoluteRebroadcast = $formAbsoluteRebroadcast; $this->view->rebroadcast = $formRebroadcast; + $this->view->live = $formLive; $this->view->addNewShow = true; //the form still needs to be "update" since @@ -719,6 +746,8 @@ class ScheduleController extends Zend_Controller_Action $this->view->repeats = $formRepeats; $this->view->who = $formWho; $this->view->style = $formStyle; + $this->view->live = $formLive; + if(!$isSaas){ $this->view->rr = $formRecord; $this->view->absoluteRebroadcast = $formAbsoluteRebroadcast; diff --git a/airtime_mvc/application/controllers/ShowbuilderController.php b/airtime_mvc/application/controllers/ShowbuilderController.php index 59df1f870..10648cd6b 100644 --- a/airtime_mvc/application/controllers/ShowbuilderController.php +++ b/airtime_mvc/application/controllers/ShowbuilderController.php @@ -12,6 +12,7 @@ class ShowbuilderController extends Zend_Controller_Action ->addActionContext('builder-dialog', 'json') ->addActionContext('check-builder-feed', 'json') ->addActionContext('builder-feed', 'json') + ->addActionContext('context-menu', 'json') ->initContext(); } @@ -94,6 +95,28 @@ class ShowbuilderController extends Zend_Controller_Action $this->_helper->actionStack('library', 'library'); $this->_helper->actionStack('builder', 'showbuilder'); } + + public function contextMenuAction() + { + $id = $this->_getParam('id'); + $now = time(); + + $request = $this->getRequest(); + $baseUrl = $request->getBaseUrl(); + $menu = array(); + + $userInfo = Zend_Auth::getInstance()->getStorage()->read(); + $user = new Application_Model_User($userInfo->id); + + $item = CcScheduleQuery::create()->findPK($id); + $instance = $item->getCcShowInstances(); + + if ($now < intval($item->getDbStarts("U")) && $user->canSchedule($instance->getDbShowId())) { + $menu["del"] = array("name"=> "Delete", "icon" => "delete", "url" => "/showbuilder/schedule-remove"); + } + + $this->view->items = $menu; + } public function builderAction() { diff --git a/airtime_mvc/application/forms/AddShowLiveStream.php b/airtime_mvc/application/forms/AddShowLiveStream.php new file mode 100644 index 000000000..44e6e4087 --- /dev/null +++ b/airtime_mvc/application/forms/AddShowLiveStream.php @@ -0,0 +1,54 @@ +setLabel("Connect using Airtime username & password") + ->setDescription($description1) + ->setRequired(false) + ->setDecorators(array('ViewHelper')); + $this->addElement($cb_airtime_auth); + + $description2 = "Specifiy custom athentification which will work for only the show."; + $cb_custom_auth = new Zend_Form_Element_Checkbox("cb_custom_auth"); + $cb_custom_auth ->setLabel("Custom") + ->setDescription($description2) + ->setRequired(false) + ->setDecorators(array('ViewHelper')); + $this->addElement($cb_custom_auth); + + //custom username + $custom_username = new Zend_Form_Element_Text('custom_username'); + $custom_username->setAttrib('class', 'input_text') + ->setAttrib('autocomplete', 'off') + ->setAllowEmpty(true) + ->setLabel('Custom Username') + ->setFilters(array('StringTrim')) + ->setValidators(array( + new ConditionalNotEmpty(array("cb_custom_auth"=>"1")))) + ->setDecorators(array('ViewHelper')); + $this->addElement($custom_username); + + //custom password + $custom_password = new Zend_Form_Element_Password('custom_password'); + $custom_password->setAttrib('class', 'input_text') + ->setAttrib('autocomplete', 'off') + ->setAttrib('renderPassword','true') + ->setAllowEmpty(true) + ->setLabel('Custom Password') + ->setFilters(array('StringTrim')) + ->setValidators(array( + new ConditionalNotEmpty(array("cb_custom_auth"=>"1")))) + ->setDecorators(array('ViewHelper')); + $this->addElement($custom_password); + + $this->setDecorators(array( + array('ViewScript', array('viewScript' => 'form/add-show-live-stream.phtml', "connection_url"=>Application_Model_Preference::GetLiveDJSourceConnectionURL())) + )); + } +} \ No newline at end of file diff --git a/airtime_mvc/application/forms/LiveStreamingPreferences.php b/airtime_mvc/application/forms/LiveStreamingPreferences.php new file mode 100644 index 000000000..235d75786 --- /dev/null +++ b/airtime_mvc/application/forms/LiveStreamingPreferences.php @@ -0,0 +1,77 @@ +setAttrib('autocomplete', 'off') + ->setAllowEmpty(true) + ->setLabel('Master Username') + ->setFilters(array('StringTrim')) + ->setValue(Application_Model_Preference::GetLiveSteamMasterUsername()) + ->setDecorators(array('ViewHelper')); + $this->addElement($master_username); + + //Master password + $master_password = new Zend_Form_Element_Password('master_password'); + $master_password->setAttrib('autocomplete', 'off') + ->setAttrib('renderPassword','true') + ->setAllowEmpty(true) + ->setValue(Application_Model_Preference::GetLiveSteamMasterPassword()) + ->setLabel('Master Password') + ->setFilters(array('StringTrim')) + ->setDecorators(array('ViewHelper')); + $this->addElement($master_password); + + //liquidsoap harbor.input port + $m_port = Application_Model_StreamSetting::GetMasterLiveSteamPort(); + $master_dj_port = new Zend_Form_Element_Text('master_harbor_input_port'); + $master_dj_port->setLabel("Master DJ Port") + ->setValue($m_port) + ->setValidators(array(new Zend_Validate_Between(array('min'=>0, 'max'=>99999)))) + ->addValidator('regex', false, array('pattern'=>'/^[0-9]+$/', 'messages'=>array('regexNotMatch'=>'Only numbers are allowed.'))) + ->setDecorators(array('ViewHelper')); + $this->addElement($master_dj_port); + + $m_mount = Application_Model_StreamSetting::GetMasterLiveSteamMountPoint(); + $master_dj_mount = new Zend_Form_Element_Text('master_harbor_input_mount_point'); + $master_dj_mount->setLabel("Master DJ Mount Point") + ->setValue($m_mount) + ->setValidators(array( + array('regex', false, array('/^[^ &<>]+$/', 'messages' => 'Invalid character entered')))) + ->setDecorators(array('ViewHelper')); + $this->addElement($master_dj_mount); + + //liquidsoap harbor.input port + $l_port = Application_Model_StreamSetting::GetDJLiveSteamPort(); + $live_dj_port = new Zend_Form_Element_Text('dj_harbor_input_port'); + $live_dj_port->setLabel("DJ Port") + ->setValue($l_port) + ->setValidators(array(new Zend_Validate_Between(array('min'=>0, 'max'=>99999)))) + ->addValidator('regex', false, array('pattern'=>'/^[0-9]+$/', 'messages'=>array('regexNotMatch'=>'Only numbers are allowed.'))) + ->setDecorators(array('ViewHelper')); + $this->addElement($live_dj_port); + + $l_mount = Application_Model_StreamSetting::GetDJLiveSteamMountPoint(); + $live_dj_mount = new Zend_Form_Element_Text('dj_harbor_input_mount_point'); + $live_dj_mount->setLabel("DJ Mount Point") + ->setValue($l_mount) + ->setValidators(array( + array('regex', false, array('/^[^ &<>]+$/', 'messages' => 'Invalid character entered')))) + ->setDecorators(array('ViewHelper')); + $this->addElement($live_dj_mount); + + $master_dj_connection_url = Application_Model_Preference::GetMasterDJSourceConnectionURL(); + $live_dj_connection_url = Application_Model_Preference::GetLiveDJSourceConnectionURL(); + + $master_dj_connection_url = ($master_dj_connection_url == "")?("http://".$_SERVER['SERVER_NAME'].":".$m_port."/".$m_mount):$master_dj_connection_url; + $live_dj_connection_url = ($live_dj_connection_url == "")?"http://".$_SERVER['SERVER_NAME'].":".$l_port."/".$l_mount:$live_dj_connection_url; + + $this->setDecorators(array( + array('ViewScript', array('viewScript' => 'form/preferences_livestream.phtml', 'master_dj_connection_url'=>$master_dj_connection_url, 'live_dj_connection_url'=>$live_dj_connection_url,)) + )); + } +} \ No newline at end of file diff --git a/airtime_mvc/application/forms/Preferences.php b/airtime_mvc/application/forms/Preferences.php index b4783c591..fbabf0849 100644 --- a/airtime_mvc/application/forms/Preferences.php +++ b/airtime_mvc/application/forms/Preferences.php @@ -14,12 +14,12 @@ class Application_Form_Preferences extends Zend_Form $general_pref = new Application_Form_GeneralPreferences(); $this->addSubForm($general_pref, 'preferences_general'); + + $livestream_pref = new Application_Form_LiveStreamingPreferences(); + $this->addSubForm($livestream_pref, 'preferences_livestream'); $soundcloud_pref = new Application_Form_SoundcloudPreferences(); $this->addSubForm($soundcloud_pref, 'preferences_soundcloud'); - - /*$support_pref = new Application_Form_SupportPreferences(); - $this->addSubForm($support_pref, 'preferences_support');*/ $this->addElement('submit', 'submit', array( 'class' => 'ui-button ui-state-default right-floated', diff --git a/airtime_mvc/application/forms/StreamSetting.php b/airtime_mvc/application/forms/StreamSetting.php index 127706d48..0168c8c2a 100644 --- a/airtime_mvc/application/forms/StreamSetting.php +++ b/airtime_mvc/application/forms/StreamSetting.php @@ -80,6 +80,7 @@ class Application_Form_StreamSetting extends Zend_Form $d["streamFormat"] = $data['streamFormat']; $this->populate($d); } - return true; + $isValid = parent::isValid($data); + return $isValid; } } diff --git a/airtime_mvc/application/forms/StreamSettingSubForm.php b/airtime_mvc/application/forms/StreamSettingSubForm.php index bbda6576c..a520b8cec 100644 --- a/airtime_mvc/application/forms/StreamSettingSubForm.php +++ b/airtime_mvc/application/forms/StreamSettingSubForm.php @@ -177,20 +177,21 @@ class Application_Form_StreamSettingSubForm extends Zend_Form_SubForm{ } public function isValid ($data){ - $isValid = parent::isValid($data); - if($data['enable'] == 1){ - if($data['host'] == ''){ + $f_data = $data['s'.$this->prefix."_data"]; + $isValid = parent::isValid($f_data); + if($f_data['enable'] == 1){ + if($f_data['host'] == ''){ $element = $this->getElement("host"); $element->addError("Server cannot be empty."); $isValid = false; } - if($data['port'] == ''){ + if($f_data['port'] == ''){ $element = $this->getElement("port"); $element->addError("Port cannot be empty."); $isValid = false; } - if($data['output'] == 'icecast'){ - if($data['mount'] == ''){ + if($f_data['output'] == 'icecast'){ + if($f_data['mount'] == ''){ $element = $this->getElement("mount"); $element->addError("Mount cannot be empty with Icecast server."); $isValid = false; diff --git a/airtime_mvc/application/layouts/scripts/builder.phtml b/airtime_mvc/application/layouts/scripts/builder.phtml index d1d37a4f2..02a9b8253 100644 --- a/airtime_mvc/application/layouts/scripts/builder.phtml +++ b/airtime_mvc/application/layouts/scripts/builder.phtml @@ -10,9 +10,14 @@
- versionNotify() ?> - partial('partialviews/header.phtml', array("user" => $this->loggedInAs(), "is_trial"=>$this->isTrial(), "trial_remain"=> $this->trialRemaining())) ?> - + versionNotify(); + $sss = $this->SourceSwitchStatus(); + $scs = $this->SourceConnectionStatus(); + ?> + partial('partialviews/header.phtml', array("user" => $this->loggedInAs(), "is_trial"=>$this->isTrial(), "trial_remain"=> $this->trialRemaining(), + "live_dj_switch"=>$sss['live_dj'], "live_dj_connection"=>$scs['live_dj'], "master_dj_switch"=>$sss['master_dj'], "master_dj_connection"=>$scs['master_dj'], + "scheduled_play_switch"=>$sss['scheduled_play'])) ?> + navigation()->menu()->setPartial($partial); ?> diff --git a/airtime_mvc/application/layouts/scripts/layout.phtml b/airtime_mvc/application/layouts/scripts/layout.phtml index 10e6a5ec9..92d38eef9 100644 --- a/airtime_mvc/application/layouts/scripts/layout.phtml +++ b/airtime_mvc/application/layouts/scripts/layout.phtml @@ -11,9 +11,13 @@
- versionNotify() ?> - partial('partialviews/header.phtml', array("user" => $this->loggedInAs(), "is_trial"=>$this->isTrial(), "trial_remain"=> $this->trialRemaining())) ?> - + versionNotify(); + $sss = $this->SourceSwitchStatus(); + $scs = $this->SourceConnectionStatus(); + ?> + partial('partialviews/header.phtml', array("user" => $this->loggedInAs(), "is_trial"=>$this->isTrial(), "trial_remain"=> $this->trialRemaining(), + "live_dj_switch"=>$sss['live_dj'], "live_dj_connection"=>$scs['live_dj'], "master_dj_switch"=>$sss['master_dj'], "master_dj_connection"=>$scs['master_dj'], + "scheduled_play_switch"=>$sss['scheduled_play'])) ?> navigation()->menu()->setPartial($partial); ?> diff --git a/airtime_mvc/application/layouts/scripts/library.phtml b/airtime_mvc/application/layouts/scripts/library.phtml index d015fee03..fb411f40f 100644 --- a/airtime_mvc/application/layouts/scripts/library.phtml +++ b/airtime_mvc/application/layouts/scripts/library.phtml @@ -11,8 +11,14 @@
- versionNotify() ?> - partial('partialviews/header.phtml', array("user" => $this->loggedInAs(), "is_trial"=>$this->isTrial(), "trial_remain"=> $this->trialRemaining())) ?> + versionNotify(); + $sss = $this->SourceSwitchStatus(); + $scs = $this->SourceConnectionStatus(); + $isPermissionAllowed = $this->IsPermissionAllowed(); + ?> + partial('partialviews/header.phtml', array("user" => $this->loggedInAs(), "is_trial"=>$this->isTrial(), "trial_remain"=> $this->trialRemaining(), + "live_dj_switch"=>$sss['live_dj'], "live_dj_connection"=>$scs['live_dj'], "master_dj_switch"=>$sss['master_dj'], "master_dj_connection"=>$scs['master_dj'], + "scheduled_play_switch"=>$sss['scheduled_play'], "isPermissionAllowed"=>$isPermissionAllowed)) ?> navigation()->menu()->setPartial($partial); ?> diff --git a/airtime_mvc/application/layouts/scripts/playouthistory.phtml b/airtime_mvc/application/layouts/scripts/playouthistory.phtml index 210bbeaa8..1e8028866 100644 --- a/airtime_mvc/application/layouts/scripts/playouthistory.phtml +++ b/airtime_mvc/application/layouts/scripts/playouthistory.phtml @@ -11,8 +11,13 @@
- versionNotify() ?> - partial('partialviews/header.phtml', array("user" => $this->loggedInAs(), "is_trial"=>$this->isTrial(), "trial_remain"=> $this->trialRemaining())) ?> + versionNotify(); + $sss = $this->SourceSwitchStatus(); + $scs = $this->SourceConnectionStatus(); + ?> + partial('partialviews/header.phtml', array("user" => $this->loggedInAs(), "is_trial"=>$this->isTrial(), "trial_remain"=> $this->trialRemaining(), + "live_dj_switch"=>$sss['live_dj'], "live_dj_connection"=>$scs['live_dj'], "master_dj_switch"=>$sss['master_dj'], "master_dj_connection"=>$scs['master_dj'], + "scheduled_play_switch"=>$sss['scheduled_play'])) ?> navigation()->menu()->setPartial($partial); ?> diff --git a/airtime_mvc/application/models/Preference.php b/airtime_mvc/application/models/Preference.php index 061c694c8..ce6a61d13 100644 --- a/airtime_mvc/application/models/Preference.php +++ b/airtime_mvc/application/models/Preference.php @@ -60,10 +60,10 @@ class Application_Model_Preference public static function GetValue($key, $isUserValue = false){ global $CC_CONFIG, $CC_DBC; + //Check if key already exists $sql = "SELECT COUNT(*) FROM cc_pref" ." WHERE keystr = '$key'"; - //For user specific preference, check if id matches as well if($isUserValue) { $auth = Zend_Auth::getInstance(); @@ -72,7 +72,6 @@ class Application_Model_Preference $sql .= " AND subjid = '$id'"; } } - $result = $CC_DBC->GetOne($sql); if ($result == 0) @@ -700,6 +699,63 @@ class Application_Model_Preference return $val; } + public static function SetLiveSteamMasterUsername($value){ + self::SetValue("live_stream_master_username", $value, false); + } + + public static function GetLiveSteamMasterUsername(){ + return self::GetValue("live_stream_master_username"); + } + + public static function SetLiveSteamMasterPassword($value){ + self::SetValue("live_stream_master_password", $value, false); + } + + public static function GetLiveSteamMasterPassword(){ + return self::GetValue("live_stream_master_password"); + } + + public static function SetSourceStatus($sourcename, $status){ + self::SetValue($sourcename, $status, false); + } + + public static function GetSourceStatus($sourcename){ + $value = self::GetValue($sourcename); + if($value == null || $value == "false"){ + return false; + }else{ + return true; + } + } + + public static function SetSourceSwitchStatus($sourcename, $status){ + self::SetValue($sourcename."_switch", $status, false); + } + + public static function GetSourceSwitchStatus($sourcename){ + $value = self::GetValue($sourcename."_switch"); + if($value == null || $value == "off"){ + return "off"; + }else{ + return "on"; + } + } + + public static function SetMasterDJSourceConnectionURL($value){ + self::SetValue("master_dj_source_connection_url", $value, false); + } + + public static function GetMasterDJSourceConnectionURL(){ + return self::GetValue("master_dj_source_connection_url"); + } + + public static function SetLiveDJSourceConnectionURL($value){ + self::SetValue("live_dj_source_connection_url", $value, false); + } + + public static function GetLiveDJSourceConnectionURL(){ + return self::GetValue("live_dj_source_connection_url"); + } /* User specific preferences end */ } diff --git a/airtime_mvc/application/models/Schedule.php b/airtime_mvc/application/models/Schedule.php index 2338fd0e8..b924ee4c0 100644 --- a/airtime_mvc/application/models/Schedule.php +++ b/airtime_mvc/application/models/Schedule.php @@ -742,22 +742,25 @@ class Application_Model_Schedule { $isSaas = Application_Model_Preference::GetPlanLevel() == 'disabled'?false:true; $formWhat = new Application_Form_AddShowWhat(); - $formWho = new Application_Form_AddShowWho(); - $formWhen = new Application_Form_AddShowWhen(); - $formRepeats = new Application_Form_AddShowRepeats(); - $formStyle = new Application_Form_AddShowStyle(); + $formWho = new Application_Form_AddShowWho(); + $formWhen = new Application_Form_AddShowWhen(); + $formRepeats = new Application_Form_AddShowRepeats(); + $formStyle = new Application_Form_AddShowStyle(); + $formLive = new Application_Form_AddShowLiveStream(); - $formWhat->removeDecorator('DtDdWrapper'); - $formWho->removeDecorator('DtDdWrapper'); - $formWhen->removeDecorator('DtDdWrapper'); - $formRepeats->removeDecorator('DtDdWrapper'); - $formStyle->removeDecorator('DtDdWrapper'); + $formWhat->removeDecorator('DtDdWrapper'); + $formWho->removeDecorator('DtDdWrapper'); + $formWhen->removeDecorator('DtDdWrapper'); + $formRepeats->removeDecorator('DtDdWrapper'); + $formStyle->removeDecorator('DtDdWrapper'); + $formLive->removeDecorator('DtDdWrapper'); $p_view->what = $formWhat; $p_view->when = $formWhen; $p_view->repeats = $formRepeats; $p_view->who = $formWho; $p_view->style = $formStyle; + $p_view->live = $formLive; $formWhat->populate(array('add_show_id' => '-1')); $formWhen->populate(array('add_show_start_date' => date("Y-m-d"), diff --git a/airtime_mvc/application/models/Show.php b/airtime_mvc/application/models/Show.php index 1bca7c7a2..afd9255b7 100644 --- a/airtime_mvc/application/models/Show.php +++ b/airtime_mvc/application/models/Show.php @@ -104,6 +104,19 @@ class Application_Model_Show { return $res; } + + public function getHostsIds() + { + global $CC_DBC; + + $sql = "SELECT subjs_id + FROM cc_show_hosts + WHERE show_id = {$this->_showId}"; + + $hosts = $CC_DBC->GetAll($sql); + + return $hosts; + } //remove everything about this show. public function delete() @@ -771,6 +784,24 @@ class Application_Model_Show { return $showInstance; } + + /** + * returns info about live stream override info + */ + public function getLiveStreamInfo(){ + $info = array(); + if($this->_showId == null){ + return $info; + }else{ + $ccShow = CcShowQuery::create()->findPK($this->_showId); + $info['custom_username'] = $ccShow->getDbLiveStreamUser(); + $info['cb_airtime_auth'] = $ccShow->getDbLiveStreamUsingAirtimeAuth(); + $info['cb_custom_auth'] = $ccShow->getDbLiveStreamUsingCustomAuth(); + $info['custom_username'] = $ccShow->getDbLiveStreamUser(); + $info['custom_password'] = $ccShow->getDbLiveStreamPass(); + return $info; + } + } /* Only used for shows that are repeating. Note that this will return * true even for dates that only have a "modified" show instance (does not @@ -959,6 +990,10 @@ class Application_Model_Show { $ccShow->setDbGenre($data['add_show_genre']); $ccShow->setDbColor($data['add_show_color']); $ccShow->setDbBackgroundColor($data['add_show_background_color']); + $ccShow->setDbLiveStreamUsingAirtimeAuth($data['cb_airtime_auth'] == 1?true:false); + $ccShow->setDbLiveStreamUsingCustomAuth($data['cb_custom_auth'] == 1?true:false); + $ccShow->setDbLiveStreamUser($data['custom_username']); + $ccShow->setDbLiveStreamPass($data['custom_password']); $ccShow->save(); $showId = $ccShow->getDbId(); @@ -1676,9 +1711,13 @@ class Application_Model_Show { * @param String $timeNow - current time (in UTC) * @return array - show being played right now */ - public static function GetCurrentShow($timeNow) + public static function GetCurrentShow($timeNow=null) { global $CC_CONFIG, $CC_DBC; + if($timeNow == null){ + $date = new Application_Model_DateHelper; + $timeNow = $date->getUtcTimestamp(); + } //TODO, returning starts + ends twice (once with an alias). Unify this after the 2.0 release. --Martin $sql = "SELECT si.starts as start_timestamp, si.ends as end_timestamp, s.name, s.id, si.id as instance_id, si.record, s.url, starts, ends" ." FROM $CC_CONFIG[showInstances] si, $CC_CONFIG[showTable] s" diff --git a/airtime_mvc/application/models/StreamSetting.php b/airtime_mvc/application/models/StreamSetting.php index 0ae84a009..bc0fe6f06 100644 --- a/airtime_mvc/application/models/StreamSetting.php +++ b/airtime_mvc/application/models/StreamSetting.php @@ -1,5 +1,48 @@ GetOne($sql); + + if($result == 1) { + $sql = "UPDATE cc_stream_setting" + ." SET value = '$value', type='$type'" + ." WHERE keyname = '$key'"; + } else { + $sql = "INSERT INTO cc_stream_setting (keyname, value, type)" + ." VALUES ('$key', '$value', '$type')"; + } + + return $CC_DBC->query($sql); + } + + public static function GetValue($key){ + global $CC_CONFIG, $CC_DBC; + + //Check if key already exists + $sql = "SELECT COUNT(*) FROM cc_stream_setting" + ." WHERE keyname = '$key'"; + $result = $CC_DBC->GetOne($sql); + + if ($result == 0) + return ""; + else { + $sql = "SELECT value FROM cc_stream_setting" + ." WHERE keyname = '$key'"; + + $result = $CC_DBC->GetOne($sql); + return $result; + } + } /* Returns the id's of all streams that are enabled in an array. An * example of the array returned in JSON notation is ["s1", "s2", "s3"] */ @@ -63,6 +106,33 @@ class Application_Model_StreamSetting { ." WHERE keyname not like '%_error'"; $rows = $CC_DBC->getAll($sql); + + $exists = array(); + + foreach($rows as $r){ + if($r['keyname'] == 'master_live_stream_port'){ + $exists['master_live_stream_port'] = true; + }elseif($r['keyname'] == 'master_live_stream_mp'){ + $exists['master_live_stream_mp'] = true; + }elseif($r['keyname'] == 'dj_live_stream_port'){ + $exists['dj_live_stream_port'] = true; + }elseif($r['keyname'] == 'dj_live_stream_mp'){ + $exists['dj_live_stream_mp'] = true; + } + } + + if(!isset($exists["master_live_stream_port"])){ + $rows[] = (array("keyname" =>"master_live_stream_port", "value"=>self::GetMasterLiveSteamPort(), "type"=>"integer")); + } + if(!isset($exists["master_live_stream_mp"])){ + $rows[] = (array("keyname" =>"master_live_stream_mp", "value"=>self::GetMasterLiveSteamMountPoint(), "type"=>"string")); + } + if(!isset($exists["dj_live_stream_port"])){ + $rows[] = (array("keyname" =>"dj_live_stream_port", "value"=>self::GetDJLiveSteamPort(), "type"=>"integer")); + } + if(!isset($exists["dj_live_stream_mp"])){ + $rows[] = (array("keyname" =>"dj_live_stream_mp", "value"=>self::GetDJLiveSteamMountPoint(), "type"=>"string")); + } return $rows; } @@ -197,4 +267,36 @@ class Application_Model_StreamSetting { } return $out; } + + public static function SetMasterLiveSteamPort($value){ + self::SetValue("master_live_stream_port", $value, "integer"); + } + + public static function GetMasterLiveSteamPort(){ + return self::GetValue("master_live_stream_port"); + } + + public static function SetMasterLiveSteamMountPoint($value){ + self::SetValue("master_live_stream_mp", $value, "string"); + } + + public static function GetMasterLiveSteamMountPoint(){ + return self::GetValue("master_live_stream_mp"); + } + + public static function SetDJLiveSteamPort($value){ + self::SetValue("dj_live_stream_port", $value, "integer"); + } + + public static function GetDJLiveSteamPort(){ + return self::GetValue("dj_live_stream_port"); + } + + public static function SetDJLiveSteamMountPoint($value){ + self::SetValue("dj_live_stream_mp", $value, "string"); + } + + public static function GetDJLiveSteamMountPoint(){ + return self::GetValue("dj_live_stream_mp"); + } } diff --git a/airtime_mvc/application/models/User.php b/airtime_mvc/application/models/User.php index b028a87e9..56312d568 100644 --- a/airtime_mvc/application/models/User.php +++ b/airtime_mvc/application/models/User.php @@ -30,6 +30,10 @@ class Application_Model_User { public function isHost($showId) { return $this->isUserType(UTYPE_HOST, $showId); } + + public function isPM() { + return $this->isUserType(UTYPE_PROGRAM_MANAGER); + } public function isAdmin() { return $this->isUserType(UTYPE_ADMIN); diff --git a/airtime_mvc/application/models/airtime/map/CcShowTableMap.php b/airtime_mvc/application/models/airtime/map/CcShowTableMap.php index b558b6e93..f0d7c84c3 100644 --- a/airtime_mvc/application/models/airtime/map/CcShowTableMap.php +++ b/airtime_mvc/application/models/airtime/map/CcShowTableMap.php @@ -45,6 +45,10 @@ class CcShowTableMap extends TableMap { $this->addColumn('DESCRIPTION', 'DbDescription', 'VARCHAR', false, 512, null); $this->addColumn('COLOR', 'DbColor', 'VARCHAR', false, 6, null); $this->addColumn('BACKGROUND_COLOR', 'DbBackgroundColor', 'VARCHAR', false, 6, null); + $this->addColumn('LIVE_STREAM_USING_AIRTIME_AUTH', 'DbLiveStreamUsingAirtimeAuth', 'BOOLEAN', false, null, null); + $this->addColumn('LIVE_STREAM_USING_CUSTOM_AUTH', 'DbLiveStreamUsingCustomAuth', 'BOOLEAN', false, null, null); + $this->addColumn('LIVE_STREAM_USER', 'DbLiveStreamUser', 'VARCHAR', false, 255, null); + $this->addColumn('LIVE_STREAM_PASS', 'DbLiveStreamPass', 'VARCHAR', false, 255, null); // validators } // initialize() diff --git a/airtime_mvc/application/models/airtime/om/BaseCcShow.php b/airtime_mvc/application/models/airtime/om/BaseCcShow.php index b2ab6b257..78722b5ba 100644 --- a/airtime_mvc/application/models/airtime/om/BaseCcShow.php +++ b/airtime_mvc/application/models/airtime/om/BaseCcShow.php @@ -69,6 +69,30 @@ abstract class BaseCcShow extends BaseObject implements Persistent */ protected $background_color; + /** + * The value for the live_stream_using_airtime_auth field. + * @var boolean + */ + protected $live_stream_using_airtime_auth; + + /** + * The value for the live_stream_using_custom_auth field. + * @var boolean + */ + protected $live_stream_using_custom_auth; + + /** + * The value for the live_stream_user field. + * @var string + */ + protected $live_stream_user; + + /** + * The value for the live_stream_pass field. + * @var string + */ + protected $live_stream_pass; + /** * @var array CcShowInstances[] Collection to store aggregation of CcShowInstances objects. */ @@ -196,6 +220,46 @@ abstract class BaseCcShow extends BaseObject implements Persistent return $this->background_color; } + /** + * Get the [live_stream_using_airtime_auth] column value. + * + * @return boolean + */ + public function getDbLiveStreamUsingAirtimeAuth() + { + return $this->live_stream_using_airtime_auth; + } + + /** + * Get the [live_stream_using_custom_auth] column value. + * + * @return boolean + */ + public function getDbLiveStreamUsingCustomAuth() + { + return $this->live_stream_using_custom_auth; + } + + /** + * Get the [live_stream_user] column value. + * + * @return string + */ + public function getDbLiveStreamUser() + { + return $this->live_stream_user; + } + + /** + * Get the [live_stream_pass] column value. + * + * @return string + */ + public function getDbLiveStreamPass() + { + return $this->live_stream_pass; + } + /** * Set the value of [id] column. * @@ -336,6 +400,86 @@ abstract class BaseCcShow extends BaseObject implements Persistent return $this; } // setDbBackgroundColor() + /** + * Set the value of [live_stream_using_airtime_auth] column. + * + * @param boolean $v new value + * @return CcShow The current object (for fluent API support) + */ + public function setDbLiveStreamUsingAirtimeAuth($v) + { + if ($v !== null) { + $v = (boolean) $v; + } + + if ($this->live_stream_using_airtime_auth !== $v) { + $this->live_stream_using_airtime_auth = $v; + $this->modifiedColumns[] = CcShowPeer::LIVE_STREAM_USING_AIRTIME_AUTH; + } + + return $this; + } // setDbLiveStreamUsingAirtimeAuth() + + /** + * Set the value of [live_stream_using_custom_auth] column. + * + * @param boolean $v new value + * @return CcShow The current object (for fluent API support) + */ + public function setDbLiveStreamUsingCustomAuth($v) + { + if ($v !== null) { + $v = (boolean) $v; + } + + if ($this->live_stream_using_custom_auth !== $v) { + $this->live_stream_using_custom_auth = $v; + $this->modifiedColumns[] = CcShowPeer::LIVE_STREAM_USING_CUSTOM_AUTH; + } + + return $this; + } // setDbLiveStreamUsingCustomAuth() + + /** + * Set the value of [live_stream_user] column. + * + * @param string $v new value + * @return CcShow The current object (for fluent API support) + */ + public function setDbLiveStreamUser($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->live_stream_user !== $v) { + $this->live_stream_user = $v; + $this->modifiedColumns[] = CcShowPeer::LIVE_STREAM_USER; + } + + return $this; + } // setDbLiveStreamUser() + + /** + * Set the value of [live_stream_pass] column. + * + * @param string $v new value + * @return CcShow The current object (for fluent API support) + */ + public function setDbLiveStreamPass($v) + { + if ($v !== null) { + $v = (string) $v; + } + + if ($this->live_stream_pass !== $v) { + $this->live_stream_pass = $v; + $this->modifiedColumns[] = CcShowPeer::LIVE_STREAM_PASS; + } + + return $this; + } // setDbLiveStreamPass() + /** * Indicates whether the columns in this object are only set to default values. * @@ -387,6 +531,10 @@ abstract class BaseCcShow extends BaseObject implements Persistent $this->description = ($row[$startcol + 4] !== null) ? (string) $row[$startcol + 4] : null; $this->color = ($row[$startcol + 5] !== null) ? (string) $row[$startcol + 5] : null; $this->background_color = ($row[$startcol + 6] !== null) ? (string) $row[$startcol + 6] : null; + $this->live_stream_using_airtime_auth = ($row[$startcol + 7] !== null) ? (boolean) $row[$startcol + 7] : null; + $this->live_stream_using_custom_auth = ($row[$startcol + 8] !== null) ? (boolean) $row[$startcol + 8] : null; + $this->live_stream_user = ($row[$startcol + 9] !== null) ? (string) $row[$startcol + 9] : null; + $this->live_stream_pass = ($row[$startcol + 10] !== null) ? (string) $row[$startcol + 10] : null; $this->resetModified(); $this->setNew(false); @@ -395,7 +543,7 @@ abstract class BaseCcShow extends BaseObject implements Persistent $this->ensureConsistency(); } - return $startcol + 7; // 7 = CcShowPeer::NUM_COLUMNS - CcShowPeer::NUM_LAZY_LOAD_COLUMNS). + return $startcol + 11; // 11 = CcShowPeer::NUM_COLUMNS - CcShowPeer::NUM_LAZY_LOAD_COLUMNS). } catch (Exception $e) { throw new PropelException("Error populating CcShow object", $e); @@ -787,6 +935,18 @@ abstract class BaseCcShow extends BaseObject implements Persistent case 6: return $this->getDbBackgroundColor(); break; + case 7: + return $this->getDbLiveStreamUsingAirtimeAuth(); + break; + case 8: + return $this->getDbLiveStreamUsingCustomAuth(); + break; + case 9: + return $this->getDbLiveStreamUser(); + break; + case 10: + return $this->getDbLiveStreamPass(); + break; default: return null; break; @@ -817,6 +977,10 @@ abstract class BaseCcShow extends BaseObject implements Persistent $keys[4] => $this->getDbDescription(), $keys[5] => $this->getDbColor(), $keys[6] => $this->getDbBackgroundColor(), + $keys[7] => $this->getDbLiveStreamUsingAirtimeAuth(), + $keys[8] => $this->getDbLiveStreamUsingCustomAuth(), + $keys[9] => $this->getDbLiveStreamUser(), + $keys[10] => $this->getDbLiveStreamPass(), ); return $result; } @@ -869,6 +1033,18 @@ abstract class BaseCcShow extends BaseObject implements Persistent case 6: $this->setDbBackgroundColor($value); break; + case 7: + $this->setDbLiveStreamUsingAirtimeAuth($value); + break; + case 8: + $this->setDbLiveStreamUsingCustomAuth($value); + break; + case 9: + $this->setDbLiveStreamUser($value); + break; + case 10: + $this->setDbLiveStreamPass($value); + break; } // switch() } @@ -900,6 +1076,10 @@ abstract class BaseCcShow extends BaseObject implements Persistent if (array_key_exists($keys[4], $arr)) $this->setDbDescription($arr[$keys[4]]); if (array_key_exists($keys[5], $arr)) $this->setDbColor($arr[$keys[5]]); if (array_key_exists($keys[6], $arr)) $this->setDbBackgroundColor($arr[$keys[6]]); + if (array_key_exists($keys[7], $arr)) $this->setDbLiveStreamUsingAirtimeAuth($arr[$keys[7]]); + if (array_key_exists($keys[8], $arr)) $this->setDbLiveStreamUsingCustomAuth($arr[$keys[8]]); + if (array_key_exists($keys[9], $arr)) $this->setDbLiveStreamUser($arr[$keys[9]]); + if (array_key_exists($keys[10], $arr)) $this->setDbLiveStreamPass($arr[$keys[10]]); } /** @@ -918,6 +1098,10 @@ abstract class BaseCcShow extends BaseObject implements Persistent if ($this->isColumnModified(CcShowPeer::DESCRIPTION)) $criteria->add(CcShowPeer::DESCRIPTION, $this->description); if ($this->isColumnModified(CcShowPeer::COLOR)) $criteria->add(CcShowPeer::COLOR, $this->color); if ($this->isColumnModified(CcShowPeer::BACKGROUND_COLOR)) $criteria->add(CcShowPeer::BACKGROUND_COLOR, $this->background_color); + if ($this->isColumnModified(CcShowPeer::LIVE_STREAM_USING_AIRTIME_AUTH)) $criteria->add(CcShowPeer::LIVE_STREAM_USING_AIRTIME_AUTH, $this->live_stream_using_airtime_auth); + if ($this->isColumnModified(CcShowPeer::LIVE_STREAM_USING_CUSTOM_AUTH)) $criteria->add(CcShowPeer::LIVE_STREAM_USING_CUSTOM_AUTH, $this->live_stream_using_custom_auth); + if ($this->isColumnModified(CcShowPeer::LIVE_STREAM_USER)) $criteria->add(CcShowPeer::LIVE_STREAM_USER, $this->live_stream_user); + if ($this->isColumnModified(CcShowPeer::LIVE_STREAM_PASS)) $criteria->add(CcShowPeer::LIVE_STREAM_PASS, $this->live_stream_pass); return $criteria; } @@ -985,6 +1169,10 @@ abstract class BaseCcShow extends BaseObject implements Persistent $copyObj->setDbDescription($this->description); $copyObj->setDbColor($this->color); $copyObj->setDbBackgroundColor($this->background_color); + $copyObj->setDbLiveStreamUsingAirtimeAuth($this->live_stream_using_airtime_auth); + $copyObj->setDbLiveStreamUsingCustomAuth($this->live_stream_using_custom_auth); + $copyObj->setDbLiveStreamUser($this->live_stream_user); + $copyObj->setDbLiveStreamPass($this->live_stream_pass); if ($deepCopy) { // important: temporarily setNew(false) because this affects the behavior of @@ -1583,6 +1771,10 @@ abstract class BaseCcShow extends BaseObject implements Persistent $this->description = null; $this->color = null; $this->background_color = null; + $this->live_stream_using_airtime_auth = null; + $this->live_stream_using_custom_auth = null; + $this->live_stream_user = null; + $this->live_stream_pass = null; $this->alreadyInSave = false; $this->alreadyInValidation = false; $this->clearAllReferences(); diff --git a/airtime_mvc/application/models/airtime/om/BaseCcShowPeer.php b/airtime_mvc/application/models/airtime/om/BaseCcShowPeer.php index 110c08101..e559e0d1a 100644 --- a/airtime_mvc/application/models/airtime/om/BaseCcShowPeer.php +++ b/airtime_mvc/application/models/airtime/om/BaseCcShowPeer.php @@ -26,7 +26,7 @@ abstract class BaseCcShowPeer { const TM_CLASS = 'CcShowTableMap'; /** The total number of columns. */ - const NUM_COLUMNS = 7; + const NUM_COLUMNS = 11; /** The number of lazy-loaded columns. */ const NUM_LAZY_LOAD_COLUMNS = 0; @@ -52,6 +52,18 @@ abstract class BaseCcShowPeer { /** the column name for the BACKGROUND_COLOR field */ const BACKGROUND_COLOR = 'cc_show.BACKGROUND_COLOR'; + /** the column name for the LIVE_STREAM_USING_AIRTIME_AUTH field */ + const LIVE_STREAM_USING_AIRTIME_AUTH = 'cc_show.LIVE_STREAM_USING_AIRTIME_AUTH'; + + /** the column name for the LIVE_STREAM_USING_CUSTOM_AUTH field */ + const LIVE_STREAM_USING_CUSTOM_AUTH = 'cc_show.LIVE_STREAM_USING_CUSTOM_AUTH'; + + /** the column name for the LIVE_STREAM_USER field */ + const LIVE_STREAM_USER = 'cc_show.LIVE_STREAM_USER'; + + /** the column name for the LIVE_STREAM_PASS field */ + const LIVE_STREAM_PASS = 'cc_show.LIVE_STREAM_PASS'; + /** * An identiy map to hold any loaded instances of CcShow objects. * This must be public so that other peer classes can access this when hydrating from JOIN @@ -68,12 +80,12 @@ abstract class BaseCcShowPeer { * e.g. self::$fieldNames[self::TYPE_PHPNAME][0] = 'Id' */ private static $fieldNames = array ( - BasePeer::TYPE_PHPNAME => array ('DbId', 'DbName', 'DbUrl', 'DbGenre', 'DbDescription', 'DbColor', 'DbBackgroundColor', ), - BasePeer::TYPE_STUDLYPHPNAME => array ('dbId', 'dbName', 'dbUrl', 'dbGenre', 'dbDescription', 'dbColor', 'dbBackgroundColor', ), - BasePeer::TYPE_COLNAME => array (self::ID, self::NAME, self::URL, self::GENRE, self::DESCRIPTION, self::COLOR, self::BACKGROUND_COLOR, ), - BasePeer::TYPE_RAW_COLNAME => array ('ID', 'NAME', 'URL', 'GENRE', 'DESCRIPTION', 'COLOR', 'BACKGROUND_COLOR', ), - BasePeer::TYPE_FIELDNAME => array ('id', 'name', 'url', 'genre', 'description', 'color', 'background_color', ), - BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, ) + BasePeer::TYPE_PHPNAME => array ('DbId', 'DbName', 'DbUrl', 'DbGenre', 'DbDescription', 'DbColor', 'DbBackgroundColor', 'DbLiveStreamUsingAirtimeAuth', 'DbLiveStreamUsingCustomAuth', 'DbLiveStreamUser', 'DbLiveStreamPass', ), + BasePeer::TYPE_STUDLYPHPNAME => array ('dbId', 'dbName', 'dbUrl', 'dbGenre', 'dbDescription', 'dbColor', 'dbBackgroundColor', 'dbLiveStreamUsingAirtimeAuth', 'dbLiveStreamUsingCustomAuth', 'dbLiveStreamUser', 'dbLiveStreamPass', ), + BasePeer::TYPE_COLNAME => array (self::ID, self::NAME, self::URL, self::GENRE, self::DESCRIPTION, self::COLOR, self::BACKGROUND_COLOR, self::LIVE_STREAM_USING_AIRTIME_AUTH, self::LIVE_STREAM_USING_CUSTOM_AUTH, self::LIVE_STREAM_USER, self::LIVE_STREAM_PASS, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID', 'NAME', 'URL', 'GENRE', 'DESCRIPTION', 'COLOR', 'BACKGROUND_COLOR', 'LIVE_STREAM_USING_AIRTIME_AUTH', 'LIVE_STREAM_USING_CUSTOM_AUTH', 'LIVE_STREAM_USER', 'LIVE_STREAM_PASS', ), + BasePeer::TYPE_FIELDNAME => array ('id', 'name', 'url', 'genre', 'description', 'color', 'background_color', 'live_stream_using_airtime_auth', 'live_stream_using_custom_auth', 'live_stream_user', 'live_stream_pass', ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ) ); /** @@ -83,12 +95,12 @@ abstract class BaseCcShowPeer { * e.g. self::$fieldNames[BasePeer::TYPE_PHPNAME]['Id'] = 0 */ private static $fieldKeys = array ( - BasePeer::TYPE_PHPNAME => array ('DbId' => 0, 'DbName' => 1, 'DbUrl' => 2, 'DbGenre' => 3, 'DbDescription' => 4, 'DbColor' => 5, 'DbBackgroundColor' => 6, ), - BasePeer::TYPE_STUDLYPHPNAME => array ('dbId' => 0, 'dbName' => 1, 'dbUrl' => 2, 'dbGenre' => 3, 'dbDescription' => 4, 'dbColor' => 5, 'dbBackgroundColor' => 6, ), - BasePeer::TYPE_COLNAME => array (self::ID => 0, self::NAME => 1, self::URL => 2, self::GENRE => 3, self::DESCRIPTION => 4, self::COLOR => 5, self::BACKGROUND_COLOR => 6, ), - BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'NAME' => 1, 'URL' => 2, 'GENRE' => 3, 'DESCRIPTION' => 4, 'COLOR' => 5, 'BACKGROUND_COLOR' => 6, ), - BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'name' => 1, 'url' => 2, 'genre' => 3, 'description' => 4, 'color' => 5, 'background_color' => 6, ), - BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, ) + BasePeer::TYPE_PHPNAME => array ('DbId' => 0, 'DbName' => 1, 'DbUrl' => 2, 'DbGenre' => 3, 'DbDescription' => 4, 'DbColor' => 5, 'DbBackgroundColor' => 6, 'DbLiveStreamUsingAirtimeAuth' => 7, 'DbLiveStreamUsingCustomAuth' => 8, 'DbLiveStreamUser' => 9, 'DbLiveStreamPass' => 10, ), + BasePeer::TYPE_STUDLYPHPNAME => array ('dbId' => 0, 'dbName' => 1, 'dbUrl' => 2, 'dbGenre' => 3, 'dbDescription' => 4, 'dbColor' => 5, 'dbBackgroundColor' => 6, 'dbLiveStreamUsingAirtimeAuth' => 7, 'dbLiveStreamUsingCustomAuth' => 8, 'dbLiveStreamUser' => 9, 'dbLiveStreamPass' => 10, ), + BasePeer::TYPE_COLNAME => array (self::ID => 0, self::NAME => 1, self::URL => 2, self::GENRE => 3, self::DESCRIPTION => 4, self::COLOR => 5, self::BACKGROUND_COLOR => 6, self::LIVE_STREAM_USING_AIRTIME_AUTH => 7, self::LIVE_STREAM_USING_CUSTOM_AUTH => 8, self::LIVE_STREAM_USER => 9, self::LIVE_STREAM_PASS => 10, ), + BasePeer::TYPE_RAW_COLNAME => array ('ID' => 0, 'NAME' => 1, 'URL' => 2, 'GENRE' => 3, 'DESCRIPTION' => 4, 'COLOR' => 5, 'BACKGROUND_COLOR' => 6, 'LIVE_STREAM_USING_AIRTIME_AUTH' => 7, 'LIVE_STREAM_USING_CUSTOM_AUTH' => 8, 'LIVE_STREAM_USER' => 9, 'LIVE_STREAM_PASS' => 10, ), + BasePeer::TYPE_FIELDNAME => array ('id' => 0, 'name' => 1, 'url' => 2, 'genre' => 3, 'description' => 4, 'color' => 5, 'background_color' => 6, 'live_stream_using_airtime_auth' => 7, 'live_stream_using_custom_auth' => 8, 'live_stream_user' => 9, 'live_stream_pass' => 10, ), + BasePeer::TYPE_NUM => array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ) ); /** @@ -167,6 +179,10 @@ abstract class BaseCcShowPeer { $criteria->addSelectColumn(CcShowPeer::DESCRIPTION); $criteria->addSelectColumn(CcShowPeer::COLOR); $criteria->addSelectColumn(CcShowPeer::BACKGROUND_COLOR); + $criteria->addSelectColumn(CcShowPeer::LIVE_STREAM_USING_AIRTIME_AUTH); + $criteria->addSelectColumn(CcShowPeer::LIVE_STREAM_USING_CUSTOM_AUTH); + $criteria->addSelectColumn(CcShowPeer::LIVE_STREAM_USER); + $criteria->addSelectColumn(CcShowPeer::LIVE_STREAM_PASS); } else { $criteria->addSelectColumn($alias . '.ID'); $criteria->addSelectColumn($alias . '.NAME'); @@ -175,6 +191,10 @@ abstract class BaseCcShowPeer { $criteria->addSelectColumn($alias . '.DESCRIPTION'); $criteria->addSelectColumn($alias . '.COLOR'); $criteria->addSelectColumn($alias . '.BACKGROUND_COLOR'); + $criteria->addSelectColumn($alias . '.LIVE_STREAM_USING_AIRTIME_AUTH'); + $criteria->addSelectColumn($alias . '.LIVE_STREAM_USING_CUSTOM_AUTH'); + $criteria->addSelectColumn($alias . '.LIVE_STREAM_USER'); + $criteria->addSelectColumn($alias . '.LIVE_STREAM_PASS'); } } diff --git a/airtime_mvc/application/models/airtime/om/BaseCcShowQuery.php b/airtime_mvc/application/models/airtime/om/BaseCcShowQuery.php index 10f9dac11..580a7b1ad 100644 --- a/airtime_mvc/application/models/airtime/om/BaseCcShowQuery.php +++ b/airtime_mvc/application/models/airtime/om/BaseCcShowQuery.php @@ -13,6 +13,10 @@ * @method CcShowQuery orderByDbDescription($order = Criteria::ASC) Order by the description column * @method CcShowQuery orderByDbColor($order = Criteria::ASC) Order by the color column * @method CcShowQuery orderByDbBackgroundColor($order = Criteria::ASC) Order by the background_color column + * @method CcShowQuery orderByDbLiveStreamUsingAirtimeAuth($order = Criteria::ASC) Order by the live_stream_using_airtime_auth column + * @method CcShowQuery orderByDbLiveStreamUsingCustomAuth($order = Criteria::ASC) Order by the live_stream_using_custom_auth column + * @method CcShowQuery orderByDbLiveStreamUser($order = Criteria::ASC) Order by the live_stream_user column + * @method CcShowQuery orderByDbLiveStreamPass($order = Criteria::ASC) Order by the live_stream_pass column * * @method CcShowQuery groupByDbId() Group by the id column * @method CcShowQuery groupByDbName() Group by the name column @@ -21,6 +25,10 @@ * @method CcShowQuery groupByDbDescription() Group by the description column * @method CcShowQuery groupByDbColor() Group by the color column * @method CcShowQuery groupByDbBackgroundColor() Group by the background_color column + * @method CcShowQuery groupByDbLiveStreamUsingAirtimeAuth() Group by the live_stream_using_airtime_auth column + * @method CcShowQuery groupByDbLiveStreamUsingCustomAuth() Group by the live_stream_using_custom_auth column + * @method CcShowQuery groupByDbLiveStreamUser() Group by the live_stream_user column + * @method CcShowQuery groupByDbLiveStreamPass() Group by the live_stream_pass column * * @method CcShowQuery leftJoin($relation) Adds a LEFT JOIN clause to the query * @method CcShowQuery rightJoin($relation) Adds a RIGHT JOIN clause to the query @@ -52,6 +60,10 @@ * @method CcShow findOneByDbDescription(string $description) Return the first CcShow filtered by the description column * @method CcShow findOneByDbColor(string $color) Return the first CcShow filtered by the color column * @method CcShow findOneByDbBackgroundColor(string $background_color) Return the first CcShow filtered by the background_color column + * @method CcShow findOneByDbLiveStreamUsingAirtimeAuth(boolean $live_stream_using_airtime_auth) Return the first CcShow filtered by the live_stream_using_airtime_auth column + * @method CcShow findOneByDbLiveStreamUsingCustomAuth(boolean $live_stream_using_custom_auth) Return the first CcShow filtered by the live_stream_using_custom_auth column + * @method CcShow findOneByDbLiveStreamUser(string $live_stream_user) Return the first CcShow filtered by the live_stream_user column + * @method CcShow findOneByDbLiveStreamPass(string $live_stream_pass) Return the first CcShow filtered by the live_stream_pass column * * @method array findByDbId(int $id) Return CcShow objects filtered by the id column * @method array findByDbName(string $name) Return CcShow objects filtered by the name column @@ -60,6 +72,10 @@ * @method array findByDbDescription(string $description) Return CcShow objects filtered by the description column * @method array findByDbColor(string $color) Return CcShow objects filtered by the color column * @method array findByDbBackgroundColor(string $background_color) Return CcShow objects filtered by the background_color column + * @method array findByDbLiveStreamUsingAirtimeAuth(boolean $live_stream_using_airtime_auth) Return CcShow objects filtered by the live_stream_using_airtime_auth column + * @method array findByDbLiveStreamUsingCustomAuth(boolean $live_stream_using_custom_auth) Return CcShow objects filtered by the live_stream_using_custom_auth column + * @method array findByDbLiveStreamUser(string $live_stream_user) Return CcShow objects filtered by the live_stream_user column + * @method array findByDbLiveStreamPass(string $live_stream_pass) Return CcShow objects filtered by the live_stream_pass column * * @package propel.generator.airtime.om */ @@ -318,6 +334,84 @@ abstract class BaseCcShowQuery extends ModelCriteria return $this->addUsingAlias(CcShowPeer::BACKGROUND_COLOR, $dbBackgroundColor, $comparison); } + /** + * Filter the query on the live_stream_using_airtime_auth column + * + * @param boolean|string $dbLiveStreamUsingAirtimeAuth The value to use as filter. + * Accepts strings ('false', 'off', '-', 'no', 'n', and '0' are false, the rest is true) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcShowQuery The current query, for fluid interface + */ + public function filterByDbLiveStreamUsingAirtimeAuth($dbLiveStreamUsingAirtimeAuth = null, $comparison = null) + { + if (is_string($dbLiveStreamUsingAirtimeAuth)) { + $live_stream_using_airtime_auth = in_array(strtolower($dbLiveStreamUsingAirtimeAuth), array('false', 'off', '-', 'no', 'n', '0')) ? false : true; + } + return $this->addUsingAlias(CcShowPeer::LIVE_STREAM_USING_AIRTIME_AUTH, $dbLiveStreamUsingAirtimeAuth, $comparison); + } + + /** + * Filter the query on the live_stream_using_custom_auth column + * + * @param boolean|string $dbLiveStreamUsingCustomAuth The value to use as filter. + * Accepts strings ('false', 'off', '-', 'no', 'n', and '0' are false, the rest is true) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcShowQuery The current query, for fluid interface + */ + public function filterByDbLiveStreamUsingCustomAuth($dbLiveStreamUsingCustomAuth = null, $comparison = null) + { + if (is_string($dbLiveStreamUsingCustomAuth)) { + $live_stream_using_custom_auth = in_array(strtolower($dbLiveStreamUsingCustomAuth), array('false', 'off', '-', 'no', 'n', '0')) ? false : true; + } + return $this->addUsingAlias(CcShowPeer::LIVE_STREAM_USING_CUSTOM_AUTH, $dbLiveStreamUsingCustomAuth, $comparison); + } + + /** + * Filter the query on the live_stream_user column + * + * @param string $dbLiveStreamUser The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcShowQuery The current query, for fluid interface + */ + public function filterByDbLiveStreamUser($dbLiveStreamUser = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($dbLiveStreamUser)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $dbLiveStreamUser)) { + $dbLiveStreamUser = str_replace('*', '%', $dbLiveStreamUser); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcShowPeer::LIVE_STREAM_USER, $dbLiveStreamUser, $comparison); + } + + /** + * Filter the query on the live_stream_pass column + * + * @param string $dbLiveStreamPass The value to use as filter. + * Accepts wildcards (* and % trigger a LIKE) + * @param string $comparison Operator to use for the column comparison, defaults to Criteria::EQUAL + * + * @return CcShowQuery The current query, for fluid interface + */ + public function filterByDbLiveStreamPass($dbLiveStreamPass = null, $comparison = null) + { + if (null === $comparison) { + if (is_array($dbLiveStreamPass)) { + $comparison = Criteria::IN; + } elseif (preg_match('/[\%\*]/', $dbLiveStreamPass)) { + $dbLiveStreamPass = str_replace('*', '%', $dbLiveStreamPass); + $comparison = Criteria::LIKE; + } + } + return $this->addUsingAlias(CcShowPeer::LIVE_STREAM_PASS, $dbLiveStreamPass, $comparison); + } + /** * Filter the query by a related CcShowInstances object * diff --git a/airtime_mvc/application/views/helpers/SourceConnectionStatus.php b/airtime_mvc/application/views/helpers/SourceConnectionStatus.php new file mode 100644 index 000000000..1d86b0f57 --- /dev/null +++ b/airtime_mvc/application/views/helpers/SourceConnectionStatus.php @@ -0,0 +1,8 @@ +Application_Model_Preference::GetSourceStatus("live_dj"), "master_dj"=>Application_Model_Preference::GetSourceStatus("master_dj")); + return $status; + } +} \ No newline at end of file diff --git a/airtime_mvc/application/views/helpers/SourceSwitchStatus.php b/airtime_mvc/application/views/helpers/SourceSwitchStatus.php new file mode 100644 index 000000000..86ca01707 --- /dev/null +++ b/airtime_mvc/application/views/helpers/SourceSwitchStatus.php @@ -0,0 +1,10 @@ +Application_Model_Preference::GetSourceSwitchStatus("live_dj"), + "master_dj"=>Application_Model_Preference::GetSourceSwitchStatus("master_dj"), + "scheduled_play"=>Application_Model_Preference::GetSourceSwitchStatus("scheduled_play")); + return $status; + } +} \ No newline at end of file diff --git a/airtime_mvc/application/views/scripts/form/add-show-live-stream.phtml b/airtime_mvc/application/views/scripts/form/add-show-live-stream.phtml new file mode 100644 index 000000000..aed91716d --- /dev/null +++ b/airtime_mvc/application/views/scripts/form/add-show-live-stream.phtml @@ -0,0 +1,67 @@ +
+
+
+ +
+
+ element->getElement('cb_airtime_auth') ?> +
+
+ +
+
+ element->getElement('cb_custom_auth') ?> +
+ + +
+ +
+
+ element->getElement('custom_username') ?> + element->getElement('custom_username')->hasErrors()) : ?> +
    + element->getElement('custom_username')->getMessages() as $error): ?> +
  • + +
+ +
+
+ +
+
+ element->getElement('custom_password') ?> + element->getElement('custom_password')->hasErrors()) : ?> +
    + element->getElement('custom_password')->getMessages() as $error): ?> +
  • + +
+ +
+
+ +
+
+ connection_url; ?> +
+
+
+ diff --git a/airtime_mvc/application/views/scripts/form/preferences.phtml b/airtime_mvc/application/views/scripts/form/preferences.phtml index d6d20ea00..412d5aa01 100644 --- a/airtime_mvc/application/views/scripts/form/preferences.phtml +++ b/airtime_mvc/application/views/scripts/form/preferences.phtml @@ -2,7 +2,6 @@ element->getSubform('preferences_general') ?> -

SoundCloud Settings

+
+ +
ON AIR
Listen diff --git a/airtime_mvc/application/views/scripts/playlist/update.phtml b/airtime_mvc/application/views/scripts/playlist/update.phtml index 3557d207c..803a98116 100644 --- a/airtime_mvc/application/views/scripts/playlist/update.phtml +++ b/airtime_mvc/application/views/scripts/playlist/update.phtml @@ -4,18 +4,25 @@ if (count($items)) : ?>
  • " unqid=""> -
    +
    + +
    ">
    + +
    + +
    + +
    - - " ?> +
    statusMsg;?> +
    Global Settings
    @@ -67,6 +68,9 @@
    + form->getSubform('live_stream_subform'); ?> +
    +
    num_stream;$i++){ echo $this->form->getSubform("s".$i."_subform"); @@ -77,5 +81,6 @@
    - +
    +
  • \ No newline at end of file diff --git a/airtime_mvc/application/views/scripts/schedule/add-show-form.phtml b/airtime_mvc/application/views/scripts/schedule/add-show-form.phtml index b42b408d2..e61593d78 100644 --- a/airtime_mvc/application/views/scripts/schedule/add-show-form.phtml +++ b/airtime_mvc/application/views/scripts/schedule/add-show-form.phtml @@ -16,6 +16,10 @@ when; ?> repeats; ?>
    +

    Live Stream

    +
    + live; ?> +
    isSaas()){?>

    Record & Rebroadcast

    diff --git a/airtime_mvc/build/schema.xml b/airtime_mvc/build/schema.xml index fc0d7284b..261bdcf43 100644 --- a/airtime_mvc/build/schema.xml +++ b/airtime_mvc/build/schema.xml @@ -146,6 +146,10 @@ + + + + diff --git a/airtime_mvc/build/sql/schema.sql b/airtime_mvc/build/sql/schema.sql index e3074c01c..6cc7c2bc1 100644 --- a/airtime_mvc/build/sql/schema.sql +++ b/airtime_mvc/build/sql/schema.sql @@ -178,6 +178,10 @@ CREATE TABLE "cc_show" "description" VARCHAR(512), "color" VARCHAR(6), "background_color" VARCHAR(6), + "live_stream_using_airtime_auth" BOOLEAN, + "live_stream_using_custom_auth" BOOLEAN, + "live_stream_user" VARCHAR(255), + "live_stream_pass" VARCHAR(255), PRIMARY KEY ("id") ); diff --git a/airtime_mvc/public/css/images/file_import_loader.gif b/airtime_mvc/public/css/images/file_import_loader.gif new file mode 100644 index 000000000..c9b844e82 Binary files /dev/null and b/airtime_mvc/public/css/images/file_import_loader.gif differ diff --git a/airtime_mvc/public/css/images/loader.gif b/airtime_mvc/public/css/images/loader.gif new file mode 100644 index 000000000..fc4e07611 Binary files /dev/null and b/airtime_mvc/public/css/images/loader.gif differ diff --git a/airtime_mvc/public/css/jquery.ui.timepicker.css b/airtime_mvc/public/css/jquery.ui.timepicker.css index 08b442a7e..374da6bbb 100644 --- a/airtime_mvc/public/css/jquery.ui.timepicker.css +++ b/airtime_mvc/public/css/jquery.ui.timepicker.css @@ -10,7 +10,10 @@ .ui-timepicker-inline { display: inline; } -#ui-timepicker-div { padding: 0.2em } +#ui-timepicker-div { + padding: 0.2em; + z-index: 1000; +} .ui-timepicker-table { display: inline-table; width: 0; } .ui-timepicker-table table { margin:0.15em 0 0 0; border-collapse: collapse; } diff --git a/airtime_mvc/public/css/media_library.css b/airtime_mvc/public/css/media_library.css index 87e156037..f6fa6486b 100644 --- a/airtime_mvc/public/css/media_library.css +++ b/airtime_mvc/public/css/media_library.css @@ -80,4 +80,13 @@ td.library_track, td.library_sr, td.library_bitrate { text-align: right; +} + +.library_import { + padding-bottom: 5px; +} + +.library_import img { + vertical-align: middle; + padding-left: 5px; } \ No newline at end of file diff --git a/airtime_mvc/public/css/playlist_builder.css b/airtime_mvc/public/css/playlist_builder.css index 4f08a3976..8a1e98125 100644 --- a/airtime_mvc/public/css/playlist_builder.css +++ b/airtime_mvc/public/css/playlist_builder.css @@ -159,14 +159,10 @@ background: -moz-linear-gradient(top, #3b3b3b 0, #292929 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #3b3b3b), color-stop(100%, #292929)); } -#spl_sortable div.big_play .ui-icon-play { +#spl_sortable div.big_play .ui-icon { margin: 17px 0 0 1px; } -#spl_sortable div.big_play .ui-icon-pause { - margin: 17px 0 0 1px; -} - #spl_sortable div.big_play:hover .ui-icon-play, #spl_sortable div.big_play:hover .ui-icon-pause { background-image:url(redmond/images/ui-icons_ff5d1a_256x240.png); } @@ -456,4 +452,14 @@ div.helper li { li.spl_empty { height: 56px; +} + +.pl-overlay { + z-index:100; + opacity: 0.7; +} + +.pl-loading { + z-index:101; + background: transparent url(images/loader.gif) no-repeat 0 0; } \ No newline at end of file diff --git a/airtime_mvc/public/css/styles.css b/airtime_mvc/public/css/styles.css index a7de7fa82..9396cf733 100644 --- a/airtime_mvc/public/css/styles.css +++ b/airtime_mvc/public/css/styles.css @@ -152,7 +152,7 @@ select { position:relative; } -.now-playing-block, .show-block, .on-air-block, .time-info-block, .personal-block, .listen-control-block, .trial-info-block { +.now-playing-block, .show-block, .on-air-block, .time-info-block, .personal-block, .listen-control-block, .trial-info-block, .source-info-block { height:100px; float:left; margin-right:10px; @@ -263,7 +263,7 @@ select { padding:0 12px 0 0; background:url(images/masterpanel_spacer.png) no-repeat right 0; } -.time-info-block { +.time-info-block { padding:0 14px 0 2px; background:url(images/masterpanel_spacer.png) no-repeat right 0; min-width:105px; @@ -288,6 +288,24 @@ select { font-size:17px; margin-bottom:0; } + +.source-info-block { + padding:0 14px 0 2px; + min-width:180px; +} + +.source-info-block li { + list-style-type:none; + font-size:14px; + color:#bdbdbd; + margin:0 0 6px; +} + +.source-info-block ul { + margin:0; + padding:6px 0 0; +} + .on-air-info { height:26px; border:1px solid #242424; @@ -361,6 +379,97 @@ select { color:#fff; } +.source-switch-button { + font-size:11px; + text-transform:uppercase; + padding:0; + border:1px solid #242424; + color:#fff; + text-decoration:none; + font-weight:bold; + display:block; + text-align:center; + width: 40px; + float: right; +} + +.source-switch-button span { + background-color: #6e6e6e; + background: -moz-linear-gradient(top, #868686 0, #6e6e6e 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #868686), color-stop(100%, #6e6e6e)); + border:1px solid #a1a1a1; + border-width:1px 0; + border-bottom-color:#646464; + color:#dcdcdc; + text-shadow: #555555 0px -1px; + display:block; +} + +.source-switch-button:hover { + border:1px solid #000; +} + +.source-switch-button:hover span { + background-color: #292929; + background: -moz-linear-gradient(top, #3b3b3b 0, #292929 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #3b3b3b), color-stop(100%, #292929)); + border:1px solid #555555; + border-width:1px 0; + border-bottom-color:#1e1e1e; + color:#fff; + color:#0C0; + text-shadow: #000 0px -1px; + display:block; +} + +.source-kick-button { + border:1px solid #242424; + color:#fff; + min-height:14px; + min-width:14px; + background-color:#464646; + margin-right: 2px; + float: left; +} + +.source-kick-button:hover{ + cursor:pointer; + background-color:#FFFFFF; +} + +.line-to-switch { + float: right; + width: 10px; + height:6px; + border:1px solid #242424; + border-width:1px 1px 0 1px; + margin-left: 5px; +} + +.line-to-switch.off{ + background:#FFFFFF +} + +.line-to-switch.on{ + background:#D40000 +} + +.line-to-on-air { + float: right; + height:6px; + border:1px solid #242424; + border-width:1px 1px 0 1px; + width: 10px; +} + +.line-to-on-air.off{ + background:#FFFFFF +} + +.line-to-on-air.on{ + background:#D40000 +} + /* END Master Panel */ @@ -1762,10 +1871,14 @@ div.errors{ .medium-icon.finishedplaying { background:url(images/icon_finishedplaying_m.png) no-repeat 0 0; } -.preferences, .manage-folders, .stream-config { +.preferences, .manage-folders { width: 500px; } +.stream-config { + width: 1080px; +} + .preferences .padded { margin-top: 5px; /* Firefox needs this */ } @@ -2096,7 +2209,7 @@ dd .info-text-small { /*width:98.5%;*/ min-width:152px; } -.stream-config .display_field dd input[type="text"], .stream-config .display_field dd textarea { +.stream-config .display_field dd input[type="text"], .stream-config .display_field dd input[type="password"], .stream-config .display_field dd textarea { min-width:99%; padding: 4px 3px; } @@ -2106,9 +2219,6 @@ dd .info-text-small { .simple-formblock .display_field dd { min-width:68%; } -.stream-config dd input[id$=port] { - width:152px; -} dt.block-display.info-block { width: auto; diff --git a/airtime_mvc/public/js/airtime/dashboard/playlist.js b/airtime_mvc/public/js/airtime/dashboard/playlist.js index 0c0e2f4c6..6646f94a4 100644 --- a/airtime_mvc/public/js/airtime/dashboard/playlist.js +++ b/airtime_mvc/public/js/airtime/dashboard/playlist.js @@ -13,6 +13,11 @@ var currentElem; var serverUpdateInterval = 5000; var uiUpdateInterval = 200; +var master_dj_on_air = false; +var live_dj_on_air = false; +var scheduled_play_on_air = false; +var scheduled_play_source = false; + //var timezoneOffset = 0; //set to "development" if we are developing :). Useful to disable alerts @@ -31,6 +36,8 @@ function secondsTimer(){ estimatedSchedulePosixTime = date.getTime() - localRemoteTimeOffset; updateProgressBarValue(); updatePlaybar(); + controlOnAirLight(); + controlSwitchLight(); } setTimeout(secondsTimer, uiUpdateInterval); } @@ -74,14 +81,20 @@ function updateProgressBarValue(){ songPercentDone = 0; currentSong = null; } else { - if (currentSong.media_item_played == "t" && currentShow.length > 0) - $('#on-air-info').attr("class", "on-air-info on"); - else - $('#on-air-info').attr("class", "on-air-info off"); + var scheduled_play_line_to_switch = $("#scheduled_play_div").find(".line-to-switch") + if (currentSong.media_item_played == "t" && currentShow.length > 0){ + scheduled_play_line_to_switch.attr("class", "line-to-switch on"); + scheduled_play_source = true; + } + else{ + scheduled_play_source = false; + scheduled_play_line_to_switch.attr("class", "line-to-switch off"); + } $('#progress-show').attr("class", "progress-show"); } } else { - $('#on-air-info').attr("class", "on-air-info off"); + scheduled_play_source = false; + $("#scheduled_play_div").find(".line-to-switch").attr("class", "line-to-switch off"); $('#progress-show').attr("class", "progress-show-error"); } $('#progress-bar').attr("style", "width:"+songPercentDone+"%"); @@ -218,10 +231,85 @@ function parseItems(obj){ localRemoteTimeOffset = date.getTime() - schedulePosixTime; } +function parseSourceStatus(obj){ + var live_div = $("#live_dj_div").find(".line-to-switch") + var master_div = $("#master_dj_div").find(".line-to-switch") + + if(obj.live_dj_source == false){ + live_div.attr("class", "line-to-switch off") + }else{ + live_div.attr("class", "line-to-switch on") + } + + if(obj.master_dj_source == false){ + master_div.attr("class", "line-to-switch off") + }else{ + master_div.attr("class", "line-to-switch on") + } +} + +function parseSwitchStatus(obj){ + + if(obj.live_dj_source == "on" && obj.master_dj_source == "off"){ + live_dj_on_air = true; + }else{ + live_dj_on_air = false; + } + + if(obj.master_dj_source == "on"){ + master_dj_on_air = true; + }else{ + master_dj_on_air = false; + } + + if(obj.scheduled_play == "on"){ + scheduled_play_on_air = true; + }else{ + scheduled_play_on_air = false; + } + + $("#scheduled_play.source-switch-button").find("span").html(obj.scheduled_play) + $("#live_dj.source-switch-button").find("span").html(obj.live_dj_source) + $("#master_dj.source-switch-button").find("span").html(obj.master_dj_source) +} + +function controlOnAirLight(){ + if((scheduled_play_on_air && scheduled_play_source)|| live_dj_on_air || master_dj_on_air){ + $('#on-air-info').attr("class", "on-air-info on"); + }else{ + $('#on-air-info').attr("class", "on-air-info off"); + } +} + +function controlSwitchLight(){ + var live_div = $("#live_dj_div") + var master_div = $("#master_dj_div") + var scheduled_play_div = $("#scheduled_play_div") + + if((scheduled_play_on_air && scheduled_play_source) && !live_dj_on_air && !master_dj_on_air){ + scheduled_play_div.find(".line-to-on-air").attr("class", "line-to-on-air on") + live_div.find(".line-to-on-air").attr("class", "line-to-on-air off") + master_div.find(".line-to-on-air").attr("class", "line-to-on-air off") + }else if(live_dj_on_air && !master_dj_on_air){ + scheduled_play_div.find(".line-to-on-air").attr("class", "line-to-on-air off") + live_div.find(".line-to-on-air").attr("class", "line-to-on-air on") + master_div.find(".line-to-on-air").attr("class", "line-to-on-air off") + }else if(master_dj_on_air){ + scheduled_play_div.find(".line-to-on-air").attr("class", "line-to-on-air off") + live_div.find(".line-to-on-air").attr("class", "line-to-on-air off") + master_div.find(".line-to-on-air").attr("class", "line-to-on-air on") + }else{ + scheduled_play_div.find(".line-to-on-air").attr("class", "line-to-on-air off") + live_div.find(".line-to-on-air").attr("class", "line-to-on-air off") + master_div.find(".line-to-on-air").attr("class", "line-to-on-air off") + } +} function getScheduleFromServer(){ $.ajax({ url: "/Schedule/get-current-playlist/format/json", dataType:"json", success:function(data){ parseItems(data.entries); + parseSourceStatus(data.source_status); + parseSwitchStatus(data.switch_status); }, error:function(jqXHR, textStatus, errorThrown){}}); setTimeout(getScheduleFromServer, serverUpdateInterval); } @@ -251,6 +339,31 @@ function setupQtip(){ } } +function setSwitchListener(ele){ + var sourcename = $(ele).attr('id') + var status_span = $(ele).find("span") + var status = status_span.html() + var source_connection_status = false + + $.get("/Dashboard/switch-source/format/json/sourcename/"+sourcename+"/status/"+status, function(data){ + if(data.error){ + alert(data.error); + }else{ + status_span.html(data.status) + } + }); +} + +function kickSource(ele){ + var sourcename = $(ele).attr('id') + + $.get("/Dashboard/disconnect-source/format/json/sourcename/"+sourcename, function(data){ + if(data.error){ + alert(data.error); + } + }); +} + var stream_window = null; function init() { diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index 78aa08b13..33c9dab7c 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -154,7 +154,7 @@ var AIRTIME = (function(AIRTIME) { $.ajax( { "dataType": 'json', - "type": "GET", + "type": "POST", "url": sSource, "data": aoData, "success": fnCallback @@ -329,6 +329,8 @@ var AIRTIME = (function(AIRTIME) { addQtipToSCIcons(); + + //begin context menu initialization. $.contextMenu({ selector: '#library_display td:not(.library_checkbox)', trigger: "left", diff --git a/airtime_mvc/public/js/airtime/library/spl.js b/airtime_mvc/public/js/airtime/library/spl.js index 90e8d603f..982d0d79a 100644 --- a/airtime_mvc/public/js/airtime/library/spl.js +++ b/airtime_mvc/public/js/airtime/library/spl.js @@ -5,6 +5,7 @@ var AIRTIME = (function(AIRTIME){ AIRTIME.playlist = {}; + var mod = AIRTIME.playlist; function isTimeValid(time) { @@ -287,7 +288,7 @@ var AIRTIME = (function(AIRTIME){ } function setPlaylistContent(json) { - + $('#spl_name > a') .empty() .append(json.name); @@ -302,7 +303,6 @@ var AIRTIME = (function(AIRTIME){ .append(json.html); setModified(json.modified); - redrawLib(); } @@ -634,49 +634,84 @@ var AIRTIME = (function(AIRTIME){ }); }; - mod.fnAddItems = function(aItems, iAfter, sAddType) { - var lastMod = getModified(); + function playlistRequest(sUrl, oData) { + var lastMod = getModified(), + plContent = $("#side_playlist"), + offset = plContent.offset(), + plHeight = plContent.height(), + plWidth = plContent.width(), + overlay, + loading; - $.post("/playlist/add-items", - {format: "json", "ids": aItems, "afterItem": iAfter, "type": sAddType, "modified": lastMod}, - function(json){ + oData["modified"] = lastMod; + oData["format"] = "json"; + + + overlay = $("
    ", { + "class": "pl-overlay ui-widget-content", + "css": { + "position": "absolute", + "top": offset.top, + "left": offset.left, + "height": plHeight + 16, + "width": plWidth + 16 + } + }).click(function(){ + return false; + }); + + loading = $("
    ", { + "class": "pl-loading", + "css": { + "position": "absolute", + "top": offset.top + plHeight/2 - 32 - 8, + "left": offset.left + plWidth/2 - 32 -8, + "height": 64, + "width": 64 + } + }); + + $("body") + .append(overlay) + .append(loading); + + $.post( + sUrl, + oData, + function(json){ + if (json.error !== undefined) { playlistError(json); } else { setPlaylistContent(json); } - }); + + loading.remove(); + overlay.remove(); + } + ); + } + + mod.fnAddItems = function(aItems, iAfter, sAddType) { + var sUrl = "/playlist/add-items"; + oData = {"ids": aItems, "afterItem": iAfter, "type": sAddType}; + + playlistRequest(sUrl, oData); }; mod.fnMoveItems = function(aIds, iAfter) { - var lastMod = getModified(); + var sUrl = "/playlist/move-items", + oData = {"ids": aIds, "afterItem": iAfter}; - $.post("/playlist/move-items", - {format: "json", "ids": aIds, "afterItem": iAfter, "modified": lastMod}, - function(json){ - if (json.error !== undefined) { - playlistError(json); - } - else { - setPlaylistContent(json); - } - }); + playlistRequest(sUrl, oData); }; mod.fnDeleteItems = function(aItems) { - var lastMod = getModified(); + var sUrl = "/playlist/delete-items", + oData = {"ids": aItems}; - $.post("/playlist/delete-items", - {format: "json", "ids": aItems, "modified": lastMod}, - function(json){ - if (json.error !== undefined) { - playlistError(json); - } - else { - setPlaylistContent(json); - } - }); + playlistRequest(sUrl, oData); }; mod.init = function() { @@ -717,4 +752,24 @@ $(document).ready(function() { AIRTIME.library.libraryInit(); AIRTIME.playlist.init(); + + pl.find(".ui-icon-alert").qtip({ + content: { + text: "File does not exist on disk..." + }, + position:{ + adjust: { + resize: true, + method: "flip flip" + }, + at: "right center", + my: "left top", + viewport: $(window) + }, + style: { + classes: "ui-tooltip-dark" + }, + show: 'mouseover', + hide: 'mouseout' + }); }); diff --git a/airtime_mvc/public/js/airtime/preferences/preferences.js b/airtime_mvc/public/js/airtime/preferences/preferences.js index eb6b7a935..6c760d939 100644 --- a/airtime_mvc/public/js/airtime/preferences/preferences.js +++ b/airtime_mvc/public/js/airtime/preferences/preferences.js @@ -2,7 +2,12 @@ function showErrorSections() { if($("#soundcloud-settings .errors").length > 0) { $("#soundcloud-settings").show(); - $(window).scrollTop($("soundcloud-settings .errors").position().top); + $(window).scrollTop($("#soundcloud-settings .errors").position().top); + } + + if($("#livestream-settings .errors").length > 0) { + $("#livestream-settings").show(); + $(window).scrollTop($("#livestream-settings .errors").position().top); } } diff --git a/airtime_mvc/public/js/airtime/preferences/streamsetting.js b/airtime_mvc/public/js/airtime/preferences/streamsetting.js index a88d27ace..4dcb8476e 100644 --- a/airtime_mvc/public/js/airtime/preferences/streamsetting.js +++ b/airtime_mvc/public/js/airtime/preferences/streamsetting.js @@ -106,6 +106,52 @@ function checkLiquidsoapStatus(){ }); } +function setLiveSourceConnectionOverrideListener(){ + $("[id=connection_url_override]").click(function(){ + var div_ele = $(this).parent().find("div[id$='_dj_connection_url_tb']") + div_ele.find(":input").val("") + div_ele.show() + }) + + // set action for "OK" and "X" + var live_dj_input = $("#live_dj_connection_url_tb") + var live_dj_label = live_dj_input.parent().find("span") + var master_dj_input = $("#master_dj_connection_url_tb") + var master_dj_label = master_dj_input.parent().find("span") + + live_dj_input.find("#ok").click(function(){ + var url = $(this).parent().find(":input").val() + live_dj_label.html(url) + live_dj_input.hide() + $.get("/Preference/set-source-connection-url/", {format: "json", type: "livedj", url:encodeURIComponent(url)}); + }) + + live_dj_input.find("#reset").click(function(){ + var port = $("#dj_harbor_input_port").val() + var mount = $("#dj_harbor_input_mount_point").val() + var url = "http://"+location.hostname+":"+port+"/"+mount + live_dj_label.html(url) + live_dj_input.hide() + $.get("/Preference/set-source-connection-url", {format: "json", type: "livedj", url:encodeURIComponent(url)}); + }) + + master_dj_input.find("#ok").click(function(){ + var url = $(this).parent().find(":input").val() + master_dj_label.html(url) + master_dj_input.hide() + $.get("/Preference/set-source-connection-url", {format: "json", type: "masterdj", url:encodeURIComponent(url)}) + }) + + master_dj_input.find("#reset").click(function(){ + var port = $("#master_harbor_input_port").val() + var mount = $("#master_harbor_input_mount_point").val() + var url = "http://"+location.hostname+":"+port+"/"+mount + master_dj_label.html(url) + master_dj_input.hide() + $.get("/Preference/set-source-connection-url", {format: "json", type: "masterdj", url:encodeURIComponent(url)}) + }) +} + $(document).ready(function() { // initial stream url @@ -178,6 +224,8 @@ $(document).ready(function() { return false; }) + setLiveSourceConnectionOverrideListener() + showErrorSections() setInterval('checkLiquidsoapStatus()', 1000) $.mask.rules = { diff --git a/airtime_mvc/public/js/airtime/showbuilder/builder.js b/airtime_mvc/public/js/airtime/showbuilder/builder.js index 93616d52a..d0839d14b 100644 --- a/airtime_mvc/public/js/airtime/showbuilder/builder.js +++ b/airtime_mvc/public/js/airtime/showbuilder/builder.js @@ -1,592 +1,638 @@ var AIRTIME = (function(AIRTIME){ -var mod, - oSchedTable, - fnServerData; + var mod, + oSchedTable, + fnServerData; + + if (AIRTIME.showbuilder === undefined) { + AIRTIME.showbuilder = {}; + } + mod = AIRTIME.showbuilder; + + function checkError(json) { + if (json.error !== undefined) { + alert(json.error); + } + } + + mod.timeout = undefined; + + mod.resetTimestamp = function() { + var timestamp = $("#sb_timestamp"); + //reset timestamp value since input values could have changed. + timestamp.val(-1); + }; + + mod.setTimestamp = function(timestamp) { + $("#sb_timestamp").val(timestamp); + }; + + mod.getTimestamp = function() { + var timestamp = $("#sb_timestamp"), + val; + + //if the timestamp field is on the page return it, or give the default of -1 + //to ensure a page refresh. + if (timestamp.length === 1) { + val = timestamp.val(); + } + else { + val = -1; + } + + return val; + }; + + mod.fnAdd = function(aMediaIds, aSchedIds) { + var oLibTT = TableTools.fnGetInstance('library_display'); + + $.post("/showbuilder/schedule-add", + {"format": "json", "mediaIds": aMediaIds, "schedIds": aSchedIds}, + function(json){ + checkError(json); + oSchedTable.fnDraw(); + oLibTT.fnSelectNone(); + }); + }; + + mod.fnMove = function(aSelect, aAfter) { + + $.post("/showbuilder/schedule-move", + {"format": "json", "selectedItem": aSelect, "afterItem": aAfter}, + function(json){ + checkError(json); + oSchedTable.fnDraw(); + }); + }; + + mod.fnRemove = function(aItems) { + + $.post( "/showbuilder/schedule-remove", + {"items": aItems, "format": "json"}, + function(json) { + checkError(json); + oSchedTable.fnDraw(); + }); + }; + + fnServerData = function ( sSource, aoData, fnCallback ) { + + aoData.push( { name: "timestamp", value: AIRTIME.showbuilder.getTimestamp()} ); + aoData.push( { name: "format", value: "json"} ); + + if (fnServerData.hasOwnProperty("start")) { + aoData.push( { name: "start", value: fnServerData.start} ); + } + if (fnServerData.hasOwnProperty("end")) { + aoData.push( { name: "end", value: fnServerData.end} ); + } + if (fnServerData.hasOwnProperty("ops")) { + aoData.push( { name: "myShows", value: fnServerData.ops.myShows} ); + aoData.push( { name: "showFilter", value: fnServerData.ops.showFilter} ); + } + + $.ajax( { + "dataType": "json", + "type": "POST", + "url": sSource, + "data": aoData, + "success": function(json) { + AIRTIME.showbuilder.setTimestamp(json.timestamp); + fnCallback(json); + } + } ); + }; + + mod.fnServerData = fnServerData; + + mod.builderDataTable = function() { + var tableDiv = $('#show_builder_table'), + oTable, + fnRemoveSelectedItems, + tableHeight; -if (AIRTIME.showbuilder === undefined) { - AIRTIME.showbuilder = {}; -} -mod = AIRTIME.showbuilder; + fnRemoveSelectedItems = function() { + var oTT = TableTools.fnGetInstance('show_builder_table'), + aData = oTT.fnGetSelectedData(), + i, + length, + temp, + aItems = []; + + for (i=0, length = aData.length; i < length; i++) { + temp = aData[i]; + aItems.push({"id": temp.id, "instance": temp.instance, "timestamp": temp.timestamp}); + } + + AIRTIME.showbuilder.fnRemove(aItems); + }; + + oTable = tableDiv.dataTable( { + "aoColumns": [ + /* checkbox */ {"mDataProp": "allowed", "sTitle": "", "sWidth": "15px", "sClass": "sb_checkbox"}, + /* Type */ {"mDataProp": "image", "sTitle": "", "sClass": "library_image", "sWidth": "25px", "bVisible": true}, + /* starts */{"mDataProp": "starts", "sTitle": "Start"}, + /* ends */{"mDataProp": "ends", "sTitle": "End"}, + /* runtime */{"mDataProp": "runtime", "sTitle": "Duration", "sClass": "library_length"}, + /* title */{"mDataProp": "title", "sTitle": "Title"}, + /* creator */{"mDataProp": "creator", "sTitle": "Creator"}, + /* album */{"mDataProp": "album", "sTitle": "Album"}, + /* cue in */{"mDataProp": "cuein", "sTitle": "Cue In", "bVisible": false}, + /* cue out */{"mDataProp": "cueout", "sTitle": "Cue Out", "bVisible": false}, + /* fade in */{"mDataProp": "fadein", "sTitle": "Fade In", "bVisible": false}, + /* fade out */{"mDataProp": "fadeout", "sTitle": "Fade Out", "bVisible": false} + ], + + "bJQueryUI": true, + "bSort": false, + "bFilter": false, + "bProcessing": true, + "bServerSide": true, + "bInfo": false, + "bAutoWidth": false, + + "bStateSave": true, + "fnStateSaveParams": function (oSettings, oData) { + //remove oData components we don't want to save. + delete oData.oSearch; + delete oData.aoSearchCols; + }, + "fnStateSave": function (oSettings, oData) { + + $.ajax({ + url: "/usersettings/set-timeline-datatable", + type: "POST", + data: {settings : oData, format: "json"}, + dataType: "json", + success: function(){}, + error: function (jqXHR, textStatus, errorThrown) { + var x; + } + }); + }, + "fnStateLoad": function (oSettings) { + var o; -function checkError(json) { - if (json.error !== undefined) { - alert(json.error); - } -} - -mod.timeout = undefined; - -mod.resetTimestamp = function() { - var timestamp = $("#sb_timestamp"); - //reset timestamp value since input values could have changed. - timestamp.val(-1); -}; - -mod.setTimestamp = function(timestamp) { - $("#sb_timestamp").val(timestamp); -}; - -mod.getTimestamp = function() { - var timestamp = $("#sb_timestamp"), - val; - - //if the timestamp field is on the page return it, or give the default of -1 - //to ensure a page refresh. - if (timestamp.length === 1) { - val = timestamp.val(); - } - else { - val = -1; - } - - return val; -}; - -mod.fnAdd = function(aMediaIds, aSchedIds) { - var oLibTT = TableTools.fnGetInstance('library_display'); - - $.post("/showbuilder/schedule-add", - {"format": "json", "mediaIds": aMediaIds, "schedIds": aSchedIds}, - function(json){ - checkError(json); - oSchedTable.fnDraw(); - oLibTT.fnSelectNone(); - }); -}; - -mod.fnMove = function(aSelect, aAfter) { - - $.post("/showbuilder/schedule-move", - {"format": "json", "selectedItem": aSelect, "afterItem": aAfter}, - function(json){ - checkError(json); - oSchedTable.fnDraw(); - }); -}; - -mod.fnRemove = function(aItems) { - - $.post( "/showbuilder/schedule-remove", - {"items": aItems, "format": "json"}, - function(json) { - checkError(json); - oSchedTable.fnDraw(); - }); -}; - -fnServerData = function ( sSource, aoData, fnCallback ) { - - aoData.push( { name: "timestamp", value: AIRTIME.showbuilder.getTimestamp()} ); - aoData.push( { name: "format", value: "json"} ); - - if (fnServerData.hasOwnProperty("start")) { - aoData.push( { name: "start", value: fnServerData.start} ); - } - if (fnServerData.hasOwnProperty("end")) { - aoData.push( { name: "end", value: fnServerData.end} ); - } - if (fnServerData.hasOwnProperty("ops")) { - aoData.push( { name: "myShows", value: fnServerData.ops.myShows} ); - aoData.push( { name: "showFilter", value: fnServerData.ops.showFilter} ); - } - - $.ajax( { - "dataType": "json", - "type": "GET", - "url": sSource, - "data": aoData, - "success": function(json) { - AIRTIME.showbuilder.setTimestamp(json.timestamp); - fnCallback(json); - } - } ); -}; - -mod.fnServerData = fnServerData; - -mod.builderDataTable = function() { - var tableDiv = $('#show_builder_table'), - oTable, - fnRemoveSelectedItems, - tableHeight; - - fnRemoveSelectedItems = function() { - var oTT = TableTools.fnGetInstance('show_builder_table'), - aData = oTT.fnGetSelectedData(), - i, - length, - temp, - aItems = []; - - for (i=0, length = aData.length; i < length; i++) { - temp = aData[i]; - aItems.push({"id": temp.id, "instance": temp.instance, "timestamp": temp.timestamp}); - } - - AIRTIME.showbuilder.fnRemove(aItems); - }; - - oTable = tableDiv.dataTable( { - "aoColumns": [ - /* checkbox */ {"mDataProp": "allowed", "sTitle": "", "sWidth": "15px"}, - /* Type */ {"mDataProp": "image", "sTitle": "", "sClass": "library_image", "sWidth": "25px", "bVisible": true}, - /* starts */{"mDataProp": "starts", "sTitle": "Start"}, - /* ends */{"mDataProp": "ends", "sTitle": "End"}, - /* runtime */{"mDataProp": "runtime", "sTitle": "Duration", "sClass": "library_length"}, - /* title */{"mDataProp": "title", "sTitle": "Title"}, - /* creator */{"mDataProp": "creator", "sTitle": "Creator"}, - /* album */{"mDataProp": "album", "sTitle": "Album"}, - /* cue in */{"mDataProp": "cuein", "sTitle": "Cue In", "bVisible": false}, - /* cue out */{"mDataProp": "cueout", "sTitle": "Cue Out", "bVisible": false}, - /* fade in */{"mDataProp": "fadein", "sTitle": "Fade In", "bVisible": false}, - /* fade out */{"mDataProp": "fadeout", "sTitle": "Fade Out", "bVisible": false} - ], - - "bJQueryUI": true, - "bSort": false, - "bFilter": false, - "bProcessing": true, - "bServerSide": true, - "bInfo": false, - "bAutoWidth": false, - - "bStateSave": true, - "fnStateSaveParams": function (oSettings, oData) { - //remove oData components we don't want to save. - delete oData.oSearch; - delete oData.aoSearchCols; - }, - "fnStateSave": function (oSettings, oData) { - - $.ajax({ - url: "/usersettings/set-timeline-datatable", - type: "POST", - data: {settings : oData, format: "json"}, - dataType: "json", - success: function(){}, - error: function (jqXHR, textStatus, errorThrown) { - var x; - } - }); - }, - "fnStateLoad": function (oSettings) { - var o; - - $.ajax({ - url: "/usersettings/get-timeline-datatable", - type: "GET", - data: {format: "json"}, - dataType: "json", - async: false, - success: function(json){ - o = json.settings; - }, - error: function (jqXHR, textStatus, errorThrown) { - var x; - } - }); - - return o; - }, - "fnStateLoadParams": function (oSettings, oData) { - var i, - length, - a = oData.abVisCols; - - //putting serialized data back into the correct js type to make - //sure everything works properly. - for (i = 0, length = a.length; i < length; i++) { - a[i] = (a[i] === "true") ? true : false; - } - - a = oData.ColReorder; - for (i = 0, length = a.length; i < length; i++) { - a[i] = parseInt(a[i], 10); - } - - oData.iCreate = parseInt(oData.iCreate, 10); - }, - - "fnServerData": AIRTIME.showbuilder.fnServerData, - "fnRowCallback": function ( nRow, aData, iDisplayIndex, iDisplayIndexFull ) { - var i, - sSeparatorHTML, - fnPrepareSeparatorRow, - node, - cl=""; - - - - //save some info for reordering purposes. - $(nRow).data({"aData": aData}); - - if (aData.current === true) { - $(nRow).addClass("sb-now-playing"); - } - - if (aData.allowed !== true) { - $(nRow).addClass("sb-not-allowed"); - } - else { - $(nRow).addClass("sb-allowed"); - } - - //status used to colour tracks. - if (aData.status === 2) { - $(nRow).addClass("sb-boundry"); - } - else if (aData.status === 0) { - $(nRow).addClass("sb-over"); - } - - fnPrepareSeparatorRow = function(sRowContent, sClass, iNodeIndex) { - - node = nRow.children[iNodeIndex]; - node.innerHTML = sRowContent; - node.setAttribute('colspan',100); - for (i = iNodeIndex + 1; i < nRow.children.length; i = i+1) { - node = nRow.children[i]; - node.innerHTML = ""; - node.setAttribute("style", "display : none"); - } - - $(nRow).addClass(sClass); - }; - - //add the play function to the library_type td or the speaker - $(nRow).find('td.library_image').click(function(){ - open_show_preview(aData.instance, iDisplayIndex); - return false; - }); - - - if (aData.header === true) { - cl = 'sb-header'; - - sSeparatorHTML = ''+aData.title+''+aData.starts+''+aData.ends+''; - fnPrepareSeparatorRow(sSeparatorHTML, cl, 0); - - //add the play function to the show row...could be confusing so leave it out for now - //$(nRow).click(function(){ - // open_show_preview(aData.instance); - // return false; - //}); - } - else if (aData.footer === true) { - node = nRow.children[0]; - cl = 'sb-footer'; - - //check the show's content status. - if (aData.runtime > 0) { - node.innerHTML = ''; - cl = cl + ' ui-state-highlight'; - } - else { - node.innerHTML = ''; - cl = cl + ' ui-state-error'; - } + $.ajax({ + url: "/usersettings/get-timeline-datatable", + type: "GET", + data: {format: "json"}, + dataType: "json", + async: false, + success: function(json){ + o = json.settings; + }, + error: function (jqXHR, textStatus, errorThrown) { + var x; + } + }); + + return o; + }, + "fnStateLoadParams": function (oSettings, oData) { + var i, + length, + a = oData.abVisCols; + + //putting serialized data back into the correct js type to make + //sure everything works properly. + for (i = 0, length = a.length; i < length; i++) { + a[i] = (a[i] === "true") ? true : false; + } + + a = oData.ColReorder; + for (i = 0, length = a.length; i < length; i++) { + a[i] = parseInt(a[i], 10); + } + + oData.iCreate = parseInt(oData.iCreate, 10); + }, + + "fnServerData": AIRTIME.showbuilder.fnServerData, + "fnRowCallback": function ( nRow, aData, iDisplayIndex, iDisplayIndexFull ) { + var i, + sSeparatorHTML, + fnPrepareSeparatorRow, + node, + cl=""; + + //call the context menu so we can prevent the event from propagating. + $(nRow).find('td:not(.sb_checkbox)').click(function(e){ - sSeparatorHTML = ''+aData.fRuntime+''; - fnPrepareSeparatorRow(sSeparatorHTML, cl, 1); - } - else if (aData.empty === true) { - - sSeparatorHTML = 'Show Empty'; - cl = cl + " sb-empty odd"; - - fnPrepareSeparatorRow(sSeparatorHTML, cl, 0); - } - else if (aData.record === true) { - - sSeparatorHTML = 'Recording From Line In'; - cl = cl + " sb-record odd"; - - fnPrepareSeparatorRow(sSeparatorHTML, cl, 0); - } - else { - - node = nRow.children[0]; - if (aData.allowed === true) { - node.innerHTML = ''; - } - else { - node.innerHTML = ''; - } - } - }, - "fnDrawCallback": function(oSettings, json) { - var wrapperDiv, - markerDiv, - td, - $lib = $("#library_content"), - tr, - oTable = $('#show_builder_table').dataTable(), - aData; - - clearTimeout(AIRTIME.showbuilder.timeout); - - //only create the cursor arrows if the library is on the page. - if ($lib.length > 0 && $lib.filter(":visible").length > 0) { - - //create cursor arrows. - tableDiv.find("tr.sb-now-playing, tr:not(:first, .sb-footer, .sb-empty, .sb-not-allowed)").each(function(i, el) { - td = $(el).find("td:first"); - if (td.hasClass("dataTables_empty")) { - return false; - } + $(this).contextMenu({x: e.pageX, y: e.pageY}); - wrapperDiv = $("
    ", { - "class": "innerWrapper", - "css": { - "height": td.height() - } - }); - markerDiv = $("
    ", { - "class": "marker" - }); - - td.append(markerDiv).wrapInner(wrapperDiv); - }); - } - - //if the now playing song is visible set a timeout to redraw the table at the start of the next song. - tr = tableDiv.find("tr.sb-now-playing"); - - if (tr.length > 0) { - aData = tr.data("aData"); - - setTimeout(function(){ - AIRTIME.showbuilder.resetTimestamp(); - oTable.fnDraw(); - }, aData.refresh * 1000); //need refresh in milliseconds - } - //current song is not set, set a timeout to refresh when the first item on the timeline starts. - else { - tr = tableDiv.find("tbody tr.sb-allowed.sb-header:first"); - - if (tr.length > 0) { - aData = tr.data("aData"); - - AIRTIME.showbuilder.timeout = setTimeout(function(){ - AIRTIME.showbuilder.resetTimestamp(); - oTable.fnDraw(); - }, aData.timeUntil * 1000); //need refresh in milliseconds - } - } - }, - "fnHeaderCallback": function(nHead) { - $(nHead).find("input[type=checkbox]").attr("checked", false); - }, - //remove any selected nodes before the draw. - "fnPreDrawCallback": function( oSettings ) { - var oTT; - - oTT = TableTools.fnGetInstance('show_builder_table'); - oTT.fnSelectNone(); - }, - - "oColVis": { - "aiExclude": [ 0, 1 ] - }, - - "oColReorder": { - "iFixedColumns": 2 - }, - - "oTableTools": { - "sRowSelect": "multi", - "aButtons": [], - "fnPreRowSelect": function ( e ) { - var node = e.currentTarget; - //don't select separating rows, or shows without privileges. - if ($(node).hasClass("sb-header") - || $(node).hasClass("sb-footer") - || $(node).hasClass("sb-empty") - || $(node).hasClass("sb-not-allowed")) { return false; - } - return true; - }, - "fnRowSelected": function ( node ) { + }); + + //save some info for reordering purposes. + $(nRow).data({"aData": aData}); + + if (aData.current === true) { + $(nRow).addClass("sb-now-playing"); + } + + if (aData.allowed !== true) { + $(nRow).addClass("sb-not-allowed"); + } + else { + $(nRow).addClass("sb-allowed"); + } + + //status used to colour tracks. + if (aData.status === 2) { + $(nRow).addClass("sb-boundry"); + } + else if (aData.status === 0) { + $(nRow).addClass("sb-over"); + } + + fnPrepareSeparatorRow = function(sRowContent, sClass, iNodeIndex) { + + node = nRow.children[iNodeIndex]; + node.innerHTML = sRowContent; + node.setAttribute('colspan',100); + for (i = iNodeIndex + 1; i < nRow.children.length; i = i+1) { + node = nRow.children[i]; + node.innerHTML = ""; + node.setAttribute("style", "display : none"); + } + + $(nRow).addClass(sClass); + }; + + //add the play function to the library_type td or the speaker + $(nRow).find('td.library_image').click(function(){ + open_show_preview(aData.instance, iDisplayIndex); + return false; + }); + + if (aData.header === true) { + cl = 'sb-header'; + + sSeparatorHTML = ''+aData.title+''+aData.starts+''+aData.ends+''; + fnPrepareSeparatorRow(sSeparatorHTML, cl, 0); + } + else if (aData.footer === true) { + node = nRow.children[0]; + cl = 'sb-footer'; + + //check the show's content status. + if (aData.runtime > 0) { + node.innerHTML = ''; + cl = cl + ' ui-state-highlight'; + } + else { + node.innerHTML = ''; + cl = cl + ' ui-state-error'; + } + + sSeparatorHTML = ''+aData.fRuntime+''; + fnPrepareSeparatorRow(sSeparatorHTML, cl, 1); + } + else if (aData.empty === true) { + + sSeparatorHTML = 'Show Empty'; + cl = cl + " sb-empty odd"; + + fnPrepareSeparatorRow(sSeparatorHTML, cl, 0); + } + else if (aData.record === true) { + + sSeparatorHTML = 'Recording From Line In'; + cl = cl + " sb-record odd"; + + fnPrepareSeparatorRow(sSeparatorHTML, cl, 0); + } + else { + + node = nRow.children[0]; + if (aData.allowed === true) { + node.innerHTML = ''; + } + else { + node.innerHTML = ''; + } + } + }, + "fnDrawCallback": function(oSettings, json) { + var wrapperDiv, + markerDiv, + td, + $lib = $("#library_content"), + tr, + oTable = $('#show_builder_table').dataTable(), + aData; + + clearTimeout(AIRTIME.showbuilder.timeout); + + //only create the cursor arrows if the library is on the page. + if ($lib.length > 0 && $lib.filter(":visible").length > 0) { + + //create cursor arrows. + tableDiv.find("tr.sb-now-playing, tr:not(:first, .sb-footer, .sb-empty, .sb-not-allowed)").each(function(i, el) { + td = $(el).find("td:first"); + if (td.hasClass("dataTables_empty")) { + return false; + } + + wrapperDiv = $("
    ", { + "class": "innerWrapper", + "css": { + "height": td.height() + } + }); + markerDiv = $("
    ", { + "class": "marker" + }); + + td.append(markerDiv).wrapInner(wrapperDiv); + }); + } + + //if the now playing song is visible set a timeout to redraw the table at the start of the next song. + tr = tableDiv.find("tr.sb-now-playing"); + + if (tr.length > 0) { + aData = tr.data("aData"); + + setTimeout(function(){ + AIRTIME.showbuilder.resetTimestamp(); + oTable.fnDraw(); + }, aData.refresh * 1000); //need refresh in milliseconds + } + //current song is not set, set a timeout to refresh when the first item on the timeline starts. + else { + tr = tableDiv.find("tbody tr.sb-allowed.sb-header:first"); + + if (tr.length > 0) { + aData = tr.data("aData"); + + AIRTIME.showbuilder.timeout = setTimeout(function(){ + AIRTIME.showbuilder.resetTimestamp(); + oTable.fnDraw(); + }, aData.timeUntil * 1000); //need refresh in milliseconds + } + } + }, + "fnHeaderCallback": function(nHead) { + $(nHead).find("input[type=checkbox]").attr("checked", false); + }, + //remove any selected nodes before the draw. + "fnPreDrawCallback": function( oSettings ) { + var oTT; + + oTT = TableTools.fnGetInstance('show_builder_table'); + oTT.fnSelectNone(); + }, + + "oColVis": { + "aiExclude": [ 0, 1 ] + }, + + "oColReorder": { + "iFixedColumns": 2 + }, + + "oTableTools": { + "sRowSelect": "multi", + "aButtons": [], + "fnPreRowSelect": function ( e ) { + var node = e.currentTarget; + //don't select separating rows, or shows without privileges. + if ($(node).hasClass("sb-header") + || $(node).hasClass("sb-footer") + || $(node).hasClass("sb-empty") + || $(node).hasClass("sb-not-allowed")) { + return false; + } + return true; + }, + "fnRowSelected": function ( node ) { - //seems to happen if everything is selected - if ( node === null) { - oTable.find("input[type=checkbox]").attr("checked", true); - } - else { - $(node).find("input[type=checkbox]").attr("checked", true); + //seems to happen if everything is selected + if ( node === null) { + oTable.find("input[type=checkbox]").attr("checked", true); + } + else { + $(node).find("input[type=checkbox]").attr("checked", true); + } + + //checking to enable buttons + AIRTIME.button.enableButton("sb_delete"); + }, + "fnRowDeselected": function ( node ) { + var selected; + + //seems to happen if everything is deselected + if ( node === null) { + tableDiv.find("input[type=checkbox]").attr("checked", false); + selected = []; + } + else { + $(node).find("input[type=checkbox]").attr("checked", false); + selected = tableDiv.find("input[type=checkbox]").filter(":checked"); + } + + //checking to disable buttons + if (selected.length === 0) { + AIRTIME.button.disableButton("sb_delete"); + } + } + }, + + // R = ColReorderResize, C = ColVis, T = TableTools + "sDom": 'Rr<"H"CT>t', + + "sAjaxDataProp": "schedule", + "sAjaxSource": "/showbuilder/builder-feed" + }); + + $('[name="sb_cb_all"]').click(function(){ + var oTT = TableTools.fnGetInstance('show_builder_table'); + + if ($(this).is(":checked")) { + var allowedNodes; + + allowedNodes = oTable.find('tr:not(:first, .sb-header, .sb-empty, .sb-footer, .sb-not-allowed)'); + + allowedNodes.each(function(i, el){ + oTT.fnSelect(el); + }); + } + else { + oTT.fnSelectNone(); + } + }); + + var sortableConf = (function(){ + var origTrs, + aItemData = [], + oPrevData, + fnAdd, + fnMove, + fnReceive, + fnUpdate, + i, + html; + + fnAdd = function() { + var aMediaIds = [], + aSchedIds = []; + + for(i = 0; i < aItemData.length; i++) { + aMediaIds.push({"id": aItemData[i].id, "type": aItemData[i].ftype}); + } + aSchedIds.push({"id": oPrevData.id, "instance": oPrevData.instance, "timestamp": oPrevData.timestamp}); + + AIRTIME.showbuilder.fnAdd(aMediaIds, aSchedIds); + }; + + fnMove = function() { + var aSelect = [], + aAfter = []; + + aSelect.push({"id": aItemData[0].id, "instance": aItemData[0].instance, "timestamp": aItemData[0].timestamp}); + aAfter.push({"id": oPrevData.id, "instance": oPrevData.instance, "timestamp": oPrevData.timestamp}); + + AIRTIME.showbuilder.fnMove(aSelect, aAfter); + }; + + fnReceive = function(event, ui) { + var aItems = [], + oLibTT = TableTools.fnGetInstance('library_display'); + + aItems = oLibTT.fnGetSelectedData(); + + //if nothing is checked select the dragged item. + if (aItems.length === 0) { + aItems.push(ui.item.data("aData")); + } + + origTrs = aItems; + html = ui.helper.html(); + }; + + fnUpdate = function(event, ui) { + var prev = ui.item.prev(); + + //can't add items outside of shows. + if (prev.hasClass("sb-footer") + || prev.find("td:first").hasClass("dataTables_empty") + || prev.length === 0) { + alert("Cannot schedule outside a show."); + ui.item.remove(); + return; + } + + aItemData = []; + oPrevData = prev.data("aData"); + + //item was dragged in + if (origTrs !== undefined) { + + $("#show_builder_table tr.ui-draggable") + .empty() + .after(html); + + aItemData = origTrs; + origTrs = undefined; + fnAdd(); + } + //item was reordered. + else { + aItemData.push(ui.item.data("aData")); + fnMove(); + } + }; + + return { + placeholder: "placeholder show-builder-placeholder ui-state-highlight", + forcePlaceholderSize: true, + items: 'tr:not(:first, :last, .sb-header, .sb-footer, .sb-not-allowed)', + receive: fnReceive, + update: fnUpdate + }; + }()); + + tableDiv.sortable(sortableConf); + + $("#show_builder .fg-toolbar") + .append('
    ') + .click(fnRemoveSelectedItems); + + //set things like a reference to the table. + AIRTIME.showbuilder.init(oTable); + + //add event to cursors. + tableDiv.find("tbody").on("click", "div.marker", function(event){ + var tr = $(this).parents("tr"), + cursorSelClass = "cursor-selected-row"; + + if (tr.hasClass(cursorSelClass)) { + tr.removeClass(cursorSelClass); + } + else { + tr.addClass(cursorSelClass); + } + + //check if add button can still be enabled. + AIRTIME.library.events.enableAddButtonCheck(); + + return false; + }); + + //begin context menu initialization. + $.contextMenu({ + selector: '#show_builder_table td:not(.sb_checkbox)', + trigger: "left", + ignoreRightClick: true, + + build: function($el, e) { + var data, items, callback, $tr; + + $tr = $el.parent(); + data = $tr.data("aData"); + + function processMenuItems(oItems) { + + //define a delete callback. + if (oItems.del !== undefined) { + + callback = function() { + AIRTIME.showbuilder.fnRemove([{ + id: data.id, + timestamp: data.timestamp, + instance: data.instance + }]); + }; + + oItems.del.callback = callback; + } + + items = oItems; } - //checking to enable buttons - AIRTIME.button.enableButton("sb_delete"); - }, - "fnRowDeselected": function ( node ) { - var selected; - - //seems to happen if everything is deselected - if ( node === null) { - tableDiv.find("input[type=checkbox]").attr("checked", false); - selected = []; - } - else { - $(node).find("input[type=checkbox]").attr("checked", false); - selected = tableDiv.find("input[type=checkbox]").filter(":checked"); - } - - //checking to disable buttons - if (selected.length === 0) { - AIRTIME.button.disableButton("sb_delete"); - } + request = $.ajax({ + url: "/showbuilder/context-menu", + type: "GET", + data: {id : data.id, format: "json"}, + dataType: "json", + async: false, + success: function(json){ + processMenuItems(json.items); + } + }); + + return { + items: items + }; } - }, - - // R = ColReorderResize, C = ColVis, T = TableTools - "sDom": 'Rr<"H"CT>t', - - "sAjaxDataProp": "schedule", - "sAjaxSource": "/showbuilder/builder-feed" - }); - - $('[name="sb_cb_all"]').click(function(){ - var oTT = TableTools.fnGetInstance('show_builder_table'); - - if ($(this).is(":checked")) { - var allowedNodes; - - allowedNodes = oTable.find('tr:not(:first, .sb-header, .sb-empty, .sb-footer, .sb-not-allowed)'); - - allowedNodes.each(function(i, el){ - oTT.fnSelect(el); - }); - } - else { - oTT.fnSelectNone(); - } - }); - - var sortableConf = (function(){ - var origTrs, - aItemData = [], - oPrevData, - fnAdd, - fnMove, - fnReceive, - fnUpdate, - i, - html; - - fnAdd = function() { - var aMediaIds = [], - aSchedIds = []; - - for(i = 0; i < aItemData.length; i++) { - aMediaIds.push({"id": aItemData[i].id, "type": aItemData[i].ftype}); - } - aSchedIds.push({"id": oPrevData.id, "instance": oPrevData.instance, "timestamp": oPrevData.timestamp}); - - AIRTIME.showbuilder.fnAdd(aMediaIds, aSchedIds); - }; - - fnMove = function() { - var aSelect = [], - aAfter = []; - - aSelect.push({"id": aItemData[0].id, "instance": aItemData[0].instance, "timestamp": aItemData[0].timestamp}); - aAfter.push({"id": oPrevData.id, "instance": oPrevData.instance, "timestamp": oPrevData.timestamp}); - - AIRTIME.showbuilder.fnMove(aSelect, aAfter); - }; - - fnReceive = function(event, ui) { - var aItems = [], - oLibTT = TableTools.fnGetInstance('library_display'); - - aItems = oLibTT.fnGetSelectedData(); - - //if nothing is checked select the dragged item. - if (aItems.length === 0) { - aItems.push(ui.item.data("aData")); - } - - origTrs = aItems; - html = ui.helper.html(); - }; - - fnUpdate = function(event, ui) { - var prev = ui.item.prev(); - - //can't add items outside of shows. - if (prev.hasClass("sb-footer") - || prev.find("td:first").hasClass("dataTables_empty") - || prev.length === 0) { - alert("Cannot schedule outside a show."); - ui.item.remove(); - return; - } - - aItemData = []; - oPrevData = prev.data("aData"); - - //item was dragged in - if (origTrs !== undefined) { - - $("#show_builder_table tr.ui-draggable") - .empty() - .after(html); - - aItemData = origTrs; - origTrs = undefined; - fnAdd(); - } - //item was reordered. - else { - aItemData.push(ui.item.data("aData")); - fnMove(); - } - }; - - return { - placeholder: "placeholder show-builder-placeholder ui-state-highlight", - forcePlaceholderSize: true, - items: 'tr:not(:first, :last, .sb-header, .sb-footer, .sb-not-allowed)', - receive: fnReceive, - update: fnUpdate - }; - }()); - - tableDiv.sortable(sortableConf); - - $("#show_builder .fg-toolbar") - .append('
    ') - .click(fnRemoveSelectedItems); - - //set things like a reference to the table. - AIRTIME.showbuilder.init(oTable); - - //add event to cursors. - tableDiv.find("tbody").on("click", "div.marker", function(event){ - var tr = $(this).parents("tr"), - cursorSelClass = "cursor-selected-row"; - - if (tr.hasClass(cursorSelClass)) { - tr.removeClass(cursorSelClass); - } - else { - tr.addClass(cursorSelClass); - } - - //check if add button can still be enabled. - AIRTIME.library.events.enableAddButtonCheck(); - - return false; - }); - -}; - -mod.init = function(oTable) { - oSchedTable = oTable; -}; - -return AIRTIME; - + }); + }; + + mod.init = function(oTable) { + oSchedTable = oTable; + }; + + return AIRTIME; + }(AIRTIME || {})); \ No newline at end of file diff --git a/install_minimal/upgrades/airtime-2.0.1/api_client.cfg.201 b/install_minimal/upgrades/airtime-2.0.1/api_client.cfg.201 index f7c63bee8..7aa4cc883 100644 --- a/install_minimal/upgrades/airtime-2.0.1/api_client.cfg.201 +++ b/install_minimal/upgrades/airtime-2.0.1/api_client.cfg.201 @@ -48,6 +48,12 @@ remove_watched_dir = 'remove-watched-dir/format/json/api_key/%%api_key%%/path/%% # URL to tell Airtime we want to add watched directory set_storage_dir = 'set-storage-dir/format/json/api_key/%%api_key%%/path/%%path%%' +# URL to tell Airtime about file system mount change +update_fs_mount = 'update-file-system-mount/format/json/api_key/%%api_key%%' + +# URL to tell Airtime about file system mount change +handle_watched_dir_missing = 'handle-watched-dir-missing/format/json/api_key/%%api_key%%/dir/%%dir%%' + ############################# ## Config for Recorder ############################# @@ -95,3 +101,4 @@ get_stream_setting = 'get-stream-setting/format/json/api_key/%%api_key%%/' #URL to update liquidsoap status update_liquidsoap_status = 'update-liquidsoap-status/format/json/api_key/%%api_key%%/msg/%%msg%%/stream_id/%%stream_id%%/boot_time/%%boot_time%%' + diff --git a/install_minimal/upgrades/airtime-2.0.2/api_client.cfg.202 b/install_minimal/upgrades/airtime-2.0.2/api_client.cfg.202 index 2d51bb2d2..f7c63bee8 100644 --- a/install_minimal/upgrades/airtime-2.0.2/api_client.cfg.202 +++ b/install_minimal/upgrades/airtime-2.0.2/api_client.cfg.202 @@ -95,23 +95,3 @@ get_stream_setting = 'get-stream-setting/format/json/api_key/%%api_key%%/' #URL to update liquidsoap status update_liquidsoap_status = 'update-liquidsoap-status/format/json/api_key/%%api_key%%/msg/%%msg%%/stream_id/%%stream_id%%/boot_time/%%boot_time%%' -############## -# OBP config # -############## - -# URL to get the version number of the server API -#version_url = 'api/pypo/status/json' - -# Schedule export path. -# %%from%% - starting date/time in the form YYYY-MM-DD-hh-mm -# %%to%% - starting date/time in the form YYYY-MM-DD-hh-mm - -# Update whether an item has been played. -#update_item_url = 'api/pypo/update_shedueled_item/$$item_id%%?played=%%played%%' - -# Update whether an item is currently playing. -#update_start_playing_url = 'api/pypo/mod/medialibrary/?playlist_type=%%playlist_type%%&export_source=%%export_source%%&media_id=%%media_id%%&playlist_id=%%playlist_id%%&transmission_id=%%transmission_id%%' - -# ??? -#generate_range_url = 'api/pypo/generate_range_dp/' - diff --git a/python_apps/api_clients/api_client.cfg b/python_apps/api_clients/api_client.cfg index 1e7e01edc..cc73bd534 100644 --- a/python_apps/api_clients/api_client.cfg +++ b/python_apps/api_clients/api_client.cfg @@ -101,3 +101,11 @@ get_stream_setting = 'get-stream-setting/format/json/api_key/%%api_key%%/' #URL to update liquidsoap status update_liquidsoap_status = 'update-liquidsoap-status/format/json/api_key/%%api_key%%/msg/%%msg%%/stream_id/%%stream_id%%/boot_time/%%boot_time%%' +#URL to check live stream auth +check_live_stream_auth = 'check-live-stream-auth/format/json/api_key/%%api_key%%/username/%%username%%/password/%%password%%/djtype/%%djtype%%' + +#URL to update source status +update_source_status = 'update-source-status/format/json/api_key/%%api_key%%/sourcename/%%sourcename%%/status/%%status%%' + +get_switch_status = 'get-switch-status/format/json/api_key/%%api_key%%' + diff --git a/python_apps/api_clients/api_client.py b/python_apps/api_clients/api_client.py index 1148dcdc1..766908db6 100755 --- a/python_apps/api_clients/api_client.py +++ b/python_apps/api_clients/api_client.py @@ -21,6 +21,7 @@ from urlparse import urlparse import base64 from configobj import ConfigObj import string +import hashlib AIRTIME_VERSION = "2.1.0" @@ -363,6 +364,29 @@ class AirTimeApiClient(ApiClientInterface): time.sleep(retries_wait) return response + + def check_live_stream_auth(self, username, password, dj_type): + #logger = logging.getLogger() + response = '' + try: + url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["check_live_stream_auth"]) + + url = url.replace("%%api_key%%", self.config["api_key"]) + url = url.replace("%%username%%", username) + url = url.replace("%%djtype%%", dj_type) + url = url.replace("%%password%%", password) + + req = urllib2.Request(url) + response = urllib2.urlopen(req).read() + response = json.loads(response) + except Exception, e: + import traceback + top = traceback.format_exc() + print "Exception: %s", e + print "traceback: %s", top + response = None + + return response def setup_media_monitor(self): logger = self.logger @@ -559,12 +583,26 @@ class AirTimeApiClient(ApiClientInterface): response = urllib2.urlopen(req).read() except Exception, e: logger.error("Exception: %s", e) + + def notify_source_status(self, sourcename, status): + logger = self.logger + try: + url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_source_status"]) + + url = url.replace("%%api_key%%", self.config["api_key"]) + url = url.replace("%%sourcename%%", sourcename) + url = url.replace("%%status%%", status) + + req = urllib2.Request(url) + response = urllib2.urlopen(req).read() + except Exception, e: + logger.error("Exception: %s", e) """ This function updates status of mounted file system information on airtime """ def update_file_system_mount(self, added_dir, removed_dir): - logger = logging.getLogger() + logger = self.logger try: url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["update_fs_mount"]) @@ -591,7 +629,7 @@ class AirTimeApiClient(ApiClientInterface): and will call appropriate function on Airtime. """ def handle_watched_dir_missing(self, dir): - logger = logging.getLogger() + logger = self.logger try: url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["handle_watched_dir_missing"]) @@ -606,4 +644,25 @@ class AirTimeApiClient(ApiClientInterface): top = traceback.format_exc() logger.error('Exception: %s', e) logger.error("traceback: %s", top) - + + """ + Retrive current switch status of streams sources + """ + def get_switch_status(self): + logger = self.logger + try: + url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["get_switch_status"]) + + url = url.replace("%%api_key%%", self.config["api_key"]) + + req = urllib2.Request(url) + response = urllib2.urlopen(req).read() + response = json.loads(response) + logger.info("Switch status retrieved %s", response) + except Exception, e: + import traceback + top = traceback.format_exc() + logger.error('Exception: %s', e) + logger.error("traceback: %s", top) + return response + \ No newline at end of file diff --git a/python_apps/pypo/airtime-playout-init-d b/python_apps/pypo/airtime-playout-init-d index 49573e187..688e4e790 100755 --- a/python_apps/pypo/airtime-playout-init-d +++ b/python_apps/pypo/airtime-playout-init-d @@ -28,6 +28,7 @@ liquidsoap_start () { liquidsoap_stop () { monit unmonitor airtime-liquidsoap >/dev/null 2>&1 + python /usr/lib/airtime/pypo/bin/liquidsoap_scripts/liquidsoap_prepare_terminate.py # Send TERM after 5 seconds, wait at most 30 seconds. start-stop-daemon --stop --oknodo --retry TERM/5/0/30 --quiet --pidfile $PIDFILE1 @@ -64,6 +65,7 @@ monit_restart() { start-stop-daemon --stop --oknodo --retry TERM/5/0/30 --quiet --pidfile $PIDFILE0 rm -f $PIDFILE0 + python /usr/lib/airtime/pypo/bin/liquidsoap_scripts/liquidsoap_prepare_terminate.py start-stop-daemon --stop --oknodo --retry TERM/5/0/30 --quiet --pidfile $PIDFILE1 rm -f $PIDFILE1 diff --git a/python_apps/pypo/liquidsoap_scripts/liquidsoap_auth.py b/python_apps/pypo/liquidsoap_scripts/liquidsoap_auth.py new file mode 100644 index 000000000..a7955add9 --- /dev/null +++ b/python_apps/pypo/liquidsoap_scripts/liquidsoap_auth.py @@ -0,0 +1,27 @@ +from api_clients import * +from configobj import ConfigObj +import sys +import json + +try: + config = ConfigObj('/etc/airtime/pypo.cfg') + +except Exception, e: + print 'error: ', e + sys.exit() + +api_clients = api_client.api_client_factory(config) + +dj_type = sys.argv[1] +username = sys.argv[2] +password = sys.argv[3] + +type = '' +if dj_type == '--master': + type = 'master' +elif dj_type == '--dj': + type = 'dj' + +response = api_clients.check_live_stream_auth(username, password, type) + +print response['msg'] \ No newline at end of file diff --git a/python_apps/pypo/liquidsoap_scripts/liquidsoap_prepare_terminate.py b/python_apps/pypo/liquidsoap_scripts/liquidsoap_prepare_terminate.py new file mode 100644 index 000000000..e1dac82b6 --- /dev/null +++ b/python_apps/pypo/liquidsoap_scripts/liquidsoap_prepare_terminate.py @@ -0,0 +1,19 @@ +from configobj import ConfigObj +import telnetlib +import sys + +try: + config = ConfigObj('/etc/airtime/pypo.cfg') + LS_HOST = config['ls_host'] + LS_PORT = config['ls_port'] + + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + tn.write("master_harbor.stop\n") + tn.write("live_dj_harbor.stop\n") + tn.write('exit\n') + tn.read_all() + +except Exception, e: + print('Error loading config file: %s', e) + sys.exit() + \ No newline at end of file diff --git a/python_apps/pypo/liquidsoap_scripts/ls_lib.liq b/python_apps/pypo/liquidsoap_scripts/ls_lib.liq index 74c289f84..cc515875a 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_lib.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_lib.liq @@ -15,6 +15,17 @@ def append_title(m) = end end +default_dj_fade_in = ref 5. +default_dj_fade_out = ref 5. + +def transition(a,b) = + log("transition called...") + add(normalize=false, + [ sequence([ blank(duration=2.), + fade.initial(duration=!default_dj_fade_in, b) ]), + fade.final(duration=!default_dj_fade_out, a) ]) +end + def crossfade(s) #duration is automatically overwritten by metadata fields passed in #with audio diff --git a/python_apps/pypo/liquidsoap_scripts/ls_script.liq b/python_apps/pypo/liquidsoap_scripts/ls_script.liq index 42bc1e479..f0ec2b960 100644 --- a/python_apps/pypo/liquidsoap_scripts/ls_script.liq +++ b/python_apps/pypo/liquidsoap_scripts/ls_script.liq @@ -8,7 +8,7 @@ set("server.telnet.port", 1234) time = ref string_of(gettimeofday()) -queue = audio_to_stereo(request.queue(id="queue", length=0.5)) +queue = audio_to_stereo(id="queue_src", request.equeue(id="queue", length=0.5)) queue = cue_cut(queue) pypo_data = ref '0' @@ -26,6 +26,10 @@ s3_namespace = ref '' %include "ls_lib.liq" +queue = on_metadata(notify, queue) +queue = map_metadata(append_title, queue) +ignore(output.dummy(queue, fallible=true)) + server.register(namespace="vars", "pypo_data", fun (s) -> begin pypo_data := s "Done" end) server.register(namespace="vars", "web_stream_enabled", fun (s) -> begin web_stream_enabled := (s == "true") string_of(!web_stream_enabled) end) server.register(namespace="vars", "stream_metadata_type", fun (s) -> begin stream_metadata_type := int_of_string(s) s end) @@ -35,13 +39,118 @@ server.register(namespace="vars", "bootup_time", fun (s) -> begin time := s s en server.register(namespace="streams", "connection_status", fun (s) -> begin "1:#{!s1_connected},2:#{!s2_connected},3:#{!s3_connected}" end) -default = amplify(0.00001, noise()) +default = amplify(id="silence_src", 0.00001, noise()) default = rewrite_metadata([("artist","Airtime"), ("title", "offline")],default) +ignore(output.dummy(default, fallible=true)) -s = fallback(track_sensitive=false, [queue, default]) +master_dj_enabled = ref false; +live_dj_enabled = ref false; +scheduled_play_enabled = ref false; + +def make_master_dj_available() + master_dj_enabled := true +end + +def make_master_dj_unavailable() + master_dj_enabled := false +end + +def make_live_dj_available() + live_dj_enabled := true +end + +def make_live_dj_unavailable() + live_dj_enabled := false +end + +def make_scheduled_play_available() + scheduled_play_enabled := true +end + +def make_scheduled_play_unavailable() + scheduled_play_enabled := false +end + +#live stream setup +set("harbor.bind_addr", "0.0.0.0") + +def update_source_status(sourcename, status) = + system("/usr/lib/airtime/pypo/bin/liquidsoap_scripts/notify.sh --source-name=#{sourcename} --source-status=#{status}") + log("/usr/lib/airtime/pypo/bin/liquidsoap_scripts/notify.sh --source-name=#{sourcename} --source-status=#{status}") +end + +def live_dj_connect(header) = + update_source_status("live_dj", true) +end + +def live_dj_disconnect() = + update_source_status("live_dj", false) +end + +def master_dj_connect(header) = + update_source_status("master_dj", true) +end + +def master_dj_disconnect() = + update_source_status("master_dj", false) +end + +#auth function for live stream +def check_master_dj_client(user,password) = + #get the output of the php script + ret = get_process_lines("python /usr/lib/airtime/pypo/bin/liquidsoap_scripts/liquidsoap_auth.py --master #{user} #{password}") + #ret has now the value of the live client (dj1,dj2, or djx), or "ERROR"/"unknown" ... + ret = list.hd(ret) + #return true to let the client transmit data, or false to tell harbor to decline + if (ret == "True") then + true + else + false + end +end + +def check_dj_client(user,password) = + #get the output of the php script + ret = get_process_lines("python /usr/lib/airtime/pypo/bin/liquidsoap_scripts/liquidsoap_auth.py --dj #{user} #{password}") + #ret has now the value of the live client (dj1,dj2, or djx), or "ERROR"/"unknown" ... + ret = list.hd(ret) + #return true to let the client transmit data, or false to tell harbor to decline + if (ret == "True") then + true + else + false + end +end + +def append_dj_inputs(master_harbor_input_port, master_harbor_input_mount_point, dj_harbor_input_port, dj_harbor_input_mount_point, s) = + if master_harbor_input_port != 0 and master_harbor_input_mount_point != "" and dj_harbor_input_port != 0 and dj_harbor_input_mount_point != "" then + master_dj = input.harbor(id="master_harbor", master_harbor_input_mount_point, port=master_harbor_input_port, auth=check_master_dj_client, + max=40., on_connect=master_dj_connect, on_disconnect=master_dj_disconnect) + dj_live = input.harbor(id="live_dj_harbor", dj_harbor_input_mount_point, port=dj_harbor_input_port, auth=check_dj_client, + max=40., on_connect=live_dj_connect, on_disconnect=live_dj_disconnect) + ignore(output.dummy(master_dj, fallible=true)) + ignore(output.dummy(dj_live, fallible=true)) + switch(id="master_dj_switch", track_sensitive=false, transitions=[transition, transition, transition], [({!master_dj_enabled},master_dj), ({!live_dj_enabled},dj_live), ({true}, s)]) + elsif master_harbor_input_port != 0 and master_harbor_input_mount_point != "" then + master_dj = input.harbor(id="master_harbor", master_harbor_input_mount_point, port=master_harbor_input_port, auth=check_master_dj_client, + max=40., on_connect=master_dj_connect, on_disconnect=master_dj_disconnect) + ignore(output.dummy(master_dj, fallible=true)) + switch(id="master_dj_switch", track_sensitive=false, transitions=[transition, transition], [({!master_dj_enabled},master_dj), ({true}, s)]) + elsif dj_harbor_input_port != 0 and dj_harbor_input_mount_point != "" then + dj_live = input.harbor(id="live_dj_harbor", dj_harbor_input_mount_point, port=dj_harbor_input_port, auth=check_dj_client, + max=40., on_connect=live_dj_connect, on_disconnect=live_dj_disconnect) + ignore(output.dummy(dj_live, fallible=true)) + switch(id="live_dj_switch", track_sensitive=false, transitions=[transition, transition], [({!live_dj_enabled},dj_live), ({true}, s)]) + else + s + end +end + +s = switch(id="default_switch", track_sensitive=false, transitions=[transition, transition], [({!scheduled_play_enabled},queue),({true},default)]) +s = append_dj_inputs(master_live_stream_port, master_live_stream_mp, dj_live_stream_port, dj_live_stream_mp, s) -s = on_metadata(notify, s) s = crossfade(s) + # Attach a skip command to the source s: #web_stream_source = input.http(id="web_stream", autostart = false, buffer=0.5, max=20., "") @@ -59,7 +168,37 @@ s = crossfade(s) #) add_skip_command(s) -s = map_metadata(append_title, s) + +server.register(namespace="streams", + description="Stop Master DJ source.", + usage="master_dj_stop", + "master_dj_stop", + fun (s) -> begin make_master_dj_unavailable() "Done." end) +server.register(namespace="streams", + description="Start Master DJ source.", + usage="master_dj_start", + "master_dj_start", + fun (s) -> begin make_master_dj_available() "Done." end) +server.register(namespace="streams", + description="Stop Live DJ source.", + usage="live_dj_stop", + "live_dj_stop", + fun (s) -> begin make_live_dj_unavailable() "Done." end) +server.register(namespace="streams", + description="Start Live DJ source.", + usage="live_dj_start", + "live_dj_start", + fun (s) -> begin make_live_dj_available() "Done." end) +server.register(namespace="streams", + description="Stop Scheduled Play source.", + usage="scheduled_play_stop", + "scheduled_play_stop", + fun (s) -> begin make_scheduled_play_unavailable() "Done." end) +server.register(namespace="streams", + description="Start Scheduled Play source.", + usage="scheduled_play_start", + "scheduled_play_start", + fun (s) -> begin make_scheduled_play_available() "Done." end) if output_sound_device then diff --git a/python_apps/pypo/pypo-notify.py b/python_apps/pypo/pypo-notify.py index 5139a4aad..0df1abab5 100644 --- a/python_apps/pypo/pypo-notify.py +++ b/python_apps/pypo/pypo-notify.py @@ -51,6 +51,8 @@ parser.add_option("-e", "--error", action="store", dest="error", type="string", parser.add_option("-s", "--stream-id", help="ID stream", metavar="stream_id") parser.add_option("-c", "--connect", help="liquidsoap connected", action="store_true", metavar="connect") parser.add_option("-t", "--time", help="liquidsoap boot up time", action="store", dest="time", metavar="time", type="string") +parser.add_option("-x", "--source-name", help="source connection name", metavar="source_name") +parser.add_option("-y", "--source-status", help="source connection stauts", metavar="source_status") # parse options (options, args) = parser.parse_args() @@ -91,6 +93,16 @@ class Notify: logger.debug('msg = '+ str(msg)) response = self.api_client.notify_liquidsoap_status(msg, stream_id, time) logger.debug("Response: "+json.dumps(response)) + + def notify_source_status(self, source_name, status): + logger = logging.getLogger() + + logger.debug('#################################################') + logger.debug('# Calling server to update source status #') + logger.debug('#################################################') + logger.debug('msg = '+ str(source_name) + ' : ' + str(status)) + response = self.api_client.notify_source_status(source_name, status) + logger.debug("Response: "+json.dumps(response)) if __name__ == '__main__': print @@ -101,7 +113,6 @@ if __name__ == '__main__': # initialize logger = logging.getLogger("notify") - if options.error and options.stream_id: try: n = Notify() @@ -114,6 +125,12 @@ if __name__ == '__main__': n.notify_liquidsoap_status("OK", options.stream_id, options.time) except Exception, e: print e + elif options.source_name and options.source_status: + try: + n = Notify() + n.notify_source_status(options.source_name, options.source_status) + except Exception, e: + print e else: if not options.data: print "NOTICE: 'data' command-line argument not given." diff --git a/python_apps/pypo/pypocli.py b/python_apps/pypo/pypocli.py index 265d141d9..9eeb15a6f 100644 --- a/python_apps/pypo/pypocli.py +++ b/python_apps/pypo/pypocli.py @@ -153,11 +153,12 @@ if __name__ == '__main__': recorder.daemon = True recorder.start() - pmh.join() - pfile.join() + # all join() are commented out becase we want to exit entire pypo + # if pypofetch is exiting + #pmh.join() + #recorder.join() + #pp.join() pf.join() - pp.join() - recorder.join() logger.info("pypo fetch exit") sys.exit() diff --git a/python_apps/pypo/pypofetch.py b/python_apps/pypo/pypofetch.py index e01df5619..c7634b878 100644 --- a/python_apps/pypo/pypofetch.py +++ b/python_apps/pypo/pypofetch.py @@ -92,13 +92,69 @@ class PypoFetch(Thread): elif command == 'cancel_current_show': self.logger.info("Cancel current show command received...") self.stop_current_show() + elif command == 'switch_source': + self.logger.info("switch_on_source show command received...") + self.switch_source(m['sourcename'], m['status']) + elif command == 'disconnect_source': + self.logger.info("disconnect_on_source show command received...") + self.disconnect_source(m['sourcename']) except Exception, e: import traceback top = traceback.format_exc() self.logger.error('Exception: %s', e) self.logger.error("traceback: %s", top) self.logger.error("Exception in handling Message Handler message: %s", e) - + + def disconnect_source(self,sourcename): + self.logger.debug('Disconnecting source: %s', sourcename) + command = "" + if(sourcename == "master_dj"): + command += "master_harbor.kick\n" + elif(sourcename == "live_dj"): + command += "live_dj_harbor.kick\n" + + try: + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + tn.write(command) + tn.write('exit\n') + tn.read_all() + except Exception, e: + self.logger.debug(e) + self.logger.debug('Could not connect to liquidsoap') + + def switch_source(self, sourcename, status): + self.logger.debug('Switching source: %s to "%s" status', sourcename, status) + command = "streams." + if(sourcename == "master_dj"): + command += "master_dj_" + elif(sourcename == "live_dj"): + command += "live_dj_" + elif(sourcename == "scheduled_play"): + command += "scheduled_play_" + + if(status == "on"): + command += "start\n" + else: + command += "stop\n" + + try: + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + tn.write(command) + tn.write('exit\n') + tn.read_all() + except Exception, e: + self.logger.debug(e) + self.logger.debug('Could not connect to liquidsoap') + + """ + This check current switch status from Airtime and update the status + """ + def check_switch_status(self): + self.logger.debug('Checking current switch status with Airtime') + switch_status = self.api_client.get_switch_status() + self.logger.debug('switch_status:%s',switch_status) + for k, v in switch_status['status'].iteritems(): + self.switch_source(k, v) def stop_current_show(self): self.logger.debug('Notifying Liquidsoap to stop playback.') @@ -111,7 +167,7 @@ class PypoFetch(Thread): self.logger.debug(e) self.logger.debug('Could not connect to liquidsoap') - def regenerateLiquidsoapConf(self, setting): + def regenerateLiquidsoapConf(self, setting_p): existing = {} # create a temp file fh = open('/etc/airtime/liquidsoap.cfg', 'r') @@ -146,8 +202,9 @@ class PypoFetch(Thread): restart = False self.logger.info("Looking for changes...") + setting = sorted(setting_p.items()) # look for changes - for s in setting: + for k, s in setting: if "output_sound_device" in s[u'keyname'] or "icecast_vorbis_metadata" in s[u'keyname']: dump, stream = s[u'keyname'].split('_', 1) state_change_restart[stream] = False @@ -155,6 +212,10 @@ class PypoFetch(Thread): if (existing[s[u'keyname']] != s[u'value']): self.logger.info("'Need-to-restart' state detected for %s...", s[u'keyname']) restart = True; + elif "master_live_stream_port" in s[u'keyname'] or "master_live_stream_mp" in s[u'keyname'] or "dj_live_stream_port" in s[u'keyname'] or "dj_live_stream_mp" in s[u'keyname']: + if (existing[s[u'keyname']] != s[u'value']): + self.logger.info("'Need-to-restart' state detected for %s...", s[u'keyname']) + restart = True; else: stream, dump = s[u'keyname'].split('_',1) if "_output" in s[u'keyname']: @@ -190,7 +251,7 @@ class PypoFetch(Thread): fh.write("################################################\n") fh.write("# THIS FILE IS AUTO GENERATED. DO NOT CHANGE!! #\n") fh.write("################################################\n") - for d in setting: + for k, d in setting: buffer = d[u'keyname'] + " = " if(d[u'type'] == 'string'): temp = d[u'value'] @@ -218,8 +279,7 @@ class PypoFetch(Thread): """ updates the status of liquidsoap connection to the streaming server This fucntion updates the bootup time variable in liquidsoap script - """ - def update_liquidsoap_connection_status(self): + """ tn = telnetlib.Telnet(LS_HOST, LS_PORT) # update the boot up time of liquidsoap. Since liquidsoap is not restarting, # we are manually adjusting the bootup time variable so the status msg will get @@ -348,15 +408,22 @@ class PypoFetch(Thread): def create_liquidsoap_annotation(self, media): - entry = \ + """entry = \ 'annotate:media_id="%s",liq_start_next="%s",liq_fade_in="%s",liq_fade_out="%s",liq_cue_in="%s",liq_cue_out="%s",schedule_table_id="%s":%s' \ % (media['id'], 0, \ float(media['fade_in']) / 1000, \ float(media['fade_out']) / 1000, \ float(media['cue_in']), \ float(media['cue_out']), \ + media['row_id'], media['dst'])""" + + entry = \ + 'annotate:media_id="%s",liq_start_next="%s",liq_cue_in="%s",liq_cue_out="%s",schedule_table_id="%s":%s' \ + % (media['id'], 0, \ + float(media['cue_in']), \ + float(media['cue_out']), \ media['row_id'], media['dst']) - + return entry def check_for_previous_crash(self, media_item): @@ -474,6 +541,7 @@ class PypoFetch(Thread): if success: self.logger.info("Bootstrap schedule received: %s", self.schedule_data) self.process_schedule(self.schedule_data, True) + self.check_switch_status() loops = 1 while True: diff --git a/python_apps/pypo/pypomessagehandler.py b/python_apps/pypo/pypomessagehandler.py index 75b0407ea..06aebc0c5 100644 --- a/python_apps/pypo/pypomessagehandler.py +++ b/python_apps/pypo/pypomessagehandler.py @@ -74,6 +74,12 @@ class PypoMessageHandler(Thread): elif command == 'cancel_current_show': self.logger.info("Cancel current show command received...") self.pypo_queue.put(message) + elif command == 'switch_source': + self.logger.info("switch_source command received...") + self.pypo_queue.put(message) + elif command == 'disconnect_source': + self.logger.info("disconnect_source command received...") + self.pypo_queue.put(message) elif command == 'update_recorder_schedule': self.recorder_queue.put(message) elif command == 'cancel_recording': diff --git a/python_apps/pypo/pypopush.py b/python_apps/pypo/pypopush.py index 61635fc55..abdcb4c7b 100644 --- a/python_apps/pypo/pypopush.py +++ b/python_apps/pypo/pypopush.py @@ -9,10 +9,16 @@ import telnetlib import calendar import json import math + +""" +It is possible to use a list as a queue, where the first element added is the first element +retrieved ("first-in, first-out"); however, lists are not efficient for this purpose. Let's use +"deque" +""" +from collections import deque + from threading import Thread - from api_clients import api_client - from configobj import ConfigObj @@ -25,6 +31,7 @@ try: LS_HOST = config['ls_host'] LS_PORT = config['ls_port'] PUSH_INTERVAL = 2 + MAX_LIQUIDSOAP_QUEUE_LENGTH = 2 except Exception, e: logger = logging.getLogger() logger.error('Error loading config file %s', e) @@ -42,6 +49,8 @@ class PypoPush(Thread): self.push_ahead = 10 self.last_end_time = 0 + self.pushed_objects = {} + self.logger = logging.getLogger('push') def push(self): @@ -51,6 +60,9 @@ class PypoPush(Thread): If yes, the current liquidsoap playlist gets replaced with the corresponding one, then liquidsoap is asked (via telnet) to reload and immediately play it. """ + + liquidsoap_queue_approx = self.get_queue_items_from_liquidsoap() + self.logger.debug('liquidsoap_queue_approx %s', liquidsoap_queue_approx) timenow = time.time() # get a new schedule from pypo-fetch @@ -58,38 +70,42 @@ class PypoPush(Thread): # make sure we get the latest schedule while not self.queue.empty(): self.media = self.queue.get() + self.logger.debug("Received data from pypo-fetch") self.logger.debug('media %s' % json.dumps(self.media)) + self.handle_new_media(self.media, liquidsoap_queue_approx) + media = self.media - currently_on_air = False - if media: - tnow = time.gmtime(timenow) - tcoming = time.gmtime(timenow + self.push_ahead) - str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5]) - str_tcoming_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcoming[0], tcoming[1], tcoming[2], tcoming[3], tcoming[4], tcoming[5]) - - for key in media.keys(): - media_item = media[key] - item_start = media_item['start'][0:19] - - if str_tnow_s <= item_start and item_start < str_tcoming_s: - """ - If the media item starts in the next 30 seconds, push it to the queue. - """ - self.logger.debug('Preparing to push media item scheduled at: %s', key) - - if self.push_to_liquidsoap(media_item): - self.logger.debug("Pushed to liquidsoap, updating 'played' status.") - + if len(liquidsoap_queue_approx) < MAX_LIQUIDSOAP_QUEUE_LENGTH: + currently_on_air = False + if media: + tnow = time.gmtime(timenow) + tcoming = time.gmtime(timenow + self.push_ahead) + str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5]) + str_tcoming_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tcoming[0], tcoming[1], tcoming[2], tcoming[3], tcoming[4], tcoming[5]) + + for key in media.keys(): + media_item = media[key] + item_start = media_item['start'][0:19] + + if str_tnow_s <= item_start and item_start < str_tcoming_s: """ - Temporary solution to make sure we don't push the same track multiple times. + If the media item starts in the next 30 seconds, push it to the queue. """ - del media[key] - - currently_on_air = True - self.liquidsoap_state_play = True + self.logger.debug('Preparing to push media item scheduled at: %s', key) + + if self.push_to_liquidsoap(media_item): + self.logger.debug("Pushed to liquidsoap, updating 'played' status.") + + """ + Temporary solution to make sure we don't push the same track multiple times. + """ + del media[key] + + currently_on_air = True + self.liquidsoap_state_play = True def push_to_liquidsoap(self, media_item): """ @@ -122,6 +138,129 @@ class PypoPush(Thread): return False return True + + """ + def update_liquidsoap_queue(self): +# the queue variable liquidsoap_queue is our attempt to mirror +# what liquidsoap actually has in its own queue. Liquidsoap automatically +# updates its own queue when an item finishes playing, we have to do this +# manually. +# +# This function will iterate through the liquidsoap_queue and remove items +# whose end time are in the past. + + tnow = time.gmtime(timenow) + str_tnow_s = "%04d-%02d-%02d-%02d-%02d-%02d" % (tnow[0], tnow[1], tnow[2], tnow[3], tnow[4], tnow[5]) + + while len(self.liquidsoap_queue) > 0: + if self.liquidsoap_queue[0]["end"] < str_tnow_s: + self.liquidsoap_queue.popleft() + """ + + def get_queue_items_from_liquidsoap(self): + """ + This function connects to Liquidsoap to find what media items are in its queue. + """ + + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + + msg = 'queue.queue\n' + tn.write(msg) + response = tn.read_until("\r\n").strip(" \r\n") + tn.write('exit\n') + tn.read_all() + + liquidsoap_queue_approx = [] + + if len(response) > 0: + items_in_queue = response.split(" ") + + self.logger.debug("items_in_queue: %s", items_in_queue) + + for item in items_in_queue: + if item in self.pushed_objects: + liquidsoap_queue_approx.append(self.pushed_objects[item]) + else: + self.logger.error("ID exists in liquidsoap queue that does not exist in our pushed_objects queue: " + item) + + return liquidsoap_queue_approx + + + def handle_new_media(self, media, liquidsoap_queue_approx): + """ + This function's purpose is to gracefully handle situations where + Liquidsoap already has a track in its queue, but the schedule + has changed. If the schedule has changed, this function's job is to + call other functions that will connect to Liquidsoap and alter its + queue. + """ + + #TODO: Keys should already be sorted. Verify this. + sorted_keys = sorted(media.keys()) + + if len(liquidsoap_queue_approx) == 0: + """ + liquidsoap doesn't have anything in its queue, so we have nothing + to worry about. Life is good. + """ + pass + elif len(liquidsoap_queue_approx) == 1: + queue_item_0_start = liquidsoap_queue_approx[0]['start'] + try: + if liquidsoap_queue_approx[0]['id'] != media[queue_item_0_start]['id']: + """ + liquidsoap's queue does not match the schedule we just received from the Airtime server. + The queue is only of length 1 which means the item in the queue is playing. + Need to do source.skip. + + Since only one item, we don't have to worry about the current item ending and us calling + source.skip unintentionally on the next item (there is no next item). + """ + + self.logger.debug("%s from ls does not exist in queue new schedule. Removing" % liquidsoap_queue_approx[0]['id'], media) + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[0]) + except KeyError, k: + self.logger.debug("%s from ls does not exist in queue schedule: %s Removing" % (queue_item_0_start, media)) + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[0]) + + + elif len(liquidsoap_queue_approx) == 2: + queue_item_0_start = liquidsoap_queue_approx[0]['start'] + queue_item_1_start = liquidsoap_queue_approx[1]['start'] + + if queue_item_1_start in media.keys(): + if liquidsoap_queue_approx[1]['id'] != media[queue_item_1_start]['id']: + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[1]) + else: + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[1]) + + if queue_item_0_start in media.keys(): + if liquidsoap_queue_approx[0]['id'] != media[queue_item_0_start]['id']: + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[0]) + else: + self.remove_from_liquidsoap_queue(liquidsoap_queue_approx[0]) + + def remove_from_liquidsoap_queue(self, media_item, do_only_source_skip=False): + if 'queue_id' in media_item: + queue_id = media_item['queue_id'] + + tn = telnetlib.Telnet(LS_HOST, LS_PORT) + msg = "queue.remove %s\n" % queue_id + tn.write(msg) + response = tn.read_until("\r\n").strip("\r\n") + + if "No such request in my queue" in response: + """ + Cannot remove because Liquidsoap started playing the item. Need + to use source.skip instead + """ + msg = "source.skip" + tn.write("source.skip") + + tn.write("exit\n") + tn.read_all() + else: + self.logger.error("'queue_id' key doesn't exist in media_item dict()") def sleep_until_start(self, media_item): """ @@ -162,8 +301,16 @@ class PypoPush(Thread): annotation = media_item['annotation'] msg = 'queue.push %s\n' % annotation.encode('utf-8') - tn.write(msg) self.logger.debug(msg) + tn.write(msg) + queue_id = tn.read_until("\r\n").strip("\r\n") + + #remember the media_item's queue id which we may use + #later if we need to remove it from the queue. + media_item['queue_id'] = queue_id + + #add media_item to the end of our queue + self.pushed_objects[queue_id] = media_item show_name = media_item['show_name'] msg = 'vars.show_name %s\n' % show_name.encode('utf-8')