2019-06-15 17:07:10 +08:00
|
|
|
|
/*
|
2020-04-04 20:30:09 +08:00
|
|
|
|
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
2019-06-11 09:25:54 +08:00
|
|
|
|
*
|
2021-01-17 18:31:50 +08:00
|
|
|
|
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
|
2019-06-11 09:25:54 +08:00
|
|
|
|
*
|
2020-04-04 20:30:09 +08:00
|
|
|
|
* Use of this source code is governed by MIT license that can be found in the
|
|
|
|
|
* LICENSE file in the root of the source tree. All contributing project authors
|
|
|
|
|
* may be found in the AUTHORS file in the root of the source tree.
|
2019-06-11 09:25:54 +08:00
|
|
|
|
*/
|
|
|
|
|
|
2020-06-10 10:33:48 +08:00
|
|
|
|
#include <sys/stat.h>
|
2020-12-06 22:36:44 +08:00
|
|
|
|
#include <math.h>
|
2019-05-20 11:22:59 +08:00
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <functional>
|
|
|
|
|
#include <unordered_map>
|
|
|
|
|
#include "Util/util.h"
|
|
|
|
|
#include "Util/logger.h"
|
|
|
|
|
#include "Util/onceToken.h"
|
|
|
|
|
#include "Util/NoticeCenter.h"
|
2019-05-21 09:26:24 +08:00
|
|
|
|
#ifdef ENABLE_MYSQL
|
2019-05-20 11:22:59 +08:00
|
|
|
|
#include "Util/SqlPool.h"
|
2019-05-21 09:26:24 +08:00
|
|
|
|
#endif //ENABLE_MYSQL
|
2019-05-20 11:22:59 +08:00
|
|
|
|
#include "Common/config.h"
|
|
|
|
|
#include "Common/MediaSource.h"
|
|
|
|
|
#include "Http/HttpRequester.h"
|
|
|
|
|
#include "Http/HttpSession.h"
|
|
|
|
|
#include "Network/TcpServer.h"
|
2021-06-08 11:29:32 +08:00
|
|
|
|
#include "Network/UdpServer.h"
|
2019-05-20 16:26:04 +08:00
|
|
|
|
#include "Player/PlayerProxy.h"
|
2021-06-16 19:40:08 +08:00
|
|
|
|
#include "Pusher/PusherProxy.h"
|
2019-06-06 18:28:33 +08:00
|
|
|
|
#include "Util/MD5.h"
|
|
|
|
|
#include "WebApi.h"
|
2020-12-06 22:36:44 +08:00
|
|
|
|
#include "WebHook.h"
|
2019-10-24 11:21:55 +08:00
|
|
|
|
#include "Thread/WorkThreadPool.h"
|
2019-12-05 19:53:55 +08:00
|
|
|
|
#include "Rtp/RtpSelector.h"
|
2019-06-15 17:07:10 +08:00
|
|
|
|
#include "FFmpegSource.h"
|
2020-07-02 22:23:43 +08:00
|
|
|
|
#if defined(ENABLE_RTPPROXY)
|
|
|
|
|
#include "Rtp/RtpServer.h"
|
|
|
|
|
#endif
|
2021-03-24 16:52:41 +08:00
|
|
|
|
#ifdef ENABLE_WEBRTC
|
2021-10-15 16:27:17 +08:00
|
|
|
|
#include "../webrtc/WebRtcPlayer.h"
|
|
|
|
|
#include "../webrtc/WebRtcPusher.h"
|
2021-10-16 10:52:28 +08:00
|
|
|
|
#include "../webrtc/WebRtcEchoTest.h"
|
2021-03-24 16:52:41 +08:00
|
|
|
|
#endif
|
2021-12-19 17:39:50 +08:00
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
#include <io.h>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <tchar.h>
|
|
|
|
|
#endif // _WIN32
|
|
|
|
|
|
2022-02-02 20:34:50 +08:00
|
|
|
|
using namespace std;
|
|
|
|
|
using namespace Json;
|
2019-05-20 11:22:59 +08:00
|
|
|
|
using namespace toolkit;
|
|
|
|
|
using namespace mediakit;
|
|
|
|
|
|
|
|
|
|
namespace API {
|
|
|
|
|
#define API_FIELD "api."
|
2019-06-24 14:51:49 +08:00
|
|
|
|
const string kApiDebug = API_FIELD"apiDebug";
|
|
|
|
|
const string kSecret = API_FIELD"secret";
|
2020-05-09 00:06:36 +08:00
|
|
|
|
const string kSnapRoot = API_FIELD"snapRoot";
|
2020-06-10 10:33:48 +08:00
|
|
|
|
const string kDefaultSnap = API_FIELD"defaultSnap";
|
2019-05-20 17:12:00 +08:00
|
|
|
|
|
2019-05-20 11:22:59 +08:00
|
|
|
|
static onceToken token([]() {
|
2019-05-20 16:26:04 +08:00
|
|
|
|
mINI::Instance()[kApiDebug] = "1";
|
2019-05-20 17:12:00 +08:00
|
|
|
|
mINI::Instance()[kSecret] = "035c73f7-bb6b-4889-a715-d9eb2d1925cc";
|
2020-05-09 00:06:36 +08:00
|
|
|
|
mINI::Instance()[kSnapRoot] = "./www/snap/";
|
2020-06-10 10:33:48 +08:00
|
|
|
|
mINI::Instance()[kDefaultSnap] = "./www/logo.png";
|
2019-05-20 11:22:59 +08:00
|
|
|
|
});
|
|
|
|
|
}//namespace API
|
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
using HttpApi = function<void(const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, SockInfo &sender)>;
|
2020-01-17 11:44:20 +08:00
|
|
|
|
//http api列表
|
2020-12-06 21:50:41 +08:00
|
|
|
|
static map<string, HttpApi> s_map_api;
|
2020-01-17 11:44:20 +08:00
|
|
|
|
|
2020-12-06 21:50:41 +08:00
|
|
|
|
static void responseApi(const Json::Value &res, const HttpSession::HttpResponseInvoker &invoker){
|
|
|
|
|
GET_CONFIG(string, charSet, Http::kCharSet);
|
|
|
|
|
HttpSession::KeyValue headerOut;
|
|
|
|
|
headerOut["Content-Type"] = string("application/json; charset=") + charSet;
|
2021-01-02 21:24:06 +08:00
|
|
|
|
invoker(200, headerOut, res.toStyledString());
|
2020-12-06 21:50:41 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void responseApi(int code, const string &msg, const HttpSession::HttpResponseInvoker &invoker){
|
|
|
|
|
Json::Value res;
|
|
|
|
|
res["code"] = code;
|
|
|
|
|
res["msg"] = msg;
|
|
|
|
|
responseApi(res, invoker);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ApiArgsType getAllArgs(const Parser &parser);
|
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
static HttpApi toApi(const function<void(API_ARGS_MAP_ASYNC)> &cb) {
|
2020-12-06 21:50:41 +08:00
|
|
|
|
return [cb](const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, SockInfo &sender) {
|
|
|
|
|
GET_CONFIG(string, charSet, Http::kCharSet);
|
|
|
|
|
HttpSession::KeyValue headerOut;
|
|
|
|
|
headerOut["Content-Type"] = string("application/json; charset=") + charSet;
|
|
|
|
|
|
|
|
|
|
Json::Value val;
|
|
|
|
|
val["code"] = API::Success;
|
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
//参数解析成map
|
2020-12-06 21:50:41 +08:00
|
|
|
|
auto args = getAllArgs(parser);
|
2021-08-12 16:07:31 +08:00
|
|
|
|
cb(sender, headerOut, HttpAllArgs<decltype(args)>(parser, args), val, invoker);
|
2020-12-06 21:50:41 +08:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
static HttpApi toApi(const function<void(API_ARGS_MAP)> &cb) {
|
|
|
|
|
return toApi([cb](API_ARGS_MAP_ASYNC) {
|
|
|
|
|
cb(API_ARGS_VALUE);
|
2021-01-02 21:24:06 +08:00
|
|
|
|
invoker(200, headerOut, val.toStyledString());
|
2020-01-17 11:44:20 +08:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
static HttpApi toApi(const function<void(API_ARGS_JSON_ASYNC)> &cb) {
|
|
|
|
|
return [cb](const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, SockInfo &sender) {
|
|
|
|
|
GET_CONFIG(string, charSet, Http::kCharSet);
|
|
|
|
|
HttpSession::KeyValue headerOut;
|
|
|
|
|
headerOut["Content-Type"] = string("application/json; charset=") + charSet;
|
|
|
|
|
|
2021-08-12 16:07:31 +08:00
|
|
|
|
Json::Value val;
|
|
|
|
|
val["code"] = API::Success;
|
2020-12-27 22:14:59 +08:00
|
|
|
|
|
|
|
|
|
if (parser["Content-Type"].find("application/json") == string::npos) {
|
|
|
|
|
throw InvalidArgsException("该接口只支持json格式的请求");
|
|
|
|
|
}
|
|
|
|
|
//参数解析成json对象然后处理
|
2021-08-12 16:07:31 +08:00
|
|
|
|
Json::Value args;
|
2020-12-27 22:14:59 +08:00
|
|
|
|
Json::Reader reader;
|
2021-08-12 16:07:31 +08:00
|
|
|
|
reader.parse(parser.Content(), args);
|
2020-12-27 22:14:59 +08:00
|
|
|
|
|
2021-08-12 16:07:31 +08:00
|
|
|
|
cb(sender, headerOut, HttpAllArgs<decltype(args)>(parser, args), val, invoker);
|
2020-12-27 22:14:59 +08:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static HttpApi toApi(const function<void(API_ARGS_JSON)> &cb) {
|
|
|
|
|
return toApi([cb](API_ARGS_JSON_ASYNC) {
|
2021-08-12 16:07:31 +08:00
|
|
|
|
cb(API_ARGS_VALUE);
|
2021-01-02 21:24:06 +08:00
|
|
|
|
invoker(200, headerOut, val.toStyledString());
|
2020-12-27 22:14:59 +08:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-31 17:15:26 +08:00
|
|
|
|
static HttpApi toApi(const function<void(API_ARGS_STRING_ASYNC)> &cb) {
|
|
|
|
|
return [cb](const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, SockInfo &sender) {
|
|
|
|
|
GET_CONFIG(string, charSet, Http::kCharSet);
|
|
|
|
|
HttpSession::KeyValue headerOut;
|
|
|
|
|
headerOut["Content-Type"] = string("application/json; charset=") + charSet;
|
|
|
|
|
|
|
|
|
|
Json::Value val;
|
|
|
|
|
val["code"] = API::Success;
|
|
|
|
|
|
2021-08-12 16:07:31 +08:00
|
|
|
|
cb(sender, headerOut, HttpAllArgs<string>(parser, (string &)parser.Content()), val, invoker);
|
2021-03-31 17:15:26 +08:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static HttpApi toApi(const function<void(API_ARGS_STRING)> &cb) {
|
|
|
|
|
return toApi([cb](API_ARGS_STRING_ASYNC) {
|
|
|
|
|
cb(API_ARGS_VALUE);
|
|
|
|
|
invoker(200, headerOut, val.toStyledString());
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
void api_regist(const string &api_path, const function<void(API_ARGS_MAP)> &func) {
|
|
|
|
|
s_map_api.emplace(api_path, toApi(func));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void api_regist(const string &api_path, const function<void(API_ARGS_MAP_ASYNC)> &func) {
|
|
|
|
|
s_map_api.emplace(api_path, toApi(func));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void api_regist(const string &api_path, const function<void(API_ARGS_JSON)> &func) {
|
|
|
|
|
s_map_api.emplace(api_path, toApi(func));
|
2020-01-17 11:44:20 +08:00
|
|
|
|
}
|
2019-05-20 11:22:59 +08:00
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
void api_regist(const string &api_path, const function<void(API_ARGS_JSON_ASYNC)> &func) {
|
|
|
|
|
s_map_api.emplace(api_path, toApi(func));
|
|
|
|
|
}
|
2020-12-06 21:50:41 +08:00
|
|
|
|
|
2021-03-31 17:15:26 +08:00
|
|
|
|
void api_regist(const string &api_path, const function<void(API_ARGS_STRING)> &func){
|
|
|
|
|
s_map_api.emplace(api_path, toApi(func));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void api_regist(const string &api_path, const function<void(API_ARGS_STRING_ASYNC)> &func){
|
|
|
|
|
s_map_api.emplace(api_path, toApi(func));
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-20 11:22:59 +08:00
|
|
|
|
//获取HTTP请求中url参数、content参数
|
2019-05-20 16:26:04 +08:00
|
|
|
|
static ApiArgsType getAllArgs(const Parser &parser) {
|
|
|
|
|
ApiArgsType allArgs;
|
2019-12-29 10:49:04 +08:00
|
|
|
|
if (parser["Content-Type"].find("application/x-www-form-urlencoded") == 0) {
|
2019-05-20 11:22:59 +08:00
|
|
|
|
auto contentArgs = parser.parseArgs(parser.Content());
|
|
|
|
|
for (auto &pr : contentArgs) {
|
2019-05-20 16:26:04 +08:00
|
|
|
|
allArgs[pr.first] = HttpSession::urlDecode(pr.second);
|
2019-05-20 11:22:59 +08:00
|
|
|
|
}
|
2019-12-29 10:49:04 +08:00
|
|
|
|
} else if (parser["Content-Type"].find("application/json") == 0) {
|
2019-05-20 16:26:04 +08:00
|
|
|
|
try {
|
|
|
|
|
stringstream ss(parser.Content());
|
|
|
|
|
Value jsonArgs;
|
|
|
|
|
ss >> jsonArgs;
|
|
|
|
|
auto keys = jsonArgs.getMemberNames();
|
2019-12-29 10:49:04 +08:00
|
|
|
|
for (auto key = keys.begin(); key != keys.end(); ++key) {
|
2019-05-20 16:26:04 +08:00
|
|
|
|
allArgs[*key] = jsonArgs[*key].asString();
|
|
|
|
|
}
|
2019-12-29 10:49:04 +08:00
|
|
|
|
} catch (std::exception &ex) {
|
2019-05-20 16:26:04 +08:00
|
|
|
|
WarnL << ex.what();
|
2019-05-20 11:22:59 +08:00
|
|
|
|
}
|
2019-12-29 10:49:04 +08:00
|
|
|
|
} else if (!parser["Content-Type"].empty()) {
|
2019-05-20 16:26:04 +08:00
|
|
|
|
WarnL << "invalid Content-Type:" << parser["Content-Type"];
|
2019-05-20 11:22:59 +08:00
|
|
|
|
}
|
2019-05-20 16:26:04 +08:00
|
|
|
|
|
2019-12-29 10:49:04 +08:00
|
|
|
|
for (auto &pr : parser.getUrlArgs()) {
|
|
|
|
|
allArgs[pr.first] = pr.second;
|
2019-05-20 16:26:04 +08:00
|
|
|
|
}
|
2020-09-21 14:32:56 +08:00
|
|
|
|
return allArgs;
|
2019-05-20 11:22:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
2021-02-21 21:28:17 +08:00
|
|
|
|
extern uint64_t getTotalMemUsage();
|
2021-12-29 20:48:15 +08:00
|
|
|
|
extern uint64_t getTotalMemBlock();
|
2021-12-27 17:40:15 +08:00
|
|
|
|
extern uint64_t getThisThreadMemUsage();
|
2021-12-29 20:48:15 +08:00
|
|
|
|
extern uint64_t getThisThreadMemBlock();
|
|
|
|
|
extern std::vector<size_t> getBlockTypeSize();
|
|
|
|
|
extern uint64_t getTotalMemBlockByType(int type);
|
|
|
|
|
extern uint64_t getThisThreadMemBlockByType(int type) ;
|
2021-02-21 21:28:17 +08:00
|
|
|
|
|
2019-05-20 11:22:59 +08:00
|
|
|
|
static inline void addHttpListener(){
|
2019-05-28 17:14:36 +08:00
|
|
|
|
GET_CONFIG(bool, api_debug, API::kApiDebug);
|
2019-05-20 11:22:59 +08:00
|
|
|
|
//注册监听kBroadcastHttpRequest事件
|
|
|
|
|
NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastHttpRequest, [](BroadcastHttpRequestArgs) {
|
|
|
|
|
auto it = s_map_api.find(parser.Url());
|
|
|
|
|
if (it == s_map_api.end()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
//该api已被消费
|
|
|
|
|
consumed = true;
|
2020-12-06 21:50:41 +08:00
|
|
|
|
|
2019-05-20 17:34:39 +08:00
|
|
|
|
if(api_debug){
|
2021-08-12 21:02:07 +08:00
|
|
|
|
auto newInvoker = [invoker, parser](int code, const HttpSession::KeyValue &headerOut, const HttpBody::Ptr &body) {
|
2019-10-28 17:23:16 +08:00
|
|
|
|
//body默认为空
|
2021-01-19 16:05:38 +08:00
|
|
|
|
ssize_t size = 0;
|
2019-10-28 17:23:16 +08:00
|
|
|
|
if (body && body->remainSize()) {
|
|
|
|
|
//有body,获取body大小
|
|
|
|
|
size = body->remainSize();
|
|
|
|
|
}
|
2019-05-20 17:34:39 +08:00
|
|
|
|
|
2022-01-06 12:34:21 +08:00
|
|
|
|
LogContextCapture log(getLogger(), LDebug, __FILE__, "http api debug", __LINE__);
|
2021-08-12 21:02:07 +08:00
|
|
|
|
log << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n";
|
|
|
|
|
log << "# header:\r\n";
|
|
|
|
|
|
|
|
|
|
for (auto &pr : parser.getHeader()) {
|
|
|
|
|
log << pr.first << " : " << pr.second << "\r\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto &content = parser.Content();
|
|
|
|
|
log << "# content:\r\n" << (content.size() > 4 * 1024 ? content.substr(0, 4 * 1024) : content) << "\r\n";
|
|
|
|
|
|
2021-08-23 11:00:20 +08:00
|
|
|
|
if (size > 0 && size < 4 * 1024) {
|
2021-08-12 21:02:07 +08:00
|
|
|
|
auto response = body->readData(size);
|
|
|
|
|
log << "# response:\r\n" << response->data() << "\r\n";
|
|
|
|
|
invoker(code, headerOut, response);
|
2020-12-06 21:50:41 +08:00
|
|
|
|
} else {
|
2021-08-12 21:02:07 +08:00
|
|
|
|
log << "# response size:" << size << "\r\n";
|
2021-01-02 21:24:06 +08:00
|
|
|
|
invoker(code, headerOut, body);
|
2019-10-28 17:23:16 +08:00
|
|
|
|
}
|
2019-05-20 17:34:39 +08:00
|
|
|
|
};
|
2020-12-06 21:50:41 +08:00
|
|
|
|
((HttpSession::HttpResponseInvoker &) invoker) = newInvoker;
|
2019-05-20 17:34:39 +08:00
|
|
|
|
}
|
2019-05-20 11:22:59 +08:00
|
|
|
|
|
2019-05-20 17:34:39 +08:00
|
|
|
|
try {
|
2020-12-06 21:50:41 +08:00
|
|
|
|
it->second(parser, invoker, sender);
|
|
|
|
|
} catch (ApiRetException &ex) {
|
|
|
|
|
responseApi(ex.code(), ex.what(), invoker);
|
2019-05-21 09:26:24 +08:00
|
|
|
|
}
|
|
|
|
|
#ifdef ENABLE_MYSQL
|
|
|
|
|
catch(SqlException &ex){
|
2020-12-06 21:50:41 +08:00
|
|
|
|
responseApi(API::SqlFailed, StrPrinter << "操作数据库失败:" << ex.what() << ":" << ex.getSql(), invoker);
|
2019-05-21 09:26:24 +08:00
|
|
|
|
}
|
|
|
|
|
#endif// ENABLE_MYSQL
|
|
|
|
|
catch (std::exception &ex) {
|
2020-12-06 21:50:41 +08:00
|
|
|
|
responseApi(API::Exception, ex.what(), invoker);
|
2019-05-20 17:34:39 +08:00
|
|
|
|
}
|
2019-05-20 11:22:59 +08:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-02 22:23:43 +08:00
|
|
|
|
//拉流代理器列表
|
2021-06-17 10:12:34 +08:00
|
|
|
|
static unordered_map<string, PlayerProxy::Ptr> s_proxyMap;
|
2019-05-20 17:03:04 +08:00
|
|
|
|
static recursive_mutex s_proxyMapMtx;
|
|
|
|
|
|
2021-06-16 19:40:08 +08:00
|
|
|
|
//推流代理器列表
|
2021-06-17 10:12:34 +08:00
|
|
|
|
static unordered_map<string, PusherProxy::Ptr> s_proxyPusherMap;
|
2021-06-16 19:40:08 +08:00
|
|
|
|
static recursive_mutex s_proxyPusherMapMtx;
|
|
|
|
|
|
2020-07-02 22:23:43 +08:00
|
|
|
|
//FFmpeg拉流代理器列表
|
2021-06-17 10:12:34 +08:00
|
|
|
|
static unordered_map<string, FFmpegSource::Ptr> s_ffmpegMap;
|
2019-06-06 18:28:33 +08:00
|
|
|
|
static recursive_mutex s_ffmpegMapMtx;
|
|
|
|
|
|
2020-07-02 22:23:43 +08:00
|
|
|
|
#if defined(ENABLE_RTPPROXY)
|
|
|
|
|
//rtp服务器列表
|
2021-08-12 16:07:31 +08:00
|
|
|
|
static unordered_map<string, RtpServer::Ptr> s_rtpServerMap;
|
|
|
|
|
static recursive_mutex s_rtpServerMapMtx;
|
2020-07-02 22:23:43 +08:00
|
|
|
|
#endif
|
|
|
|
|
|
2021-06-17 10:41:26 +08:00
|
|
|
|
static inline string getProxyKey(const string &vhost, const string &app, const string &stream) {
|
2020-07-02 22:23:43 +08:00
|
|
|
|
return vhost + "/" + app + "/" + stream;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-17 10:41:26 +08:00
|
|
|
|
static inline string getPusherKey(const string &schema, const string &vhost, const string &app, const string &stream,
|
|
|
|
|
const string &dst_url) {
|
|
|
|
|
return schema + "/" + vhost + "/" + app + "/" + stream + "/" + MD5(dst_url).hexdigest();
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-08 17:34:13 +08:00
|
|
|
|
Value makeMediaSourceJson(MediaSource &media){
|
|
|
|
|
Value item;
|
|
|
|
|
item["schema"] = media.getSchema();
|
2021-05-22 09:53:31 +08:00
|
|
|
|
item[VHOST_KEY] = media.getVhost();
|
2021-04-08 17:34:13 +08:00
|
|
|
|
item["app"] = media.getApp();
|
|
|
|
|
item["stream"] = media.getId();
|
|
|
|
|
item["createStamp"] = (Json::UInt64) media.getCreateStamp();
|
|
|
|
|
item["aliveSecond"] = (Json::UInt64) media.getAliveSecond();
|
|
|
|
|
item["bytesSpeed"] = media.getBytesSpeed();
|
|
|
|
|
item["readerCount"] = media.readerCount();
|
|
|
|
|
item["totalReaderCount"] = media.totalReaderCount();
|
|
|
|
|
item["originType"] = (int) media.getOriginType();
|
|
|
|
|
item["originTypeStr"] = getOriginTypeString(media.getOriginType());
|
|
|
|
|
item["originUrl"] = media.getOriginUrl();
|
2021-05-22 09:31:59 +08:00
|
|
|
|
item["isRecordingMP4"] = media.isRecording(Recorder::type_mp4);
|
|
|
|
|
item["isRecordingHLS"] = media.isRecording(Recorder::type_hls);
|
2021-04-08 17:34:13 +08:00
|
|
|
|
auto originSock = media.getOriginSock();
|
|
|
|
|
if (originSock) {
|
|
|
|
|
item["originSock"]["local_ip"] = originSock->get_local_ip();
|
|
|
|
|
item["originSock"]["local_port"] = originSock->get_local_port();
|
|
|
|
|
item["originSock"]["peer_ip"] = originSock->get_peer_ip();
|
|
|
|
|
item["originSock"]["peer_port"] = originSock->get_peer_port();
|
|
|
|
|
item["originSock"]["identifier"] = originSock->getIdentifier();
|
|
|
|
|
} else {
|
|
|
|
|
item["originSock"] = Json::nullValue;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-11 11:39:49 +08:00
|
|
|
|
for(auto &track : media.getTracks(false)){
|
2021-04-08 17:34:13 +08:00
|
|
|
|
Value obj;
|
|
|
|
|
auto codec_type = track->getTrackType();
|
|
|
|
|
obj["codec_id"] = track->getCodecId();
|
|
|
|
|
obj["codec_id_name"] = track->getCodecName();
|
|
|
|
|
obj["ready"] = track->ready();
|
|
|
|
|
obj["codec_type"] = codec_type;
|
|
|
|
|
switch(codec_type){
|
|
|
|
|
case TrackAudio : {
|
|
|
|
|
auto audio_track = dynamic_pointer_cast<AudioTrack>(track);
|
|
|
|
|
obj["sample_rate"] = audio_track->getAudioSampleRate();
|
|
|
|
|
obj["channels"] = audio_track->getAudioChannel();
|
|
|
|
|
obj["sample_bit"] = audio_track->getAudioSampleBit();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case TrackVideo : {
|
|
|
|
|
auto video_track = dynamic_pointer_cast<VideoTrack>(track);
|
|
|
|
|
obj["width"] = video_track->getVideoWidth();
|
|
|
|
|
obj["height"] = video_track->getVideoHeight();
|
|
|
|
|
obj["fps"] = round(video_track->getVideoFps());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
item["tracks"].append(obj);
|
|
|
|
|
}
|
|
|
|
|
return item;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-27 17:40:15 +08:00
|
|
|
|
void getStatisticJson(const function<void(Value &val)> &cb) {
|
|
|
|
|
auto obj = std::make_shared<Value>(objectValue);
|
|
|
|
|
auto &val = *obj;
|
2021-08-21 19:11:20 +08:00
|
|
|
|
val["MediaSource"] = (Json::UInt64)(ObjectStatistic<MediaSource>::count());
|
|
|
|
|
val["MultiMediaSourceMuxer"] = (Json::UInt64)(ObjectStatistic<MultiMediaSourceMuxer>::count());
|
|
|
|
|
|
|
|
|
|
val["TcpServer"] = (Json::UInt64)(ObjectStatistic<TcpServer>::count());
|
|
|
|
|
val["TcpSession"] = (Json::UInt64)(ObjectStatistic<TcpSession>::count());
|
|
|
|
|
val["UdpServer"] = (Json::UInt64)(ObjectStatistic<UdpServer>::count());
|
|
|
|
|
val["UdpSession"] = (Json::UInt64)(ObjectStatistic<UdpSession>::count());
|
|
|
|
|
val["TcpClient"] = (Json::UInt64)(ObjectStatistic<TcpClient>::count());
|
|
|
|
|
val["Socket"] = (Json::UInt64)(ObjectStatistic<Socket>::count());
|
|
|
|
|
|
|
|
|
|
val["FrameImp"] = (Json::UInt64)(ObjectStatistic<FrameImp>::count());
|
|
|
|
|
val["Frame"] = (Json::UInt64)(ObjectStatistic<Frame>::count());
|
|
|
|
|
|
|
|
|
|
val["Buffer"] = (Json::UInt64)(ObjectStatistic<Buffer>::count());
|
|
|
|
|
val["BufferRaw"] = (Json::UInt64)(ObjectStatistic<BufferRaw>::count());
|
|
|
|
|
val["BufferLikeString"] = (Json::UInt64)(ObjectStatistic<BufferLikeString>::count());
|
|
|
|
|
val["BufferList"] = (Json::UInt64)(ObjectStatistic<BufferList>::count());
|
|
|
|
|
|
|
|
|
|
val["RtpPacket"] = (Json::UInt64)(ObjectStatistic<RtpPacket>::count());
|
|
|
|
|
val["RtmpPacket"] = (Json::UInt64)(ObjectStatistic<RtmpPacket>::count());
|
|
|
|
|
#ifdef ENABLE_MEM_DEBUG
|
|
|
|
|
auto bytes = getTotalMemUsage();
|
2021-12-29 20:48:15 +08:00
|
|
|
|
val["totalMemUsage"] = (Json::UInt64) bytes;
|
|
|
|
|
val["totalMemUsageMB"] = (int) (bytes / 1024 / 1024);
|
|
|
|
|
val["totalMemBlock"] = (Json::UInt64) getTotalMemBlock();
|
|
|
|
|
static auto block_type_size = getBlockTypeSize();
|
|
|
|
|
{
|
|
|
|
|
int i = 0;
|
|
|
|
|
string str;
|
|
|
|
|
size_t last = 0;
|
|
|
|
|
for (auto sz : block_type_size) {
|
|
|
|
|
str.append(to_string(last) + "~" + to_string(sz) + ":" + to_string(getTotalMemBlockByType(i++)) + ";");
|
|
|
|
|
last = sz;
|
|
|
|
|
}
|
|
|
|
|
str.pop_back();
|
|
|
|
|
val["totalMemBlockTypeCount"] = str;
|
|
|
|
|
}
|
2021-12-27 17:40:15 +08:00
|
|
|
|
|
|
|
|
|
auto thread_size = EventPollerPool::Instance().getExecutorSize() + WorkThreadPool::Instance().getExecutorSize();
|
|
|
|
|
std::shared_ptr<vector<Value> > thread_mem_info = std::make_shared<vector<Value> >(thread_size);
|
|
|
|
|
|
2021-12-29 20:48:15 +08:00
|
|
|
|
shared_ptr<void> finished(nullptr, [thread_mem_info, cb, obj](void *) {
|
2021-12-27 17:40:15 +08:00
|
|
|
|
for (auto &val : *thread_mem_info) {
|
|
|
|
|
(*obj)["threadMem"].append(val);
|
|
|
|
|
}
|
|
|
|
|
//触发回调
|
|
|
|
|
cb(*obj);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
auto pos = 0;
|
2021-12-29 20:48:15 +08:00
|
|
|
|
auto lam0 = [&](TaskExecutor &executor) {
|
2021-12-27 17:40:15 +08:00
|
|
|
|
auto &val = (*thread_mem_info)[pos++];
|
2021-12-29 20:48:15 +08:00
|
|
|
|
executor.async([finished, &val]() {
|
2021-12-27 17:40:15 +08:00
|
|
|
|
auto bytes = getThisThreadMemUsage();
|
|
|
|
|
val["threadName"] = getThreadName();
|
|
|
|
|
val["threadMemUsage"] = (Json::UInt64) bytes;
|
2021-12-29 20:48:15 +08:00
|
|
|
|
val["threadMemUsageMB"] = (Json::UInt64) (bytes / 1024 / 1024);
|
|
|
|
|
val["threadMemBlock"] = (Json::UInt64) getThisThreadMemBlock();
|
|
|
|
|
{
|
|
|
|
|
int i = 0;
|
|
|
|
|
string str;
|
|
|
|
|
size_t last = 0;
|
|
|
|
|
for (auto sz : block_type_size) {
|
|
|
|
|
str.append(to_string(last) + "~" + to_string(sz) + ":" + to_string(getThisThreadMemBlockByType(i++)) + ";");
|
|
|
|
|
last = sz;
|
|
|
|
|
}
|
|
|
|
|
str.pop_back();
|
|
|
|
|
val["threadMemBlockTypeCount"] = str;
|
|
|
|
|
}
|
2021-12-27 17:40:15 +08:00
|
|
|
|
});
|
|
|
|
|
};
|
2021-12-29 20:48:15 +08:00
|
|
|
|
auto lam1 = [lam0](const TaskExecutor::Ptr &executor) {
|
|
|
|
|
lam0(*executor);
|
|
|
|
|
};
|
|
|
|
|
EventPollerPool::Instance().for_each(lam1);
|
|
|
|
|
WorkThreadPool::Instance().for_each(lam1);
|
2021-12-27 17:40:15 +08:00
|
|
|
|
#else
|
|
|
|
|
cb(*obj);
|
2021-08-21 19:11:20 +08:00
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-12 16:45:47 +08:00
|
|
|
|
void addStreamProxy(const string &vhost, const string &app, const string &stream, const string &url, int retry_count,
|
|
|
|
|
bool enable_hls, bool enable_mp4, int rtp_type, float timeout_sec,
|
|
|
|
|
const function<void(const SockException &ex, const string &key)> &cb) {
|
|
|
|
|
auto key = getProxyKey(vhost, app, stream);
|
|
|
|
|
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
|
|
|
|
if (s_proxyMap.find(key) != s_proxyMap.end()) {
|
|
|
|
|
//已经在拉流了
|
|
|
|
|
cb(SockException(Err_success), key);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
//添加拉流代理
|
|
|
|
|
auto player = std::make_shared<PlayerProxy>(vhost, app, stream, enable_hls, enable_mp4, retry_count ? retry_count : -1);
|
|
|
|
|
s_proxyMap[key] = player;
|
|
|
|
|
|
|
|
|
|
//指定RTP over TCP(播放rtsp时有效)
|
2022-02-02 20:34:50 +08:00
|
|
|
|
(*player)[Client::kRtpType] = rtp_type;
|
2022-01-12 16:45:47 +08:00
|
|
|
|
|
|
|
|
|
if (timeout_sec > 0.1) {
|
|
|
|
|
//播放握手超时时间
|
2022-02-02 20:34:50 +08:00
|
|
|
|
(*player)[Client::kTimeoutMS] = timeout_sec * 1000;
|
2022-01-12 16:45:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//开始播放,如果播放失败或者播放中止,将会自动重试若干次,默认一直重试
|
|
|
|
|
player->setPlayCallbackOnce([cb, key](const SockException &ex) {
|
|
|
|
|
if (ex) {
|
|
|
|
|
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
|
|
|
|
s_proxyMap.erase(key);
|
|
|
|
|
}
|
|
|
|
|
cb(ex, key);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//被主动关闭拉流
|
|
|
|
|
player->setOnClose([key](const SockException &ex) {
|
|
|
|
|
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
|
|
|
|
s_proxyMap.erase(key);
|
|
|
|
|
});
|
|
|
|
|
player->play(url);
|
|
|
|
|
};
|
|
|
|
|
|
2019-05-27 15:09:44 +08:00
|
|
|
|
/**
|
|
|
|
|
* 安装api接口
|
|
|
|
|
* 所有api都支持GET和POST两种方式
|
|
|
|
|
* POST方式参数支持application/json和application/x-www-form-urlencoded方式
|
|
|
|
|
*/
|
2019-05-20 11:22:59 +08:00
|
|
|
|
void installWebApi() {
|
|
|
|
|
addHttpListener();
|
2019-05-28 17:14:36 +08:00
|
|
|
|
GET_CONFIG(string,api_secret,API::kSecret);
|
2019-05-20 17:12:00 +08:00
|
|
|
|
|
2019-05-27 15:09:44 +08:00
|
|
|
|
//获取线程负载
|
|
|
|
|
//测试url http://127.0.0.1/index/api/getThreadsLoad
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/getThreadsLoad",[](API_ARGS_MAP_ASYNC){
|
2019-05-20 11:22:59 +08:00
|
|
|
|
EventPollerPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
|
|
|
|
|
Value val;
|
|
|
|
|
auto vec = EventPollerPool::Instance().getExecutorLoad();
|
2019-05-28 09:38:15 +08:00
|
|
|
|
int i = API::Success;
|
2019-05-20 11:22:59 +08:00
|
|
|
|
for (auto load : vec) {
|
|
|
|
|
Value obj(objectValue);
|
|
|
|
|
obj["load"] = load;
|
2019-10-24 11:21:55 +08:00
|
|
|
|
obj["delay"] = vecDelay[i++];
|
|
|
|
|
val["data"].append(obj);
|
|
|
|
|
}
|
2019-11-13 14:33:19 +08:00
|
|
|
|
val["code"] = API::Success;
|
2021-01-02 21:24:06 +08:00
|
|
|
|
invoker(200, headerOut, val.toStyledString());
|
2019-10-24 11:21:55 +08:00
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//获取后台工作线程负载
|
|
|
|
|
//测试url http://127.0.0.1/index/api/getWorkThreadsLoad
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC){
|
2019-10-24 11:21:55 +08:00
|
|
|
|
WorkThreadPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
|
|
|
|
|
Value val;
|
|
|
|
|
auto vec = WorkThreadPool::Instance().getExecutorLoad();
|
|
|
|
|
int i = 0;
|
|
|
|
|
for (auto load : vec) {
|
|
|
|
|
Value obj(objectValue);
|
|
|
|
|
obj["load"] = load;
|
2019-05-20 11:22:59 +08:00
|
|
|
|
obj["delay"] = vecDelay[i++];
|
|
|
|
|
val["data"].append(obj);
|
|
|
|
|
}
|
2019-11-13 14:33:19 +08:00
|
|
|
|
val["code"] = API::Success;
|
2021-01-02 21:24:06 +08:00
|
|
|
|
invoker(200, headerOut, val.toStyledString());
|
2019-05-20 11:22:59 +08:00
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2019-05-27 15:09:44 +08:00
|
|
|
|
//获取服务器配置
|
|
|
|
|
//测试url http://127.0.0.1/index/api/getServerConfig
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/getServerConfig",[](API_ARGS_MAP){
|
2019-05-20 17:12:00 +08:00
|
|
|
|
CHECK_SECRET();
|
2019-05-20 11:22:59 +08:00
|
|
|
|
Value obj;
|
|
|
|
|
for (auto &pr : mINI::Instance()) {
|
|
|
|
|
obj[pr.first] = (string &) pr.second;
|
|
|
|
|
}
|
|
|
|
|
val["data"].append(obj);
|
|
|
|
|
});
|
|
|
|
|
|
2019-05-27 15:09:44 +08:00
|
|
|
|
//设置服务器配置
|
|
|
|
|
//测试url(比如关闭http api调试) http://127.0.0.1/index/api/setServerConfig?api.apiDebug=0
|
|
|
|
|
//你也可以通过http post方式传参,可以通过application/x-www-form-urlencoded或application/json方式传参
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/setServerConfig",[](API_ARGS_MAP){
|
2019-05-20 17:12:00 +08:00
|
|
|
|
CHECK_SECRET();
|
2019-05-20 11:22:59 +08:00
|
|
|
|
auto &ini = mINI::Instance();
|
2019-05-28 09:38:15 +08:00
|
|
|
|
int changed = API::Success;
|
2021-08-12 16:07:31 +08:00
|
|
|
|
for (auto &pr : allArgs.getArgs()) {
|
2019-05-20 11:22:59 +08:00
|
|
|
|
if (ini.find(pr.first) == ini.end()) {
|
2021-06-08 14:49:32 +08:00
|
|
|
|
#if 1
|
2019-05-20 11:22:59 +08:00
|
|
|
|
//没有这个key
|
2021-06-08 14:49:32 +08:00
|
|
|
|
continue;
|
|
|
|
|
#else
|
2021-06-04 18:06:40 +08:00
|
|
|
|
// 新增配置选项,为了动态添加多个ffmpeg cmd 模板
|
|
|
|
|
ini[pr.first] = pr.second;
|
2021-06-08 14:49:32 +08:00
|
|
|
|
// 防止changed变化
|
|
|
|
|
continue;
|
|
|
|
|
#endif
|
2019-05-20 11:22:59 +08:00
|
|
|
|
}
|
|
|
|
|
if (ini[pr.first] == pr.second) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
ini[pr.first] = pr.second;
|
|
|
|
|
//替换成功
|
|
|
|
|
++changed;
|
|
|
|
|
}
|
|
|
|
|
if (changed > 0) {
|
|
|
|
|
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
|
2019-11-04 09:21:11 +08:00
|
|
|
|
ini.dumpFile(g_ini_file);
|
2019-05-20 11:22:59 +08:00
|
|
|
|
}
|
|
|
|
|
val["changed"] = changed;
|
|
|
|
|
});
|
|
|
|
|
|
2019-05-27 15:09:44 +08:00
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
static auto s_get_api_list = [](API_ARGS_MAP){
|
2019-05-20 17:12:00 +08:00
|
|
|
|
CHECK_SECRET();
|
2019-05-20 11:22:59 +08:00
|
|
|
|
for(auto &pr : s_map_api){
|
|
|
|
|
val["data"].append(pr.first);
|
|
|
|
|
}
|
2020-01-17 11:44:20 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//获取服务器api列表
|
|
|
|
|
//测试url http://127.0.0.1/index/api/getApiList
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/getApiList",[](API_ARGS_MAP){
|
|
|
|
|
s_get_api_list(API_ARGS_VALUE);
|
2020-01-17 11:44:20 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//获取服务器api列表
|
|
|
|
|
//测试url http://127.0.0.1/index/
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/",[](API_ARGS_MAP){
|
|
|
|
|
s_get_api_list(API_ARGS_VALUE);
|
2019-05-20 11:22:59 +08:00
|
|
|
|
});
|
|
|
|
|
|
2019-06-15 17:07:10 +08:00
|
|
|
|
#if !defined(_WIN32)
|
2019-05-27 15:09:44 +08:00
|
|
|
|
//重启服务器,只有Daemon方式才能重启,否则是直接关闭!
|
|
|
|
|
//测试url http://127.0.0.1/index/api/restartServer
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/restartServer",[](API_ARGS_MAP){
|
2019-05-20 17:12:00 +08:00
|
|
|
|
CHECK_SECRET();
|
2019-05-20 11:22:59 +08:00
|
|
|
|
EventPollerPool::Instance().getPoller()->doDelayTask(1000,[](){
|
|
|
|
|
//尝试正常退出
|
|
|
|
|
::kill(getpid(), SIGINT);
|
|
|
|
|
|
|
|
|
|
//3秒后强制退出
|
|
|
|
|
EventPollerPool::Instance().getPoller()->doDelayTask(3000,[](){
|
|
|
|
|
exit(0);
|
|
|
|
|
return 0;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
});
|
|
|
|
|
val["msg"] = "服务器将在一秒后自动重启";
|
|
|
|
|
});
|
2021-12-19 17:39:50 +08:00
|
|
|
|
#else
|
|
|
|
|
//增加Windows下的重启代码
|
|
|
|
|
api_regist("/index/api/restartServer", [](API_ARGS_MAP) {
|
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
//创建重启批处理脚本文件
|
|
|
|
|
FILE *pf;
|
|
|
|
|
errno_t err = ::_wfopen_s(&pf, L"RestartServer.cmd", L"w"); //“w”如果该文件存在,其内容将被覆盖
|
|
|
|
|
if (err == 0) {
|
|
|
|
|
char szExeName[1024];
|
|
|
|
|
char drive[_MAX_DRIVE] = { 0 };
|
|
|
|
|
char dir[_MAX_DIR] = { 0 };
|
|
|
|
|
char fname[_MAX_FNAME] = { 0 };
|
|
|
|
|
char ext[_MAX_EXT] = { 0 };
|
|
|
|
|
char exeName[_MAX_FNAME] = { 0 };
|
|
|
|
|
GetModuleFileNameA(NULL, szExeName, 1024); //获取进程的全路径
|
|
|
|
|
_splitpath(szExeName, drive, dir, fname, ext);
|
|
|
|
|
strcpy(exeName, fname);
|
|
|
|
|
strcat(exeName, ext);
|
|
|
|
|
fprintf(pf, "@echo off\ntaskkill /f /im %s\nstart \"\" \"%s\"\ndel %%0", exeName, szExeName);
|
|
|
|
|
fclose(pf);
|
|
|
|
|
// 1秒后执行创建的批处理脚本
|
|
|
|
|
EventPollerPool::Instance().getPoller()->doDelayTask(1000, []() {
|
|
|
|
|
STARTUPINFO si;
|
|
|
|
|
PROCESS_INFORMATION pi;
|
|
|
|
|
ZeroMemory(&si, sizeof si);
|
|
|
|
|
ZeroMemory(&pi, sizeof pi);
|
|
|
|
|
si.cb = sizeof si;
|
|
|
|
|
si.dwFlags = STARTF_USESHOWWINDOW;
|
|
|
|
|
si.wShowWindow = SW_HIDE;
|
|
|
|
|
TCHAR winSysDir[1024];
|
|
|
|
|
ZeroMemory(winSysDir, sizeof winSysDir);
|
|
|
|
|
GetSystemDirectory(winSysDir, 1024);
|
|
|
|
|
TCHAR appName[1024];
|
|
|
|
|
ZeroMemory(appName, sizeof appName);
|
|
|
|
|
|
|
|
|
|
_stprintf(appName, "%s\\cmd.exe", winSysDir);
|
|
|
|
|
BOOL bRet = CreateProcess(appName, " /c RestartServer.cmd", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
|
|
|
|
|
|
|
|
|
|
if (bRet == FALSE) {
|
|
|
|
|
int err = GetLastError();
|
|
|
|
|
cout << endl << "无法执行重启操作,错误代码:" << err << endl;
|
|
|
|
|
}
|
|
|
|
|
WaitForSingleObject(pi.hProcess, INFINITE);
|
|
|
|
|
CloseHandle(pi.hProcess);
|
|
|
|
|
CloseHandle(pi.hThread);
|
|
|
|
|
return 0;
|
|
|
|
|
});
|
|
|
|
|
val["msg"] = "服务器将在一秒后自动重启";
|
|
|
|
|
} else {
|
|
|
|
|
val["msg"] = "创建重启脚本文件失败";
|
|
|
|
|
val["code"] = API::OtherFailed;
|
|
|
|
|
}
|
|
|
|
|
});
|
2019-06-15 17:07:10 +08:00
|
|
|
|
#endif//#if !defined(_WIN32)
|
2019-05-20 11:22:59 +08:00
|
|
|
|
|
2019-05-27 15:09:44 +08:00
|
|
|
|
//获取流列表,可选筛选参数
|
|
|
|
|
//测试url0(获取所有流) http://127.0.0.1/index/api/getMediaList
|
|
|
|
|
//测试url1(获取虚拟主机为"__defaultVost__"的流) http://127.0.0.1/index/api/getMediaList?vhost=__defaultVost__
|
|
|
|
|
//测试url2(获取rtsp类型的流) http://127.0.0.1/index/api/getMediaList?schema=rtsp
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/getMediaList",[](API_ARGS_MAP){
|
2019-05-20 17:12:00 +08:00
|
|
|
|
CHECK_SECRET();
|
2019-05-20 11:22:59 +08:00
|
|
|
|
//获取所有MediaSource列表
|
2021-06-30 21:06:29 +08:00
|
|
|
|
MediaSource::for_each_media([&](const MediaSource::Ptr &media) {
|
2021-04-08 17:34:13 +08:00
|
|
|
|
val["data"].append(makeMediaSourceJson(*media));
|
2021-06-30 21:06:29 +08:00
|
|
|
|
}, allArgs["schema"], allArgs["vhost"], allArgs["app"], allArgs["stream"]);
|
2019-05-20 11:22:59 +08:00
|
|
|
|
});
|
|
|
|
|
|
2019-11-13 14:26:12 +08:00
|
|
|
|
//测试url http://127.0.0.1/index/api/isMediaOnline?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/isMediaOnline",[](API_ARGS_MAP){
|
2019-11-13 14:26:12 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("schema","vhost","app","stream");
|
2020-05-26 10:11:58 +08:00
|
|
|
|
val["online"] = (bool) (MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"]));
|
2019-11-13 14:26:12 +08:00
|
|
|
|
});
|
|
|
|
|
|
2019-12-13 13:35:27 +08:00
|
|
|
|
//测试url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/getMediaInfo",[](API_ARGS_MAP){
|
2019-12-13 13:35:27 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("schema","vhost","app","stream");
|
2020-05-26 10:11:58 +08:00
|
|
|
|
auto src = MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"]);
|
2019-12-13 13:35:27 +08:00
|
|
|
|
if(!src){
|
|
|
|
|
val["online"] = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-04-08 17:34:13 +08:00
|
|
|
|
val = makeMediaSourceJson(*src);
|
2019-12-13 13:35:27 +08:00
|
|
|
|
val["online"] = true;
|
2020-04-23 23:45:20 +08:00
|
|
|
|
val["code"] = API::Success;
|
2019-12-13 13:35:27 +08:00
|
|
|
|
});
|
|
|
|
|
|
2019-05-27 14:14:42 +08:00
|
|
|
|
//主动关断流,包括关断拉流、推流
|
2019-05-27 18:39:43 +08:00
|
|
|
|
//测试url http://127.0.0.1/index/api/close_stream?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/close_stream",[](API_ARGS_MAP){
|
2019-05-20 17:12:00 +08:00
|
|
|
|
CHECK_SECRET();
|
2019-05-20 16:53:29 +08:00
|
|
|
|
CHECK_ARGS("schema","vhost","app","stream");
|
2019-05-20 11:22:59 +08:00
|
|
|
|
//踢掉推流器
|
|
|
|
|
auto src = MediaSource::find(allArgs["schema"],
|
|
|
|
|
allArgs["vhost"],
|
|
|
|
|
allArgs["app"],
|
|
|
|
|
allArgs["stream"]);
|
2020-10-24 23:31:40 +08:00
|
|
|
|
if (src) {
|
2019-05-27 18:39:43 +08:00
|
|
|
|
bool flag = src->close(allArgs["force"].as<bool>());
|
2019-11-18 16:28:56 +08:00
|
|
|
|
val["result"] = flag ? 0 : -1;
|
2019-05-27 14:15:21 +08:00
|
|
|
|
val["msg"] = flag ? "success" : "close failed";
|
2020-10-24 23:31:40 +08:00
|
|
|
|
val["code"] = flag ? API::Success : API::OtherFailed;
|
|
|
|
|
} else {
|
2019-11-18 16:28:56 +08:00
|
|
|
|
val["result"] = -2;
|
2019-05-27 14:15:21 +08:00
|
|
|
|
val["msg"] = "can not find the stream";
|
2020-04-15 22:30:24 +08:00
|
|
|
|
val["code"] = API::OtherFailed;
|
2019-05-20 11:22:59 +08:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2019-11-18 10:46:59 +08:00
|
|
|
|
//批量主动关断流,包括关断拉流、推流
|
|
|
|
|
//测试url http://127.0.0.1/index/api/close_streams?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/close_streams",[](API_ARGS_MAP){
|
2019-11-18 10:46:59 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
//筛选命中个数
|
|
|
|
|
int count_hit = 0;
|
|
|
|
|
int count_closed = 0;
|
2019-11-19 10:55:44 +08:00
|
|
|
|
list<MediaSource::Ptr> media_list;
|
2021-06-30 21:06:29 +08:00
|
|
|
|
MediaSource::for_each_media([&](const MediaSource::Ptr &media) {
|
2019-11-18 10:46:59 +08:00
|
|
|
|
++count_hit;
|
2019-11-19 10:55:44 +08:00
|
|
|
|
media_list.emplace_back(media);
|
2021-06-30 21:06:29 +08:00
|
|
|
|
}, allArgs["schema"], allArgs["vhost"], allArgs["app"], allArgs["stream"]);
|
2019-11-19 10:55:44 +08:00
|
|
|
|
|
|
|
|
|
bool force = allArgs["force"].as<bool>();
|
|
|
|
|
for(auto &media : media_list){
|
|
|
|
|
if(media->close(force)){
|
2019-11-18 10:46:59 +08:00
|
|
|
|
++count_closed;
|
|
|
|
|
}
|
2019-11-19 10:55:44 +08:00
|
|
|
|
}
|
2019-11-18 10:46:59 +08:00
|
|
|
|
val["count_hit"] = count_hit;
|
|
|
|
|
val["count_closed"] = count_closed;
|
|
|
|
|
});
|
|
|
|
|
|
2019-05-27 15:09:44 +08:00
|
|
|
|
//获取所有TcpSession列表信息
|
|
|
|
|
//可以根据本地端口和远端ip来筛选
|
|
|
|
|
//测试url(筛选某端口下的tcp会话) http://127.0.0.1/index/api/getAllSession?local_port=1935
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/getAllSession",[](API_ARGS_MAP){
|
2019-05-27 15:09:44 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
Value jsession;
|
|
|
|
|
uint16_t local_port = allArgs["local_port"].as<uint16_t>();
|
2021-08-12 16:07:31 +08:00
|
|
|
|
string peer_ip = allArgs["peer_ip"];
|
2019-05-27 15:09:44 +08:00
|
|
|
|
|
2021-06-08 11:29:32 +08:00
|
|
|
|
SessionMap::Instance().for_each_session([&](const string &id,const Session::Ptr &session){
|
2019-11-18 16:16:56 +08:00
|
|
|
|
if(local_port != 0 && local_port != session->get_local_port()){
|
2019-05-27 15:09:44 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if(!peer_ip.empty() && peer_ip != session->get_peer_ip()){
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
jsession["peer_ip"] = session->get_peer_ip();
|
|
|
|
|
jsession["peer_port"] = session->get_peer_port();
|
|
|
|
|
jsession["local_ip"] = session->get_local_ip();
|
|
|
|
|
jsession["local_port"] = session->get_local_port();
|
|
|
|
|
jsession["id"] = id;
|
|
|
|
|
jsession["typeid"] = typeid(*session).name();
|
|
|
|
|
val["data"].append(jsession);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//断开tcp连接,比如说可以断开rtsp、rtmp播放器等
|
|
|
|
|
//测试url http://127.0.0.1/index/api/kick_session?id=123456
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/kick_session",[](API_ARGS_MAP){
|
2019-05-20 17:12:00 +08:00
|
|
|
|
CHECK_SECRET();
|
2019-05-20 16:53:29 +08:00
|
|
|
|
CHECK_ARGS("id");
|
2019-05-20 11:22:59 +08:00
|
|
|
|
//踢掉tcp会话
|
2019-05-28 09:38:15 +08:00
|
|
|
|
auto session = SessionMap::Instance().get(allArgs["id"]);
|
2019-05-20 11:22:59 +08:00
|
|
|
|
if(!session){
|
2019-11-13 14:33:19 +08:00
|
|
|
|
throw ApiRetException("can not find the target",API::OtherFailed);
|
2019-05-20 11:22:59 +08:00
|
|
|
|
}
|
|
|
|
|
session->safeShutdown();
|
|
|
|
|
});
|
|
|
|
|
|
2019-11-18 16:34:39 +08:00
|
|
|
|
|
|
|
|
|
//批量断开tcp连接,比如说可以断开rtsp、rtmp播放器等
|
|
|
|
|
//测试url http://127.0.0.1/index/api/kick_sessions?local_port=1935
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/kick_sessions",[](API_ARGS_MAP){
|
2019-11-18 16:34:39 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
uint16_t local_port = allArgs["local_port"].as<uint16_t>();
|
2021-08-12 16:07:31 +08:00
|
|
|
|
string peer_ip = allArgs["peer_ip"];
|
2021-01-17 18:31:50 +08:00
|
|
|
|
size_t count_hit = 0;
|
2019-11-18 16:34:39 +08:00
|
|
|
|
|
2021-06-08 11:29:32 +08:00
|
|
|
|
list<Session::Ptr> session_list;
|
|
|
|
|
SessionMap::Instance().for_each_session([&](const string &id,const Session::Ptr &session){
|
2019-11-18 16:34:39 +08:00
|
|
|
|
if(local_port != 0 && local_port != session->get_local_port()){
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if(!peer_ip.empty() && peer_ip != session->get_peer_ip()){
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-11-19 10:55:44 +08:00
|
|
|
|
session_list.emplace_back(session);
|
2019-11-18 16:34:39 +08:00
|
|
|
|
++count_hit;
|
|
|
|
|
});
|
2019-11-19 10:55:44 +08:00
|
|
|
|
|
|
|
|
|
for(auto &session : session_list){
|
|
|
|
|
session->safeShutdown();
|
|
|
|
|
}
|
2019-11-18 16:34:39 +08:00
|
|
|
|
val["count_hit"] = (Json::UInt64)count_hit;
|
|
|
|
|
});
|
|
|
|
|
|
2021-06-16 19:40:08 +08:00
|
|
|
|
static auto addStreamPusherProxy = [](const string &schema,
|
|
|
|
|
const string &vhost,
|
|
|
|
|
const string &app,
|
|
|
|
|
const string &stream,
|
|
|
|
|
const string &url,
|
2021-06-17 10:41:26 +08:00
|
|
|
|
int retry_count,
|
|
|
|
|
int rtp_type,
|
|
|
|
|
float timeout_sec,
|
2021-06-17 10:12:34 +08:00
|
|
|
|
const function<void(const SockException &ex, const string &key)> &cb) {
|
2021-06-17 10:41:26 +08:00
|
|
|
|
auto key = getPusherKey(schema, vhost, app, stream, url);
|
2021-06-17 10:59:58 +08:00
|
|
|
|
auto src = MediaSource::find(schema, vhost, app, stream);
|
|
|
|
|
if (!src) {
|
|
|
|
|
cb(SockException(Err_other, "can not find the source stream"), key);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-06-16 19:40:08 +08:00
|
|
|
|
lock_guard<recursive_mutex> lck(s_proxyPusherMapMtx);
|
2021-06-17 10:12:34 +08:00
|
|
|
|
if (s_proxyPusherMap.find(key) != s_proxyPusherMap.end()) {
|
2021-06-16 19:40:08 +08:00
|
|
|
|
//已经在推流了
|
2021-06-17 10:12:34 +08:00
|
|
|
|
cb(SockException(Err_success), key);
|
2021-06-16 19:40:08 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//添加推流代理
|
2021-06-17 10:59:58 +08:00
|
|
|
|
PusherProxy::Ptr pusher(new PusherProxy(src, retry_count ? retry_count : -1));
|
2021-06-16 19:40:08 +08:00
|
|
|
|
s_proxyPusherMap[key] = pusher;
|
|
|
|
|
|
2021-06-17 10:41:26 +08:00
|
|
|
|
//指定RTP over TCP(播放rtsp时有效)
|
2022-02-02 20:34:50 +08:00
|
|
|
|
(*pusher)[Client::kRtpType] = rtp_type;
|
2021-06-17 10:41:26 +08:00
|
|
|
|
|
|
|
|
|
if (timeout_sec > 0.1) {
|
|
|
|
|
//推流握手超时时间
|
2022-02-02 20:34:50 +08:00
|
|
|
|
(*pusher)[Client::kTimeoutMS] = timeout_sec * 1000;
|
2021-06-17 10:41:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-16 19:40:08 +08:00
|
|
|
|
//开始推流,如果推流失败或者推流中止,将会自动重试若干次,默认一直重试
|
2021-06-17 10:12:34 +08:00
|
|
|
|
pusher->setPushCallbackOnce([cb, key, url](const SockException &ex) {
|
|
|
|
|
if (ex) {
|
2021-06-17 10:59:58 +08:00
|
|
|
|
WarnL << "Push " << url << " failed, key: " << key << ", err: " << ex.what();
|
|
|
|
|
lock_guard<recursive_mutex> lck(s_proxyPusherMapMtx);
|
|
|
|
|
s_proxyPusherMap.erase(key);
|
2021-06-16 19:40:08 +08:00
|
|
|
|
}
|
2021-06-17 10:12:34 +08:00
|
|
|
|
cb(ex, key);
|
2021-06-16 19:40:08 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//被主动关闭推流
|
2021-06-17 10:12:34 +08:00
|
|
|
|
pusher->setOnClose([key, url](const SockException &ex) {
|
2021-06-17 10:59:58 +08:00
|
|
|
|
WarnL << "Push " << url << " failed, key: " << key << ", err: " << ex.what();
|
|
|
|
|
lock_guard<recursive_mutex> lck(s_proxyPusherMapMtx);
|
|
|
|
|
s_proxyPusherMap.erase(key);
|
2021-06-16 19:40:08 +08:00
|
|
|
|
});
|
|
|
|
|
pusher->publish(url);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//动态添加rtsp/rtmp推流代理
|
|
|
|
|
//测试url http://127.0.0.1/index/api/addStreamPusherProxy?schema=rtmp&vhost=__defaultVhost__&app=proxy&stream=0&dst_url=rtmp://127.0.0.1/live/obs
|
|
|
|
|
api_regist("/index/api/addStreamPusherProxy", [](API_ARGS_MAP_ASYNC) {
|
|
|
|
|
CHECK_SECRET();
|
2021-06-17 10:12:34 +08:00
|
|
|
|
CHECK_ARGS("schema", "vhost", "app", "stream", "dst_url");
|
|
|
|
|
auto dst_url = allArgs["dst_url"];
|
2021-06-16 19:40:08 +08:00
|
|
|
|
addStreamPusherProxy(allArgs["schema"],
|
|
|
|
|
allArgs["vhost"],
|
|
|
|
|
allArgs["app"],
|
|
|
|
|
allArgs["stream"],
|
2021-06-17 10:12:34 +08:00
|
|
|
|
allArgs["dst_url"],
|
2021-06-16 19:40:08 +08:00
|
|
|
|
allArgs["retry_count"],
|
2021-06-17 10:41:26 +08:00
|
|
|
|
allArgs["rtp_type"],
|
|
|
|
|
allArgs["timeout_sec"],
|
2021-06-17 10:12:34 +08:00
|
|
|
|
[invoker, val, headerOut, dst_url](const SockException &ex, const string &key) mutable {
|
|
|
|
|
if (ex) {
|
|
|
|
|
val["code"] = API::OtherFailed;
|
|
|
|
|
val["msg"] = ex.what();
|
|
|
|
|
} else {
|
|
|
|
|
val["data"]["key"] = key;
|
|
|
|
|
InfoL << "Publish success, please play with player:" << dst_url;
|
2021-06-16 19:40:08 +08:00
|
|
|
|
}
|
|
|
|
|
invoker(200, headerOut, val.toStyledString());
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//关闭推流代理
|
|
|
|
|
//测试url http://127.0.0.1/index/api/delStreamPusherProxy?key=__defaultVhost__/proxy/0
|
2021-06-17 10:12:34 +08:00
|
|
|
|
api_regist("/index/api/delStreamPusherProxy", [](API_ARGS_MAP) {
|
2021-06-16 19:40:08 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("key");
|
|
|
|
|
lock_guard<recursive_mutex> lck(s_proxyPusherMapMtx);
|
|
|
|
|
val["data"]["flag"] = s_proxyPusherMap.erase(allArgs["key"]) == 1;
|
|
|
|
|
});
|
|
|
|
|
|
2019-05-27 23:07:12 +08:00
|
|
|
|
//动态添加rtsp/rtmp拉流代理
|
2019-07-22 11:38:32 +08:00
|
|
|
|
//测试url http://127.0.0.1/index/api/addStreamProxy?vhost=__defaultVhost__&app=proxy&enable_rtsp=1&enable_rtmp=1&stream=0&url=rtmp://127.0.0.1/live/obs
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/addStreamProxy",[](API_ARGS_MAP_ASYNC){
|
2019-05-27 23:07:12 +08:00
|
|
|
|
CHECK_SECRET();
|
2020-09-20 09:26:00 +08:00
|
|
|
|
CHECK_ARGS("vhost","app","stream","url");
|
2019-05-27 23:07:12 +08:00
|
|
|
|
addStreamProxy(allArgs["vhost"],
|
|
|
|
|
allArgs["app"],
|
|
|
|
|
allArgs["stream"],
|
|
|
|
|
allArgs["url"],
|
2021-06-17 10:39:22 +08:00
|
|
|
|
allArgs["retry_count"],
|
2019-07-22 11:27:17 +08:00
|
|
|
|
allArgs["enable_hls"],/* 是否hls转发 */
|
|
|
|
|
allArgs["enable_mp4"],/* 是否MP4录制 */
|
2019-05-27 23:07:12 +08:00
|
|
|
|
allArgs["rtp_type"],
|
2021-04-30 18:01:48 +08:00
|
|
|
|
allArgs["timeout_sec"],
|
2021-03-14 10:29:17 +08:00
|
|
|
|
[invoker,val,headerOut](const SockException &ex,const string &key) mutable{
|
|
|
|
|
if (ex) {
|
|
|
|
|
val["code"] = API::OtherFailed;
|
|
|
|
|
val["msg"] = ex.what();
|
|
|
|
|
} else {
|
|
|
|
|
val["data"]["key"] = key;
|
2019-05-27 23:07:12 +08:00
|
|
|
|
}
|
2021-01-02 21:24:06 +08:00
|
|
|
|
invoker(200, headerOut, val.toStyledString());
|
2019-05-27 23:07:12 +08:00
|
|
|
|
});
|
2019-05-20 16:26:04 +08:00
|
|
|
|
});
|
|
|
|
|
|
2019-05-27 15:09:44 +08:00
|
|
|
|
//关闭拉流代理
|
|
|
|
|
//测试url http://127.0.0.1/index/api/delStreamProxy?key=__defaultVhost__/proxy/0
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/delStreamProxy",[](API_ARGS_MAP){
|
2019-05-20 17:12:00 +08:00
|
|
|
|
CHECK_SECRET();
|
2019-05-20 18:17:53 +08:00
|
|
|
|
CHECK_ARGS("key");
|
2019-05-20 16:26:04 +08:00
|
|
|
|
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
2019-05-20 18:17:53 +08:00
|
|
|
|
val["data"]["flag"] = s_proxyMap.erase(allArgs["key"]) == 1;
|
2019-05-20 16:26:04 +08:00
|
|
|
|
});
|
|
|
|
|
|
2021-01-23 09:42:15 +08:00
|
|
|
|
static auto addFFmpegSource = [](const string &ffmpeg_cmd_key,
|
|
|
|
|
const string &src_url,
|
2019-06-06 18:28:33 +08:00
|
|
|
|
const string &dst_url,
|
|
|
|
|
int timeout_ms,
|
2020-12-20 20:25:44 +08:00
|
|
|
|
bool enable_hls,
|
|
|
|
|
bool enable_mp4,
|
|
|
|
|
const function<void(const SockException &ex, const string &key)> &cb) {
|
2019-06-06 18:28:33 +08:00
|
|
|
|
auto key = MD5(dst_url).hexdigest();
|
|
|
|
|
lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
|
2020-12-20 20:25:44 +08:00
|
|
|
|
if (s_ffmpegMap.find(key) != s_ffmpegMap.end()) {
|
2019-06-06 18:28:33 +08:00
|
|
|
|
//已经在拉流了
|
2020-12-20 20:25:44 +08:00
|
|
|
|
cb(SockException(Err_success), key);
|
2019-06-06 18:28:33 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FFmpegSource::Ptr ffmpeg = std::make_shared<FFmpegSource>();
|
|
|
|
|
s_ffmpegMap[key] = ffmpeg;
|
|
|
|
|
|
2020-12-20 20:25:44 +08:00
|
|
|
|
ffmpeg->setOnClose([key]() {
|
2019-06-06 18:28:33 +08:00
|
|
|
|
lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
|
|
|
|
|
s_ffmpegMap.erase(key);
|
|
|
|
|
});
|
2021-01-17 20:15:08 +08:00
|
|
|
|
ffmpeg->setupRecordFlag(enable_hls, enable_mp4);
|
2021-01-23 09:42:15 +08:00
|
|
|
|
ffmpeg->play(ffmpeg_cmd_key, src_url, dst_url, timeout_ms, [cb, key](const SockException &ex) {
|
2020-12-20 20:25:44 +08:00
|
|
|
|
if (ex) {
|
2019-06-06 18:28:33 +08:00
|
|
|
|
lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
|
|
|
|
|
s_ffmpegMap.erase(key);
|
|
|
|
|
}
|
2020-12-20 20:25:44 +08:00
|
|
|
|
cb(ex, key);
|
2019-06-06 18:28:33 +08:00
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//动态添加rtsp/rtmp拉流代理
|
|
|
|
|
//测试url http://127.0.0.1/index/api/addFFmpegSource?src_url=http://live.hkstv.hk.lxdns.com/live/hks2/playlist.m3u8&dst_url=rtmp://127.0.0.1/live/hks2&timeout_ms=10000
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/addFFmpegSource",[](API_ARGS_MAP_ASYNC){
|
2019-06-06 18:28:33 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("src_url","dst_url","timeout_ms");
|
|
|
|
|
auto src_url = allArgs["src_url"];
|
|
|
|
|
auto dst_url = allArgs["dst_url"];
|
|
|
|
|
int timeout_ms = allArgs["timeout_ms"];
|
2020-12-20 20:25:44 +08:00
|
|
|
|
auto enable_hls = allArgs["enable_hls"].as<int>();
|
|
|
|
|
auto enable_mp4 = allArgs["enable_mp4"].as<int>();
|
2019-06-06 18:28:33 +08:00
|
|
|
|
|
2021-01-23 09:42:15 +08:00
|
|
|
|
addFFmpegSource(allArgs["ffmpeg_cmd_key"], src_url, dst_url, timeout_ms, enable_hls, enable_mp4,
|
2021-03-14 10:29:17 +08:00
|
|
|
|
[invoker, val, headerOut](const SockException &ex, const string &key) mutable{
|
2020-12-20 20:25:44 +08:00
|
|
|
|
if (ex) {
|
2021-03-14 10:29:17 +08:00
|
|
|
|
val["code"] = API::OtherFailed;
|
|
|
|
|
val["msg"] = ex.what();
|
2020-12-20 20:25:44 +08:00
|
|
|
|
} else {
|
2021-03-14 10:29:17 +08:00
|
|
|
|
val["data"]["key"] = key;
|
2019-06-06 18:28:33 +08:00
|
|
|
|
}
|
2021-01-02 21:24:06 +08:00
|
|
|
|
invoker(200, headerOut, val.toStyledString());
|
2019-06-06 18:28:33 +08:00
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2020-12-27 21:48:04 +08:00
|
|
|
|
//关闭拉流代理
|
|
|
|
|
//测试url http://127.0.0.1/index/api/delFFmepgSource?key=key
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/delFFmpegSource",[](API_ARGS_MAP){
|
2019-06-06 18:28:33 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("key");
|
|
|
|
|
lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
|
|
|
|
|
val["data"]["flag"] = s_ffmpegMap.erase(allArgs["key"]) == 1;
|
|
|
|
|
});
|
2019-05-20 16:26:04 +08:00
|
|
|
|
|
2019-10-28 17:23:16 +08:00
|
|
|
|
//新增http api下载可执行程序文件接口
|
|
|
|
|
//测试url http://127.0.0.1/index/api/downloadBin
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/downloadBin",[](API_ARGS_MAP_ASYNC){
|
2019-10-28 17:23:16 +08:00
|
|
|
|
CHECK_SECRET();
|
2021-08-12 16:07:31 +08:00
|
|
|
|
invoker.responseFile(allArgs.getParser().getHeader(),StrCaseMap(),exePath());
|
2019-10-28 17:23:16 +08:00
|
|
|
|
});
|
|
|
|
|
|
2019-12-06 11:54:10 +08:00
|
|
|
|
#if defined(ENABLE_RTPPROXY)
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/getRtpInfo",[](API_ARGS_MAP){
|
2019-12-05 19:53:55 +08:00
|
|
|
|
CHECK_SECRET();
|
2020-07-07 10:01:12 +08:00
|
|
|
|
CHECK_ARGS("stream_id");
|
2019-12-09 18:15:02 +08:00
|
|
|
|
|
2020-07-07 10:01:12 +08:00
|
|
|
|
auto process = RtpSelector::Instance().getProcess(allArgs["stream_id"], false);
|
|
|
|
|
if (!process) {
|
2019-12-05 19:53:55 +08:00
|
|
|
|
val["exist"] = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
val["exist"] = true;
|
|
|
|
|
val["peer_ip"] = process->get_peer_ip();
|
|
|
|
|
val["peer_port"] = process->get_peer_port();
|
2020-07-09 10:41:17 +08:00
|
|
|
|
val["local_port"] = process->get_local_port();
|
|
|
|
|
val["local_ip"] = process->get_local_ip();
|
2019-12-05 19:53:55 +08:00
|
|
|
|
});
|
2020-07-02 22:23:43 +08:00
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/openRtpServer",[](API_ARGS_MAP){
|
2020-07-02 22:23:43 +08:00
|
|
|
|
CHECK_SECRET();
|
2020-07-07 10:01:12 +08:00
|
|
|
|
CHECK_ARGS("port", "enable_tcp", "stream_id");
|
2020-07-09 10:38:57 +08:00
|
|
|
|
auto stream_id = allArgs["stream_id"];
|
2020-07-09 10:57:17 +08:00
|
|
|
|
|
|
|
|
|
lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
|
2022-01-14 22:04:46 +08:00
|
|
|
|
if (s_rtpServerMap.find(stream_id) != s_rtpServerMap.end()) {
|
2020-07-09 10:57:17 +08:00
|
|
|
|
//为了防止RtpProcess所有权限混乱的问题,不允许重复添加相同的stream_id
|
|
|
|
|
throw InvalidArgsException("该stream_id已存在");
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-02 22:23:43 +08:00
|
|
|
|
RtpServer::Ptr server = std::make_shared<RtpServer>();
|
2022-01-19 17:44:58 +08:00
|
|
|
|
server->start(allArgs["port"], stream_id, allArgs["enable_tcp"].as<bool>(), "0.0.0.0", false);
|
2020-07-09 10:38:57 +08:00
|
|
|
|
server->setOnDetach([stream_id]() {
|
2020-07-08 10:25:30 +08:00
|
|
|
|
//设置rtp超时移除事件
|
|
|
|
|
lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
|
2020-07-09 10:38:57 +08:00
|
|
|
|
s_rtpServerMap.erase(stream_id);
|
2020-07-08 10:25:30 +08:00
|
|
|
|
});
|
2020-07-02 22:23:43 +08:00
|
|
|
|
|
2020-07-09 10:57:17 +08:00
|
|
|
|
//保存对象
|
|
|
|
|
s_rtpServerMap.emplace(stream_id, server);
|
2020-07-08 10:25:30 +08:00
|
|
|
|
//回复json
|
2020-07-09 10:38:57 +08:00
|
|
|
|
val["port"] = server->getPort();
|
2020-07-02 22:23:43 +08:00
|
|
|
|
});
|
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/closeRtpServer",[](API_ARGS_MAP){
|
2020-07-02 22:23:43 +08:00
|
|
|
|
CHECK_SECRET();
|
2020-07-09 10:38:57 +08:00
|
|
|
|
CHECK_ARGS("stream_id");
|
2020-07-02 22:23:43 +08:00
|
|
|
|
|
|
|
|
|
lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
|
2020-08-08 12:20:44 +08:00
|
|
|
|
auto it = s_rtpServerMap.find(allArgs["stream_id"]);
|
|
|
|
|
if(it == s_rtpServerMap.end()){
|
|
|
|
|
val["hit"] = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
auto server = it->second;
|
|
|
|
|
s_rtpServerMap.erase(it);
|
|
|
|
|
val["hit"] = 1;
|
2020-07-02 22:23:43 +08:00
|
|
|
|
});
|
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/listRtpServer",[](API_ARGS_MAP){
|
2020-07-02 22:26:38 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
|
|
|
|
|
lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
|
2020-07-09 10:38:57 +08:00
|
|
|
|
for (auto &pr : s_rtpServerMap) {
|
|
|
|
|
Value obj;
|
|
|
|
|
obj["stream_id"] = pr.first;
|
|
|
|
|
obj["port"] = pr.second->getPort();
|
|
|
|
|
val["data"].append(obj);
|
2020-07-02 22:26:38 +08:00
|
|
|
|
}
|
|
|
|
|
});
|
2020-07-02 22:23:43 +08:00
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/startSendRtp",[](API_ARGS_MAP_ASYNC){
|
2020-09-06 17:56:05 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("vhost", "app", "stream", "ssrc", "dst_url", "dst_port", "is_udp");
|
|
|
|
|
|
2021-10-21 10:21:52 +08:00
|
|
|
|
auto src = MediaSource::find(allArgs["vhost"], allArgs["app"], allArgs["stream"], allArgs["from_mp4"].as<int>());
|
2021-08-12 16:07:31 +08:00
|
|
|
|
if (!src) {
|
|
|
|
|
throw ApiRetException("该媒体流不存在", API::OtherFailed);
|
2020-09-06 17:56:05 +08:00
|
|
|
|
}
|
|
|
|
|
|
2021-08-12 16:07:31 +08:00
|
|
|
|
//src_port为空时,则随机本地端口
|
|
|
|
|
src->startSendRtp(allArgs["dst_url"], allArgs["dst_port"], allArgs["ssrc"], allArgs["is_udp"], allArgs["src_port"], [val, headerOut, invoker](uint16_t local_port, const SockException &ex) mutable{
|
|
|
|
|
if (ex) {
|
|
|
|
|
val["code"] = API::OtherFailed;
|
|
|
|
|
val["msg"] = ex.what();
|
|
|
|
|
}
|
|
|
|
|
val["local_port"] = local_port;
|
|
|
|
|
invoker(200, headerOut, val.toStyledString());
|
|
|
|
|
});
|
2020-09-06 17:56:05 +08:00
|
|
|
|
});
|
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/stopSendRtp",[](API_ARGS_MAP){
|
2020-09-06 17:56:05 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("vhost", "app", "stream");
|
|
|
|
|
|
2020-09-06 18:19:54 +08:00
|
|
|
|
auto src = MediaSource::find(allArgs["vhost"], allArgs["app"], allArgs["stream"]);
|
2020-09-06 17:56:05 +08:00
|
|
|
|
if (!src) {
|
|
|
|
|
throw ApiRetException("该媒体流不存在", API::OtherFailed);
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-27 18:41:53 +08:00
|
|
|
|
//ssrc如果为空,关闭全部
|
|
|
|
|
if (!src->stopSendRtp(allArgs["ssrc"])) {
|
2020-09-06 18:24:48 +08:00
|
|
|
|
throw ApiRetException("尚未开始推流,停止失败", API::OtherFailed);
|
|
|
|
|
}
|
2020-09-06 17:56:05 +08:00
|
|
|
|
});
|
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/pauseRtpCheck", [](API_ARGS_MAP) {
|
2020-12-25 16:05:38 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("stream_id");
|
|
|
|
|
//只是暂停流的检查,流媒体服务器做为流负载服务,收流就转发,RTSP/RTMP有自己暂停协议
|
2021-03-16 11:30:51 +08:00
|
|
|
|
auto rtp_process = RtpSelector::Instance().getProcess(allArgs["stream_id"], false);
|
|
|
|
|
if (rtp_process) {
|
|
|
|
|
rtp_process->setStopCheckRtp(true);
|
|
|
|
|
} else {
|
|
|
|
|
val["code"] = API::NotFound;
|
2020-12-25 16:05:38 +08:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/resumeRtpCheck", [](API_ARGS_MAP) {
|
2020-12-25 16:05:38 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("stream_id");
|
2021-03-16 11:30:51 +08:00
|
|
|
|
auto rtp_process = RtpSelector::Instance().getProcess(allArgs["stream_id"], false);
|
|
|
|
|
if (rtp_process) {
|
|
|
|
|
rtp_process->setStopCheckRtp(false);
|
|
|
|
|
} else {
|
|
|
|
|
val["code"] = API::NotFound;
|
2020-12-25 16:05:38 +08:00
|
|
|
|
}
|
|
|
|
|
});
|
2020-09-06 17:56:05 +08:00
|
|
|
|
|
2019-12-06 11:54:10 +08:00
|
|
|
|
#endif//ENABLE_RTPPROXY
|
2019-12-05 19:53:55 +08:00
|
|
|
|
|
2020-04-05 09:26:29 +08:00
|
|
|
|
// 开始录制hls或MP4
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/startRecord",[](API_ARGS_MAP){
|
2020-04-05 09:26:29 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("type","vhost","app","stream");
|
2020-04-15 22:30:24 +08:00
|
|
|
|
auto result = Recorder::startRecord((Recorder::type) allArgs["type"].as<int>(),
|
2021-03-07 10:41:57 +08:00
|
|
|
|
allArgs["vhost"],
|
|
|
|
|
allArgs["app"],
|
|
|
|
|
allArgs["stream"],
|
|
|
|
|
allArgs["customized_path"],
|
|
|
|
|
allArgs["max_second"].as<size_t>());
|
2020-04-15 22:30:24 +08:00
|
|
|
|
val["result"] = result;
|
|
|
|
|
val["code"] = result ? API::Success : API::OtherFailed;
|
2020-04-16 21:31:07 +08:00
|
|
|
|
val["msg"] = result ? "success" : "start record failed";
|
2020-04-05 09:26:29 +08:00
|
|
|
|
});
|
2021-10-25 15:13:21 +08:00
|
|
|
|
|
|
|
|
|
//设置录像流播放速度
|
|
|
|
|
api_regist("/index/api/setRecordSpeed", [](API_ARGS_MAP) {
|
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("schema", "vhost", "app", "stream", "speed");
|
|
|
|
|
auto src = MediaSource::find(allArgs["schema"],
|
|
|
|
|
allArgs["vhost"],
|
|
|
|
|
allArgs["app"],
|
|
|
|
|
allArgs["stream"]);
|
|
|
|
|
if (src) {
|
|
|
|
|
bool flag = src->speed(allArgs["speed"].as<float>());
|
|
|
|
|
val["result"] = flag ? 0 : -1;
|
|
|
|
|
val["msg"] = flag ? "success" : "set failed";
|
|
|
|
|
val["code"] = flag ? API::Success : API::OtherFailed;
|
|
|
|
|
} else {
|
|
|
|
|
val["result"] = -2;
|
|
|
|
|
val["msg"] = "can not find the stream";
|
|
|
|
|
val["code"] = API::OtherFailed;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
api_regist("/index/api/seekRecordStamp", [](API_ARGS_MAP) {
|
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("schema", "vhost", "app", "stream", "stamp");
|
|
|
|
|
auto src = MediaSource::find(allArgs["schema"],
|
|
|
|
|
allArgs["vhost"],
|
|
|
|
|
allArgs["app"],
|
|
|
|
|
allArgs["stream"]);
|
|
|
|
|
if (src) {
|
|
|
|
|
bool flag = src->seekTo(allArgs["stamp"].as<size_t>());
|
|
|
|
|
val["result"] = flag ? 0 : -1;
|
|
|
|
|
val["msg"] = flag ? "success" : "seek failed";
|
|
|
|
|
val["code"] = flag ? API::Success : API::OtherFailed;
|
|
|
|
|
} else {
|
|
|
|
|
val["result"] = -2;
|
|
|
|
|
val["msg"] = "can not find the stream";
|
|
|
|
|
val["code"] = API::OtherFailed;
|
|
|
|
|
}
|
|
|
|
|
});
|
2020-04-05 09:26:29 +08:00
|
|
|
|
|
|
|
|
|
// 停止录制hls或MP4
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/stopRecord",[](API_ARGS_MAP){
|
2020-04-05 09:26:29 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("type","vhost","app","stream");
|
2020-04-15 22:30:24 +08:00
|
|
|
|
auto result = Recorder::stopRecord((Recorder::type) allArgs["type"].as<int>(),
|
2020-04-05 09:26:29 +08:00
|
|
|
|
allArgs["vhost"],
|
|
|
|
|
allArgs["app"],
|
|
|
|
|
allArgs["stream"]);
|
2020-04-15 22:30:24 +08:00
|
|
|
|
val["result"] = result;
|
|
|
|
|
val["code"] = result ? API::Success : API::OtherFailed;
|
2020-04-16 21:31:07 +08:00
|
|
|
|
val["msg"] = result ? "success" : "stop record failed";
|
2020-04-05 09:26:29 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 获取hls或MP4录制状态
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/isRecording",[](API_ARGS_MAP){
|
2020-04-05 09:26:29 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("type","vhost","app","stream");
|
|
|
|
|
val["status"] = Recorder::isRecording((Recorder::type) allArgs["type"].as<int>(),
|
|
|
|
|
allArgs["vhost"],
|
|
|
|
|
allArgs["app"],
|
|
|
|
|
allArgs["stream"]);
|
|
|
|
|
});
|
|
|
|
|
|
2020-03-20 11:51:24 +08:00
|
|
|
|
//获取录像文件夹列表或mp4文件列表
|
|
|
|
|
//http://127.0.0.1/index/api/getMp4RecordFile?vhost=__defaultVhost__&app=live&stream=ss&period=2020-01
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/getMp4RecordFile", [](API_ARGS_MAP){
|
2020-02-01 23:26:33 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("vhost", "app", "stream");
|
|
|
|
|
auto record_path = Recorder::getRecordPath(Recorder::type_mp4, allArgs["vhost"], allArgs["app"],allArgs["stream"]);
|
|
|
|
|
auto period = allArgs["period"];
|
|
|
|
|
|
|
|
|
|
//判断是获取mp4文件列表还是获取文件夹列表
|
|
|
|
|
bool search_mp4 = period.size() == sizeof("2020-02-01") - 1;
|
|
|
|
|
if (search_mp4) {
|
|
|
|
|
record_path = record_path + period + "/";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Json::Value paths(arrayValue);
|
|
|
|
|
//这是筛选日期,获取文件夹列表
|
|
|
|
|
File::scanDir(record_path, [&](const string &path, bool isDir) {
|
2021-01-17 18:31:50 +08:00
|
|
|
|
auto pos = path.rfind('/');
|
2020-02-01 23:26:33 +08:00
|
|
|
|
if (pos != string::npos) {
|
|
|
|
|
string relative_path = path.substr(pos + 1);
|
|
|
|
|
if (search_mp4) {
|
|
|
|
|
if (!isDir) {
|
|
|
|
|
//我们只收集mp4文件,对文件夹不感兴趣
|
|
|
|
|
paths.append(relative_path);
|
|
|
|
|
}
|
|
|
|
|
} else if (isDir && relative_path.find(period) == 0) {
|
|
|
|
|
//匹配到对应日期的文件夹
|
|
|
|
|
paths.append(relative_path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}, false);
|
2020-01-19 14:54:31 +08:00
|
|
|
|
|
2020-02-01 23:26:33 +08:00
|
|
|
|
val["data"]["rootPath"] = record_path;
|
|
|
|
|
val["data"]["paths"] = paths;
|
2020-03-20 11:51:24 +08:00
|
|
|
|
});
|
2020-01-19 14:54:31 +08:00
|
|
|
|
|
2020-06-10 10:50:37 +08:00
|
|
|
|
static auto responseSnap = [](const string &snap_path,
|
|
|
|
|
const HttpSession::KeyValue &headerIn,
|
|
|
|
|
const HttpSession::HttpResponseInvoker &invoker) {
|
|
|
|
|
StrCaseMap headerOut;
|
|
|
|
|
GET_CONFIG(string, defaultSnap, API::kDefaultSnap);
|
2022-02-23 17:06:09 +08:00
|
|
|
|
if (!File::fileSize(snap_path.data()) && !defaultSnap.empty()) {
|
2020-06-10 10:50:37 +08:00
|
|
|
|
//空文件且设置了预设图,则返回预设图片(也就是FFmpeg生成截图中空档期的默认图片)
|
|
|
|
|
const_cast<string&>(snap_path) = File::absolutePath(defaultSnap, "");
|
|
|
|
|
headerOut["Content-Type"] = HttpFileManager::getContentType(snap_path.data());
|
|
|
|
|
} else {
|
|
|
|
|
//之前生成的截图文件,我们默认为jpeg格式
|
|
|
|
|
headerOut["Content-Type"] = HttpFileManager::getContentType(".jpeg");
|
|
|
|
|
}
|
|
|
|
|
//返回图片给http客户端
|
|
|
|
|
invoker.responseFile(headerIn, headerOut, snap_path);
|
|
|
|
|
};
|
2020-05-09 00:06:36 +08:00
|
|
|
|
|
|
|
|
|
//获取截图缓存或者实时截图
|
|
|
|
|
//http://127.0.0.1/index/api/getSnap?url=rtmp://127.0.0.1/record/robot.mp4&timeout_sec=10&expire_sec=3
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/api/getSnap", [](API_ARGS_MAP_ASYNC){
|
2020-05-09 00:06:36 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("url", "timeout_sec", "expire_sec");
|
2020-06-10 10:50:37 +08:00
|
|
|
|
GET_CONFIG(string, snap_root, API::kSnapRoot);
|
|
|
|
|
|
2020-09-26 10:07:22 +08:00
|
|
|
|
bool have_old_snap = false, res_old_snap = false;
|
2020-05-09 00:06:36 +08:00
|
|
|
|
int expire_sec = allArgs["expire_sec"];
|
2020-05-09 09:29:45 +08:00
|
|
|
|
auto scan_path = File::absolutePath(MD5(allArgs["url"]).hexdigest(), snap_root) + "/";
|
2020-09-26 10:07:22 +08:00
|
|
|
|
string new_snap = StrPrinter << scan_path << time(NULL) << ".jpeg";
|
|
|
|
|
|
2020-05-09 09:29:45 +08:00
|
|
|
|
File::scanDir(scan_path, [&](const string &path, bool isDir) {
|
2020-09-26 10:07:22 +08:00
|
|
|
|
if (isDir || !end_with(path, ".jpeg")) {
|
|
|
|
|
//忽略文件夹或其他类型的文件
|
2020-05-09 09:29:45 +08:00
|
|
|
|
return true;
|
2020-05-09 00:06:36 +08:00
|
|
|
|
}
|
2020-05-09 09:29:45 +08:00
|
|
|
|
|
|
|
|
|
//找到截图
|
|
|
|
|
auto tm = FindField(path.data() + scan_path.size(), nullptr, ".jpeg");
|
|
|
|
|
if (atoll(tm.data()) + expire_sec < time(NULL)) {
|
2020-09-26 10:07:22 +08:00
|
|
|
|
//截图已经过期,改名,以便再次请求时,可以返回老截图
|
|
|
|
|
rename(path.data(), new_snap.data());
|
|
|
|
|
have_old_snap = true;
|
2020-05-09 09:29:45 +08:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-26 10:07:22 +08:00
|
|
|
|
//截图存在,且未过期,那么返回之
|
|
|
|
|
res_old_snap = true;
|
2021-08-12 16:07:31 +08:00
|
|
|
|
responseSnap(path, allArgs.getParser().getHeader(), invoker);
|
2020-09-26 10:07:22 +08:00
|
|
|
|
//中断遍历
|
2020-05-09 09:29:45 +08:00
|
|
|
|
return false;
|
2020-05-09 00:06:36 +08:00
|
|
|
|
});
|
|
|
|
|
|
2020-09-26 10:07:22 +08:00
|
|
|
|
if (res_old_snap) {
|
|
|
|
|
//已经回复了旧的截图
|
2020-06-10 10:33:48 +08:00
|
|
|
|
return;
|
2020-05-09 00:06:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//无截图或者截图已经过期
|
2020-09-26 10:07:22 +08:00
|
|
|
|
if (!have_old_snap) {
|
|
|
|
|
//无过期截图,生成一个空文件,目的是顺便创建文件夹路径
|
|
|
|
|
//同时防止在FFmpeg生成截图途中不停的尝试调用该api多次启动FFmpeg进程
|
|
|
|
|
auto file = File::create_file(new_snap.data(), "wb");
|
|
|
|
|
if (file) {
|
|
|
|
|
fclose(file);
|
|
|
|
|
}
|
2020-05-09 09:39:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-26 10:07:22 +08:00
|
|
|
|
//启动FFmpeg进程,开始截图,生成临时文件,截图成功后替换为正式文件
|
|
|
|
|
auto new_snap_tmp = new_snap + ".tmp";
|
2021-08-12 16:07:31 +08:00
|
|
|
|
FFmpegSnap::makeSnap(allArgs["url"], new_snap_tmp, allArgs["timeout_sec"], [invoker, allArgs, new_snap, new_snap_tmp](bool success) {
|
2020-09-26 10:07:22 +08:00
|
|
|
|
if (!success) {
|
2020-05-09 00:06:36 +08:00
|
|
|
|
//生成截图失败,可能残留空文件
|
2020-09-26 10:07:22 +08:00
|
|
|
|
File::delete_file(new_snap_tmp.data());
|
|
|
|
|
} else {
|
|
|
|
|
//临时文件改成正式文件
|
|
|
|
|
File::delete_file(new_snap.data());
|
|
|
|
|
rename(new_snap_tmp.data(), new_snap.data());
|
2020-05-09 00:06:36 +08:00
|
|
|
|
}
|
2021-08-12 16:07:31 +08:00
|
|
|
|
responseSnap(new_snap, allArgs.getParser().getHeader(), invoker);
|
2020-05-09 00:06:36 +08:00
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2021-12-27 17:40:15 +08:00
|
|
|
|
api_regist("/index/api/getStatistic",[](API_ARGS_MAP_ASYNC){
|
2021-01-23 09:44:37 +08:00
|
|
|
|
CHECK_SECRET();
|
2021-12-27 17:40:15 +08:00
|
|
|
|
getStatisticJson([headerOut, val, invoker](const Value &data) mutable{
|
|
|
|
|
val["data"] = data;
|
|
|
|
|
invoker(200, headerOut, val.toStyledString());
|
|
|
|
|
});
|
2021-01-23 09:44:37 +08:00
|
|
|
|
});
|
|
|
|
|
|
2021-03-24 16:52:41 +08:00
|
|
|
|
#ifdef ENABLE_WEBRTC
|
2021-10-19 15:23:12 +08:00
|
|
|
|
class WebRtcArgsImp : public WebRtcArgs {
|
|
|
|
|
public:
|
|
|
|
|
WebRtcArgsImp(const HttpAllArgs<string> &args) : _args(args) {}
|
|
|
|
|
~WebRtcArgsImp() override = default;
|
|
|
|
|
|
|
|
|
|
variant operator[](const string &key) const override {
|
|
|
|
|
if (key == "url") {
|
|
|
|
|
return getUrl();
|
2021-04-04 23:20:10 +08:00
|
|
|
|
}
|
2021-10-19 15:23:12 +08:00
|
|
|
|
return _args[key];
|
2021-04-04 23:20:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-19 15:23:12 +08:00
|
|
|
|
private:
|
|
|
|
|
string getUrl() const{
|
|
|
|
|
auto &allArgs = _args;
|
2021-10-16 17:07:21 +08:00
|
|
|
|
CHECK_ARGS("app", "stream");
|
|
|
|
|
|
2021-10-19 15:23:12 +08:00
|
|
|
|
return StrPrinter << RTC_SCHEMA << "://" << _args["Host"] << "/" << _args["app"] << "/"
|
|
|
|
|
<< _args["stream"] << "?" << _args.getParser().Params();
|
2021-04-04 23:20:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-19 15:23:12 +08:00
|
|
|
|
private:
|
|
|
|
|
HttpAllArgs<string> _args;
|
|
|
|
|
};
|
2021-10-16 10:52:28 +08:00
|
|
|
|
|
2021-10-19 15:23:12 +08:00
|
|
|
|
api_regist("/index/api/webrtc",[](API_ARGS_STRING_ASYNC){
|
|
|
|
|
CHECK_ARGS("type");
|
|
|
|
|
auto type = allArgs["type"];
|
|
|
|
|
auto offer = allArgs.getArgs();
|
|
|
|
|
CHECK(!offer.empty(), "http body(webrtc offer sdp) is empty");
|
|
|
|
|
|
|
|
|
|
WebRtcPluginManager::Instance().getAnswerSdp(*(static_cast<Session *>(&sender)), type, offer, WebRtcArgsImp(allArgs),
|
|
|
|
|
[invoker, val, offer, headerOut](const WebRtcInterface &exchanger) mutable {
|
|
|
|
|
//设置返回类型
|
|
|
|
|
headerOut["Content-Type"] = HttpFileManager::getContentType(".json");
|
|
|
|
|
//设置跨域
|
|
|
|
|
headerOut["Access-Control-Allow-Origin"] = "*";
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
val["sdp"] = const_cast<WebRtcInterface &>(exchanger).getAnswerSdp(offer);
|
|
|
|
|
val["id"] = exchanger.getIdentifier();
|
|
|
|
|
val["type"] = "answer";
|
|
|
|
|
invoker(200, headerOut, val.toStyledString());
|
|
|
|
|
} catch (std::exception &ex) {
|
|
|
|
|
val["code"] = API::Exception;
|
|
|
|
|
val["msg"] = ex.what();
|
|
|
|
|
invoker(200, headerOut, val.toStyledString());
|
|
|
|
|
}
|
|
|
|
|
});
|
2021-03-24 16:52:41 +08:00
|
|
|
|
});
|
|
|
|
|
#endif
|
|
|
|
|
|
2019-05-20 11:22:59 +08:00
|
|
|
|
////////////以下是注册的Hook API////////////
|
2021-10-21 10:50:22 +08:00
|
|
|
|
api_regist("/index/hook/on_publish",[](API_ARGS_JSON){
|
2019-05-20 11:22:59 +08:00
|
|
|
|
//开始推流事件
|
2019-09-10 11:06:31 +08:00
|
|
|
|
//转换hls
|
|
|
|
|
val["enableHls"] = true;
|
|
|
|
|
//不录制mp4
|
|
|
|
|
val["enableMP4"] = false;
|
2019-05-20 11:22:59 +08:00
|
|
|
|
});
|
|
|
|
|
|
2021-10-21 10:50:22 +08:00
|
|
|
|
api_regist("/index/hook/on_play",[](API_ARGS_JSON){
|
2019-05-20 11:22:59 +08:00
|
|
|
|
//开始播放事件
|
|
|
|
|
});
|
|
|
|
|
|
2021-10-21 10:50:22 +08:00
|
|
|
|
api_regist("/index/hook/on_flow_report",[](API_ARGS_JSON){
|
2019-05-20 11:22:59 +08:00
|
|
|
|
//流量统计hook api
|
|
|
|
|
});
|
2019-05-20 16:26:04 +08:00
|
|
|
|
|
2021-10-21 10:50:22 +08:00
|
|
|
|
api_regist("/index/hook/on_rtsp_realm",[](API_ARGS_JSON){
|
2019-05-27 13:56:37 +08:00
|
|
|
|
//rtsp是否需要鉴权,默认需要鉴权
|
2019-05-28 09:38:15 +08:00
|
|
|
|
val["code"] = API::Success;
|
2019-05-20 16:26:04 +08:00
|
|
|
|
val["realm"] = "zlmediakit_reaml";
|
|
|
|
|
});
|
|
|
|
|
|
2021-10-21 10:50:22 +08:00
|
|
|
|
api_regist("/index/hook/on_rtsp_auth",[](API_ARGS_JSON){
|
2019-05-20 16:26:04 +08:00
|
|
|
|
//rtsp鉴权密码,密码等于用户名
|
|
|
|
|
//rtsp可以有双重鉴权!后面还会触发on_play事件
|
2019-05-20 16:53:29 +08:00
|
|
|
|
CHECK_ARGS("user_name");
|
2019-05-28 09:38:15 +08:00
|
|
|
|
val["code"] = API::Success;
|
2019-05-20 16:26:04 +08:00
|
|
|
|
val["encrypted"] = false;
|
2019-05-20 16:32:28 +08:00
|
|
|
|
val["passwd"] = allArgs["user_name"].data();
|
2019-05-20 16:26:04 +08:00
|
|
|
|
});
|
|
|
|
|
|
2021-04-08 17:34:13 +08:00
|
|
|
|
api_regist("/index/hook/on_stream_changed",[](API_ARGS_JSON){
|
2019-05-20 16:26:04 +08:00
|
|
|
|
//媒体注册或反注册事件
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-15 17:07:10 +08:00
|
|
|
|
|
|
|
|
|
#if !defined(_WIN32)
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/hook/on_stream_not_found_ffmpeg",[](API_ARGS_MAP_ASYNC){
|
2019-05-27 23:07:12 +08:00
|
|
|
|
//媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流
|
2019-05-29 18:30:43 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("vhost","app","stream");
|
2019-06-10 14:13:43 +08:00
|
|
|
|
//通过FFmpeg按需拉流
|
|
|
|
|
GET_CONFIG(int,rtmp_port,Rtmp::kPort);
|
2019-06-24 14:51:49 +08:00
|
|
|
|
GET_CONFIG(int,timeout_sec,Hook::kTimeoutSec);
|
|
|
|
|
|
2019-06-06 18:28:33 +08:00
|
|
|
|
string dst_url = StrPrinter
|
|
|
|
|
<< "rtmp://127.0.0.1:"
|
|
|
|
|
<< rtmp_port << "/"
|
|
|
|
|
<< allArgs["app"] << "/"
|
|
|
|
|
<< allArgs["stream"] << "?vhost="
|
|
|
|
|
<< allArgs["vhost"];
|
|
|
|
|
|
2021-01-23 09:42:15 +08:00
|
|
|
|
addFFmpegSource("", "http://hls-ott-zhibo.wasu.tv/live/272/index.m3u8",/** ffmpeg拉流支持任意编码格式任意协议 **/
|
2019-06-06 18:28:33 +08:00
|
|
|
|
dst_url,
|
2019-06-24 14:51:49 +08:00
|
|
|
|
(1000 * timeout_sec) - 500,
|
2020-12-20 20:25:44 +08:00
|
|
|
|
false,
|
|
|
|
|
false,
|
2021-03-14 10:29:17 +08:00
|
|
|
|
[invoker,val,headerOut](const SockException &ex,const string &key) mutable{
|
2019-06-06 18:28:33 +08:00
|
|
|
|
if(ex){
|
2021-03-14 10:29:17 +08:00
|
|
|
|
val["code"] = API::OtherFailed;
|
|
|
|
|
val["msg"] = ex.what();
|
2019-06-06 18:28:33 +08:00
|
|
|
|
}else{
|
2021-03-14 10:29:17 +08:00
|
|
|
|
val["data"]["key"] = key;
|
2019-06-06 18:28:33 +08:00
|
|
|
|
}
|
2021-01-02 21:24:06 +08:00
|
|
|
|
invoker(200, headerOut, val.toStyledString());
|
2019-06-06 18:28:33 +08:00
|
|
|
|
});
|
2019-06-15 17:07:10 +08:00
|
|
|
|
});
|
2019-07-09 14:30:00 +08:00
|
|
|
|
#endif//!defined(_WIN32)
|
2019-06-15 17:07:10 +08:00
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/hook/on_stream_not_found",[](API_ARGS_MAP_ASYNC){
|
2019-06-15 17:07:10 +08:00
|
|
|
|
//媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流
|
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("vhost","app","stream");
|
2019-06-10 14:13:43 +08:00
|
|
|
|
//通过内置支持的rtsp/rtmp按需拉流
|
|
|
|
|
addStreamProxy(allArgs["vhost"],
|
|
|
|
|
allArgs["app"],
|
|
|
|
|
allArgs["stream"],
|
|
|
|
|
/** 支持rtsp和rtmp方式拉流 ,rtsp支持h265/h264/aac,rtmp仅支持h264/aac **/
|
2019-07-22 11:27:17 +08:00
|
|
|
|
"rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov",
|
2021-06-17 10:39:22 +08:00
|
|
|
|
-1,/*无限重试*/
|
2019-07-22 11:27:17 +08:00
|
|
|
|
true,/* 开启hls转发 */
|
|
|
|
|
false,/* 禁用MP4录制 */
|
2019-06-10 14:13:43 +08:00
|
|
|
|
0,//rtp over tcp方式拉流
|
2021-04-30 18:01:48 +08:00
|
|
|
|
10,//10秒超时
|
2021-03-14 10:29:17 +08:00
|
|
|
|
[invoker,val,headerOut](const SockException &ex,const string &key) mutable{
|
2019-06-10 14:13:43 +08:00
|
|
|
|
if(ex){
|
2021-03-14 10:29:17 +08:00
|
|
|
|
val["code"] = API::OtherFailed;
|
|
|
|
|
val["msg"] = ex.what();
|
2019-06-10 14:13:43 +08:00
|
|
|
|
}else{
|
2021-03-14 10:29:17 +08:00
|
|
|
|
val["data"]["key"] = key;
|
2019-06-10 14:13:43 +08:00
|
|
|
|
}
|
2021-01-02 21:24:06 +08:00
|
|
|
|
invoker(200, headerOut, val.toStyledString());
|
2019-06-10 14:13:43 +08:00
|
|
|
|
});
|
2019-05-20 16:26:04 +08:00
|
|
|
|
});
|
|
|
|
|
|
2021-10-21 10:50:22 +08:00
|
|
|
|
api_regist("/index/hook/on_record_mp4",[](API_ARGS_JSON){
|
2019-05-20 16:26:04 +08:00
|
|
|
|
//录制mp4分片完毕事件
|
|
|
|
|
});
|
|
|
|
|
|
2021-10-21 10:50:22 +08:00
|
|
|
|
api_regist("/index/hook/on_shell_login",[](API_ARGS_JSON){
|
2019-05-27 13:56:37 +08:00
|
|
|
|
//shell登录调试事件
|
|
|
|
|
});
|
2019-05-27 18:39:43 +08:00
|
|
|
|
|
2021-10-21 10:50:22 +08:00
|
|
|
|
api_regist("/index/hook/on_stream_none_reader",[](API_ARGS_JSON){
|
2019-05-27 18:39:43 +08:00
|
|
|
|
//无人观看流默认关闭
|
|
|
|
|
val["close"] = true;
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-13 13:02:30 +08:00
|
|
|
|
static auto checkAccess = [](const string ¶ms){
|
|
|
|
|
//我们假定大家都要权限访问
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
2020-12-27 22:14:59 +08:00
|
|
|
|
api_regist("/index/hook/on_http_access",[](API_ARGS_MAP){
|
2019-06-13 13:02:30 +08:00
|
|
|
|
//在这里根据allArgs["params"](url参数)来判断该http客户端是否有权限访问该文件
|
|
|
|
|
if(!checkAccess(allArgs["params"])){
|
|
|
|
|
//无访问权限
|
2019-06-14 18:42:09 +08:00
|
|
|
|
val["err"] = "无访问权限";
|
|
|
|
|
//仅限制访问当前目录
|
2019-06-13 13:02:30 +08:00
|
|
|
|
val["path"] = "";
|
2019-06-14 18:42:09 +08:00
|
|
|
|
//标记该客户端无权限1分钟
|
2019-06-13 13:02:30 +08:00
|
|
|
|
val["second"] = 60;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-14 18:42:09 +08:00
|
|
|
|
//可以访问
|
|
|
|
|
val["err"] = "";
|
|
|
|
|
//只能访问当前目录
|
|
|
|
|
val["path"] = "";
|
|
|
|
|
//该http客户端用户被授予10分钟的访问权限,该权限仅限访问当前目录
|
2019-06-13 13:02:30 +08:00
|
|
|
|
val["second"] = 10 * 60;
|
2019-06-12 17:53:48 +08:00
|
|
|
|
});
|
|
|
|
|
|
2021-10-21 10:50:22 +08:00
|
|
|
|
api_regist("/index/hook/on_server_started",[](API_ARGS_JSON){
|
2019-11-29 10:12:20 +08:00
|
|
|
|
//服务器重启报告
|
|
|
|
|
});
|
2021-10-21 10:50:22 +08:00
|
|
|
|
|
|
|
|
|
api_regist("/index/hook/on_server_keepalive",[](API_ARGS_JSON){
|
|
|
|
|
//心跳hook
|
|
|
|
|
});
|
2019-05-20 17:03:04 +08:00
|
|
|
|
}
|
2019-05-20 16:26:04 +08:00
|
|
|
|
|
2019-05-20 17:03:04 +08:00
|
|
|
|
void unInstallWebApi(){
|
2019-06-06 18:28:33 +08:00
|
|
|
|
{
|
|
|
|
|
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
|
|
|
|
s_proxyMap.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
lock_guard<recursive_mutex> lck(s_ffmpegMapMtx);
|
|
|
|
|
s_ffmpegMap.clear();
|
|
|
|
|
}
|
2020-10-24 23:29:44 +08:00
|
|
|
|
|
2020-07-02 22:23:43 +08:00
|
|
|
|
{
|
|
|
|
|
#if defined(ENABLE_RTPPROXY)
|
2020-11-21 22:32:01 +08:00
|
|
|
|
RtpSelector::Instance().clear();
|
2020-07-02 22:23:43 +08:00
|
|
|
|
lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
|
|
|
|
|
s_rtpServerMap.clear();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2021-10-25 15:13:21 +08:00
|
|
|
|
}
|