Streaming audio server with a Raspberry Pi 5 and Mopidy – Never Mind

Well, this is a bummer. The Iris interface on Mopidy is nice, and when I’m doing everything manually, it works fine. BUT, I wanted to do things with scripting. I can somewhat get things to work, but not reliably. So I’m going to give up on Mopidy and go back to MPD (Music Player Demon).

I’d managed to build a little routine that, once an hour, generates a sound file with the current time in it. I copied it to the right place, updated the file cache so that the new file is the current one, and then threw (using CURL) the command to add the file to the Mopidy queue. For testing, I had cron run it every two minutes.

# Add the current_time.mp3 file to the queue

/usr/bin/curl -s -X POST "$MOPIDY_URL" \
-H "Content-Type: application/json" \
-d '{
"method": "core.tracklist.add",
"params": {
"uris": ["'"$FILE_URI"'"],
"at_position": 1
},
"id": 1,
"jsonrpc": "2.0"
}'

But, if the queue is empty, Mopidy isn’t going to play anything. So then I had to add:

# Start playback

/usr/bin/curl -s -X POST "$MOPIDY_URL" \
-H "Content-Type: application/json" \
-d '{
"method": "core.playback.play",
"id": 2,
"jsonrpc": "2.0"
}'

And now I’ve three problems.

The first is that there is a weird loop problem where once: the file plays correctly. I’ll hear “The current time is 16:40.” The next time the cron job runs the bash script, I’ll hear “The current time is 16:42. The current time is 16:42.” The third time it runs, I’ll hear “The current time is 16:44. The current time is 16:44. The curren” and the playback is interrupted.

It is always in triples; 16:46 would play fine, 16:48 would play twice, and 16:50 would attempt to play three times but get cut off.

So that is one problem. The next is that I prefer to do my scripting in Perl, and I know that Perl has been doing JSON-RPC for a really long time now. But the Perl script I wrote doesn’t seem to be connecting to the Mopidy server. It tells me that it connected, and shows me a result (instead of a refusal to connect), but the Mopidy debug log does not acknowledge that a connection was made.

The third is that even when I add a song manually using the Iris interface, that “core.playback.play” method would break the playback of the currently playing song. The idea was that if I only create a current_time.mp3 file at the top of the hour, and add it to the queue, then within a few minutes, I’ll hear was hour it is. But now I’ve got the worst of both worlds: if the queue was empty, no playback of the current_time.mp3, but if I trigger the playback, the current song gets clobbered. Sigh.

I really liked that Mopidy has the nice Iris web client; but, if the only way to interact with the server is via mouse and keyboard, well then I’m out.

Streaming audio server with a Raspberry Pi 5 and Mopidy

My first attempt at an MPD streaming box was a tough config, because I was learning along the way, hitting every stumbling block there is. Getting the Music Player Demon running wasn’t terrible, but getting my various client machines to listen to the streaming server was tough. Okay, I am going to try again, and hopefully, it actually works with Mopidy this time.

The Iris plugin to Mopidy is nice. Previously I used Cantata, which was fine, but it was a local install on each machine. Iris is good because it runs on the server, and I don’t need to do a local install on each machine just to control the music stream.

On to the build!


I used the Raspberry Pi Imager program to put the Raspberry Pi OS Lite (64-bit) on the SD card. Then I booted the device, ssh’d in and ran

sudo apt update

followed by

sudo apt upgrade -y

followed by a sudo reboot now

sudo raspi-config

I’m changing the hostname. Also, because I prefer vim to nano:

sudo apt install vim -y
sudo update-alternatives --config editor

And of course, now I get to install my favorite aliases and history search keystrokes. These are detailed here.

Next, I’m going to follow the instructions at Mopidy Installation

I am following the section Install from apt.mopidy.com:

sudo mkdir -p /etc/apt/keyrings
sudo wget -q -O /etc/apt/keyrings/mopidy-archive-keyring.gpg https://apt.mopidy.com/mopidy.gpg
sudo wget -q -O /etc/apt/sources.list.d/mopidy.list https://apt.mopidy.com/bookworm.list

So one difference is that the instructions say to wget from https://apt.mopidy.com/bullseye.list but this version of Debian on the Raspberry Pi is bookworm (Debian 12 instead of 11). We’ll find out if I just shot myself in the foot. 😉

sudo apt update
sudo apt install mopidy -y
sudo apt install mopidy-mpd -y
sudo apt install mopidy-local -y
sudo apt install snapserver snapclient -y
sudo apt install python3-pip -y
sudo python3 -m pip install Mopidy-Iris --break-system-packages

The instructions I found say to simply do sudo python3 -m pip install Mopidy-Iris but Debian (or perhaps it is Python) barks at me with “error: externally-managed-environment – This environment is externally managed” and some potential way around the problem. That’s a nope: I want to run as a systemd service, and isolating stuff into unreachable environments is not the battle I wish to fight today.

And another thing: the Snapcast instructions assume that Snapweb is installed, but it never is. First, I downloaded it from https://github.com/badaix/snapweb/releases and picked the file snapweb.zip

Then I copied it to the Mopidy server, and did this:

sudo mkdir -p /usr/share/snapweb
sudo unzip snapweb.zip -d /usr/share/snapweb

Okay, I’m almost ready to start configuring things.

Later on down the line, the mopidy.conf file will consider that the files to serve up are at a certain location. What location? Here’s how to find out:

sudo mopidyctl config

and look in the [local] section for media_dir = /var/lib/mopidy/media

Now would be a great time to mount my music files via a CIFS / SMB share:

sudo apt install cifs-utils -y
touch /root/credentials.smb
vim /root/credentials.smb
username=epstein_did_not_kill_himself
password=Apparently-Child-Rape-Is-Okay-If-The-Deep-State-Does-It,-Because-With-The-Deep-State,-The-Ends-(Blackmail)-Justify-The-Means-(Child-Rape).--Everyone-Who-Is-Human-Recognizes-The-Evil,-But-The-People-Who-Make-Up-The-Deep-State-Are-Sociopaths.--Unfortunately,-The-People-In-The-Current-Administration-Who-Promised-To-Drain-The-Swamp-Have-Been-Broken,-Most-Likely-With-Threats-To-Their-Family.--What-A-World,-Amirite?
sudo chmod 600 /root/credentials.smb

Then we can edit /etc/fstab

//hostname/smbsharename/data /var/lib/mopidy/media cifs vers=3.0,credentials=/root/credentials.smb,_netdev,iocharset=utf8 0 0

I’m also going to try to keep from wearing out the SD card so quickly, so I’ll try this:

tmpfs   /var/cache/mopidy   tmpfs   size=1G,mode=1777   0 0

Okay, things I’ve changed in /etc/mopidy/mopidy.conf :

[http]
hostname = ::

[mpd]
hostname = ::

This was previously hostname = 127.0.0.1

I changed to listen on all interfaces (that’s what :: does) because I do want any of my machines on my local network to be able to interact with the Mopidy server. Note that I am planning on never putting this box on the public Internet.

There is another change I made in /etc/mopidy/mopidy.conf :


output = audioresample ! audioconvert ! audio/x-raw,rate=48000,channels=2,format=S16LE ! wavenc ! filesink location=/tmp/snapfifo

Then I get to edit /etc/snapserver.conf:

sudo vim /etc/snapserver.conf
source = pipe:///tmp/snapfifo?name=Mopidy

I’m going to try to make sure Snapcast loads first, before Mopidy, because Snapcast needs to be writing into /tmp/snapfifo before Mopidy starts reading it.

sudo systemctl edit mopidy.service
[Unit]
After=snapserver.service
Requires=snapserver.service

To configure the Snapweb server:

sudo vim /etc/snapserver.conf

Find the line #doc_root = /usr/share/snapserver/snapweb and change it to:

doc_root = /usr/share/snapweb

And I think I am at the moment of truth:

sudo systemctl enable snapserver.service
sudo systemctl enable mopidy.service

sudo systemctl restart snapserver.service
sudo systemctl restart mopidy.service

If things are looking correctly, my next step is….

sudo mopidyctl local scan

Well, it almost worked right on the first try. I also needed to do this:

echo "fs.protected_fifos = 0" | sudo tee /etc/sysctl.d/snapcast-unprotect-fifo.conf
sudo sysctl --system

Hooray! My streaming music server is up and running within my local network. This was fun. 🙂

Home alarm clock update – now with streaming audio

As mentioned in Home alarm clock update, I’d like to work with Snapcast.

Well, right off the bat, all the instructions for getting the Snapcast client to work automatically, did not. When I say “automatically” I mean that after I reboot the machine, I simply want the Snapcast client running without me having to do anything else. There were suggestions about making it a system service, and a user service, and none of those worked. I’m pretty sure it has something to do with my logged-in user having an environment which is different from what systemd or cron sees.

KAlarm to the rescue!

It has an option to launch stuff after reboot. That’s what I needed.

snapclient --host <IP address of MPD server goes here>

It does take a few seconds after reboot for KAlarm to figure out to run this command. But as soon as it does, my machine (whichever machine) taps into the stream, and music starts playing out. But because KAlarm doesn’t launch until after everything in KDE is up and running, I’m not having these weird errors where the Snapcast client cannot see the stream or the audio devices to play it out.

This is great.

However, what about my alarms? Those are music files too (well, sometimes a TTS wave file). The multiplexing nature of computer audio would have the two playing simultaneously. That is less than ideal.

Turns out that VLC has an option for exiting nicely after playing a file. Add a couple of MPC commands, and we’re golden. The magic command for VLC is rc --play-and-exit

I did have to install the MPC client (for controlling MPD servers) on my machines.

But now my KAlarm commands look like this:

mpc --host <IP address> pause
vlc --intf rc --play-and-exit /path/to/friday_morning.pls
mpc --host <IP address> play

I can queue up a whole stream of music files as background music, using Cantata, and when it comes time for my alarm to fire, to let me know it is time for the next event, the background music pauses through the whole house, the alarm does its thing, and then the background music resumes.

This is so neat. I am having fun with my computers again. 🙂

And I enjoy hearing the London Philharmonic Orchestra playing Sonic the Hedgehog: a Symphonic Suite and Elder Scrolls – Skyrim: Far Horizons. The Elder Scrolls V: Skyrim: Original Game Soundtrack has some great orchestral music. And now my whole house is filled with it.

Raspberry Pi MPD server

Notes about starting fresh on a Raspberry Pi and making a Music Player Daemon (MPD) server out of it.

New image preparation

I did use the Raspberry Pi Imager program and put the base Debian with no desktop environment on the SD card. I did use the customizer to put a user and password on it.

I also have put the MAC in my DHCP server so that the Pi gets a static IP address, and I put an entry in DNS so that IP address maps to the host name I want.

First, an update:

sudo apt-get update

Then I install vim. The default is to use nano, but I like vim.

sudo apt-get install vim -y

Followed by

sudo update-alternatives --config editor

Choice 3 picks vim as my editor.

Followed by turning off Wi-Fi. For music streaming, I only want the Raspberry Pi to be hard-wired into the network.

sudo vim /boot/firmware/config.txt

At the very bottom, I added this to the config.txt file:

dtoverlay=disable-wifi

It goes underneath the [all] section. Then I reboot and log in, and perform

sudo apt update
sudo apt upgrade -y

Now I get to install my favorite aliases and history search keystrokes. These are detailed here.

The next steps are so that I can do ssh from my main machine. I followed this, although I wanted to set a root user password first:

sudo passwd root

Then I mostly followed these steps: New Debian install; ssh and sudo changes

Then I did the ssh-copy-id thing and changed Password Authentication back to no in /etc/ssh/sshd_config

Start with the MPD install

The documentation says that the version of MPD that one can install from the Debian repositories is out of date. I can confirm that.

However, going through those motions sets things up well for later.

apt install mpd -y

Followed by

apt install snapserver -y

The Snapcast server needs to be configured to look to MPD for the sound source.

vim /etc/snapserver.conf

to say:

[stream]  
source = pipe:///tmp/snapfifo?name=MPD

That line was already there, except the name= was default instead of MPD

So later I get to download the latest .tar.xy file, and copy it to the Raspberry Pi. Then:

tar xf mpd-version.tar.xz
cd mpd-version

At this point, I should simply point you at https://mpd.readthedocs.io/en/stable/user.html

There’s a whole bit about apt install meson g++ pkgconf \ and some whole bunches of packages. Then there’s the compile after that. 696 things it compiles.

After all that is done, it is time to update the /etc/mpd.conf file.

This is what mine looks like with the comments stripped out:

music_directory       "/var/lib/mpd/music"
playlist_directory "/var/lib/mpd/playlists"
db_file "/var/lib/mpd/tag_cache"

state_file "/var/lib/mpd/state"
sticker_file "/var/lib/mpd/sticker.sql"

user "mpd"
bind_to_address "0.0.0.0"
port "6600"

auto_update "yes"
auto_update_depth "0"

zeroconf_enabled "yes"
zeroconf_name "Music Player @ %h"

input {
plugin "curl"
}

decoder {
plugin "hybrid_dsd"
enabled "no"
}

decoder {
plugin "wildmidi"
enabled "no"
#config_file "/etc/timidity/timidity.cfg"
}

audio_output {
type "fifo"
encoder "flac"
name "snapserver"
format "48000:16:2"
path "/tmp/snapfifo"
compression "8"
mixer_type "software"
}

filesystem_charset "UTF-8"

Eventually, we get it installed, which includes creating the /var/lib/mpd/music directory. We need that for the next step.

Access to the sound files

This took an edit of /etc/fstab although this is always more difficult than I think it should be.

I did it for Nextcloud, so it is the same thing, kind of. Nextcloud gets read/write access, where this MPD server doesn’t need to be able to write to the sound files or directory.

First, set up a credentials file, with the login name and password:

touch /root/credentials.smb
vim /root/credentials.smb
username=epstein_did_not_kill_himself
password=Apparently-Child-Rape-Is-Okay-If-0.1%-Richest-People-Opt-In-To-Doing-It,Obviously,Because-Otherwise-The-Rapists-Would-Have-Gone-To-Jail

Then we can edit /etc/fstab

//hostname/smbsharename/data /var/lib/mpd/music cifs vers=3.0,credentials=/root/credentials.smb,_netdev,iocharset=utf8 0 0

One thing that kicked my ass for a couple of hours:

vim /usr/local/lib/systemd/system/mpd.service

And on the ExecStart line, I had to explicitly add the configuration file path and file name.

So before, it looked like this:

ExecStart=/usr/local/bin/mpd --systemd

But that would err out with “could not find config file”. I changed it to this:

ExecStart=/usr/local/bin/mpd --systemd /etc/mpd.conf

And now it magically works. Of course, yesterday the first time I set it up, I had no such problems. Sigh.

I still have one more thing to add: Snapweb, where Snapcast server will show you what is currently running. I had that running yesterday, and liked it.