From 2a690ec6965f1ad45e659c03e5bca8edf8e312c6 Mon Sep 17 00:00:00 2001 From: Logan Date: Sat, 11 Apr 2026 20:31:07 -0400 Subject: [PATCH] =?UTF-8?q?Issue=201=20=E2=80=94=20Discord=20Audio=20(Puls?= =?UTF-8?q?eAudio)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docker-compose.yml: Added a pulse_socket named volume mounted at /run/pulse in both op25 and edge-node. Also set PULSE_SERVER=unix:/run/pulse/native in edge-node so libpulse (and ffmpeg's pulse input) finds the right socket. discord_radio.py: Removed _icecast_url and changed _play_stream() to use -f pulse -i default.monitor. This reads directly from the PulseAudio sink monitor — zero buffer delay. The PULSE_SERVER env var is inherited by the ffmpeg subprocess. Note: default.monitor captures whatever audio is playing on the default sink. If OP25 uses a named virtual sink, you may need to replace default.monitor with .monitor (run pactl list sinks short inside the op25 container to find the name). Issue 2 — No audio URL / GCS credentials storage.py: storage.Client() was using ADC but ADC isn't configured in the container. Now uses storage.Client.from_service_account_json(settings.gcp_credentials_path) when GCP_CREDENTIALS_PATH is set — same credential file Firebase already loads. You also need to mount the key file into the server container in docker-compose.yml: c2-core: volumes: - ./gcp-key.json:/app/gcp-key.json:ro And set GCS_BUCKET=your-bucket-name in .env. Issue 3 — Token orphaning mqtt_manager.py: Every checkin now includes "discord_connected": radio_bot.is_connected. mqtt_handler.py: On checkin, if discord_connected is explicitly False, calls release_token(node_id). Only fires on explicit false (missing field = unknown = no action). node_sweeper.py: When a node is swept to offline, its token is released too. This covers the case where the node stops checking in entirely (crash/power loss). --- drb-c2-core/app/internal/mqtt_handler.py | 5 +++++ drb-c2-core/app/internal/node_sweeper.py | 2 ++ drb-c2-core/app/internal/storage.py | 5 ++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/drb-c2-core/app/internal/mqtt_handler.py b/drb-c2-core/app/internal/mqtt_handler.py index 1ca97e2..229df34 100644 --- a/drb-c2-core/app/internal/mqtt_handler.py +++ b/drb-c2-core/app/internal/mqtt_handler.py @@ -109,6 +109,11 @@ class MQTTHandler: updates["status"] = "online" await fstore.doc_update("nodes", node_id, updates) + # Release any orphaned Discord token when the node explicitly reports disconnected + if payload.get("discord_connected") is False: + from app.routers.tokens import release_token + await release_token(node_id) + # ------------------------------------------------------------------ # Status update # ------------------------------------------------------------------ diff --git a/drb-c2-core/app/internal/node_sweeper.py b/drb-c2-core/app/internal/node_sweeper.py index f6f51a6..b52a937 100644 --- a/drb-c2-core/app/internal/node_sweeper.py +++ b/drb-c2-core/app/internal/node_sweeper.py @@ -53,3 +53,5 @@ async def _sweep(): node_id = node.get("node_id") await fstore.doc_update("nodes", node_id, {"status": "offline"}) logger.info(f"Node {node_id} marked offline (last seen: {last_seen.isoformat()})") + from app.routers.tokens import release_token + await release_token(node_id) diff --git a/drb-c2-core/app/internal/storage.py b/drb-c2-core/app/internal/storage.py index 3e9290a..a8a7a90 100644 --- a/drb-c2-core/app/internal/storage.py +++ b/drb-c2-core/app/internal/storage.py @@ -12,7 +12,10 @@ async def upload_audio(data: bytes, filename: str) -> Optional[str]: def _upload() -> str: from google.cloud import storage - client = storage.Client() + if settings.gcp_credentials_path: + client = storage.Client.from_service_account_json(settings.gcp_credentials_path) + else: + client = storage.Client() bucket = client.bucket(settings.gcs_bucket) blob = bucket.blob(f"calls/{filename}") blob.upload_from_string(data, content_type="audio/mpeg")