mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-27 04:20:13 +08:00
实现hls按需拉流
This commit is contained in:
parent
54736859d4
commit
4b4c4e0cec
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
#拉流代理是否添加静音音频(直接拉流模式本协议无效)
|
||||
|
@ -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){
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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<bool>(is_hls);
|
||||
if(is_hls){
|
||||
//hls相关信息
|
||||
replace(const_cast<string &>(mediaInfo._streamid),kHlsSuffix,"");
|
||||
(*cookie)[kHlsData].set<HlsCookieData>(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<string>());
|
||||
}
|
||||
cb("401 Unauthorized", "text/html", headerOut, std::make_shared<HttpStringBody>(errMsg));
|
||||
return;
|
||||
}
|
||||
|
||||
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){
|
||||
auto is_hls = (*cookie)[kAccessHls].get<bool>();
|
||||
if(is_hls){
|
||||
(*cookie)[kHlsData].get<HlsCookieData>().addByteUsage(body->remainSize());
|
||||
if(is_hls){
|
||||
//hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
|
||||
const_cast<string &>(mediaInfo._schema) = HLS_SCHEMA;
|
||||
replace(const_cast<string &>(mediaInfo._streamid), kHlsSuffix, "");
|
||||
}
|
||||
|
||||
weak_ptr<TcpSession> 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<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(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);
|
||||
}
|
||||
|
||||
|
@ -44,10 +44,7 @@ class HlsMediaSource : public MediaSource {
|
||||
public:
|
||||
friend class HlsCookieData;
|
||||
typedef std::shared_ptr<HlsMediaSource> 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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user