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
772fccba
Commit
772fccba
authored
Apr 06, 2023
by
xiongguangjie
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into dev
parents
11fa293a
599208dd
隐藏空白字符变更
内嵌
并排
正在显示
19 个修改的文件
包含
146 行增加
和
47 行删除
+146
-47
.github/ISSUE_TEMPLATE/bug.md
+1
-1
.github/ISSUE_TEMPLATE/compile.md
+1
-1
.github/ISSUE_TEMPLATE/feature.md
+1
-1
.github/ISSUE_TEMPLATE/question.md
+2
-2
3rdpart/ZLToolKit
+1
-1
3rdpart/media-server
+1
-1
AUTHORS
+6
-2
README.md
+5
-1
api/include/mk_common.h
+1
-1
server/WebApi.cpp
+6
-6
server/WebHook.cpp
+1
-1
server/main.cpp
+1
-1
src/Http/HlsParser.cpp
+2
-5
src/Http/HlsParser.h
+12
-5
src/Http/HlsPlayer.cpp
+23
-11
src/Http/HlsPlayer.h
+5
-4
src/Http/HttpRequester.cpp
+1
-1
src/Rtp/RtpSender.cpp
+8
-0
www/webrtc/index.html
+68
-2
没有找到文件。
.github/ISSUE_TEMPLATE/bug.md
查看文件 @
772fccba
---
name
:
bug 反馈
about
:
反馈 ZLMediaKit 代码本身的 bug
title
:
"
[BUG]
:
BUG
现象描述
"
title
:
"
[BUG]
BUG现象描述(必填)
"
labels
:
bug
assignees
:
'
'
...
...
.github/ISSUE_TEMPLATE/compile.md
查看文件 @
772fccba
---
name
:
编译问题反馈
about
:
反馈 ZLMediaKit 编译相关的问题
title
:
"
[编译问题]
:
"
title
:
"
[编译问题]
编译问题描述(必填)
"
labels
:
编译问题
assignees
:
'
'
...
...
.github/ISSUE_TEMPLATE/feature.md
查看文件 @
772fccba
---
name
:
新增功能请求
about
:
请求新增某些新功能或新特性,或者对已有功能的改进
title
:
"
[功能请求]"
title
:
"
[功能请求]
需求描述(必填)
"
labels
:
意见建议
assignees
:
'
'
...
...
.github/ISSUE_TEMPLATE/question.md
查看文件 @
772fccba
---
name
:
技术咨询
about
:
使用咨询、技术咨询等
title
:
"
[技术咨询]"
title
:
"
[技术咨询]
咨询描述(必填)
"
labels
:
技术咨询
assignees
:
'
'
...
...
@@ -16,4 +16,4 @@ assignees: ''
**注意事项**
-
技术咨询前请先认真阅读readme,
[
wiki
](
https://github.com/xia-chu/ZLMediaKit/wiki
)
,如有必要,您也可以同时搜索已经答复的issue,如果没找到答案才在此提issue
-
技术咨询不属于bug缺陷,
建议先star本项目,否则可能会降低答复优先级
-
技术咨询不属于bug缺陷,
要求用户先star(收藏)本项目,否则会直接关闭issue
ZLToolKit
@
ca26e43a
Subproject commit
aea48a14f9619d292789b6ba66d2922e1ed36e71
Subproject commit
ca26e43a5f62986bb8a007226e0bad148d154abc
media-server
@
5aa98846
Subproject commit 5
39f579d59b39b386d8f2d3b59df8f56f9946025
Subproject commit 5
aa9884660df1c193d730a90835af36ee411668c
AUTHORS
查看文件 @
772fccba
...
...
@@ -66,4 +66,8 @@ WuPeng <wp@zafu.edu.cn>
[KevinZang](https://github.com/ZSC714725)
[gongluck](https://github.com/gongluck)
[a-ucontrol](https://github.com/a-ucontrol)
[TalusL](https://github.com/TalusL)
\ No newline at end of file
[TalusL](https://github.com/TalusL)
[ahaooahaz](https://github.com/AHAOAHA)
[TempoTian](https://github.com/TempoTian)
[Derek Liu](https://github.com/yjkhtddx)
[ljx0305](https://github.com/ljx0305)
\ No newline at end of file
README.md
查看文件 @
772fccba
...
...
@@ -198,7 +198,7 @@ bash build_docker_images.sh
## 联系方式
-
邮箱:
<1213642868@qq.com>
(本项目相关或流媒体相关问题请走issue流程,否则恕不邮件答复)
-
QQ群:
qq群号在wiki中,请阅读wiki后再加群
-
QQ群:
两个qq群已满员(共4000人),后续将不再新建qq群,用户可加入
[
知识星球
](
https://t.zsxq.com/0cVcuquPJ
)
提问以支持本项目。
## 怎么提问?
...
...
@@ -295,6 +295,10 @@ bash build_docker_images.sh
[
gongluck
](
https://github.com/gongluck
)
[
a-ucontrol
](
https://github.com/a-ucontrol
)
[
TalusL
](
https://github.com/TalusL
)
[
ahaooahaz
](
https://github.com/AHAOAHA
)
[
TempoTian
](
https://github.com/TempoTian
)
[
Derek Liu
](
https://github.com/yjkhtddx
)
[
ljx0305
](
https://github.com/ljx0305
)
## 使用案例
...
...
api/include/mk_common.h
查看文件 @
772fccba
...
...
@@ -33,7 +33,7 @@
# endif
# endif
#elif !defined(GENERATE_EXPORT)
# define API_EXPORT
# define API_EXPORT
__attribute__((visibility("default")))
#endif
#ifdef __cplusplus
...
...
server/WebApi.cpp
查看文件 @
772fccba
...
...
@@ -537,7 +537,7 @@ void addStreamProxy(const string &vhost, const string &app, const string &stream
return
;
}
//添加拉流代理
auto
player
=
std
::
make_shared
<
PlayerProxy
>
(
vhost
,
app
,
stream
,
option
,
retry_count
>=
0
?
retry_count
:
-
1
);
auto
player
=
std
::
make_shared
<
PlayerProxy
>
(
vhost
,
app
,
stream
,
option
,
retry_count
);
s_proxyMap
[
key
]
=
player
;
//指定RTP over TCP(播放rtsp时有效)
...
...
@@ -952,7 +952,7 @@ void installWebApi() {
}
//添加推流代理
PusherProxy
::
Ptr
pusher
(
new
PusherProxy
(
src
,
retry_count
>=
0
?
retry_count
:
-
1
)
);
auto
pusher
=
std
::
make_shared
<
PusherProxy
>
(
src
,
retry_count
);
s_proxyPusherMap
[
key
]
=
pusher
;
//指定RTP over TCP(播放rtsp时有效)
...
...
@@ -988,7 +988,7 @@ void installWebApi() {
CHECK_SECRET
();
CHECK_ARGS
(
"schema"
,
"vhost"
,
"app"
,
"stream"
,
"dst_url"
);
auto
dst_url
=
allArgs
[
"dst_url"
];
auto
retry_count
=
allArgs
[
"retry_count"
].
empty
()
?
-
1
:
allArgs
[
"retry_count"
].
as
<
int
>
();
auto
retry_count
=
allArgs
[
"retry_count"
].
empty
()
?
-
1
:
allArgs
[
"retry_count"
].
as
<
int
>
();
addStreamPusherProxy
(
allArgs
[
"schema"
],
allArgs
[
"vhost"
],
allArgs
[
"app"
],
...
...
@@ -1327,7 +1327,7 @@ void installWebApi() {
invoker
(
200
,
headerOut
,
val
.
toStyledString
());
});
});
//设置录像流播放速度
api_regist
(
"/index/api/setRecordSpeed"
,
[](
API_ARGS_MAP_ASYNC
)
{
CHECK_SECRET
();
...
...
@@ -1407,7 +1407,7 @@ void installWebApi() {
invoker
(
200
,
headerOut
,
val
.
toStyledString
());
});
});
// 删除录像文件夹
// http://127.0.0.1/index/api/deleteRecordDirectroy?vhost=__defaultVhost__&app=live&stream=ss&period=2020-01-01
api_regist
(
"/index/api/deleteRecordDirectory"
,
[](
API_ARGS_MAP
)
{
...
...
@@ -1424,7 +1424,7 @@ void installWebApi() {
val
[
"path"
]
=
record_path
;
val
[
"code"
]
=
result
;
});
//获取录像文件夹列表或mp4文件列表
//http://127.0.0.1/index/api/getMp4RecordFile?vhost=__defaultVhost__&app=live&stream=ss&period=2020-01
api_regist
(
"/index/api/getMp4RecordFile"
,
[](
API_ARGS_MAP
){
...
...
server/WebHook.cpp
查看文件 @
772fccba
...
...
@@ -161,7 +161,7 @@ void do_http_hook(const string &url, const ArgsType &body, const function<void(c
GET_CONFIG
(
float
,
retry_delay
,
Hook
::
kRetryDelay
);
const_cast
<
ArgsType
&>
(
body
)[
"mediaServerId"
]
=
mediaServerId
;
HttpRequester
::
Ptr
requester
(
new
HttpRequester
);
auto
requester
=
std
::
make_shared
<
HttpRequester
>
(
);
requester
->
setMethod
(
"POST"
);
auto
bodyStr
=
to_string
(
body
);
requester
->
setBody
(
bodyStr
);
...
...
server/main.cpp
查看文件 @
772fccba
...
...
@@ -108,7 +108,7 @@ onceToken token1([](){
class
CMD_main
:
public
CMD
{
public
:
CMD_main
()
{
_parser
.
reset
(
new
OptionParser
(
nullptr
)
);
_parser
=
std
::
make_shared
<
OptionParser
>
(
nullptr
);
#if !defined(_WIN32)
(
*
_parser
)
<<
Option
(
'd'
,
/*该选项简称,如果是\x00则说明无简称*/
...
...
src/Http/HlsParser.cpp
查看文件 @
772fccba
...
...
@@ -98,17 +98,14 @@ bool HlsParser::parse(const string &http_url, const string &m3u8) {
continue
;
}
if
(
_is_m3u8
)
{
onParsed
(
_is_m3u8_inner
,
_sequence
,
ts_map
);
}
return
_is_m3u8
;
return
_is_m3u8
&&
onParsed
(
_is_m3u8_inner
,
_sequence
,
ts_map
);
}
bool
HlsParser
::
isM3u8
()
const
{
return
_is_m3u8
;
}
bool
HlsParser
::
isLive
()
const
{
bool
HlsParser
::
isLive
()
const
{
return
_is_live
;
}
...
...
src/Http/HlsParser.h
查看文件 @
772fccba
...
...
@@ -36,8 +36,9 @@ typedef struct{
class
HlsParser
{
public
:
HlsParser
(){}
~
HlsParser
(){}
HlsParser
()
=
default
;
~
HlsParser
()
=
default
;
bool
parse
(
const
std
::
string
&
http_url
,
const
std
::
string
&
m3u8
);
/**
...
...
@@ -79,10 +80,16 @@ public:
* 得到总时间
*/
float
getTotalDuration
()
const
;
protected
:
//解析出ts文件地址回调
virtual
void
onParsed
(
bool
is_m3u8_inner
,
int64_t
sequence
,
const
std
::
map
<
int
,
ts_segment
>
&
ts_list
)
{};
/**
* 解析m3u8文件回调
* @param is_m3u8_inner 该m3u8文件中是否包含多个hls地址
* @param sequence ts序号
* @param ts_list ts地址列表
* @return 是否解析成功,返回false时,将导致HlsParser::parse返回false
*/
virtual
bool
onParsed
(
bool
is_m3u8_inner
,
int64_t
sequence
,
const
std
::
map
<
int
,
ts_segment
>
&
ts_list
)
=
0
;
private
:
bool
_is_m3u8
=
false
;
...
...
src/Http/HlsPlayer.cpp
查看文件 @
772fccba
...
...
@@ -51,7 +51,7 @@ void HlsPlayer::teardown_l(const SockException &ex) {
}
else
{
_try_fetch_index_times
+=
1
;
shutdown
(
ex
);
WarnL
<<
"
重新尝试拉取索引文件
["
<<
_try_fetch_index_times
<<
"]:"
<<
_play_url
;
WarnL
<<
"
Attempt to pull the m3u8 file again
["
<<
_try_fetch_index_times
<<
"]:"
<<
_play_url
;
fetchIndexFile
();
return
;
}
...
...
@@ -118,7 +118,7 @@ void HlsPlayer::fetchSegment() {
return
;
}
if
(
err
)
{
WarnL
<<
"
d
ownload ts segment "
<<
url
<<
" failed:"
<<
err
.
what
();
WarnL
<<
"
D
ownload ts segment "
<<
url
<<
" failed:"
<<
err
.
what
();
if
(
err
.
getErrCode
()
==
Err_timeout
)
{
strong_self
->
_timeout_multiple
=
MAX
(
strong_self
->
_timeout_multiple
+
1
,
MAX_TIMEOUT_MULTIPLE
);
}
else
{
...
...
@@ -147,30 +147,41 @@ void HlsPlayer::fetchSegment() {
_http_ts_player
->
sendRequest
(
url
);
}
void
HlsPlayer
::
onParsed
(
bool
is_m3u8_inner
,
int64_t
sequence
,
const
map
<
int
,
ts_segment
>
&
ts_map
)
{
bool
HlsPlayer
::
onParsed
(
bool
is_m3u8_inner
,
int64_t
sequence
,
const
map
<
int
,
ts_segment
>
&
ts_map
)
{
if
(
!
is_m3u8_inner
)
{
//这是ts播放列表
//
这是ts播放列表
if
(
_last_sequence
==
sequence
)
{
return
;
// 如果是重复的ts列表,那么忽略
// 但是需要注意, 如果当前ts列表为空了, 那么表明直播结束了或者m3u8文件有问题,需要重新拉流
// 这里的5倍是为了防止m3u8文件有问题导致的无限重试
if
(
_last_sequence
>
0
&&
_ts_list
.
empty
()
&&
HlsParser
::
isLive
()
&&
_wait_index_update_ticker
.
elapsedTime
()
>
(
uint64_t
)
HlsParser
::
getTargetDur
()
*
1000
*
5
)
{
_wait_index_update_ticker
.
resetTime
();
WarnL
<<
"Fetch new ts list from m3u8 timeout"
;
return
false
;
}
return
true
;
}
_last_sequence
=
sequence
;
_wait_index_update_ticker
.
resetTime
();
for
(
auto
&
pr
:
ts_map
)
{
auto
&
ts
=
pr
.
second
;
if
(
_ts_url_cache
.
emplace
(
ts
.
url
).
second
)
{
//该ts未重复
//
该ts未重复
_ts_list
.
emplace_back
(
ts
);
//按时间排序
//
按时间排序
_ts_url_sort
.
emplace_back
(
ts
.
url
);
}
}
if
(
_ts_url_sort
.
size
()
>
2
*
ts_map
.
size
())
{
//去除防重列表中过多的数据
//
去除防重列表中过多的数据
_ts_url_cache
.
erase
(
_ts_url_sort
.
front
());
_ts_url_sort
.
pop_front
();
}
fetchSegment
();
}
else
{
//这是m3u8列表,我们播放最高清的子hls
//
这是m3u8列表,我们播放最高清的子hls
if
(
ts_map
.
empty
())
{
throw
invalid_argument
(
"empty sub hls list:"
+
getUrl
());
}
...
...
@@ -184,6 +195,7 @@ void HlsPlayer::onParsed(bool is_m3u8_inner, int64_t sequence, const map<int, ts
}
},
false
);
}
return
true
;
}
void
HlsPlayer
::
onResponseHeader
(
const
string
&
status
,
const
HttpClient
::
HttpHeader
&
headers
)
{
...
...
@@ -193,7 +205,7 @@ void HlsPlayer::onResponseHeader(const string &status, const HttpClient::HttpHea
}
auto
content_type
=
strToLower
(
const_cast
<
HttpClient
::
HttpHeader
&>
(
headers
)[
"Content-Type"
]);
if
(
content_type
.
find
(
"application/vnd.apple.mpegurl"
)
!=
0
&&
content_type
.
find
(
"/x-mpegurl"
)
==
_StrPrinter
::
npos
)
{
WarnL
<<
"
m
ay not a hls video: "
<<
content_type
<<
", url: "
<<
getUrl
();
WarnL
<<
"
M
ay not a hls video: "
<<
content_type
<<
", url: "
<<
getUrl
();
}
_m3u8
.
clear
();
}
...
...
@@ -208,7 +220,7 @@ void HlsPlayer::onResponseCompleted(const SockException &ex) {
return
;
}
if
(
!
HlsParser
::
parse
(
getUrl
(),
_m3u8
))
{
teardown_l
(
SockException
(
Err_other
,
"parse m3u8 failed:"
+
_
m3u8
));
teardown_l
(
SockException
(
Err_other
,
"parse m3u8 failed:"
+
_
play_url
));
return
;
}
if
(
!
_play_result
)
{
...
...
src/Http/HlsPlayer.h
查看文件 @
772fccba
...
...
@@ -73,11 +73,11 @@ protected:
virtual
void
onPacket
(
const
char
*
data
,
size_t
len
)
=
0
;
private
:
void
onParsed
(
bool
is_m3u8_inner
,
int64_t
sequence
,
const
map
<
int
,
ts_segment
>
&
ts_map
)
override
;
void
onResponseHeader
(
const
std
::
string
&
status
,
const
HttpHeader
&
headers
)
override
;
void
onResponseBody
(
const
char
*
buf
,
size_t
size
)
override
;
bool
onParsed
(
bool
is_m3u8_inner
,
int64_t
sequence
,
const
map
<
int
,
ts_segment
>
&
ts_map
)
override
;
void
onResponseHeader
(
const
std
::
string
&
status
,
const
HttpHeader
&
headers
)
override
;
void
onResponseBody
(
const
char
*
buf
,
size_t
size
)
override
;
void
onResponseCompleted
(
const
toolkit
::
SockException
&
e
)
override
;
bool
onRedirectUrl
(
const
std
::
string
&
url
,
bool
temporary
)
override
;
bool
onRedirectUrl
(
const
std
::
string
&
url
,
bool
temporary
)
override
;
private
:
void
playDelay
();
...
...
@@ -101,6 +101,7 @@ private:
std
::
string
_play_url
;
toolkit
::
Timer
::
Ptr
_timer
;
toolkit
::
Timer
::
Ptr
_timer_ts
;
toolkit
::
Ticker
_wait_index_update_ticker
;
std
::
list
<
ts_segment
>
_ts_list
;
std
::
list
<
std
::
string
>
_ts_url_sort
;
std
::
set
<
std
::
string
,
UrlComp
>
_ts_url_cache
;
...
...
src/Http/HttpRequester.cpp
查看文件 @
772fccba
...
...
@@ -271,7 +271,7 @@ static void sendReport() {
}
static
toolkit
::
onceToken
s_token
([]()
{
NoticeCenter
::
Instance
().
addListener
(
nullptr
,
EventPollerPool
::
kOnStarted
,
[](
EventPollerPool
&
pool
,
size_t
&
size
)
{
NoticeCenter
::
Instance
().
addListener
(
nullptr
,
"kBroadcastEventPollerPoolStarted"
,
[](
EventPollerPool
&
pool
,
size_t
&
size
)
{
// 第一次汇报在程序启动后5分钟
pool
.
getPoller
()
->
doDelayTask
(
5
*
60
*
1000
,
[]()
{
sendReport
();
...
...
src/Rtp/RtpSender.cpp
查看文件 @
772fccba
...
...
@@ -246,6 +246,10 @@ void RtpSender::onConnect(){
}
bool
RtpSender
::
addTrack
(
const
Track
::
Ptr
&
track
){
if
(
_args
.
only_audio
&&
track
->
getTrackType
()
==
TrackVideo
)
{
// 如果只发送音频则忽略视频
return
false
;
}
return
_interface
->
addTrack
(
track
);
}
...
...
@@ -265,6 +269,10 @@ void RtpSender::flush() {
//此函数在其他线程执行
bool
RtpSender
::
inputFrame
(
const
Frame
::
Ptr
&
frame
)
{
if
(
_args
.
only_audio
&&
frame
->
getTrackType
()
==
TrackVideo
)
{
// 如果只发送音频则忽略视频
return
false
;
}
//连接成功后才做实质操作(节省cpu资源)
return
_is_connect
?
_interface
->
inputFrame
(
frame
)
:
false
;
}
...
...
www/webrtc/index.html
查看文件 @
772fccba
...
...
@@ -18,11 +18,19 @@
</video>
</div>
<div>
<div
style=
"float: left; width:30%;"
>
<span>
已存在的流列表,点击自动播放:
</span>
<ol
id=
"olstreamlist"
>
<li>
初始演示
</li>
<li>
每秒自动刷新
</li>
</ol>
</div>
<div
style=
"float: right; width: 70%"
>
<p>
<label
for=
"streamUrl"
>
url:
</label>
<input
type=
"text"
style=
"co"
id=
'streamUrl'
value=
"http://192.168.1.101/index/api/webrtc?app=live&stream=xiong&type=play"
>
<input
type=
"text"
style=
"co
; width:70%
"
id=
'streamUrl'
value=
"http://192.168.1.101/index/api/webrtc?app=live&stream=xiong&type=play"
>
</p>
<p>
...
...
@@ -252,6 +260,64 @@
}
}
function
on_click_to_play
(
app
,
stream
)
{
console
.
log
(
`on_click_to_play:
${
app
}
/
${
stream
}
`
);
var
url
=
`
${
document
.
location
.
protocol
}
//
${
window
.
location
.
host
}
/index/api/webrtc?app=
${
app
}
&stream=
${
stream
}
&type=play`
;
document
.
getElementById
(
'streamUrl'
).
value
=
url
;
start
();
}
function
clearStreamList
()
{
let
content
=
document
.
getElementById
(
"olstreamlist"
);
while
(
content
.
hasChildNodes
())
{
content
.
removeChild
(
content
.
firstChild
);
}
}
function
fillStreamList
(
json
)
{
clearStreamList
();
if
(
json
.
code
!=
0
)
{
return
;
}
let
ss
=
{};
for
(
let
o
of
json
.
data
)
{
if
(
ss
[
o
.
app
])
{
ss
[
o
.
app
].
add
(
o
.
stream
);
}
else
{
let
set
=
new
Set
();
set
.
add
(
o
.
steram
);
ss
[
o
.
app
]
=
set
;
}
}
for
(
let
o
in
ss
)
{
let
app
=
o
;
for
(
let
s
of
ss
[
o
])
{
if
(
s
)
{
//console.log(app, s);
let
content
=
document
.
getElementById
(
"olstreamlist"
);
let
child
=
`<li app="
${
app
}
" stream="
${
s
}
" onmouseover="this.style.color='blue';" onclick="on_click_to_play('
${
app
}
', '
${
s
}
')">
${
app
}
/
${
s
}
</li>`
;
content
.
insertAdjacentHTML
(
"beforeend"
,
child
);
}
}
}
}
async
function
getData
(
url
)
{
const
response
=
await
fetch
(
url
,
{
method
:
'GET'
});
const
data
=
await
response
.
json
();
//console.log(data);
return
data
;
}
function
get_media_list
()
{
let
url
=
document
.
location
.
protocol
+
"//"
+
window
.
location
.
host
+
"/index/api/getMediaList?secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc"
;
let
json
=
getData
(
url
);
json
.
then
((
json
)
=>
fillStreamList
(
json
));
}
setInterval
(()
=>
{
get_media_list
();
},
1000
);
</script>
</body>
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论