mirror of
https://gitee.com/xia-chu/ZLMediaKit.git
synced 2026-05-21 17:17:49 +08:00
Compare commits
10 Commits
7534a70f34
...
df9f3bd8a9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df9f3bd8a9 | ||
|
|
96c62cdac0 | ||
|
|
881238fcf3 | ||
|
|
85524f102b | ||
|
|
a28b0fc0a4 | ||
|
|
b0cf40d281 | ||
|
|
b118c5e936 | ||
|
|
171b354e64 | ||
|
|
e3b3630f34 | ||
|
|
9b37d69bfd |
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -10,3 +10,9 @@
|
||||
[submodule "www/webassist"]
|
||||
path = www/webassist
|
||||
url = https://gitee.com/victor1002/zlm_webassist
|
||||
[submodule "3rdpart/pybind11"]
|
||||
path = 3rdpart/pybind11
|
||||
url = https://gitee.com/mirrors/pybind11.git
|
||||
[submodule "python/StreamUI"]
|
||||
path = python/StreamUI
|
||||
url = https://gitee.com/xia-chu/StreamUI.git
|
||||
|
||||
@ -9,4 +9,10 @@
|
||||
url = https://github.com/open-source-parsers/jsoncpp.git
|
||||
[submodule "www/webassist"]
|
||||
path = www/webassist
|
||||
url = https://github.com/1002victor/zlm_webassist
|
||||
url = https://github.com/1002victor/zlm_webassist
|
||||
[submodule "3rdpart/pybind11"]
|
||||
path = 3rdpart/pybind11
|
||||
url = https://github.com/pybind/pybind11.git
|
||||
[submodule "python/StreamUI"]
|
||||
path = python/StreamUI
|
||||
url = https://github.com/xia-chu/StreamUI.git
|
||||
@ -120,4 +120,14 @@ add_subdirectory(ZLToolKit)
|
||||
# 添加库别名
|
||||
add_library(ZLMediaKit::ToolKit ALIAS ZLToolKit)
|
||||
# 添加依赖
|
||||
update_cached_list(MK_LINK_LIBRARIES ZLMediaKit::ToolKit)
|
||||
update_cached_list(MK_LINK_LIBRARIES ZLMediaKit::ToolKit)
|
||||
|
||||
##############################################################################
|
||||
|
||||
if (ENABLE_PYTHON)
|
||||
# ============ pybind11 lib ============
|
||||
add_subdirectory(pybind11)
|
||||
update_cached_list(MK_LINK_LIBRARIES pybind11::embed)
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/pybind11/include)
|
||||
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_PYTHON)
|
||||
endif ()
|
||||
1
3rdpart/pybind11
Submodule
1
3rdpart/pybind11
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit ed5057ded698e305210269dafa57574ecf964483
|
||||
@ -64,6 +64,7 @@ option(USE_SOLUTION_FOLDERS "Enable solution dir supported" ON)
|
||||
option(ENABLE_OBJCOPY "Enable use objcopy to generate debug info file" ON)
|
||||
# 编译静态库
|
||||
option(BUILD_SHARED_LIBS "Build shared instead of static" OFF)
|
||||
option(ENABLE_PYTHON "Enable python plugin" OFF)
|
||||
|
||||
##############################################################################
|
||||
# 设置socket默认缓冲区大小为256k.如果设置为0则不设置socket的默认缓冲区大小,使用系统内核默认值(设置为0仅对linux有效)
|
||||
@ -569,6 +570,15 @@ endif ()
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/www" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/conf/config.ini" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/default.pem" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
if (ENABLE_PYTHON)
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/python" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
file(GLOB FRONTEND_FILES
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/python/StreamUI/frontend/*"
|
||||
)
|
||||
file(COPY ${FRONTEND_FILES}
|
||||
DESTINATION "${EXECUTABLE_OUTPUT_PATH}/www/StreamUI/"
|
||||
)
|
||||
endif ()
|
||||
|
||||
# 拷贝VideoStack 无视频流时默认填充的背景图片
|
||||
# Copy the default background image used by VideoStack when there is no video stream
|
||||
|
||||
1
python/StreamUI
Submodule
1
python/StreamUI
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit d055e925a5c061d3ce785f60f07b59ded08b4e55
|
||||
27
python/mk_logger.py
Normal file
27
python/mk_logger.py
Normal file
@ -0,0 +1,27 @@
|
||||
import inspect
|
||||
|
||||
try:
|
||||
import mk_loader
|
||||
USE_PLUGIN_LOGGER = True
|
||||
except ImportError:
|
||||
USE_PLUGIN_LOGGER = False
|
||||
|
||||
def _do_log(level: int, *args):
|
||||
frame_info = inspect.stack()[2]
|
||||
filename = frame_info.filename
|
||||
lineno = frame_info.lineno
|
||||
funcname = frame_info.function
|
||||
|
||||
# 把所有参数转成字符串后用空格拼接
|
||||
msg = " ".join(str(arg) for arg in args)
|
||||
|
||||
if USE_PLUGIN_LOGGER:
|
||||
mk_loader.log(level, filename, lineno, funcname, msg)
|
||||
else:
|
||||
print(f"[{filename}:{lineno}] {funcname} | {msg}")
|
||||
|
||||
def log_trace(*args): _do_log(0, *args)
|
||||
def log_debug(*args): _do_log(1, *args)
|
||||
def log_info(*args): _do_log(2, *args)
|
||||
def log_warn(*args): _do_log(3, *args)
|
||||
def log_error(*args): _do_log(4, *args)
|
||||
89
python/mk_plugin.py
Normal file
89
python/mk_plugin.py
Normal file
@ -0,0 +1,89 @@
|
||||
import mk_logger
|
||||
import mk_loader
|
||||
import asyncio
|
||||
import threading
|
||||
from StreamUI.backend.main import app
|
||||
from starlette.routing import Match
|
||||
|
||||
def start_background_loop(loop):
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_forever()
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
threading.Thread(target=start_background_loop, args=(loop,), daemon=True).start()
|
||||
|
||||
def submit_coro(scope, body, send):
|
||||
async def run():
|
||||
# 包装 send 函数,确保它总是可等待的
|
||||
async def async_send(message):
|
||||
# 调用原始的 send 函数,它现在应该返回一个协程
|
||||
result = send(message)
|
||||
if result is not None:
|
||||
await result
|
||||
|
||||
async def receive():
|
||||
return {
|
||||
"type": "http.request",
|
||||
"body": body,
|
||||
"more_body": False,
|
||||
}
|
||||
|
||||
try:
|
||||
await app(scope, receive, async_send)
|
||||
except Exception as e:
|
||||
mk_logger.log_warn(f"FastAPI failed: {e}")
|
||||
# 发送错误响应
|
||||
await async_send({
|
||||
"type": "http.response.start",
|
||||
"status": 500,
|
||||
"headers": [(b"content-type", b"text/plain")],
|
||||
})
|
||||
await async_send({
|
||||
"type": "http.response.body",
|
||||
"body": b"Internal Server Error",
|
||||
"more_body": False,
|
||||
})
|
||||
return asyncio.run_coroutine_threadsafe(run(), loop)
|
||||
|
||||
def check_route(scope) -> bool:
|
||||
for route in app.routes:
|
||||
if hasattr(route, "matches"):
|
||||
match, _ = route.matches(scope)
|
||||
if match == Match.FULL:
|
||||
return True
|
||||
return False
|
||||
|
||||
def on_start():
|
||||
mk_logger.log_info(f"on_start, secret: {mk_loader.get_config('api.secret')}")
|
||||
# mk_loader.set_config('api.secret', "new_secret_from_python")
|
||||
# mk_loader.update_config()
|
||||
mk_loader.set_fastapi(check_route, submit_coro)
|
||||
|
||||
def on_exit():
|
||||
mk_logger.log_info("on_exit")
|
||||
|
||||
def on_publish(type: str, args: dict, invoker, sender: dict) -> bool:
|
||||
mk_logger.log_info(f"args: {type}, args: {args}, sender: {sender}")
|
||||
# opt 控制转协议,请参考配置文件[protocol]下字段
|
||||
opt = {
|
||||
"enable_rtmp": "1"
|
||||
}
|
||||
# 响应推流鉴权结果
|
||||
mk_loader.publish_auth_invoker_do(invoker, "", opt);
|
||||
# 返回True代表此事件被python拦截
|
||||
return True
|
||||
|
||||
def on_play(args: dict, invoker, sender: dict) -> bool:
|
||||
mk_logger.log_info(f"args: {args}, sender: {sender}")
|
||||
# 响应播放鉴权结果
|
||||
mk_loader.auth_invoker_do(invoker, "");
|
||||
# 返回True代表此事件被python拦截
|
||||
return True
|
||||
|
||||
def on_flow_report(args: dict, totalBytes: int, totalDuration: int, isPlayer: bool, sender: dict) -> bool:
|
||||
mk_logger.log_info(f"args: {args}, totalBytes: {totalBytes}, totalDuration: {totalDuration}, isPlayer: {isPlayer}, sender: {sender}")
|
||||
# 返回True代表此事件被python拦截
|
||||
return True
|
||||
|
||||
def on_reload_config():
|
||||
mk_logger.log_info(f"on_reload_config")
|
||||
@ -345,7 +345,7 @@ static inline string getPusherKey(const string &schema, const string &vhost, con
|
||||
return schema + "/" + vhost + "/" + app + "/" + stream + "/" + MD5(dst_url).hexdigest();
|
||||
}
|
||||
|
||||
static void fillSockInfo(Value& val, SockInfo* info) {
|
||||
void fillSockInfo(Value& val, SockInfo* info) {
|
||||
val["peer_ip"] = info->get_peer_ip();
|
||||
val["peer_port"] = info->get_peer_port();
|
||||
val["local_port"] = info->get_local_port();
|
||||
|
||||
@ -21,6 +21,10 @@
|
||||
#include "WebHook.h"
|
||||
#include "WebApi.h"
|
||||
|
||||
#if defined(ENABLE_PYTHON)
|
||||
#include "pyinvoker.h"
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace Json;
|
||||
using namespace toolkit;
|
||||
@ -226,7 +230,7 @@ void do_http_hook(const string &url, const ArgsType &body, const function<void(c
|
||||
|
||||
void dumpMediaTuple(const MediaTuple &tuple, Json::Value& item);
|
||||
|
||||
static ArgsType make_json(const MediaInfo &args) {
|
||||
ArgsType make_json(const MediaInfo &args) {
|
||||
ArgsType body;
|
||||
body["schema"] = args.schema;
|
||||
if(!args.protocol.empty()){
|
||||
@ -358,6 +362,12 @@ void installWebHook() {
|
||||
GET_CONFIG(bool, hook_enable, Hook::kEnable);
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) {
|
||||
#if defined(ENABLE_PYTHON)
|
||||
if (PythonInvoker::Instance().on_publish(type, args, invoker, sender)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
GET_CONFIG(string, hook_publish, Hook::kOnPublish);
|
||||
if (!hook_enable || hook_publish.empty()) {
|
||||
invoker("", ProtocolOption());
|
||||
@ -387,6 +397,11 @@ void installWebHook() {
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) {
|
||||
#if defined(ENABLE_PYTHON)
|
||||
if (PythonInvoker::Instance().on_play(args, invoker, sender)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
GET_CONFIG(string, hook_play, Hook::kOnPlay);
|
||||
if (!hook_enable || hook_play.empty()) {
|
||||
invoker("");
|
||||
@ -402,6 +417,11 @@ void installWebHook() {
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) {
|
||||
#if defined(ENABLE_PYTHON)
|
||||
if (PythonInvoker::Instance().on_flow_report(args, totalBytes, totalDuration, isPlayer, sender)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
GET_CONFIG(string, hook_flowreport, Hook::kOnFlowReport);
|
||||
if (!hook_enable || hook_flowreport.empty()) {
|
||||
return;
|
||||
|
||||
@ -43,6 +43,10 @@
|
||||
#include "ZLMVersion.h"
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_PYTHON)
|
||||
#include "pyinvoker.h"
|
||||
#endif
|
||||
|
||||
#include "System.h"
|
||||
|
||||
using namespace std;
|
||||
@ -107,6 +111,14 @@ onceToken token1([](){
|
||||
},nullptr);
|
||||
} //namespace RtpProxy
|
||||
|
||||
namespace Python {
|
||||
#define Python_FIELD "python."
|
||||
const string kPlugin = Python_FIELD"plugin";
|
||||
onceToken token1([](){
|
||||
mINI::Instance()[kPlugin] = "mk_plugin";
|
||||
},nullptr);
|
||||
} //namespace RtpProxy
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
|
||||
@ -494,6 +506,13 @@ int start_main(int argc,char *argv[]) {
|
||||
g_reload_certificates();
|
||||
});
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_PYTHON)
|
||||
auto py_plugin = mINI::Instance()[Python::kPlugin];
|
||||
if (!py_plugin.empty()) {
|
||||
PythonInvoker::Instance().load(py_plugin);
|
||||
}
|
||||
#endif
|
||||
sem.wait();
|
||||
}
|
||||
unInstallWebApi();
|
||||
|
||||
310
server/pyinvoker.cpp
Normal file
310
server/pyinvoker.cpp
Normal file
@ -0,0 +1,310 @@
|
||||
#if defined(ENABLE_PYTHON)
|
||||
|
||||
#include "pyinvoker.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include "WebHook.h"
|
||||
#include "Common/Parser.h"
|
||||
#include "Http/HttpSession.h"
|
||||
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
extern ArgsType make_json(const MediaInfo &args);
|
||||
extern void fillSockInfo(Json::Value & val, SockInfo* info);
|
||||
extern std::string g_ini_file;
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_copy_constructible<T>::value, py::capsule>::type to_python(const T &obj) {
|
||||
static auto name_str = toolkit::demangle(typeid(T).name());
|
||||
auto p = new toolkit::Any(std::make_shared<T>(obj));
|
||||
return py::capsule(p, name_str.data(), [](PyObject *capsule) {
|
||||
auto p = reinterpret_cast<toolkit::Any *>(PyCapsule_GetPointer(capsule, name_str.data()));
|
||||
delete p;
|
||||
TraceL << "delete " << name_str << "(" << p << ")";
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_copy_constructible<T>::value, py::capsule>::type to_python(const T &obj) {
|
||||
static auto name_str = toolkit::demangle(typeid(T).name());
|
||||
auto p = new toolkit::Any(std::shared_ptr<T>(const_cast<T *>(&obj), [](T *) {}));
|
||||
return py::capsule(p, name_str.data(), [](PyObject *capsule) {
|
||||
auto p = reinterpret_cast<toolkit::Any *>(PyCapsule_GetPointer(capsule, name_str.data()));
|
||||
delete p;
|
||||
TraceL << "unref " << name_str << "(" << p << ")";
|
||||
});
|
||||
}
|
||||
|
||||
static py::dict jsonToPython(const Json::Value &obj) {
|
||||
py::dict ret;
|
||||
if (obj.isObject()) {
|
||||
for (auto it = obj.begin(); it != obj.end(); ++it) {
|
||||
if (it->isNull()) {
|
||||
// 忽略null,修复wvp传null覆盖Protocol配置的问题
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
auto str = (*it).asString();
|
||||
ret[it.name().data()] = std::move(str);
|
||||
} catch (std::exception &) {
|
||||
WarnL << "Json is not convertible to string, key: " << it.name() << ", value: " << (*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
py::dict to_python(const MediaInfo &args) {
|
||||
auto json = make_json(args);
|
||||
return jsonToPython(json);
|
||||
}
|
||||
|
||||
py::dict to_python(const SockInfo &info) {
|
||||
Json::Value json;
|
||||
fillSockInfo(json, const_cast<SockInfo *>(&info));
|
||||
return jsonToPython(json);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T &to_native(const py::capsule &cap) {
|
||||
static auto name_str = toolkit::demangle(typeid(T).name());
|
||||
if (std::string(cap.name()) != name_str) {
|
||||
throw std::runtime_error("Invalid capsule name!");
|
||||
}
|
||||
auto any = static_cast<toolkit::Any *>(cap.get_pointer());
|
||||
return any->get<T>();
|
||||
}
|
||||
|
||||
mINI to_native(const py::dict &opt) {
|
||||
mINI ret;
|
||||
for (auto &item : opt) {
|
||||
// 转换为字符串(允许 int/float/bool 等)
|
||||
ret.emplace(py::str(item.first).cast<std::string>(), py::str(item.second).cast<std::string>());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void handle_http_request(const py::object &check_route, const py::object &submit_coro, const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, bool &consumed, toolkit::SockInfo &sender) {
|
||||
py::gil_scoped_acquire guard;
|
||||
|
||||
py::dict scope;
|
||||
scope["type"] = "http";
|
||||
scope["http_version"] = "1.1";
|
||||
scope["method"] = parser.method();
|
||||
scope["path"] = parser.url();
|
||||
scope["query_string"] = parser.params();
|
||||
py::list hdrs;
|
||||
for (auto &kv : parser.getHeader()) {
|
||||
hdrs.append(py::make_tuple(py::bytes(kv.first), py::bytes(kv.second)));
|
||||
}
|
||||
scope["headers"] = hdrs;
|
||||
|
||||
bool ok = check_route(scope).cast<bool>();
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
consumed = true;
|
||||
|
||||
StrCaseMap resp_headers;
|
||||
std::string resp_body;
|
||||
int status = 500;
|
||||
auto send = py::cpp_function([invoker, status, resp_body, resp_headers](const py::dict &msg) mutable {
|
||||
auto type = msg["type"].cast<std::string>();
|
||||
if (type == "http.response.start") {
|
||||
status = msg["status"].cast<int>();
|
||||
for (auto tup : msg["headers"].cast<py::list>()) {
|
||||
auto t = tup.cast<py::tuple>();
|
||||
resp_headers[t[0].cast<std::string>()] = t[1].cast<std::string>();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "http.response.body") {
|
||||
resp_body += msg["body"].cast<std::string>();
|
||||
// 💥 只在 more_body=False 时回调
|
||||
bool more = msg.contains("more_body") && msg["more_body"].cast<bool>();
|
||||
if (!more) {
|
||||
invoker(status, resp_headers, resp_body);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
submit_coro(scope, py::bytes(parser.content()), send);
|
||||
}
|
||||
|
||||
|
||||
PYBIND11_EMBEDDED_MODULE(mk_loader, m) {
|
||||
m.def("log", [](int lev, const char *file, int line, const char *func, const char *content) {
|
||||
py::gil_scoped_release release;
|
||||
LoggerWrapper::printLog(::toolkit::getLogger(), lev, file, func, line, content);
|
||||
});
|
||||
|
||||
m.def("get_config", [](const std::string &key) -> std::string {
|
||||
py::gil_scoped_release release;
|
||||
const auto it = mINI::Instance().find(key);
|
||||
if (it != mINI::Instance().end()) {
|
||||
return it->second;
|
||||
}
|
||||
return "";
|
||||
});
|
||||
m.def("set_config", [](const std::string &key, const std::string &value) -> bool {
|
||||
py::gil_scoped_release release;
|
||||
mINI::Instance()[key]= value;
|
||||
return true;
|
||||
});
|
||||
m.def("update_config", []() {
|
||||
NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig);
|
||||
mINI::Instance().dumpFile(g_ini_file);
|
||||
return true;
|
||||
});
|
||||
|
||||
m.def("publish_auth_invoker_do", [](const py::capsule &cap, const std::string &err, const py::dict &opt) {
|
||||
ProtocolOption option;
|
||||
option.load(to_native(opt));
|
||||
// 执行c++代码时释放gil锁
|
||||
py::gil_scoped_release release;
|
||||
auto &invoker = to_native<Broadcast::PublishAuthInvoker>(cap);
|
||||
invoker(err, option);
|
||||
});
|
||||
m.def("auth_invoker_do", [](const py::capsule &cap, const std::string &err) {
|
||||
// 执行c++代码时释放gil锁
|
||||
py::gil_scoped_release release;
|
||||
auto &invoker = to_native<Broadcast::AuthInvoker>(cap);
|
||||
invoker(err);
|
||||
});
|
||||
|
||||
m.def("set_fastapi", [](const py::object &check_route, const py::object &submit_coro) {
|
||||
static void *fastapi_tag = nullptr;
|
||||
NoticeCenter::Instance().delListener(&fastapi_tag, Broadcast::kBroadcastHttpRequest);
|
||||
NoticeCenter::Instance().addListener(&fastapi_tag, Broadcast::kBroadcastHttpRequest, [check_route, submit_coro](BroadcastHttpRequestArgs) {
|
||||
handle_http_request(check_route, submit_coro, parser, invoker, consumed, sender);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
inline bool set_env(const char *name, const char *value) {
|
||||
#if defined(_WIN32)
|
||||
std::string env_str = std::string(name) + "=" + value;
|
||||
return _putenv(env_str.c_str()) == 0;
|
||||
#else
|
||||
return setenv(name, value, 1) == 0; // overwrite = 1
|
||||
#endif
|
||||
}
|
||||
|
||||
bool set_python_path() {
|
||||
const char *env_var = std::getenv("PYTHONPATH");
|
||||
if (env_var && *env_var) {
|
||||
PrintI("PYTHONPATH is already set to: %s", env_var);
|
||||
return false;
|
||||
}
|
||||
auto default_path = exeDir() + "/python";
|
||||
// 1 表示覆盖已存在的值
|
||||
if (!set_env("PYTHONPATH", default_path.data())) {
|
||||
PrintW("Failed to set PYTHONPATH");
|
||||
return false;
|
||||
}
|
||||
PrintI("PYTHONPATH was not set. Set to default: %s", default_path.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
PythonInvoker &PythonInvoker::Instance() {
|
||||
static std::shared_ptr<PythonInvoker> instance(new PythonInvoker);
|
||||
return *instance;
|
||||
}
|
||||
|
||||
PythonInvoker::PythonInvoker() {
|
||||
// 确保日志一直可用
|
||||
_logger = Logger::Instance().shared_from_this();
|
||||
set_python_path(); // 确保 PYTHONPATH 在第一次调用时设置
|
||||
_interpreter = new py::scoped_interpreter;
|
||||
_rel = new py::gil_scoped_release;
|
||||
|
||||
NoticeCenter::Instance().addListener(this, Broadcast::kBroadcastReloadConfig, [this] (BroadcastReloadConfigArgs) {
|
||||
py::gil_scoped_acquire guard;
|
||||
if (_on_reload_config) {
|
||||
_on_reload_config();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
PythonInvoker::~PythonInvoker() {
|
||||
NoticeCenter::Instance().delListener(this, Broadcast::kBroadcastReloadConfig);
|
||||
{
|
||||
py::gil_scoped_acquire gil; // 加锁
|
||||
if (_on_exit) {
|
||||
_on_exit();
|
||||
}
|
||||
_on_exit = py::object();
|
||||
_on_publish = py::object();
|
||||
_module = py::module();
|
||||
}
|
||||
|
||||
delete _rel;
|
||||
delete _interpreter;
|
||||
}
|
||||
|
||||
void PythonInvoker::load(const std::string &module_name) {
|
||||
try {
|
||||
py::gil_scoped_acquire gil; // 加锁
|
||||
_module = py::module::import(module_name.c_str());
|
||||
if (hasattr(_module, "on_exit")) {
|
||||
_on_exit = _module.attr("on_exit");
|
||||
}
|
||||
if (hasattr(_module, "on_publish")) {
|
||||
_on_publish = _module.attr("on_publish");
|
||||
}
|
||||
if (hasattr(_module, "on_play")) {
|
||||
_on_play = _module.attr("on_play");
|
||||
}
|
||||
if (hasattr(_module, "on_flow_report")) {
|
||||
_on_flow_report = _module.attr("on_flow_report");
|
||||
}
|
||||
if (hasattr(_module, "on_reload_config")) {
|
||||
_on_reload_config = _module.attr("on_reload_config");
|
||||
}
|
||||
if (hasattr(_module, "on_start")) {
|
||||
py::object on_start = _module.attr("on_start");
|
||||
if (on_start) {
|
||||
on_start();
|
||||
}
|
||||
}
|
||||
} catch (py::error_already_set &e) {
|
||||
PrintE("Python exception:%s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool PythonInvoker::on_publish(BroadcastMediaPublishArgs) const {
|
||||
py::gil_scoped_acquire gil; // 确保在 Python 调用期间持有 GIL
|
||||
if (!_on_publish) {
|
||||
return false;
|
||||
}
|
||||
return _on_publish(getOriginTypeString(type), to_python(args), to_python(invoker), to_python(sender)).cast<bool>();
|
||||
}
|
||||
|
||||
bool PythonInvoker::on_play(BroadcastMediaPlayedArgs) const {
|
||||
py::gil_scoped_acquire gil; // 确保在 Python 调用期间持有 GIL
|
||||
if (!_on_play) {
|
||||
return false;
|
||||
}
|
||||
return _on_play(to_python(args), to_python(invoker), to_python(sender)).cast<bool>();
|
||||
}
|
||||
|
||||
bool PythonInvoker::on_flow_report(BroadcastFlowReportArgs) const {
|
||||
py::gil_scoped_acquire gil; // 确保在 Python 调用期间持有 GIL
|
||||
if (!_on_flow_report) {
|
||||
return false;
|
||||
}
|
||||
return _on_flow_report(to_python(args), totalBytes, totalDuration, isPlayer, to_python(sender)).cast<bool>();
|
||||
}
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
#endif
|
||||
54
server/pyinvoker.h
Normal file
54
server/pyinvoker.h
Normal file
@ -0,0 +1,54 @@
|
||||
|
||||
#ifndef PYINVOKER_H
|
||||
#define PYINVOKER_H
|
||||
|
||||
#if defined(ENABLE_PYTHON)
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <pybind11/embed.h>
|
||||
#include <pybind11/numpy.h>
|
||||
#include "Util/logger.h"
|
||||
#include "Common/config.h"
|
||||
#include "Common/MediaSource.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class PythonInvoker : public std::enable_shared_from_this<PythonInvoker>{
|
||||
public:
|
||||
~PythonInvoker();
|
||||
|
||||
static PythonInvoker& Instance();
|
||||
|
||||
void load(const std::string &module_name);
|
||||
bool on_publish(BroadcastMediaPublishArgs) const;
|
||||
bool on_play(BroadcastMediaPlayedArgs) const;
|
||||
bool on_flow_report(BroadcastFlowReportArgs) const;
|
||||
|
||||
private:
|
||||
PythonInvoker();
|
||||
|
||||
private:
|
||||
py::gil_scoped_release *_rel;
|
||||
py::scoped_interpreter *_interpreter;
|
||||
std::shared_ptr<toolkit::Logger> _logger;
|
||||
py::module _module;
|
||||
|
||||
// 程序退出
|
||||
py::object _on_exit;
|
||||
// 推流鉴权
|
||||
py::object _on_publish;
|
||||
// 播放鉴权
|
||||
py::object _on_play;
|
||||
// 流量汇报接口
|
||||
py::object _on_flow_report;
|
||||
// 配置文件热更新回调
|
||||
py::object _on_reload_config;
|
||||
};
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
#endif
|
||||
#endif // PYINVOKER_H
|
||||
@ -55,31 +55,31 @@ extern const std::string kBroadcastRecordTs;
|
||||
// 收到http api请求广播 [AUTO-TRANSLATED:c72e7c3f]
|
||||
// Broadcast for receiving http api request
|
||||
extern const std::string kBroadcastHttpRequest;
|
||||
#define BroadcastHttpRequestArgs const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, bool &consumed, SockInfo &sender
|
||||
#define BroadcastHttpRequestArgs const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, bool &consumed, toolkit::SockInfo &sender
|
||||
|
||||
// 在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限 [AUTO-TRANSLATED:2de426b4]
|
||||
// In the http file server, broadcast for receiving http access to files or directories. Control access permissions to the http directory through this event.
|
||||
extern const std::string kBroadcastHttpAccess;
|
||||
#define BroadcastHttpAccessArgs const Parser &parser, const std::string &path, const bool &is_dir, const HttpSession::HttpAccessPathInvoker &invoker, SockInfo &sender
|
||||
#define BroadcastHttpAccessArgs const Parser &parser, const std::string &path, const bool &is_dir, const HttpSession::HttpAccessPathInvoker &invoker, toolkit::SockInfo &sender
|
||||
|
||||
// 在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射 [AUTO-TRANSLATED:0294d0c5]
|
||||
// In the http file server, broadcast before receiving http access to files or directories. Control the mapping from http url to file path through this event.
|
||||
// 在该事件中通过自行覆盖path参数,可以做到譬如根据虚拟主机或者app选择不同http根目录的目的 [AUTO-TRANSLATED:1bea3efb]
|
||||
// By overriding the path parameter in this event, you can achieve the purpose of selecting different http root directories based on virtual hosts or apps.
|
||||
extern const std::string kBroadcastHttpBeforeAccess;
|
||||
#define BroadcastHttpBeforeAccessArgs const Parser &parser, std::string &path, SockInfo &sender
|
||||
#define BroadcastHttpBeforeAccessArgs const Parser &parser, std::string &path, toolkit::SockInfo &sender
|
||||
|
||||
// 该流是否需要认证?是的话调用invoker并传入realm,否则传入空的realm.如果该事件不监听则不认证 [AUTO-TRANSLATED:5f436d8f]
|
||||
// Does this stream need authentication? If yes, call invoker and pass in realm, otherwise pass in an empty realm. If this event is not listened to, no authentication will be performed.
|
||||
extern const std::string kBroadcastOnGetRtspRealm;
|
||||
#define BroadcastOnGetRtspRealmArgs const MediaInfo &args, const RtspSession::onGetRealm &invoker, SockInfo &sender
|
||||
#define BroadcastOnGetRtspRealmArgs const MediaInfo &args, const RtspSession::onGetRealm &invoker, toolkit::SockInfo &sender
|
||||
|
||||
// 请求认证用户密码事件,user_name为用户名,must_no_encrypt如果为true,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败 [AUTO-TRANSLATED:22b6dfcc]
|
||||
// Request authentication user password event, user_name is the username, must_no_encrypt if true, then the plaintext password must be provided (because it is base64 authentication method at this time), otherwise it will lead to authentication failure.
|
||||
// 获取到密码后请调用invoker并输入对应类型的密码和密码类型,invoker执行时会匹配密码 [AUTO-TRANSLATED:8c57fd43]
|
||||
// After getting the password, please call invoker and input the corresponding type of password and password type. The invoker will match the password when executing.
|
||||
extern const std::string kBroadcastOnRtspAuth;
|
||||
#define BroadcastOnRtspAuthArgs const MediaInfo &args, const std::string &realm, const std::string &user_name, const bool &must_no_encrypt, const RtspSession::onAuth &invoker, SockInfo &sender
|
||||
#define BroadcastOnRtspAuthArgs const MediaInfo &args, const std::string &realm, const std::string &user_name, const bool &must_no_encrypt, const RtspSession::onAuth &invoker, toolkit::SockInfo &sender
|
||||
|
||||
// 推流鉴权结果回调对象 [AUTO-TRANSLATED:7e508ed1]
|
||||
// Push stream authentication result callback object
|
||||
@ -90,7 +90,7 @@ using PublishAuthInvoker = std::function<void(const std::string &err, const Prot
|
||||
// 收到rtsp/rtmp推流事件广播,通过该事件控制推流鉴权 [AUTO-TRANSLATED:72417373]
|
||||
// Broadcast for receiving rtsp/rtmp push stream event. Control push stream authentication through this event.
|
||||
extern const std::string kBroadcastMediaPublish;
|
||||
#define BroadcastMediaPublishArgs const MediaOriginType &type, const MediaInfo &args, const Broadcast::PublishAuthInvoker &invoker, SockInfo &sender
|
||||
#define BroadcastMediaPublishArgs const MediaOriginType &type, const MediaInfo &args, const Broadcast::PublishAuthInvoker &invoker, toolkit::SockInfo &sender
|
||||
|
||||
// 播放鉴权结果回调对象 [AUTO-TRANSLATED:c980162b]
|
||||
// Playback authentication result callback object
|
||||
@ -101,22 +101,22 @@ using AuthInvoker = std::function<void(const std::string &err)>;
|
||||
// 播放rtsp/rtmp/http-flv事件广播,通过该事件控制播放鉴权 [AUTO-TRANSLATED:eddd7014]
|
||||
// Broadcast for playing rtsp/rtmp/http-flv events. Control playback authentication through this event.
|
||||
extern const std::string kBroadcastMediaPlayed;
|
||||
#define BroadcastMediaPlayedArgs const MediaInfo &args, const Broadcast::AuthInvoker &invoker, SockInfo &sender
|
||||
#define BroadcastMediaPlayedArgs const MediaInfo &args, const Broadcast::AuthInvoker &invoker, toolkit::SockInfo &sender
|
||||
|
||||
// shell登录鉴权 [AUTO-TRANSLATED:26b135d4]
|
||||
// Shell login authentication
|
||||
extern const std::string kBroadcastShellLogin;
|
||||
#define BroadcastShellLoginArgs const std::string &user_name, const std::string &passwd, const Broadcast::AuthInvoker &invoker, SockInfo &sender
|
||||
#define BroadcastShellLoginArgs const std::string &user_name, const std::string &passwd, const Broadcast::AuthInvoker &invoker, toolkit::SockInfo &sender
|
||||
|
||||
// 停止rtsp/rtmp/http-flv会话后流量汇报事件广播 [AUTO-TRANSLATED:69df61d8]
|
||||
// Broadcast for traffic reporting event after stopping rtsp/rtmp/http-flv session
|
||||
extern const std::string kBroadcastFlowReport;
|
||||
#define BroadcastFlowReportArgs const MediaInfo &args, const uint64_t &totalBytes, const uint64_t &totalDuration, const bool &isPlayer, SockInfo &sender
|
||||
#define BroadcastFlowReportArgs const MediaInfo &args, const uint64_t &totalBytes, const uint64_t &totalDuration, const bool &isPlayer, toolkit::SockInfo &sender
|
||||
|
||||
// 未找到流后会广播该事件,请在监听该事件后去拉流或其他方式产生流,这样就能按需拉流了 [AUTO-TRANSLATED:0c00171d]
|
||||
// This event will be broadcast after the stream is not found. Please pull the stream or other methods to generate the stream after listening to this event, so that you can pull the stream on demand.
|
||||
extern const std::string kBroadcastNotFoundStream;
|
||||
#define BroadcastNotFoundStreamArgs const MediaInfo &args, SockInfo &sender, const std::function<void()> &closePlayer
|
||||
#define BroadcastNotFoundStreamArgs const MediaInfo &args, toolkit::SockInfo &sender, const std::function<void()> &closePlayer
|
||||
|
||||
// 某个流无人消费时触发,目的为了实现无人观看时主动断开拉流等业务逻辑 [AUTO-TRANSLATED:3c45f002]
|
||||
// Triggered when a stream is not consumed by anyone. The purpose is to achieve business logic such as actively disconnecting the pull stream when no one is watching.
|
||||
|
||||
@ -61,6 +61,7 @@ ssize_t HttpSession::onRecvHeader(const char *header, size_t len) {
|
||||
static onceToken token([]() {
|
||||
s_func_map.emplace("GET", &HttpSession::onHttpRequest_GET);
|
||||
s_func_map.emplace("POST", &HttpSession::onHttpRequest_POST);
|
||||
s_func_map.emplace("PUT", &HttpSession::onHttpRequest_POST);
|
||||
// DELETE命令用于whip/whep用,只用于触发http api [AUTO-TRANSLATED:f3b7aaea]
|
||||
// DELETE command is used for whip/whep, only used to trigger http api
|
||||
s_func_map.emplace("DELETE", &HttpSession::onHttpRequest_POST);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user