Channel Status
The channel status system tracks which streaming channels are currently online. It is a generic utility that any feature can consume — currently used by the Spotify worker to avoid unnecessary API requests and event storage when offline.
Architecture
PostgreSQL (source of truth) + Redis (cache + pub/sub)
Database
The channel_status table in PostgreSQL stores the current state per account per platform:
channel_status (
account_id UUID, -- FK → accounts
platform VARCHAR(50), -- "twitch", "youtube", "kick", "trovo"
broadcast_id TEXT, -- per-broadcast identifier (multi-stream)
is_online BOOLEAN,
broadcast_status TEXT, -- "live", "upcoming", or NULL
stream_title TEXT,
category TEXT,
viewer_count INTEGER,
like_count INTEGER, -- YouTube only
total_views BIGINT, -- YouTube only
started_at TIMESTAMPTZ,
scheduled_start TIMESTAMPTZ, -- upcoming broadcast start time
live_chat_id TEXT, -- YouTube live chat ID
updated_at TIMESTAMPTZ,
PRIMARY KEY (account_id, platform, broadcast_id)
)
A partial index on is_online = TRUE enables fast lookups.
Redis
Three Redis structures per account:
| Key | Type | Purpose |
|---|---|---|
lumio:channel_status:{account_id}:{platform} | String (JSON) | Cached status per platform (24h safety TTL) |
lumio:channel_online:{account_id} | SET | Set of online platform names (SADD/SREM/SCARD) |
lumio:channel_status:{account_id} | Pub/Sub | Status change notifications |
The online set uses SADD/SREM for idempotency — duplicate events have no effect.
Manual Connect
| Key | Type | Purpose |
|---|---|---|
lumio:spotify_manual:{account_id} | String | Manual override, TTL 30 minutes |
Rust API
The channel status service (apps/api/src/services/channel_status.rs) exposes a platform-agnostic interface:
// Core operations
services::channel_status::set_online(pool, redis, account_id, platform, metadata) -> Result<()>
services::channel_status::set_offline(pool, redis, account_id, platform) -> Result<()>
// Queries (via db::channel_status)
db::channel_status::is_any_online(pool, account_id) -> Result<bool>
db::channel_status::get_status(pool, account_id) -> Result<Vec<ChannelStatus>>
Integration with Platform Adapters
Platform adapters call the channel status module directly when detecting stream status changes:
// In the Twitch EventSub worker:
"stream.online" => {
channel_status::set_online(&db, &redis, account_id, "twitch", metadata).await?;
// ... then proceed with normal event processing
}
"stream.offline" => {
channel_status::set_offline(&db, &redis, account_id, "twitch").await?;
// ... then proceed with normal event processing
}
The same pattern applies to YouTube, Kick, and Trovo adapters.
Worker Lifecycle
A channel_status_relay background worker subscribes to lumio:channel_status:* via PSUBSCRIBE and reacts to status changes by starting/stopping the Spotify worker through the WorkerManager.
A separate 60-second poll task checks for expired manual connect keys.
Status change → channel_status_relay → WorkerManager → start/stop Spotify worker
Decision logic:
SCARD(online_set) > 0 OR manual_key exists?
├── YES + Worker not running → start_spotify_worker()
├── YES + Worker running → no-op
├── NO + Worker running → stop_spotify_worker()
└── NO + Worker not running → no-op
Server Start Recovery
On startup, the API server:
- Reads
channel_statusfrom PostgreSQL for all accounts - Rebuilds Redis keys (cache + online sets)
- Starts Spotify workers only for accounts with at least one online channel
GraphQL API
type ChannelStatus {
platform: String!
broadcastId: String!
isOnline: Boolean!
broadcastStatus: String
streamTitle: String
category: String
viewerCount: Int
likeCount: Int
totalViews: Int
startedAt: DateTime
scheduledStart: DateTime
liveChatId: String
updatedAt: DateTime!
}
type SpotifyManualStatus {
active: Boolean!
remainingSeconds: Int
}
type Query {
channelStatus: [ChannelStatus!]!
spotifyManualStatus: SpotifyManualStatus!
}
type Mutation {
startSpotifyManual: SpotifyManualStatus!
stopSpotifyManual: Boolean!
}
All queries/mutations use account_id from AuthContext.
REST API
| Method | Path | Description |
|---|---|---|
GET | /v1/channel-status | Status for authenticated account |
GET | /v1/spotify/manual-connect | Manual connect status + remaining time |
POST | /v1/spotify/manual-connect | Start manual connect (30min TTL) |
DELETE | /v1/spotify/manual-connect | Stop manual connect |
All routes support popout token auth.
WebSocket Events
Status changes are broadcast (not persisted) on lumio:events:{account_id}:
channel:online—{ platform, stream_title?, game_name?, viewer_count?, started_at }channel:offline—{ platform }
Adding New Consumers
To use channel status in a new feature:
- Import the channel status module
- Call
is_any_online(redis, account_id)for a fast boolean check - Or subscribe to
lumio:channel_status:{account_id}for reactive updates - No changes needed to the channel status module itself