Azaan Broadcast
:::info Status
Implemented. Source: docs/azan-flow.md, docs/live-broadcast-flow.md, docs/transmitter-audio-uplink-contract.md.
:::
Delivers the azan (call to prayer) live from a microphone, or automatically from a recording when nobody broadcasts in time. See Data Flow for the end-to-end diagram; this page is the module-level detail.
Prerequisites (per mosque, one-time)
- Azan voices — Admin Panel → Azan Audio: upload/record a Fajr
recording (
audio_files.prayer = 'fajr') and a general recording (audio_files.prayer IS NULL). Multiple recordings per category are kept; the newest becomesis_defaultautomatically, any older one can be re-promoted. - Prayer times — a saved timetable (
mosque_prayer_times) or mosque coordinates/city+pincode for the AlAdhan API fallback. See Prayer Timing module. - Scheduler settings — Admin Panel → Auto Azan (
azan_auto_settings): master on/off, grace/window minutes, per-prayer toggles.
Path A — Live azan
A transmitter can also be the source, over its own dedicated audio uplink WebSocket — same FFmpeg → Icecast pipeline, same fan-out function, no second implementation.
Path B — Offline azan (scheduler fallback)
core/azan_scheduler.py, started in the FastAPI lifespan, ticks every
AZAN_AUTO_CHECK_INTERVAL_SECS (default 60s).
The Icecast file stream starts before notifications go out — members answering the CallKit/FCM call screen always pull the stream URL, so the mount must already be live.
Race handling — no double azan
| Scenario | Outcome |
|---|---|
| Admin goes live within grace period | on-air flag set → scheduler marks slot handled, never fires |
| Admin was live, ended within window | audit row found → no auto azan |
| Manual azaan trigger sent from app | triggers row → no auto azan |
| Auto azan fired, server restarts inside window | trigger row survives in DB → not refired |
| Admin connects while auto file stream is playing | mount flag already true → admin joins as extra broadcaster; watchdog only releases state it still owns |
| Two prayers fall inside the window after downtime | each (mosque, date, prayer) slot evaluated independently |
Configuration
Per mosque (azan_auto_settings, Admin Panel → Auto Azan)
| Field | Default | Meaning |
|---|---|---|
enabled | true | master switch |
grace_minutes | 5 | wait for admin after adhan before falling back (0–60) |
window_minutes | 20 | stop trying this long after adhan (1–120, must exceed grace) |
{prayer}_enabled | true | per-prayer opt-out |
Server-wide (env)
| Var | Default | Meaning |
|---|---|---|
AZAN_AUTO_ENABLED | 1 | global kill switch |
AZAN_AUTO_CHECK_INTERVAL_SECS | 60 | scheduler tick |
AZAN_AUTO_GRACE_MINUTES / AZAN_AUTO_WINDOW_MINUTES | 5 / 20 | defaults for mosques without saved settings |
AZAN_AUTO_MAX_STREAM_SECS | 600 | hard cap on an auto file stream |
AZAN_AUTO_STREAM_TAIL_SECS | 12 | trailing silence so lagging listeners hear the full azan |
BROADCAST_IDLE_TIMEOUT_SECS | 60 | live broadcast auto-end on silence |
Fan-out (both paths)
| Broadcast type | iOS | Android | Devices (MQTT) |
|---|---|---|---|
| Azaan (live or offline) | VoIP push → CallKit ring | data-only FCM → native call UI | play command to every bound stream player + transmitter |
| Non-azaan (e.g. announcement/khutbah) | APNs alert (if configured), FCM fallback | FCM notification | none — push only |
Every fan-out step is individually try/excepted — an MQTT or push failure
never blocks or ends the audio stream. See
MQTT — commands for the exact play payload and
Device Authorization for why a blocked/unbound
device is excluded (and counted) rather than silently sent a URL.
Related API
| Endpoint | Purpose |
|---|---|
GET/PUT /api/v1/azan-auto/mosque/{id}/settings | scheduler settings |
GET /api/v1/azan-auto/mosque/{id}/status | today's times + source, powers the status banner |
GET /api/v1/azan-auto/mosque/{id}/history | recent auto-fired azans |
GET/POST /api/v1/audio-files/mosque/{id}[/upload] | azan recordings |
DELETE /api/v1/audio-files/{audio_id} | delete a recording |
Full schemas: API Reference.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Auto azan never fires | mosque inactive; toggles off; no prayer times/location (status source: "none"); no recording uploaded |
| Fired, but phones silent on answer | Icecast mount not live — check FFmpeg file-stream errors |
| Recording plays for wrong prayer | check audio_files.prayer — 'fajr' vs NULL |
| Auto azan double-fired | both DB dedupe checks failed — audit tables missing? |
| Upload 413 | nginx client_max_body_size missing on /api/ |
More operational detail: Operations — Troubleshooting.