2019-06-15 17:07:10 +08:00
|
|
|
|
/*
|
2019-06-11 09:25:54 +08:00
|
|
|
|
* MIT License
|
|
|
|
|
*
|
|
|
|
|
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
|
|
|
|
*
|
|
|
|
|
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
|
|
|
|
*
|
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
|
*
|
|
|
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
|
|
|
* copies or substantial portions of the Software.
|
|
|
|
|
*
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
|
* SOFTWARE.
|
|
|
|
|
*/
|
|
|
|
|
|
2019-05-20 11:22:59 +08:00
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <functional>
|
|
|
|
|
#include <sstream>
|
|
|
|
|
#include <unordered_map>
|
|
|
|
|
#include "jsoncpp/json.h"
|
|
|
|
|
#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"
|
2019-05-20 16:26:04 +08:00
|
|
|
|
#include "Player/PlayerProxy.h"
|
2019-06-06 18:28:33 +08:00
|
|
|
|
#include "Util/MD5.h"
|
|
|
|
|
#include "WebApi.h"
|
2019-06-24 14:51:49 +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"
|
|
|
|
|
|
2019-05-20 11:22:59 +08:00
|
|
|
|
using namespace Json;
|
|
|
|
|
using namespace toolkit;
|
|
|
|
|
using namespace mediakit;
|
|
|
|
|
|
2019-05-20 16:26:04 +08:00
|
|
|
|
|
|
|
|
|
typedef map<string,variant,StrCaseCompare> ApiArgsType;
|
|
|
|
|
|
|
|
|
|
|
2019-05-27 14:21:38 +08:00
|
|
|
|
#define API_ARGS TcpSession &sender, \
|
|
|
|
|
HttpSession::KeyValue &headerIn, \
|
2019-05-20 11:22:59 +08:00
|
|
|
|
HttpSession::KeyValue &headerOut, \
|
2019-05-20 16:26:04 +08:00
|
|
|
|
ApiArgsType &allArgs, \
|
2019-05-20 11:22:59 +08:00
|
|
|
|
Json::Value &val
|
|
|
|
|
|
|
|
|
|
#define API_REGIST(field, name, ...) \
|
|
|
|
|
s_map_api.emplace("/index/"#field"/"#name,[](API_ARGS,const HttpSession::HttpResponseInvoker &invoker){ \
|
|
|
|
|
static auto lam = [&](API_ARGS) __VA_ARGS__ ; \
|
2019-05-27 14:21:38 +08:00
|
|
|
|
lam(sender,headerIn, headerOut, allArgs, val); \
|
2019-05-20 11:22:59 +08:00
|
|
|
|
invoker("200 OK", headerOut, val.toStyledString()); \
|
|
|
|
|
});
|
|
|
|
|
|
2019-11-13 14:11:49 +08:00
|
|
|
|
#define API_ARGS_VALUE sender,headerIn,headerOut,allArgs,val,invoker
|
|
|
|
|
|
2019-05-20 11:22:59 +08:00
|
|
|
|
#define API_REGIST_INVOKER(field, name, ...) \
|
|
|
|
|
s_map_api.emplace("/index/"#field"/"#name,[](API_ARGS,const HttpSession::HttpResponseInvoker &invoker) __VA_ARGS__);
|
|
|
|
|
|
|
|
|
|
//异步http api lambad定义
|
|
|
|
|
typedef std::function<void(API_ARGS,const HttpSession::HttpResponseInvoker &invoker)> AsyncHttpApi;
|
|
|
|
|
//api列表
|
|
|
|
|
static map<string, AsyncHttpApi> s_map_api;
|
|
|
|
|
|
|
|
|
|
namespace API {
|
|
|
|
|
typedef enum {
|
2019-05-27 13:56:37 +08:00
|
|
|
|
InvalidArgs = -300,
|
2019-05-20 11:22:59 +08:00
|
|
|
|
SqlFailed = -200,
|
|
|
|
|
AuthFailed = -100,
|
|
|
|
|
OtherFailed = -1,
|
|
|
|
|
Success = 0
|
|
|
|
|
} ApiErr;
|
|
|
|
|
|
|
|
|
|
#define API_FIELD "api."
|
2019-06-24 14:51:49 +08:00
|
|
|
|
const string kApiDebug = API_FIELD"apiDebug";
|
|
|
|
|
const string kSecret = API_FIELD"secret";
|
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";
|
2019-05-20 11:22:59 +08:00
|
|
|
|
});
|
|
|
|
|
}//namespace API
|
|
|
|
|
|
|
|
|
|
class ApiRetException: public std::runtime_error {
|
|
|
|
|
public:
|
|
|
|
|
ApiRetException(const char *str = "success" ,int code = API::Success):runtime_error(str){
|
|
|
|
|
_code = code;
|
|
|
|
|
}
|
|
|
|
|
~ApiRetException() = default;
|
|
|
|
|
int code(){ return _code; }
|
|
|
|
|
private:
|
|
|
|
|
int _code;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class AuthException : public ApiRetException {
|
|
|
|
|
public:
|
|
|
|
|
AuthException(const char *str):ApiRetException(str,API::AuthFailed){}
|
|
|
|
|
~AuthException() = default;
|
|
|
|
|
};
|
|
|
|
|
|
2019-05-27 13:56:37 +08:00
|
|
|
|
class InvalidArgsException: public ApiRetException {
|
2019-05-20 16:53:29 +08:00
|
|
|
|
public:
|
2019-05-27 13:56:37 +08:00
|
|
|
|
InvalidArgsException(const char *str):ApiRetException(str,API::InvalidArgs){}
|
|
|
|
|
~InvalidArgsException() = default;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class SuccessException: public ApiRetException {
|
|
|
|
|
public:
|
|
|
|
|
SuccessException():ApiRetException("success",API::Success){}
|
|
|
|
|
~SuccessException() = default;
|
2019-05-20 16:53:29 +08:00
|
|
|
|
};
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
return std::move(allArgs);
|
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()) {
|
|
|
|
|
consumed = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
//该api已被消费
|
|
|
|
|
consumed = true;
|
2019-05-20 17:34:39 +08:00
|
|
|
|
//执行API
|
|
|
|
|
Json::Value val;
|
|
|
|
|
val["code"] = API::Success;
|
|
|
|
|
HttpSession::KeyValue headerOut;
|
|
|
|
|
auto allArgs = getAllArgs(parser);
|
|
|
|
|
HttpSession::KeyValue &headerIn = parser.getValues();
|
|
|
|
|
headerOut["Content-Type"] = "application/json; charset=utf-8";
|
|
|
|
|
if(api_debug){
|
|
|
|
|
auto newInvoker = [invoker,parser,allArgs](const string &codeOut,
|
|
|
|
|
const HttpSession::KeyValue &headerOut,
|
2019-10-28 17:23:16 +08:00
|
|
|
|
const HttpBody::Ptr &body){
|
2019-05-20 17:34:39 +08:00
|
|
|
|
stringstream ss;
|
|
|
|
|
for(auto &pr : allArgs ){
|
|
|
|
|
ss << pr.first << " : " << pr.second << "\r\n";
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-28 17:23:16 +08:00
|
|
|
|
//body默认为空
|
|
|
|
|
int64_t size = 0;
|
|
|
|
|
if (body && body->remainSize()) {
|
|
|
|
|
//有body,获取body大小
|
|
|
|
|
size = body->remainSize();
|
|
|
|
|
}
|
2019-05-20 17:34:39 +08:00
|
|
|
|
|
2019-10-28 17:23:16 +08:00
|
|
|
|
if(size < 4 * 1024){
|
|
|
|
|
string contentOut = body->readData(size)->toString();
|
|
|
|
|
DebugL << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n"
|
|
|
|
|
<< "# content:\r\n" << parser.Content() << "\r\n"
|
|
|
|
|
<< "# args:\r\n" << ss.str()
|
|
|
|
|
<< "# response:\r\n"
|
|
|
|
|
<< contentOut << "\r\n";
|
|
|
|
|
invoker(codeOut,headerOut,contentOut);
|
|
|
|
|
} else{
|
|
|
|
|
DebugL << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n"
|
|
|
|
|
<< "# content:\r\n" << parser.Content() << "\r\n"
|
|
|
|
|
<< "# args:\r\n" << ss.str()
|
|
|
|
|
<< "# response size:"
|
|
|
|
|
<< size <<"\r\n";
|
|
|
|
|
invoker(codeOut,headerOut,body);
|
|
|
|
|
}
|
2019-05-20 17:34:39 +08:00
|
|
|
|
};
|
|
|
|
|
((HttpSession::HttpResponseInvoker &)invoker) = newInvoker;
|
|
|
|
|
}
|
2019-05-20 11:22:59 +08:00
|
|
|
|
|
2019-05-20 17:34:39 +08:00
|
|
|
|
try {
|
2019-05-27 14:21:38 +08:00
|
|
|
|
it->second(sender,headerIn, headerOut, allArgs, val, invoker);
|
2019-05-20 17:34:39 +08:00
|
|
|
|
} catch(ApiRetException &ex){
|
|
|
|
|
val["code"] = ex.code();
|
|
|
|
|
val["msg"] = ex.what();
|
|
|
|
|
invoker("200 OK", headerOut, val.toStyledString());
|
2019-05-21 09:26:24 +08:00
|
|
|
|
}
|
|
|
|
|
#ifdef ENABLE_MYSQL
|
|
|
|
|
catch(SqlException &ex){
|
2019-05-20 17:34:39 +08:00
|
|
|
|
val["code"] = API::SqlFailed;
|
|
|
|
|
val["msg"] = StrPrinter << "操作数据库失败:" << ex.what() << ":" << ex.getSql();
|
|
|
|
|
WarnL << ex.what() << ":" << ex.getSql();
|
|
|
|
|
invoker("200 OK", headerOut, val.toStyledString());
|
2019-05-21 09:26:24 +08:00
|
|
|
|
}
|
|
|
|
|
#endif// ENABLE_MYSQL
|
|
|
|
|
catch (std::exception &ex) {
|
2019-05-20 17:34:39 +08:00
|
|
|
|
val["code"] = API::OtherFailed;
|
|
|
|
|
val["msg"] = ex.what();
|
|
|
|
|
invoker("200 OK", headerOut, val.toStyledString());
|
|
|
|
|
}
|
2019-05-20 11:22:59 +08:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-20 16:53:29 +08:00
|
|
|
|
template <typename Args,typename First>
|
2019-05-20 16:55:09 +08:00
|
|
|
|
bool checkArgs(Args &&args,First &&first){
|
2019-05-20 16:53:29 +08:00
|
|
|
|
return !args[first].empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename Args,typename First,typename ...KeyTypes>
|
2019-05-20 16:55:09 +08:00
|
|
|
|
bool checkArgs(Args &&args,First &&first,KeyTypes && ...keys){
|
|
|
|
|
return !args[first].empty() && checkArgs(std::forward<Args>(args),std::forward<KeyTypes>(keys)...);
|
2019-05-20 16:53:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define CHECK_ARGS(...) \
|
2019-05-20 16:55:09 +08:00
|
|
|
|
if(!checkArgs(allArgs,##__VA_ARGS__)){ \
|
2019-05-27 13:56:37 +08:00
|
|
|
|
throw InvalidArgsException("缺少必要参数:" #__VA_ARGS__); \
|
2019-05-20 16:53:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-20 17:12:00 +08:00
|
|
|
|
#define CHECK_SECRET() \
|
2019-05-27 14:21:38 +08:00
|
|
|
|
if(sender.get_peer_ip() != "127.0.0.1"){ \
|
|
|
|
|
CHECK_ARGS("secret"); \
|
|
|
|
|
if(api_secret != allArgs["secret"]){ \
|
|
|
|
|
throw AuthException("secret错误"); \
|
|
|
|
|
} \
|
2019-05-20 17:12:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-20 18:17:53 +08:00
|
|
|
|
static unordered_map<string ,PlayerProxy::Ptr> s_proxyMap;
|
2019-05-20 17:03:04 +08:00
|
|
|
|
static recursive_mutex s_proxyMapMtx;
|
2019-05-20 18:17:53 +08:00
|
|
|
|
static inline string getProxyKey(const string &vhost,const string &app,const string &stream){
|
|
|
|
|
return vhost + "/" + app + "/" + stream;
|
|
|
|
|
}
|
2019-05-20 17:03:04 +08:00
|
|
|
|
|
2019-06-06 18:28:33 +08:00
|
|
|
|
static unordered_map<string ,FFmpegSource::Ptr> s_ffmpegMap;
|
|
|
|
|
static recursive_mutex s_ffmpegMapMtx;
|
|
|
|
|
|
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
|
2019-05-20 11:22:59 +08:00
|
|
|
|
API_REGIST_INVOKER(api, getThreadsLoad, {
|
|
|
|
|
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;
|
2019-10-24 11:21:55 +08:00
|
|
|
|
invoker("200 OK", headerOut, val.toStyledString());
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//获取后台工作线程负载
|
|
|
|
|
//测试url http://127.0.0.1/index/api/getWorkThreadsLoad
|
|
|
|
|
API_REGIST_INVOKER(api, getWorkThreadsLoad, {
|
|
|
|
|
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;
|
2019-05-20 11:22:59 +08:00
|
|
|
|
invoker("200 OK", headerOut, val.toStyledString());
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2019-05-27 15:09:44 +08:00
|
|
|
|
//获取服务器配置
|
|
|
|
|
//测试url http://127.0.0.1/index/api/getServerConfig
|
2019-05-20 11:22:59 +08:00
|
|
|
|
API_REGIST(api, getServerConfig, {
|
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方式传参
|
2019-05-20 11:22:59 +08:00
|
|
|
|
API_REGIST(api, setServerConfig, {
|
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;
|
2019-05-20 11:22:59 +08:00
|
|
|
|
for (auto &pr : allArgs) {
|
|
|
|
|
if (ini.find(pr.first) == ini.end()) {
|
|
|
|
|
//没有这个key
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
//获取服务器api列表
|
|
|
|
|
//测试url http://127.0.0.1/index/api/getApiList
|
2019-05-20 11:22:59 +08:00
|
|
|
|
API_REGIST(api,getApiList,{
|
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);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
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
|
2019-05-20 11:22:59 +08:00
|
|
|
|
API_REGIST(api,restartServer,{
|
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"] = "服务器将在一秒后自动重启";
|
|
|
|
|
});
|
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
|
2019-05-20 11:22:59 +08:00
|
|
|
|
API_REGIST(api,getMediaList,{
|
2019-05-20 17:12:00 +08:00
|
|
|
|
CHECK_SECRET();
|
2019-05-20 11:22:59 +08:00
|
|
|
|
//获取所有MediaSource列表
|
2019-12-03 16:10:02 +08:00
|
|
|
|
MediaSource::for_each_media([&](const MediaSource::Ptr &media){
|
|
|
|
|
if(!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()){
|
2019-05-20 11:22:59 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
2019-12-03 16:10:02 +08:00
|
|
|
|
if(!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()){
|
2019-05-20 11:22:59 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
2019-12-03 16:10:02 +08:00
|
|
|
|
if(!allArgs["app"].empty() && allArgs["app"] != media->getApp()){
|
2019-05-20 11:22:59 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Value item;
|
2019-12-03 16:10:02 +08:00
|
|
|
|
item["schema"] = media->getSchema();
|
|
|
|
|
item["vhost"] = media->getVhost();
|
|
|
|
|
item["app"] = media->getApp();
|
|
|
|
|
item["stream"] = media->getId();
|
2019-12-13 13:35:27 +08:00
|
|
|
|
item["readerCount"] = media->readerCount();
|
2019-12-28 16:57:35 +08:00
|
|
|
|
item["totalReaderCount"] = media->totalReaderCount();
|
2019-12-13 13:35:27 +08:00
|
|
|
|
for(auto &track : media->getTracks()){
|
|
|
|
|
Value obj;
|
|
|
|
|
obj["codec_id"] = track->getCodecId();
|
|
|
|
|
obj["codec_type"] = track->getTrackType();
|
|
|
|
|
obj["ready"] = track->ready();
|
|
|
|
|
item["tracks"].append(obj);
|
|
|
|
|
}
|
2019-05-20 16:26:04 +08:00
|
|
|
|
val["data"].append(item);
|
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
|
|
|
|
|
API_REGIST(api,isMediaOnline,{
|
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("schema","vhost","app","stream");
|
|
|
|
|
val["online"] = (bool) (MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"],false));
|
|
|
|
|
});
|
|
|
|
|
|
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
|
|
|
|
|
API_REGIST(api,getMediaInfo,{
|
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("schema","vhost","app","stream");
|
|
|
|
|
auto src = MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"],false);
|
|
|
|
|
if(!src){
|
|
|
|
|
val["online"] = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
val["online"] = true;
|
|
|
|
|
val["readerCount"] = src->readerCount();
|
2019-12-28 16:57:35 +08:00
|
|
|
|
val["totalReaderCount"] = src->totalReaderCount();
|
2019-12-13 13:35:27 +08:00
|
|
|
|
for(auto &track : src->getTracks()){
|
|
|
|
|
Value obj;
|
|
|
|
|
obj["codec_id"] = track->getCodecId();
|
|
|
|
|
obj["codec_type"] = track->getTrackType();
|
|
|
|
|
obj["ready"] = track->ready();
|
|
|
|
|
val["tracks"].append(obj);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
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
|
2019-05-27 14:14:42 +08:00
|
|
|
|
API_REGIST(api,close_stream,{
|
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"]);
|
|
|
|
|
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";
|
2019-05-20 11:22:59 +08:00
|
|
|
|
}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";
|
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
|
|
|
|
|
API_REGIST(api,close_streams,{
|
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
//筛选命中个数
|
|
|
|
|
int count_hit = 0;
|
|
|
|
|
int count_closed = 0;
|
2019-11-19 10:55:44 +08:00
|
|
|
|
list<MediaSource::Ptr> media_list;
|
2019-12-03 16:10:02 +08:00
|
|
|
|
MediaSource::for_each_media([&](const MediaSource::Ptr &media){
|
|
|
|
|
if(!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()){
|
2019-11-18 10:46:59 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
2019-12-03 16:10:02 +08:00
|
|
|
|
if(!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()){
|
2019-11-18 10:46:59 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
2019-12-03 16:10:02 +08:00
|
|
|
|
if(!allArgs["app"].empty() && allArgs["app"] != media->getApp()){
|
2019-11-18 10:46:59 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
2019-12-03 16:10:02 +08:00
|
|
|
|
if(!allArgs["stream"].empty() && allArgs["stream"] != media->getId()){
|
2019-11-18 10:46:59 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
++count_hit;
|
2019-11-19 10:55:44 +08:00
|
|
|
|
media_list.emplace_back(media);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
API_REGIST(api,getAllSession,{
|
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
Value jsession;
|
|
|
|
|
uint16_t local_port = allArgs["local_port"].as<uint16_t>();
|
|
|
|
|
string &peer_ip = allArgs["peer_ip"];
|
|
|
|
|
|
|
|
|
|
SessionMap::Instance().for_each_session([&](const string &id,const TcpSession::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
|
2019-05-20 11:22:59 +08:00
|
|
|
|
API_REGIST(api,kick_session,{
|
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
|
|
|
|
|
API_REGIST(api,kick_sessions,{
|
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
uint16_t local_port = allArgs["local_port"].as<uint16_t>();
|
|
|
|
|
string &peer_ip = allArgs["peer_ip"];
|
|
|
|
|
uint64_t count_hit = 0;
|
|
|
|
|
|
2019-11-19 10:55:44 +08:00
|
|
|
|
list<TcpSession::Ptr> session_list;
|
2019-11-18 16:34:39 +08:00
|
|
|
|
SessionMap::Instance().for_each_session([&](const string &id,const TcpSession::Ptr &session){
|
|
|
|
|
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;
|
|
|
|
|
});
|
|
|
|
|
|
2019-05-27 23:07:12 +08:00
|
|
|
|
static auto addStreamProxy = [](const string &vhost,
|
|
|
|
|
const string &app,
|
|
|
|
|
const string &stream,
|
|
|
|
|
const string &url,
|
2019-07-22 11:27:17 +08:00
|
|
|
|
bool enable_rtsp,
|
|
|
|
|
bool enable_rtmp,
|
2019-05-27 23:07:12 +08:00
|
|
|
|
bool enable_hls,
|
|
|
|
|
bool enable_mp4,
|
|
|
|
|
int rtp_type,
|
|
|
|
|
const function<void(const SockException &ex,const string &key)> &cb){
|
|
|
|
|
auto key = getProxyKey(vhost,app,stream);
|
2019-05-30 16:45:11 +08:00
|
|
|
|
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
|
|
|
|
if(s_proxyMap.find(key) != s_proxyMap.end()){
|
|
|
|
|
//已经在拉流了
|
|
|
|
|
cb(SockException(Err_success),key);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-05-20 16:26:04 +08:00
|
|
|
|
//添加拉流代理
|
2019-07-22 11:27:17 +08:00
|
|
|
|
PlayerProxy::Ptr player(new PlayerProxy(vhost,app,stream,enable_rtsp,enable_rtmp,enable_hls,enable_mp4));
|
2019-05-30 16:45:11 +08:00
|
|
|
|
s_proxyMap[key] = player;
|
|
|
|
|
|
2019-05-20 16:26:04 +08:00
|
|
|
|
//指定RTP over TCP(播放rtsp时有效)
|
2019-05-27 23:07:12 +08:00
|
|
|
|
(*player)[kRtpType] = rtp_type;
|
2019-05-20 18:08:55 +08:00
|
|
|
|
//开始播放,如果播放失败或者播放中止,将会自动重试若干次,默认一直重试
|
2019-06-06 18:28:33 +08:00
|
|
|
|
player->setPlayCallbackOnce([cb,key](const SockException &ex){
|
2019-05-30 16:45:11 +08:00
|
|
|
|
if(ex){
|
2019-05-20 18:08:55 +08:00
|
|
|
|
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
2019-05-30 16:45:11 +08:00
|
|
|
|
s_proxyMap.erase(key);
|
2019-05-20 18:08:55 +08:00
|
|
|
|
}
|
2019-05-27 23:07:12 +08:00
|
|
|
|
cb(ex,key);
|
2019-05-20 18:08:55 +08:00
|
|
|
|
});
|
2019-05-27 14:14:42 +08:00
|
|
|
|
|
|
|
|
|
//被主动关闭拉流
|
|
|
|
|
player->setOnClose([key](){
|
|
|
|
|
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
|
|
|
|
s_proxyMap.erase(key);
|
|
|
|
|
});
|
2019-05-27 23:07:12 +08:00
|
|
|
|
player->play(url);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//动态添加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
|
2019-05-27 23:07:12 +08:00
|
|
|
|
API_REGIST_INVOKER(api,addStreamProxy,{
|
|
|
|
|
CHECK_SECRET();
|
2019-07-22 11:27:17 +08:00
|
|
|
|
CHECK_ARGS("vhost","app","stream","url","enable_rtsp","enable_rtmp");
|
2019-05-27 23:07:12 +08:00
|
|
|
|
addStreamProxy(allArgs["vhost"],
|
|
|
|
|
allArgs["app"],
|
|
|
|
|
allArgs["stream"],
|
|
|
|
|
allArgs["url"],
|
2019-07-22 11:27:17 +08:00
|
|
|
|
allArgs["enable_rtsp"],/* 是否rtsp转发 */
|
|
|
|
|
allArgs["enable_rtmp"],/* 是否rtmp转发 */
|
|
|
|
|
allArgs["enable_hls"],/* 是否hls转发 */
|
|
|
|
|
allArgs["enable_mp4"],/* 是否MP4录制 */
|
2019-05-27 23:07:12 +08:00
|
|
|
|
allArgs["rtp_type"],
|
|
|
|
|
[invoker,val,headerOut](const SockException &ex,const string &key){
|
|
|
|
|
if(ex){
|
|
|
|
|
const_cast<Value &>(val)["code"] = API::OtherFailed;
|
|
|
|
|
const_cast<Value &>(val)["msg"] = ex.what();
|
|
|
|
|
}else{
|
|
|
|
|
const_cast<Value &>(val)["data"]["key"] = key;
|
|
|
|
|
}
|
|
|
|
|
invoker("200 OK", headerOut, val.toStyledString());
|
|
|
|
|
});
|
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
|
2019-05-20 16:26:04 +08:00
|
|
|
|
API_REGIST(api,delStreamProxy,{
|
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
|
|
|
|
});
|
|
|
|
|
|
2019-11-13 14:05:15 +08:00
|
|
|
|
static auto addFFmpegSource = [](const string &src_url,
|
2019-06-06 18:28:33 +08:00
|
|
|
|
const string &dst_url,
|
|
|
|
|
int timeout_ms,
|
|
|
|
|
const function<void(const SockException &ex,const string &key)> &cb){
|
|
|
|
|
auto key = MD5(dst_url).hexdigest();
|
|
|
|
|
lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
|
|
|
|
|
if(s_ffmpegMap.find(key) != s_ffmpegMap.end()){
|
|
|
|
|
//已经在拉流了
|
|
|
|
|
cb(SockException(Err_success),key);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FFmpegSource::Ptr ffmpeg = std::make_shared<FFmpegSource>();
|
|
|
|
|
s_ffmpegMap[key] = ffmpeg;
|
|
|
|
|
|
|
|
|
|
ffmpeg->setOnClose([key](){
|
|
|
|
|
lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
|
|
|
|
|
s_ffmpegMap.erase(key);
|
|
|
|
|
});
|
|
|
|
|
ffmpeg->play(src_url, dst_url,timeout_ms,[cb , key](const SockException &ex){
|
|
|
|
|
if(ex){
|
|
|
|
|
lock_guard<decltype(s_ffmpegMapMtx)> lck(s_ffmpegMapMtx);
|
|
|
|
|
s_ffmpegMap.erase(key);
|
|
|
|
|
}
|
|
|
|
|
cb(ex,key);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//动态添加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
|
|
|
|
|
API_REGIST_INVOKER(api,addFFmpegSource,{
|
|
|
|
|
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"];
|
|
|
|
|
|
2019-11-13 14:05:15 +08:00
|
|
|
|
addFFmpegSource(src_url,dst_url,timeout_ms,[invoker,val,headerOut](const SockException &ex,const string &key){
|
2019-06-06 18:28:33 +08:00
|
|
|
|
if(ex){
|
|
|
|
|
const_cast<Value &>(val)["code"] = API::OtherFailed;
|
|
|
|
|
const_cast<Value &>(val)["msg"] = ex.what();
|
|
|
|
|
}else{
|
|
|
|
|
const_cast<Value &>(val)["data"]["key"] = key;
|
|
|
|
|
}
|
|
|
|
|
invoker("200 OK", headerOut, val.toStyledString());
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2019-11-13 14:11:49 +08:00
|
|
|
|
|
|
|
|
|
static auto api_delFFmpegSource = [](API_ARGS,const HttpSession::HttpResponseInvoker &invoker){
|
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-11-13 14:11:49 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//关闭拉流代理
|
|
|
|
|
//测试url http://127.0.0.1/index/api/delFFmepgSource?key=key
|
|
|
|
|
API_REGIST(api,delFFmpegSource,{
|
|
|
|
|
api_delFFmpegSource(API_ARGS_VALUE);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//此处为了兼容之前的拼写错误
|
|
|
|
|
API_REGIST(api,delFFmepgSource,{
|
|
|
|
|
api_delFFmpegSource(API_ARGS_VALUE);
|
2019-06-06 18:28:33 +08:00
|
|
|
|
});
|
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
|
|
|
|
|
API_REGIST_INVOKER(api,downloadBin,{
|
|
|
|
|
CHECK_SECRET();
|
2019-10-29 00:35:44 +08:00
|
|
|
|
invoker.responseFile(headerIn,StrCaseMap(),exePath());
|
2019-10-28 17:23:16 +08:00
|
|
|
|
});
|
|
|
|
|
|
2019-12-06 11:54:10 +08:00
|
|
|
|
#if defined(ENABLE_RTPPROXY)
|
2019-12-05 19:53:55 +08:00
|
|
|
|
API_REGIST(api,getSsrcInfo,{
|
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("ssrc");
|
2019-12-09 18:15:02 +08:00
|
|
|
|
uint32_t ssrc = 0;
|
|
|
|
|
stringstream ss(allArgs["ssrc"]);
|
|
|
|
|
ss >> std::hex >> ssrc;
|
|
|
|
|
|
|
|
|
|
auto process = RtpSelector::Instance().getProcess(ssrc,false);
|
2019-12-05 19:53:55 +08:00
|
|
|
|
if(!process){
|
|
|
|
|
val["exist"] = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
val["exist"] = true;
|
|
|
|
|
val["peer_ip"] = process->get_peer_ip();
|
|
|
|
|
val["peer_port"] = process->get_peer_port();
|
|
|
|
|
});
|
2019-12-06 11:54:10 +08:00
|
|
|
|
#endif//ENABLE_RTPPROXY
|
2019-12-05 19:53:55 +08:00
|
|
|
|
|
2019-12-05 12:04:08 +08:00
|
|
|
|
// 开始录制hls或MP4
|
2019-12-05 12:55:43 +08:00
|
|
|
|
API_REGIST(api,startRecord,{
|
2019-12-05 12:04:08 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("type","vhost","app","stream","wait_for_record","continue_record");
|
2020-01-02 12:47:12 +08:00
|
|
|
|
|
2019-12-05 12:04:08 +08:00
|
|
|
|
int result = Recorder::startRecord((Recorder::type)allArgs["type"].as<int>(),
|
|
|
|
|
allArgs["vhost"],
|
|
|
|
|
allArgs["app"],
|
|
|
|
|
allArgs["stream"],
|
2020-01-02 12:47:12 +08:00
|
|
|
|
allArgs["customized_path"],
|
2019-12-05 12:04:08 +08:00
|
|
|
|
allArgs["wait_for_record"],
|
|
|
|
|
allArgs["continue_record"]);
|
|
|
|
|
val["result"] = result;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 停止录制hls或MP4
|
2019-12-05 12:55:43 +08:00
|
|
|
|
API_REGIST(api,stopRecord,{
|
2019-12-05 12:04:08 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("type","vhost","app","stream");
|
2019-12-05 14:31:44 +08:00
|
|
|
|
int result = Recorder::stopRecord((Recorder::type)allArgs["type"].as<int>(),
|
|
|
|
|
allArgs["vhost"],
|
|
|
|
|
allArgs["app"],
|
|
|
|
|
allArgs["stream"]);
|
|
|
|
|
val["result"] = result;
|
2019-12-05 12:04:08 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 获取hls或MP4录制状态
|
2019-12-05 14:12:22 +08:00
|
|
|
|
API_REGIST(api,getRecordStatus,{
|
2019-12-05 12:04:08 +08:00
|
|
|
|
CHECK_SECRET();
|
|
|
|
|
CHECK_ARGS("type","vhost","app","stream");
|
|
|
|
|
auto status = Recorder::getRecordStatus((Recorder::type)allArgs["type"].as<int>(),
|
|
|
|
|
allArgs["vhost"],
|
|
|
|
|
allArgs["app"],
|
|
|
|
|
allArgs["stream"]);
|
|
|
|
|
val["status"] = (int)status;
|
|
|
|
|
});
|
|
|
|
|
|
2019-05-20 11:22:59 +08:00
|
|
|
|
////////////以下是注册的Hook API////////////
|
|
|
|
|
API_REGIST(hook,on_publish,{
|
|
|
|
|
//开始推流事件
|
2019-09-10 11:06:31 +08:00
|
|
|
|
//转换成rtsp或rtmp
|
|
|
|
|
val["enableRtxp"] = true;
|
|
|
|
|
//转换hls
|
|
|
|
|
val["enableHls"] = true;
|
|
|
|
|
//不录制mp4
|
|
|
|
|
val["enableMP4"] = false;
|
2019-05-20 11:22:59 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
API_REGIST(hook,on_play,{
|
|
|
|
|
//开始播放事件
|
2019-05-27 13:56:37 +08:00
|
|
|
|
throw SuccessException();
|
2019-05-20 11:22:59 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
API_REGIST(hook,on_flow_report,{
|
|
|
|
|
//流量统计hook api
|
2019-05-27 13:56:37 +08:00
|
|
|
|
throw SuccessException();
|
2019-05-20 11:22:59 +08:00
|
|
|
|
});
|
2019-05-20 16:26:04 +08:00
|
|
|
|
|
|
|
|
|
API_REGIST(hook,on_rtsp_realm,{
|
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";
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
API_REGIST(hook,on_rtsp_auth,{
|
|
|
|
|
//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
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
API_REGIST(hook,on_stream_changed,{
|
|
|
|
|
//媒体注册或反注册事件
|
2019-05-27 13:56:37 +08:00
|
|
|
|
throw SuccessException();
|
2019-05-20 16:26:04 +08:00
|
|
|
|
});
|
|
|
|
|
|
2019-06-15 17:07:10 +08:00
|
|
|
|
|
|
|
|
|
#if !defined(_WIN32)
|
2019-07-09 14:30:00 +08:00
|
|
|
|
API_REGIST_INVOKER(hook,on_stream_not_found_ffmpeg,{
|
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"];
|
|
|
|
|
|
2019-12-29 11:52:02 +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,
|
2019-06-06 18:28:33 +08:00
|
|
|
|
[invoker,val,headerOut](const SockException &ex,const string &key){
|
|
|
|
|
if(ex){
|
|
|
|
|
const_cast<Value &>(val)["code"] = API::OtherFailed;
|
|
|
|
|
const_cast<Value &>(val)["msg"] = ex.what();
|
|
|
|
|
}else{
|
|
|
|
|
const_cast<Value &>(val)["data"]["key"] = key;
|
|
|
|
|
}
|
|
|
|
|
invoker("200 OK", headerOut, val.toStyledString());
|
|
|
|
|
});
|
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
|
|
|
|
|
|
|
|
|
API_REGIST_INVOKER(hook,on_stream_not_found,{
|
|
|
|
|
//媒体未找到事件,我们都及时拉流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",
|
|
|
|
|
true,/* 开启rtsp转发 */
|
|
|
|
|
true,/* 开启rtmp转发 */
|
|
|
|
|
true,/* 开启hls转发 */
|
|
|
|
|
false,/* 禁用MP4录制 */
|
2019-06-10 14:13:43 +08:00
|
|
|
|
0,//rtp over tcp方式拉流
|
|
|
|
|
[invoker,val,headerOut](const SockException &ex,const string &key){
|
|
|
|
|
if(ex){
|
|
|
|
|
const_cast<Value &>(val)["code"] = API::OtherFailed;
|
|
|
|
|
const_cast<Value &>(val)["msg"] = ex.what();
|
|
|
|
|
}else{
|
|
|
|
|
const_cast<Value &>(val)["data"]["key"] = key;
|
|
|
|
|
}
|
|
|
|
|
invoker("200 OK", headerOut, val.toStyledString());
|
|
|
|
|
});
|
2019-05-20 16:26:04 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
API_REGIST(hook,on_record_mp4,{
|
|
|
|
|
//录制mp4分片完毕事件
|
2019-05-27 13:56:37 +08:00
|
|
|
|
throw SuccessException();
|
2019-05-20 16:26:04 +08:00
|
|
|
|
});
|
|
|
|
|
|
2019-05-27 13:56:37 +08:00
|
|
|
|
API_REGIST(hook,on_shell_login,{
|
|
|
|
|
//shell登录调试事件
|
|
|
|
|
throw SuccessException();
|
|
|
|
|
});
|
2019-05-27 18:39:43 +08:00
|
|
|
|
|
|
|
|
|
API_REGIST(hook,on_stream_none_reader,{
|
|
|
|
|
//无人观看流默认关闭
|
|
|
|
|
val["close"] = true;
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-13 13:02:30 +08:00
|
|
|
|
static auto checkAccess = [](const string ¶ms){
|
|
|
|
|
//我们假定大家都要权限访问
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
2019-06-12 17:53:48 +08:00
|
|
|
|
API_REGIST(hook,on_http_access,{
|
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
|
|
|
|
});
|
|
|
|
|
|
2019-05-27 18:39:43 +08:00
|
|
|
|
|
2019-11-29 10:12:20 +08:00
|
|
|
|
API_REGIST(hook,on_server_started,{
|
|
|
|
|
//服务器重启报告
|
|
|
|
|
throw SuccessException();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
2019-05-20 11:22:59 +08:00
|
|
|
|
}
|