mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-27 13:49:01 +08:00
Merge branch 'master' into dev
This commit is contained in:
commit
772fccba41
2
.github/ISSUE_TEMPLATE/bug.md
vendored
2
.github/ISSUE_TEMPLATE/bug.md
vendored
@ -1,7 +1,7 @@
|
||||
---
|
||||
name: bug 反馈
|
||||
about: 反馈 ZLMediaKit 代码本身的 bug
|
||||
title: "[BUG]: BUG 现象描述"
|
||||
title: "[BUG] BUG现象描述(必填)"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
|
2
.github/ISSUE_TEMPLATE/compile.md
vendored
2
.github/ISSUE_TEMPLATE/compile.md
vendored
@ -1,7 +1,7 @@
|
||||
---
|
||||
name: 编译问题反馈
|
||||
about: 反馈 ZLMediaKit 编译相关的问题
|
||||
title: "[编译问题]: "
|
||||
title: "[编译问题] 编译问题描述(必填)"
|
||||
labels: 编译问题
|
||||
assignees: ''
|
||||
|
||||
|
2
.github/ISSUE_TEMPLATE/feature.md
vendored
2
.github/ISSUE_TEMPLATE/feature.md
vendored
@ -1,7 +1,7 @@
|
||||
---
|
||||
name: 新增功能请求
|
||||
about: 请求新增某些新功能或新特性,或者对已有功能的改进
|
||||
title: "[功能请求]"
|
||||
title: "[功能请求] 需求描述(必填)"
|
||||
labels: 意见建议
|
||||
assignees: ''
|
||||
|
||||
|
4
.github/ISSUE_TEMPLATE/question.md
vendored
4
.github/ISSUE_TEMPLATE/question.md
vendored
@ -1,7 +1,7 @@
|
||||
---
|
||||
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
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit aea48a14f9619d292789b6ba66d2922e1ed36e71
|
||||
Subproject commit ca26e43a5f62986bb8a007226e0bad148d154abc
|
@ -1 +1 @@
|
||||
Subproject commit 539f579d59b39b386d8f2d3b59df8f56f9946025
|
||||
Subproject commit 5aa9884660df1c193d730a90835af36ee411668c
|
4
AUTHORS
4
AUTHORS
@ -67,3 +67,7 @@ WuPeng <wp@zafu.edu.cn>
|
||||
[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)
|
@ -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)
|
||||
|
||||
## 使用案例
|
||||
|
||||
|
@ -33,7 +33,7 @@
|
||||
# endif
|
||||
# endif
|
||||
#elif !defined(GENERATE_EXPORT)
|
||||
# define API_EXPORT
|
||||
# define API_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -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时有效)
|
||||
|
@ -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);
|
||||
|
@ -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则说明无简称*/
|
||||
|
@ -98,10 +98,7 @@ 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 {
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
@ -81,8 +82,14 @@ 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;
|
||||
|
@ -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 << "download ts segment " << url << " failed:" << err.what();
|
||||
WarnL << "Download 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,13 +147,24 @@ 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播放列表
|
||||
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) {
|
||||
@ -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 << "may not a hls video: " << content_type << ", url: " << getUrl();
|
||||
WarnL << "May 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) {
|
||||
|
@ -73,7 +73,7 @@ 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;
|
||||
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;
|
||||
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user