Skip to content

Instantly share code, notes, and snippets.

@arcs-
Last active February 5, 2023 16:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arcs-/96dafa9beb699a8b823b54efdbc83e36 to your computer and use it in GitHub Desktop.
Save arcs-/96dafa9beb699a8b823b54efdbc83e36 to your computer and use it in GitHub Desktop.
Record Player to Sonos on a Raspberry
const http = require('http');
const { spawn } = require('child_process');
const { DeviceDiscovery, Helpers } = require('sonos');
const ip = require('ip');
const PORT = 8080;
const HOST = ip.address();
const SOURCE = 'hw:1,0';
const STREAM_NAME = 'Vinyl';
const SONOS_GROUP = 'Pool';
const httpConnections = [];
// AUDIO STREAM
// --------------------------
function createAudioStream() {
console.log('> Start streaming audio');
// https://linux.die.net/man/1/arecord
const arecord = [
'arecord',
'--device', SOURCE,
'--buffer-time', '600000',
'--period-time', '300000',
'--format', 'cd', // = [-f S16_LE -c2 -r44100]
'--duration', 60 * 30, // kill it after 30 minutes, and we'll restart it
];
// https://linux.die.net/man/1/lame
const lame = [
'lame',
'-r', // assume raw input
'-s 44.1',
'-v', // variable bitrate
'--vbr-new', // Invokes the newest VBR algorithm.
'-V 4', // quallity 0 - 9 (lower is better)
'-d', // allows the left and right channels to use different block size types
'-', // read from stdin
];
const audioInputStream = spawn('sh', ['-c', `${arecord.join(' ')} | ${lame.join(' ')}`]);
audioInputStream.stdout.on('data', (data) => {
for (const stream of httpConnections) stream.write(data);
});
audioInputStream.stderr.on('data', (data) => {
console.error('stream info:', ''+data);
});
audioInputStream.on('exit', (code) => {
console.log('stream exited with:', ''+code);
setTimeout(createAudioStream, 500);
});
}
createAudioStream();
// SERVER
// --------------------------
http.createServer((req, res) => {
console.log('> Request for connection');
res.writeHead(200, { 'Content-Type': 'audio/mpeg', Connection: 'close' });
httpConnections.push(res);
res.on('close', () => {
httpConnections.slice(httpConnections.indexOf(res), 1);
console.log('connection closed by client');
});
}).listen(PORT);
console.log('> Start HTTP server at', HOST, PORT);
// SONOS
// ------------------------------
function connectSonosGroup(group) {
const configuration = {
uri: `x-rincon-mp3radio://${HOST}:${PORT}/${STREAM_NAME}`,
metadata: `
<DIDL-Lite
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"
xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/"
xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"
>
<item id="R:0/0/49" parentID="R:0/0" restricted="true">
<dc:title>Recordplayer</dc:title>
<upnp:albumArtURI>https://stillh.art/static/playerimage.jpeg</upnp:albumArtURI>
<upnp:class>object.item.audioItem.audioBroadcast</upnp:class>
</item>
</DIDL-Lite>`
};
group
.CoordinatorDevice()
.play(configuration)
.then(() => { console.log(`${group.Name} connected`); })
.catch((err) => { console.error('SONOS Error: ', err); });
}
console.log('> Find Sonos');
DeviceDiscovery().once('DeviceAvailable', (device) => {
device.getAllGroups().then((groups) => {
groups.forEach((group) => {
if (group.Name === SONOS_GROUP) {
connectSonosGroup(group);
}
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment