Last active
March 11, 2025 20:15
-
-
Save arcs-/96dafa9beb699a8b823b54efdbc83e36 to your computer and use it in GitHub Desktop.
Record Player to Sonos on a Raspberry
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const http = require('http'); | |
const { spawn } = require('child_process'); | |
const { DeviceDiscovery } = 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', '60000000', // = 1 minutes | |
'--period-time', '20000000', // = 20 seconds | |
'--format', 'dat', // = [-f S16_LE -c2 -r48000] | |
'--duration', 60 * 30, // kill it after 30 minutes, and we'll restart it | |
'--mmap', // read input buffer directly | |
]; | |
// https://linux.die.net/man/1/lame | |
const lame = [ | |
'lame', | |
'-r', // assume raw input | |
'-s 48', // sample rate (must match arecord) | |
'--preset extreme', // optimized VBR ~220-270 kbps | |
'-', // 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); | |
if (data.includes('overrun')) { | |
audioInputStream.kill(); | |
setTimeout(createAudioStream, 200); | |
console.log('> Restarting audio stream'); | |
} | |
}); | |
audioInputStream.on('exit', (code) => { | |
console.log('stream exited with:', ''+code); | |
setTimeout(createAudioStream, 200); | |
console.log('> Restarting audio stream'); | |
}); | |
} | |
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="-1" restricted="true"> | |
<dc:title>Schallplatte</dc:title> | |
<<upnp:album>Schallplatte</upnp:album> | |
<dc:creator>Plattenspieler</dc:creator> | |
<upnp:albumArtURI>https://stillh.art/static/playerimage.jpeg</upnp:albumArtURI> | |
<upnp:class>object.item.audioItem.audioBroadcast.#artistRadio</upnp:class> | |
</item> | |
</DIDL-Lite> | |
` | |
}; | |
group | |
.CoordinatorDevice() | |
.play(configuration) | |
.then(() => { console.log(`${group.Name} connected`); }) | |
.catch((err) => { console.error('SONOS Error: ', err); }); | |
} | |
setInterval(() => { | |
if(httpConnections.length === 0) { | |
console.log('> Find Sonos'); | |
DeviceDiscovery().once('DeviceAvailable', (device) => { | |
device.getAllGroups().then((groups) => { | |
groups.forEach((group) => { | |
if (group.Name.includes(SONOS_GROUP)) { | |
connectSonosGroup(group); | |
} | |
}); | |
}); | |
}); | |
} | |
}, 10 * 1000); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment