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 pi@raspberrypi.local


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/