Merge branch 'saas' into saas-speedy
This commit is contained in:
commit
8449194ca1
51 changed files with 4395 additions and 640 deletions
|
@ -37,7 +37,8 @@ $ccAcl->add(new Zend_Acl_Resource('library'))
|
|||
->add(new Zend_Acl_Resource('rest:show-image'))
|
||||
->add(new Zend_Acl_Resource('billing'))
|
||||
->add(new Zend_Acl_Resource('thank-you'))
|
||||
->add(new Zend_Acl_Resource('provisioning'));
|
||||
->add(new Zend_Acl_Resource('provisioning'))
|
||||
->add(new Zend_Acl_Resource('player'));
|
||||
|
||||
/** Creating permissions */
|
||||
$ccAcl->allow('G', 'index')
|
||||
|
@ -70,6 +71,7 @@ $ccAcl->allow('G', 'index')
|
|||
->allow('A', 'user')
|
||||
->allow('A', 'systemstatus')
|
||||
->allow('A', 'preference')
|
||||
->allow('A', 'player')
|
||||
->allow('S', 'thank-you')
|
||||
->allow('S', 'billing');
|
||||
|
||||
|
|
|
@ -85,6 +85,12 @@ $pages = array(
|
|||
'controller' => 'listenerstat',
|
||||
'action' => 'index',
|
||||
'resource' => 'listenerstat'
|
||||
),
|
||||
array(
|
||||
'label' => _('Player'),
|
||||
'module' => 'default',
|
||||
'controller' => 'player',
|
||||
'action' => 'customize'
|
||||
)
|
||||
)
|
||||
),
|
||||
|
|
|
@ -523,6 +523,7 @@ class ApiController extends Zend_Controller_Action
|
|||
$result["description"] = Application_Model_Preference::GetStationDescription();
|
||||
$result["timezone"] = Application_Model_Preference::GetDefaultTimezone();
|
||||
$result["locale"] = Application_Model_Preference::GetDefaultLocale();
|
||||
$result["stream_data"] = Application_Model_StreamSetting::getEnabledStreamData();
|
||||
|
||||
// used by caller to determine if the airtime they are running or widgets in use is out of date.
|
||||
$result['AIRTIME_API_VERSION'] = AIRTIME_API_VERSION;
|
||||
|
@ -1310,7 +1311,7 @@ class ApiController extends Zend_Controller_Action
|
|||
}
|
||||
|
||||
public function getStreamParametersAction() {
|
||||
$streams = array("s1", "s2", "s3");
|
||||
$streams = array("s1", "s2", "s3", "s4");
|
||||
$stream_params = array();
|
||||
foreach ($streams as $s) {
|
||||
$stream_params[$s] =
|
||||
|
|
57
airtime_mvc/application/controllers/EmbedController.php
Normal file
57
airtime_mvc/application/controllers/EmbedController.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
class EmbedController extends Zend_Controller_Action
|
||||
{
|
||||
public function init()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the action that is called to insert the player onto a web page.
|
||||
* It passes all the js and css files to the view, as well as all the
|
||||
* stream customization information.
|
||||
*
|
||||
* The view for this action contains all the inline javascript needed to
|
||||
* create the player.
|
||||
*/
|
||||
public function playerAction()
|
||||
{
|
||||
$this->view->layout()->disableLayout();
|
||||
|
||||
$CC_CONFIG = Config::getConfig();
|
||||
|
||||
$request = $this->getRequest();
|
||||
|
||||
$this->view->css = Application_Common_HTTPHelper::getStationUrl() . "css/player.css?".$CC_CONFIG['airtime_version'];
|
||||
$this->view->mrp_js = Application_Common_HTTPHelper::getStationUrl() . "js/airtime/player/mrp.js?".$CC_CONFIG['airtime_version'];
|
||||
$this->view->jquery = Application_Common_HTTPHelper::getStationUrl() . "js/libs/jquery-1.10.2.js";
|
||||
$this->view->muses_swf = Application_Common_HTTPHelper::getStationUrl() . "js/airtime/player/muses.swf";
|
||||
$this->view->metadata_api_url = Application_Common_HTTPHelper::getStationUrl() . "api/live-info";
|
||||
$this->view->player_title = json_encode($request->getParam('title'));
|
||||
|
||||
$stream = $request->getParam('stream');
|
||||
$streamData = Application_Model_StreamSetting::getEnabledStreamData();
|
||||
$availableMobileStreams = array();
|
||||
$availableDesktopStreams = array();
|
||||
|
||||
if ($stream == "auto") {
|
||||
$this->view->playerMode = "auto";
|
||||
$this->view->streamURL = json_encode("");
|
||||
foreach ($streamData as $s) {
|
||||
if ($s["mobile"]) {
|
||||
array_push($availableMobileStreams, $s);
|
||||
} else if (!$s["mobile"]) {
|
||||
array_push($availableDesktopStreams, $s);
|
||||
}
|
||||
}
|
||||
} elseif (!empty($stream)) {
|
||||
$this->view->playerMode = "manual";
|
||||
$selectedStreamData = $streamData[$stream];
|
||||
$this->view->streamURL = json_encode($selectedStreamData["url"]);
|
||||
$this->view->codec = $selectedStreamData["codec"];
|
||||
}
|
||||
$this->view->availableMobileStreams = json_encode($availableMobileStreams);
|
||||
$this->view->availableDesktopStreams = json_encode($availableDesktopStreams);
|
||||
}
|
||||
}
|
31
airtime_mvc/application/controllers/PlayerController.php
Normal file
31
airtime_mvc/application/controllers/PlayerController.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
class PlayerController extends Zend_Controller_Action
|
||||
{
|
||||
public function init()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function customizeAction()
|
||||
{
|
||||
$CC_CONFIG = Config::getConfig();
|
||||
$baseUrl = Application_Common_OsPath::getBaseDir();
|
||||
$this->view->headLink()->appendStylesheet($baseUrl.'css/player-form.css?'.$CC_CONFIG['airtime_version']);
|
||||
$this->view->headScript()->appendFile($baseUrl.'js/airtime/player/player.js?'.$CC_CONFIG['airtime_version']);
|
||||
|
||||
$form = new Application_Form_Player();
|
||||
|
||||
$apiEnabled = Application_Model_Preference::GetAllow3rdPartyApi();
|
||||
$numEnabledStreams = $form->getElement('player_stream_url')->getAttrib('numberOfEnabledStreams');
|
||||
|
||||
if ($numEnabledStreams > 0 && $apiEnabled) {
|
||||
$this->view->form = $form;
|
||||
} else {
|
||||
$this->view->errorMsg = "To configure and use the embeddable player you must:<br><br>
|
||||
1. Enable at least one MP3, AAC, or OGG stream under System -> Streams<br>
|
||||
2. Enable the Public Airtime API under System -> Preferences";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -141,11 +141,7 @@ class PreferenceController extends Zend_Controller_Action
|
|||
$this->view->headScript()->appendFile($baseUrl.'js/airtime/preferences/streamsetting.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
|
||||
|
||||
// get current settings
|
||||
$temp = Application_Model_StreamSetting::getStreamSetting();
|
||||
$setting = array();
|
||||
foreach ($temp as $t) {
|
||||
$setting[$t['keyname']] = $t['value'];
|
||||
}
|
||||
$setting = Application_Model_StreamSetting::getStreamSetting();
|
||||
|
||||
$name_map = array(
|
||||
'ogg' => 'Ogg Vorbis',
|
||||
|
@ -208,6 +204,7 @@ class PreferenceController extends Zend_Controller_Action
|
|||
$s1_data = array();
|
||||
$s2_data = array();
|
||||
$s3_data = array();
|
||||
$s4_data = array();
|
||||
$values = array();
|
||||
foreach($postData as $k=>$v) {
|
||||
$v = explode('=', urldecode($v));
|
||||
|
@ -223,6 +220,9 @@ class PreferenceController extends Zend_Controller_Action
|
|||
} elseif (strpos($v[0], "s3_data") !== false) {
|
||||
preg_match('/\[(.*)\]/', $v[0], $matches);
|
||||
$s3_data[$matches[1]] = $v[1];
|
||||
} elseif (strpos($v[0], "s4_data") !== false) {
|
||||
preg_match('/\[(.*)\]/', $v[0], $matches);
|
||||
$s4_data[$matches[1]] = $v[1];
|
||||
} else {
|
||||
$values[$v[0]] = $v[1];
|
||||
}
|
||||
|
@ -230,6 +230,7 @@ class PreferenceController extends Zend_Controller_Action
|
|||
$values["s1_data"] = $s1_data;
|
||||
$values["s2_data"] = $s2_data;
|
||||
$values["s3_data"] = $s3_data;
|
||||
$values["s4_data"] = $s4_data;
|
||||
|
||||
$error = false;
|
||||
if ($form->isValid($values)) {
|
||||
|
@ -245,6 +246,7 @@ class PreferenceController extends Zend_Controller_Action
|
|||
$s1_set_admin_pass = !empty($values["s1_data"]["admin_pass"]);
|
||||
$s2_set_admin_pass = !empty($values["s2_data"]["admin_pass"]);
|
||||
$s3_set_admin_pass = !empty($values["s3_data"]["admin_pass"]);
|
||||
$s4_set_admin_pass = !empty($values["s4_data"]["admin_pass"]);
|
||||
|
||||
// this goes into cc_pref table
|
||||
Application_Model_Preference::SetStreamLabelFormat($values['streamFormat']);
|
||||
|
@ -290,6 +292,7 @@ class PreferenceController extends Zend_Controller_Action
|
|||
"s1_set_admin_pass"=>$s1_set_admin_pass,
|
||||
"s2_set_admin_pass"=>$s2_set_admin_pass,
|
||||
"s3_set_admin_pass"=>$s3_set_admin_pass,
|
||||
"s4_set_admin_pass"=>$s4_set_admin_pass,
|
||||
));
|
||||
} else {
|
||||
$live_stream_subform->updateVariables();
|
||||
|
@ -441,7 +444,8 @@ class PreferenceController extends Zend_Controller_Action
|
|||
public function getAdminPasswordStatusAction()
|
||||
{
|
||||
$out = array();
|
||||
for ($i=1; $i<=3; $i++) {
|
||||
$num_of_stream = intval(Application_Model_Preference::GetNumOfStreams());
|
||||
for ($i=1; $i<=$num_of_stream; $i++) {
|
||||
if (Application_Model_StreamSetting::getAdminPass('s'.$i)=='') {
|
||||
$out["s".$i] = false;
|
||||
} else {
|
||||
|
|
|
@ -118,7 +118,8 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
|
|||
"locale",
|
||||
"upgrade",
|
||||
'whmcs-login',
|
||||
"provisioning"
|
||||
"provisioning",
|
||||
"embed"
|
||||
)))
|
||||
{
|
||||
$this->setRoleName("G");
|
||||
|
|
81
airtime_mvc/application/forms/Player.php
Normal file
81
airtime_mvc/application/forms/Player.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
define("OPUS", "opus");
|
||||
|
||||
class Application_Form_Player extends Zend_Form_SubForm
|
||||
{
|
||||
public function init()
|
||||
{
|
||||
$this->setDecorators(array(
|
||||
array('ViewScript', array('viewScript' => 'form/player.phtml'))
|
||||
));
|
||||
|
||||
$title = new Zend_Form_Element_Text('player_title');
|
||||
$title->setValue(_('Now Playing'));
|
||||
$title->setLabel(_('Title:'));
|
||||
$title->setDecorators(array(
|
||||
'ViewHelper',
|
||||
'Errors',
|
||||
'Label'
|
||||
));
|
||||
$title->addDecorator('Label', array('class' => 'player-title'));
|
||||
$this->addElement($title);
|
||||
|
||||
$streamMode = new Zend_Form_Element_Radio('player_stream_mode');
|
||||
$streamMode->setLabel(_('Select Stream:'));
|
||||
$streamMode->setMultiOptions(
|
||||
array(
|
||||
"auto" => _("Auto detect the most appropriate stream to use."),
|
||||
"manual" => _("Select a stream:")
|
||||
)
|
||||
);
|
||||
$streamMode->setValue("auto");
|
||||
$this->addElement($streamMode);
|
||||
|
||||
$streamURL = new Zend_Form_Element_Radio('player_stream_url');
|
||||
$opusStreamCount = 0;
|
||||
$urlOptions = Array();
|
||||
foreach(Application_Model_StreamSetting::getEnabledStreamData() as $stream => $data) {
|
||||
$urlOptions[$stream] = strtoupper($data["codec"])." - ".$data["bitrate"]."kbps";
|
||||
if ($data["mobile"]) {
|
||||
$urlOptions[$stream] .= _(" - Mobile friendly");
|
||||
}
|
||||
if ($data["codec"] == OPUS) {
|
||||
$opusStreamCount += 1;
|
||||
$urlOptions[$stream] .= _(" - The player does not support Opus streams.");
|
||||
}
|
||||
}
|
||||
$streamURL->setMultiOptions(
|
||||
$urlOptions
|
||||
);
|
||||
|
||||
// Set default value to the first non-opus stream we find
|
||||
foreach ($urlOptions as $o => $v) {
|
||||
if (strpos(strtolower($v), OPUS) !== false) {
|
||||
continue;
|
||||
} else {
|
||||
$streamURL->setValue($o);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$streamURL->setAttrib('numberOfEnabledStreams', sizeof($urlOptions)-$opusStreamCount);
|
||||
$streamURL->removeDecorator('label');
|
||||
$this->addElement($streamURL);
|
||||
|
||||
$embedSrc = new Zend_Form_Element_Textarea('player_embed_src');
|
||||
$embedSrc->setAttrib("readonly", "readonly");
|
||||
$embedSrc->setAttrib("class", "embed-player-text-box");
|
||||
$embedSrc->setAttrib('cols', '40')
|
||||
->setAttrib('rows', '4');
|
||||
$embedSrc->setLabel(_("Embeddable code:"));
|
||||
$embedSrc->setDescription(_("Copy this code and paste it into your website's HTML to embed the player in your site."));
|
||||
$embedSrc->setValue('<iframe frameborder="0" width="280" height="216" src="'.Application_Common_HTTPHelper::getStationUrl().'embed/player?stream=auto&title=Now Playing"></iframe>');
|
||||
$this->addElement($embedSrc);
|
||||
|
||||
$previewLabel = new Zend_Form_Element_Text('player_preview_label');
|
||||
$previewLabel->setLabel(_("Preview:"));
|
||||
$this->addElement($previewLabel);
|
||||
|
||||
}
|
||||
}
|
|
@ -53,6 +53,12 @@ class Application_Form_StreamSettingSubForm extends Zend_Form_SubForm
|
|||
}
|
||||
$this->addElement($enable);
|
||||
|
||||
$mobile = new Zend_Form_Element_Checkbox('mobile');
|
||||
$mobile->setLabel(_('Mobile:'));
|
||||
$mobile->setValue($setting[$prefix.'_mobile']);
|
||||
$mobile->setDecorators(array('ViewHelper'));
|
||||
$this->addElement($mobile);
|
||||
|
||||
$type = new Zend_Form_Element_Select('type');
|
||||
$type->setLabel(_("Stream Type:"))
|
||||
->setMultiOptions($stream_types)
|
||||
|
|
|
@ -12,4 +12,18 @@ class Airtime_Decorator_SuperAdmin_Only extends Zend_Form_Decorator_Abstract
|
|||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Hide a Zend_Form_Element unless you're logged in as an Admin or SuperAdmin. */
|
||||
class Airtime_Decorator_Admin_Only extends Zend_Form_Decorator_Abstract
|
||||
{
|
||||
public function render($content)
|
||||
{
|
||||
$currentUser = Application_Model_User::getCurrentUser();
|
||||
if ($currentUser->isSuperAdmin() || $currentUser->isAdmin()) {
|
||||
return $content;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -828,7 +828,8 @@ class Application_Model_Scheduler
|
|||
"fade_in = '{$file["fadein"]}', ".
|
||||
"fade_out = '{$file["fadeout"]}', ".
|
||||
"clip_length = '{$file["cliplength"]}', ".
|
||||
"position = {$pos} ".
|
||||
"position = {$pos}, ".
|
||||
"instance_id = {$instanceId} ".
|
||||
"WHERE id = {$sched["id"]}";
|
||||
|
||||
Application_Common_Database::prepareAndExecute(
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<?php
|
||||
|
||||
define("MAX_NUM_STREAMS", 4);
|
||||
|
||||
class Application_Model_StreamSetting
|
||||
{
|
||||
public static function setValue($key, $value, $type)
|
||||
|
@ -41,7 +44,7 @@ class Application_Model_StreamSetting
|
|||
}
|
||||
}
|
||||
|
||||
public static function getValue($key)
|
||||
public static function getValue($key, $default="")
|
||||
{
|
||||
$con = Propel::getConnection();
|
||||
|
||||
|
@ -59,7 +62,27 @@ class Application_Model_StreamSetting
|
|||
throw new Exception("Error: $msg");
|
||||
}
|
||||
|
||||
return $result ? $result : "";
|
||||
return $result ? $result : $default;
|
||||
}
|
||||
|
||||
public static function getEnabledStreamData()
|
||||
{
|
||||
$streams = Array();
|
||||
$streamIds = self::getEnabledStreamIds();
|
||||
foreach ($streamIds as $id) {
|
||||
$streamData = self::getStreamData($id);
|
||||
$prefix = $id."_";
|
||||
$host = $streamData[$prefix."host"];
|
||||
$port = $streamData[$prefix."port"];
|
||||
$mount = $streamData[$prefix."mount"];
|
||||
$streams[$id] = Array(
|
||||
"url" => "http://$host:$port/$mount",
|
||||
"codec" => $streamData[$prefix."type"],
|
||||
"bitrate" => $streamData[$prefix."bitrate"],
|
||||
"mobile" => $streamData[$prefix."mobile"]
|
||||
);
|
||||
}
|
||||
return $streams;
|
||||
}
|
||||
|
||||
/* Returns the id's of all streams that are enabled in an array. An
|
||||
|
@ -83,50 +106,63 @@ class Application_Model_StreamSetting
|
|||
return $ids;
|
||||
}
|
||||
|
||||
/* Returns only global data as array*/
|
||||
public static function getGlobalData()
|
||||
{
|
||||
$con = Propel::getConnection();
|
||||
$sql = "SELECT * "
|
||||
."FROM cc_stream_setting "
|
||||
."WHERE keyname IN ('output_sound_device', 'icecast_vorbis_metadata')";
|
||||
|
||||
$rows = Application_Common_Database::prepareAndExecute($sql, array(), 'all');
|
||||
|
||||
$data = array();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$data[$row["keyname"]] = $row["value"];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/* Returns all information related to a specific stream. An example
|
||||
* of a stream id is 's1' or 's2'. */
|
||||
public static function getStreamData($p_streamId)
|
||||
{
|
||||
$con = Propel::getConnection();
|
||||
$streamId = pg_escape_string($p_streamId);
|
||||
$sql = "SELECT * "
|
||||
."FROM cc_stream_setting "
|
||||
."WHERE keyname LIKE '{$streamId}_%'";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
$rows = $stmt->fetchAll();
|
||||
} else {
|
||||
$msg = implode(',', $stmt->errorInfo());
|
||||
throw new Exception("Error: $msg");
|
||||
}
|
||||
$rows = CcStreamSettingQuery::create()
|
||||
->filterByDbKeyName("${p_streamId}_%")
|
||||
->find();
|
||||
|
||||
//This is way too much code because someone made only stupid decisions about how
|
||||
//the layout of this table worked. The git history doesn't lie.
|
||||
$data = array();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$data[$row["keyname"]] = $row["value"];
|
||||
$key = $row->getDbKeyName();
|
||||
$value = $row->getDbValue();
|
||||
$type = $row->getDbType();
|
||||
//Fix stupid defaults so we end up with proper typing in our JSON
|
||||
if ($row->getDbType() == "boolean") {
|
||||
if (empty($value)) {
|
||||
//In Python, there is no way to tell the difference between ints and booleans,
|
||||
//which we need to differentiate between for when we're generating the Liquidsoap
|
||||
//config file. Returning booleans as a string is a workaround that lets us do that.
|
||||
$value = "false";
|
||||
}
|
||||
$data[$key] = $value;
|
||||
}
|
||||
elseif ($row->getDbType() == "integer") {
|
||||
if (empty($value)) {
|
||||
$value = 0;
|
||||
}
|
||||
$data[$key] = intval($value);
|
||||
}
|
||||
else {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
//Add in defaults in case they don't exist in the database.
|
||||
$keyPrefix = $p_streamId . '_';
|
||||
self::ensureKeyExists($keyPrefix . 'admin_pass', $data);
|
||||
self::ensureKeyExists($keyPrefix . 'admin_user', $data);
|
||||
self::ensureKeyExists($keyPrefix . 'bitrate', $data, 128);
|
||||
self::ensureKeyExists($keyPrefix . 'channels', $data, "stereo");
|
||||
self::ensureKeyExists($keyPrefix . 'description', $data);
|
||||
self::ensureKeyExists($keyPrefix . 'enable', $data, "false");
|
||||
self::ensureKeyExists($keyPrefix . 'genre', $data);
|
||||
self::ensureKeyExists($keyPrefix . 'host', $data);
|
||||
self::ensureKeyExists($keyPrefix . 'liquidsoap_error', $data, "waiting");
|
||||
self::ensureKeyExists($keyPrefix . 'mount', $data);
|
||||
self::ensureKeyExists($keyPrefix . 'name', $data);
|
||||
self::ensureKeyExists($keyPrefix . 'output', $data);
|
||||
self::ensureKeyExists($keyPrefix . 'pass', $data);
|
||||
self::ensureKeyExists($keyPrefix . 'port', $data, 8000);
|
||||
self::ensureKeyExists($keyPrefix . 'type', $data);
|
||||
self::ensureKeyExists($keyPrefix . 'url', $data);
|
||||
self::ensureKeyExists($keyPrefix . 'user', $data);
|
||||
self::ensureKeyExists($keyPrefix . 'mobile', $data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
@ -134,76 +170,41 @@ class Application_Model_StreamSetting
|
|||
* make data easier to iterate over */
|
||||
public static function getStreamDataNormalized($p_streamId)
|
||||
{
|
||||
$con = Propel::getConnection();
|
||||
$streamId = pg_escape_string($p_streamId);
|
||||
$sql = "SELECT * "
|
||||
."FROM cc_stream_setting "
|
||||
."WHERE keyname LIKE '{$streamId}_%'";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
$rows = $stmt->fetchAll();
|
||||
} else {
|
||||
$msg = implode(',', $stmt->errorInfo());
|
||||
throw new Exception("Error: $msg");
|
||||
$settings = self::getStreamData($p_streamId);
|
||||
foreach ($settings as $key => $value)
|
||||
{
|
||||
unset($settings[$key]);
|
||||
$newKey = substr($key, strlen($p_streamId)+1); //$p_streamId is assumed to be the key prefix.
|
||||
$settings[$newKey] = $value;
|
||||
}
|
||||
return $settings;
|
||||
}
|
||||
|
||||
$data = array();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
list($id, $key) = explode("_", $row["keyname"], 2);
|
||||
$data[$key] = $row["value"];
|
||||
private static function ensureKeyExists($key, &$array, $default='')
|
||||
{
|
||||
if (!array_key_exists($key, $array)) {
|
||||
$array[$key] = $default;
|
||||
}
|
||||
|
||||
return $data;
|
||||
return $array;
|
||||
}
|
||||
|
||||
public static function getStreamSetting()
|
||||
{
|
||||
$con = Propel::getConnection();
|
||||
$sql = "SELECT *"
|
||||
." FROM cc_stream_setting"
|
||||
." WHERE keyname not like '%_error' AND keyname not like '%_admin_%'";
|
||||
|
||||
$rows = Application_Common_Database::prepareAndExecute($sql, array(), 'all');
|
||||
|
||||
$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;
|
||||
}
|
||||
$settings = array();
|
||||
$numStreams = MAX_NUM_STREAMS;
|
||||
for ($streamIdx = 1; $streamIdx <= $numStreams; $streamIdx++)
|
||||
{
|
||||
$settings = array_merge($settings, self::getStreamData("s" . $streamIdx));
|
||||
}
|
||||
|
||||
if (!isset($exists["master_live_stream_port"])) {
|
||||
$rows[] = array("keyname" =>"master_live_stream_port",
|
||||
"value"=>self::getMasterLiveStreamPort(),
|
||||
"type"=>"integer");
|
||||
}
|
||||
if (!isset($exists["master_live_stream_mp"])) {
|
||||
$rows[] = array("keyname" =>"master_live_stream_mp",
|
||||
"value"=>self::getMasterLiveStreamMountPoint(),
|
||||
"type"=>"string");
|
||||
}
|
||||
if (!isset($exists["dj_live_stream_port"])) {
|
||||
$rows[] = array("keyname" =>"dj_live_stream_port",
|
||||
"value"=>self::getDjLiveStreamPort(),
|
||||
"type"=>"integer");
|
||||
}
|
||||
if (!isset($exists["dj_live_stream_mp"])) {
|
||||
$rows[] = array("keyname" =>"dj_live_stream_mp",
|
||||
"value"=>self::getDjLiveStreamMountPoint(),
|
||||
"type"=>"string");
|
||||
}
|
||||
|
||||
return $rows;
|
||||
$settings["master_live_stream_port"] = self::getMasterLiveStreamPort();
|
||||
$settings["master_live_stream_mp"] = self::getMasterLiveStreamMountPoint();
|
||||
$settings["dj_live_stream_port"] = self::getDjLiveStreamPort();
|
||||
$settings["dj_live_stream_mp"] = self::getDjLiveStreamMountPoint();
|
||||
$settings["off_air_meta"] = self::getOffAirMeta();
|
||||
$settings["icecast_vorbis_metadata"] = self::getIcecastVorbisMetadata();
|
||||
$settings["output_sound_device"] = self::getOutputSoundDevice();
|
||||
$settings["output_sound_device_type"] = self::getOutputSoundDeviceType();
|
||||
return $settings;
|
||||
}
|
||||
|
||||
|
||||
|
@ -211,7 +212,10 @@ class Application_Model_StreamSetting
|
|||
{
|
||||
$stream_setting = CcStreamSettingQuery::create()->filterByDbKeyName($key)->findOne();
|
||||
if (is_null($stream_setting)) {
|
||||
throw new Exception("Keyname $key does not exist!");
|
||||
//throw new Exception("Keyname $key does not exist!");
|
||||
$stream_setting = new CcStreamSetting();
|
||||
$stream_setting->setDbKeyName($key);
|
||||
$stream_setting->setDbType("");
|
||||
}
|
||||
|
||||
$stream_setting->setDbValue($value);
|
||||
|
@ -411,7 +415,7 @@ class Application_Model_StreamSetting
|
|||
|
||||
public static function getMasterLiveStreamPort()
|
||||
{
|
||||
return self::getValue("master_live_stream_port");
|
||||
return self::getValue("master_live_stream_port", 8001);
|
||||
}
|
||||
|
||||
public static function setMasterLiveStreamMountPoint($value)
|
||||
|
@ -421,7 +425,7 @@ class Application_Model_StreamSetting
|
|||
|
||||
public static function getMasterLiveStreamMountPoint()
|
||||
{
|
||||
return self::getValue("master_live_stream_mp");
|
||||
return self::getValue("master_live_stream_mp", "/master");
|
||||
}
|
||||
|
||||
public static function setDjLiveStreamPort($value)
|
||||
|
@ -431,7 +435,7 @@ class Application_Model_StreamSetting
|
|||
|
||||
public static function getDjLiveStreamPort()
|
||||
{
|
||||
return self::getValue("dj_live_stream_port");
|
||||
return self::getValue("dj_live_stream_port", 8001);
|
||||
}
|
||||
|
||||
public static function setDjLiveStreamMountPoint($value)
|
||||
|
@ -441,7 +445,7 @@ class Application_Model_StreamSetting
|
|||
|
||||
public static function getDjLiveStreamMountPoint()
|
||||
{
|
||||
return self::getValue("dj_live_stream_mp");
|
||||
return self::getValue("dj_live_stream_mp", "/show");
|
||||
}
|
||||
|
||||
public static function getAdminUser($stream){
|
||||
|
@ -488,4 +492,16 @@ class Application_Model_StreamSetting
|
|||
public static function SetListenerStatError($key, $v) {
|
||||
self::setValue($key, $v, 'string');
|
||||
}
|
||||
|
||||
public static function getIcecastVorbisMetadata() {
|
||||
return self::getValue("icecast_vorbis_metadata", "");
|
||||
}
|
||||
|
||||
public static function getOutputSoundDevice() {
|
||||
return self::getValue("output_sound_device", "false");
|
||||
}
|
||||
|
||||
public static function getOutputSoundDeviceType() {
|
||||
return self::getValue("output_sound_device_type", "");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -430,6 +430,7 @@ class Application_Service_ShowFormService
|
|||
->filterByDbShowId($this->ccShow->getDbId())
|
||||
->filterByDbModifiedInstance(false)
|
||||
->filterByDbStarts(gmdate("Y-m-d H:i:s"), Criteria::GREATER_THAN)
|
||||
->orderByDbStarts()
|
||||
->findOne();
|
||||
|
||||
$starts = new DateTime($ccShowInstance->getDbStarts(), new DateTimeZone("UTC"));
|
||||
|
|
332
airtime_mvc/application/views/scripts/embed/player.phtml
Normal file
332
airtime_mvc/application/views/scripts/embed/player.phtml
Normal file
|
@ -0,0 +1,332 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="<?php echo $this->css?>" type="text/css">
|
||||
<script src="<?php echo $this->mrp_js?>" type="text/javascript"></script>
|
||||
<script src="<?php echo $this->jquery?>" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var MAX_MOBILE_SCREEN_WIDTH = 760;
|
||||
|
||||
// We are creating a custom player object that acts as a wrapper
|
||||
// around the player object we get from Muses. We are doing this
|
||||
// for a couple reasons:
|
||||
//
|
||||
// 1. It will be easier to swap out Muses for a different player engine
|
||||
// in the future - if we ever decide to do that.
|
||||
// 2. We had to add in some custom behaviour depending on the player
|
||||
// customizations and whether or not the player is in Flash or HTML5
|
||||
// mode.
|
||||
var MusesPlayer = function() {
|
||||
this.mobileDetect = this.mobileDetect();
|
||||
this.playerMode = "<?php echo $this->playerMode ?>";
|
||||
this.flashDetect = FlashDetect.versionAtLeast(10, 1) ? true : false;
|
||||
this.settings = {
|
||||
'volume': 100,
|
||||
'jsevents': true,
|
||||
'autoplay': false,
|
||||
'buffering': 0,
|
||||
'title': 'test',
|
||||
'bgcolor': '#FFFFFF',
|
||||
'skin': 'mcclean',
|
||||
'width': 180,
|
||||
'height': 60
|
||||
};
|
||||
|
||||
if (this.playerMode == "manual") {
|
||||
this.settings.url = <?php echo $this->streamURL ?>;
|
||||
this.settings.codec = "<?php echo $this->codec ?>";
|
||||
} else if (this.playerMode == "auto") {
|
||||
this.availableMobileStreamQueue = <?php echo $this->availableMobileStreams?>;
|
||||
this.availableDesktopStreamQueue = <?php echo $this->availableDesktopStreams?>;
|
||||
var stream = this.getNextAvailableStream();
|
||||
this.settings.url = stream["url"];
|
||||
this.settings.codec = stream["codec"];
|
||||
}
|
||||
|
||||
// Create the Muses player object
|
||||
MRP.insert(this.settings);
|
||||
|
||||
// Configure player title
|
||||
var player_title = <?php echo $this->player_title?>;
|
||||
if (player_title === null) {
|
||||
$(".airtime_header").hide();
|
||||
$(".airtime_player").css('height', '150px');
|
||||
} else {
|
||||
$("p.station_name").html(player_title);
|
||||
}
|
||||
|
||||
attachStreamMetadataToPlayer();
|
||||
|
||||
// detects events in HTML5 mode
|
||||
if (!this.flashDetect) {
|
||||
|
||||
MRP.html.audio.addEventListener('error', function failed(e) {
|
||||
switch (e.target.error.code) {
|
||||
case e.target.error.MEDIA_ERR_NETWORK:
|
||||
// If there is a network error keep retrying to connect
|
||||
// to a stream.
|
||||
var stream;
|
||||
if (musesPlayer.playerMode == "auto") {
|
||||
var nextAvailableStream = musesPlayer.getNextAvailableStream();
|
||||
stream = nextAvailableStream["url"];
|
||||
} else {
|
||||
stream = musesPlayer.settings.url;
|
||||
}
|
||||
var audio = $(MRP.html.audio);
|
||||
audio[0].src = stream;
|
||||
audio[0].load();
|
||||
audio[0].play();
|
||||
break;
|
||||
case e.target.error.MEDIA_ERR_DECODE:
|
||||
// If there was a corruption error or a problem with the browser
|
||||
// display an error and stop playback.
|
||||
togglePlayStopButton();
|
||||
clearTimeout(metadataTimer);
|
||||
$("p.now_playing").html("Error - Try again later");
|
||||
break;
|
||||
case e.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
|
||||
// If in auto mode and the current stream format is not supported
|
||||
// or the max number of listeners has been reached
|
||||
// retry connection with the next available stream.
|
||||
if (musesPlayer.playerMode == "auto") {
|
||||
var nextAvailableStream = musesPlayer.getNextAvailableStream();
|
||||
var audio = $(MRP.html.audio);
|
||||
audio[0].src = nextAvailableStream["url"];;
|
||||
audio[0].load();
|
||||
audio[0].play();
|
||||
} else {
|
||||
// If in manual mode and the current stream format is not supported
|
||||
// or the max number of listeners has been reached
|
||||
// display an error and stop play back.
|
||||
togglePlayStopButton();
|
||||
clearTimeout(metadataTimer);
|
||||
$("p.now_playing").html("Error - Try again later");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
togglePlayStopButton();
|
||||
clearTimeout(metadataTimer);
|
||||
$("p.now_playing").html("Error - Try again later");
|
||||
break;
|
||||
}
|
||||
|
||||
}, true);
|
||||
}
|
||||
};
|
||||
|
||||
MusesPlayer.prototype.mobileDetect = function() {
|
||||
return (screen.width <= MAX_MOBILE_SCREEN_WIDTH);
|
||||
}
|
||||
|
||||
// This function is called if an error occurs while a client is
|
||||
// attempting to connect to a stream (An error would occur if
|
||||
// the streams listener count has been maxed out or if the stream is down).
|
||||
// It checks if the client is a mobile device or not and returns the next
|
||||
// best available stream.
|
||||
MusesPlayer.prototype.getNextAvailableStream = function() {
|
||||
if (this.mobileDetect && this.availableMobileStreamQueue.length > 0) {
|
||||
return this.getNextAvailableMobileStream();
|
||||
}
|
||||
|
||||
if (!this.mobileDetect && this.availableDesktopStreamQueue.length > 0) {
|
||||
return this.getNextAvailableDesktopStream();
|
||||
}
|
||||
|
||||
// If we get to this point there are no available streams for the
|
||||
// type of device the client has connected with so just return
|
||||
// the next available stream - first we'll try the desktop streams
|
||||
var desktopStream = this.getNextAvailableDesktopStream();
|
||||
if (desktopStream) {
|
||||
return desktopStream;
|
||||
} else {
|
||||
return this.getNextAvailableMobileStream();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Gets and returns the next available mobile stream from the queue,
|
||||
// but adds it back to the end of the queue to be recycled.
|
||||
MusesPlayer.prototype.getNextAvailableMobileStream = function() {
|
||||
var stream = this.availableMobileStreamQueue.shift();
|
||||
//add to end of queue
|
||||
this.availableMobileStreamQueue.push(stream);
|
||||
return stream;
|
||||
}
|
||||
|
||||
// Gets and returns the next available desktop stream from the queue,
|
||||
// but adds it back to the end of the queue to be recycled.
|
||||
MusesPlayer.prototype.getNextAvailableDesktopStream = function() {
|
||||
var stream = this.availableDesktopStreamQueue.shift();
|
||||
//add to end of queue
|
||||
this.availableDesktopStreamQueue.push(stream);
|
||||
return stream;
|
||||
}
|
||||
|
||||
MusesPlayer.prototype.play = function() {
|
||||
this.flashDetect ? MRP.play() : musesHTMLPlayClick();;
|
||||
togglePlayStopButton();
|
||||
};
|
||||
|
||||
MusesPlayer.prototype.stop = function() {
|
||||
this.flashDetect ? MRP.stop() : musesHTMLStopClick();
|
||||
togglePlayStopButton();
|
||||
};
|
||||
|
||||
MusesPlayer.prototype.setURL = function(url) {
|
||||
MRP.setUrl(url);
|
||||
};
|
||||
|
||||
// detects errors in FLASH mode
|
||||
function musesCallback(event,value) {
|
||||
switch (event) {
|
||||
case "ioError":
|
||||
// problem connecting to stream
|
||||
var stream;
|
||||
if (musesPlayer.playerMode == "auto") {
|
||||
stream = musesPlayer.getNextAvailableStream();
|
||||
musesPlayer.setURL(stream["url"]);
|
||||
} else {
|
||||
stream = musesPlayer.settings.url;
|
||||
musesPlayer.setURL(stream);
|
||||
}
|
||||
musesPlayer.play();
|
||||
break;
|
||||
case "securityError":
|
||||
// max listeners reached
|
||||
if (musesPlayer.playerMode == "auto") {
|
||||
var stream = musesPlayer.getNextAvailableStream();
|
||||
musesPlayer.setURL(stream["url"]);
|
||||
musesPlayer.play();
|
||||
} else {
|
||||
// If in manual mode and there is a problem connecting to
|
||||
// the stream display an error and stop play back.
|
||||
MRP.stop();
|
||||
if ($("#play_button").hasClass("hide_button")) {
|
||||
togglePlayStopButton();
|
||||
}
|
||||
clearTimeout(metadataTimer);
|
||||
$("p.now_playing").html("Error - Try again later");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Triggers the play function on the Muses player object in HTML5 mode
|
||||
function musesHTMLPlayClick() {
|
||||
/*if (MRP.html === undefined) {
|
||||
console.log("inserting player");
|
||||
MRP.insert(musesPlayer.settings);
|
||||
}*/
|
||||
MRP.html.audio.src = MRP.html.src;
|
||||
|
||||
MRP.html.audio.play();
|
||||
}
|
||||
|
||||
// Triggers the stop function on the Muses player object in HTML5 mode
|
||||
// NOTE: The HTML5 audio element doesn't have stop functionality. It
|
||||
// can only be paused.
|
||||
function musesHTMLStopClick() {
|
||||
MRP.html.audio.pause();
|
||||
//delete MRP.html;
|
||||
}
|
||||
|
||||
function togglePlayStopButton() {
|
||||
document.getElementById("play_button").classList.toggle("hide_button");
|
||||
document.getElementById("stop_button").classList.toggle("hide_button");
|
||||
}
|
||||
|
||||
// variables for updating the player's metadata
|
||||
var time_to_next_track_starts;
|
||||
var metadataTimer;
|
||||
|
||||
// Fetches the streams metadata from the Airtime live-info API
|
||||
// and attaches it to the player UI.
|
||||
//
|
||||
// The metadata is fetched when the current track is about to end.
|
||||
function attachStreamMetadataToPlayer(){
|
||||
$.ajax({url: "<?php echo $this->metadata_api_url?>",
|
||||
data: {type:"interval",limit:"5"},
|
||||
dataType: "jsonp",
|
||||
success: function(data) {
|
||||
|
||||
if (data.current === null) {
|
||||
$("p.now_playing").html("Offline");
|
||||
time_to_next_track_starts = 20000;
|
||||
} else {
|
||||
var artist = data.current.name.split(" - ")[0];
|
||||
var track = data.current.name.split(" - ")[1];
|
||||
$("p.now_playing").html(artist + "<span>" + track + "</span>");
|
||||
|
||||
var current_track_end_time = new Date(data.current.ends);
|
||||
var current_time = new Date();
|
||||
//convert current_time to UTC to match the timezone of time_to_next_track_starts
|
||||
current_time = new Date(current_time.getTime() + current_time.getTimezoneOffset() * 60 * 1000);
|
||||
time_to_next_track_starts = current_track_end_time - current_time;
|
||||
}
|
||||
|
||||
if (data.next === null) {
|
||||
$("ul.schedule_list").find("li").html("Nothing scheduled");
|
||||
} else {
|
||||
$("ul.schedule_list").find("li").html(data.next.name);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
// Add 3 seconds to the timeout so Airtime has time to update the metadata before we fetch it
|
||||
metadataTimer = setTimeout(attachStreamMetadataToPlayer, time_to_next_track_starts+3000);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
/*
|
||||
We have to have the default Muses skin displayed on the page or else
|
||||
the player will not work. Setting the display to none and hidden does
|
||||
not work. It has to be "visible" on the page. As a hacky work around we
|
||||
set the height and width to 1px so users will not see it.
|
||||
*/
|
||||
#muses_skin{width:1px; height:1px; overflow-x: hidden; overflow-y: hidden;}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="airtime_player">
|
||||
|
||||
<div class="airtime_header">
|
||||
<p class="station_name">fff</p>
|
||||
</div>
|
||||
|
||||
<div class="airtime_box">
|
||||
|
||||
<div class="airtime_button">
|
||||
<span id="play_button" class="play_button" onclick="musesPlayer.play()"></span>
|
||||
<span id="stop_button" class="stop_button hide_button" onclick="musesPlayer.stop()"></span>
|
||||
</div>
|
||||
<p class="now_playing"></p>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="clear:both"></div>
|
||||
|
||||
<div class="airtime_schedule">
|
||||
<p class="airtime_next">Next</p>
|
||||
<ul class="schedule_list">
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
<a class="airtime_pro" target="_blank" href="https://www.airtime.pro/">Powered by <span class="airtime-pro-orange">Airtime Pro</span></a>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="muses_skin">
|
||||
<script type="text/javascript">
|
||||
var musesPlayer = new MusesPlayer();
|
||||
</script>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
29
airtime_mvc/application/views/scripts/form/player.phtml
Normal file
29
airtime_mvc/application/views/scripts/form/player.phtml
Normal file
|
@ -0,0 +1,29 @@
|
|||
<fieldset class="padded">
|
||||
<dl class="zend_form">
|
||||
|
||||
<?php echo $this->element->getElement('player_preview_label')->renderLabel(); ?>
|
||||
|
||||
<div style="clear:both"></div>
|
||||
<div class="player-preview">
|
||||
<?php echo $this->element->getElement('player_embed_src')->getValue(); ?>
|
||||
</div>
|
||||
|
||||
<div id="player_instructions">
|
||||
Customize the player by configuring the options below. When you are done,
|
||||
copy the embeddable code below and paste it into your website's HTML.
|
||||
</div>
|
||||
|
||||
<?php echo $this->element->getElement('player_title')->render(); ?>
|
||||
|
||||
<?php echo $this->element->getElement('player_stream_mode')->render(); ?>
|
||||
|
||||
<?php echo $this->element->getElement('player_stream_url'); ?>
|
||||
|
||||
<?php echo $this->element->getElement('player_embed_src')->render(); ?>
|
||||
|
||||
<?php //echo $this->element->getElement('player_display_track_metadata'); ?>
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
</fieldset>
|
|
@ -14,6 +14,13 @@
|
|||
<dd id="<?php echo $s_name?>Enabled-element">
|
||||
<?php echo $this->element->getElement('enable')?>
|
||||
</dd>
|
||||
|
||||
<dt id="<?php echo $s_name?>Mobile-label">
|
||||
<label for="<?php echo $s_name?>Mobile"><?php echo $this->element->getElement('mobile')->getLabel() ?></label>
|
||||
</dt>
|
||||
<dd id="<?php echo $s_name?>Mobile-element">
|
||||
<?php echo $this->element->getElement('mobile')?>
|
||||
</dd>
|
||||
|
||||
<dt id="<?php echo $s_name?>Type-label">
|
||||
<label for="<?php echo $s_name?>Type"><?php echo $this->element->getElement('type')->getLabel()?></label>
|
||||
|
|
12
airtime_mvc/application/views/scripts/player/customize.phtml
Normal file
12
airtime_mvc/application/views/scripts/player/customize.phtml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<div class="ui-widget ui-widget-content block-shadow simple-formblock embed-player-form clearfix padded-strong ">
|
||||
|
||||
<?php $baseUrl = Application_Common_OsPath::getBaseDir(); ?>
|
||||
|
||||
<form method="post" id="player_form" enctype="multipart/form-data">
|
||||
<h2 style="float:left"><?php echo _("Embeddable Player") ?></h2>
|
||||
<div style="clear:both"></div>
|
||||
<?php echo $this->errorMsg; ?>
|
||||
<?php echo $this->form; ?>
|
||||
|
||||
</form>
|
||||
</div>
|
Loading…
Add table
Add a link
Reference in a new issue