增加hls的http服务
This commit is contained in:
parent
d7c944d898
commit
0cc31ca9b9
@ -305,6 +305,29 @@ set_target_properties(storage PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${RK_PLUGIN_OUTPUT_DIR}
|
||||
)
|
||||
|
||||
# zlm_http plugin (embedded HTTP file server via ZLMediaKit)
|
||||
if(RK3588_ENABLE_ZLMEDIAKIT AND RK_ZLMK_API_LIB)
|
||||
add_library(zlm_http SHARED zlm_http/zlm_http_node.cpp)
|
||||
target_include_directories(zlm_http PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/third_party)
|
||||
target_compile_definitions(zlm_http PRIVATE RK3588_ENABLE_ZLMEDIAKIT)
|
||||
target_include_directories(zlm_http PRIVATE ${RK_ZLMEDIAKIT_INCLUDE_DIR})
|
||||
target_link_libraries(zlm_http PRIVATE project_options Threads::Threads ${RK_ZLMK_API_LIB})
|
||||
set_target_properties(zlm_http PROPERTIES
|
||||
OUTPUT_NAME "zlm_http"
|
||||
LIBRARY_OUTPUT_DIRECTORY ${RK_PLUGIN_OUTPUT_DIR}
|
||||
RUNTIME_OUTPUT_DIRECTORY ${RK_PLUGIN_OUTPUT_DIR}
|
||||
BUILD_RPATH "\$ORIGIN"
|
||||
INSTALL_RPATH "\$ORIGIN"
|
||||
)
|
||||
add_custom_command(TARGET zlm_http POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${RK_ZLMK_API_LIB}" $<TARGET_FILE_DIR:zlm_http>
|
||||
)
|
||||
install(TARGETS zlm_http
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/rk3588-media-server/plugins
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/rk3588-media-server/plugins
|
||||
)
|
||||
endif()
|
||||
|
||||
install(TARGETS input_rtsp input_file publish preprocess ai_yolo osd alarm storage ai_scheduler
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/rk3588-media-server/plugins
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/rk3588-media-server/plugins
|
||||
|
||||
250
plugins/zlm_http/zlm_http_node.cpp
Normal file
250
plugins/zlm_http/zlm_http_node.cpp
Normal file
@ -0,0 +1,250 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "node.h"
|
||||
|
||||
#if defined(RK3588_ENABLE_ZLMEDIAKIT)
|
||||
extern "C" {
|
||||
#include "mk_events.h"
|
||||
#include "mk_mediakit.h"
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace rk3588 {
|
||||
|
||||
#if defined(RK3588_ENABLE_ZLMEDIAKIT)
|
||||
namespace {
|
||||
|
||||
struct HttpServeState {
|
||||
std::mutex mu;
|
||||
std::string root;
|
||||
std::string prefix;
|
||||
uint16_t port = 0;
|
||||
bool started = false;
|
||||
};
|
||||
|
||||
HttpServeState& GState() {
|
||||
static HttpServeState st;
|
||||
return st;
|
||||
}
|
||||
|
||||
static inline bool StartsWith(const std::string& s, const std::string& prefix) {
|
||||
return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin());
|
||||
}
|
||||
|
||||
static inline std::string TrimLeadingSlash(std::string s) {
|
||||
while (!s.empty() && s.front() == '/') s.erase(s.begin());
|
||||
return s;
|
||||
}
|
||||
|
||||
static inline bool HasPathTraversal(const std::string& rel) {
|
||||
// Minimal hardening: reject obvious traversal and Windows drive/UNC paths.
|
||||
if (rel.find("..") != std::string::npos) return true;
|
||||
if (rel.find('\\') != std::string::npos) return true;
|
||||
if (rel.find(':') != std::string::npos) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline const char* ContentTypeForPath(const std::filesystem::path& p) {
|
||||
auto ext = p.extension().string();
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return (char)std::tolower(c); });
|
||||
if (ext == ".m3u8") return "application/vnd.apple.mpegurl";
|
||||
if (ext == ".ts") return "video/mp2t";
|
||||
if (ext == ".mp4") return "video/mp4";
|
||||
if (ext == ".m4s") return "video/iso.segment";
|
||||
if (ext == ".mpd") return "application/dash+xml";
|
||||
if (ext == ".vtt") return "text/vtt";
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
void API_CALL OnHttpRequest(const mk_parser parser,
|
||||
const mk_http_response_invoker invoker,
|
||||
int* consumed,
|
||||
const mk_sock_info /*sender*/) {
|
||||
if (!parser || !invoker || !consumed) return;
|
||||
|
||||
const char* method_c = mk_parser_get_method(parser);
|
||||
const char* url_c = mk_parser_get_url(parser);
|
||||
if (!method_c || !url_c) return;
|
||||
|
||||
const std::string method(method_c);
|
||||
const std::string url(url_c);
|
||||
|
||||
std::string root;
|
||||
std::string prefix;
|
||||
{
|
||||
auto& st = GState();
|
||||
std::lock_guard<std::mutex> lock(st.mu);
|
||||
root = st.root;
|
||||
prefix = st.prefix;
|
||||
}
|
||||
if (root.empty()) return;
|
||||
|
||||
std::string rel_url = url;
|
||||
if (!prefix.empty() && prefix != "/") {
|
||||
if (!StartsWith(rel_url, prefix)) return;
|
||||
rel_url.erase(0, prefix.size());
|
||||
if (rel_url.empty()) rel_url = "/";
|
||||
}
|
||||
rel_url = TrimLeadingSlash(rel_url);
|
||||
if (rel_url.empty()) return;
|
||||
if (HasPathTraversal(rel_url)) {
|
||||
*consumed = 1;
|
||||
const char* hdr[] = {
|
||||
"Access-Control-Allow-Origin", "*",
|
||||
"Cache-Control", "no-cache",
|
||||
nullptr
|
||||
};
|
||||
mk_http_response_invoker_do_string(invoker, 403, hdr, "Forbidden");
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::path full = std::filesystem::path(root) / std::filesystem::path(rel_url);
|
||||
std::error_code ec;
|
||||
if (std::filesystem::is_directory(full, ec)) {
|
||||
full /= "index.m3u8";
|
||||
}
|
||||
|
||||
if (ec || !std::filesystem::exists(full, ec) || ec || !std::filesystem::is_regular_file(full, ec) || ec) {
|
||||
return; // fallthrough to ZLMediaKit default handlers
|
||||
}
|
||||
|
||||
const char* ct = ContentTypeForPath(full);
|
||||
if (method == "OPTIONS") {
|
||||
*consumed = 1;
|
||||
const char* hdr[] = {
|
||||
"Access-Control-Allow-Origin", "*",
|
||||
"Access-Control-Allow-Methods", "GET, OPTIONS",
|
||||
"Access-Control-Allow-Headers", "Range, Origin, Accept, Content-Type",
|
||||
"Access-Control-Max-Age", "86400",
|
||||
"Cache-Control", "no-cache",
|
||||
nullptr
|
||||
};
|
||||
mk_http_response_invoker_do_string(invoker, 204, hdr, "");
|
||||
return;
|
||||
}
|
||||
|
||||
if (method != "GET") return;
|
||||
|
||||
*consumed = 1;
|
||||
const char* hdr[] = {
|
||||
"Content-Type", ct,
|
||||
"Access-Control-Allow-Origin", "*",
|
||||
"Cache-Control", "no-cache",
|
||||
nullptr
|
||||
};
|
||||
const std::string file_path = full.string();
|
||||
mk_http_response_invoker_do_file(invoker, parser, hdr, file_path.c_str());
|
||||
}
|
||||
|
||||
void EnsureZlmEnvOnce() {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [] {
|
||||
mk_config cfg;
|
||||
std::memset(&cfg, 0, sizeof(cfg));
|
||||
cfg.thread_num = 0;
|
||||
cfg.log_level = 2;
|
||||
cfg.log_mask = LOG_CONSOLE;
|
||||
mk_env_init(&cfg);
|
||||
});
|
||||
}
|
||||
|
||||
void EnsureHttpEventsOnce() {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [] {
|
||||
mk_events ev;
|
||||
std::memset(&ev, 0, sizeof(ev));
|
||||
ev.on_mk_http_request = &OnHttpRequest;
|
||||
mk_events_listen(&ev);
|
||||
});
|
||||
}
|
||||
|
||||
uint16_t StartHttpServerOnce(uint16_t port, bool ssl) {
|
||||
auto& st = GState();
|
||||
std::lock_guard<std::mutex> lock(st.mu);
|
||||
if (st.started) return st.port;
|
||||
auto actual = mk_http_server_start(port, ssl ? 1 : 0);
|
||||
if (actual == 0) return 0;
|
||||
st.started = true;
|
||||
st.port = actual;
|
||||
return actual;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
class ZlmHttpNode final : public INode {
|
||||
public:
|
||||
std::string Id() const override { return id_; }
|
||||
std::string Type() const override { return "zlm_http"; }
|
||||
|
||||
bool Init(const SimpleJson& config, const NodeContext& /*ctx*/) override {
|
||||
id_ = config.ValueOr<std::string>("id", "zlm_http");
|
||||
|
||||
#if !defined(RK3588_ENABLE_ZLMEDIAKIT)
|
||||
(void)config;
|
||||
std::cerr << "[zlm_http] RK3588_ENABLE_ZLMEDIAKIT is OFF at build time\n";
|
||||
return false;
|
||||
#else
|
||||
root_ = config.ValueOr<std::string>("root", "");
|
||||
if (root_.empty()) root_ = config.ValueOr<std::string>("path", "");
|
||||
prefix_ = config.ValueOr<std::string>("prefix", "/");
|
||||
port_ = static_cast<uint16_t>(std::max(0, config.ValueOr<int>("port", 8080)));
|
||||
ssl_ = config.ValueOr<bool>("ssl", false);
|
||||
|
||||
if (root_.empty()) {
|
||||
std::cerr << "[zlm_http] missing config: root (or path)\n";
|
||||
return false;
|
||||
}
|
||||
if (prefix_.empty()) prefix_ = "/";
|
||||
if (prefix_.front() != '/') prefix_ = "/" + prefix_;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Start() override {
|
||||
#if !defined(RK3588_ENABLE_ZLMEDIAKIT)
|
||||
return false;
|
||||
#else
|
||||
EnsureZlmEnvOnce();
|
||||
EnsureHttpEventsOnce();
|
||||
|
||||
{
|
||||
auto& st = GState();
|
||||
std::lock_guard<std::mutex> lock(st.mu);
|
||||
st.root = root_;
|
||||
st.prefix = prefix_;
|
||||
}
|
||||
|
||||
auto actual = StartHttpServerOnce(port_, ssl_);
|
||||
if (actual == 0) {
|
||||
std::cerr << "[zlm_http] failed to start HTTP server on port " << port_ << "\n";
|
||||
return false;
|
||||
}
|
||||
std::cout << "[zlm_http] http file server started, port=" << actual
|
||||
<< " root=" << root_ << " prefix=" << prefix_ << "\n";
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Stop() override {
|
||||
// ZLMediaKit mk_api doesn't expose per-server stop; keep it running for process lifetime.
|
||||
}
|
||||
|
||||
private:
|
||||
std::string id_;
|
||||
std::string root_;
|
||||
std::string prefix_ = "/";
|
||||
uint16_t port_ = 8080;
|
||||
bool ssl_ = false;
|
||||
};
|
||||
|
||||
REGISTER_NODE(ZlmHttpNode, "zlm_http");
|
||||
|
||||
} // namespace rk3588
|
||||
Loading…
Reference in New Issue
Block a user