libretime/legacy/application/models/StreamSetting.php

559 lines
18 KiB
PHP

<?php
define('MAX_NUM_STREAMS', 4);
class Application_Model_StreamSetting
{
public static function setValue($key, $value, $type)
{
$con = Propel::getConnection();
// Check if key already exists
$sql = 'SELECT COUNT(*) FROM cc_stream_setting'
. ' WHERE keyname = :key';
$stmt = $con->prepare($sql);
$stmt->bindParam(':key', $key);
if ($stmt->execute()) {
$result = $stmt->fetchColumn(0);
} else {
$msg = implode(',', $stmt->errorInfo());
throw new Exception("Error: {$msg}");
}
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)';
}
$stmt = $con->prepare($sql);
$stmt->bindParam(':key', $key);
$stmt->bindParam(':value', $value);
$stmt->bindParam(':type', $type);
if ($stmt->execute()) {
// do nothing
} else {
$msg = implode(',', $stmt->errorInfo());
throw new Exception("Error: {$msg}");
}
}
public static function getValue($key, $default = '')
{
$con = Propel::getConnection();
// Check if key already exists
$sql = 'SELECT value FROM cc_stream_setting'
. ' WHERE keyname = :key';
$stmt = $con->prepare($sql);
$stmt->bindParam(':key', $key);
if ($stmt->execute()) {
$result = $stmt->fetchColumn(0);
} else {
$msg = implode(',', $stmt->errorInfo());
throw new Exception("Error: {$msg}");
}
return $result ? $result : $default;
}
public static function getEnabledStreamData()
{
$streams = [];
$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'];
if ($streamData[$prefix . 'output'] == 'shoutcast') {
$url = "http://{$host}:{$port}/;"; // The semi-colon is important to make Shoutcast stream URLs play instead turn into a page.
} else { // Icecast
$url = "http://{$host}:{$port}/{$mount}";
}
$streams[$id] = [
'url' => $url,
'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
* example of the array returned in JSON notation is ["s1", "s2", "s3"] */
public static function getEnabledStreamIds()
{
$con = Propel::getConnection();
$sql = 'SELECT * '
. 'FROM cc_stream_setting '
. "WHERE keyname LIKE '%_enable' "
. "AND value='true'";
$ids = [];
$rows = Application_Common_Database::prepareAndExecute($sql, [], 'all');
foreach ($rows as $row) {
$ids[] = substr($row['keyname'], 0, strpos($row['keyname'], '_'));
}
return $ids;
}
/* Returns all information related to a specific stream. An example
* of a stream id is 's1' or 's2'. */
public static function getStreamData($p_streamId)
{
$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 = [];
foreach ($rows as $row) {
$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;
}
/* Similar to getStreamData, but removes all sX prefixes to
* make data easier to iterate over */
public static function getStreamDataNormalized($p_streamId)
{
$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;
}
private static function ensureKeyExists($key, &$array, $default = '')
{
if (!array_key_exists($key, $array)) {
$array[$key] = $default;
}
return $array;
}
public static function getStreamSetting()
{
$settings = [];
$numStreams = MAX_NUM_STREAMS;
for ($streamIdx = 1; $streamIdx <= $numStreams; ++$streamIdx) {
$settings = array_merge($settings, self::getStreamData('s' . $streamIdx));
}
$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;
}
private static function saveStreamSetting($key, $value)
{
$stream_setting = CcStreamSettingQuery::create()->filterByDbKeyName($key)->findOne();
if (is_null($stream_setting)) {
// throw new Exception("Keyname $key does not exist!");
$stream_setting = new CcStreamSetting();
$stream_setting->setDbKeyName($key);
$stream_setting->setDbType('');
}
$stream_setting->setDbValue($value);
$stream_setting->save();
}
/*
* function that take all the information of stream and sets them.
* This is used by stream setting via UI.
*
* @param $data - array that contains all the data. $data is [][] which
* contains multiple stream information
*/
public static function setStreamSetting($data)
{
foreach ($data as $key => $d) {
if ($key == 'output_sound_device' || $key == 'icecast_vorbis_metadata') {
$v = ($d == 1) ? 'true' : 'false';
self::saveStreamSetting($key, $v);
} elseif ($key == 'output_sound_device_type') {
self::saveStreamSetting($key, $d);
} elseif (is_array($d)) {
$temp = explode('_', $key);
$prefix = $temp[0];
// SAAS-876 - If we're using Airtime Pro streaming, set the stream to use the default settings
if (!Application_Model_Preference::getUsingCustomStreamSettings()) {
$d = array_merge($d, static::getDefaults($prefix));
}
foreach ($d as $k => $v) {
$keyname = $prefix . '_' . $k;
if ($k == 'enable') {
$v = $d['enable'] == 1 ? 'true' : 'false';
}
$v = trim($v);
if ($k != 'admin_pass') {
self::saveStreamSetting($keyname, $v);
/* We use 'xxxxxx' as the admin password placeholder so we
* only want to save it when it is a different string
*/
} elseif ($v != 'xxxxxx') {
self::saveStreamSetting($keyname, $v);
}
}
}
}
}
/**
* SAAS-876 - Get the default stream settings values for Airtime Pro streaming.
*
* @param int $prefix
*
* @return array array of default stream setting values
*/
public static function getDefaults($prefix)
{
$config = Config::getConfig();
return [
'host' => $config['baseUrl'],
'port' => DEFAULT_ICECAST_PORT,
'output' => 'icecast',
'user' => $config['stationId'],
'pass' => Application_Model_Preference::getDefaultIcecastPassword(),
// Manually setting default mountpoint
'mount' => 'airtime_128',
];
}
/*
* Sets individual stream setting.
*
* $data - data array. $data is [].
* TODO: Make this SQL a prepared statement!
*
* Do not remove this function. It is called by airtime-system.php
*/
public static function setIndividualStreamSetting($data)
{
foreach ($data as $keyname => $v) {
$sql = 'UPDATE cc_stream_setting SET value=:v WHERE keyname=:keyname';
$map = [':v' => $v, ':keyname' => $keyname];
$res = Application_Common_Database::prepareAndExecute(
$sql,
$map,
Application_Common_Database::EXECUTE
);
}
}
/*
* Stores liquidsoap status if $boot_time > save time.
* save time is the time that user clicked save on stream setting page
*/
public static function setLiquidsoapError($stream_id, $msg, $boot_time = null)
{
$con = Propel::getConnection();
$update_time = Application_Model_Preference::GetStreamUpdateTimestemp();
if ($boot_time == null || $boot_time > $update_time) {
$keyname = 's' . $stream_id . '_liquidsoap_error';
$sql = 'SELECT COUNT(*) FROM cc_stream_setting'
. ' WHERE keyname = :keyname';
$stmt = $con->prepare($sql);
$stmt->bindParam(':keyname', $keyname);
if ($stmt->execute()) {
$result = $stmt->fetchColumn(0);
} else {
$msg = implode(',', $stmt->errorInfo());
throw new Exception("Error: {$msg}");
}
if ($result == 1) {
$sql = 'UPDATE cc_stream_setting'
. ' SET value = :msg'
. ' WHERE keyname = :keyname';
} else {
$sql = 'INSERT INTO cc_stream_setting (keyname, value, type)'
. " VALUES (:keyname, :msg, 'string')";
}
$stmt = $con->prepare($sql);
$stmt->bindParam(':keyname', $keyname);
$stmt->bindParam(':msg', $msg);
if ($stmt->execute()) {
// do nothing
} else {
$msg = implode(',', $stmt->errorInfo());
throw new Exception("Error: {$msg}");
}
}
}
public static function getLiquidsoapError($stream_id)
{
$con = Propel::getConnection();
$keyname = 's' . $stream_id . '_liquidsoap_error';
$sql = 'SELECT value FROM cc_stream_setting'
. ' WHERE keyname = :keyname';
$stmt = $con->prepare($sql);
$stmt->bindParam(':keyname', $keyname);
if ($stmt->execute()) {
$result = $stmt->fetchColumn(0);
} else {
$msg = implode(',', $stmt->errorInfo());
throw new Exception("Error: {$msg}");
}
return ($result !== false) ? $result : null;
}
public static function getStreamEnabled($stream_id)
{
$con = Propel::getConnection();
$keyname = 's' . $stream_id . '_enable';
$sql = 'SELECT value FROM cc_stream_setting'
. ' WHERE keyname = :keyname';
$stmt = $con->prepare($sql);
$stmt->bindParam(':keyname', $keyname);
if ($stmt->execute()) {
$result = $stmt->fetchColumn(0);
} else {
$msg = implode(',', $stmt->errorInfo());
throw new Exception("Error: {$msg}");
}
return $result != 'false';
}
/*
* Only returns info that is needed for data collection
* returns array('s1'=>array(keyname=>value))
*/
public static function getStreamInfoForDataCollection()
{
$con = Propel::getConnection();
$out = [];
$enabled_stream = self::getEnabledStreamIds();
foreach ($enabled_stream as $stream) {
$keys = ["{$stream}_output", "{$stream}_type", "{$stream}_bitrate", "{$stream}_host"];
$key_csv = implode(',', $keys);
$sql = 'SELECT keyname, value FROM cc_stream_setting'
. ' WHERE keyname IN (:key_csv)';
$stmt = $con->prepare($sql);
$stmt->bindParam(':key_csv', $key_csv);
if ($stmt->execute()) {
$rows = $stmt->fetchAll();
} else {
$msg = implode(',', $stmt->errorInfo());
throw new Exception("Error: {$msg}");
}
$info = [];
foreach ($rows as $r) {
$temp = explode('_', $r['keyname']);
$info[$temp[1]] = $r['value'];
$out[$stream] = $info;
}
}
return $out;
}
public static function setMasterLiveStreamPort($value)
{
self::setValue('master_live_stream_port', $value, 'integer');
}
public static function getMasterLiveStreamPort()
{
return self::getValue('master_live_stream_port', 8001);
}
public static function setMasterLiveStreamMountPoint($value)
{
self::setValue('master_live_stream_mp', $value, 'string');
}
public static function getMasterLiveStreamMountPoint()
{
return self::getValue('master_live_stream_mp', '/master');
}
public static function setDjLiveStreamPort($value)
{
self::setValue('dj_live_stream_port', $value, 'integer');
}
public static function getDjLiveStreamPort()
{
return self::getValue('dj_live_stream_port', 8002);
}
public static function setDjLiveStreamMountPoint($value)
{
self::setValue('dj_live_stream_mp', $value, 'string');
}
public static function getDjLiveStreamMountPoint()
{
return self::getValue('dj_live_stream_mp', '/show');
}
public static function getAdminUser($stream)
{
return self::getValue($stream . '_admin_user');
}
public static function setAdminUser($stream, $v)
{
self::setValue($stream . '_admin_user', $v, 'string');
}
public static function getAdminPass($stream)
{
return self::getValue($stream . '_admin_pass');
}
public static function setAdminPass($stream, $v)
{
self::setValue($stream . '_admin_pass', $v, 'string');
}
public static function getOffAirMeta()
{
return self::getValue('off_air_meta');
}
public static function setOffAirMeta($offAirMeta)
{
self::setValue('off_air_meta', $offAirMeta, 'string');
}
public static function GetAllListenerStatErrors()
{
$sql = 'SELECT * FROM cc_stream_setting WHERE keyname like :p1';
$mounts = Application_Common_Database::prepareAndExecute($sql, [':p1' => '%_mount']);
$mps = [];
foreach ($mounts as $mount) {
$mps[] = "'" . $mount['value'] . "_listener_stat_error'";
}
$in = implode(',', $mps);
$sql = "SELECT * FROM cc_stream_setting WHERE keyname IN ( {$in} )";
return Application_Common_Database::prepareAndExecute($sql, []);
}
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', '');
}
}