nu
This commit is contained in:
parent
d812c6aeab
commit
4c23ab840a
2 changed files with 391 additions and 2535 deletions
2360
frontend/package-lock.json
generated
2360
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
440
text.txt
440
text.txt
|
|
@ -1,6 +1,6 @@
|
||||||
Folder PATH listing
|
Folder PATH listing
|
||||||
Volume serial number is 1430-6C90
|
Volume serial number is 000002F5 1430:6C90
|
||||||
C:.
|
C:\USERS\ADMINISTRATOR\DESKTOP\PUB\DROGON
|
||||||
¦ .env
|
¦ .env
|
||||||
¦ docker
|
¦ docker
|
||||||
¦ docker-compose.yml
|
¦ docker-compose.yml
|
||||||
|
|
@ -108,7 +108,7 @@ C:.
|
||||||
¦ Server.xml
|
¦ Server.xml
|
||||||
¦
|
¦
|
||||||
+---scripts
|
+---scripts
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\.env ###
|
### C:\Users\Administrator\Desktop\pub\drogon\.env ###
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
DB_PASSWORD=your-secure-password
|
DB_PASSWORD=your-secure-password
|
||||||
|
|
@ -123,11 +123,11 @@ OME_API_TOKEN=your-ome-api-token
|
||||||
APP_ENV=production
|
APP_ENV=production
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\docker ###
|
### C:\Users\Administrator\Desktop\pub\drogon\docker ###
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\docker-compose.yml ###
|
### C:\Users\Administrator\Desktop\pub\drogon\docker-compose.yml ###
|
||||||
|
|
||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
|
|
@ -265,7 +265,7 @@ volumes:
|
||||||
uploads: # Named volume for uploads
|
uploads: # Named volume for uploads
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\.dockerignore ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\.dockerignore ###
|
||||||
|
|
||||||
# Build artifacts
|
# Build artifacts
|
||||||
build/
|
build/
|
||||||
|
|
@ -306,7 +306,7 @@ Thumbs.db
|
||||||
streaming-backend
|
streaming-backend
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\CMakeLists.txt ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\CMakeLists.txt ###
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
project(streaming-backend)
|
project(streaming-backend)
|
||||||
|
|
@ -417,7 +417,7 @@ add_executable(admin-tool src/admin_tool.cpp)
|
||||||
target_link_libraries(admin-tool PRIVATE Drogon::Drogon PostgreSQL::PostgreSQL)
|
target_link_libraries(admin-tool PRIVATE Drogon::Drogon PostgreSQL::PostgreSQL)
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\conanfile.txt ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\conanfile.txt ###
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
redis-plus-plus/1.3.13
|
redis-plus-plus/1.3.13
|
||||||
|
|
@ -432,7 +432,8 @@ CMakeDeps
|
||||||
CMakeToolchain
|
CMakeToolchain
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\config.json ###
|
|
||||||
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\config.json ###
|
||||||
|
|
||||||
{
|
{
|
||||||
"listeners": [
|
"listeners": [
|
||||||
|
|
@ -479,7 +480,7 @@ CMakeToolchain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\Dockerfile ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\Dockerfile ###
|
||||||
|
|
||||||
FROM drogonframework/drogon:latest
|
FROM drogonframework/drogon:latest
|
||||||
|
|
||||||
|
|
@ -590,7 +591,7 @@ EXPOSE 8080
|
||||||
CMD ["./start.sh"]
|
CMD ["./start.sh"]
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\admin_tool.cpp ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\admin_tool.cpp ###
|
||||||
|
|
||||||
#include <drogon/drogon.h>
|
#include <drogon/drogon.h>
|
||||||
#include <drogon/orm/DbClient.h>
|
#include <drogon/orm/DbClient.h>
|
||||||
|
|
@ -665,7 +666,7 @@ int main(int argc, char* argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\main.cpp ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\main.cpp ###
|
||||||
|
|
||||||
#include <drogon/drogon.h>
|
#include <drogon/drogon.h>
|
||||||
#include <drogon/HttpAppFramework.h>
|
#include <drogon/HttpAppFramework.h>
|
||||||
|
|
@ -702,6 +703,10 @@ int main() {
|
||||||
LOG_INFO << "Loading configuration...";
|
LOG_INFO << "Loading configuration...";
|
||||||
app().loadConfigFile("config.json");
|
app().loadConfigFile("config.json");
|
||||||
|
|
||||||
|
// Initialize StatsService BEFORE registering callbacks
|
||||||
|
LOG_INFO << "Initializing StatsService...";
|
||||||
|
StatsService::getInstance().initialize();
|
||||||
|
|
||||||
// Register a pre-routing advice to handle CORS
|
// Register a pre-routing advice to handle CORS
|
||||||
app().registerPreRoutingAdvice([](const HttpRequestPtr &req,
|
app().registerPreRoutingAdvice([](const HttpRequestPtr &req,
|
||||||
AdviceCallback &&acb,
|
AdviceCallback &&acb,
|
||||||
|
|
@ -743,13 +748,13 @@ int main() {
|
||||||
resp->addHeader("Access-Control-Allow-Credentials", "true");
|
resp->addHeader("Access-Control-Allow-Credentials", "true");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register beginning advice to initialize StatsService after app starts
|
// Register beginning advice to start the stats timer
|
||||||
app().registerBeginningAdvice([]() {
|
app().registerBeginningAdvice([]() {
|
||||||
LOG_INFO << "Application started successfully";
|
LOG_INFO << "Application started successfully";
|
||||||
|
|
||||||
// Initialize StatsService after app is running
|
// Start the stats polling timer
|
||||||
LOG_INFO << "Initializing StatsService...";
|
LOG_INFO << "Starting stats polling...";
|
||||||
StatsService::getInstance().initialize();
|
StatsService::getInstance().startPolling();
|
||||||
});
|
});
|
||||||
|
|
||||||
app().setTermSignalHandler([]() {
|
app().setTermSignalHandler([]() {
|
||||||
|
|
@ -774,7 +779,7 @@ int main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\common\utils.h ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\common\utils.h ###
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|
@ -1021,7 +1026,7 @@ inline Json::Value toJson(const std::map<std::string, std::string>& m) {
|
||||||
} // namespace utils
|
} // namespace utils
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\controllers\AdminController.cpp ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\controllers\AdminController.cpp ###
|
||||||
|
|
||||||
#include "AdminController.h"
|
#include "AdminController.h"
|
||||||
#include "../services/OmeClient.h"
|
#include "../services/OmeClient.h"
|
||||||
|
|
@ -1206,7 +1211,7 @@ void AdminController::demoteFromStreamer(const HttpRequestPtr &req,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\controllers\AdminController.h ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\controllers\AdminController.h ###
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <drogon/HttpController.h>
|
#include <drogon/HttpController.h>
|
||||||
|
|
@ -1247,7 +1252,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\controllers\RealmController.cpp ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\controllers\RealmController.cpp ###
|
||||||
|
|
||||||
#include "RealmController.h"
|
#include "RealmController.h"
|
||||||
#include "../services/DatabaseService.h"
|
#include "../services/DatabaseService.h"
|
||||||
|
|
@ -1562,19 +1567,14 @@ void RealmController::getRealmStreamKey(const HttpRequestPtr &req,
|
||||||
void RealmController::getRealm(const HttpRequestPtr &req,
|
void RealmController::getRealm(const HttpRequestPtr &req,
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback,
|
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||||
const std::string &realmId) {
|
const std::string &realmId) {
|
||||||
UserInfo user = getUserFromRequest(req);
|
// Remove authentication requirement for public viewing
|
||||||
if (user.id == 0) {
|
|
||||||
callback(jsonError("Unauthorized", k401Unauthorized));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t id = std::stoll(realmId);
|
int64_t id = std::stoll(realmId);
|
||||||
|
|
||||||
auto dbClient = app().getDbClient();
|
auto dbClient = app().getDbClient();
|
||||||
*dbClient << "SELECT r.*, u.username FROM realms r "
|
*dbClient << "SELECT r.*, u.username FROM realms r "
|
||||||
"JOIN users u ON r.user_id = u.id "
|
"JOIN users u ON r.user_id = u.id "
|
||||||
"WHERE r.id = $1 AND r.user_id = $2"
|
"WHERE r.id = $1 AND r.is_active = true"
|
||||||
<< id << user.id
|
<< id
|
||||||
>> [callback](const Result& r) {
|
>> [callback](const Result& r) {
|
||||||
if (r.empty()) {
|
if (r.empty()) {
|
||||||
callback(jsonError("Realm not found", k404NotFound));
|
callback(jsonError("Realm not found", k404NotFound));
|
||||||
|
|
@ -1586,7 +1586,8 @@ void RealmController::getRealm(const HttpRequestPtr &req,
|
||||||
auto& realm = resp["realm"];
|
auto& realm = resp["realm"];
|
||||||
realm["id"] = static_cast<Json::Int64>(r[0]["id"].as<int64_t>());
|
realm["id"] = static_cast<Json::Int64>(r[0]["id"].as<int64_t>());
|
||||||
realm["name"] = r[0]["name"].as<std::string>();
|
realm["name"] = r[0]["name"].as<std::string>();
|
||||||
realm["streamKey"] = r[0]["stream_key"].as<std::string>();
|
// Don't expose stream key in public endpoint
|
||||||
|
// realm["streamKey"] = r[0]["stream_key"].as<std::string>();
|
||||||
realm["isActive"] = r[0]["is_active"].as<bool>();
|
realm["isActive"] = r[0]["is_active"].as<bool>();
|
||||||
realm["isLive"] = r[0]["is_live"].as<bool>();
|
realm["isLive"] = r[0]["is_live"].as<bool>();
|
||||||
realm["viewerCount"] = static_cast<Json::Int64>(r[0]["viewer_count"].as<int64_t>());
|
realm["viewerCount"] = static_cast<Json::Int64>(r[0]["viewer_count"].as<int64_t>());
|
||||||
|
|
@ -1851,7 +1852,7 @@ void RealmController::getRealmStats(const HttpRequestPtr &,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\controllers\RealmController.h ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\controllers\RealmController.h ###
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <drogon/HttpController.h>
|
#include <drogon/HttpController.h>
|
||||||
|
|
@ -1926,7 +1927,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\controllers\StreamController.cpp ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\controllers\StreamController.cpp ###
|
||||||
|
|
||||||
#include "StreamController.h"
|
#include "StreamController.h"
|
||||||
#include "../services/DatabaseService.h"
|
#include "../services/DatabaseService.h"
|
||||||
|
|
@ -2300,7 +2301,7 @@ void StreamWebSocketController::broadcastStatsUpdate(const Json::Value& stats) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\controllers\StreamController.h ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\controllers\StreamController.h ###
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <drogon/HttpController.h>
|
#include <drogon/HttpController.h>
|
||||||
|
|
@ -2376,7 +2377,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\controllers\UserController.cpp ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\controllers\UserController.cpp ###
|
||||||
|
|
||||||
#include "UserController.h"
|
#include "UserController.h"
|
||||||
#include "../services/DatabaseService.h"
|
#include "../services/DatabaseService.h"
|
||||||
|
|
@ -2994,7 +2995,7 @@ void UserController::getUserPgpKeys(const HttpRequestPtr &,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\controllers\UserController.h ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\controllers\UserController.h ###
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <drogon/HttpController.h>
|
#include <drogon/HttpController.h>
|
||||||
|
|
@ -3065,7 +3066,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\models\Realm.h ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\models\Realm.h ###
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
@ -3084,7 +3085,7 @@ struct Realm {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\models\StreamKey.h ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\models\StreamKey.h ###
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
@ -3100,7 +3101,7 @@ struct StreamKey {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\services\AuthService.cpp ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\services\AuthService.cpp ###
|
||||||
|
|
||||||
#include "AuthService.h"
|
#include "AuthService.h"
|
||||||
#include "DatabaseService.h"
|
#include "DatabaseService.h"
|
||||||
|
|
@ -3451,7 +3452,7 @@ void AuthService::updatePassword(int64_t userId, const std::string& oldPassword,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\services\AuthService.h ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\services\AuthService.h ###
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <drogon/drogon.h>
|
#include <drogon/drogon.h>
|
||||||
|
|
@ -3518,7 +3519,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\services\CorsMiddleware.h ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\services\CorsMiddleware.h ###
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <drogon/drogon.h>
|
#include <drogon/drogon.h>
|
||||||
|
|
@ -3598,7 +3599,7 @@ private:
|
||||||
} // namespace middleware
|
} // namespace middleware
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\services\DatabaseService.cpp ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\services\DatabaseService.cpp ###
|
||||||
|
|
||||||
#include "DatabaseService.h"
|
#include "DatabaseService.h"
|
||||||
#include "../services/RedisHelper.h"
|
#include "../services/RedisHelper.h"
|
||||||
|
|
@ -3722,7 +3723,7 @@ void DatabaseService::validateStreamKey(const std::string& key,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\services\DatabaseService.h ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\services\DatabaseService.h ###
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <drogon/drogon.h>
|
#include <drogon/drogon.h>
|
||||||
|
|
@ -3756,7 +3757,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\services\OmeClient.h ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\services\OmeClient.h ###
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <drogon/HttpClient.h>
|
#include <drogon/HttpClient.h>
|
||||||
|
|
@ -3776,22 +3777,39 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get list of active streams
|
// Get list of active streams
|
||||||
|
// In backend/src/services/OmeClient.h
|
||||||
|
|
||||||
void getActiveStreams(std::function<void(bool, const Json::Value&)> callback) {
|
void getActiveStreams(std::function<void(bool, const Json::Value&)> callback) {
|
||||||
|
// Try the streams endpoint first
|
||||||
auto request = createRequest(drogon::Get, "/v1/vhosts/default/apps/app/streams");
|
auto request = createRequest(drogon::Get, "/v1/vhosts/default/apps/app/streams");
|
||||||
|
|
||||||
getClient()->sendRequest(request, [callback](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
|
getClient()->sendRequest(request, [callback](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
|
||||||
if (result == drogon::ReqResult::Ok && response && response->getStatusCode() == drogon::k200OK) {
|
if (result == drogon::ReqResult::Ok && response && response->getStatusCode() == drogon::k200OK) {
|
||||||
try {
|
try {
|
||||||
Json::Value json = *response->getJsonObject();
|
Json::Value json = *response->getJsonObject();
|
||||||
|
LOG_DEBUG << "OME streams response: " << json.toStyledString();
|
||||||
|
|
||||||
|
// OME might return the streams in different formats
|
||||||
|
// Sometimes it's {"response": ["stream1", "stream2"]}
|
||||||
|
// Sometimes it's {"response": {"streams": ["stream1", "stream2"]}}
|
||||||
|
if (json.isMember("response")) {
|
||||||
callback(true, json);
|
callback(true, json);
|
||||||
|
} else {
|
||||||
|
// Wrap the response if needed
|
||||||
|
Json::Value wrapped;
|
||||||
|
wrapped["response"] = json;
|
||||||
|
callback(true, wrapped);
|
||||||
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
LOG_ERROR << "Failed to parse OME response: " << e.what();
|
LOG_ERROR << "Failed to parse OME response: " << e.what();
|
||||||
Json::Value empty;
|
Json::Value empty;
|
||||||
|
empty["response"] = Json::arrayValue;
|
||||||
callback(false, empty);
|
callback(false, empty);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR << "Failed to get active streams from OME";
|
LOG_ERROR << "Failed to get active streams from OME";
|
||||||
Json::Value empty;
|
Json::Value empty;
|
||||||
|
empty["response"] = Json::arrayValue;
|
||||||
callback(false, empty);
|
callback(false, empty);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -3936,7 +3954,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\services\RedisHelper.cpp ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\services\RedisHelper.cpp ###
|
||||||
|
|
||||||
#include "RedisHelper.h"
|
#include "RedisHelper.h"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
@ -4251,7 +4269,7 @@ void RedisHelper::executeAsync(const std::string &command,
|
||||||
} // namespace services
|
} // namespace services
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\services\RedisHelper.h ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\services\RedisHelper.h ###
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
|
@ -4445,7 +4463,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\services\StatsService.cpp ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\services\StatsService.cpp ###
|
||||||
|
|
||||||
#include "StatsService.h"
|
#include "StatsService.h"
|
||||||
#include "../controllers/StreamController.h"
|
#include "../controllers/StreamController.h"
|
||||||
|
|
@ -4453,6 +4471,7 @@ public:
|
||||||
#include "../services/OmeClient.h"
|
#include "../services/OmeClient.h"
|
||||||
#include <drogon/HttpClient.h>
|
#include <drogon/HttpClient.h>
|
||||||
#include <drogon/utils/Utilities.h>
|
#include <drogon/utils/Utilities.h>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
using namespace drogon;
|
using namespace drogon;
|
||||||
|
|
||||||
|
|
@ -4466,12 +4485,22 @@ StatsService::~StatsService() {
|
||||||
void StatsService::initialize() {
|
void StatsService::initialize() {
|
||||||
LOG_INFO << "Initializing Stats Service...";
|
LOG_INFO << "Initializing Stats Service...";
|
||||||
running_ = true;
|
running_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatsService::startPolling() {
|
||||||
|
if (!running_) {
|
||||||
|
LOG_WARN << "Stats service not initialized, cannot start polling";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
drogon::app().registerBeginningAdvice([this]() {
|
|
||||||
LOG_INFO << "Starting stats polling timer...";
|
LOG_INFO << "Starting stats polling timer...";
|
||||||
|
|
||||||
if (auto loop = drogon::app().getLoop()) {
|
if (auto loop = drogon::app().getLoop()) {
|
||||||
try {
|
try {
|
||||||
|
// Do an immediate poll
|
||||||
|
pollOmeStats();
|
||||||
|
|
||||||
|
// Then set up the timer
|
||||||
timerId_ = loop->runEvery(
|
timerId_ = loop->runEvery(
|
||||||
pollInterval_.count(),
|
pollInterval_.count(),
|
||||||
[this]() {
|
[this]() {
|
||||||
|
|
@ -4487,8 +4516,9 @@ void StatsService::initialize() {
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
LOG_ERROR << "Failed to create stats timer: " << e.what();
|
LOG_ERROR << "Failed to create stats timer: " << e.what();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
LOG_ERROR << "Event loop not available for stats polling";
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatsService::shutdown() {
|
void StatsService::shutdown() {
|
||||||
|
|
@ -4522,27 +4552,82 @@ int64_t StatsService::getUniqueViewerCount(const std::string& streamKey) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatsService::pollOmeStats() {
|
void StatsService::pollOmeStats() {
|
||||||
|
LOG_INFO << "Polling OvenMediaEngine for active streams...";
|
||||||
|
|
||||||
// Get active streams from OME
|
// Get active streams from OME
|
||||||
OmeClient::getInstance().getActiveStreams([this](bool success, const Json::Value& json) {
|
OmeClient::getInstance().getActiveStreams([this](bool success, const Json::Value& json) {
|
||||||
if (success && json["response"].isArray()) {
|
if (success && json.isMember("response")) {
|
||||||
|
LOG_INFO << "OME Active Streams Response: " << json["response"].toStyledString();
|
||||||
|
|
||||||
|
std::set<std::string> activeStreamKeys;
|
||||||
|
|
||||||
|
// Handle both array and object responses from OME
|
||||||
|
if (json["response"].isArray()) {
|
||||||
for (const auto& stream : json["response"]) {
|
for (const auto& stream : json["response"]) {
|
||||||
if (stream.isString()) {
|
if (stream.isString()) {
|
||||||
updateStreamStats(stream.asString());
|
activeStreamKeys.insert(stream.asString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (json["response"].isMember("streams") && json["response"]["streams"].isArray()) {
|
||||||
|
for (const auto& stream : json["response"]["streams"]) {
|
||||||
|
if (stream.isString()) {
|
||||||
|
activeStreamKeys.insert(stream.asString());
|
||||||
|
} else if (stream.isMember("name")) {
|
||||||
|
activeStreamKeys.insert(stream["name"].asString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO << "Found " << activeStreamKeys.size() << " active streams from OME";
|
||||||
|
|
||||||
|
// Update each active stream
|
||||||
|
for (const auto& streamKey : activeStreamKeys) {
|
||||||
|
LOG_INFO << "Processing active stream: " << streamKey;
|
||||||
|
|
||||||
|
// IMMEDIATELY update database to mark as live
|
||||||
|
auto dbClient = app().getDbClient();
|
||||||
|
*dbClient << "UPDATE realms SET is_live = true, viewer_count = 0, "
|
||||||
|
"updated_at = CURRENT_TIMESTAMP WHERE stream_key = $1"
|
||||||
|
<< streamKey
|
||||||
|
>> [streamKey](const orm::Result&) {
|
||||||
|
LOG_INFO << "Successfully marked realm as live: " << streamKey;
|
||||||
|
}
|
||||||
|
>> [streamKey](const orm::DrogonDbException& e) {
|
||||||
|
LOG_ERROR << "Failed to update realm live status: " << e.base().what();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Then update detailed stats
|
||||||
|
updateStreamStats(streamKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark all non-active streams as offline
|
||||||
|
auto dbClient = app().getDbClient();
|
||||||
|
*dbClient << "SELECT stream_key FROM realms WHERE is_live = true"
|
||||||
|
>> [activeStreamKeys](const orm::Result& r) {
|
||||||
|
auto db = app().getDbClient();
|
||||||
|
for (const auto& row : r) {
|
||||||
|
std::string key = row["stream_key"].as<std::string>();
|
||||||
|
if (activeStreamKeys.find(key) == activeStreamKeys.end()) {
|
||||||
|
LOG_INFO << "Marking realm as offline: " << key;
|
||||||
|
*db << "UPDATE realms SET is_live = false, viewer_count = 0, "
|
||||||
|
"updated_at = CURRENT_TIMESTAMP WHERE stream_key = $1"
|
||||||
|
<< key
|
||||||
|
>> [key](const orm::Result&) {
|
||||||
|
LOG_INFO << "Marked realm as offline: " << key;
|
||||||
|
}
|
||||||
|
>> [](const orm::DrogonDbException& e) {
|
||||||
|
LOG_ERROR << "Failed to mark realm offline: " << e.base().what();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>> [](const orm::DrogonDbException& e) {
|
||||||
|
LOG_ERROR << "Failed to query live realms: " << e.base().what();
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
LOG_ERROR << "Failed to get active streams from OME or empty response";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Poll known stream keys from Redis
|
|
||||||
services::RedisHelper::instance().keysAsync("stream_key:*",
|
|
||||||
[this](const std::vector<std::string>& keys) {
|
|
||||||
for (const auto& key : keys) {
|
|
||||||
if (auto pos = key.find(':'); pos != std::string::npos) {
|
|
||||||
updateStreamStats(key.substr(pos + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatsService::updateStreamStats(const std::string& streamKey) {
|
void StatsService::updateStreamStats(const std::string& streamKey) {
|
||||||
|
|
@ -4556,7 +4641,7 @@ void StatsService::updateStreamStats(const std::string& streamKey) {
|
||||||
// Update realm in database
|
// Update realm in database
|
||||||
updateRealmLiveStatus(streamKey, updatedStats);
|
updateRealmLiveStatus(streamKey, updatedStats);
|
||||||
|
|
||||||
// Only broadcast if stream has meaningful data
|
// Only broadcast if stream has meaningful data or is live
|
||||||
if (updatedStats.isLive || updatedStats.totalBytesIn > 0 || updatedStats.uniqueViewers > 0) {
|
if (updatedStats.isLive || updatedStats.totalBytesIn > 0 || updatedStats.uniqueViewers > 0) {
|
||||||
Json::Value msg;
|
Json::Value msg;
|
||||||
msg["type"] = "stats_update";
|
msg["type"] = "stats_update";
|
||||||
|
|
@ -4589,11 +4674,14 @@ void StatsService::updateStreamStats(const std::string& streamKey) {
|
||||||
void StatsService::updateRealmLiveStatus(const std::string& streamKey, const StreamStats& stats) {
|
void StatsService::updateRealmLiveStatus(const std::string& streamKey, const StreamStats& stats) {
|
||||||
auto dbClient = app().getDbClient();
|
auto dbClient = app().getDbClient();
|
||||||
|
|
||||||
|
// Cast to int32 to match PostgreSQL integer type
|
||||||
|
int32_t viewerCount = static_cast<int32_t>(stats.uniqueViewers);
|
||||||
|
|
||||||
// Update realm's live status and viewer count
|
// Update realm's live status and viewer count
|
||||||
*dbClient << "UPDATE realms SET is_live = $1, viewer_count = $2 WHERE stream_key = $3"
|
*dbClient << "UPDATE realms SET is_live = $1, viewer_count = $2, updated_at = CURRENT_TIMESTAMP WHERE stream_key = $3"
|
||||||
<< stats.isLive << stats.uniqueViewers << streamKey
|
<< stats.isLive << viewerCount << streamKey
|
||||||
>> [streamKey, stats](const orm::Result&) {
|
>> [streamKey, stats](const orm::Result&) {
|
||||||
LOG_DEBUG << "Updated realm status for stream " << streamKey
|
LOG_INFO << "Updated realm status for stream " << streamKey
|
||||||
<< " - Live: " << stats.isLive
|
<< " - Live: " << stats.isLive
|
||||||
<< ", Viewers: " << stats.uniqueViewers;
|
<< ", Viewers: " << stats.uniqueViewers;
|
||||||
}
|
}
|
||||||
|
|
@ -4607,12 +4695,15 @@ void StatsService::fetchStatsFromOme(const std::string& streamKey,
|
||||||
std::function<void(bool, const StreamStats&)> callback) {
|
std::function<void(bool, const StreamStats&)> callback) {
|
||||||
LOG_DEBUG << "Fetching stats for stream: " << streamKey;
|
LOG_DEBUG << "Fetching stats for stream: " << streamKey;
|
||||||
|
|
||||||
|
// First, try to get the stream stats
|
||||||
OmeClient::getInstance().getStreamStats(streamKey, [this, callback, streamKey](bool success, const Json::Value& json) {
|
OmeClient::getInstance().getStreamStats(streamKey, [this, callback, streamKey](bool success, const Json::Value& json) {
|
||||||
StreamStats stats;
|
StreamStats stats;
|
||||||
|
bool streamExists = false;
|
||||||
|
|
||||||
if (success && json.isMember("response") && !json["response"].isNull()) {
|
if (success && json.isMember("response") && !json["response"].isNull()) {
|
||||||
try {
|
try {
|
||||||
const auto& data = json["response"];
|
const auto& data = json["response"];
|
||||||
|
streamExists = true;
|
||||||
|
|
||||||
// Parse connections
|
// Parse connections
|
||||||
if (data.isMember("connections")) {
|
if (data.isMember("connections")) {
|
||||||
|
|
@ -4635,36 +4726,107 @@ void StatsService::fetchStatsFromOme(const std::string& streamKey,
|
||||||
stats.totalConnections = totalConns;
|
stats.totalConnections = totalConns;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bitrate
|
// Check multiple indicators for live status
|
||||||
stats.bitrate = data.isMember("lastThroughputIn") ?
|
bool hasInput = false;
|
||||||
data["lastThroughputIn"].asDouble() :
|
|
||||||
(data.isMember("avgThroughputIn") ? data["avgThroughputIn"].asDouble() : 0);
|
|
||||||
|
|
||||||
// Byte counters
|
// Check for input field
|
||||||
if (data.isMember("totalBytesIn")) stats.totalBytesIn = data["totalBytesIn"].asInt64();
|
if (data.isMember("input") && !data["input"].isNull()) {
|
||||||
if (data.isMember("totalBytesOut")) stats.totalBytesOut = data["totalBytesOut"].asInt64();
|
hasInput = true;
|
||||||
|
const auto& input = data["input"];
|
||||||
|
|
||||||
|
// Get bitrate from input tracks
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternative: Check lastThroughputIn
|
||||||
|
if (!hasInput && data.isMember("lastThroughputIn")) {
|
||||||
|
double throughput = data["lastThroughputIn"].asDouble();
|
||||||
|
if (throughput > 0) {
|
||||||
|
hasInput = true;
|
||||||
|
stats.bitrate = throughput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternative: Check avgThroughputIn
|
||||||
|
if (!hasInput && data.isMember("avgThroughputIn")) {
|
||||||
|
double avgThroughput = data["avgThroughputIn"].asDouble();
|
||||||
|
if (avgThroughput > 0) {
|
||||||
|
hasInput = true;
|
||||||
|
stats.bitrate = avgThroughput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check bytes counters
|
||||||
|
if (data.isMember("totalBytesIn")) {
|
||||||
|
stats.totalBytesIn = data["totalBytesIn"].asInt64();
|
||||||
|
if (stats.totalBytesIn > 0) {
|
||||||
|
hasInput = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.isMember("totalBytesOut")) {
|
||||||
|
stats.totalBytesOut = data["totalBytesOut"].asInt64();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream is live if it has input or active bitrate
|
||||||
|
stats.isLive = hasInput || stats.bitrate > 0;
|
||||||
|
|
||||||
|
LOG_DEBUG << "Stream " << streamKey
|
||||||
|
<< " - hasInput: " << hasInput
|
||||||
|
<< ", bitrate: " << stats.bitrate
|
||||||
|
<< ", totalBytesIn: " << stats.totalBytesIn
|
||||||
|
<< ", isLive: " << stats.isLive;
|
||||||
|
|
||||||
stats.isLive = (stats.bitrate > 0 || stats.currentConnections > 0);
|
|
||||||
LOG_DEBUG << "OME stats response: " << json.toStyledString();
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
LOG_ERROR << "Failed to parse stats: " << e.what();
|
LOG_ERROR << "Failed to parse stats: " << e.what();
|
||||||
stats.isLive = false;
|
stats.isLive = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Stream doesn't exist in OME
|
||||||
stats.isLive = false;
|
stats.isLive = false;
|
||||||
|
LOG_DEBUG << "Stream " << streamKey << " not found in OME";
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.lastUpdated = std::chrono::system_clock::now();
|
stats.lastUpdated = std::chrono::system_clock::now();
|
||||||
|
|
||||||
// Now fetch stream info for resolution/codec/fps
|
// If stream exists, try to get detailed stream info
|
||||||
|
if (streamExists) {
|
||||||
OmeClient::getInstance().getStreamInfo(streamKey, [callback, stats](bool infoSuccess, const Json::Value& infoJson) mutable {
|
OmeClient::getInstance().getStreamInfo(streamKey, [callback, stats](bool infoSuccess, const Json::Value& infoJson) mutable {
|
||||||
// Parse stream metadata if available
|
// Parse stream metadata if available
|
||||||
if (infoSuccess && infoJson.isMember("response") && infoJson["response"].isMember("tracks")) {
|
if (infoSuccess && infoJson.isMember("response")) {
|
||||||
try {
|
try {
|
||||||
for (const auto& track : infoJson["response"]["tracks"]) {
|
const auto& response = infoJson["response"];
|
||||||
|
|
||||||
|
LOG_DEBUG << "Stream info response: " << response.toStyledString();
|
||||||
|
|
||||||
|
// Check if stream has input (another way to verify it's live)
|
||||||
|
if (response.isMember("input") && !response["input"].isNull()) {
|
||||||
|
stats.isLive = true;
|
||||||
|
|
||||||
|
// Try to get codec from input tracks first
|
||||||
|
if (response["input"].isMember("tracks") && response["input"]["tracks"].isArray()) {
|
||||||
|
for (const auto& track : response["input"]["tracks"]) {
|
||||||
if (track["type"].asString() == "video") {
|
if (track["type"].asString() == "video") {
|
||||||
if (track.isMember("codec")) {
|
if (track.isMember("codec")) {
|
||||||
stats.codec = track["codec"].asString();
|
std::string codec = track["codec"].asString();
|
||||||
|
// Clean up codec string
|
||||||
|
if (codec == "H264" || codec == "h264") {
|
||||||
|
stats.codec = "H.264";
|
||||||
|
} else if (codec == "H265" || codec == "h265") {
|
||||||
|
stats.codec = "H.265";
|
||||||
|
} else if (codec == "VP8" || codec == "vp8") {
|
||||||
|
stats.codec = "VP8";
|
||||||
|
} else if (codec == "VP9" || codec == "vp9") {
|
||||||
|
stats.codec = "VP9";
|
||||||
|
} else {
|
||||||
|
stats.codec = codec;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (track.isMember("width") && track.isMember("height")) {
|
if (track.isMember("width") && track.isMember("height")) {
|
||||||
stats.resolution = std::to_string(track["width"].asInt()) + "x" +
|
stats.resolution = std::to_string(track["width"].asInt()) + "x" +
|
||||||
|
|
@ -4672,10 +4834,54 @@ void StatsService::fetchStatsFromOme(const std::string& streamKey,
|
||||||
}
|
}
|
||||||
if (track.isMember("framerate")) {
|
if (track.isMember("framerate")) {
|
||||||
stats.fps = track["framerate"].asDouble();
|
stats.fps = track["framerate"].asDouble();
|
||||||
|
} else if (track.isMember("frameRate")) {
|
||||||
|
stats.fps = track["frameRate"].asDouble();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no codec found in input, try output tracks
|
||||||
|
if (stats.codec.empty() || stats.codec == "N/A") {
|
||||||
|
if (response.isMember("tracks") && response["tracks"].isArray()) {
|
||||||
|
for (const auto& track : response["tracks"]) {
|
||||||
|
if (track["type"].asString() == "video") {
|
||||||
|
if (track.isMember("codec")) {
|
||||||
|
std::string codec = track["codec"].asString();
|
||||||
|
if (codec == "H264" || codec == "h264") {
|
||||||
|
stats.codec = "H.264";
|
||||||
|
} else if (codec == "H265" || codec == "h265") {
|
||||||
|
stats.codec = "H.265";
|
||||||
|
} else if (codec == "VP8" || codec == "vp8") {
|
||||||
|
stats.codec = "VP8";
|
||||||
|
} else if (codec == "VP9" || codec == "vp9") {
|
||||||
|
stats.codec = "VP9";
|
||||||
|
} else {
|
||||||
|
stats.codec = codec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stats.resolution == "N/A" && track.isMember("width") && track.isMember("height")) {
|
||||||
|
stats.resolution = std::to_string(track["width"].asInt()) + "x" +
|
||||||
|
std::to_string(track["height"].asInt());
|
||||||
|
}
|
||||||
|
if (stats.fps == 0 && track.isMember("framerate")) {
|
||||||
|
stats.fps = track["framerate"].asDouble();
|
||||||
|
} else if (stats.fps == 0 && track.isMember("frameRate")) {
|
||||||
|
stats.fps = track["frameRate"].asDouble();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set defaults if still empty
|
||||||
|
if (stats.codec.empty()) {
|
||||||
|
stats.codec = "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
LOG_ERROR << "Failed to parse stream info: " << e.what();
|
LOG_ERROR << "Failed to parse stream info: " << e.what();
|
||||||
}
|
}
|
||||||
|
|
@ -4683,6 +4889,10 @@ void StatsService::fetchStatsFromOme(const std::string& streamKey,
|
||||||
|
|
||||||
callback(true, stats);
|
callback(true, stats);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// Stream doesn't exist, return offline stats
|
||||||
|
callback(true, stats);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4736,7 +4946,7 @@ void StatsService::getStreamStats(const std::string& streamKey,
|
||||||
fetchStatsFromOme(streamKey, [this, callback, streamKey](bool success, const StreamStats& stats) {
|
fetchStatsFromOme(streamKey, [this, callback, streamKey](bool success, const StreamStats& stats) {
|
||||||
if (success) {
|
if (success) {
|
||||||
StreamStats updatedStats = stats;
|
StreamStats updatedStats = stats;
|
||||||
// FIX: Set uniqueViewers on cache miss!
|
// Set uniqueViewers on cache miss
|
||||||
updatedStats.uniqueViewers = getUniqueViewerCount(streamKey);
|
updatedStats.uniqueViewers = getUniqueViewerCount(streamKey);
|
||||||
callback(true, updatedStats);
|
callback(true, updatedStats);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -4805,7 +5015,7 @@ void StatsService::getStreamStats(const std::string& streamKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\backend\src\services\StatsService.h ###
|
### C:\Users\Administrator\Desktop\pub\drogon\backend\src\services\StatsService.h ###
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <drogon/drogon.h>
|
#include <drogon/drogon.h>
|
||||||
|
|
@ -4850,6 +5060,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void initialize();
|
void initialize();
|
||||||
|
void startPolling(); // NEW: Separate method to start polling
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
// Get cached stats from Redis
|
// Get cached stats from Redis
|
||||||
|
|
@ -4881,7 +5092,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\database\init.sql ###
|
### C:\Users\Administrator\Desktop\pub\drogon\database\init.sql ###
|
||||||
|
|
||||||
-- Create users table
|
-- Create users table
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
|
@ -4995,14 +5206,14 @@ CREATE TRIGGER ensure_pgp_only_timestamp BEFORE INSERT OR UPDATE ON users
|
||||||
FOR EACH ROW EXECUTE FUNCTION check_pgp_only_timestamp();
|
FOR EACH ROW EXECUTE FUNCTION check_pgp_only_timestamp();
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\.env ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\.env ###
|
||||||
|
|
||||||
VITE_API_URL=http://localhost/api
|
VITE_API_URL=http://localhost/api
|
||||||
VITE_WS_URL=ws://localhost/ws
|
VITE_WS_URL=ws://localhost/ws
|
||||||
VITE_STREAM_PORT=8088
|
VITE_STREAM_PORT=8088
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\.gitignore ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\.gitignore ###
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
|
|
@ -5016,7 +5227,7 @@ vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\Dockerfile ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\Dockerfile ###
|
||||||
|
|
||||||
FROM node:20-alpine AS builder
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
|
@ -5061,7 +5272,7 @@ ENV NODE_ENV=production
|
||||||
CMD ["node", "build"]
|
CMD ["node", "build"]
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\package.json ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\package.json ###
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "streaming-frontend",
|
"name": "streaming-frontend",
|
||||||
|
|
@ -5096,7 +5307,7 @@ CMD ["node", "build"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\svelte.config.js ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\svelte.config.js ###
|
||||||
|
|
||||||
import adapter from '@sveltejs/adapter-node';
|
import adapter from '@sveltejs/adapter-node';
|
||||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
|
@ -5111,7 +5322,6 @@ const config = {
|
||||||
precompress: false
|
precompress: false
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Security improvements
|
|
||||||
csp: {
|
csp: {
|
||||||
mode: 'auto',
|
mode: 'auto',
|
||||||
directives: {
|
directives: {
|
||||||
|
|
@ -5119,14 +5329,20 @@ const config = {
|
||||||
'script-src': ["'self'", "'unsafe-inline'"],
|
'script-src': ["'self'", "'unsafe-inline'"],
|
||||||
'style-src': ["'self'", "'unsafe-inline'", 'https://cdnjs.cloudflare.com'],
|
'style-src': ["'self'", "'unsafe-inline'", 'https://cdnjs.cloudflare.com'],
|
||||||
'img-src': ["'self'", 'data:', 'blob:'],
|
'img-src': ["'self'", 'data:', 'blob:'],
|
||||||
'font-src': ["'self'", 'https://cdnjs.cloudflare.com'],
|
'font-src': ["'self'", 'data:', 'https://cdnjs.cloudflare.com'], // Added 'data:' for embedded fonts
|
||||||
'connect-src': ["'self'", 'ws://localhost', 'wss://localhost', 'http://localhost:*'],
|
'connect-src': [
|
||||||
|
"'self'",
|
||||||
|
'ws://localhost:*', // Changed to include port wildcard
|
||||||
|
'wss://localhost:*', // Changed to include port wildcard
|
||||||
|
'http://localhost:*'
|
||||||
|
],
|
||||||
'media-src': ["'self'", 'blob:', 'http://localhost:*'],
|
'media-src': ["'self'", 'blob:', 'http://localhost:*'],
|
||||||
'object-src': ["'none'"],
|
'object-src': ["'none'"],
|
||||||
'frame-ancestors': ["'none'"],
|
'frame-ancestors': ["'none'"],
|
||||||
'form-action': ["'self'"],
|
'form-action': ["'self'"],
|
||||||
'base-uri': ["'self'"]
|
'base-uri': ["'self'"]
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Enable CSRF protection (default is true)
|
// Enable CSRF protection (default is true)
|
||||||
|
|
@ -5156,7 +5372,7 @@ const config = {
|
||||||
export default config;
|
export default config;
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\tsconfig.json ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\tsconfig.json ###
|
||||||
|
|
||||||
{
|
{
|
||||||
"extends": "./.svelte-kit/tsconfig.json",
|
"extends": "./.svelte-kit/tsconfig.json",
|
||||||
|
|
@ -5178,7 +5394,7 @@ export default config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\vite.config.ts ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\vite.config.ts ###
|
||||||
|
|
||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
|
@ -5192,7 +5408,7 @@ export default defineConfig({
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\src\app.css ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\src\app.css ###
|
||||||
|
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
@ -5519,7 +5735,7 @@ html {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\src\app.d.ts ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\src\app.d.ts ###
|
||||||
|
|
||||||
// See https://kit.svelte.dev/docs/types#app
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
|
|
@ -5546,7 +5762,7 @@ declare global {
|
||||||
export {};
|
export {};
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\src\app.html ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\src\app.html ###
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
@ -5579,7 +5795,7 @@ export {};
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\src\lib\api.js ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\src\lib\api.js ###
|
||||||
|
|
||||||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost/api';
|
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost/api';
|
||||||
|
|
||||||
|
|
@ -5619,7 +5835,7 @@ export async function getStreamStats(streamKey) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\src\lib\pgp.js ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\src\lib\pgp.js ###
|
||||||
|
|
||||||
// Client-side PGP utilities - wraps openpgp for browser-only usage
|
// Client-side PGP utilities - wraps openpgp for browser-only usage
|
||||||
|
|
||||||
|
|
@ -5722,7 +5938,7 @@ export function removeStoredPrivateKey() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\src\lib\websocket.js ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\src\lib\websocket.js ###
|
||||||
|
|
||||||
let ws = null;
|
let ws = null;
|
||||||
let reconnectTimeout = null;
|
let reconnectTimeout = null;
|
||||||
|
|
@ -5782,7 +5998,7 @@ export function sendMessage(message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\src\lib\stores\auth.js ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\src\lib\stores\auth.js ###
|
||||||
|
|
||||||
import { writable, derived } from 'svelte/store';
|
import { writable, derived } from 'svelte/store';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
|
|
@ -5914,7 +6130,7 @@ export const isStreamer = derived(
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\src\routes\+layout.svelte ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\src\routes\+layout.svelte ###
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
@ -6146,7 +6362,7 @@ export const isStreamer = derived(
|
||||||
<slot />
|
<slot />
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\src\routes\+page.svelte ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\src\routes\+page.svelte ###
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
@ -6354,7 +6570,7 @@ export const isStreamer = derived(
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\src\routes\admin\+page.svelte ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\src\routes\admin\+page.svelte ###
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
@ -6743,7 +6959,7 @@ export const isStreamer = derived(
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\src\routes\login\+page.svelte ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\src\routes\login\+page.svelte ###
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
@ -7281,7 +7497,7 @@ iQEcBAABCAAGBQJe...
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\src\routes\my-realms\+page.svelte ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\src\routes\my-realms\+page.svelte ###
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { onMount, onDestroy } from 'svelte';
|
import { onMount, onDestroy } from 'svelte';
|
||||||
|
|
@ -7493,7 +7709,7 @@ iQEcBAABCAAGBQJe...
|
||||||
if (bitrate > 1000000) {
|
if (bitrate > 1000000) {
|
||||||
return (bitrate / 1000000).toFixed(2) + ' Mbps';
|
return (bitrate / 1000000).toFixed(2) + ' Mbps';
|
||||||
} else if (bitrate > 1000) {
|
} else if (bitrate > 1000) {
|
||||||
return (bitrate / 1000).toFixed(0) + ' Kbps';
|
return Math.round(bitrate / 1000) + ' kbps'; // Changed to lowercase 'kbps' and rounded
|
||||||
} else {
|
} else {
|
||||||
return bitrate + ' bps';
|
return bitrate + ' bps';
|
||||||
}
|
}
|
||||||
|
|
@ -7789,7 +8005,7 @@ iQEcBAABCAAGBQJe...
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\src\routes\profile\[username]\+page.svelte ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\src\routes\profile\[username]\+page.svelte ###
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
@ -8241,7 +8457,7 @@ iQEcBAABCAAGBQJe...
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\src\routes\settings\+page.svelte ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\src\routes\settings\+page.svelte ###
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
@ -9418,7 +9634,7 @@ iQEcBAABCAAGBQJe...
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\frontend\src\routes\[realm]\live\+page.svelte ###
|
### C:\Users\Administrator\Desktop\pub\drogon\frontend\src\routes\[realm]\live\+page.svelte ###
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { onMount, onDestroy } from 'svelte';
|
import { onMount, onDestroy } from 'svelte';
|
||||||
|
|
@ -10091,7 +10307,7 @@ iQEcBAABCAAGBQJe...
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\openresty\Dockerfile ###
|
### C:\Users\Administrator\Desktop\pub\drogon\openresty\Dockerfile ###
|
||||||
|
|
||||||
FROM openresty/openresty:alpine
|
FROM openresty/openresty:alpine
|
||||||
|
|
||||||
|
|
@ -10123,7 +10339,7 @@ EXPOSE 80 443
|
||||||
CMD ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"]
|
CMD ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"]
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\openresty\nginx.conf ###
|
### C:\Users\Administrator\Desktop\pub\drogon\openresty\nginx.conf ###
|
||||||
|
|
||||||
worker_processes auto;
|
worker_processes auto;
|
||||||
error_log stderr warn;
|
error_log stderr warn;
|
||||||
|
|
@ -10577,7 +10793,7 @@ http {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\openresty\lua\auth.lua ###
|
### C:\Users\Administrator\Desktop\pub\drogon\openresty\lua\auth.lua ###
|
||||||
|
|
||||||
local redis_helper = require "redis_helper"
|
local redis_helper = require "redis_helper"
|
||||||
local cjson = require "cjson"
|
local cjson = require "cjson"
|
||||||
|
|
@ -10648,7 +10864,7 @@ ngx.header.content_type = "application/json"
|
||||||
ngx.say(cjson.encode(response))
|
ngx.say(cjson.encode(response))
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\openresty\lua\redis_helper.lua ###
|
### C:\Users\Administrator\Desktop\pub\drogon\openresty\lua\redis_helper.lua ###
|
||||||
|
|
||||||
local redis = require "resty.redis"
|
local redis = require "resty.redis"
|
||||||
|
|
||||||
|
|
@ -10778,7 +10994,7 @@ end
|
||||||
return _M
|
return _M
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\openresty\lua\stream_monitor.lua ###
|
### C:\Users\Administrator\Desktop\pub\drogon\openresty\lua\stream_monitor.lua ###
|
||||||
|
|
||||||
|
|
||||||
local redis_helper = require "redis_helper"
|
local redis_helper = require "redis_helper"
|
||||||
|
|
@ -10866,7 +11082,7 @@ end
|
||||||
ngx.timer.at(5, start_monitoring)
|
ngx.timer.at(5, start_monitoring)
|
||||||
|
|
||||||
|
|
||||||
### C:\Users\Administrator\Desktop\pub\drogon - Copy (2) - Copy\ovenmediaengine\Server.xml ###
|
### C:\Users\Administrator\Desktop\pub\drogon\ovenmediaengine\Server.xml ###
|
||||||
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Server version="8">
|
<Server version="8">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue