From 4b4c4e0cecfe8899fdcc9f84a000ca7e518dd1c6 Mon Sep 17 00:00:00 2001 From: xiongziliang <771730766@qq.com> Date: Sun, 29 Dec 2019 11:52:02 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0hls=E6=8C=89=E9=9C=80?= =?UTF-8?q?=E6=8B=89=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/include/mk_events.h | 4 -- api/source/mk_events.cpp | 2 - api/tests/server.c | 4 -- conf/config.ini | 4 +- server/WebApi.cpp | 2 +- server/WebHook.cpp | 2 +- src/Common/config.cpp | 4 +- src/Common/config.h | 4 +- src/Http/HttpFileManager.cpp | 94 +++++++++++++++++++++++------------- src/Record/HlsMediaSource.h | 5 +- 10 files changed, 69 insertions(+), 56 deletions(-) diff --git a/api/include/mk_events.h b/api/include/mk_events.h index f9336177..e9a4b07a 100644 --- a/api/include/mk_events.h +++ b/api/include/mk_events.h @@ -94,14 +94,12 @@ typedef struct { /** * 在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限 * @param parser http请求内容对象 - * @param url_info 请求url相关信息 * @param path 文件绝对路径 * @param is_dir path是否为文件夹 * @param invoker 执行invoker返回本次访问文件的结果 * @param sender http客户端相关信息 */ void (API_CALL *on_mk_http_access)(const mk_parser parser, - const mk_media_info url_info, const char *path, int is_dir, const mk_http_access_path_invoker invoker, @@ -111,12 +109,10 @@ typedef struct { * 在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射 * 在该事件中通过自行覆盖path参数,可以做到譬如根据虚拟主机或者app选择不同http根目录的目的 * @param parser http请求内容对象 - * @param url_info 请求url相关信息 * @param path 文件绝对路径,覆盖之可以重定向到其他文件 * @param sender http客户端相关信息 */ void (API_CALL *on_mk_http_before_access)(const mk_parser parser, - const mk_media_info url_info, char *path, const mk_tcp_session sender); diff --git a/api/source/mk_events.cpp b/api/source/mk_events.cpp index 8d915eb1..d44413e7 100644 --- a/api/source/mk_events.cpp +++ b/api/source/mk_events.cpp @@ -70,7 +70,6 @@ API_EXPORT void API_CALL mk_events_listen(const mk_events *events){ NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){ if(s_events.on_mk_http_access){ s_events.on_mk_http_access((mk_parser)&parser, - (mk_media_info)&args, path.c_str(), is_dir, (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}; strcpy(path_c,path.c_str()); s_events.on_mk_http_before_access((mk_parser) &parser, - (mk_media_info) &args, path_c, (mk_tcp_session) &sender); path = path_c; diff --git a/api/tests/server.c b/api/tests/server.c index 34e3db1b..72afc1b0 100644 --- a/api/tests/server.c +++ b/api/tests/server.c @@ -198,14 +198,12 @@ void API_CALL on_mk_http_request(const mk_parser parser, /** * 在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限 * @param parser http请求内容对象 - * @param url_info 请求url相关信息 * @param path 文件绝对路径 * @param is_dir path是否为文件夹 * @param invoker 执行invoker返回本次访问文件的结果 * @param sender http客户端相关信息 */ void API_CALL on_mk_http_access(const mk_parser parser, - const mk_media_info url_info, const char *path, int is_dir, 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到文件路径的映射 * 在该事件中通过自行覆盖path参数,可以做到譬如根据虚拟主机或者app选择不同http根目录的目的 * @param parser http请求内容对象 - * @param url_info 请求url相关信息 * @param path 文件绝对路径,覆盖之可以重定向到其他文件 * @param sender http客户端相关信息 */ void API_CALL on_mk_http_before_access(const mk_parser parser, - const mk_media_info url_info, char *path, const mk_tcp_session sender) { log_printf(LOG_LEV, diff --git a/conf/config.ini b/conf/config.ini index 7e4e3029..c2e01d2b 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -25,10 +25,10 @@ flowThreshold=1024 #ZLMediaKit会最多让播放器等待maxStreamWaitMS毫秒 #如果在这个时间内,该流注册成功,那么会立即返回播放器播放成功 #否则返回播放器未找到该流,该机制的目的是可以先播放再推流 -maxStreamWaitMS=5000 +maxStreamWaitMS=15000 #某个流无人观看时,触发hook.on_stream_none_reader事件的最大等待时间,单位毫秒 #在配合hook.on_stream_none_reader事件时,可以做到无人观看自动停止拉流或停止接收推流 -streamNoneReaderDelayMS=5000 +streamNoneReaderDelayMS=20000 #是否开启低延时模式,该模式下禁用MSG_MORE,启用TCP_NODEALY,延时将降低,但数据发送性能将降低 ultraLowDelay=1 #拉流代理是否添加静音音频(直接拉流模式本协议无效) diff --git a/server/WebApi.cpp b/server/WebApi.cpp index fc8083a3..91a158f6 100644 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -834,7 +834,7 @@ void installWebApi() { << allArgs["stream"] << "?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, (1000 * timeout_sec) - 500, [invoker,val,headerOut](const SockException &ex,const string &key){ diff --git a/server/WebHook.cpp b/server/WebHook.cpp index 6c911588..87ab067f 100644 --- a/server/WebHook.cpp +++ b/server/WebHook.cpp @@ -441,7 +441,7 @@ void installWebHook(){ //如果没有url参数,客户端又不支持cookie,那么会根据ip和端口追踪用户 //追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能 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个小时 invoker("","",60 * 60); return; diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 26239807..764830a1 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -87,8 +87,8 @@ const string kPublishToMP4 = GENERAL_FIELD"publishToMP4"; onceToken token([](){ mINI::Instance()[kFlowThreshold] = 1024; - mINI::Instance()[kStreamNoneReaderDelayMS] = 5 * 1000; - mINI::Instance()[kMaxStreamWaitTimeMS] = 5 * 1000; + mINI::Instance()[kStreamNoneReaderDelayMS] = 20 * 1000; + mINI::Instance()[kMaxStreamWaitTimeMS] = 15 * 1000; mINI::Instance()[kEnableVhost] = 0; mINI::Instance()[kUltraLowDelay] = 1; mINI::Instance()[kAddMuteAudio] = 1; diff --git a/src/Common/config.h b/src/Common/config.h index cf22c3f0..53ad8e3c 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -86,12 +86,12 @@ extern const string kBroadcastHttpRequest; //在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限 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到文件路径的映射 //在该事件中通过自行覆盖path参数,可以做到譬如根据虚拟主机或者app选择不同http根目录的目的 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.如果该事件不监听则不认证 extern const string kBroadcastOnGetRtspRealm; diff --git a/src/Http/HttpFileManager.cpp b/src/Http/HttpFileManager.cpp index 5e39823c..4ba84482 100644 --- a/src/Http/HttpFileManager.cpp +++ b/src/Http/HttpFileManager.cpp @@ -218,16 +218,13 @@ static bool end_of(const string &str, const string &substr){ }; //拦截hls的播放请求 -static bool emitHlsPlayed(BroadcastHttpAccessArgs){ +static bool emitHlsPlayed(const Parser &parser, const MediaInfo &mediaInfo, const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender){ //访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件 - Broadcast::AuthInvoker mediaAuthInvoker = [invoker,path](const string &err){ + Broadcast::AuthInvoker mediaAuthInvoker = [invoker](const string &err){ //cookie有效期为kHlsCookieSecond invoker(err,"",kHlsCookieSecond); }; - - auto args_copy = args; - replace(args_copy._streamid,kHlsSuffix,""); - return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,args_copy,mediaAuthInvoker,sender); + return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,mediaInfo,mediaAuthInvoker,sender); } @@ -284,7 +281,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI HttpCookieManager::Instance().delCookie(cookie); } - bool is_hls = end_of(path,kHlsSuffix); + bool is_hls = mediaInfo._schema == HLS_SCHEMA; //该用户从来未获取过cookie,这个时候我们广播是否允许该用户访问该http目录 HttpSession::HttpAccessPathInvoker accessPathInvoker = [callback, uid, path, is_dir, is_hls, mediaInfo] (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(is_hls); if(is_hls){ //hls相关信息 - replace(const_cast(mediaInfo._streamid),kHlsSuffix,""); (*cookie)[kHlsData].set(mediaInfo); } 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的播放鉴权,拦截之 return; } //事件未被拦截,则认为是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) { //此事件无人监听,我们默认都有权限访问 callback("", nullptr); @@ -357,35 +353,65 @@ static string pathCat(const string &a, const string &b){ * @param 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); 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()); - } - cb("401 Unauthorized", "text/html", headerOut, std::make_shared(errMsg)); - return; - } - StrCaseMap httpHeader; - if (cookie) { - httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get()); - } - HttpSession::HttpResponseInvoker invoker = [&](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) { - if(cookie){ - auto is_hls = (*cookie)[kAccessHls].get(); - if(is_hls){ - (*cookie)[kHlsData].get().addByteUsage(body->remainSize()); + if(is_hls){ + //hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS + const_cast(mediaInfo._schema) = HLS_SCHEMA; + replace(const_cast(mediaInfo._streamid), kHlsSuffix, ""); + } + + weak_ptr weakSession = sender.shared_from_this(); + //判断是否有权限访问该文件 + 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()); } + cb("401 Unauthorized", "text/html", headerOut, std::make_shared(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()); + } + HttpSession::HttpResponseInvoker invoker = [&](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) { + if (cookie) { + cookie->getLock(); + auto is_hls = (*cookie)[kAccessHls].get(); + if (is_hls) { + (*cookie)[kHlsData].get().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(string, rootPath, Http::kRootPath); 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); } diff --git a/src/Record/HlsMediaSource.h b/src/Record/HlsMediaSource.h index e7a8eb2a..98390108 100644 --- a/src/Record/HlsMediaSource.h +++ b/src/Record/HlsMediaSource.h @@ -44,10 +44,7 @@ class HlsMediaSource : public MediaSource { public: friend class HlsCookieData; typedef std::shared_ptr Ptr; - HlsMediaSource(const string &vhost, - const string &app, - const string &stream_id) : - MediaSource(HLS_SCHEMA, vhost, app, stream_id){ + HlsMediaSource(const string &vhost, const string &app, const string &stream_id) : MediaSource(HLS_SCHEMA, vhost, app, stream_id){ _readerCount = 0; }