improve changing streaming modes
This commit is contained in:
@@ -51,14 +51,11 @@ if audio_mode in ["Webapp", "USB"]:
|
||||
# Input device selection for USB mode
|
||||
if audio_mode == "USB":
|
||||
try:
|
||||
import sounddevice as sd # type: ignore
|
||||
devs = sd.query_devices()
|
||||
log.info('Found audio devices: %s', devs)
|
||||
input_options = [
|
||||
f"{idx}:{d['name']}"
|
||||
for idx, d in enumerate(devs)
|
||||
if d.get('max_input_channels', 0) > 0 and ("(hw:" in d['name'].lower() or "usb" in d['name'].lower())
|
||||
]
|
||||
resp = requests.get(f"{BACKEND_URL}/audio_inputs", timeout=1)
|
||||
if resp.status_code == 200:
|
||||
input_options = [f"{d['id']}:{d['name']}" for d in resp.json().get('inputs', [])]
|
||||
else:
|
||||
input_options = []
|
||||
except Exception:
|
||||
input_options = []
|
||||
|
||||
@@ -75,8 +72,27 @@ if audio_mode in ["Webapp", "USB"]:
|
||||
else:
|
||||
input_device = None
|
||||
start_stream = st.button("Start Auracast")
|
||||
stop_stream = st.button("Stop Auracast")
|
||||
|
||||
if stop_stream:
|
||||
try:
|
||||
r = requests.post(f"{BACKEND_URL}/stop_audio")
|
||||
if r.status_code == 200:
|
||||
st.success("Stream Stopped!")
|
||||
else:
|
||||
st.error(f"Failed to stop: {r.text}")
|
||||
except Exception as e:
|
||||
st.error(f"Error: {e}")
|
||||
|
||||
if start_stream:
|
||||
# Always send stop to ensure backend is in a clean state, regardless of current status
|
||||
try:
|
||||
requests.post(f"{BACKEND_URL}/stop_audio", timeout=5)
|
||||
except Exception:
|
||||
# Ignore connection or 500 errors – backend may not be running yet
|
||||
pass
|
||||
# Small pause lets backend fully release audio devices before re-init
|
||||
import time; time.sleep(0.7)
|
||||
# Prepare config using the model (do NOT send qos_config, only relevant fields)
|
||||
q = quality_map[quality]
|
||||
config = auracast_config.AuracastConfigGroup(
|
||||
|
||||
@@ -4,7 +4,7 @@ import logging as log
|
||||
import uuid
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
import asyncio
|
||||
import numpy as np
|
||||
from pydantic import BaseModel
|
||||
from fastapi import FastAPI, HTTPException
|
||||
@@ -13,6 +13,7 @@ from auracast import multicast_control, auracast_config
|
||||
from aiortc import RTCPeerConnection, RTCSessionDescription, MediaStreamTrack
|
||||
import av
|
||||
import av.audio.layout
|
||||
import sounddevice as sd # type: ignore
|
||||
from typing import List, Set
|
||||
import traceback
|
||||
|
||||
@@ -105,15 +106,16 @@ async def initialize(conf: auracast_config.AuracastConfigGroup):
|
||||
log.info(
|
||||
'Initializing multicaster with config:\n %s', conf.model_dump_json(indent=2)
|
||||
)
|
||||
# TODO: check if multicaster is already initialized
|
||||
multicaster = multicast_control.Multicaster(
|
||||
conf,
|
||||
conf.bigs,
|
||||
)
|
||||
await multicaster.init_broadcast()
|
||||
|
||||
# Auto-start streaming for USB microphone mode
|
||||
if any(big.audio_source.startswith('device:') for big in conf.bigs):
|
||||
# Auto-start streaming only when using a local USB audio device. For Webapp mode the
|
||||
# streamer is started by the /offer handler once the WebRTC track arrives so we know
|
||||
# the peer connection is established.
|
||||
if any(big.audio_source.startswith("device:") for big in conf.bigs):
|
||||
await multicaster.start_streaming()
|
||||
except Exception as e:
|
||||
log.error("Exception in /init: %s", traceback.format_exc())
|
||||
@@ -142,7 +144,15 @@ async def send_audio(audio_data: dict[str, str]):
|
||||
async def stop_audio():
|
||||
"""Stops streaming."""
|
||||
try:
|
||||
await multicaster.stop_streaming()
|
||||
# First close any active WebRTC peer connections so their track loops finish cleanly
|
||||
close_tasks = [pc.close() for pc in list(pcs)]
|
||||
pcs.clear()
|
||||
if close_tasks:
|
||||
await asyncio.gather(*close_tasks, return_exceptions=True)
|
||||
|
||||
# Now shut down the multicaster and release audio devices
|
||||
if multicaster is not None:
|
||||
await multicaster.stop_streaming()
|
||||
return {"status": "stopped"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
@@ -158,6 +168,20 @@ async def get_status():
|
||||
status.update(load_stream_settings())
|
||||
return status
|
||||
|
||||
@app.get("/audio_inputs")
|
||||
async def list_audio_inputs():
|
||||
"""Return available hardware audio input devices for USB mode."""
|
||||
try:
|
||||
devs = sd.query_devices()
|
||||
inputs = [
|
||||
{"id": idx, "name": d["name"]}
|
||||
for idx, d in enumerate(devs)
|
||||
if d.get("max_input_channels", 0) > 0 and ("(hw:" in d["name"].lower() or "usb" in d["name"].lower())
|
||||
]
|
||||
return {"inputs": inputs}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
PTIME = 160 # TODO: seems to have no effect at all
|
||||
pcs: Set[RTCPeerConnection] = set() # keep refs so they don’t GC early
|
||||
@@ -171,6 +195,13 @@ class Offer(BaseModel):
|
||||
async def offer(offer: Offer):
|
||||
log.info("/offer endpoint called")
|
||||
|
||||
# If a previous PeerConnection is still alive, close it so we only ever keep one active.
|
||||
if pcs:
|
||||
log.info("Closing %d existing PeerConnection(s) before creating a new one", len(pcs))
|
||||
close_tasks = [p.close() for p in list(pcs)]
|
||||
await asyncio.gather(*close_tasks, return_exceptions=True)
|
||||
pcs.clear()
|
||||
|
||||
pc = RTCPeerConnection() # No STUN needed for localhost
|
||||
pcs.add(pc)
|
||||
id_ = uuid.uuid4().hex[:8]
|
||||
@@ -211,7 +242,6 @@ async def offer(offer: Offer):
|
||||
|
||||
log.info(f"mono_array.shape: {mono_array.shape}")
|
||||
|
||||
audio_input = list(multicaster.bigs.values())[0]['audio_input']
|
||||
|
||||
|
||||
frame_array = frame.to_ndarray()
|
||||
@@ -225,6 +255,12 @@ async def offer(offer: Offer):
|
||||
else:
|
||||
mono_array = samples
|
||||
|
||||
# Get current WebRTC audio input (streamer may have been restarted)
|
||||
big0 = list(multicaster.bigs.values())[0]
|
||||
audio_input = big0.get('audio_input')
|
||||
# Wait until the streamer has instantiated the WebRTCAudioInput
|
||||
if audio_input is None or getattr(audio_input, 'closed', False):
|
||||
continue
|
||||
# Feed mono PCM samples to the global WebRTC audio input
|
||||
await audio_input.put_samples(mono_array.astype(np.int16))
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ class WebRTCAudioInput:
|
||||
logging.debug(f"WebRTCAudioInput: Added {len(samples)} samples, buffer now has {len(self.buffer)} samples.")
|
||||
self.data_available.set()
|
||||
|
||||
def close(self):
|
||||
async def close(self):
|
||||
"""Mark the input closed so frames() stops yielding."""
|
||||
self.closed = True
|
||||
self.data_available.set()
|
||||
|
||||
Reference in New Issue
Block a user