Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
Z
ZLMediaKit
概览
Overview
Details
Activity
Cycle Analytics
版本库
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
问题
0
Issues
0
列表
Board
标记
里程碑
合并请求
0
Merge Requests
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
Snippets
成员
Collapse sidebar
Close sidebar
活动
图像
聊天
创建新问题
作业
提交
Issue Boards
Open sidebar
张翔宇
ZLMediaKit
Commits
4b4c4e0c
Commit
4b4c4e0c
authored
Dec 29, 2019
by
xiongziliang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
实现hls按需拉流
parent
54736859
隐藏空白字符变更
内嵌
并排
正在显示
10 个修改的文件
包含
67 行增加
和
54 行删除
+67
-54
api/include/mk_events.h
+0
-4
api/source/mk_events.cpp
+0
-2
api/tests/server.c
+0
-4
conf/config.ini
+2
-2
server/WebApi.cpp
+1
-1
server/WebHook.cpp
+1
-1
src/Common/config.cpp
+2
-2
src/Common/config.h
+2
-2
src/Http/HttpFileManager.cpp
+58
-32
src/Record/HlsMediaSource.h
+1
-4
没有找到文件。
api/include/mk_events.h
查看文件 @
4b4c4e0c
...
...
@@ -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
);
...
...
api/source/mk_events.cpp
查看文件 @
4b4c4e0c
...
...
@@ -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
;
...
...
api/tests/server.c
查看文件 @
4b4c4e0c
...
...
@@ -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
,
...
...
conf/config.ini
查看文件 @
4b4c4e0c
...
...
@@ -25,10 +25,10 @@ flowThreshold=1024
#ZLMediaKit会最多让播放器等待maxStreamWaitMS毫秒
#如果在这个时间内,该流注册成功,那么会立即返回播放器播放成功
#否则返回播放器未找到该流,该机制的目的是可以先播放再推流
maxStreamWaitMS
=
5000
maxStreamWaitMS
=
1
5000
#某个流无人观看时,触发hook.on_stream_none_reader事件的最大等待时间,单位毫秒
#在配合hook.on_stream_none_reader事件时,可以做到无人观看自动停止拉流或停止接收推流
streamNoneReaderDelayMS
=
5
000
streamNoneReaderDelayMS
=
20
000
#是否开启低延时模式,该模式下禁用MSG_MORE,启用TCP_NODEALY,延时将降低,但数据发送性能将降低
ultraLowDelay
=
1
#拉流代理是否添加静音音频(直接拉流模式本协议无效)
...
...
server/WebApi.cpp
查看文件 @
4b4c4e0c
...
...
@@ -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
){
...
...
server/WebHook.cpp
查看文件 @
4b4c4e0c
...
...
@@ -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
;
...
...
src/Common/config.cpp
查看文件 @
4b4c4e0c
...
...
@@ -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
]
=
1
5
*
1000
;
mINI
::
Instance
()[
kEnableVhost
]
=
0
;
mINI
::
Instance
()[
kUltraLowDelay
]
=
1
;
mINI
::
Instance
()[
kAddMuteAudio
]
=
1
;
...
...
src/Common/config.h
查看文件 @
4b4c4e0c
...
...
@@ -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
;
...
...
src/Http/HttpFileManager.cpp
查看文件 @
4b4c4e0c
...
...
@@ -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
;
}
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
](
const
string
&
errMsg
,
const
HttpServerCookie
::
Ptr
&
cookie
)
{
if
(
!
errMsg
.
empty
())
{
StrCaseMap
headerOut
;
if
(
cookie
)
{
headerOut
[
"Set-Cookie"
]
=
cookie
->
getCookie
((
*
cookie
)[
kCookiePathKey
].
get
<
string
>
());
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
;
}
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
());
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
);
}
...
...
src/Record/HlsMediaSource.h
查看文件 @
4b4c4e0c
...
...
@@ -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
;
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论