Callback function in the exact moment URL stream starts playing in Python

151 views Asked by At

I'm currently working on a internet radio player in Python for a Raspberry Pi. The functioning is simple: on user input the program randomly chooses one URL radio stream from a predefined list and starts playing it through omxplayer as a subprocess.

Here's my code:

import simpleaudio as sa
import random
import sys, termios, tty, os
import subprocess

radioList = ["https://edge126.rcs-rds.ro/profm/profm.mp3", "https://live.guerrillaradio.ro:8443/guerrilla.aac", "http://aska.ru-hoster.com:8053/autodj", "https://live.rockfm.ro/rockfm.aacp", "https://live.magicfm.ro/magicfm.aacp", "http://zuicast.digitalag.ro:9420/zu"]

class AudioPlayer: # used only for playing noise.wav until radio stream starts 
    def __init__(self):
        self.play_obj = None

    def play(self, filename):
        if not self.is_done():
            self.play_obj.stop()

        wave_obj = sa.WaveObject.from_wave_file(filename)
        self.play_obj = wave_obj.play()
        
    def stop(self):
        self.play_obj.stop()

    def is_done(self):
        if self.play_obj:
            return not self.play_obj.is_playing()
        return True

def getRandomURL(radioList): # returns random URL from provided list
    r = random.randint(1, len(radioList)) - 1
    return radioList[r]

def getch(): # reads input character from stdin
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setraw(sys.stdin.fileno())
        ch = sys.stdin.read(1)

    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return ch


noisePlayer = AudioPlayer() # create audio instance
omxprocess = ""


while True:
    char = getch()
        
    if(char == "p"):
        if omxprocess != "":
            omxprocess.kill()
        omxprocess = subprocess.Popen(['omxplayer', getRandomURL(radioList)], stdin=subprocess.PIPE) # you should change 'omxplayer' to 'cvlc' or 'mplayer' if you're not running this code on a raspi
        
        # noisePlayer.play("noise.wav") # this file should be played from the moment the above process is started until the radio stream is actually loaded and starts playing
        
    if(char == "x"):
        if omxprocess != "":
            omxprocess.kill()
        exit()

However, I'm running this code on a Raspberry Pi Zero and it often takes up to 3-4 seconds (or even longer) until the stream starts playing. What I'd like to do is to play a local audio file (noise.wav in this example, which can be generated at: https://www.random.org/audio-noise/) until the exact moment the stream starts playing, so there will be no silent-dead moments between commuting radio stations. My problem is that I have no idea how such a thing could be accomplished, as I found nothing in regards to any signal or event being triggered by omxplayer, nor vlc or mplayer when a media stream is actually loaded and starting to play.

I did however find out that omxplayer sends some information to stdout which gets printed exactly when the stream starts to play (the same being true for vlc and mplayer). Here's an example:

pi@raspi:~ $ omxplayer https://live.rockfm.ro/rockfm.aacp
Audio codec aac channels 2 samplerate 44100 bitspersample 16
Subtitle count: 0, state: off, index: 1, delay: 0
have a nice day ;)
pi@raspi:~ $

I'm curious if there's some way I could use this output as a trigger to stop noisePlayer from playing (using noisePlayer.stop()) or if there's any other way I can trigger noisePlayer.stop() when the radio stream starts playing? One option I tried is using Timer function from threading module, however it often overlays with the radio stream which can start playing faster:

        Timer(0.5, noisePlayer.play, ["noise.wav"]).start()
        Timer(1.5, noisePlayer.stop).start()

The only article I've found regarding my concern is this one: Python communicate with omxplayer However, it didn't quite answer my problem.

I'd highly, truly appreciate if anyone could give an exact implementation to this concern or a solution on how it could be solved, as I'm simply unable to wrap my head around it :)

1

There are 1 answers

1
Doyousketch2 On

You just need to include a subprocess.PIPE for stdout.
can also include one for stderr, if you wish to test for that.

import subprocess as sub

stdout, stderr  ##  forward declarations so that these exist within scope

while True:
    char = getch()

    if(char == "p"):
        if omxprocess != "":
            omxprocess.kill()
            command = ['omxplayer', getRandomURL(radioList)]
        omxprocess = sub.Popen(command, stdin=sub.PIPE, stdout=sub.PIPE, stderr=sub.PIPE)

        stdout, stderr = omxprocess.communicate()

        noisePlayer.play("noise.wav")

    if stdout or stderr: noisePlayer.stop()

    if(char == "x"):
        if omxprocess != "":
            omxprocess.kill()
        exit()