2016-05-06 10:57:34 +02:00
|
|
|
<?php
|
2016-12-16 21:49:07 +01:00
|
|
|
namespace AzuraCast\Radio\Frontend;
|
2016-05-06 10:57:34 +02:00
|
|
|
|
2017-05-16 09:46:43 +02:00
|
|
|
use App\Debug;
|
2016-05-19 16:56:21 +02:00
|
|
|
use App\Utilities;
|
2016-11-17 04:15:34 +01:00
|
|
|
use Doctrine\ORM\EntityManager;
|
|
|
|
use Entity\StationMount;
|
2017-05-18 09:24:54 +02:00
|
|
|
use Entity;
|
2016-05-06 10:57:34 +02:00
|
|
|
|
2016-09-29 03:36:22 +02:00
|
|
|
class IceCast extends FrontendAbstract
|
2016-05-06 10:57:34 +02:00
|
|
|
{
|
|
|
|
/* Process a nowplaying record. */
|
2017-01-24 01:17:50 +01:00
|
|
|
protected function _getNowPlaying(&$np)
|
|
|
|
{
|
|
|
|
$fe_config = (array)$this->station->frontend_config;
|
2017-05-16 09:46:43 +02:00
|
|
|
$reader = new \App\Xml\Reader();
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2017-05-16 09:46:43 +02:00
|
|
|
$radio_port = $fe_config['port'];
|
|
|
|
$np_url = 'http://localhost:' . $radio_port . '/admin/stats';
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2017-05-16 09:46:43 +02:00
|
|
|
Debug::log($np_url);
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2017-05-16 09:46:43 +02:00
|
|
|
$return_raw = $this->getUrl($np_url, [
|
|
|
|
'basic_auth' => 'admin:'.$fe_config['admin_pw'],
|
|
|
|
]);
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2017-01-24 01:35:16 +01:00
|
|
|
if (!$return_raw) {
|
2017-01-24 01:17:50 +01:00
|
|
|
return false;
|
2017-01-24 01:35:16 +01:00
|
|
|
}
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2017-05-16 09:46:43 +02:00
|
|
|
$return = $reader->fromString($return_raw);
|
|
|
|
Debug::print_r($return);
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2017-05-16 09:46:43 +02:00
|
|
|
if (!$return || empty($return['source'])) {
|
2017-01-24 01:17:50 +01:00
|
|
|
return false;
|
2017-01-24 01:35:16 +01:00
|
|
|
}
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2017-05-16 09:46:43 +02:00
|
|
|
$sources = $return['source'];
|
2017-05-18 09:24:54 +02:00
|
|
|
$mounts = (key($sources) === 0) ? $sources : [$sources];
|
2017-01-24 01:17:50 +01:00
|
|
|
|
|
|
|
|
2017-05-18 09:24:54 +02:00
|
|
|
$em = $this->di['em'];
|
|
|
|
$mount_repo = $em->getRepository(Entity\StationMount::class);
|
|
|
|
$default_mount = $mount_repo->getDefaultMount($this->station);
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2017-05-18 09:24:54 +02:00
|
|
|
$current_listeners = 0;
|
|
|
|
$unique_listeners = [];
|
|
|
|
$clients = [];
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2017-05-18 09:24:54 +02:00
|
|
|
$np['listeners']['clients'] = [];
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2017-05-18 09:24:54 +02:00
|
|
|
foreach($mounts as $mount) {
|
|
|
|
if ($mount['@mount'] === $default_mount->name) {
|
|
|
|
$song_data = $mount;
|
|
|
|
}
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2017-05-18 09:24:54 +02:00
|
|
|
$current_listeners += $mount['listeners'];
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2017-05-16 09:46:43 +02:00
|
|
|
// Attempt to fetch detailed listener information for better unique statistics.
|
2017-05-18 09:24:54 +02:00
|
|
|
$listeners_url = 'http://localhost:' . $radio_port . '/admin/listclients?mount='.urlencode($mount['@mount']);
|
2017-05-16 09:46:43 +02:00
|
|
|
$return_raw = $this->getUrl($listeners_url, [
|
|
|
|
'basic_auth' => 'admin:'.$fe_config['admin_pw'],
|
|
|
|
]);
|
|
|
|
|
|
|
|
if (!empty($return_raw)) {
|
|
|
|
$listeners_raw = $reader->fromString($return_raw);
|
|
|
|
|
|
|
|
if (!empty($listeners_raw['source']['listener']))
|
|
|
|
{
|
2017-05-18 09:24:54 +02:00
|
|
|
$listeners = $listeners_raw['source']['listener'];
|
|
|
|
$listeners = (key($listeners) === 0) ? $listeners : [$listeners];
|
2017-05-16 09:46:43 +02:00
|
|
|
|
|
|
|
foreach($listeners as $listener) {
|
2017-05-18 09:24:54 +02:00
|
|
|
$client = [
|
2017-05-16 09:46:43 +02:00
|
|
|
'uid' => $listener['ID'],
|
|
|
|
'ip' => $listener['IP'],
|
|
|
|
'user_agent' => $listener['UserAgent'],
|
|
|
|
'connected_seconds' => $listener['Connected'],
|
|
|
|
];
|
2017-05-18 09:24:54 +02:00
|
|
|
|
|
|
|
$client_hash = Entity\Listener::getListenerHash($client);
|
|
|
|
$unique_listeners[$client_hash] = $client_hash;
|
|
|
|
$clients[] = $client;
|
2017-05-16 09:46:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-18 09:24:54 +02:00
|
|
|
$unique_listeners = count($unique_listeners);
|
|
|
|
|
|
|
|
$np['listeners'] = [
|
|
|
|
'current' => $this->getListenerCount($unique_listeners, $current_listeners),
|
|
|
|
'unique' => $unique_listeners,
|
|
|
|
'total' => $current_listeners,
|
|
|
|
'clients' => $clients,
|
|
|
|
];
|
|
|
|
|
|
|
|
if (isset($song_data['artist'])) {
|
|
|
|
$np['current_song'] = [
|
|
|
|
'artist' => $song_data['artist'],
|
|
|
|
'title' => $song_data['title'],
|
|
|
|
'text' => $song_data['artist'] . ' - ' . $song_data['title'],
|
|
|
|
];
|
|
|
|
} else {
|
|
|
|
$np['current_song'] = $this->getSongFromString($song_data['title'], ' - ');
|
|
|
|
}
|
|
|
|
|
|
|
|
$np['meta']['status'] = 'online';
|
|
|
|
$np['meta']['bitrate'] = $song_data['bitrate'];
|
|
|
|
$np['meta']['format'] = $song_data['server_type'];
|
|
|
|
|
2017-01-24 01:17:50 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-05-06 10:57:34 +02:00
|
|
|
public function read()
|
|
|
|
{
|
2016-05-07 11:13:17 +02:00
|
|
|
$config = $this->_getConfig();
|
|
|
|
|
2016-11-06 00:37:09 +01:00
|
|
|
$this->station->frontend_config = $this->_loadFromConfig($config);
|
2017-01-24 01:35:16 +01:00
|
|
|
|
2016-05-07 11:13:17 +02:00
|
|
|
return true;
|
2016-05-06 10:57:34 +02:00
|
|
|
}
|
|
|
|
|
2017-01-24 01:17:50 +01:00
|
|
|
public function write()
|
|
|
|
{
|
|
|
|
$config = $this->_getDefaults();
|
|
|
|
|
|
|
|
$frontend_config = (array)$this->station->frontend_config;
|
|
|
|
|
2017-01-24 01:35:16 +01:00
|
|
|
if (!empty($frontend_config['port'])) {
|
2017-01-24 01:17:50 +01:00
|
|
|
$config['listen-socket']['port'] = $frontend_config['port'];
|
2017-01-24 01:35:16 +01:00
|
|
|
}
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2017-01-24 01:35:16 +01:00
|
|
|
if (!empty($frontend_config['source_pw'])) {
|
2017-01-24 01:17:50 +01:00
|
|
|
$config['authentication']['source-password'] = $frontend_config['source_pw'];
|
2017-01-24 01:35:16 +01:00
|
|
|
}
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2017-01-24 01:35:16 +01:00
|
|
|
if (!empty($frontend_config['admin_pw'])) {
|
2017-01-24 01:17:50 +01:00
|
|
|
$config['authentication']['admin-password'] = $frontend_config['admin_pw'];
|
2017-01-24 01:35:16 +01:00
|
|
|
}
|
2017-01-24 01:17:50 +01:00
|
|
|
|
2017-01-24 01:35:16 +01:00
|
|
|
if (!empty($frontend_config['streamer_pw'])) {
|
|
|
|
foreach ($config['mount'] as &$mount) {
|
|
|
|
if (!empty($mount['password'])) {
|
2017-01-24 01:17:50 +01:00
|
|
|
$mount['password'] = $frontend_config['streamer_pw'];
|
2017-01-24 01:35:16 +01:00
|
|
|
}
|
2017-01-24 01:17:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-24 01:18:27 +02:00
|
|
|
if (!empty($frontend_config['max_listeners'])) {
|
|
|
|
$config['limits']['clients'] = $frontend_config['max_listeners'];
|
|
|
|
}
|
|
|
|
|
2017-01-24 01:35:16 +01:00
|
|
|
if (!empty($frontend_config['custom_config'])) {
|
2017-01-24 01:17:50 +01:00
|
|
|
$custom_conf = $this->_processCustomConfig($frontend_config['custom_config']);
|
2017-01-24 01:35:16 +01:00
|
|
|
if (!empty($custom_conf)) {
|
2017-01-24 01:17:50 +01:00
|
|
|
$config = \App\Utilities::array_merge_recursive_distinct($config, $custom_conf);
|
2017-01-24 01:35:16 +01:00
|
|
|
}
|
2017-01-24 01:17:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set any unset values back to the DB config.
|
|
|
|
$this->station->frontend_config = $this->_loadFromConfig($config);
|
|
|
|
|
|
|
|
$em = $this->di['em'];
|
|
|
|
$em->persist($this->station);
|
|
|
|
$em->flush();
|
|
|
|
|
|
|
|
$config_path = $this->station->getRadioConfigDir();
|
2017-01-24 01:35:16 +01:00
|
|
|
$icecast_path = $config_path . '/icecast.xml';
|
2017-01-24 01:17:50 +01:00
|
|
|
|
|
|
|
$writer = new \App\Xml\Writer;
|
|
|
|
$icecast_config_str = $writer->toString($config, 'icecast');
|
|
|
|
|
|
|
|
// Strip the first line (the XML charset)
|
2017-01-24 01:35:16 +01:00
|
|
|
$icecast_config_str = substr($icecast_config_str, strpos($icecast_config_str, "\n") + 1);
|
2017-01-24 01:17:50 +01:00
|
|
|
|
|
|
|
file_put_contents($icecast_path, $icecast_config_str);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Process Management
|
|
|
|
*/
|
|
|
|
|
|
|
|
public function getCommand()
|
|
|
|
{
|
2017-01-24 01:35:16 +01:00
|
|
|
$config_path = $this->station->getRadioConfigDir() . '/icecast.xml';
|
|
|
|
|
2017-05-17 06:11:45 +02:00
|
|
|
return '/usr/local/bin/icecast -c ' . $config_path;
|
2017-01-24 01:17:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getStreamUrl()
|
|
|
|
{
|
|
|
|
/** @var EntityManager */
|
|
|
|
$em = $this->di->get('em');
|
|
|
|
|
|
|
|
$mount_repo = $em->getRepository(StationMount::class);
|
|
|
|
$default_mount = $mount_repo->getDefaultMount($this->station);
|
|
|
|
|
|
|
|
$mount_name = ($default_mount instanceof StationMount) ? $default_mount->name : '/radio.mp3';
|
2017-01-24 01:35:16 +01:00
|
|
|
|
2017-01-24 01:17:50 +01:00
|
|
|
return $this->getUrlForMount($mount_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getStreamUrls()
|
|
|
|
{
|
|
|
|
$urls = [];
|
2017-01-24 01:35:16 +01:00
|
|
|
foreach ($this->station->mounts as $mount) {
|
2017-01-24 01:17:50 +01:00
|
|
|
$urls[] = $this->getUrlForMount($mount->name);
|
2017-01-24 01:35:16 +01:00
|
|
|
}
|
2017-01-24 01:17:50 +01:00
|
|
|
|
|
|
|
return $urls;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getUrlForMount($mount_name)
|
|
|
|
{
|
2017-01-24 01:35:16 +01:00
|
|
|
return $this->getPublicUrl() . $mount_name . '?' . time();
|
2017-01-24 01:17:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getAdminUrl()
|
|
|
|
{
|
2017-01-24 01:35:16 +01:00
|
|
|
return $this->getPublicUrl() . '/admin/';
|
2017-01-24 01:17:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Configuration
|
|
|
|
*/
|
|
|
|
|
2016-05-06 10:57:34 +02:00
|
|
|
protected function _getConfig()
|
|
|
|
{
|
2016-05-19 16:56:21 +02:00
|
|
|
$config_path = $this->station->getRadioConfigDir();
|
2017-01-24 01:35:16 +01:00
|
|
|
$icecast_path = $config_path . '/icecast.xml';
|
2016-05-19 16:56:21 +02:00
|
|
|
|
|
|
|
$defaults = $this->_getDefaults();
|
|
|
|
|
2017-01-24 01:35:16 +01:00
|
|
|
if (file_exists($icecast_path)) {
|
2016-05-19 16:56:21 +02:00
|
|
|
$reader = new \App\Xml\Reader;
|
|
|
|
$data = $reader->fromFile($icecast_path);
|
|
|
|
|
|
|
|
return Utilities::array_merge_recursive_distinct($defaults, $data);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $defaults;
|
|
|
|
}
|
|
|
|
|
2017-01-24 01:17:50 +01:00
|
|
|
protected function _loadFromConfig($config)
|
|
|
|
{
|
|
|
|
$frontend_config = (array)$this->station->frontend_config;
|
|
|
|
|
|
|
|
return [
|
|
|
|
'custom_config' => $frontend_config['custom_config'],
|
|
|
|
'port' => $config['listen-socket']['port'],
|
|
|
|
'source_pw' => $config['authentication']['source-password'],
|
|
|
|
'admin_pw' => $config['authentication']['admin-password'],
|
|
|
|
'streamer_pw' => $config['mount'][0]['password'],
|
2017-05-24 01:18:27 +02:00
|
|
|
'max_listeners' => $config['limits']['clients'],
|
2017-01-24 01:17:50 +01:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2016-05-19 16:56:21 +02:00
|
|
|
protected function _getDefaults()
|
|
|
|
{
|
|
|
|
$config_dir = $this->station->getRadioConfigDir();
|
2016-05-06 10:57:34 +02:00
|
|
|
|
2016-11-17 04:15:34 +01:00
|
|
|
$defaults = [
|
2016-09-03 03:41:17 +02:00
|
|
|
'location' => 'AzuraCast',
|
2016-05-19 16:56:21 +02:00
|
|
|
'admin' => 'icemaster@localhost',
|
2016-09-27 22:39:54 +02:00
|
|
|
'hostname' => $this->di['em']->getRepository('Entity\Settings')->getSetting('base_url', 'localhost'),
|
2016-05-19 16:56:21 +02:00
|
|
|
'limits' => [
|
2017-05-24 01:18:27 +02:00
|
|
|
'clients' => 250,
|
2016-09-01 20:22:23 +02:00
|
|
|
'sources' => 3,
|
2017-05-17 06:11:45 +02:00
|
|
|
// 'threadpool' => 5,
|
2016-05-19 16:56:21 +02:00
|
|
|
'queue-size' => 524288,
|
|
|
|
'client-timeout' => 30,
|
|
|
|
'header-timeout' => 15,
|
|
|
|
'source-timeout' => 10,
|
2017-05-17 06:11:45 +02:00
|
|
|
// 'burst-on-connect' => 1,
|
2016-05-19 16:56:21 +02:00
|
|
|
'burst-size' => 65535,
|
|
|
|
],
|
|
|
|
'authentication' => [
|
|
|
|
'source-password' => Utilities::generatePassword(),
|
|
|
|
'relay-password' => Utilities::generatePassword(),
|
|
|
|
'admin-user' => 'admin',
|
|
|
|
'admin-password' => Utilities::generatePassword(),
|
|
|
|
],
|
2016-09-02 06:53:50 +02:00
|
|
|
|
2016-05-19 16:56:21 +02:00
|
|
|
'listen-socket' => [
|
2016-09-02 22:37:12 +02:00
|
|
|
'port' => $this->_getRadioPort(),
|
2016-05-19 16:56:21 +02:00
|
|
|
],
|
2016-09-02 06:53:50 +02:00
|
|
|
|
2016-11-17 04:15:34 +01:00
|
|
|
'mount' => [],
|
2016-05-19 16:56:21 +02:00
|
|
|
'fileserve' => 1,
|
|
|
|
'paths' => [
|
2017-05-17 06:11:45 +02:00
|
|
|
'basedir' => '/usr/local/share/icecast',
|
2016-05-19 16:56:21 +02:00
|
|
|
'logdir' => $config_dir,
|
2017-05-17 06:11:45 +02:00
|
|
|
'webroot' => '/usr/local/share/icecast/web',
|
|
|
|
'adminroot' => '/usr/local/share/icecast/admin',
|
2017-01-24 01:35:16 +01:00
|
|
|
'pidfile' => $config_dir . '/icecast.pid',
|
2017-05-17 06:11:45 +02:00
|
|
|
'x-forwarded-for' => '127.0.0.1',
|
2016-05-19 16:56:21 +02:00
|
|
|
'alias' => [
|
|
|
|
'@source' => '/',
|
2017-05-17 06:11:45 +02:00
|
|
|
'@dest' => '/status.xsl',
|
2016-05-19 16:56:21 +02:00
|
|
|
],
|
|
|
|
],
|
|
|
|
'logging' => [
|
|
|
|
'accesslog' => 'icecast_access.log',
|
|
|
|
'errorlog' => 'icecast_error.log',
|
|
|
|
'loglevel' => 3,
|
|
|
|
'logsize' => 10000,
|
|
|
|
],
|
|
|
|
'security' => [
|
|
|
|
'chroot' => 0,
|
|
|
|
],
|
|
|
|
];
|
2016-11-17 04:15:34 +01:00
|
|
|
|
2017-01-24 01:35:16 +01:00
|
|
|
foreach ($this->station->mounts as $mount_row) {
|
2016-11-17 04:15:34 +01:00
|
|
|
$mount = [
|
2017-01-24 01:35:16 +01:00
|
|
|
'@type' => 'normal',
|
2016-11-17 04:15:34 +01:00
|
|
|
'mount-name' => $mount_row->name,
|
|
|
|
];
|
|
|
|
|
2017-01-24 01:35:16 +01:00
|
|
|
if (!empty($mount_row->fallback_mount)) {
|
2016-11-17 04:15:34 +01:00
|
|
|
$mount['fallback-mount'] = $mount_row->fallback_mount;
|
|
|
|
$mount['fallback-override'] = 1;
|
|
|
|
}
|
|
|
|
|
2017-01-24 01:35:16 +01:00
|
|
|
if ($mount_row->frontend_config) {
|
2016-11-17 21:36:05 +01:00
|
|
|
$mount_conf = $this->_processCustomConfig($mount_row->frontend_config);
|
2017-01-24 01:35:16 +01:00
|
|
|
if (!empty($mount_conf)) {
|
2016-11-17 19:26:43 +01:00
|
|
|
$mount = \App\Utilities::array_merge_recursive_distinct($mount, $mount_conf);
|
2017-01-24 01:35:16 +01:00
|
|
|
}
|
2016-11-17 19:26:43 +01:00
|
|
|
}
|
|
|
|
|
2017-04-13 00:19:02 +02:00
|
|
|
if ($mount_row->relay_url) {
|
|
|
|
$relay_parts = parse_url($mount_row->relay_url);
|
|
|
|
|
|
|
|
$defaults['relay'][] = [
|
|
|
|
'server' => $relay_parts['host'],
|
|
|
|
'port' => $relay_parts['port'],
|
|
|
|
'mount' => $relay_parts['path'],
|
|
|
|
'local-mount' => $mount_row->name,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2016-11-17 19:26:43 +01:00
|
|
|
$defaults['mount'][] = $mount;
|
2016-11-17 04:15:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return $defaults;
|
2016-05-06 10:57:34 +02:00
|
|
|
}
|
2017-05-19 08:38:17 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Static Functions
|
|
|
|
*/
|
|
|
|
|
|
|
|
public static function isInstalled()
|
|
|
|
{
|
|
|
|
return file_exists('/usr/local/bin/icecast');
|
|
|
|
}
|
2016-05-06 10:57:34 +02:00
|
|
|
}
|