Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions etc/systemd/system/sendspin.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[Unit]
Description=Sendspin Multi Room Audio Client
After=network-online.target
Requires=network-online.target

[Service]
Type=simple
ExecStart=/root/.local/share/uv/tools/sendspin/bin/sendspin daemon
Restart=on-failure
User=root

[Install]
WantedBy=multi-user.target
28 changes: 14 additions & 14 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ gulp.task('patchheader', function (done) {
.pipe($.if('header.php', $.cacheBust({
type: 'timestamp'
})) )
.pipe($.if(!(mode.test()||mode.remote()), $.chown('root','root')))
.pipe($.if(!(mode.test()||mode.remote()), $.chown(0o644)))
.pipe($.size({showFiles: true, total: false}))
.pipe(gulp.dest(DEPLOY_LOCATION))
.on('end', done);
Expand All @@ -415,7 +415,7 @@ gulp.task('patchfooter', function (done) {
.pipe($.rename(function (path) {
path.basename += '.min';
}))
.pipe($.if(!(mode.test()||mode.remote()), $.chown('root','root')))
.pipe($.if(!(mode.test()||mode.remote()), $.chown(0o644)))
.pipe($.size({showFiles: true, total: false}))
.pipe(gulp.dest(DEPLOY_LOCATION))
.on('end', done);
Expand All @@ -426,7 +426,7 @@ gulp.task('patchindex', function (done) {
.pipe($.if(!mode.force(), $.newer( { dest: pkg.app.dist})))
.pipe($.replace(/indextpl[.]html/g, "indextpl.min.html"))
.pipe($.replace(/footer[.]php/g, "footer.min.php"))
.pipe($.if(!(mode.test()||mode.remote()), $.chown('root','root')))
.pipe($.if(!(mode.test()||mode.remote()), $.chown(0o644)))
.pipe($.size({showFiles: true, total: false}))
.pipe(gulp.dest(DEPLOY_LOCATION))
.on('end', done);
Expand All @@ -436,7 +436,7 @@ gulp.task('patchconfigs', function (done) {
return gulp.src(pkg.app.src+'/*-config.php')
.pipe($.if(!mode.force(), $.newer( { dest: pkg.app.dist})))
.pipe($.replace(/footer[.]php/g, "footer.min.php"))
.pipe($.if(!(mode.test()||mode.remote()), $.chown('root','root')))
.pipe($.if(!(mode.test()||mode.remote()), $.chown(0o644)))
.pipe($.size({showFiles: true, total: false}))
.pipe(gulp.dest(DEPLOY_LOCATION))
.on('end', done);
Expand All @@ -451,18 +451,18 @@ gulp.task('minifyhtml', function (done) {
.pipe($.rename(function (path) {
path.basename += '.min';
}))
.pipe($.if(!(mode.test()||mode.remote()), $.chown('root','root')))
.pipe($.if(!(mode.test()||mode.remote()), $.chown(0o644)))
.pipe($.size({showFiles: true, total: false}))
.pipe(gulp.dest(DEPLOY_LOCATION+'/templates'))
.on('end', done);
});

gulp.task('artwork', function(done) {
gulp.src([ pkg.app.src+'/webfonts/**/*'
,pkg.app.src+'/fonts/**/*'
,pkg.app.src+'/images/**/*' ], {base:pkg.app.src})
.pipe($.if(!mode.force(), $.newer( { dest: pkg.app.dest})))
// .pipe($.size({showFiles: true, total: true}))
gulp.src([ pkg.app.src+'/webfonts/**/*',
pkg.app.src+'/fonts/**/*',
pkg.app.src+'/images/**/*' ], {base: pkg.app.src, encoding: false})
.pipe($.if(!mode.force(), $.newer( { dest: pkg.app.dest })))
// .pipe($.size({showFiles: true, total: true}))
.pipe(gulp.dest(pkg.app.dest));
done();
});
Expand Down Expand Up @@ -511,11 +511,11 @@ gulp.task('deployback', gulp.series(['patchheader','patchfooter', 'patchindex',
,'!'+pkg.app.src+'/*-config.php'
,pkg.app.src+'/css/shellinabox*.css'
],
{base: pkg.app.src})
{base: pkg.app.src, encoding: false})
// optional headers fields can be update and or added:
//.pipe( $.replaceTask({ patterns: REPLACEMENT_PATTERNS }))
//.pipe($.if('*.html', $.header(banner_html, {pkg: pkg}) ))
.pipe($.if(!(mode.test()||mode.remote()), $.chown('root','root')))
.pipe($.if(!(mode.test()||mode.remote()), $.chown(0o644)))
.pipe($.if(!mode.force(), $.newer( { dest: DEPLOY_LOCATION})))
//.pipe($.size({showFiles: true, total: true}))
.pipe($.if('*.html', $.replaceTask({ patterns: REPLACEMENT_PATTERNS})))
Expand All @@ -525,9 +525,9 @@ gulp.task('deployback', gulp.series(['patchheader','patchfooter', 'patchindex',
}));

gulp.task('deployfront', function (done) {
return gulp.src( [pkg.app.dest+'/**/*', '!'+pkg.app.dest+'/index.html'] )
return gulp.src( [pkg.app.dest+'/**/*', '!'+pkg.app.dest+'/index.html'], { encoding: false } )
.pipe($.if(!mode.force(), $.newer( { dest: DEPLOY_LOCATION})))
.pipe($.if(!(mode.test()||mode.remote()), $.chown('root','root')))
.pipe($.if(!(mode.test()||mode.remote()), $.chown(0o644)))
.pipe(gulp.dest(DEPLOY_LOCATION))
.on('end', done);
});
Expand Down
28 changes: 21 additions & 7 deletions usr/local/bin/moodeutl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ $features = array(
FEAT_BLUETOOTH => 'Bluetooth renderer',
FEAT_DEVTWEAKS => 'Developer tweaks',
FEAT_MULTIROOM => 'Multiroom audio',
FEAT_PEPPYDISPLAY => 'PeppyMeter display'
FEAT_PEPPYDISPLAY => 'PeppyMeter display',
FEAT_SENDSPIN => 'Sendspin renderer'
);

$featBitmask = trim(shell_exec('sqlite3 ' . SQLDB_PATH . " \"SELECT value FROM cfg_system WHERE param='feat_bitmask'\""));
Expand Down Expand Up @@ -160,7 +161,7 @@ switch ($option) {
echo VERSION . "\n";
break;
case '--help':
//[--bluetooth | --airplay | --spotify | --deezer | --upnp | --squeezelite | --plexamp | --roonbridge]
//[--bluetooth | --airplay | --spotify | --deezer | --upnp | --squeezelite | --plexamp | --roonbridge | --sendspin]
$btArg = $featBitmask & FEAT_BLUETOOTH ? '--bluetooth | ' : '';
$apArg = $featBitmask & FEAT_AIRPLAY ? '--airplay | ' : '';
$spArg = $featBitmask & FEAT_SPOTIFY ? '--spotify | ' : '';
Expand All @@ -169,7 +170,8 @@ switch ($option) {
$slArg = $featBitmask & FEAT_SQUEEZELITE ? '--squeezelite | ' : '';
$paArg = $featBitmask & FEAT_PLEXAMP ? '--plexamp | ' : '';
$rbArg = $featBitmask & FEAT_ROONBRIDGE ? '--roonbridge | ' : '';
$rendererList = rtrim($btArg . $apArg . $spArg . $dzArg . $upArg . $slArg . $paArg . $rbArg, ' | ');
$ssArg = $featBitmask & FEAT_SENDSPIN ? '--sendspin | ' : '';
$rendererList = rtrim($btArg . $apArg . $spArg . $dzArg . $upArg . $slArg . $paArg . $rbArg . $ssArg, ' | ');
echo
"Usage: moodeutl [OPTION]
Moode utility functions
Expand Down Expand Up @@ -545,12 +547,24 @@ function stopAllRenderers($featBitmask) {
} else {
echo "- roonbridge\t\tfeature disabled\n";
}

if ($featBitmask & FEAT_SENDSPIN) {
$sendspinSvc = trim(shell_exec('sqlite3 ' . SQLDB_PATH . " \"SELECT value FROM cfg_system WHERE param='sendspinsvc'\""));
if ($sendspinSvc == '1') {
sysCmd('/var/www/util/restart-renderer.php --sendspin --stop');
echo "- sendspin\t\tstopped\n";
} else {
echo "- sendspin\t\tnot on\n";
}
} else {
echo "- sendspin\t\tfeature disabled\n";
}
}

function restartRenderer($argv) {
$renderers = array('--bluetooth' => 'btsvc', '--airplay' => 'airplaysvc', '--spotify' => 'spotifysvc',
'--deezer' => 'deezersvc', '--upnp' => 'upnpsvc', '--squeezelite' => 'slsvc', '--plexamp' => 'pasvc',
'--roonbridge' => 'rbsvc');
'--roonbridge' => 'rbsvc', '--sendspin' => 'sendspinsvc');

if (!isset($argv[2])) {
echo 'Missing 2nd argument [renderer name]' . "\n";
Expand All @@ -564,7 +578,7 @@ function restartRenderer($argv) {
}
} else {
echo 'Invalid renderer name' . "\n";
echo 'Valid names are: --bluetooth, --airplay, --spotify, --deezer, --upnp, --squeezelite, --plexamp, --roonbridge' . "\n";
echo 'Valid names are: --bluetooth, --airplay, --spotify, --deezer, --upnp, --squeezelite, --plexamp, --roonbridge, --sendspin' . "\n";
return;
}

Expand All @@ -575,14 +589,14 @@ function restartRenderer($argv) {
function rendererOnoff($argv) {
$renderers = array('--bluetooth' => 'btsvc', '--airplay' => 'airplaysvc', '--spotify' => 'spotifysvc',
'--deezer' => 'deezersvc', '--upnp' => 'upnpsvc', '--squeezelite' => 'slsvc', '--plexamp' => 'pasvc',
'--roonbridge' => 'rbsvc');
'--roonbridge' => 'rbsvc', '--sendspin' => 'sendspinsvc');

if (!isset($argv[2])) {
echo 'Missing 2nd argument [renderer name]' . "\n";
return;
} else if (!array_key_exists($argv[2], $renderers)) {
echo 'Invalid renderer name' . "\n";
echo 'Valid names are: --bluetooth, --airplay, --spotify, --deezer, --upnp, --squeezelite, --plexamp, --roonbridge' . "\n";
echo 'Valid names are: --bluetooth, --airplay, --spotify, --deezer, --upnp, --squeezelite, --plexamp, --roonbridge, --sendspin' . "\n";
return;
} else if (!isset($argv[3])) {
echo 'Missing 3nd argument [on|off]' . "\n";
Expand Down
2 changes: 2 additions & 0 deletions var/local/www/db/moode-sqlite3.db.sql
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,8 @@ INSERT INTO cfg_system (id, param, value) VALUES (172, 'library_onetouch_pl', 'S
INSERT INTO cfg_system (id, param, value) VALUES (173, 'scnsaver_mode', 'Cover art');
INSERT INTO cfg_system (id, param, value) VALUES (174, 'scnsaver_layout', 'Default');
INSERT INTO cfg_system (id, param, value) VALUES (175, 'scnsaver_xmeta', 'Yes');
INSERT INTO cfg_system (id, param, value) VALUES (176, 'sendspinsvc', '0');
INSERT INTO cfg_system (id, param, value) VALUES (177, 'sendspin_installed', 'no');

-- Table: cfg_theme
CREATE TABLE cfg_theme (id INTEGER PRIMARY KEY, theme_name CHAR (32), tx_color CHAR (32), bg_color CHAR (32), mbg_color CHAR (32));
Expand Down
36 changes: 35 additions & 1 deletion www/daemon/worker.php
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,18 @@
}
workerLog('worker: RoonBridge: ' . $msg);

// Sendspin
// Installer: https://github.com/Sendspin/sendspin-cli/blob/main/scripts/systemd/install-systemd.sh
if (file_exists('/home/sendspin/.local/share/uv/tools/sendspin/bin/sendspin') === true) {
$msg = 'installed';
$_SESSION['sendspin_installed'] = 'yes';

} else {
$_SESSION['sendspin_installed'] = 'no';
$msg = 'not installed';
}
workerLog('worker: Sendspin: ' . $msg);

// Allo Boss 2
// OLED: The Allo installer adds lines to rc.local which are not needed because we start/stop it via systemd unit
if (!empty(sysCmd('grep "boss2" /etc/rc.local')[0])) {
Expand Down Expand Up @@ -1196,6 +1208,23 @@
}
workerLog('worker: RoonBridge: ' . $status);

// Start Sendspin renderer
if ($_SESSION['feat_bitmask'] & FEAT_SENDSPIN) {
if ($_SESSION['sendspin_installed'] == 'yes') {
if (isset($_SESSION['sendspinsvc']) && $_SESSION['sendspinsvc'] == 1) {
$status = 'started';
startSendspin();
} else {
$status = 'available';
}
} else {
$status = 'not installed';
}
} else {
$status = 'n/a';
}
workerLog('worker: Sendspin: ' . $status);

// Start Multiroom audio
if ($_SESSION['feat_bitmask'] & FEAT_MULTIROOM) {
// Sender
Expand Down Expand Up @@ -3262,7 +3291,12 @@ function runQueuedJob() {
}
}
break;

case 'sendspinsvc':
stopSendspin();
if ($_SESSION['sendspinsvc'] == 1) {
startSendspin();
}
break;
case 'multiroom_tx':
if ($_SESSION['multiroom_tx'] == 'On') {
// Reconfigure to Dummy sound driver
Expand Down
12 changes: 7 additions & 5 deletions www/inc/constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
const NAME_DLNA = 'DLNA';
const NAME_PLEXAMP = 'Plexamp';
const NAME_ROONBRIDGE = 'RoonBridge';
const NAME_SENDSPIN = 'Sendspin';
const NAME_GPIO = 'GPIO Controller';
const NAME_LOCALDISPLAY = 'Local Display';
const NAME_PEPPYDISPLAY = 'Peppy Display';
Expand Down Expand Up @@ -193,23 +194,24 @@
const FEAT_HTTPS = 1; // y HTTPS mode
const FEAT_AIRPLAY = 2; // y AirPlay renderer
const FEAT_MINIDLNA = 4; // y DLNA server
const FEAT_RECORDER = 8; // Stream recorder
const FEAT_RECORDER = 8; // n Stream recorder
const FEAT_SQUEEZELITE = 16; // y Squeezelite renderer
const FEAT_UPMPDCLI = 32; // y UPnP client for MPD
const FEAT_DEEZER = 64; // n Deezer Connect renderer
const FEAT_ROONBRIDGE = 128; // y RoonBridge renderer
const FEAT_LOCALDISPLAY = 256; // y Local display
const FEAT_INPSOURCE = 512; // y Input source select
const FEAT_UPNPSYNC = 1024; // UPnP volume sync
const FEAT_UPNPSYNC = 1024; // n UPnP volume sync
const FEAT_SPOTIFY = 2048; // y Spotify Connect renderer
const FEAT_GPIO = 4096; // y GPIO button handler
const FEAT_PLEXAMP = 8192; // y Plexamp renderer
const FEAT_BLUETOOTH = 16384; // y Bluetooth renderer
const FEAT_DEVTWEAKS = 32768; // Developer tweaks
const FEAT_DEVTWEAKS = 32768; // n Developer tweaks
const FEAT_MULTIROOM = 65536; // y Multiroom audio
const FEAT_PEPPYDISPLAY = 131072; // y Peppy display
// -------
// 228279
const FEAT_SENDSPIN = 262144; // y Sendspin renderer
// ------
// 490423

// Selective resampling bitmask
const SOX_UPSAMPLE_ALL = 3; // Upsample if source < target rate
Expand Down
12 changes: 11 additions & 1 deletion www/inc/renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,15 @@ function stopRoonBridge() {
sendFECmd('rbactive0');
}

// Sendspin
function startSendspin() {
sysCmd('mpc stop');
sysCmd('systemctl start sendspin');
}
function stopSendspin() {
sysCmd('systemctl stop sendspin');
}

// Stop all renderers
function stopAllRenderers() {
$renderers = array(
Expand All @@ -379,7 +388,8 @@ function stopAllRenderers() {
'upnpsvc' => 'stopUPnP',
'slsvc' => 'stopSqueezeLite',
'pasvc' => 'stopPlexamp',
'rbsvc' => 'stopRoonBridge'
'rbsvc' => 'stopRoonBridge',
'sendspinsvc' => 'stopSendspin'
);

// Watchdog (so monitored renderers are not auto restarted)
Expand Down
3 changes: 2 additions & 1 deletion www/inc/sql.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ function sqlRead($table, $dbh, $param = '', $id = '') {
$queryStr = 'SELECT value FROM ' . $table . " WHERE param='" . $param . "'";
}

return sqlQuery($queryStr, $dbh);
$rows = sqlQuery($queryStr, $dbh);
return $rows;
}

function sqlUpdate($table, $dbh, $key = '', $value) {
Expand Down
5 changes: 3 additions & 2 deletions www/js/playerlib.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ const FEAT_BLUETOOTH = 16384; // y Bluetooth renderer
const FEAT_DEVTWEAKS = 32768; // Developer tweaks
const FEAT_MULTIROOM = 65536; // y Multiroom audio
const FEAT_PEPPYDISPLAY = 131072; // y Peppy display
const FEAT_SENDSPIN = 262144; // y Sendspin renderer
// -------
// 228279
// 490423

// Notifications
const NOTIFY_TITLE_INFO = '<i class="fa fa-solid fa-sharp fa-circle-check" style="color:#27ae60;"></i> Info';
Expand Down Expand Up @@ -3706,7 +3707,7 @@ $('#btn-preferences-update').click(function(e){

// CoverView
'scnsaver_timeout': SESSION.json['scnsaver_timeout'],
'scnsaver_whenplaying' = SESSION.json['scnsaver_whenplaying'],
'scnsaver_whenplaying' : SESSION.json['scnsaver_whenplaying'],
'auto_coverview': SESSION.json['auto_coverview'],
'scnsaver_style': SESSION.json['scnsaver_style'],
'scnsaver_mode': SESSION.json['scnsaver_mode'],
Expand Down
19 changes: 19 additions & 0 deletions www/ren-config.php
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,17 @@
submitJob('rbrestart', '', NOTIFY_TITLE_INFO, NAME_ROONBRIDGE . NOTIFY_MSG_SVC_MANUAL_RESTART);
}

// Sendspin
if (isset($_POST['update_sendspin_settings'])) {
if (isset($_POST['sendspinsvc']) && $_POST['sendspinsvc'] != $_SESSION['sendspinsvc']) {
$update = true;
phpSession('write', 'sendspinsvc', $_POST['sendspinsvc']);
}
if (isset($update)) {
submitJob('sendspinsvc');
}
}

phpSession('close');

// Bluetooth
Expand Down Expand Up @@ -356,6 +367,14 @@
$_feat_roonbridge = 'hide';
}

// Sendspin
$_feat_sendspin = $_SESSION['feat_bitmask'] & FEAT_SENDSPIN ? '' : 'hide';
$_SESSION['sendspinsvc'] == '1' ? $_sendspin_btn_disable = '' : $_sendspin_btn_disable = 'disabled';
$_SESSION['sendspinsvc'] == '1' ? $_sendspin_link_disable = '' : $_sendspin_link_disable = 'onclick="return false;"';
$autoClick = " onchange=\"autoClick('#btn-set-sendspinsvc');\"";
$_select['sendspinsvc_on'] .= "<input type=\"radio\" name=\"sendspinsvc\" id=\"toggle-sendspinsvc-1\" value=\"1\" " . (($_SESSION['sendspinsvc'] == '1') ? "checked=\"checked\"" : "") . $autoClick . ">\n";
$_select['sendspinsvc_off'] .= "<input type=\"radio\" name=\"sendspinsvc\" id=\"toggle-sendspinsvc-2\" value=\"0\" " . (($_SESSION['sendspinsvc'] == '0') ? "checked=\"checked\"" : "") . $autoClick . ">\n";

waitWorker('ren-config');

$tpl = "ren-config.html";
Expand Down
Loading