Skip to main content

Channel Status

Overview

The channel status module tracks live stream status per platform for each account. It stores whether a channel is online, along with metadata such as stream title, category, viewer count, and stream start time. Status is updated by platform workers (Twitch, YouTube, Kick) and includes a manual Spotify connect feature for controlling the Spotify polling worker independently of stream status.

Architecture

Platform Workers (Twitch/YouTube/Kick)
|
+--> db::channel_status::set_online / set_offline / update_stream_data
|
v
channel_status table (PostgreSQL)
^
|
Dashboard UI
|
v
Next.js API Proxy
|
v
GraphQL (ChannelStatusQuery / ChannelStatusMutation)
|
+--> db::channel_status::get_status
+--> services::channel_status (manual connect via Redis)

Status Updates

Platform workers poll for stream status changes and update the database:

  • Going online -- set_online() upserts a row with is_online = TRUE and metadata (title, category, viewer count, start time).
  • Going offline -- set_offline() sets is_online = FALSE and clears started_at.
  • Data updates -- update_stream_data() updates viewer count, title, and category for channels that are already online.

Spotify Manual Connect

The Spotify worker normally polls only when a channel is online. The manual connect feature allows users to start Spotify polling independently, using a Redis key with a 30-minute TTL. This is useful for pre-stream setup or listening sessions without going live.

  • Start -- Sets a Redis key and publishes a synthetic status change event via Redis pub/sub.
  • Stop -- Deletes the Redis key and publishes a status change event.
  • Query -- Returns whether manual connect is active and the remaining seconds.

Spotify Worker Lifecycle (relay)

The channel-status relay (apps/api/src/workers/channel_status_relay.rs) owns the Spotify worker's start/stop lifecycle. It subscribes to lumio:channel_status:* and, on every status change, evaluates whether the worker should run for that account:

  • Should run when any channel is online or a manual connect is active. On a should-run transition the relay resolves the worker config from the live channel_connections row on demand (resolve_spotify_config) and starts the worker with the connection's current connection_id. There is no boot-time config snapshot, so a Spotify connection linked, re-authorized, or disconnected-and-reconnected after the API booted starts polling on the next stream-start or manual connect -- without an API restart. An account with no Spotify connection resolves nothing and is simply not started.
  • Should not run (no channel online, no manual connect) tears the worker down. A separate 60-second poll also stops any worker whose manual connect has expired.

This is why stopping the worker at stream end / manual-connect expiry is safe: it always restarts from the DB on the next trigger. Note the emission asymmetry: only the worker produces spotify:track; spotify:skip/play/pause/… come from the playback-control path and do not depend on the worker running.

API

GraphQL Queries

QueryArgsReturnsPermission
channelStatus--[ChannelStatus]spotify:read
spotifyManualStatus--SpotifyManualStatusspotify:read
  • channelStatus returns all platform statuses for the active account.
  • spotifyManualStatus checks the Redis TTL key and returns active status with remaining seconds.

GraphQL Mutations

MutationArgsReturnsPermission
startSpotifyManual--SpotifyManualStatusspotify:worker
stopSpotifyManual--Booleanspotify:worker

REST Endpoints

All paths live under /v1.

MethodPathPermissionDescription
GET/v1/channel-statusspotify:readAll platform statuses for the active account
GET/v1/spotify/manual-connectspotify:readManual-connect status + remaining seconds
POST/v1/spotify/manual-connectspotify:workerStart manual Spotify polling (30 min TTL)
DELETE/v1/spotify/manual-connectspotify:workerStop manual Spotify polling

GraphQL Types

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
}

Permissions

PermissionDescription
spotify:readView channel status and Spotify manual connect status
spotify:workerStart/stop Spotify manual connect

Included in: Owner, Administrator, Moderator roles have spotify:read. Owner and Administrator have spotify:worker.

Database

Table: channel_status

ColumnTypeDescription
account_idUUID (PK, FK)Account ID
platformTEXT (PK)Platform identifier
broadcast_idTEXT (PK)Broadcast/stream identifier (per-broadcast rows for multi-stream)
is_onlineBOOLEANWhether the channel is currently live
broadcast_statusTEXT NULL"live", "upcoming", or NULL (non-YouTube platforms)
stream_titleTEXTCurrent stream title
categoryTEXTCurrent stream category/game
viewer_countINTCurrent viewer count
like_countINT NULLCurrent like count (YouTube only)
total_viewsBIGINT NULLLifetime view count (YouTube only)
started_atTIMESTAMPTZWhen the stream started
scheduled_startTIMESTAMPTZ NULLWhen an upcoming broadcast is scheduled to start
live_chat_idTEXT NULLYouTube live chat ID for this broadcast
updated_atTIMESTAMPTZLast status update

Primary key: (account_id, platform, broadcast_id) -- one status row per broadcast per platform per account, using upsert on conflict.

DB Functions

FunctionDescription
set_onlineUpsert with is_online = TRUE and metadata (title, viewer count, like count, total views, scheduled start, live chat ID)
set_offlineSet is_online = FALSE, clear started_at
upsert_upcomingUpsert an upcoming broadcast (is_online = FALSE) with title, scheduled start, and live chat ID
delete_broadcastDelete a specific broadcast row (used when a broadcast disappears from discovery)
get_statusGet all statuses for an account, ordered by platform
is_any_onlineCheck if any platform is online (DB fallback)
update_stream_dataUpdate viewer count, title, category for online channels
get_all_online_accountsBoot recovery: all accounts with online channels, grouped

Redis Keys

KeyTTLDescription
lumio:spotify_manual:{account_id}30 minManual Spotify connect active flag

Key Files

FilePurpose
apps/api/src/graphql/channel_status.rsGraphQL queries and mutations
apps/api/src/db/channel_status.rsDatabase operations
apps/api/src/services/channel_status.rsManual connect logic (Redis TTL, pub/sub)