Mopidy as Mumble music bot
Botamusique re-implements many media player features - web interface, playing music from youtube.
However, there are traditional FOSS media servers out there implementing the same features, like Mopidy.
In this post, I’ll show you how to make Mopidy do all the media-related heavy lifting, and how to only redirect the audio from Mopidy so you can hear it in a Mumble server.
Why Mopidy
I understand you may already have e.g. Navidrome or Jellyfin on your local network and may be annoyed by having to install “yet another” media server.
A bit of explanation of why we’re using Mopidy instead of e.g. Subsonic:
1. Mopidy is a jukebox by default
Opening the Mopidy web UI in multiple browser windows and pressing “Play” in one of them will show the notification Browser1 resumed playback in all of the other windows. Then, the audio will be played on the server where Mopidy is installed (NOT in the browsers).
Subsonic on the other hand would allow each browser window to have a separate audio stream. In Subsonic’s defense though, it supports Jukebox mode in certain implementations (see gonic Subsonic server implementation).
2. Mopidy supports playing audio of videos from youtube
Mopidy has a nice Mopidy-Youtube extension which seamlessly integrates with Mopidy’s own search functionality.
Subsonic on the other hand is all about a playing local media files and has no youtube support.
Mopidy-Youtube is just one of many extensions. With extensions, Mopidy can also:
- play Spotify
- be controlled with MPD
- connect to Jellyfin
- or even connect to Subsonic with
Mopidy-Subidy.
All of the above sound good for a music bot use-case or usage by a power user.
Let’s start! Mopidy setup
Mopidy plays audio to a local, hardware audio output by default. However, you don’t want to hear audio through on the built-in speakers of your server where Mopidy will be running. You want sound to be heard in a channel on your Mumble server.
This section focuses only on the basics:
- Installing Mopidy and a web UI for it
- Getting it to download and play audio from youtube videos
- Playing the youtube video’s audio to your local headphones or speakers
In further sections, you’ll learn how to broadcast Mopidy’s audio output to the network and get Mumble to consume that audio stream.
I recommend following the below instructions on your personal computer first. When you are sure everything is working - move the setup to your server.
Installing Mopidy
Install Mopidy using your Linux distribution’s package manager. (on Arch Linux paru -S mopidy)
Run $ mopidy in the terminal to see if it starts at all and go to http://localhost:6680 to see the if the (rather empty) default page is there.
There’s not much for you to do on that page, because I didn’t tell you how to install a web interface yet.
Installing a web interface
Mopidy has no web interface by default - you need to download it separately. I’ll show you how to install the iris web UI.
Install according to the instructions here (on Arch Linux paru -S mopidy-iris), then restart mopidy.
Now go to http://localhost:6680/iris/, where you should see a web UI. Next, I’ll show you how to play a song from the file system and youtube.
Register extensions for playing songs
Playing songs from the filesystem
We’ll be debugging sending audio over the network to Mumble, so it’s good to have good old reliable playback of audio files from the filesystem.
Playing from filesystem is supported out of the box - no need to download anything.
When you’ve first ran Mopidy, it should’ve created a ~/.config/mopidy/mopidy.conf file for you.
Open it in your editor of choice, find the [file] section and modify it as such (remember to restart mopidy once you’re done):
[file]
enabled = true
media_dirs = /path/to/folder/containing/music/files
Now go to http://localhost:6680/iris/library/browse, then click the “Files” tile. Find a song and double click it to see if it plays through your personal computer’s speakers.
Now let’s move on to playing audio from youtube videos.
Playing songs from YouTube
Playback of youtube videos’ audio is supported by the Mopidy-Youtube extension - install it using the below instructions.
WARNING: By default, Mopidy-Youtube uses the youtube-dl executable by to download videos. Unfortunately, when I tried to use it that way, it kept showing me errors related to “uploader_id” and did not play any sound. Thankfully, Mopidy-Youtube’s settings let you change youtube-dl to something else. I changed it to use yt-dlp instead of youtube-dl. That fixed all of my video download issues. The below instructions take that fix into account.
Run:
pip install Mopidy-Youtube # this is how I install mopidy-youtube, if you used another way, skip this
pip install yt-dlp
then in your mopidy.conf add:
[youtube]
enabled = true
youtube_dl_package = yt_dlp
# yt_dlp with an underscore, not with a dash
and restart Mopidy.
Here it’s helpful to start Mopidy from the terminal (just $ mopidy). It’s so you can inspect the output in case any errors occur when trying to play your first youtube video using yt-dlp from Mopidy.
Now let’s see if the youtube integration works, and whether we can search and play videos.
In the Iris web interface, click Search in the sidebar in the top left. Before your first search, know that the search results take a moment to load, take notice of the loading indicator in the bottom right (after running a search).
Now, type in any search term as if you were on youtube’s regular webpage, e.g. “audio test”. Double click one of the results and see if you hear any audio.
If you can’t find the search bar, just go to this link: http://localhost:6680/iris/search/all/youtube%20audio%20test
If the search is not returning any results, look at the right side of the search bar and see if a “Sources” button is visible there. If it’s there, click it and select “Youtube”. For some reason it disappeared for me now, but it appears sometimes.
If it’s still not returning any results, something’s broken deeper down. See if there’s any red text in the terminal output. Look at yt-dlp’s issue tracker on github to see if other people are having the same problem. Sometimes a change on youtube’s side breaks yt-dlp and you need to wait for an udpate of yt-dlp.
Taking Mopidy output and streaming it to the network
Currently Mopidy should be only playing audio to your local headphones or speakers.
In this section, you’ll move one step closer to playing your music in Mumble - you’ll take Mopidy’s audio output and broadcast it on your network, so other applications can do something with the audio. One such application could be … a Mumble bot that’d listen to the audio you are broadcasting and play it back in Mumble.
wink wink wink in the section after this one cough
Streaming to the network is really easy to set up (once you have the code handed to you on a silver plattter haha). There’s only one step - modify your ~/.config/mopidy/mopidy.conf like this:
[audio]
output = audioconvert ! audio/x-raw,format=S16LE,rate=48000,channels=2 ! udpsink host=127.0.0.1 port=5004
The syntax is a bit uncommon, but this is how you write gstreamer pipelines. There’s an example of gstreamer syntax on the Mopidy wiki too.
Basically, the above snippet does this:
- Takes the raw audio played by Mopidy
- Converts it to a format that the Python library for interacting with Mumble servers supports - see
add_soundhere - Broadcasts it on your network using UDP
Taking the audio stream from the network and playing it back in Mumble
Now that you have an audio stream being broadcasted on your network, you just need a bot that’ll listen to that stream and play it back in your Mumble’s server channel as if it were “speaking”.
I have to thank user bitconnector from github for writing this example of a music bot that plays a local audio file in a Mumble channel. I wouldn’t have figured out how to do this without it.
Instructions
- Run
pip install pymumblebecause the bot code depends on this library. All other dependencies are Python builtins
- Copy the code below this list and save it in a file named
udp.py
- Change the
93.184.216.34IP address inudp.pyto the IP address of your Mumble server.
If you havemurmur(Mumble server) installed, you can runmurmurlocally and set the IP in the script to127.0.0.1
The code will try to connect to Mumble the default Mumble port which is64738
If your Mumble server needs a password to connect to or has a custom port, you’ll need to modify the call ofpymumble_py3.Mumble()to take that into account. See the docs here for available arguments
- Run
python udp.py
- Open the Mumble desktop client and connect to the same IP as you provided in
udp.pyas you normally would so you can listen to your bot “speak” audio. The bot should already be connected and have the nicknamemopidyplayer
- Open the Mopidy Iris web UI and play some video from Youtube
The udp.py bot should start playing audio in Mumble.
The bot code to put into udp.py:
import socket
import time
import subprocess as sp
import pymumble_py3
UDP_IP = "127.0.0.1"
UDP_PORT = 5004
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# REMEMBER TO CHANGE THE IP HERE TO YOUR MUMBLE SERVER'S IP
# If your server needs a passsword to connect to it, use the password= argument
# If your server uses a port different than the Mumble default of 64738, use the port= argument and pass the port as a number
mumble = pymumble_py3.Mumble('93.184.216.34', 'mopidyplayer', stereo=True, reconnect=True)
mumble.set_codec_profile('audio')
mumble.start()
mumble.is_ready() #wait for Mumble to get ready to avoid errors after startup
mumble.set_bandwidth(192000)
sock.bind((UDP_IP, UDP_PORT))
while True:
while True:
raw_music, addr = sock.recvfrom(65507)
if not raw_music:
break
mumble.sound_output.add_sound(raw_music)
while mumble.sound_output.get_buffer_size() > 0.5: #
time.sleep(0.01)
time.sleep(2)
Postscriptum: Improvements to be done
- Why not just implement a
Mopidy-Mumbleextension than go through all this… - There are probably smarter/lower latency way of broadcasting the audio packets from the Mopidy gstreamer pipeline.
I’m just using raw UDP but there’s also RTP , WebRTC and so on.
RTP can be done using thertpL16paypipeline component in gstreamer in Mopidy. It outputs only big endian S16BE so you’d need to swap the endianness on the Python script side, usingraw_music[::-1], becausepymumbleonly takes S16LE little endian.
Postscriptum: Don’t use Icecast for this
Instead of streaming audio directly from Mopidy’s [audio] output to the Mumble bot like I’ve shown above, you might’ve come across Mopidy’s wiki recommendation:
If you want to play the audio on another computer than the one running Mopidy, you can stream the audio from Mopidy through an Icecast audio streaming server.
Unfortunately, audio streamed from Icecast will have a lot of delay. When you press “Play” in the web UI, you’ll hear sound in Mumble after a couple of seconds, instead of instantly. Same if you stop the playback. The delay makes listening to things in Mumble rather unenjoyable.
Here’s what Thomas B. Ruecker - one of the Icecast maintainers - has to say on the topic (source, accessed at 2023-06-25):
Don’t use Icecast if you need sub-second latency!
Don’t use Icecast if you need sub-10-second latency and don’t have full control over the whole chain of software and network!
Yes, this is an answer, not a comment. Icecast is not designed for such use cases. It was designed for 1-to-n bulk broadcast of data over HTTP in non-synchronized fashion.
What you are explaining sounds like you really should consider something that was designed to be low latency, like web-RTC.
If you think you really should use Icecast, please explain why. Because your question speaks otherwise. I’m all for more Icecast use, I’m it’s maintainer after all, but its application should make sense.
Additionally, streaming audio directly from Mopidy’s [audio] output is also simpler, because you need to only set up Mopidy and the Mumble bot. Here you need Icecast in addition.
Nonetheless, I used Icecast for this purpose for a while. If for some reason you need to too, some instructions are below.
Installing
Icecast can be installed on Arch Linux using pacman -S icecast.
On Debian, it’s apt install icecast2.
Configuring
The [audio] snippets on the linked Mopidy wiki page are meant to be put in ~/.config/mopidy/mopidy.conf (or /etc/mopidy/mopidy.conf if you start mopidy using a systemd service). Put those snippets in now.
The snippets conveniently already contain the default credentials for Icecast.
Running
To run, do icecast -c path_to_icecast_config_file.xml (copy config file from /etc/icecast.xml). Do not modify the default config - it’s not necessary.
You can also start Icecast using the systemd service systemctl start icecast (icecast2 on Debian)
So, provided that you:
- Added the necessary config to the
[audio]section inmopidy.conf - Started Icecast
- Started Mopidy
You should now be able to play some song in Mopidy and listen it from your browser through the Icecast stream.
To listen to the Icecast stream, go to http://youriphere:8000/mopidy (the resource is /mopidy because the line of code in the [audio] section in mopidy.conf we’ve added has mount=mopidy - the /mopidy path gets registered automatically when mopidy starts. You don’t need to modify the icecast config to add the /mopidy path manually)
Now go to the Iris web UI and play a song. If you can hear the song you’re playing, congrats! That’s it!
Troubleshooting
WARNING: The instructions above worked fine for me on Arch Linux, but on Debian 11 Bullseye I ran into the following issue:
- Start icecast2
- Start mopidy
- Play some song in Mopidy through the Iris web interface
- Here’s the bad problem:
youriphere:8000/mopidyreturns 404, and mopidy’s logs (journalctl -eu mopidy) sayERROR [MainThread] mopidy.audio.gst GStreamer error: Could not connect to server
To fix this, I recommend following the excellent guide over at jc-lan: https://jc-lan.org/2020/05/26/mopidy-fails-to-connect-to-icecast2-server-with-ubuntu-20-04/
This issue is also mentioned at the top of the Mopidy’s Icecast wiki page I’ve linked (regarding libshout3).