A play here Volumio Pluggin / Addon which adds a play here button on your volumio web ui screen which allows you to stream directly to the web interface in the same way the paid "Play Here" function operates.
Works on Volumio Version 3
Does not add a button on the local hdmi screen. Note some adjusting of the code may be required to suit your volumio installation name on the network. However not usually required..
Aprt from that, this has been tested and used on Pi4 and Rock 3A devices and survives restarting and functions to all expectations.
You can also stream the music from your favourite media player, VLC, moode player etc by adding the Volumio URL to a network stream in VLC volumio-ip:8000 or in moode for example add a station with your volumio-IP:8000
First of all ssh into your volumio box:-
/\_ \ __
__ __ ___\//\ \ __ __ ___ ___ /\_\ ___
/\ \/\ \ / __`\\ \ \ /\ \/\ \ /' __` __`\/\ \ / __`\
\ \ \_/ |/\ \L\ \\_\ \_\ \ \_\ \/\ \/\ \/\ \ \ \/\ \L\ \
\ \___/ \ \____//\____\\ \____/\ \_\ \_\ \_\ \_\ \____/
\/__/ \/___/ \/____/ \/___/ \/_/\/_/\/_/\/_/\/___/
Free Audiophile Linux Music Player - Version 3.0
© 2015-2021 Michelangelo Guarise - Volumio Team - Volumio.org
Volumio Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Welcome to Volumio for Rock 3A (6.1.29-rk35xx aarch64)
DO NOT USE SUDO for these commands unless specified here.
Create the working folder and go there:-
mkdir -p /data/plugins/miscellanea/labtools
cd /data/plugins/miscellanea/labtools
Create file
package.json
edit file and add:-
{
"name": "labtools",
"version": "2.0.0",
"description": "Lab Tools - Play Here stream button",
"main": "index.js",
"author": "Anthony",
"license": "ISC",
"volumio_info": {
"plugin_type": "miscellanea",
"boot_priority": 2
}
}
Create
config.json
edit and add :-
{}
Create
UIConfig.json
Edit and add:-
{
"page": { "label": "Lab Tools" },
"sections": [
{
"id": "labtools",
"element": "section",
"label": "Play Here",
"icon": "fa-headphones",
"content": [
{ "id": "volumiostream", "element": "button", "label": "Show stream address",
"onClick": { "type": "emit", "message": "callMethod",
"data": { "endpoint": "miscellanea/labtools", "method": "volumioStream", "data": "" } } }
]
}
]
}
create
index.js
edit and add:-
'use strict';
var libQ = require('/volumio/node_modules/kew');
var fs = require('fs');
var path = require('path');
var os = require('os');
var UI_DIRS = ['/volumio/http/www', '/volumio/http/www3', '/volumio/http/www4'];
var SCRIPT_NAME = 'labtools-playhere.js';
var SCRIPT_TAG = '<script src="' + SCRIPT_NAME + '"></script>';
var MPD_TMPL = '/volumio/app/plugins/music_service/mpd/mpd.conf.tmpl';
var HTTPD_BLOCK = [
'audio_output {',
'type "httpd"',
'name "HTTP Stream"',
'encoder "vorbis"',
'port "8000"',
'bitrate "320"',
'format "44100:16:2"',
'}'
].join('\n');
module.exports = LabTools;
function LabTools(context) {
this.context = context;
this.commandRouter = context.coreCommand;
this.logger = context.logger;
this.configManager = context.configManager;
}
LabTools.prototype.onVolumioStart = function () {
return libQ.resolve();
};
LabTools.prototype.onStart = function () {
this.logger.info('LabTools Started');
this.installPlayHere();
this.ensureHttpStream();
return libQ.resolve();
};
LabTools.prototype.onStop = function () {
this.removePlayHere();
return libQ.resolve();
};
LabTools.prototype.getUIConfig = function () {
var defer = libQ.defer();
try {
defer.resolve(require(__dirname + '/UIConfig.json'));
} catch (e) {
defer.reject(e);
}
return defer.promise;
};
// ---------- Play Here: inject button script into the web UI ----------
LabTools.prototype.installPlayHere = function () {
var self = this;
var src = path.join(__dirname, SCRIPT_NAME);
UI_DIRS.forEach(function (dir) {
var index = path.join(dir, 'index.html');
if (!fs.existsSync(index)) return;
try {
fs.copyFileSync(src, path.join(dir, SCRIPT_NAME));
var html = fs.readFileSync(index, 'utf8');
if (html.indexOf(SCRIPT_TAG) !== -1) {
self.logger.info('LabTools: Play Here already present in ' + index);
return;
}
if (html.indexOf('</body>') === -1) {
self.logger.error('LabTools: no </body> in ' + index + ', skipped');
return;
}
if (!fs.existsSync(index + '.labtools.bak')) {
fs.copyFileSync(index, index + '.labtools.bak');
}
fs.writeFileSync(index, html.replace('</body>', SCRIPT_TAG + '</body>'));
self.logger.info('LabTools: Play Here added to ' + index);
} catch (e) {
self.logger.error('LabTools: cannot update ' + index + ': ' + e);
}
});
};
LabTools.prototype.removePlayHere = function () {
var self = this;
UI_DIRS.forEach(function (dir) {
var index = path.join(dir, 'index.html');
if (!fs.existsSync(index)) return;
try {
var html = fs.readFileSync(index, 'utf8');
if (html.indexOf(SCRIPT_TAG) !== -1) {
fs.writeFileSync(index, html.split(SCRIPT_TAG).join(''));
self.logger.info('LabTools: Play Here removed from ' + index);
}
var js = path.join(dir, SCRIPT_NAME);
if (fs.existsSync(js)) fs.unlinkSync(js);
} catch (e) {
self.logger.error('LabTools: cannot clean ' + index + ': ' + e);
}
});
};
// ---------- Keep the MPD HTTP stream block alive after updates ----------
LabTools.prototype.ensureHttpStream = function () {
var self = this;
try {
var tmpl = fs.readFileSync(MPD_TMPL, 'utf8');
if (/type\s+"httpd"/.test(tmpl)) {
self.logger.info('LabTools: HTTP stream block present in mpd template');
return;
}
var m = tmpl.match(/audio_output\s*\{\s*type\s+"fifo"/);
if (!m) {
self.logger.error('LabTools: HTTP block missing and no fifo block to anchor on; not touching template');
return;
}
fs.copyFileSync(MPD_TMPL, MPD_TMPL + '.labtools.bak');
fs.writeFileSync(MPD_TMPL, tmpl.slice(0, m.index) + HTTPD_BLOCK + '\n\n' + tmpl.slice(m.index));
self.logger.info('LabTools: HTTP stream block RESTORED - restart Volumio once to activate');
self.commandRouter.pushToastMessage('warning', 'Lab Tools',
'HTTP stream was missing (update?) and has been restored. Restart Volumio once.');
} catch (e) {
self.logger.error('LabTools: cannot check mpd template: ' + e);
}
};
// ---------- Settings page button: this box's own stream address ----------
LabTools.prototype.volumioStream = function () {
var host = os.hostname();
var ip = '';
var nets = os.networkInterfaces();
Object.keys(nets).forEach(function (name) {
nets[name].forEach(function (a) {
if (!ip && a.family === 'IPv4' && !a.internal) ip = a.address;
});
});
this.commandRouter.pushToastMessage('success', 'Volumio Stream',
'Listen at http://' + host + '.local:8000' + (ip ? ' or http://' + ip + ':8000' : ''));
return libQ.resolve();
};
create
labtools-playhere.js
edit and add:-
(function () {
'use strict';
var STREAM = location.protocol + '//' + location.hostname + ':8000/';
var audio = null, active = false, retryTimer = null;
var btn = document.createElement('button');
btn.id = 'labtools-playhere';
btn.style.cssText = 'position:fixed;left:125px;bottom:148px;z-index:99999;' +
'padding:8px 14px;border-radius:20px;border:1px solid rgba(255,255,255,.35);' +
'background:rgba(0,0,0,.65);color:#fff;font:14px sans-serif;cursor:pointer;';
function setState(text, on) {
btn.textContent = text;
btn.style.background = on ? '#2e7d32' : 'rgba(0,0,0,.65)';
}
function teardown() {
if (!audio) return;
var a = audio;
audio = null;
a.pause();
a.removeAttribute('src');
a.load();
}
function onTrouble(e) {
if (e.target !== audio || !active) return;
teardown();
setState('\u2026 Waiting for stream', true);
clearTimeout(retryTimer);
retryTimer = setTimeout(function () { if (active) start(); }, 3000);
}
function start() {
active = true;
audio = new Audio(STREAM);
audio.addEventListener('playing', function (e) {
if (e.target === audio) setState('\u25A0 Stop here', true);
});
audio.addEventListener('error', onTrouble);
audio.addEventListener('ended', onTrouble);
setState('\u2026 Connecting', true);
var p = audio.play();
if (p && p.catch) p.catch(function () { onTrouble({ target: audio }); });
}
function stop() {
active = false;
clearTimeout(retryTimer);
teardown();
setState('\u266A Play here', false);
}
btn.onclick = function () { if (active) { stop(); } else { start(); } };
setState('\u266A Play here', false);
if (document.body) { document.body.appendChild(btn); }
else { document.addEventListener('DOMContentLoaded', function () { document.body.appendChild(btn); }); }
})();
Test everything :-
cd /data/plugins/miscellanea/labtools
for f in package.json config.json UIConfig.json; do node -e "require('./$f')" && echo "$f OK"; done
node -c index.js && echo "index.js OK"
node -c labtools-playhere.js && echo "labtools-playhere.js OK"
grep -c installPlayHere index.js
All five must say OK and the last line must print 2
Replies shoud look like :-
nano labtools-playhere.js
volumio@volumio:/data/plugins/miscellanea/labtools$ volumio@volumio:/data/plugins/miscellanea/labtools$ cd /data/plugins/miscellanea/labtools
volumio@volumio:/data/plugins/miscellanea/labtools$ for f in package.json config.json UIConfig.json; do node -e "require('./$f')" && echo "$f OK"; done
K"
node -c labtools-playhere.js && echo "labtools-playhere.js OK"
grep -c installPlayHere index.jspackage.json OK
config.json OK
UIConfig.json OK
volumio@volumio:/data/plugins/miscellanea/labtools$ node -c index.js && echo "index.js OK"
index.js OK
volumio@volumio:/data/plugins/miscellanea/labtools$ node -c labtools-playhere.js && echo "labtools-playhere.js OK"
labtools-playhere.js OK
volumio@volumio:/data/plugins/miscellanea/labtools$ grep -c installPlayHere index.js
2
If all is well, restart volumio and wait 30 seconds
sudo systemctl restart volumio
after 30 seconds or more , You can run this in the ssh session:-
journalctl -u volumio --since "3 min ago" | grep -i labtools
volumio@volumio:/data/plugins/miscellanea/labtools$ journalctl -u volumio --since "3 min ago" | grep -i labtools
Oct 20 03:32:53 volumio volumio[7809]: info: Plugin labtools is not enabled
Oct 20 03:33:29 volumio volumio[7809]: info: Enabling plugin labtools
Oct 20 03:33:29 volumio volumio[7809]: info: Loading plugin "labtools"...
Oct 20 03:33:29 volumio volumio[7809]: info: PLUGIN START: labtools
Oct 20 03:33:29 volumio volumio[7809]: info: LabTools Started
Oct 20 03:33:29 volumio volumio[7809]: info: LabTools: Play Here added to /volumio/http/www/index.html
Oct 20 03:33:29 volumio volumio[7809]: info: LabTools: Play Here added to /volumio/http/www3/index.html
Oct 20 03:33:29 volumio volumio[7809]: info: LabTools: Play Here added to /volumio/http/www4/index.html
Oct 20 03:33:29 volumio volumio[7809]: info: LabTools: HTTP stream block present in mpd template
Oct 20 03:33:33 volumio volumio[7809]: info: CALLMETHOD: miscellanea labtools volumioStream
Oct 20 03:33:33 volumio volumio[7809]: info: CoreCommandRouter::executeOnPlugin: labtools , volumioStream
look at your volumio devices hdmi screen if you have one, look at volumios web ui..
Volumio most likely will not enable the plugin itself on first installation.
So navigate to the Volumio menu and go to pluggins / click on installed pluggins and look for LabTools.
Enable it..
A play here button should be now visible on the web ui... Note there is not a button on the local HDMI display of volumio.
The music does not stop on the local Volumio machine when play back is used in the Play Here area on the web ui. Bt if you dont want to play locally on the main device, reduce the sound on the web ui to 0..
The volume on you web ui , iphone, ipad is controlled by the devices volume not the web ui.
Backup the sytem and youre done:-
cp -R /data/plugins/miscellanea/labtools ~/labtools-manual-backup
The self instal one script sh installer can be downloaded with all files from WorldWideWebDev/playhere-stream: # Play Here Stream for Volumio it will do the job in around 30 seconds for you/ Enjoy
