Personal OneDrive Music Player on Raspberry Pi with a Web-based Remote Control (Part 1)

There are so much things that we can build with a Raspberry Pi. It’s always my small little dream to have a personal music player that sits on my desk. With the successful setup of the Raspberry Pi 3 Model B few weeks ago, it’s time to realise that dream.

Project GitHub Repository

The complete source code of the music player program on the Raspberry Pi mentioned in this article can be found at https://github.com/goh-chunlin/Lunar.Music.RaspberryPi.

Project Objective

In this article and the next, I will be sharing with you the journey of building a personal music player on Raspberry Pi. The end product will just be like Spotify on Google Nest Hub where we can listen to our favourite music not on our computer or smart phone but on another standalone device, which is Raspberry Pi in this case.

In this project, there is no monitor or any other displays connected to my Raspberry Pi. So the output is simply music and songs. However, since there is no touch screen for user to interact with the Raspberry Pi like how Google Nest Hub does, we will need to use a web app which acts as the remote control of the Raspberry Pi music player. With the web app, user will be able to scan through the playlist and choose which music or song to play.

A month in, I barely use the Google Nest Hub Max (yet I still recommend it)
[Image Caption: Spotify on Google Nest Hub (Image Credit: Android Authority)]

In addition, while Google Nest Hub has its music streamed from Spotify, the music that our Raspberry Pi music player will use is available on our personal OneDrive Music folder. In the next article, I will talk more about how we can use Azure Active Directory and Microsoft Graph API to connect with our personal OneDrive service.

So with this introduction, we can confirm that the objective of this project is to build a music player on a Raspberry Pi which can be controlled remotely via a web app to play music from OneDrive.

[Image Caption: System design of this music player together with its web portal.]

We will only focus on the setup of Raspberry Pi and RabbitMQ server in this Part 1 of the article. In the Part 2, which can be found by clicking here, we will continue to talk about the web portal and how we access the OneDrive with Microsoft Graph and go-onedrive client.

Installing Golang on Raspberry Pi

The music player that we are going to build is using Golang. The reason of choosing Golang is because it’s easy to work with, especially integrating with the RabbitMQ server.

To install Golang on a Raspberry Pi, we can simply use the following command.

$ sudo apt-get install golang

However, please take note that the version of the Golang downloaded is not the latest one. So the alternative way of doing it is download the package from the Golang official website with the following commands.

Since the Raspberry Pi 3 Model B is having a 64-bit ARMv8 CPU and the latest Golang version is 1.15.5, according to the Golang website, what we need to download is the highlighted one in the following screenshot. Hence, we can run the following commands.

$ wget https://golang.org/dl/go1.15.5.linux-arm64.tar.gz
$ sudo tar -C /usr/local -xzf go1.15.5.linux-arm64.tar.gz
$ rm go1.15.5.linux-arm64.tar.gz
[Image Caption: Finding the correct package to download for Raspberry Pi 3 Model B.]

Now we have Golang installed on our Raspberry Pi, we can proceed to build the music player.

Music Player on Raspberry Pi

The multimedia player used in this project is the VLC Media Player which is not only free and open-source but also cross-platform. The reason VLC Media Player is chosen is also because it has nvlc, a command-line ncurses interface to play an MP3. Hence, to play an MP3 with nvlc, we just need to run the following command.

$ nvlc <mp3_file> --play-and-exit

The –play-and-exit flag is added to make sure that the VLC will exit once the particular music is finished.

Also, we can use Go exec command to execute the command above. For example, if we have an MP3 file called song and it’s stored in the directory songFileRoot, then we can play the MP3 file with the following codes.

cmd := exec.Command("nvlc", song, "--play-and-exit")

cmd.Dir = songFileRoot
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stdout

if err := cmd.Run(); err != nil {
    fmt.Println("Error:", err)
}

Messaging, RabbitMQ, and Remote Controlling Via Web App

Now we need a communication channel for our web app to tell the Golang programme above which MP3 file to play.

A common way is to use HTTP and REST to communicate. That will require us to design some RESTful HTTP APIs and turn our Raspberry Pi to be a web server so that the web app can invoke the APIs with HTTP request.

Using RESTful API sounds great and easy but what if the Raspberry Pi is not responding? Then our web app has to implement some sort of reconnection or failover logic. In addition, when our web app makes a call to the API, it will be blocked waiting for a response. Finally, due to the nature of RESTful API, there will always be some coupling of services.

This makes me turn to an alternative way, which is messaging which is loose coupling and asynchronous. Using messaging with message brokers like RabbitMQ helps a lot with scalability too.

Here RabbitMQ is used because is a popular light-weight message broker and it’s suitable for general-purpose messaging. Also, I like how RabbitMQ simply stores messages and passes them to consumers when ready.

It’s also simple to setup RabbitMQ on a cloud server too. For example, now on Microsoft Azure, we can just launch a VM running Ubuntu 18.04 LTS and follow the steps listed in the tutorials below to install RabbitMQ Server on Azure.

RabbitMQ supports several messaging protocols, directly and through the use of plugins. Here we will be using the AMQP 0-9-1 which we have Golang client support for it as well.

How about the message format? Basically, to RabbitMQ, all messages are just byte arrays. So, we can store our data in JSON and serialise the JSON object to byte array before sending the data via RabbitMQ. Hence, we have the two structs below.

type Command struct {
    Tasks []*Task `json:"tasks"`
}

type Task struct {
    Name    string   `json:"name"`
    Content []string `json:"content"`
}

With this, we then can publish the message via RabbitMQ as follows.

ch, err := conn.Channel()
...
err = ch.Publish(
    "",     // exchange
    q.Name, // routing key
    false,  // mandatory
    false,  // immediate
    amqp.Publishing{
        ContentType: "application/json",
        Body:        body,
    }
)

When the message is received at Raspberry Pi side, we simply need to de-serialise it accordingly to get the actual data.

Now, by connecting both the Raspberry Pi music player and the web app to the RabbitMQ server, both of them can communicate. We will then make our app web app as a sender to send the music filename over to the music player on the Raspberry Pi to instruct it to play the music.

Transferring OneDrive Music to Raspberry Pi

The music is stored on personal OneDrive Music folder online. Hence, it’s not a wise idea to always let our Raspberry Pi to stream the music from OneDrive every time the same music is chosen. Instead, we shall download only once to the Raspberry Pi right before the first time of a music is playing.

Hence, in our music player programme, we have a data file called playlist.data which keeps track of the OneDrive Item ID and its local file name on the Raspberry Pi. Once the music is successfully downloaded to the Raspberry Pi, a new line of record as shown below will be appended to the data file.

121235678##########music.mp3

Hence for the subsequent play of the same music, the music player programme will scan through the data file and play the local MP3 file instead of downloading it again from OneDrive.

func isDriveItemDownloaded(driveItemId string) bool {
    isMusicFileDownloaded := false

    dataFile, err := os.Open("playlist.dat")

    defer dataFile.Close()

    if err == nil {
        scanner := bufio.NewScanner(dataFile)
        scanner.Split(bufio.ScanLines)

        for scanner.Scan() {
            line := scanner.Text()
            if !isMusicFileDownloaded && 0 == strings.Index(line, driveItemId) {
                lineComponents := strings.Split(line, "##########")
                playSingleMusicFile(lineComponents[1])
                isMusicFileDownloaded = true
            }
        }
    }
    return isMusicFileDownloaded
}

Launch the Music Player Automatically

With our music player ready, instead of having us to launch it manually, could we make it run on our Raspberry Pi at startup? Yes, there are five ways of doing so listed on the Dexter Industry article. I choose to modify the .bashrc file which will run the music player when we boot up the Raspberry Pi and log in to it.

$ sudo nano /home/pi/.bashrc

In the .bashrc file, we simply need to append the following lines where we will change directory to the root of the music player programme. We cannot simply just run the programme just yet. We need to make sure two things.

Firstly, we need to make our music player run in the background. Doing so is because we want to continue to use the shell without waiting for the song finishes playing. So we can simply add the & to the end of the command so that the music player programme will start in the background.

Secondly, since we login to the Raspberry Pi using SSH, when we start a shell script and then logout from it, the process will be killed and thus cause the music player programme to stop. Hence, we need nohup (no hang up) command to catch the hangup signal so that our programme can run even after we log out from the shell.

# auto run customised programmes
cd /home/pi/golang/src/music-player
nohup ./music-player &

USB Speaker and ALSA

Next, we need to connect our Raspberry Pi to a speaker.

I get myself a USB speaker from Sim Lim Square which uses a “C-Media” chipset.. Now, I simply need to connect it to the Raspberry Pi.

[Image Caption: C-Media Electronics Inc. is a Taiwan computer hardware company building processors for audio devices.]

Next, we can use lsusb, as shown below, to display information about USB buses in Raspberry Pi and the devices connected to them. We should be able to locate the speaker that we just connect.

$ lsusb
Bus 001 Device 004: ID ... C-Media Electronics, Inc. Audio Adapter
Bus 001 Device 003: ID ...
...

Hence now we know that the USB speaker is successfully picked up by the Raspberry Pi OS.

We can then proceed to list down all soundcards and digital audio devices with the following command.

$ aplay -l
**** List of PLAYBACK Hardware devices ****
card 0: Headphones...
  Subdevices: 8/8
  Subdevices #0: ...
  ...
card 1: Set [C-Media USB Headphone Set], device 0: USB Audio [USB Audio]
  ...

Now we also know that the USB speaker is at the Card 1. With this info, we can set the USB speaker as the default audio device by editing the configuration file of ALSA, the Advanced Linux Sound Architecture. ALSA is used for providing kernel driven sound card drivers.

$ sudo nano /usr/share/alsa/alsa.conf

The following two lines in the file are the only lines we need to update to be as follows in the file. The reason we put “1” for both of them because the USB speaker is using card 1, as shown above. So the number may be different for your device. For more information about this change, please refer to the ALSA Project page about setting default device.

defaults.ctl.card 1
defaults.pcm.card 1

Finally, if you would like to adjust the volume of the speaker, you can use alsamixer. Alsamixer is an ncurses mixer program for use with ALSA soundcard drivers.

$ alsamixer

There is a very good article about setting up USB speaker on Raspberry Pi by Matt on raspberrypi-spy.co.uk website. Feel free to refer to it if you would like to find our more.

Next: The Web-Based Remote Control and OneDrive

Since now we’ve already successfully setup the music player on the Raspberry Pi, it’s time to move on to see how we can control it from a website and how we can access our personal OneDrive Music folder with Azure AD and Microsoft Graph.

When you’re ready, let’s continue our journey here: https://cuteprogramming.wordpress.com/2020/11/21/personal-onedrive-music-player-on-raspberry-pi-with-a-web-based-remote-control-part-2/.

References

The code of the music player described in this article can be found in my GitHub repository: https://github.com/goh-chunlin/Lunar.Music.RaspberryPi.

One thought on “Personal OneDrive Music Player on Raspberry Pi with a Web-based Remote Control (Part 1)

Leave a comment