Initial commit - realms platform

This commit is contained in:
doomtube 2026-01-05 22:54:27 -05:00
parent c590ab6d18
commit c717c3751c
234 changed files with 74103 additions and 15231 deletions

View file

@ -2,6 +2,7 @@
#include "../controllers/StreamController.h"
#include "../services/RedisHelper.h"
#include "../services/OmeClient.h"
#include "../services/RestreamService.h"
#include <drogon/HttpClient.h>
#include <drogon/utils/Utilities.h>
#include <set>
@ -116,19 +117,25 @@ void StatsService::pollOmeStats() {
// Update each active stream
for (const auto& streamKey : activeStreamKeys) {
LOG_INFO << "Processing active stream: " << streamKey;
// IMMEDIATELY update database to mark as live
// IMMEDIATELY update database to mark as live and get realm ID
auto dbClient = app().getDbClient();
*dbClient << "UPDATE realms SET is_live = true, viewer_count = 0, "
"updated_at = CURRENT_TIMESTAMP WHERE stream_key = $1"
"updated_at = CURRENT_TIMESTAMP WHERE stream_key = $1 RETURNING id"
<< streamKey
>> [streamKey](const orm::Result&) {
>> [streamKey](const orm::Result& r) {
LOG_INFO << "Successfully marked realm as live: " << streamKey;
// Attempt reconnection for any disconnected restream destinations
if (!r.empty()) {
int64_t realmId = r[0]["id"].as<int64_t>();
RestreamService::getInstance().attemptReconnections(streamKey, realmId);
}
}
>> [streamKey](const orm::DrogonDbException& e) {
LOG_ERROR << "Failed to update realm live status: " << e.base().what();
};
// Then update detailed stats
updateStreamStats(streamKey);
}
@ -167,10 +174,12 @@ void StatsService::updateStreamStats(const std::string& streamKey) {
fetchStatsFromOme(streamKey, [this, streamKey](bool success, const StreamStats& stats) {
if (success) {
StreamStats updatedStats = stats;
updatedStats.uniqueViewers = getUniqueViewerCount(streamKey);
// Only count viewer tokens when stream is actually live
// Offline streams should show 0 viewers (tokens may linger for 5 min after disconnect)
updatedStats.uniqueViewers = stats.isLive ? getUniqueViewerCount(streamKey) : 0;
storeStatsInRedis(streamKey, updatedStats);
// Update realm in database
updateRealmLiveStatus(streamKey, updatedStats);
@ -267,31 +276,31 @@ void StatsService::fetchStatsFromOme(const std::string& streamKey,
hasInput = true;
const auto& input = data["input"];
// Get bitrate from input tracks
// Get bitrate from input tracks (OME returns bytes/sec, convert to bits/sec)
if (input.isMember("tracks") && input["tracks"].isArray()) {
for (const auto& track : input["tracks"]) {
if (track["type"].asString() == "video" && track.isMember("bitrate")) {
stats.bitrate = track["bitrate"].asDouble();
stats.bitrate = track["bitrate"].asDouble() * 8; // Convert bytes/sec to bits/sec
}
}
}
}
// Alternative: Check lastThroughputIn
// Alternative: Check lastThroughputIn (OME returns bytes/sec, convert to bits/sec)
if (!hasInput && data.isMember("lastThroughputIn")) {
double throughput = data["lastThroughputIn"].asDouble();
if (throughput > 0) {
hasInput = true;
stats.bitrate = throughput;
stats.bitrate = throughput * 8; // Convert bytes/sec to bits/sec
}
}
// Alternative: Check avgThroughputIn
// Alternative: Check avgThroughputIn (OME returns bytes/sec, convert to bits/sec)
if (!hasInput && data.isMember("avgThroughputIn")) {
double avgThroughput = data["avgThroughputIn"].asDouble();
if (avgThroughput > 0) {
hasInput = true;
stats.bitrate = avgThroughput;
stats.bitrate = avgThroughput * 8; // Convert bytes/sec to bits/sec
}
}
@ -479,8 +488,8 @@ void StatsService::getStreamStats(const std::string& streamKey,
fetchStatsFromOme(streamKey, [this, callback, streamKey](bool success, const StreamStats& stats) {
if (success) {
StreamStats updatedStats = stats;
// Set uniqueViewers on cache miss
updatedStats.uniqueViewers = getUniqueViewerCount(streamKey);
// Only count viewer tokens when stream is actually live
updatedStats.uniqueViewers = stats.isLive ? getUniqueViewerCount(streamKey) : 0;
callback(true, updatedStats);
} else {
callback(false, stats);
@ -504,7 +513,7 @@ void StatsService::getStreamStats(const std::string& streamKey,
stats.resolution = json["resolution"].asString();
stats.fps = json["fps"].asDouble();
stats.isLive = json["is_live"].asBool();
// Parse protocol connections
if (json.isMember("protocol_connections")) {
const auto& pc = json["protocol_connections"];
@ -513,19 +522,42 @@ void StatsService::getStreamStats(const std::string& streamKey,
stats.protocolConnections.llhls = pc["llhls"].asInt64();
stats.protocolConnections.dash = pc["dash"].asInt64();
}
stats.lastUpdated = std::chrono::system_clock::time_point(
std::chrono::seconds(json["last_updated"].asInt64())
);
callback(true, stats);
// Verify is_live from database (source of truth from webhooks)
// This prevents stale cache from overriding the webhook-updated DB state
auto dbClient = app().getDbClient();
*dbClient << "SELECT is_live FROM realms WHERE stream_key = $1"
<< streamKey
>> [callback, stats](const orm::Result& r) mutable {
if (!r.empty()) {
bool dbIsLive = r[0]["is_live"].as<bool>();
// If database says live but cache says offline, trust database
// (webhooks update DB immediately, cache may be stale)
if (dbIsLive && !stats.isLive) {
LOG_DEBUG << "Overriding stale cache: DB says live, cache says offline";
stats.isLive = true;
}
}
callback(true, stats);
}
>> [callback, stats](const orm::DrogonDbException& e) {
LOG_ERROR << "Failed to verify is_live from DB: " << e.base().what();
// Fall back to cached value on DB error
callback(true, stats);
};
LOG_DEBUG << "Retrieved cached stats for " << streamKey;
return; // Callback handled async
} else {
// Fallback to fresh fetch if cached data is corrupted
fetchStatsFromOme(streamKey, [this, callback, streamKey](bool success, const StreamStats& stats) {
if (success) {
StreamStats updatedStats = stats;
updatedStats.uniqueViewers = getUniqueViewerCount(streamKey);
// Only count viewer tokens when stream is actually live
updatedStats.uniqueViewers = stats.isLive ? getUniqueViewerCount(streamKey) : 0;
callback(true, updatedStats);
} else {
callback(false, stats);
@ -538,7 +570,8 @@ void StatsService::getStreamStats(const std::string& streamKey,
fetchStatsFromOme(streamKey, [this, callback, streamKey](bool success, const StreamStats& stats) {
if (success) {
StreamStats updatedStats = stats;
updatedStats.uniqueViewers = getUniqueViewerCount(streamKey);
// Only count viewer tokens when stream is actually live
updatedStats.uniqueViewers = stats.isLive ? getUniqueViewerCount(streamKey) : 0;
callback(true, updatedStats);
} else {
callback(false, stats);