2022-04-01      Raspberrypi Webradio Player

Table of Contents
_________________

1. Raspberrypi webradio player
. 1. Concept
.... 1. Preparing OS
.... 2. on the host
. 2. on the pi
. 3. Configuring MPD
. 4. Autostart
.... 1. via systemd
.... 2. via ~/.profile
. 5. Creating a playlist
. 6. Programming
.... 1. webradio.py
. 7. Assembly


1 Raspberrypi webradio player
=============================

1.1 Concept
~~~~~~~~~~~

 - raspberrypi music streamer as /minimum viable product/
 - hardware and software should be as basic as possible
 - stream only webradio urls (not streaming services like spotify)
 - headless pi
 - audio player: mpd + mpc[1]
 - feedback via text to speech
 - sound output via headphone jack (sorry audiophiles)
 - one and only one button
   - single press -> next channel
   - long press (when powered on) -> power off
   - single press (when halted) -> power on

 once configured it's supposed to run on it's own
 -> no bloat == less error prone


1.2 Preparing OS
~~~~~~~~~~~~~~~~

 burn Raspberry Pi OS Lite[2] to a sd card


1.2.1 on the host
~~~~~~~~~~~~~~~~~

   (host)$ touch /[path to sd card]/boot/ssh

 put sd card in the pi, connect it via ethernet to your router,
 power it on (theres is also a way to preconfigure the sd card with
 your wifi credentials)

   (host)$ ssh [email protected]


1.2.2 on the pi
~~~~~~~~~~~~~~~

   $ sudo raspi-config

 - Localisation Options / Keyboard / ...
 - Localisation Options / TimeZone / ...
 - System Options / Password
 - System Options / Wireless LAN
 - Interface / SSH
 - Boot / Auto Login / Console Autologin


   $ sudo apt update && sudo apt dist-upgrade
   $ sudo install mpd mpc pulseuadio python3-gpiozero espeak


 We absolutely do *NOT* want to start mpd as a system-wide service
 -> configuration and permission hell


   $ sudo systemctl disable mpd.service
   $ sudo reboot


1.3 Configuring MPD
~~~~~~~~~~~~~~~~~~~

   $ mkdir ~/music
   $ mkdir -p ~/.config/mpd/playlists && cd ~/.config/mpd/
   $ touch mpd.conf mpd.db mpd.fifo mpd.log mpd.pid mpdstate

   $ cat << EOF > ~/.config/mpd/mpd.conf
   music_directory    "/home/pi/music"
   playlist_directory "/home/pi/.config/mpd/playlists"
   db_file            "/home/pi/.config/mpd/mpd.db"
   log_file           "/home/pi/.config/mpd/mpd.log"
   pid_file           "/home/pi/.config/mpd/mpd.pid"
   state_file         "/home/pi/.config/mpd/mpdstate"

   audio_output {
     type    "pulse"
     name    "MPD"
   }
   EOF

1.4 Autostart
~~~~~~~~~~~~~

1.4.1 via systemd
-----------------

   $ cat << EOF > ~/.config/systemd/user/webradio.service
   [Unit]
   Description=Webradio
   After=network-online.target mpd.service

   [Service]
   Type=simple
   ExecStart=/home/pi/webradio.py
   Restart=on-failure

   [Install]
   WantedBy=default.target
   EOF

   $ systemctl enable mpd.service --user
   $ systemctl enable webradio.service --user

1.4.2 via ~/.profile
--------------------

 if it doesn't work with systemd

   echo "[ ! -s ~/.config/mpd/mpd.pid ] && mpd && /home/pi/webradio.py &" >> ~/.profile


 then try it with `systemctl start mpd.service --user' instead of `mpd'


1.5 Creating a playlist
~~~~~~~~~~~~~~~~~~~~~~~

 If mpd is up and running you can add urls like so

   $ mpc add https://url1
   $ mpc add https://url2
   $ mpc add https://url3
   $ mpc save webradio

 or put them here

   $ nano ~/.config/mpd/playlists/webradio.m3u


1.6 Programming
~~~~~~~~~~~~~~~

   $ touch ~/webradio.py
   $ chmod +x ~/webradio.py


1.6.1 webradio.py
-----------------

   #!/usr/bin/env python3

   import re, subprocess
   from gpiozero import Button
   from signal import pause

   def speak(vol, message):
       subprocess.run(["mpc", "vol", str(vol-50)])
       subprocess.run(["espeak", "-ven+f3",  "-s170", message])
       subprocess.run(["mpc", "vol", str(vol)])

   def get_song_info():
       mpc_status = str(subprocess.run(
           ['mpc', 'status'],
           stdout=subprocess.PIPE).stdout)
       matched = re.search(r"(volume:)(\s*\d+)", mpc_status)
       vol = matched.groups()[1]
       matched = re.search(r"(\[playing\] #)(\d+)\/(\d+)", mpc_status)
       num = matched.groups()[1]
       lim = matched.groups()[2]
       return int(num), int(vol), int(lim)

   def init_player():
       subprocess.run(["mpc", "clear"])
       subprocess.run(["mpc", "load", "webradio"])
       subprocess.run(["mpc", "repeat", "on"])
       subprocess.run(["mpc", "single", "on"])
       subprocess.run(["mpc", "random", "off"])
       subprocess.run(["mpc", "play"])

   def next_song():
       try:
           num, vol, lim = get_song_info()
           num = num + 1
           if num > lim: num = 1
           speak(vol, "playing channel {0}".format(num))
           subprocess.run(["mpc", "next"])
       except Exception as e:
           print(e)
           # fails if mpd is not playing
           subprocess.run(['mpc', 'play'])

   def poweroff():
       try:
           num, vol, lim = get_song_info()
           speak(vol, "shutting down")
           subprocess.run(['sudo', 'poweroff'])
       except Exception as e:
           print(e)
           # shutdown without tts feedback
           subprocess.run(['sudo', 'poweroff'])


   def btn_released():
       # print("button released")
       if not btn.was_held:
           btn_pressed()
       btn.was_held = False

   def btn_held():
       # print("button held")
       btn.was_held = True
       poweroff()

   def btn_pressed():
       # print("button pressed")
       next_song()


   if __name__ == "__main__":
       Button.was_held   = False
       btn               = Button(3, pull_up=True, hold_time = 2)
       btn.when_held     = btn_held
       btn.when_released = btn_released

       init_player()
       pause()


1.7 Assembly
~~~~~~~~~~~~

 - connect a button with jumper cables to GPIO3 and GND
   (pins 5 and 6, see <https://pinout.xyz/> for reference)
 - connect speakers to headphone jack
 - build an absolutely ridiculous case for the pi :^)



Footnotes
_________

[1] https://musicpd.org/

[2] https://www.raspberrypi.com/software/operating-systems/