实现hls按需拉流

This commit is contained in:
xiongziliang 2019-12-29 11:52:02 +08:00
parent 54736859d4
commit 4b4c4e0cec
10 changed files with 69 additions and 56 deletions

View File

@ -94,14 +94,12 @@ typedef struct {
/** /**
* http文件服务器中,http访问文件或目录的广播,访http目录的权限 * http文件服务器中,http访问文件或目录的广播,访http目录的权限
* @param parser http请求内容对象 * @param parser http请求内容对象
* @param url_info url相关信息
* @param path * @param path
* @param is_dir path是否为文件夹 * @param is_dir path是否为文件夹
* @param invoker invoker返回本次访问文件的结果 * @param invoker invoker返回本次访问文件的结果
* @param sender http客户端相关信息 * @param sender http客户端相关信息
*/ */
void (API_CALL *on_mk_http_access)(const mk_parser parser, void (API_CALL *on_mk_http_access)(const mk_parser parser,
const mk_media_info url_info,
const char *path, const char *path,
int is_dir, int is_dir,
const mk_http_access_path_invoker invoker, const mk_http_access_path_invoker invoker,
@ -111,12 +109,10 @@ typedef struct {
* http文件服务器中,http访问文件或目录前的广播,http url到文件路径的映射 * http文件服务器中,http访问文件或目录前的广播,http url到文件路径的映射
* path参数app选择不同http根目录的目的 * path参数app选择不同http根目录的目的
* @param parser http请求内容对象 * @param parser http请求内容对象
* @param url_info url相关信息
* @param path , * @param path ,
* @param sender http客户端相关信息 * @param sender http客户端相关信息
*/ */
void (API_CALL *on_mk_http_before_access)(const mk_parser parser, void (API_CALL *on_mk_http_before_access)(const mk_parser parser,
const mk_media_info url_info,
char *path, char *path,
const mk_tcp_session sender); const mk_tcp_session sender);

View File

@ -70,7 +70,6 @@ API_EXPORT void API_CALL mk_events_listen(const mk_events *events){
NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){ NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){
if(s_events.on_mk_http_access){ if(s_events.on_mk_http_access){
s_events.on_mk_http_access((mk_parser)&parser, s_events.on_mk_http_access((mk_parser)&parser,
(mk_media_info)&args,
path.c_str(), path.c_str(),
is_dir, is_dir,
(mk_http_access_path_invoker)&invoker, (mk_http_access_path_invoker)&invoker,
@ -85,7 +84,6 @@ API_EXPORT void API_CALL mk_events_listen(const mk_events *events){
char path_c[4 * 1024] = {0}; char path_c[4 * 1024] = {0};
strcpy(path_c,path.c_str()); strcpy(path_c,path.c_str());
s_events.on_mk_http_before_access((mk_parser) &parser, s_events.on_mk_http_before_access((mk_parser) &parser,
(mk_media_info) &args,
path_c, path_c,
(mk_tcp_session) &sender); (mk_tcp_session) &sender);
path = path_c; path = path_c;

View File

@ -198,14 +198,12 @@ void API_CALL on_mk_http_request(const mk_parser parser,
/** /**
* http文件服务器中,http访问文件或目录的广播,访http目录的权限 * http文件服务器中,http访问文件或目录的广播,访http目录的权限
* @param parser http请求内容对象 * @param parser http请求内容对象
* @param url_info url相关信息
* @param path * @param path
* @param is_dir path是否为文件夹 * @param is_dir path是否为文件夹
* @param invoker invoker返回本次访问文件的结果 * @param invoker invoker返回本次访问文件的结果
* @param sender http客户端相关信息 * @param sender http客户端相关信息
*/ */
void API_CALL on_mk_http_access(const mk_parser parser, void API_CALL on_mk_http_access(const mk_parser parser,
const mk_media_info url_info,
const char *path, const char *path,
int is_dir, int is_dir,
const mk_http_access_path_invoker invoker, const mk_http_access_path_invoker invoker,
@ -236,12 +234,10 @@ void API_CALL on_mk_http_access(const mk_parser parser,
* http文件服务器中,http访问文件或目录前的广播,http url到文件路径的映射 * http文件服务器中,http访问文件或目录前的广播,http url到文件路径的映射
* path参数app选择不同http根目录的目的 * path参数app选择不同http根目录的目的
* @param parser http请求内容对象 * @param parser http请求内容对象
* @param url_info url相关信息
* @param path , * @param path ,
* @param sender http客户端相关信息 * @param sender http客户端相关信息
*/ */
void API_CALL on_mk_http_before_access(const mk_parser parser, void API_CALL on_mk_http_before_access(const mk_parser parser,
const mk_media_info url_info,
char *path, char *path,
const mk_tcp_session sender) { const mk_tcp_session sender) {
log_printf(LOG_LEV, log_printf(LOG_LEV,

View File

@ -25,10 +25,10 @@ flowThreshold=1024
#ZLMediaKit会最多让播放器等待maxStreamWaitMS毫秒 #ZLMediaKit会最多让播放器等待maxStreamWaitMS毫秒
#如果在这个时间内,该流注册成功,那么会立即返回播放器播放成功 #如果在这个时间内,该流注册成功,那么会立即返回播放器播放成功
#否则返回播放器未找到该流,该机制的目的是可以先播放再推流 #否则返回播放器未找到该流,该机制的目的是可以先播放再推流
maxStreamWaitMS=5000 maxStreamWaitMS=15000
#某个流无人观看时触发hook.on_stream_none_reader事件的最大等待时间单位毫秒 #某个流无人观看时触发hook.on_stream_none_reader事件的最大等待时间单位毫秒
#在配合hook.on_stream_none_reader事件时可以做到无人观看自动停止拉流或停止接收推流 #在配合hook.on_stream_none_reader事件时可以做到无人观看自动停止拉流或停止接收推流
streamNoneReaderDelayMS=5000 streamNoneReaderDelayMS=20000
#是否开启低延时模式该模式下禁用MSG_MORE,启用TCP_NODEALY延时将降低但数据发送性能将降低 #是否开启低延时模式该模式下禁用MSG_MORE,启用TCP_NODEALY延时将降低但数据发送性能将降低
ultraLowDelay=1 ultraLowDelay=1
#拉流代理是否添加静音音频(直接拉流模式本协议无效) #拉流代理是否添加静音音频(直接拉流模式本协议无效)

View File

@ -834,7 +834,7 @@ void installWebApi() {
<< allArgs["stream"] << "?vhost=" << allArgs["stream"] << "?vhost="
<< allArgs["vhost"]; << allArgs["vhost"];
addFFmpegSource("http://live.hkstv.hk.lxdns.com/live/hks2/playlist.m3u8",/** ffmpeg拉流支持任意编码格式任意协议 **/ addFFmpegSource("http://hls-ott-zhibo.wasu.tv/live/272/index.m3u8",/** ffmpeg拉流支持任意编码格式任意协议 **/
dst_url, dst_url,
(1000 * timeout_sec) - 500, (1000 * timeout_sec) - 500,
[invoker,val,headerOut](const SockException &ex,const string &key){ [invoker,val,headerOut](const SockException &ex,const string &key){

View File

@ -441,7 +441,7 @@ void installWebHook(){
//如果没有url参数客户端又不支持cookie那么会根据ip和端口追踪用户 //如果没有url参数客户端又不支持cookie那么会根据ip和端口追踪用户
//追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能 //追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){ NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){
if(sender.get_peer_ip() == "127.0.0.1" && args._param_strs == hook_adminparams){ if(sender.get_peer_ip() == "127.0.0.1" && parser.Params() == hook_adminparams){
//如果是本机或超级管理员访问那么不做访问鉴权权限有效期1个小时 //如果是本机或超级管理员访问那么不做访问鉴权权限有效期1个小时
invoker("","",60 * 60); invoker("","",60 * 60);
return; return;

View File

@ -87,8 +87,8 @@ const string kPublishToMP4 = GENERAL_FIELD"publishToMP4";
onceToken token([](){ onceToken token([](){
mINI::Instance()[kFlowThreshold] = 1024; mINI::Instance()[kFlowThreshold] = 1024;
mINI::Instance()[kStreamNoneReaderDelayMS] = 5 * 1000; mINI::Instance()[kStreamNoneReaderDelayMS] = 20 * 1000;
mINI::Instance()[kMaxStreamWaitTimeMS] = 5 * 1000; mINI::Instance()[kMaxStreamWaitTimeMS] = 15 * 1000;
mINI::Instance()[kEnableVhost] = 0; mINI::Instance()[kEnableVhost] = 0;
mINI::Instance()[kUltraLowDelay] = 1; mINI::Instance()[kUltraLowDelay] = 1;
mINI::Instance()[kAddMuteAudio] = 1; mINI::Instance()[kAddMuteAudio] = 1;

View File

@ -86,12 +86,12 @@ extern const string kBroadcastHttpRequest;
//在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限 //在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限
extern const string kBroadcastHttpAccess; extern const string kBroadcastHttpAccess;
#define BroadcastHttpAccessArgs const Parser &parser,const MediaInfo &args,const string &path,const bool &is_dir,const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender #define BroadcastHttpAccessArgs const Parser &parser,const string &path,const bool &is_dir,const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender
//在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射 //在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射
//在该事件中通过自行覆盖path参数可以做到譬如根据虚拟主机或者app选择不同http根目录的目的 //在该事件中通过自行覆盖path参数可以做到譬如根据虚拟主机或者app选择不同http根目录的目的
extern const string kBroadcastHttpBeforeAccess; extern const string kBroadcastHttpBeforeAccess;
#define BroadcastHttpBeforeAccessArgs const Parser &parser,const MediaInfo &args,string &path,TcpSession &sender #define BroadcastHttpBeforeAccessArgs const Parser &parser,string &path,TcpSession &sender
//该流是否需要认证是的话调用invoker并传入realm,否则传入空的realm.如果该事件不监听则不认证 //该流是否需要认证是的话调用invoker并传入realm,否则传入空的realm.如果该事件不监听则不认证
extern const string kBroadcastOnGetRtspRealm; extern const string kBroadcastOnGetRtspRealm;

View File

@ -218,16 +218,13 @@ static bool end_of(const string &str, const string &substr){
}; };
//拦截hls的播放请求 //拦截hls的播放请求
static bool emitHlsPlayed(BroadcastHttpAccessArgs){ static bool emitHlsPlayed(const Parser &parser, const MediaInfo &mediaInfo, const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender){
//访问的hls.m3u8结尾我们转换成kBroadcastMediaPlayed事件 //访问的hls.m3u8结尾我们转换成kBroadcastMediaPlayed事件
Broadcast::AuthInvoker mediaAuthInvoker = [invoker,path](const string &err){ Broadcast::AuthInvoker mediaAuthInvoker = [invoker](const string &err){
//cookie有效期为kHlsCookieSecond //cookie有效期为kHlsCookieSecond
invoker(err,"",kHlsCookieSecond); invoker(err,"",kHlsCookieSecond);
}; };
return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,mediaInfo,mediaAuthInvoker,sender);
auto args_copy = args;
replace(args_copy._streamid,kHlsSuffix,"");
return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,args_copy,mediaAuthInvoker,sender);
} }
@ -284,7 +281,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
HttpCookieManager::Instance().delCookie(cookie); HttpCookieManager::Instance().delCookie(cookie);
} }
bool is_hls = end_of(path,kHlsSuffix); bool is_hls = mediaInfo._schema == HLS_SCHEMA;
//该用户从来未获取过cookie这个时候我们广播是否允许该用户访问该http目录 //该用户从来未获取过cookie这个时候我们广播是否允许该用户访问该http目录
HttpSession::HttpAccessPathInvoker accessPathInvoker = [callback, uid, path, is_dir, is_hls, mediaInfo] HttpSession::HttpAccessPathInvoker accessPathInvoker = [callback, uid, path, is_dir, is_hls, mediaInfo]
(const string &errMsg, const string &cookie_path_in, int cookieLifeSecond) { (const string &errMsg, const string &cookie_path_in, int cookieLifeSecond) {
@ -308,7 +305,6 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
(*cookie)[kAccessHls].set<bool>(is_hls); (*cookie)[kAccessHls].set<bool>(is_hls);
if(is_hls){ if(is_hls){
//hls相关信息 //hls相关信息
replace(const_cast<string &>(mediaInfo._streamid),kHlsSuffix,"");
(*cookie)[kHlsData].set<HlsCookieData>(mediaInfo); (*cookie)[kHlsData].set<HlsCookieData>(mediaInfo);
} }
callback(errMsg, cookie); callback(errMsg, cookie);
@ -317,13 +313,13 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
} }
}; };
if (is_hls && emitHlsPlayed(parser, mediaInfo, path, is_dir, accessPathInvoker, sender)) { if (is_hls && emitHlsPlayed(parser, mediaInfo, accessPathInvoker, sender)) {
//是hls的播放鉴权,拦截之 //是hls的播放鉴权,拦截之
return; return;
} }
//事件未被拦截则认为是http下载请求 //事件未被拦截则认为是http下载请求
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, mediaInfo, path, is_dir, accessPathInvoker, sender); bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, sender);
if (!flag) { if (!flag) {
//此事件无人监听,我们默认都有权限访问 //此事件无人监听,我们默认都有权限访问
callback("", nullptr); callback("", nullptr);
@ -357,35 +353,65 @@ static string pathCat(const string &a, const string &b){
* @param cb * @param cb
*/ */
static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo &mediaInfo, const string &strFile, const HttpFileManager::invoker &cb) { static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo &mediaInfo, const string &strFile, const HttpFileManager::invoker &cb) {
if (!File::is_file(strFile.data())) { bool is_hls = end_of(strFile, kHlsSuffix);
if (!is_hls && !File::is_file(strFile.data())) {
//文件不存在且不是hls,那么直接返回404
sendNotFound(cb); sendNotFound(cb);
return; return;
} }
//判断是否有权限访问该文件
canAccessPath(sender, parser, mediaInfo, false, [cb, strFile, parser](const string &errMsg, const HttpServerCookie::Ptr &cookie) {
if (!errMsg.empty()) {
StrCaseMap headerOut;
if (cookie) {
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get<string>());
}
cb("401 Unauthorized", "text/html", headerOut, std::make_shared<HttpStringBody>(errMsg));
return;
}
StrCaseMap httpHeader; if(is_hls){
if (cookie) { //hls那么移除掉后缀获取真实的stream_id并且修改协议为HLS
httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get<string>()); const_cast<string &>(mediaInfo._schema) = HLS_SCHEMA;
} replace(const_cast<string &>(mediaInfo._streamid), kHlsSuffix, "");
HttpSession::HttpResponseInvoker invoker = [&](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) { }
if(cookie){
auto is_hls = (*cookie)[kAccessHls].get<bool>(); weak_ptr<TcpSession> weakSession = sender.shared_from_this();
if(is_hls){ //判断是否有权限访问该文件
(*cookie)[kHlsData].get<HlsCookieData>().addByteUsage(body->remainSize()); canAccessPath(sender, parser, mediaInfo, false, [cb, strFile, parser, is_hls, mediaInfo, weakSession](const string &errMsg, const HttpServerCookie::Ptr &cookie) {
if (!errMsg.empty()) {
//文件鉴权失败
StrCaseMap headerOut;
if (cookie) {
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get<string>());
} }
cb("401 Unauthorized", "text/html", headerOut, std::make_shared<HttpStringBody>(errMsg));
return;
}
auto response_file = [](const HttpServerCookie::Ptr &cookie, const HttpFileManager::invoker &cb, const string &strFile, const Parser &parser) {
StrCaseMap httpHeader;
if (cookie) {
httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get<string>());
}
HttpSession::HttpResponseInvoker invoker = [&](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) {
if (cookie) {
cookie->getLock();
auto is_hls = (*cookie)[kAccessHls].get<bool>();
if (is_hls) {
(*cookie)[kHlsData].get<HlsCookieData>().addByteUsage(body->remainSize());
}
}
cb(codeOut.data(), getContentType(strFile.data()), headerOut, body);
};
invoker.responseFile(parser.getValues(), httpHeader, strFile);
};
if (!is_hls) {
//不是hls直接回复
response_file(cookie, cb, strFile, parser);
} else {
//文件不存在那么说明是hls我们等待其生成并延后回复
auto strongSession = weakSession.lock();
if(!strongSession){
//http客户端已经断开不需要回复
return;
}
MediaSource::findAsync(mediaInfo, strongSession, [response_file, cookie, cb, strFile, parser](const MediaSource::Ptr &src) {
//hls已经生成或者超时后仍未生成那么不管怎么样都返回客户端
response_file(cookie, cb, strFile, parser);
});
} }
cb(codeOut.data(), getContentType(strFile.data()), headerOut, body);
};
invoker.responseFile(parser.getValues(), httpHeader, strFile);
}); });
} }
@ -393,7 +419,7 @@ static string getFilePath(const Parser &parser,const MediaInfo &mediaInfo, TcpSe
GET_CONFIG(bool, enableVhost, General::kEnableVhost); GET_CONFIG(bool, enableVhost, General::kEnableVhost);
GET_CONFIG(string, rootPath, Http::kRootPath); GET_CONFIG(string, rootPath, Http::kRootPath);
auto ret = File::absolutePath(enableVhost ? mediaInfo._vhost + parser.Url() : parser.Url(), rootPath); auto ret = File::absolutePath(enableVhost ? mediaInfo._vhost + parser.Url() : parser.Url(), rootPath);
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, mediaInfo, ret, sender); NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, sender);
return std::move(ret); return std::move(ret);
} }

View File

@ -44,10 +44,7 @@ class HlsMediaSource : public MediaSource {
public: public:
friend class HlsCookieData; friend class HlsCookieData;
typedef std::shared_ptr<HlsMediaSource> Ptr; typedef std::shared_ptr<HlsMediaSource> Ptr;
HlsMediaSource(const string &vhost, HlsMediaSource(const string &vhost, const string &app, const string &stream_id) : MediaSource(HLS_SCHEMA, vhost, app, stream_id){
const string &app,
const string &stream_id) :
MediaSource(HLS_SCHEMA, vhost, app, stream_id){
_readerCount = 0; _readerCount = 0;
} }