mk-fg / python-pulse-control

Python high-level interface and ctypes-based bindings for PulseAudio (libpulse)
https://pypi.org/project/pulsectl/
MIT License
170 stars 36 forks source link

State of sink inputs: Is there sink input? #52

Closed Syphdias closed 3 years ago

Syphdias commented 3 years ago

I am trying to write a script to change the volume of the running application instead of the entire sink. I tried to figure out which sink input is currently playing sound. Ideally I would get the current sound level for every sink input and if it is greater than zero, it is running.

I found pacmd list-sink-inputs which has state (not perfect since some inputs always seem running).

❯ pacmd list-sink-inputs |grep -e state: -e index: -e client: # spotify stopped
    index: 1
    state: RUNNING
    client: 7 <TeamSpeak3>
    index: 15
    state: CORKED
    client: 2 <Spotify>
❯ pacmd list-sink-inputs |grep -e state: -e index: -e client: # spotify playing
    index: 1
    state: RUNNING
    client: 7 <TeamSpeak3>
    index: 15
    state: RUNNING
    client: 2 <Spotify>

I tried to find this state with pulsectl but I failed. Is there any way to get the state of sink-input or its current sound level? It might be the client state I am looking for. I don't know...

mk-fg commented 3 years ago

Hi,

Yeah, that's what Pulse.get_peak_sample() method does, in the same way as you see it displayed for each app in the first tab of pavucontrol window (volume tool that usually comes with pulse). You need to pass it a sink and an index of a stream as "stream_idx" parameter, which hopefully docstring on it explains a bit.

For more gory details on how it works, see https://github.com/mk-fg/python-pulse-control/issues/33 where it was originally added, and where person wanted to do fairly similar thing, I think.

Syphdias commented 3 years ago

Thanks! Exactly what I needed. I found this to be quite useful.

with Pulse() as pulse:
    for sink in pulse.sink_input_list():
        print(sink)
        print("Before peak sample.", flush=True)
        print(pulse.get_peak_sample(None, .07, sink.index))
        print("After peak sample.", flush=True)

I couldn't go lower than .7 for timeout without getting false negatives.

mk-fg commented 3 years ago

Technically if no one's talking in teamspeak or there's a pause between tracks in spotify, you should be able to get false-negative even with seconds of timeout.

If you want something more robust like instant "was there any output within last 1 min?" queries, I'd suggest using pulsectl-asyncio instead, where you can just integrate periodic sampling into asyncio eventloop to continusly monitor such streams alongside other code, instead of stopping everything to monitor them for a short time like blocking code in this module does.

Syphdias commented 3 years ago

Technically if no one's talking in teamspeak or there's a pause between tracks in spotify, you should be able to get false-negative even with seconds of timeout.

Yes, totally could happen but I am not too worried about that.

I used some async functionality in other projects but I want to keep this kinda simple, not have a daemon and a client component and replace my regular volume knobs and buttons. I might run into issues if I have too many sink inputs but until that happens, synchronous it is. :)