spatialaudio / python-sounddevice

:sound: Play and Record Sound with Python :snake:
https://python-sounddevice.readthedocs.io/
MIT License
980 stars 145 forks source link

How can I stop the stream and start another one? #437

Open elroropiroro opened 1 year ago

elroropiroro commented 1 year ago

using the example code "play a web stream", I am trying to press a button to change the URL to use, however, the current stream does not stop or does not change in this case.

I have tried using the API "stop", "abort", "CallbackStop" but it only stops the script which is the last thing I want.

So, what I am looking for is to play several web pages stream without stopping the script using buttons.

web stream list

here is where I create the list of stations to use and a loop to change them using pushbuttons:

NEXT = next station PREV = previous station COUNTRY = choose country to stream those stations

argentina_stations = [ 
    "https://26423.live.streamtheworld.com/LOS40_ARGENTINA.mp3","http://server.laradio.online:25224/live.mp3"
]
spain_stations = [
     "https://bbkissfm.kissfmradio.cires21.com/bbkissfm.mp3", "https://dreamsiteradiocp4.com-proxy-rmspain1"
]

#BUTTONS 

NEXT = 17  
PREV = 23
COUNTRY = 25

current_country = "argentina"
current_stations = argentina_stations
currently_playing = current_stations[0]

while True:
    if ( GPIO.input(COUNTRY) == True):
        if current_country == "argentina":
             current_stations = spain_stations
             current_country = "spain"
        else:   
            current_stations = argentina_stations
            current_country = "argentina"

    if  ( GPIO.input(NEXT) == True):
        if currently_playing == current_stations[0]:
            currently_playing = current_stations[1]
        else:
            currently_playing = current_stations[0]

    if ( GPIO.input(PREV) == True):
        if currently_playing == current_stations[1]:
            currently_playing = current_stations[0]
        else:
             currently_playing = current_stations[1]
    break

This is the example code adapted to what I am trying to do. I have been using the functions "stop" or "abort" to stop the stream and repeat the loop with the new URL. However, testing the functionality of the buttons, in this case the NEXT, the value of "currently_playing" does change, but when I get to the stop() function the whole script stops and I don't know how to make it just interrupt the stream and start again with the new URL as a normal radio.

import argparse
import queue
import sys

import ffmpeg
import sounddevice as sd

#PLAY WEB STREAM 

def int_or_str(text):
    """Helper function for argument parsing."""
    try:
        return int(text)
    except ValueError:
        return text

parser = argparse.ArgumentParser(add_help=False)
parser.add_argument(
    '-l', '--list-devices', action='store_true',
    help='show list of audio devices and exit')
args, remaining = parser.parse_known_args()
if args.list_devices:
    print(sd.query_devices())
    parser.exit(0)
parser = argparse.ArgumentParser(
    description=__doc__,
    formatter_class=argparse.RawDescriptionHelpFormatter,
    parents=[parser])
parser.add_argument(
    '-d', '--device', type=int_or_str,
    help='output device (numeric ID or substring)')
parser.add_argument(
    '-b', '--blocksize', type=int, default=3072,
    help='block size (default: %(default)s)')
parser.add_argument(
    '-q', '--buffersize', type=int, default=20,
    help='number of blocks used for buffering (default: %(default)s)')
args = parser.parse_args(remaining)
if args.blocksize == 0:
    parser.error('blocksize must not be zero')
if args.buffersize < 1:
    parser.error('buffersize must be at least 1')

q = queue.Queue(maxsize=args.buffersize)

print('Getting stream information ...')

def StreamRadio():

try:
    info = ffmpeg.probe(currently_playing)
except ffmpeg.Error as e:
    sys.stderr.buffer.write(e.stderr)
    parser.exit(e)

streams = info.get('streams', [])
if len(streams) != 1:
    parser.exit('There must be exactly one stream available')

stream = streams[0]

if stream.get('codec_type') != 'audio':
    parser.exit('The stream must be an audio stream')

channels = stream['channels']
samplerate = float(stream['sample_rate'])

def callback(outdata, frames, time, status):
    assert frames == args.blocksize
    if status.output_underflow:
        print('Output underflow: increase blocksize?', file=sys.stderr)
        raise sd.CallbackAbort
    assert not status
    try:
        data = q.get_nowait()
    except queue.Empty as e:
        print('Buffer is empty: increase buffersize?', file=sys.stderr)
        raise sd.CallbackAbort from e
    assert len(data) == len(outdata)
    outdata[:] = data

try:
    print('Opening stream ...')
    process = ffmpeg.input(
        currently_playing
    ).output(
        'pipe:',
        format='f32le',
        acodec='pcm_f32le',
        ac=channels,
        ar=samplerate,
        loglevel='quiet',
    ).run_async(pipe_stdout=True)
    stream = sd.RawOutputStream(
        samplerate=samplerate, blocksize=args.blocksize,
        device=args.device, channels=channels, dtype='float32',
        callback=callback)
    read_size = args.blocksize * channels * stream.samplesize
    print('Buffering ...')
    for _ in range(args.buffersize):
        q.put_nowait(process.stdout.read(read_size))
    print('Starting Playback ...')
    with stream:
        timeout = args.blocksize * args.buffersize / samplerate
        while True:
            q.put(process.stdout.read(read_size), timeout=timeout)
                if  ( GPIO.input(NEXT) == True):
                    if currently_playing == current_stations[0]:
                        currently_playing = current_stations[1]
                    else:
                        currently_playing = current_stations[0]
                    stop()
                    StreamRadio()

except KeyboardInterrupt:
    parser.exit('\nInterrupted by user')
except queue.Full:
    # A timeout occurred, i.e. there was an error in the callback
    parser.exit(1)
except Exception as e:
    parser.exit(type(e).__name__ + ': ' + str(e))

StreamRadio()
mgeier commented 1 year ago

Please use code formatting, see https://python-sounddevice.readthedocs.io/en/0.4.5/CONTRIBUTING.html#reporting-problems

elroropiroro commented 1 year ago

Please use code formatting, see https://python-sounddevice.readthedocs.io/en/0.4.5/CONTRIBUTING.html#reporting-problems

My mistake, I already updated the topic, please review it.

mgeier commented 1 year ago

Thanks for the update!

I guess you'll have to somehow stop the process and then re-create it with the new URL. Otherwise, your changes to currently_playing will have no effect.