\",\"artwork\":\"\",\"genre\":\"\",\"comment\":\"\"}");
sysCmd('curl -s -X GET http://localhost/command/?cmd=renderui');
}
}
function wrk_stopAirplay($redis)
{
$activePlayer = $redis->get('activePlayer');
if ($activePlayer == 'Airplay') {
$stoppedPlayer = $redis->get('stoppedPlayer');
runelog('stoppedPlayer = ', $stoppedPlayer);
if ($stoppedPlayer !== '') {
// we previously stopped playback of one player to use the Airport Stream
if ($stoppedPlayer === 'MPD') {
// connect to MPD daemon
$sock = openMpdSocket('/run/mpd.sock', 0);
$status = _parseStatusResponse(MpdStatus($sock));
runelog('MPD status', $status);
if ($status['state'] === 'pause') {
// clear the stopped player if we left MPD paused
$redis->set('stoppedPlayer', '');
}
//sendMpdCommand($sock, 'pause');
// to get MPD out of its idle-loop we discribe to a channel
sendMpdCommand($sock, 'subscribe Airplay');
sendMpdCommand($sock, 'unsubscribe Airplay');
closeMpdSocket($sock);
// debug
//runelog('sendMpdCommand', 'pause');
} elseif ($stoppedPlayer === 'Spotify') {
// connect to SPOPD daemon
$sock = openSpopSocket('localhost', 6602, 1);
$status = _parseSpopStatusResponse(SpopStatus($sock));
runelog('SPOP status', $status);
if ($status['state'] === 'pause') {
// clear the stopped player if we left SPOP paused
$redis->set('stoppedPlayer', '');
}
// to get SPOP out of its idle-loop
sendSpopCommand($sock, 'notify');
//sendSpopCommand($sock, 'toggle');
closeSpopSocket($sock);
// debug
//runelog('sendSpopCommand', 'toggle');
}
// set the active player back to the one we stopped
$redis->set('activePlayer', $stoppedPlayer);
//delete all files in shairport folder except "now_playing"
$dir = '/var/run/shairport/';
$leave_files = array('now_playing');
foreach( glob("$dir/*") as $file ) {
if( !in_array(basename($file), $leave_files) )
unlink($file);
}
}
runelog('endFunction!!!', $stoppedPlayer);
sysCmd('curl -s -X GET http://localhost/command/?cmd=renderui');
}
}
function wrk_playerID($arch)
{
// $playerid = $arch.md5(uniqid(rand(), true)).md5(uniqid(rand(), true));
$playerid = $arch.md5_file('/sys/class/net/eth0/address');
return $playerid;
}
function wrk_switchplayer($redis, $playerengine)
{
switch ($playerengine) {
case 'MPD':
$return = sysCmd('systemctl start mpd');
usleep(500000);
if ($redis->hGet('lastfm','enable') === '1') sysCmd('systemctl start mpdscribble');
if ($redis->hGet('dlna','enable') === '1') sysCmd('systemctl start upmpdcli');
$redis->set('activePlayer', 'MPD');
$return = sysCmd('systemctl stop spopd');
$return = sysCmd('curl -s -X GET http://localhost/command/?cmd=renderui');
// set process priority
sysCmdAsync('rune_prio nice');
break;
case 'Spotify':
$return = sysCmd('systemctl start spopd');
usleep(500000);
if ($redis->hGet('lastfm','enable') === '1') sysCmd('systemctl stop mpdscribble');
if ($redis->hGet('dlna','enable') === '1') sysCmd('systemctl stop upmpdcli');
$redis->set('activePlayer', 'Spotify');
$return = sysCmd('systemctl stop mpd');
$redis->set('mpd_playback_status', 'stop');
$return = sysCmd('curl -s -X GET http://localhost/command/?cmd=renderui');
// set process priority
sysCmdAsync('rune_prio nice');
break;
}
return $return;
}
function wrk_sysAcl()
{
sysCmd('chown -R http.http /srv/http/');
sysCmd('chmod 777 /run');
sysCmd('chmod 644 $(find /srv/http/ -type f)');
sysCmd('chmod 755 $(find /srv/http/ -type d)');
sysCmd('chmod 755 /srv/http/command/*');
sysCmd('chmod 755 /srv/http/db/redis_datastore_setup');
sysCmd('chmod 755 /srv/http/db/redis_acards_details');
sysCmd('chown -R mpd.audio /var/lib/mpd');
}
function wrk_NTPsync($ntpserver)
{
//debug
runelog('NTP SERVER', $ntpserver);
// if (sysCmd('ntpdate '.$ntpserver)) {
if (sysCmdAsync('ntpdate '.$ntpserver)) {
return $ntpserver;
} else {
return false;
}
}
function wrk_changeHostname($redis, $newhostname)
{
$hn = sysCmd('hostname');
runelog('current hostname', $hn[0]);
// change system hostname
sysCmd('hostnamectl set-hostname '.$newhostname);
// restart avahi-daemon
sysCmd('systemctl restart avahi-daemon');
// reconfigure MPD
sysCmd('systemctl stop mpd');
// update zeroconfname in MPD configuration
$redis->hMset('mpdconf','zeroconf_name', $newhostname);
// update airplayname
if ($redis->hGet('airplay','name') === $hn[0]) {
$redis->hSet('airplay','name', $newhostname);
if ($redis->hGet('airplay','enable') === '1') sysCmd('systemctl restart shairport');
}
// update AVAHI serice data
wrk_avahiconfig($newhostname);
// rewrite mpd.conf file
wrk_mpdconf('/etc', $redis);
// restart MPD
sysCmd('systemctl start mpd');
// restart SAMBA << TODO: use systemd!!!
sysCmd('killall -HUP smbd && killall -HUP nmbd');
// TODO: restart MiniDLNA
// set process priority
sysCmdAsync('sleep 1 && rune_prio nice');
}
function wrk_upmpdcli($redis, $name = null)
{
if (!isset($name)) {
$name = $redis->hGet('dlna', 'name');
}
$file = '/usr/lib/systemd/system/upmpdcli.service';
$newArray = wrk_replaceTextLine($file, '', 'ExecStart', 'ExecStart=/usr/bin/upmpdcli -d '.$redis->hGet('dlna', 'logfile').' -l '.$redis->hGet('dlna', 'loglevel').' -f '.$name);
runelog('upmpdcli.service :', $newArray);
// Commit changes to /usr/lib/systemd/system/upmpdcli.service
$fp = fopen($file, 'w');
fwrite($fp, implode("", $newArray));
fclose($fp);
// update systemd
sysCmd('systemctl daemon-reload');
if ($redis->hGet('dlna','enable') === '1') {
runelog('restart upmpdcli');
sysCmd('systemctl restart upmpdcli');
}
// set process priority
sysCmdAsync('sleep 1 && rune_prio nice');
}
function alsa_findHwMixerControl($cardID)
{
$cmd = "amixer -c ".$cardID." |grep \"mixer control\"";
$str = sysCmd($cmd);
$hwmixerdev = substr(substr($str[0], 0, -(strlen($str[0]) - strrpos($str[0], "'"))), strpos($str[0], "'")+1);
runelog('Try to find HwMixer control (str): ', $str);
runelog('Try to find HwMixer control: (output)', $hwmixerdev);
return $hwmixerdev;
}
// webradio management (via .pls files)
function addRadio($mpd, $redis, $data)
{
if ($data->label !== '' && $data->url !== '') {
//debug
runelog('addRadio (data)', $data);
// store webradio record in redis
$redis->hSet('webradios', $data->label, $data->url);
// create new file
// $file = '/mnt/MPD/Webradio/'.$data['label'].'.pls';
$file = '/mnt/MPD/Webradio/'.$data->label.'.pls';
$newpls = "[playlist]\n";
$newpls .= "NumberOfEntries=1\n";
$newpls .= "File1=".$data->url."\n";
$newpls .= "Title1=".$data->label;
// Commit changes to .pls file
$fp = fopen($file, 'w');
$return = fwrite($fp, $newpls);
fclose($fp);
if ($return) sendMpdCommand($mpd, 'update Webradio');
} else {
$return = false;
}
return $return;
}
function editRadio($mpd,$redis,$data)
{
if ($data->label !== '' && $data->url !== '') {
//debug
runelog('editRadio (data)', $data);
// edit webradio URL in .pls file
$file = '/mnt/MPD/Webradio/'.$data->label.'.pls';
if ($data->label !== $data->newlabel) {
unlink($file);
// delete old webradio record in redis
$redis->hDel('webradios', $data->label);
// store new webradio record in redis
$data->label = $data->newlabel;
$data->newlabel = null;
$return = addRadio($mpd, $redis, $data);
} else {
$redis->hSet('webradios',$data->label,$data->url);
$newArray = wrk_replaceTextLine($file, '', 'File1=', 'File1='.$data->url, 'NumberOfEntries=1',1);
// Commit changes to .pls file
$fp = fopen($file, 'w');
$return = fwrite($fp, implode("", $newArray));
fclose($fp);
}
if ($return) sendMpdCommand($mpd, 'update Webradio');
} else {
$return = false;
}
return $return;
}
function deleteRadio($mpd,$redis,$data)
{
if ($data->label !== '') {
//debug
runelog('deleteRadio (data)', $data);
// delete .pls file
$file = '/mnt/MPD/Webradio/'.$data->label;
$label = parseFileStr($data->label, '.', 1);
runelog('deleteRadio (label)', $label);
$return = unlink($file);
if ($return) {
// delete webradio record in redis
$redis->hDel('webradios', $label);
sendMpdCommand($mpd, 'update Webradio');
}
} else {
$return = false;
}
return $return;
}
function ui_notify($title = null, $text, $type = null, $permanotice = null)
{
if (is_object($permanotice)) {
$output = array('title' => $title, 'permanotice' => '', 'permaremove' => '');
} else {
if ($permanotice === 1) {
$output = array('title' => $title, 'text' => $text, 'permanotice' => '');
} else {
$output = array('title' => $title, 'text' => $text);
}
}
ui_render('notify', json_encode($output));
}
function ui_notify_async($title = null, $text, $type = null, $permanotice = null)
{
if (is_object($permanotice)) {
$output = array('title' => $title, 'permanotice' => '', 'permaremove' => '');
} else {
if ($permanotice === 1) {
$output = array('title' => $title, 'text' => $text, 'permanotice' => '');
} else {
$output = array('title' => $title, 'text' => $text);
}
}
$output = json_encode($output);
runelog('notify (async) JSON string: ', $output);
sysCmdAsync('/var/www/command/ui_notify.php \''.$output);
}
function wrk_notify($redis, $action, $notification, $jobID = null)
{
switch ($action) {
case 'raw':
// debug
runelog('wrk_notify (raw)', $notification);
break;
case 'startjob':
if (!empty($notification)) {
if (is_object($notification)) {
$notification = json_encode(array('title' => $notification->title, 'text' => $notification->text, 'icon' => 'fa fa-cog fa-spin', 'permanotice' => $jobID));
// debug
runelog('wrk_notify (startjob) jobID='.$jobID, $notification);
}
if (wrk_notify_check($notification)) {
if (empty($redis->hGet('notifications', $jobID)) && empty($redis->hGet('notifications', 'permanotice_'.$jobID))) {
$redis->hSet('notifications', $jobID, $notification);
}
}
}
break;
case 'endjob':
$notification = $redis->hGet('notifications', $jobID);
if (!empty($notification)) {
$notification = json_decode($notification);
$notification = json_encode(array('title' => $notification->title, 'text' => '', 'permanotice' => $jobID, 'permaremove' => $jobID));
// debug
runelog('wrk_notify (endjob) jobID='.$jobID, $notification);
$redis->hDel('notifications', $jobID);
}
break;
case 'kernelswitch':
// debug
runelog('wrk_notify (kernelswitch) jobID='.$jobID, $notification);
if (!empty($notification)) {
$notification = json_encode(array('title' => $notification->title, 'text' => $notification->text, 'custom' => 'kernelswitch'));
if (wrk_notify_check($notification)) {
// if (empty($redis->hGet('notifications', $jobID)) && empty($redis->hGet('notifications', 'permanotice_'.$jobID))) {
$redis->hSet('notifications', 'permanotice_kernelswitch', $notification);
// }
}
}
break;
}
if (wrk_notify_check($notification)) ui_render('notify', $notification);
}
function wrk_notify_check($notification)
{
if (json_decode($notification) !== null) {
$notification = json_decode($notification);
if (isset($notification->title) && isset($notification->text)) {
return true;
} else {
return false;
}
} else {
return false;
}
}
class ui_renderQueue
{
public function __construct($socket)
{
$this->socket = $socket;
}
public function output()
{
$queue = getPlayQueue($this->socket);
ui_render('queue', json_encode($queue));
}
}
function ui_status($mpd, $status)
{
$curTrack = getTrackInfo($mpd, $status['song']);
if (isset($curTrack[0]['Title'])) {
$status['currentartist'] = $curTrack[0]['Artist'];
$status['currentsong'] = htmlentities($curTrack[0]['Title'], ENT_XML1, 'UTF-8');
$status['currentalbum'] = $curTrack[0]['Album'];
$status['fileext'] = parseFileStr($curTrack[0]['file'], '.');
} else {
$path = parseFileStr($curTrack[0]['file'], '/');
$status['fileext'] = parseFileStr($curTrack[0]['file'], '.');
$status['currentartist'] = "";
// $status['currentsong'] = $song;
if (!empty($path)) {
$status['currentalbum'] = $path;
} else {
$status['currentalbum'] = '';
}
}
$status['file'] = $curTrack[0]['file'];
$status['radioname'] = $curTrack[0]['Name'];
return $status;
}
function ui_libraryHome($redis)
{
// LocalStorage
$localStorages = countDirs('/mnt/MPD/LocalStorage');
// runelog('networkmounts: ',$networkmounts);
// Network mounts
$networkmounts = countDirs('/mnt/MPD/NAS');
// runelog('networkmounts: ',$networkmounts);
// USB mounts
$usbmounts = countDirs('/mnt/MPD/USB');
// runelog('usbmounts: ',$usbmounts);
// Webradios
$webradios = count($redis->hKeys('webradios'));
// runelog('webradios: ',$webradios);
// Dirble
$proxy = $redis->hGetall('proxy');
$dirblecfg = $redis->hGetAll('dirble');
$dirble = json_decode(curlGet($dirblecfg['baseurl'].'amountStation/apikey/'.$dirblecfg['apikey'], $proxy));
// runelog('dirble: ',$dirble);
// Spotify
$spotify = $redis->hGet('spotify', 'enable');
// Check current player backend
$activePlayer = $redis->get('activePlayer');
// Bookmarks
$redis_bookmarks = $redis->hGetAll('bookmarks');
$bookmarks = array();
foreach ($redis_bookmarks as $key => $data) {
$bookmark = json_decode($data);
runelog('bookmark details', $data);
// $bookmarks[] = array('bookmark' => $key, 'name' => $bookmark->name, 'path' => $bookmark->path);
$bookmarks[] = array('id' => $key, 'name' => $bookmark->name, 'path' => $bookmark->path);
}
// runelog('bookmarks: ',$bookmarks);
// $jsonHome = json_encode(array_merge($bookmarks, array(0 => array('networkMounts' => $networkmounts)), array(0 => array('USBMounts' => $usbmounts)), array(0 => array('webradio' => $webradios)), array(0 => array('Dirble' => $dirble->amount)), array(0 => array('ActivePlayer' => $activePlayer))));
// $jsonHome = json_encode(array_merge($bookmarks, array(0 => array('networkMounts' => $networkmounts)), array(0 => array('USBMounts' => $usbmounts)), array(0 => array('webradio' => $webradios)), array(0 => array('Spotify' => $spotify)), array(0 => array('Dirble' => $dirble->amount)), array(0 => array('ActivePlayer' => $activePlayer))));
$jsonHome = json_encode(array('bookmarks' => $bookmarks, 'localStorages' => $localStorages, 'networkMounts' => $networkmounts, 'USBMounts' => $usbmounts, 'webradio' => $webradios, 'Spotify' => $spotify, 'Dirble' => $dirble->amount, 'ActivePlayer' => $activePlayer));
// Encode UI response
runelog('libraryHome JSON: ', $jsonHome);
ui_render('library', $jsonHome);
}
function ui_lastFM_coverart($artist, $album, $lastfm_apikey, $proxy)
{
if (!empty($album)) {
$url = "http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=".$lastfm_apikey."&artist=".urlencode($artist)."&album=".urlencode($album)."&format=json";
unset($artist);
} else {
$url = "http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&api_key=".$lastfm_apikey."&artist=".urlencode($artist)."&format=json";
$artist = 1;
}
// debug
//echo $url;
$output = json_decode(curlGet($url, $proxy), true);
// debug
runelog('coverart lastfm query URL', $url);
// debug++
// echo "";
// print_r($output);
// echo "
";
// key [3] == extralarge last.fm image
// key [4] == mega last.fm image
if(isset($artist)) {
runelog('coverart lastfm query URL', $output['artist']['image'][3]['#text']);
return $output['artist']['image'][3]['#text'];
} else {
runelog('coverart lastfm query URL', $output['album']['image'][3]['#text']);
return $output['album']['image'][3]['#text'];
}
}
// populate queue with similiar tracks suggested by Last.fm
function ui_lastFM_similar($artist, $track, $lastfm_apikey, $proxy)
{
runelog('similar lastfm artist', $artist);
runelog('similar lastfm track', $track);
runelog('similar lastfm name', $proxy);
runelog('similar lastfm lastfm_api', $lastfm_api);
// This makes the call to Last.fm. The limit parameter can be adjusted to the number of tracks you want returned.
// [TODO] adjustable amount of tracks in settings screen
$url = "http://ws.audioscrobbler.com/2.0/?method=track.getsimilar&limit=1000&api_key=".$lastfm_apikey."&artist=".urlencode($artist)."&track=".urlencode($track)."&format=json";
runelog('similar lastfm query URL', $url);
// debug
//echo $url;
// This call does not work
//$output = json_decode(curlGet($url, $proxy), true);
// But these 2 lines do
$content = file_get_contents($url);
$output = json_decode($content,true);
// debug
// debug++
// echo "";
// print_r($output);
// echo "
";
foreach($output['similartracks']['track'] as $similar) {
$simtrack = $similar['name'];
$simartist = $similar['artist']['name'];
if (strlen($simtrack)>0 and strlen($simartist)>0) {
// If we have a track and an artist then make a call to mpd to add it. If it doesn't exist then it doesn't
// matter
$status = sysCmd("mpc search artist '".$simartist."' title '".$simtrack. "' | head -n1 | mpc add");
}
}
}
// push UI update to NGiNX channel
function ui_render($channel, $data)
{
curlPost('http://127.0.0.1/pub?id='.$channel, $data);
}
function ui_timezone() {
$zones_array = array();
$timestamp = time();
foreach(timezone_identifiers_list() as $key => $zone) {
date_default_timezone_set($zone);
$zones_array[$key]['zone'] = $zone;
$zones_array[$key]['diff_from_GMT'] = 'GMT ' . date('P', $timestamp);
}
return $zones_array;
}
function ui_update($redis ,$sock)
{
ui_libraryHome($redis);
switch ($redis->get('activePlayer')) {
case 'MPD':
if ($redis->get('pl_length') !== '0') {
sendMpdCommand($sock, 'swap 0 0');
} else {
sendMpdCommand($sock, 'clear');
}
// return MPD response
return readMpdResponse($sock);
break;
case 'Spotify':
sendSpopCommand($sock, 'repeat');
sendSpopCommand($sock, 'repeat');
// return SPOP response
return readSpopResponse($sock);
break;
}
}
function ui_mpd_response($mpd, $notify = null)
{
runelog('ui_mpd_response invoked');
$response = json_encode(readMpdResponse($mpd));
// --- TODO: check this condition
if (strpos($response, "OK") && isset($notify)) {
runelog('send UI notify: ', $notify);
ui_notify($notify['title'], $notify['text']);
}
echo $response;
}
function curlPost($url, $data, $proxy = null)
{
$ch = curl_init($url);
@curl_setopt($curl, CURLOPT_HTTPHEADER, array("Connection: close"));
@curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
@curl_setopt($curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
if (isset($proxy)) {
if ($proxy['enable'] === '1') {
$proxy['user'] === '' || @curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy['user'].':'.$proxy['pass']);
@curl_setopt($ch, CURLOPT_PROXY, $proxy['host']);
//runelog('cURL proxy HOST: ',$proxy['host']);
//runelog('cURL proxy USER: ',$proxy['user']);
//runelog('cURL proxy PASS: ',$proxy['pass']);
}
}
@curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 400);
@curl_setopt($ch, CURLOPT_TIMEOUT, 2);
@curl_setopt($ch, CURLOPT_POST, 1);
@curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
@curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
@curl_setopt($ch, CURLOPT_HEADER, 0); // DO NOT RETURN HTTP HEADERS
@curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // RETURN THE CONTENTS OF THE CALL
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
function curlGet($url, $proxy = null)
{
$ch = curl_init($url);
@curl_setopt($curl, CURLOPT_HTTPHEADER, array("Connection: close"));
@curl_setopt($ch, CURLOPT_NOSIGNAL, 1);
@curl_setopt($curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
if (isset($proxy)) {
if ($proxy['enable'] === '1') {
$proxy['user'] === '' || @curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy['user'].':'.$proxy['pass']);
@curl_setopt($ch, CURLOPT_PROXY, $proxy['host']);
// runelog('cURL proxy HOST: ',$proxy['host']);
// runelog('cURL proxy USER: ',$proxy['user']);
// runelog('cURL proxy PASS: ',$proxy['pass']);
}
}
@curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 400);
@curl_setopt($ch, CURLOPT_TIMEOUT, 10);
@curl_setopt($ch, CURLOPT_HEADER, 0);
@curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
function countDirs($basepath)
{
$scandir = scandir($basepath."/", SCANDIR_SORT_NONE);
$count = count(array_diff($scandir, array('..', '.')));
return $count;
}
function netmask($bitcount)
{
$netmask = str_split(str_pad(str_pad('', $bitcount, '1'), 32, '0'), 8);
foreach ($netmask as &$element) $element = bindec($element);
return join('.', $netmask);
}
// sort multi-dimensional array by key
function osort(&$array, $key)
{
usort($array, function($a, $b) use ($key) {
return $a->$key > $b->$key ? 1 : -1;
});
}