Issue 1 — Discord Audio (PulseAudio)
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 <sink_name>.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).
This commit is contained in:
@@ -109,6 +109,11 @@ class MQTTHandler:
|
|||||||
updates["status"] = "online"
|
updates["status"] = "online"
|
||||||
await fstore.doc_update("nodes", node_id, updates)
|
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
|
# Status update
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|||||||
@@ -53,3 +53,5 @@ async def _sweep():
|
|||||||
node_id = node.get("node_id")
|
node_id = node.get("node_id")
|
||||||
await fstore.doc_update("nodes", node_id, {"status": "offline"})
|
await fstore.doc_update("nodes", node_id, {"status": "offline"})
|
||||||
logger.info(f"Node {node_id} marked offline (last seen: {last_seen.isoformat()})")
|
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)
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ async def upload_audio(data: bytes, filename: str) -> Optional[str]:
|
|||||||
|
|
||||||
def _upload() -> str:
|
def _upload() -> str:
|
||||||
from google.cloud import storage
|
from google.cloud import storage
|
||||||
|
if settings.gcp_credentials_path:
|
||||||
|
client = storage.Client.from_service_account_json(settings.gcp_credentials_path)
|
||||||
|
else:
|
||||||
client = storage.Client()
|
client = storage.Client()
|
||||||
bucket = client.bucket(settings.gcs_bucket)
|
bucket = client.bucket(settings.gcs_bucket)
|
||||||
blob = bucket.blob(f"calls/{filename}")
|
blob = bucket.blob(f"calls/{filename}")
|
||||||
|
|||||||
Reference in New Issue
Block a user