I’ve got plexamp for the raspberry pi running on a 3b+ with a hifiberry digi+ hat running perfectly fine except for one problem. When there is no file playing plexamp will hold onto the audio device until I go into the rpi plexamp web page and manually change the output to a different device and then manually change it back when I want to use plexamp again.
Needless to say, this is tedious to do every time I want the rpi to output audio from a different source or go back to using plexamp. Is it possible to get the plexamp service to release the device after a certain time of no audio playing?
Plexamp releasing the audio device would be an enormous +1.
Now that @elan has graciously and awesomely re-bestowed headless RPI functionality (working great for me), my next hifi project is going to be trying to integrate RPI plexamp with the soon-to-be 64 bit 8.1.0 release of moOde. Essential to that project would be plexamp letting go of the device when it’s not playing.
would a long-press of the play/pause button accomplish this? that stops playback, rather than pausing it (but I agree, for the purpose of sharing the audio device with other apps, releasing the device on either pause or stop might be better)
It should be easy enough to have Plexamp use dmix instead of opening the device for exclusive output. I’m not sure if that’s desirable, however.
Alternatively, we could close the device completely any time the music stops/pauses for a bit. Not sure if that would lead to clicking/artifacts in some cases.
Thanks for the reply.
I agree going through dmix would not be desirable at all.
I know that things like the headless AirPlay player and Roon bridge applications for Linux will automatically release the audio device after a certain amount of time of not playing audio so it should be more than possible to get done.
as a temporary measure, would it be possible to query the playing status and change the output device through the command line? If so, we could set up a cron job that runs a script that queries plexamps playing status and changes the output device based on if something is playing or not.
It’s not that elegant but should be a workable hack.
This turned out to be more complicated than I thought (w/o using DMIX). However, the way the audio library works, it will open non-exclusively if something is already holding onto the audio device when it starts. This might be helpful for those who prefer something else to “own” ALSA outright.
There may be more flexible solutions in the future, but there’s no simple fix here, and in many use cases, having Plexamp grab the device and hold it for bit-perfect output is what’s desired.
Thanks for looking into it and being so responsive.
my main desire for this is:
my dac has an auto turn on and input switch feature when it seas a valid audio stream on one of its inputs and auto turn off when it has no valid audio stream. the current behavior breaks this functionality.
The ability to use other services such as airplay and Spotify connect quickly and easily would be nice to have.
We’re definitely in a better place now than we have been and you and your team are doing good work, so I’m not going to complain much about it otherwise.
Is it possible to query what plexamp is doing and change the input through bash on the rasberrypi?
it is conceptually possible for a enterprising person to figure this out, yes. i’m going to focus any time spent on trying to figure out a more … universally accessible solution
hahaha ya, that’s probably a better way to spend your time.
However, I’m just crazy enough to sink some time into it this weekend and see if I can hack something together in the meantime. At the very least to answer my curiosity to see if it can be done.
If anyone has any leads on documentation about what can be done with plex through bash let me know.
If I get really desperate I might open up fiddler and try to mimic the API calls made from the browser to the pi when selecting the audio device. though I hope I don’t have to make it that far.
It would be very useful and versatile to be able to make plexamp interwork with other audio sources like Airplay or Tidal/Spotify Connect but grabbing the audio device allows to play bit-perfect and this is absolute priority for me so, please, no mixers-in-the-middle
Alright got something hacked up. Turned out to be way easier than I thought it would be. The only caveat is that it relies on tautulli in order to monitor when the pi is streaming and not. If you’re not running it already it’s quite a nice tool to go along with your plex install. I’m not going to go over how to install it here but there are a plethora of tutorials just a google search away. So here’s how I got it working.
get a working tautulli install.
In your tautulli installs config directory(folder) make a subdirectory called scripts. In that folder make a file with the name plexamp-rpi-output-switch.py (It can honestly be called whatever you want it to be but this is what I called it). open that file and copy and paste the following script into it then save and exit from whatever text editor you’re using. Warning: It’s never a good idea to blindly copy and paste code people post on the internet so take a minute and look it over until you’re satisfied I’m not trying to do anything funny with your stuff. Or, you know, live on the wild side, I promise this code won’t give you any sort of computer virus.
import requests
import sys
import time
#replace with the hostname or IP of your raspberry pi.
#if your tautulli install is a docker container you'll probably have to use an IP address
rpi_host = "172.16.0.34" #replace with host name or ip of your raspberry pi
rpi_port = "32500"
rpi_path = "/settings"
rpi_url = "http://" + rpi_host + ":" + rpi_port + rpi_path
rpi_player_name = "rpi audio desk" #<--- this is the name of my player change for yours
#information for tautulli requests
tautulli_host = "172.16.0.32" #replace with the host name or IP of your tautulli install
tautulli_port = "8181"
tautulli_path = "/api/v2"
api_key = "c996af6732dc45578fbf52dc8d58ccae" #found in settings -> Web Interface
tautulli_url = "http://" + tautulli_host + ":" + tautulli_port + tautulli_path
device = "0,0" #default device
if len(sys.argv) > 1:
device = sys.argv[1]
make_request = True
if len(sys.argv) > 2 and sys.argv[2] == "start":
response = requests.get(rpi_url)
if response.status_code == 200:
current_device = response.json()["audioDeviceUuid"]
make_request = device not in current_device #if device is not a sub string of current device set to True
#wait 1 second then make a request to see if playback has actually stopped
elif len(sys.argv) > 2 and sys.argv[2] == "stop":
time.sleep(1)
params = {"apikey": api_key, "cmd": "get_activity"}
response = requests.get(tautulli_url, params=params)
if response.status_code == 200:
sessions = response.json()["response"]["data"]["sessions"]
#if the session still exists break if it doesn't set make_requet to false
for session in sessions:
if session["player"] == rpi_player_name and session["state"] == "playing":
make_request = False
break
if make_request:
params = {"name": "audioDeviceUuid", "value": "hw:" + device}
requests.put(rpi_url, params=params)
go to the tautulli web UI and go to settings → Notification Agents
In the window that pops up click the “Browse” button and navigate to the script directory you created. and then click on the box under “Script File” and select the script you created in step 2. Add a description if you want
(this next step is vital to make sure you’re not triggering the script all the time)
7. Click on the “Conditions” tab. In the “Parameter” drop-down select “Player”, In the “Operator” drop-down select “is”, and in the “Value” box type the name of the device as it appears in your app when you are in the “Select Player” menu.
Before we go on in tautulli we’ll need to make a little detour. open a new tab in your web browser and type the following URL in. (replace hostname.local with the IP address or hostname of your device) and you’ll see the following pop up.
Find the device you want to stream to. and make note of the number next to hw: (In my case 2,0) (note its a comma and not a period) and the number of a device that’s not hooked up to anything (in my case 0,0).
9 Go back to the browser tab that has tautulli in it and click on the “Arguments” tab. Click on “Playback Start” and enter the number of the device you want to stream to followed by a space and the word “start”.
Click Save and exit the pop-up and you should be done.
I haven’t tested it much so I don’t know if there are any edge cases that’ll make my solution break. While it’s not as good as a native baked-in solution would be, the performance is snappy and so far consistent.
On my end I had an epiphany this afternoon and tried another direction, which seemed to work. Need to do more testing and it might be a little while, but it’s promising for native support.
So for those of you interested I discovered something wrong with the assumptions I made with my previous code. I assumed tautulli would only trigger playback start and stop when the stream was completely stopped. However it looks like these commands are instead triggered every time a song starts and end respectively. meaning the output is changed back and forth rapidly between tracks causing audible artifacts. This as I’m sure you can tell is not desirable behavior.
It’s late where I am and I want to go to bed, so the patch I made is the first solution that came to mind. So it might not be the most elegant solution. The code doesn’t look very clean but it works and doesn’t produce any audible artifacts.
I resolved the issue by adding an extra argument when calling stop that has the code to wait 1 second and then check tautulli to see if anything is playing. If there is, the off switch request is not made.
I can’t think of an easy solution to stop the extra playback start calls without the script keeping track of its own state between executions. Which I’d like to avoid if I can. It also doesn’t seem to cause any problems requesting to change to the currently active device anyway. So I’ll leave that be until I think of a good solution. If anyone has any ideas I wouldn’t mind hearing them.
Anyway here’s the solution.
1 this is the new script. In addition to the rpi hostname change, you’ll need to put the tautulli IP or hostname, port, and API key in the indicated places.
import requests
import sys
import time
#replace with the hostname or IP of your raspberry pi.
#if your tautulli install is a docker container you'll probably have to use an IP address
rpi_host = "IP or hostname here" #replace with host name or ip of your raspberry pi
rpi_port = "32500"
rpi_path = "/settings"
rpi_player_name = "rpi audio desk" #<--- this is the name of my player change for yours
#information for tautulli requests
tautulli_host = "tautulli host name here" #replace with the host name or IP of your tautulli install
tautulli_port = "8181" #set the port as well if it is different
tautulli_path = "/api/v2"
api_key = "API key here" #found in settings -> Web Interface
device = "0,0" #default device
if len(sys.argv) > 1:
device = sys.argv[1]
make_request = True
#wait 1 second then make a request to see if playback has actually stopped
if len(sys.argv) > 2 and sys.argv[2] == "stop":
time.sleep(1)
url = "http://" + tautulli_host + ":" + tautulli_port + tautulli_path
params = {
"apikey": api_key,
"cmd": "get_activity"
}
response = requests.get(url, params=params)
sessions = response.json()["response"]["data"]["sessions"]
#if the session still exists break if it doesn't set make_requet to false
for session in sessions:
if session["player"] == rpi_player_name and session["state"] == "playing":
make_request = False
break
if make_request:
url = "http://" + rpi_host + ":" + rpi_port + rpi_path
params = {
"name": "audioDeviceUuid",
"value": "hw:" + device
}
requests.put(url, params=params)
in Tautulli edit the notification you made before and in the Arguments tab under playback stop type “stop” after the number you gave with a space.
Deprecated native solution has been integrated.
Had some time to come up with and code a solution to the extra start calls. the script will no longer produce any unnecessary calls to change the output device between tracks.
import requests
import sys
import time
#replace with the hostname or IP of your raspberry pi.
#if your tautulli install is a docker container you'll probably have to use an IP address
rpi_host = "172.16.0.34" #replace with host name or ip of your raspberry pi
rpi_port = "32500"
rpi_path = "/settings"
rpi_url = "http://" + rpi_host + ":" + rpi_port + rpi_path
rpi_player_name = "rpi audio desk" #<--- this is the name of my player change for yours
#information for tautulli requests
tautulli_host = "172.16.0.32" #replace with the host name or IP of your tautulli install
tautulli_port = "8181"
tautulli_path = "/api/v2"
api_key = "c996af6732dc45578fbf52dc8d58ccae" #found in settings -> Web Interface
tautulli_url = "http://" + tautulli_host + ":" + tautulli_port + tautulli_path
device = "0,0" #default device
if len(sys.argv) > 1:
device = sys.argv[1]
make_request = True
if len(sys.argv) > 2 and sys.argv[2] == "start":
response = requests.get(rpi_url)
if response.status_code == 200:
current_device = response.json()["audioDeviceUuid"]
make_request = device not in current_device #if device is not a sub string of current device set to True
#wait 1 second then make a request to see if playback has actually stopped
elif len(sys.argv) > 2 and sys.argv[2] == "stop":
time.sleep(1)
params = {
"apikey": api_key,
"cmd": "get_activity"
}
response = requests.get(tautulli_url, params=params)
if response.status_code == 200:
sessions = response.json()["response"]["data"]["sessions"]
#if the session still exists break if it doesn't set make_requet to false
for session in sessions:
if session["player"] == rpi_player_name and session["state"] == "playing":
make_request = False
break
if make_request:
params = {
"name": "audioDeviceUuid",
"value": "hw:" + device
}
requests.put(rpi_url, params=params)
in addition to the change to the playback stop argument change from the previous point add a space and the word "start the playback start argument.
I’d like to get to the point where the script can get the current playing status from the rpi directly instead of having to make a get request to tautulli but I’ll leave that for another day.
Seems to be working great. Thanks for getting a native solution out so fast.
Even though it only needed to be used for a day or two it’s been years since I sniffed an applications API and programed something for my own use. It felt good to get back into doing stuff like this.
On a side note: Now that this is working, I honestly think plexamp is a couple of features away from being a significantly better alternative to Roon. You honestly already have a significantly better radio and autoplay feature. At least for my needs if you’re ever able to get Qobuz integrated I’d drop my Roon subscription immediately.