fix usb device update behavior
This commit is contained in:
@@ -66,7 +66,12 @@ if audio_mode in ["Webapp", "USB"]:
|
||||
if default_input not in input_options:
|
||||
default_input = input_options[0]
|
||||
|
||||
col1, col2 = st.columns([3, 1], vertical_alignment="bottom")
|
||||
with col1:
|
||||
selected_option = st.selectbox("Input Device", input_options, index=input_options.index(default_input))
|
||||
with col2:
|
||||
if st.button("Refresh"):
|
||||
st.rerun()
|
||||
# We send only the numeric/card identifier (before :) or 'default'
|
||||
input_device = selected_option.split(":", 1)[0] if ":" in selected_option else selected_option
|
||||
else:
|
||||
@@ -76,23 +81,22 @@ if audio_mode in ["Webapp", "USB"]:
|
||||
|
||||
if stop_stream:
|
||||
try:
|
||||
r = requests.post(f"{BACKEND_URL}/stop_audio")
|
||||
if r.status_code == 200:
|
||||
r = requests.post(f"{BACKEND_URL}/stop_audio").json()
|
||||
if r['was_running']:
|
||||
st.success("Stream Stopped!")
|
||||
else:
|
||||
st.error(f"Failed to stop: {r.text}")
|
||||
st.success("Stream was not running.")
|
||||
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
|
||||
r = requests.post(f"{BACKEND_URL}/stop_audio").json()
|
||||
if r['was_running']:
|
||||
st.success("Stream Stopped!")
|
||||
|
||||
# Small pause lets backend fully release audio devices before re-init
|
||||
import time; time.sleep(0.7)
|
||||
import time; time.sleep(1)
|
||||
# Prepare config using the model (do NOT send qos_config, only relevant fields)
|
||||
q = quality_map[quality]
|
||||
config = auracast_config.AuracastConfigGroup(
|
||||
|
||||
@@ -3,6 +3,7 @@ import os
|
||||
import logging as log
|
||||
import uuid
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
import asyncio
|
||||
import numpy as np
|
||||
@@ -14,9 +15,18 @@ from aiortc import RTCPeerConnection, RTCSessionDescription, MediaStreamTrack
|
||||
import av
|
||||
import av.audio.layout
|
||||
import sounddevice as sd # type: ignore
|
||||
from typing import List, Set
|
||||
from typing import Set
|
||||
import traceback
|
||||
|
||||
|
||||
PTIME = 40 # TODO: seems to have no effect at all
|
||||
pcs: Set[RTCPeerConnection] = set() # keep refs so they don’t GC early
|
||||
|
||||
class Offer(BaseModel):
|
||||
sdp: str
|
||||
type: str
|
||||
|
||||
|
||||
# Path to persist stream settings
|
||||
STREAM_SETTINGS_FILE = os.path.join(os.path.dirname(__file__), 'stream_settings.json')
|
||||
|
||||
@@ -38,6 +48,7 @@ def save_stream_settings(settings: dict):
|
||||
except Exception as e:
|
||||
log.error('Unable to persist stream settings: %s', e)
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# Allow CORS for frontend on localhost
|
||||
@@ -151,9 +162,12 @@ async def stop_audio():
|
||||
await asyncio.gather(*close_tasks, return_exceptions=True)
|
||||
|
||||
# Now shut down the multicaster and release audio devices
|
||||
running=False
|
||||
if multicaster is not None:
|
||||
await multicaster.stop_streaming()
|
||||
return {"status": "stopped"}
|
||||
running=True
|
||||
|
||||
return {"status": "stopped", "was_running": running}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@@ -172,25 +186,25 @@ async def get_status():
|
||||
async def list_audio_inputs():
|
||||
"""Return available hardware audio input devices for USB mode."""
|
||||
try:
|
||||
# Re-scan devices on Linux, see https://github.com/spatialaudio/python-sounddevice/issues/16
|
||||
if sys.platform == 'linux':
|
||||
log.info("Re-initializing sounddevice to scan for new devices")
|
||||
sd._terminate()
|
||||
sd._initialize()
|
||||
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())
|
||||
]
|
||||
log.info('Found %d audio input devices:', len(inputs))
|
||||
for i in inputs:
|
||||
log.info(' %s', i)
|
||||
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
|
||||
|
||||
class Offer(BaseModel):
|
||||
sdp: str
|
||||
type: str
|
||||
|
||||
|
||||
@app.post("/offer")
|
||||
async def offer(offer: Offer):
|
||||
log.info("/offer endpoint called")
|
||||
@@ -210,7 +224,9 @@ async def offer(offer: Offer):
|
||||
# create directory for records - only for testing
|
||||
os.makedirs("./records", exist_ok=True)
|
||||
|
||||
await multicaster.start_streaming()
|
||||
# Do NOT start the streamer yet – we'll start it lazily once we actually
|
||||
# receive the first audio frame, ensuring WebRTCAudioInput is ready and
|
||||
# avoiding race-conditions on restarts.
|
||||
@pc.on("track")
|
||||
async def on_track(track: MediaStreamTrack):
|
||||
log.info(f"{id_}: track {track.kind} received")
|
||||
@@ -224,6 +240,12 @@ async def offer(offer: Offer):
|
||||
log.info(
|
||||
f"{id_}: frame sample_rate={frame.sample_rate}, samples_per_channel={frame.samples}, planes={frame.planes}"
|
||||
)
|
||||
# Lazily start the streamer now that we know a track exists.
|
||||
if multicaster.streamer is None:
|
||||
await multicaster.start_streaming()
|
||||
# Yield control so the Streamer coroutine has a chance to
|
||||
# create the WebRTCAudioInput before we push samples.
|
||||
await asyncio.sleep(0)
|
||||
first = False
|
||||
# in stereo case this is interleaved data format
|
||||
frame_array = frame.to_ndarray()
|
||||
@@ -243,7 +265,6 @@ async def offer(offer: Offer):
|
||||
log.info(f"mono_array.shape: {mono_array.shape}")
|
||||
|
||||
|
||||
|
||||
frame_array = frame.to_ndarray()
|
||||
|
||||
# Flatten in case it's (1, N) or (N,)
|
||||
|
||||
Reference in New Issue
Block a user