Merge branch 'master' into dev

This commit is contained in:
xiongguangjie 2023-11-30 10:31:52 +08:00
commit f5d78c7fb1
62 changed files with 5219 additions and 217 deletions

@ -1 +1 @@
Subproject commit 97871cfa78fcd2fae164243a8c653e323385772d Subproject commit ad44a16c99834540b397774ad6c7f3f8ed619d56

@ -1 +1 @@
Subproject commit 3dc623a899eee3810587fb267dbff770b626a55b Subproject commit a8a80e0738b052aa5671ef82a295ef388bd28e13

View File

@ -13,6 +13,7 @@
#include "mk_common.h" #include "mk_common.h"
#include "mk_tcp.h" #include "mk_tcp.h"
#include "mk_track.h" #include "mk_track.h"
#include "mk_util.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -317,6 +318,8 @@ API_EXPORT void API_CALL mk_publish_auth_invoker_do(const mk_publish_auth_invoke
int enable_hls, int enable_hls,
int enable_mp4); int enable_mp4);
API_EXPORT void API_CALL mk_publish_auth_invoker_do2(const mk_publish_auth_invoker ctx, const char *err_msg, mk_ini option);
/** /**
* mk_publish_auth_invoker对象线mk_publish_auth_invoker_do * mk_publish_auth_invoker对象线mk_publish_auth_invoker_do
* mk_publish_auth_invoker_do * mk_publish_auth_invoker_do

View File

@ -461,6 +461,13 @@ API_EXPORT void API_CALL mk_publish_auth_invoker_do(const mk_publish_auth_invoke
(*invoker)(err_msg ? err_msg : "", option); (*invoker)(err_msg ? err_msg : "", option);
} }
API_EXPORT void API_CALL mk_publish_auth_invoker_do2(const mk_publish_auth_invoker ctx, const char *err_msg, mk_ini ini) {
assert(ctx);
Broadcast::PublishAuthInvoker *invoker = (Broadcast::PublishAuthInvoker *)ctx;
ProtocolOption option(ini ? *((mINI *)ini) : mINI{} );
(*invoker)(err_msg ? err_msg : "", option);
}
API_EXPORT mk_publish_auth_invoker API_CALL mk_publish_auth_invoker_clone(const mk_publish_auth_invoker ctx){ API_EXPORT mk_publish_auth_invoker API_CALL mk_publish_auth_invoker_clone(const mk_publish_auth_invoker ctx){
assert(ctx); assert(ctx);
Broadcast::PublishAuthInvoker *invoker = (Broadcast::PublishAuthInvoker *)ctx; Broadcast::PublishAuthInvoker *invoker = (Broadcast::PublishAuthInvoker *)ctx;

View File

@ -1,4 +1,4 @@
FROM ubuntu:18.04 AS build FROM ubuntu:20.04 AS build
ARG MODEL ARG MODEL
#shell,rtmp,rtsp,rtsps,http,https,rtp #shell,rtmp,rtsp,rtsps,http,https,rtp
EXPOSE 1935/tcp EXPOSE 1935/tcp
@ -31,24 +31,31 @@ RUN apt-get update && \
gcc \ gcc \
g++ \ g++ \
libavcodec-dev libavutil-dev libswscale-dev libresample-dev \ libavcodec-dev libavutil-dev libswscale-dev libresample-dev \
libsdl-dev libusrsctp-dev \
gdb && \ gdb && \
apt-get autoremove -y && \ apt-get autoremove -y && \
apt-get clean -y && \ apt-get clean -y && \
wget https://github.com/cisco/libsrtp/archive/v2.2.0.tar.gz -O libsrtp-2.2.0.tar.gz && tar xfv libsrtp-2.2.0.tar.gz && \
cd libsrtp-2.2.0 && ./configure --enable-openssl && make -j $(nproc) && make install && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
RUN mkdir -p /opt/media RUN mkdir -p /opt/media
COPY . /opt/media/ZLMediaKit COPY . /opt/media/ZLMediaKit
WORKDIR /opt/media/ZLMediaKit WORKDIR /opt/media/ZLMediaKit
# 3rdpart init
WORKDIR /opt/media/ZLMediaKit/3rdpart
RUN wget https://mirror.ghproxy.com/https://github.com/cisco/libsrtp/archive/v2.3.0.tar.gz -O libsrtp-2.3.0.tar.gz && \
tar xfv libsrtp-2.3.0.tar.gz && \
mv libsrtp-2.3.0 libsrtp && \
cd libsrtp && ./configure --enable-openssl && make -j $(nproc) && make install
#RUN git submodule update --init --recursive && \ #RUN git submodule update --init --recursive && \
RUN mkdir -p build release/linux/${MODEL}/ RUN mkdir -p build release/linux/${MODEL}/
WORKDIR /opt/media/ZLMediaKit/build WORKDIR /opt/media/ZLMediaKit/build
RUN cmake -DCMAKE_BUILD_TYPE=${MODEL} -DENABLE_WEBRTC=true -DENABLE_FFMPEG=true -DENABLE_TESTS=false -DENABLE_API=false .. && \ RUN cmake -DCMAKE_BUILD_TYPE=${MODEL} -DENABLE_WEBRTC=true -DENABLE_FFMPEG=true -DENABLE_TESTS=false -DENABLE_API=false .. && \
make -j $(nproc) make -j $(nproc)
FROM ubuntu:18.04 FROM ubuntu:20.04
ARG MODEL ARG MODEL
# ADD sources.list /etc/apt/sources.list # ADD sources.list /etc/apt/sources.list
@ -68,6 +75,7 @@ RUN apt-get update && \
gcc \ gcc \
g++ \ g++ \
libavcodec-dev libavutil-dev libswscale-dev libresample-dev \ libavcodec-dev libavutil-dev libswscale-dev libresample-dev \
libsdl-dev libusrsctp-dev \
gdb && \ gdb && \
apt-get autoremove -y && \ apt-get autoremove -y && \
apt-get clean -y && \ apt-get clean -y && \

View File

@ -2048,6 +2048,142 @@
} }
}, },
"response": [] "response": []
},
{
"name": "点播mp4文件(loadMP4File)",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{ZLMediaKit_URL}}/index/api/loadMP4File?secret={{ZLMediaKit_secret}}&vhost={{defaultVhost}}&app=live&stream=test&file_path=/path/to/mp4/file.mp4",
"host": [
"{{ZLMediaKit_URL}}"
],
"path": [
"index",
"api",
"loadMP4File"
],
"query": [
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}",
"description": "api操作密钥(配置文件配置)"
},
{
"key": "vhost",
"value": "{{defaultVhost}}",
"description": "添加的流的虚拟主机例如__defaultVhost__"
},
{
"key": "app",
"value": "live",
"description": "添加的流的应用名例如live"
},
{
"key": "stream",
"value": "test",
"description": "添加的流的id名例如test"
},
{
"key": "file_path",
"value": "/path/to/mp4/file.mp4",
"description": "mp4文件绝对路径"
},
{
"key": "file_repeat",
"value": "1",
"description": "是否循环点播mp4文件如果配置文件已经开启循环点播此参数无效",
"disabled": true
},
{
"key": "enable_hls",
"value": "",
"description": "是否转hls-ts",
"disabled": true
},
{
"key": "enable_hls_fmp4",
"value": "",
"description": "是否转hls-fmp4",
"disabled": true
},
{
"key": "enable_mp4",
"value": "",
"description": "是否mp4录制默认不开启(覆盖配置文件)",
"disabled": true
},
{
"key": "enable_rtsp",
"value": "1",
"description": "是否转协议为rtsp/webrtc",
"disabled": true
},
{
"key": "enable_rtmp",
"value": "1",
"description": "是否转协议为rtmp/flv",
"disabled": true
},
{
"key": "enable_ts",
"value": "1",
"description": "是否转协议为http-ts/ws-ts",
"disabled": true
},
{
"key": "enable_fmp4",
"value": "1",
"description": "是否转协议为http-fmp4/ws-fmp4",
"disabled": true
},
{
"key": "enable_audio",
"value": "1",
"description": "转协议是否开启音频",
"disabled": true
},
{
"key": "add_mute_audio",
"value": "1",
"description": "转协议无音频时是否添加静音aac音频",
"disabled": true
},
{
"key": "mp4_save_path",
"value": "",
"description": "mp4录制保存根目录置空使用默认目录",
"disabled": true
},
{
"key": "mp4_max_second",
"value": "1800",
"description": "mp4录制切片大小单位秒",
"disabled": true
},
{
"key": "hls_save_path",
"value": "",
"description": "hls保存根目录置空使用默认目录",
"disabled": true
},
{
"key": "modify_stamp",
"value": "",
"description": "是否修改原始时间戳默认值2取值范围0.采用源视频流绝对时间戳,不做任何改变;1.采用zlmediakit接收数据时的系统时间戳(有平滑处理);2.采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正",
"disabled": true
},
{
"key": "auto_close",
"value": "",
"description": "无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close);强制开启,此参数不生效",
"disabled": true
}
]
}
},
"response": []
} }
], ],
"event": [ "event": [

View File

@ -11,44 +11,52 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <math.h> #include <math.h>
#include <signal.h> #include <signal.h>
#include <functional>
#include <unordered_map>
#include "Util/util.h"
#include "Util/logger.h"
#include "Util/onceToken.h"
#include "Util/NoticeCenter.h"
#include "Util/File.h"
#ifdef ENABLE_MYSQL
#include "Util/SqlPool.h"
#endif //ENABLE_MYSQL
#include "Common/config.h"
#include "Common/MediaSource.h"
#include "Http/HttpRequester.h"
#include "Http/HttpSession.h"
#include "Network/TcpServer.h"
#include "Network/UdpServer.h"
#include "Player/PlayerProxy.h"
#include "Pusher/PusherProxy.h"
#include "Util/MD5.h"
#include "WebApi.h"
#include "WebHook.h"
#include "Thread/WorkThreadPool.h"
#include "Rtp/RtpSelector.h"
#include "FFmpegSource.h"
#if defined(ENABLE_RTPPROXY)
#include "Rtp/RtpServer.h"
#endif
#ifdef ENABLE_WEBRTC
#include "../webrtc/WebRtcPlayer.h"
#include "../webrtc/WebRtcPusher.h"
#include "../webrtc/WebRtcEchoTest.h"
#endif
#ifdef _WIN32 #ifdef _WIN32
#include <io.h> #include <io.h>
#include <iostream> #include <iostream>
#include <tchar.h> #include <tchar.h>
#endif // _WIN32 #endif // _WIN32
#include <functional>
#include <unordered_map>
#include "Util/MD5.h"
#include "Util/util.h"
#include "Util/File.h"
#include "Util/logger.h"
#include "Util/onceToken.h"
#include "Util/NoticeCenter.h"
#include "Network/TcpServer.h"
#include "Network/UdpServer.h"
#include "Thread/WorkThreadPool.h"
#ifdef ENABLE_MYSQL
#include "Util/SqlPool.h"
#endif //ENABLE_MYSQL
#include "WebApi.h"
#include "WebHook.h"
#include "FFmpegSource.h"
#include "Common/config.h"
#include "Common/MediaSource.h"
#include "Http/HttpSession.h"
#include "Http/HttpRequester.h"
#include "Player/PlayerProxy.h"
#include "Pusher/PusherProxy.h"
#include "Rtp/RtpSelector.h"
#include "Record/MP4Reader.h"
#if defined(ENABLE_RTPPROXY)
#include "Rtp/RtpServer.h"
#endif
#ifdef ENABLE_WEBRTC
#include "../webrtc/WebRtcPlayer.h"
#include "../webrtc/WebRtcPusher.h"
#include "../webrtc/WebRtcEchoTest.h"
#endif
#if defined(ENABLE_VERSION) #if defined(ENABLE_VERSION)
#include "version.h" #include "version.h"
#endif #endif
@ -1777,6 +1785,23 @@ void installWebApi() {
}); });
#endif #endif
api_regist("/index/api/loadMP4File", [](API_ARGS_MAP) {
CHECK_SECRET();
CHECK_ARGS("vhost", "app", "stream", "file_path");
ProtocolOption option;
// 默认解复用mp4不生成mp4
option.enable_mp4 = false;
// 但是如果参数明确指定开启mp4, 那么也允许之
option.load(allArgs);
// 强制无人观看时自动关闭
option.auto_close = true;
auto reader = std::make_shared<MP4Reader>(allArgs["vhost"], allArgs["app"], allArgs["stream"], allArgs["file_path"], option);
// sample_ms设置为0从配置文件加载file_repeat可以指定如果配置文件也指定循环解复用那么强制开启
reader->startReadMP4(0, true, allArgs["file_repeat"]);
});
////////////以下是注册的Hook API//////////// ////////////以下是注册的Hook API////////////
api_regist("/index/hook/on_publish",[](API_ARGS_JSON){ api_regist("/index/hook/on_publish",[](API_ARGS_JSON){
//开始推流事件 //开始推流事件

View File

@ -577,8 +577,8 @@ void installWebHook() {
}); });
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastStreamNoneReader, [](BroadcastStreamNoneReaderArgs) { NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastStreamNoneReader, [](BroadcastStreamNoneReaderArgs) {
if (!origin_urls.empty()) { if (!origin_urls.empty() && sender.getOriginType() == MediaOriginType::pull) {
// 边沿站无人观看时立即停止溯源 // 边沿站无人观看时如果是拉流的则立即停止溯源
sender.close(false); sender.close(false);
WarnL << "无人观看主动关闭流:" << sender.getOriginUrl(); WarnL << "无人观看主动关闭流:" << sender.getOriginUrl();
return; return;

View File

@ -11,6 +11,7 @@
#include "JemallocUtil.h" #include "JemallocUtil.h"
#include "Util/logger.h" #include "Util/logger.h"
#include <cstdint>
#ifdef USE_JEMALLOC #ifdef USE_JEMALLOC
#include <array> #include <array>
#include <iostream> #include <iostream>

View File

@ -12,6 +12,7 @@
#define ZLMEDIAKIT_JEMALLOCUTIL_H #define ZLMEDIAKIT_JEMALLOCUTIL_H
#include <functional> #include <functional>
#include <string> #include <string>
#include <cstdint>
namespace mediakit { namespace mediakit {
class JemallocUtil { class JemallocUtil {
public: public:

View File

@ -202,6 +202,11 @@ public:
template <typename MAP> template <typename MAP>
ProtocolOption(const MAP &allArgs) : ProtocolOption() { ProtocolOption(const MAP &allArgs) : ProtocolOption() {
load(allArgs);
}
template <typename MAP>
void load(const MAP &allArgs) {
#define GET_OPT_VALUE(key) getArgsValue(allArgs, #key, key) #define GET_OPT_VALUE(key) getArgsValue(allArgs, #key, key)
GET_OPT_VALUE(modify_stamp); GET_OPT_VALUE(modify_stamp);
GET_OPT_VALUE(enable_audio); GET_OPT_VALUE(enable_audio);

View File

@ -529,6 +529,8 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
ret = _fmp4->inputFrame(frame) ? true : ret; ret = _fmp4->inputFrame(frame) ? true : ret;
} }
if (_ring) { if (_ring) {
// 此场景由于直接转发可能存在切换线程引起的数据被缓存在管道所以需要CacheAbleFrame
frame = Frame::getCacheAbleFrame(frame);
if (frame->getTrackType() == TrackVideo) { if (frame->getTrackType() == TrackVideo) {
// 视频时遇到第一帧配置帧或关键帧则标记为gop开始处 // 视频时遇到第一帧配置帧或关键帧则标记为gop开始处
auto video_key_pos = frame->keyFrame() || frame->configFrame(); auto video_key_pos = frame->keyFrame() || frame->configFrame();

View File

@ -11,7 +11,7 @@
#include <cinttypes> #include <cinttypes>
#include "Parser.h" #include "Parser.h"
#include "strCoding.h" #include "strCoding.h"
#include "macros.h" #include "Util/base64.h"
#include "Network/sockutil.h" #include "Network/sockutil.h"
#include "Common/macros.h" #include "Common/macros.h"
@ -325,6 +325,25 @@ void splitUrl(const std::string &url, std::string &host, uint16_t &port) {
host = url.substr(0, pos); host = url.substr(0, pos);
checkHost(host); checkHost(host);
} }
void parseProxyUrl(const std::string &proxy_url, std::string &proxy_host, uint16_t &proxy_port, std::string &proxy_auth) {
// 判断是否包含http://, 如果是则去掉
std::string host;
auto pos = proxy_url.find("://");
if (pos != string::npos) {
host = proxy_url.substr(pos + 3);
} else {
host = proxy_url;
}
// 判断是否包含用户名和密码
pos = host.rfind('@');
if (pos != string::npos) {
proxy_auth = encodeBase64(host.substr(0, pos));
host = host.substr(pos + 1, host.size());
}
splitUrl(host, proxy_host, proxy_port);
}
#if 0 #if 0
//测试代码 //测试代码
static onceToken token([](){ static onceToken token([](){

View File

@ -21,6 +21,8 @@ namespace mediakit {
std::string findSubString(const char *buf, const char *start, const char *end, size_t buf_size = 0); std::string findSubString(const char *buf, const char *start, const char *end, size_t buf_size = 0);
// 把url解析为主机地址和端口号,兼容ipv4/ipv6/dns // 把url解析为主机地址和端口号,兼容ipv4/ipv6/dns
void splitUrl(const std::string &url, std::string &host, uint16_t &port); void splitUrl(const std::string &url, std::string &host, uint16_t &port);
// 解析proxy url,仅支持http
void parseProxyUrl(const std::string &proxy_url, std::string &proxy_host, uint16_t &proxy_port, std::string &proxy_auth);
struct StrCaseCompare { struct StrCaseCompare {
bool operator()(const std::string &__x, const std::string &__y) const { return strcasecmp(__x.data(), __y.data()) < 0; } bool operator()(const std::string &__x, const std::string &__y) const { return strcasecmp(__x.data(), __y.data()) < 0; }

View File

@ -20,8 +20,13 @@ using namespace toolkit;
namespace mediakit { namespace mediakit {
int64_t DeltaStamp::relativeStamp(int64_t stamp) { DeltaStamp::DeltaStamp() {
_relative_stamp += deltaStamp(stamp); // 时间戳最大允许跳跃300ms
_max_delta = 300;
}
int64_t DeltaStamp::relativeStamp(int64_t stamp, bool enable_rollback) {
_relative_stamp += deltaStamp(stamp, enable_rollback);
return _relative_stamp; return _relative_stamp;
} }
@ -29,7 +34,7 @@ int64_t DeltaStamp::relativeStamp() {
return _relative_stamp; return _relative_stamp;
} }
int64_t DeltaStamp::deltaStamp(int64_t stamp) { int64_t DeltaStamp::deltaStamp(int64_t stamp, bool enable_rollback) {
if (!_last_stamp) { if (!_last_stamp) {
// 第一次计算时间戳增量,时间戳增量为0 // 第一次计算时间戳增量,时间戳增量为0
if (stamp) { if (stamp) {
@ -43,14 +48,25 @@ int64_t DeltaStamp::deltaStamp(int64_t stamp) {
// 时间戳增量为正,返回之 // 时间戳增量为正,返回之
_last_stamp = stamp; _last_stamp = stamp;
// 在直播情况下时间戳增量不得大于MAX_DELTA_STAMP否则强制相对时间戳加1 // 在直播情况下时间戳增量不得大于MAX_DELTA_STAMP否则强制相对时间戳加1
return ret < MAX_DELTA_STAMP ? ret : 1; if (ret > _max_delta) {
needSync();
return 1;
}
return ret;
} }
// 时间戳增量为负,说明时间戳回环了或回退了 // 时间戳增量为负,说明时间戳回环了或回退了
_last_stamp = stamp; _last_stamp = stamp;
if (!enable_rollback || -ret > _max_delta) {
// 不允许回退或者回退太多了, 强制时间戳加1
needSync();
return 1;
}
return ret;
}
// 如果时间戳回退不多那么返回负值否则返回加1 void DeltaStamp::setMaxDelta(size_t max_delta) {
return -ret < MAX_DELTA_STAMP ? ret : 1; _max_delta = max_delta;
} }
void Stamp::setPlayBack(bool playback) { void Stamp::setPlayBack(bool playback) {
@ -58,9 +74,18 @@ void Stamp::setPlayBack(bool playback) {
} }
void Stamp::syncTo(Stamp &other) { void Stamp::syncTo(Stamp &other) {
_need_sync = true;
_sync_master = &other; _sync_master = &other;
} }
void Stamp::needSync() {
_need_sync = true;
}
void Stamp::enableRollback(bool flag) {
_enable_rollback = flag;
}
// 限制dts回退 // 限制dts回退
void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) { void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) {
revise_l(dts, pts, dts_out, pts_out, modifyStamp); revise_l(dts, pts, dts_out, pts_out, modifyStamp);
@ -87,15 +112,26 @@ void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_ou
return; return;
} }
if (_sync_master && _sync_master->_last_dts_in) { // 需要同步时间戳
if (_sync_master && _sync_master->_last_dts_in && (_need_sync || _sync_master->_need_sync)) {
// 音视频dts当前时间差 // 音视频dts当前时间差
int64_t dts_diff = _last_dts_in - _sync_master->_last_dts_in; int64_t dts_diff = _last_dts_in - _sync_master->_last_dts_in;
if (ABS(dts_diff) < 5000) { if (ABS(dts_diff) < 5000) {
// 如果绝对时间戳小于5秒那么说明他们的起始时间戳是一致的那么强制同步 // 如果绝对时间戳小于5秒那么说明他们的起始时间戳是一致的那么强制同步
_relative_stamp = _sync_master->_relative_stamp + dts_diff; auto target_stamp = _sync_master->_relative_stamp + dts_diff;
if (target_stamp > _relative_stamp || _enable_rollback) {
// 强制同步后,时间戳增加跳跃了,或允许回退
TraceL << "Relative stamp changed: " << _relative_stamp << " -> " << target_stamp;
_relative_stamp = target_stamp;
} else {
// 不允许回退, 则让另外一个Track的时间戳增长
target_stamp = _relative_stamp - dts_diff;
TraceL << "Relative stamp changed: " << _sync_master->_relative_stamp << " -> " << target_stamp;
_sync_master->_relative_stamp = target_stamp;
} }
// 下次不用再强制同步 }
_sync_master = nullptr; _need_sync = false;
_sync_master->_need_sync = false;
} }
} }
@ -124,7 +160,7 @@ void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_o
// 内部自己生产时间戳 // 内部自己生产时间戳
_relative_stamp = _ticker.elapsedTime(); _relative_stamp = _ticker.elapsedTime();
} else { } else {
_relative_stamp += deltaStamp(dts); _relative_stamp += deltaStamp(dts, _enable_rollback);
} }
_last_dts_in = dts; _last_dts_in = dts;
} }

View File

@ -19,19 +19,27 @@ namespace mediakit {
class DeltaStamp{ class DeltaStamp{
public: public:
DeltaStamp() = default; DeltaStamp();
~DeltaStamp() = default; ~DeltaStamp() = default;
/** /**
* *
* @param stamp * @param stamp
* @param enable_rollback 退
* @return * @return
*/ */
int64_t deltaStamp(int64_t stamp); int64_t deltaStamp(int64_t stamp, bool enable_rollback = true);
int64_t relativeStamp(int64_t stamp); int64_t relativeStamp(int64_t stamp, bool enable_rollback = true);
int64_t relativeStamp(); int64_t relativeStamp();
private: // 设置最大允许回退或跳跃幅度
void setMaxDelta(size_t max_delta);
protected:
virtual void needSync() {}
protected:
int _max_delta;
int64_t _last_stamp = 0; int64_t _last_stamp = 0;
int64_t _relative_stamp = 0; int64_t _relative_stamp = 0;
}; };
@ -77,6 +85,11 @@ public:
*/ */
void syncTo(Stamp &other); void syncTo(Stamp &other);
/**
* 退
*/
void enableRollback(bool flag);
private: private:
//主要实现音视频时间戳同步功能 //主要实现音视频时间戳同步功能
void revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp = false); void revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp = false);
@ -84,13 +97,18 @@ private:
//主要实现获取相对时间戳功能 //主要实现获取相对时间戳功能
void revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp = false); void revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp = false);
void needSync() override;
private: private:
bool _playback = false;
bool _need_sync = false;
// 默认不允许时间戳回滚
bool _enable_rollback = false;
int64_t _relative_stamp = 0; int64_t _relative_stamp = 0;
int64_t _last_dts_in = 0; int64_t _last_dts_in = 0;
int64_t _last_dts_out = 0; int64_t _last_dts_out = 0;
int64_t _last_pts_out = 0; int64_t _last_pts_out = 0;
toolkit::SmoothTicker _ticker; toolkit::SmoothTicker _ticker;
bool _playback = false;
Stamp *_sync_master = nullptr; Stamp *_sync_master = nullptr;
}; };

View File

@ -353,6 +353,7 @@ const string kBeatIntervalMS = "beat_interval_ms";
const string kBenchmarkMode = "benchmark_mode"; const string kBenchmarkMode = "benchmark_mode";
const string kWaitTrackReady = "wait_track_ready"; const string kWaitTrackReady = "wait_track_ready";
const string kPlayTrack = "play_track"; const string kPlayTrack = "play_track";
const string kProxyUrl = "proxy_url";
} // namespace Client } // namespace Client
} // namespace mediakit } // namespace mediakit

View File

@ -403,6 +403,8 @@ extern const std::string kWaitTrackReady;
// rtsp播放指定track可选项有0(不指定,默认)、1(视频)、2(音频) // rtsp播放指定track可选项有0(不指定,默认)、1(视频)、2(音频)
// 设置方法:player[Client::kPlayTrack] = 0/1/2; // 设置方法:player[Client::kPlayTrack] = 0/1/2;
extern const std::string kPlayTrack; extern const std::string kPlayTrack;
//设置代理url目前只支持http协议
extern const std::string kProxyUrl;
} // namespace Client } // namespace Client
} // namespace mediakit } // namespace mediakit

View File

@ -22,6 +22,7 @@ HlsPlayer::HlsPlayer(const EventPoller::Ptr &poller) {
void HlsPlayer::play(const string &url) { void HlsPlayer::play(const string &url) {
_play_result = false; _play_result = false;
_play_url = url; _play_url = url;
setProxyUrl((*this)[Client::kProxyUrl]);
fetchIndexFile(); fetchIndexFile();
} }
@ -88,6 +89,7 @@ void HlsPlayer::fetchSegment() {
weak_ptr<HlsPlayer> weak_self = static_pointer_cast<HlsPlayer>(shared_from_this()); weak_ptr<HlsPlayer> weak_self = static_pointer_cast<HlsPlayer>(shared_from_this());
if (!_http_ts_player) { if (!_http_ts_player) {
_http_ts_player = std::make_shared<HttpTSPlayer>(getPoller()); _http_ts_player = std::make_shared<HttpTSPlayer>(getPoller());
_http_ts_player->setProxyUrl((*this)[Client::kProxyUrl]);
_http_ts_player->setOnCreateSocket([weak_self](const EventPoller::Ptr &poller) { _http_ts_player->setOnCreateSocket([weak_self](const EventPoller::Ptr &poller) {
auto strong_self = weak_self.lock(); auto strong_self = weak_self.lock();
if (strong_self) { if (strong_self) {

View File

@ -78,13 +78,16 @@ void HttpClient::sendRequest(const string &url) {
printer.pop_back(); printer.pop_back();
_header.emplace("Cookie", printer); _header.emplace("Cookie", printer);
} }
if (isUsedProxy()) {
startConnect(_proxy_host, _proxy_port, _wait_header_ms / 1000.0f);
} else {
if (!alive() || host_changed) { if (!alive() || host_changed) {
startConnect(host, port, _wait_header_ms / 1000.0f); startConnect(host, port, _wait_header_ms / 1000.0f);
} else { } else {
SockException ex; SockException ex;
onConnect_l(ex); onConnect_l(ex);
} }
}
} }
void HttpClient::clear() { void HttpClient::clear() {
@ -158,8 +161,9 @@ void HttpClient::onConnect_l(const SockException &ex) {
onResponseCompleted_l(ex); onResponseCompleted_l(ex);
return; return;
} }
_StrPrinter printer; _StrPrinter printer;
//不使用代理或者代理服务器已经连接成功
if (_proxy_connected || !isUsedProxy()) {
printer << _method + " " << _path + " HTTP/1.1\r\n"; printer << _method + " " << _path + " HTTP/1.1\r\n";
for (auto &pr : _header) { for (auto &pr : _header) {
printer << pr.first + ": "; printer << pr.first + ": ";
@ -167,6 +171,13 @@ void HttpClient::onConnect_l(const SockException &ex) {
} }
_header.clear(); _header.clear();
_path.clear(); _path.clear();
} else {
printer << "CONNECT " << _last_host << " HTTP/1.1\r\n";
printer << "Proxy-Connection: keep-alive\r\n";
if (!_proxy_auth.empty()) {
printer << "Proxy-Authorization: Basic " << _proxy_auth << "\r\n";
}
}
SockSender::send(printer << "\r\n"); SockSender::send(printer << "\r\n");
onFlush(); onFlush();
} }
@ -401,4 +412,28 @@ void HttpClient::setCompleteTimeout(size_t timeout_ms) {
_wait_complete_ms = timeout_ms; _wait_complete_ms = timeout_ms;
} }
bool HttpClient::isUsedProxy() const {
return _used_proxy;
}
bool HttpClient::isProxyConnected() const {
return _proxy_connected;
}
void HttpClient::setProxyUrl(string proxy_url) {
_proxy_url = std::move(proxy_url);
if (!_proxy_url.empty()) {
parseProxyUrl(_proxy_url, _proxy_host, _proxy_port, _proxy_auth);
_used_proxy = true;
} else {
_used_proxy = false;
}
}
bool HttpClient::checkProxyConnected(const char *data, size_t len) {
auto ret = strstr(data, "HTTP/1.1 200 Connection established");
_proxy_connected = ret != nullptr;
return _proxy_connected;
}
} /* namespace mediakit */ } /* namespace mediakit */

View File

@ -141,6 +141,11 @@ public:
*/ */
void setCompleteTimeout(size_t timeout_ms); void setCompleteTimeout(size_t timeout_ms);
/**
* http代理url
*/
void setProxyUrl(std::string proxy_url);
protected: protected:
/** /**
* http回复头 * http回复头
@ -181,11 +186,16 @@ protected:
void onFlush() override; void onFlush() override;
void onManager() override; void onManager() override;
void clearResponse();
bool checkProxyConnected(const char *data, size_t len);
bool isUsedProxy() const;
bool isProxyConnected() const;
private: private:
void onResponseCompleted_l(const toolkit::SockException &ex); void onResponseCompleted_l(const toolkit::SockException &ex);
void onConnect_l(const toolkit::SockException &ex); void onConnect_l(const toolkit::SockException &ex);
void checkCookie(HttpHeader &headers); void checkCookie(HttpHeader &headers);
void clearResponse();
private: private:
//for http response //for http response
@ -215,6 +225,13 @@ private:
toolkit::Ticker _wait_header; toolkit::Ticker _wait_header;
toolkit::Ticker _wait_body; toolkit::Ticker _wait_body;
toolkit::Ticker _wait_complete; toolkit::Ticker _wait_complete;
bool _used_proxy = false;
bool _proxy_connected = false;
uint16_t _proxy_port;
std::string _proxy_url;
std::string _proxy_host;
std::string _proxy_auth;
}; };
} /* namespace mediakit */ } /* namespace mediakit */

View File

@ -15,13 +15,30 @@ using namespace toolkit;
namespace mediakit { namespace mediakit {
void HttpClientImp::onConnect(const SockException &ex) { void HttpClientImp::onConnect(const SockException &ex) {
if (isUsedProxy() && !isProxyConnected()) {
// 连接代理服务器
setDoNotUseSSL();
HttpClient::onConnect(ex);
} else {
if (!isHttps()) { if (!isHttps()) {
//https 302跳转 http时需要关闭ssl // https 302跳转 http时需要关闭ssl
setDoNotUseSSL(); setDoNotUseSSL();
HttpClient::onConnect(ex); HttpClient::onConnect(ex);
} else { } else {
TcpClientWithSSL<HttpClient>::onConnect(ex); TcpClientWithSSL<HttpClient>::onConnect(ex);
} }
}
}
ssize_t HttpClientImp::onRecvHeader(const char *data, size_t len) {
if (isUsedProxy() && !isProxyConnected()) {
if (checkProxyConnected(data, len)) {
clearResponse();
onConnect(SockException(Err_success, "proxy connected"));
return 0;
}
}
return HttpClient::onRecvHeader(data, len);
} }
} /* namespace mediakit */ } /* namespace mediakit */

View File

@ -24,6 +24,7 @@ public:
protected: protected:
void onConnect(const toolkit::SockException &ex) override; void onConnect(const toolkit::SockException &ex) override;
ssize_t onRecvHeader(const char *data, size_t len) override;
}; };
} /* namespace mediakit */ } /* namespace mediakit */

View File

@ -21,6 +21,7 @@ void TsPlayer::play(const string &url) {
TraceL << "play http-ts: " << url; TraceL << "play http-ts: " << url;
_play_result = false; _play_result = false;
_benchmark_mode = (*this)[Client::kBenchmarkMode].as<int>(); _benchmark_mode = (*this)[Client::kBenchmarkMode].as<int>();
setProxyUrl((*this)[Client::kProxyUrl]);
setHeaderTimeout((*this)[Client::kTimeoutMS].as<int>()); setHeaderTimeout((*this)[Client::kTimeoutMS].as<int>());
setBodyTimeout((*this)[Client::kMediaTimeoutMS].as<int>()); setBodyTimeout((*this)[Client::kMediaTimeoutMS].as<int>());
setMethod("GET"); setMethod("GET");

View File

@ -21,6 +21,20 @@ using namespace toolkit;
namespace mediakit { namespace mediakit {
MP4Reader::MP4Reader(const std::string &vhost, const std::string &app, const std::string &stream_id, const string &file_path) { MP4Reader::MP4Reader(const std::string &vhost, const std::string &app, const std::string &stream_id, const string &file_path) {
ProtocolOption option;
// 读取mp4文件并流化时不重复生成mp4/hls文件
option.enable_mp4 = false;
option.enable_hls = false;
option.enable_hls_fmp4 = false;
setup(vhost, app, stream_id, file_path, option);
}
MP4Reader::MP4Reader(const std::string &vhost, const std::string &app, const std::string &stream_id, const string &file_path, const ProtocolOption &option) {
setup(vhost, app, stream_id, file_path, option);
}
void MP4Reader::setup(const std::string &vhost, const std::string &app, const std::string &stream_id, const std::string &file_path, const ProtocolOption &option) {
//读写文件建议放在后台线程 //读写文件建议放在后台线程
auto tuple = MediaTuple{vhost, app, stream_id}; auto tuple = MediaTuple{vhost, app, stream_id};
_poller = WorkThreadPool::Instance().getPoller(); _poller = WorkThreadPool::Instance().getPoller();
@ -42,10 +56,7 @@ MP4Reader::MP4Reader(const std::string &vhost, const std::string &app, const std
if (tuple.stream.empty()) { if (tuple.stream.empty()) {
return; return;
} }
ProtocolOption option;
//读取mp4文件并流化时不重复生成mp4/hls文件
option.enable_mp4 = false;
option.enable_hls = false;
_muxer = std::make_shared<MultiMediaSourceMuxer>(tuple, _demuxer->getDurationMS() / 1000.0f, option); _muxer = std::make_shared<MultiMediaSourceMuxer>(tuple, _demuxer->getDurationMS() / 1000.0f, option);
auto tracks = _demuxer->getTracks(false); auto tracks = _demuxer->getTracks(false);
if (tracks.empty()) { if (tracks.empty()) {

View File

@ -28,7 +28,12 @@ public:
* @param stream_id id,,mp4,MediaSource * @param stream_id id,,mp4,MediaSource
* @param file_path 使 * @param file_path 使
*/ */
MP4Reader(const std::string &vhost, const std::string &app, const std::string &stream_id, const std::string &file_path = ""); MP4Reader(const std::string &vhost, const std::string &app, const std::string &stream_id,
const std::string &file_path = "");
MP4Reader(const std::string &vhost, const std::string &app, const std::string &stream_id,
const std::string &file_path, const ProtocolOption &option);
~MP4Reader() override = default; ~MP4Reader() override = default;
/** /**
@ -66,6 +71,8 @@ private:
void setCurrentStamp(uint32_t stamp); void setCurrentStamp(uint32_t stamp);
bool seekTo(uint32_t stamp_seek); bool seekTo(uint32_t stamp_seek);
void setup(const std::string &vhost, const std::string &app, const std::string &stream_id, const std::string &file_path, const ProtocolOption &option);
private: private:
bool _file_repeat = false; bool _file_repeat = false;
bool _have_video = false; bool _have_video = false;

View File

@ -22,6 +22,7 @@ FlvPlayer::FlvPlayer(const EventPoller::Ptr &poller) {
void FlvPlayer::play(const string &url) { void FlvPlayer::play(const string &url) {
TraceL << "play http-flv: " << url; TraceL << "play http-flv: " << url;
_play_result = false; _play_result = false;
setProxyUrl((*this)[Client::kProxyUrl]);
setHeaderTimeout((*this)[Client::kTimeoutMS].as<int>()); setHeaderTimeout((*this)[Client::kTimeoutMS].as<int>());
setBodyTimeout((*this)[Client::kMediaTimeoutMS].as<int>()); setBodyTimeout((*this)[Client::kMediaTimeoutMS].as<int>());
setMethod("GET"); setMethod("GET");

View File

@ -69,7 +69,7 @@ void RtmpPlayer::play(const string &url) {
} }
DebugL << host_url << " " << _app << " " << _stream_id; DebugL << host_url << " " << _app << " " << _stream_id;
uint16_t port = 1935; uint16_t port = start_with(url, "rtmps") ? 443 : 1935;
splitUrl(host_url, host_url, port); splitUrl(host_url, host_url, port);
if (!(*this)[Client::kNetAdapter].empty()) { if (!(*this)[Client::kNetAdapter].empty()) {

View File

@ -82,7 +82,7 @@ void RtmpPusher::publish(const string &url) {
} }
DebugL << host_url << " " << _app << " " << _stream_id; DebugL << host_url << " " << _app << " " << _stream_id;
uint16_t port = 1935; uint16_t port = start_with(url, "rtmps") ? 443 : 1935;
splitUrl(host_url, host_url, port); splitUrl(host_url, host_url, port);
weak_ptr<RtmpPusher> weakSelf = static_pointer_cast<RtmpPusher>(shared_from_this()); weak_ptr<RtmpPusher> weakSelf = static_pointer_cast<RtmpPusher>(shared_from_this());

View File

@ -79,6 +79,9 @@ bool RtpProcess::inputRtp(bool is_udp, const Socket::Ptr &sock, const char *data
WarnP(this) << "Not rtp packet"; WarnP(this) << "Not rtp packet";
return false; return false;
} }
if (!_auth_err.empty()) {
throw toolkit::SockException(toolkit::Err_other, _auth_err);
}
if (_sock != sock) { if (_sock != sock) {
// 第一次运行本函数 // 第一次运行本函数
bool first = !_sock; bool first = !_sock;
@ -260,6 +263,7 @@ void RtpProcess::emitOnPublish() {
strong_self->doCachedFunc(); strong_self->doCachedFunc();
InfoP(strong_self) << "允许RTP推流"; InfoP(strong_self) << "允许RTP推流";
} else { } else {
strong_self->_auth_err = err;
WarnP(strong_self) << "禁止RTP推流:" << err; WarnP(strong_self) << "禁止RTP推流:" << err;
} }
}); });

View File

@ -94,6 +94,7 @@ private:
private: private:
bool _only_audio = false; bool _only_audio = false;
std::string _auth_err;
uint64_t _dts = 0; uint64_t _dts = 0;
uint64_t _total_bytes = 0; uint64_t _total_bytes = 0;
std::unique_ptr<sockaddr_storage> _addr; std::unique_ptr<sockaddr_storage> _addr;

View File

@ -173,15 +173,8 @@ void RtpSender::createRtcpSocket() {
return; return;
} }
struct sockaddr_storage addr; // 绑定目标rtcp端口(目标rtp端口 + 1)
//目标rtp端口 auto addr = SockUtil::make_sockaddr(_socket_rtp->get_peer_ip().data(), _socket_rtp->get_peer_port() + 1);
SockUtil::get_sock_peer_addr(_socket_rtp->rawFD(), addr);
//绑定目标rtcp端口(目标rtp端口 + 1)
switch (addr.ss_family) {
case AF_INET: ((sockaddr_in *)&addr)->sin_port = htons(ntohs(((sockaddr_in *)&addr)->sin_port) + 1); break;
case AF_INET6: ((sockaddr_in6 *)&addr)->sin6_port = htons(ntohs(((sockaddr_in6 *)&addr)->sin6_port) + 1); break;
default: assert(0); break;
}
_socket_rtcp->bindPeerAddr((struct sockaddr *)&addr, 0, true); _socket_rtcp->bindPeerAddr((struct sockaddr *)&addr, 0, true);
_rtcp_context = std::make_shared<RtcpContextForSend>(); _rtcp_context = std::make_shared<RtcpContextForSend>();

View File

@ -139,9 +139,16 @@ void RtpSession::onRtpPacket(const char *data, size_t len) {
} else { } else {
throw; throw;
} }
} catch (...) { } catch (std::exception &ex) {
if (!_is_udp) {
// tcp情况下立即断开连接
throw; throw;
} }
// udp情况下延时断开连接(等待超时自动关闭)防止频繁创建销毁RtpSession对象
WarnP(this) << ex.what();
_delay_close = true;
return;
}
_ticker.resetTime(); _ticker.resetTime();
} }
@ -164,8 +171,23 @@ static const char *findSSRC(const char *data, ssize_t len, uint32_t ssrc) {
return nullptr; return nullptr;
} }
static const char *findPsHeaderFlag(const char *data, ssize_t len) {
for (ssize_t i = 2; i <= len - 4; ++i) {
auto ptr = (const uint8_t *) data + i;
//PsHeader 0x000001ba、PsSystemHeader0x000001bb关键帧标识
if (ptr[0] == (0x00) && ptr[1] == (0x00) && ptr[2] == (0x01) && ptr[3] == (0xbb)) {
return (const char *) ptr;
}
}
return nullptr;
}
//rtp长度到ssrc间的长度固定为10 //rtp长度到ssrc间的长度固定为10
static size_t constexpr kSSRCOffset = 2 + 4 + 4; static size_t constexpr kSSRCOffset = 2 + 4 + 4;
// rtp长度到ps header间的长度固定为14 暂时不采用找ps header,采用找system header代替
// rtp长度到ps system header间的长度固定为20 (关键帧标识)
static size_t constexpr kPSHeaderOffset = 2 + 4 + 4 + 4 + 20;
const char *RtpSession::onSearchPacketTail(const char *data, size_t len) { const char *RtpSession::onSearchPacketTail(const char *data, size_t len) {
if (!_search_rtp) { if (!_search_rtp) {
@ -173,12 +195,26 @@ const char *RtpSession::onSearchPacketTail(const char *data, size_t len) {
return RtpSplitter::onSearchPacketTail(data, len); return RtpSplitter::onSearchPacketTail(data, len);
} }
if (!_process) { if (!_process) {
throw SockException(Err_shutdown, "ssrc未获取到无法通过ssrc恢复tcp上下文"); InfoL << "ssrc未获取到无法通过ssrc恢复tcp上下文尝试搜索PsSystemHeader恢复tcp上下文。";
auto rtp_ptr1 = searchByPsHeaderFlag(data,len);
return rtp_ptr1;
} }
auto rtp_ptr0 = searchBySSRC(data,len);
if(rtp_ptr0) {
return rtp_ptr0;
}
// ssrc搜索失败继续尝试搜索ps header flag
auto rtp_ptr2 = searchByPsHeaderFlag(data,len);
return rtp_ptr2;
}
const char *RtpSession::searchBySSRC(const char *data, size_t len) {
InfoL << "尝试rtp搜索ssrc..._ssrc=" << _ssrc;
//搜索第一个rtp的ssrc //搜索第一个rtp的ssrc
auto ssrc_ptr0 = findSSRC(data, len, _ssrc); auto ssrc_ptr0 = findSSRC(data, len, _ssrc);
if (!ssrc_ptr0) { if (!ssrc_ptr0) {
//未搜索到任意rtp返回数据不够 //未搜索到任意rtp返回数据不够
InfoL << "rtp搜索ssrc失败第一个数据不够丢弃rtp数据为" << len;
return nullptr; return nullptr;
} }
//这两个字节是第一个rtp的长度字段 //这两个字节是第一个rtp的长度字段
@ -189,13 +225,14 @@ const char *RtpSession::onSearchPacketTail(const char *data, size_t len) {
auto ssrc_ptr1 = findSSRC(ssrc_ptr0 + rtp_len, data + (ssize_t) len - ssrc_ptr0 - rtp_len, _ssrc); auto ssrc_ptr1 = findSSRC(ssrc_ptr0 + rtp_len, data + (ssize_t) len - ssrc_ptr0 - rtp_len, _ssrc);
if (!ssrc_ptr1) { if (!ssrc_ptr1) {
//未搜索到第二个rtp返回数据不够 //未搜索到第二个rtp返回数据不够
InfoL << "rtp搜索ssrc失败(第二个数据不够)丢弃rtp数据为" << len;
return nullptr; return nullptr;
} }
//两个ssrc的间隔正好等于rtp的长度(外加rtp长度字段)那么说明找到rtp //两个ssrc的间隔正好等于rtp的长度(外加rtp长度字段)那么说明找到rtp
auto ssrc_offset = ssrc_ptr1 - ssrc_ptr0; auto ssrc_offset = ssrc_ptr1 - ssrc_ptr0;
if (ssrc_offset == rtp_len + 2 || ssrc_offset == rtp_len + 4) { if (ssrc_offset == rtp_len + 2 || ssrc_offset == rtp_len + 4) {
InfoL << "rtp搜索成功tcp上下文恢复成功丢弃的rtp残余数据为" << rtp_len_ptr - data; InfoL << "rtp搜索ssrc成功tcp上下文恢复成功丢弃的rtp残余数据为" << rtp_len_ptr - data;
_search_rtp_finished = true; _search_rtp_finished = true;
if (rtp_len_ptr == data) { if (rtp_len_ptr == data) {
//停止搜索rtp否则会进入死循环 //停止搜索rtp否则会进入死循环
@ -208,5 +245,32 @@ const char *RtpSession::onSearchPacketTail(const char *data, size_t len) {
return ssrc_ptr1 - kSSRCOffset; return ssrc_ptr1 - kSSRCOffset;
} }
const char *RtpSession::searchByPsHeaderFlag(const char *data, size_t len) {
InfoL << "尝试rtp搜索PsSystemHeaderFlag..._ssrc=" << _ssrc;
// 搜索rtp中的第一个PsHeaderFlag
auto ps_header_flag_ptr = findPsHeaderFlag(data,len);
if (!ps_header_flag_ptr) {
InfoL << "rtp搜索flag失败丢弃rtp数据为" << len;
return nullptr;
}
auto rtp_ptr = ps_header_flag_ptr - kPSHeaderOffset;
_search_rtp_finished = true;
if (rtp_ptr == data) {
//停止搜索rtp否则会进入死循环
_search_rtp = false;
}
InfoL << "rtp搜索flag成功tcp上下文恢复成功丢弃的rtp残余数据为" << rtp_ptr - data;
// TODO or Not ? 更新设置ssrc
uint32_t rtp_ssrc = 0;
RtpSelector::getSSRC(rtp_ptr+2, len, rtp_ssrc);
_ssrc = rtp_ssrc;
InfoL << "设置_ssrc为" << _ssrc;
// RtpServer::updateSSRC(uint32_t ssrc)
return rtp_ptr;
}
}//namespace mediakit }//namespace mediakit
#endif//defined(ENABLE_RTPPROXY) #endif//defined(ENABLE_RTPPROXY)

View File

@ -41,6 +41,10 @@ protected:
void onRtpPacket(const char *data, size_t len) override; void onRtpPacket(const char *data, size_t len) override;
// RtpSplitter override // RtpSplitter override
const char *onSearchPacketTail(const char *data, size_t len) override; const char *onSearchPacketTail(const char *data, size_t len) override;
// 搜寻SSRC
const char *searchBySSRC(const char *data, size_t len);
// 搜寻PS包里的关键帧标头
const char *searchByPsHeaderFlag(const char *data, size_t len);
private: private:
bool _delay_close = false; bool _delay_close = false;

View File

@ -14,7 +14,7 @@
using namespace std; using namespace std;
using namespace toolkit; using namespace toolkit;
#define ENABLE_NTP_STAMP 0 #define ENABLE_NTP_STAMP 1
namespace mediakit { namespace mediakit {
@ -78,6 +78,10 @@ bool RtspMuxer::addTrack(const Track::Ptr &track) {
//添加其sdp //添加其sdp
_sdp.append(sdp->getSdp()); _sdp.append(sdp->getSdp());
trySyncTrack(); trySyncTrack();
// rtp的时间戳是pts允许回退
_stamp[TrackAudio].enableRollback(true);
_stamp[TrackVideo].enableRollback(true);
return true; return true;
} }

View File

@ -358,7 +358,10 @@ void RtspPusher::updateRtcpContext(const RtpPacket::Ptr &rtp){
auto &ticker = _rtcp_send_ticker[track_index]; auto &ticker = _rtcp_send_ticker[track_index];
auto &rtcp_ctx = _rtcp_context[track_index]; auto &rtcp_ctx = _rtcp_context[track_index];
rtcp_ctx->onRtp(rtp->getSeq(), rtp->getStamp(), rtp->ntp_stamp, rtp->sample_rate, rtp->size() - RtpPacket::kRtpTcpHeaderSize); rtcp_ctx->onRtp(rtp->getSeq(), rtp->getStamp(), rtp->ntp_stamp, rtp->sample_rate, rtp->size() - RtpPacket::kRtpTcpHeaderSize);
if (!rtp->ntp_stamp && !rtp->getStamp()) {
// 忽略时间戳都为0的rtp
return;
}
//send rtcp every 5 second //send rtcp every 5 second
if (ticker.elapsedTime() > 5 * 1000) { if (ticker.elapsedTime() > 5 * 1000) {
ticker.resetTime(); ticker.resetTime();

View File

@ -1242,6 +1242,10 @@ void RtspSession::updateRtcpContext(const RtpPacket::Ptr &rtp){
int track_index = getTrackIndexByTrackType(rtp->type); int track_index = getTrackIndexByTrackType(rtp->type);
auto &rtcp_ctx = _rtcp_context[track_index]; auto &rtcp_ctx = _rtcp_context[track_index];
rtcp_ctx->onRtp(rtp->getSeq(), rtp->getStamp(), rtp->ntp_stamp, rtp->sample_rate, rtp->size() - RtpPacket::kRtpTcpHeaderSize); rtcp_ctx->onRtp(rtp->getSeq(), rtp->getStamp(), rtp->ntp_stamp, rtp->sample_rate, rtp->size() - RtpPacket::kRtpTcpHeaderSize);
if (!rtp->ntp_stamp && !rtp->getStamp()) {
// 忽略时间戳都为0的rtp
return;
}
auto &ticker = _rtcp_send_tickers[track_index]; auto &ticker = _rtcp_send_tickers[track_index];
//send rtcp every 5 second //send rtcp every 5 second

View File

@ -11,12 +11,9 @@
#include <signal.h> #include <signal.h>
#include <iostream> #include <iostream>
#include "Util/logger.h" #include "Util/logger.h"
#include "Util/NoticeCenter.h"
#include "Poller/EventPoller.h"
#include "Player/PlayerProxy.h"
#include "Rtmp/RtmpPusher.h"
#include "Common/config.h" #include "Common/config.h"
#include "Common/Parser.h" #include "Common/Parser.h"
#include "Poller/EventPoller.h"
#include "Pusher/MediaPusher.h" #include "Pusher/MediaPusher.h"
#include "Record/MP4Reader.h" #include "Record/MP4Reader.h"
@ -24,113 +21,101 @@ using namespace std;
using namespace toolkit; using namespace toolkit;
using namespace mediakit; using namespace mediakit;
//推流器,保持强引用 // 这里才是真正执行main函数你可以把函数名(domain)改成main然后就可以输入自定义url了
MediaPusher::Ptr g_pusher; int domain(const string &file, const string &url) {
Timer::Ptr g_timer; // 设置日志
MediaSource::Ptr g_src; Logger::Instance().add(std::make_shared<ConsoleChannel>());
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
//声明函数 // 关闭所有转协议
//推流失败或断开延迟2秒后重试推流 mINI::Instance()[Protocol::kEnableMP4] = 0;
void rePushDelay(const EventPoller::Ptr &poller, mINI::Instance()[Protocol::kEnableFMP4] = 0;
const string &schema, mINI::Instance()[Protocol::kEnableHls] = 0;
const string &vhost, mINI::Instance()[Protocol::kEnableHlsFmp4] = 0;
const string &app, mINI::Instance()[Protocol::kEnableTS] = 0;
const string &stream, mINI::Instance()[Protocol::kEnableRtsp] = 0;
const string &filePath, mINI::Instance()[Protocol::kEnableRtmp] = 0;
const string &url);
//创建推流器并开始推流 // 根据url获取媒体协议类型注意大小写
void createPusher(const EventPoller::Ptr &poller, auto schema = strToLower(findSubString(url.data(), nullptr, "://").substr(0, 4));
const string &schema,
const string &vhost, // 只开启推流协议对应的转协议
const string &app, mINI::Instance()["protocol.enable_" + schema] = 1;
const string &stream,
const string &filePath, // 从mp4文件加载生成MediaSource对象
const string &url) { auto reader = std::make_shared<MP4Reader>(DEFAULT_VHOST, "live", "stream", file);
if (!g_src) { // 开始加载mp4ref_self设置为false这样reader对象设置为nullptr就能注销了file_repeat可以设置为空这样文件读完了就停止推流了
//不限制APP名并且指定文件绝对路径 reader->startReadMP4(100, false, true);
g_src = MediaSource::createFromMP4(schema, vhost, app, stream, filePath, false); auto src = MediaSource::find(schema, DEFAULT_VHOST, "live", "stream", false);
if (!src) {
// 文件不存在
WarnL << "File not existed: " << file;
return -1;
} }
if (!g_src) {
//文件不存在 // 选择一个poller线程绑定
WarnL << "MP4文件不存在:" << filePath; auto poller = EventPollerPool::Instance().getPoller();
// 创建推流器并绑定一个MediaSource
auto pusher = std::make_shared<MediaPusher>(src, poller);
std::weak_ptr<MediaSource> weak_src = src;
// src用完了可以直接置空防止main函数持有它(MP4Reader持有它即可)
src = nullptr;
// 可以指定rtsp推流方式支持tcp和udp方式默认tcp
//(*pusher)[Client::kRtpType] = Rtsp::RTP_UDP;
// 设置推流中断处理逻辑
std::weak_ptr<MediaPusher> weak_pusher = pusher;
pusher->setOnShutdown([poller, url, weak_pusher, weak_src](const SockException &ex) {
if (!weak_src.lock()) {
// 媒体注销导致的推流中断,不在重试推流
WarnL << "MediaSource released:" << ex << ", publish stopped";
return;
}
WarnL << "Server connection is closed:" << ex << ", republish after 2 seconds";
// 重新推流, 2秒后重试
poller->doDelayTask(2 * 1000, [weak_pusher, url]() {
if (auto strong_push = weak_pusher.lock()) {
strong_push->publish(url);
}
return 0;
});
});
// 设置发布结果处理逻辑
pusher->setOnPublished([poller, weak_pusher, url](const SockException &ex) {
if (!ex) {
InfoL << "Publish success, please play with player:" << url;
return; return;
} }
//创建推流器并绑定一个MediaSource WarnL << "Publish fail:" << ex << ", republish after 2 seconds";
g_pusher.reset(new MediaPusher(g_src, poller)); // 如果发布失败,就重试
//可以指定rtsp推流方式支持tcp和udp方式默认tcp poller->doDelayTask(2 * 1000, [weak_pusher, url]() {
//(*g_pusher)[Client::kRtpType] = Rtsp::RTP_UDP; if (auto strong_push = weak_pusher.lock()) {
strong_push->publish(url);
//设置推流中断处理逻辑
g_pusher->setOnShutdown([poller, schema, vhost, app, stream, filePath, url](const SockException &ex) {
WarnL << "Server connection is closed:" << ex.getErrCode() << " " << ex.what();
//重新推流
rePushDelay(poller, schema, vhost, app, stream, filePath, url);
});
//设置发布结果处理逻辑
g_pusher->setOnPublished([poller, schema, vhost, app, stream, filePath, url](const SockException &ex) {
if (ex) {
WarnL << "Publish fail:" << ex.getErrCode() << " " << ex.what();
//如果发布失败,就重试
rePushDelay(poller, schema, vhost, app, stream, filePath, url);
} else {
InfoL << "Publish success,Please play with player:" << url;
} }
return 0;
}); });
g_pusher->publish(url); });
} pusher->publish(url);
//推流失败或断开延迟2秒后重试推流 // sleep(5);
void rePushDelay(const EventPoller::Ptr &poller, // reader 置空可以终止推流相关资源
const string &schema, // reader = nullptr;
const string &vhost,
const string &app,
const string &stream,
const string &filePath,
const string &url) {
g_timer = std::make_shared<Timer>(2.0f, [poller, schema, vhost, app, stream, filePath, url]() {
InfoL << "Re-Publishing...";
//重新推流
createPusher(poller, schema, vhost, app, stream, filePath, url);
//此任务不重复
return false;
}, poller);
}
//这里才是真正执行main函数你可以把函数名(domain)改成main然后就可以输入自定义url了 // 设置退出信号处理函数
int domain(const string &filePath, const string &pushUrl) {
//设置日志
Logger::Instance().add(std::make_shared<ConsoleChannel>());
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
//循环点播mp4文件
mINI::Instance()[Record::kFileRepeat] = 1;
mINI::Instance()[Protocol::kHlsDemand] = 1;
mINI::Instance()[Protocol::kTSDemand] = 1;
mINI::Instance()[Protocol::kFMP4Demand] = 1;
//mINI::Instance()[Protocol::kRtspDemand] = 1;
//mINI::Instance()[Protocol::kRtmpDemand] = 1;
auto poller = EventPollerPool::Instance().getPoller();
//vhost/app/stream可以随便自己填现在不限制app应用名了
createPusher(poller, findSubString(pushUrl.data(), nullptr, "://").substr(0, 4), DEFAULT_VHOST, "live", "stream", filePath, pushUrl);
//设置退出信号处理函数
static semaphore sem; static semaphore sem;
signal(SIGINT, [](int) { sem.post(); });// 设置退出信号 signal(SIGINT, [](int) { sem.post(); }); // 设置退出信号
sem.wait(); sem.wait();
g_pusher.reset();
g_timer.reset();
return 0; return 0;
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
//可以使用test_server生成的mp4文件 // 可以使用test_server生成的mp4文件
//文件使用绝对路径推流url支持rtsp和rtmp // 文件使用绝对路径推流url支持rtsp和rtmp
return domain("/home/work/test2.mp4", "rtmp://127.0.0.1/live/rtsp_push"); // return domain("/Users/xiongziliang/Downloads/mp4/Quantum.mp4", "rtsp://127.0.0.1/live/rtsp_push");
return domain(argv[1], argv[2]);
} }

145
tools/openapi/generates.py Normal file
View File

@ -0,0 +1,145 @@
# -*- coding:utf-8 -*-
import copy
import os
import re
import subprocess
import sys
import json
def check_installed(command: str) -> bool:
"""
check command is installed
:param command:
:return:
"""
if os.system(f"command -v {command} > /dev/null") == 0:
return True
else:
return False
def run_cmd(cmd: str, assert_success=False, capture_output=False, env=None) -> bool:
"""
run cmd
:param cmd:
:param assert_success:
:param env:
:param capture_output:
:param env:
:return:
"""
if not env:
env = os.environ.copy()
result = subprocess.run(cmd, shell=True, env=env, capture_output=capture_output)
# Assert the command ran successfully
if assert_success and result.returncode != 0:
print("Command '" + cmd + "' failed with exit status code '" + str(
result.returncode) + "'.\n\nExiting now.\nTry running the script again.")
print(result.stderr.decode())
sys.stdout.flush()
sys.stderr.flush()
sys.exit(1)
return True
def check_dependencies() -> None:
"""
check dependencies
:return:
"""
if not check_installed("p2o"):
print()
print("p2o is not installed, please install it first!")
print("If you use npm, you can install it by the following command:")
print("npm install -g postman-to-openapi")
print()
sys.exit(1)
else:
print("p2o is installed")
def get_version() -> str:
"""
get version
:return:
"""
if os.path.isfile("../../cmake-build-debug/version.h"):
print("Found version.h in cmake-build-debug")
version_h_path = "../../cmake-build-debug/version.h"
elif os.path.isfile("../../cmake-build-release/version.h"):
print("Found version.h in cmake-build-release")
version_h_path = "../../cmake-build-release/version.h"
else:
print("version.h not found")
print("Please compile first")
exit()
with open(version_h_path, 'r') as f:
content = f.read()
commit_hash = re.search(r'define COMMIT_HASH (.*)', content).group(1)
commit_time = re.search(r'define COMMIT_TIME (.*)', content).group(1)
branch_name = re.search(r'define BRANCH_NAME (.*)', content).group(1)
build_time = re.search(r'define BUILD_TIME (.*)', content).group(1)
version = f"ZLMediaKit(git hash:{commit_hash}/{commit_time},branch:{branch_name},build time:{build_time})"
print(f"version: {version}")
return version
def get_secret() -> str:
"""
get secret from default config file or user config file
:return:
"""
default_postman = json.load(open("../../postman/127.0.0.1.postman_environment.json", 'r'))
secret = "035c73f7-bb6b-4889-a715-d9eb2d1925cc"
for item in default_postman["values"]:
if item["key"] == "ZLMediaKit_secret":
secret = item["value"]
break
for root, dirs, files in os.walk("../../release/"):
for file in files:
if file == "config.ini":
config_path = os.path.join(root, file)
with open(config_path, 'r') as f:
content = f.read()
secret = re.search(r'secret=(.*)', content).group(1)
return secret
def update_options(version: str, secret: str) -> None:
"""
update options
:param version:
:param secret:
:return:
"""
print("update options")
options = json.load(open("./options.json", 'r'))
options["info"]["version"] = version
options["additionalVars"]["ZLMediaKit_secret"] = secret
json.dump(options, open("./options.json", 'w'), indent=4)
def generate() -> None:
"""
generate
:return:
"""
print("generate")
run_cmd("p2o ../../postman/ZLMediaKit.postman_collection.json -f ../../www/swagger/openapi.json -o ./options.json",
True, True)
openapi = json.load(open("../../www/swagger/openapi.json", 'r'))
for path in openapi["paths"]:
openapi["paths"][path]["post"] = copy.deepcopy(openapi["paths"][path]["get"])
openapi["paths"][path]["post"]["tags"] = ["POST"]
# save
json.dump(openapi, open("../../www/swagger/openapi.json", 'w'), indent=4)
print("generate success")
if __name__ == "__main__":
check_dependencies()
version = get_version()
secret = get_secret()
update_options(version, secret)
generate()

View File

@ -0,0 +1,40 @@
{
"info": {
"title": "ZLMediaKit HTTP API",
"version": "ZLMediaKit(git hash:\"a78ca2e\"/\"2023-11-17T11:12:51+08:00\",branch:\"patch-63\",build time:\"2023-11-23T14:35:02\")",
"description": "You can test the HTTP API provided by ZlMediaKit here. For usage documentation, please refer to [here](https://docs.zlmediakit.com/guide/media_server/restful_api.html)",
"termsOfService": "https://docs.zlmediakit.com",
"license": {
"name": "MIT",
"url": "https://docs.zlmediakit.com/more/license.html"
},
"contact": {
"name": "Contact Support",
"url": "https://docs.zlmediakit.com/more/contact.html",
"email": "1213642868@qq.com"
},
"xLogo": {
"url": "/logo.png",
"backgroundColor": "#FFFFFF",
"altText": "ZLMediaKit"
}
},
"defaultTag": "GET",
"outputFormat": "json",
"replaceVars": true,
"servers": [
{
"url": "/",
"description": "Localhost"
}
],
"externalDocs": {
"description": "ZLMediaKit Documentation",
"url": "https://docs.zlmediakit.com"
},
"additionalVars": {
"defaultVhost": "__defaultVhost__",
"ZLMediaKit_secret": "1oV1R5Z9xlrjH4QN7GXNvS5IUaYtuFgX",
"ZLMediaKit_URL": ""
}
}

View File

@ -258,7 +258,7 @@ uint64_t NackContext::reSendNack() {
for (auto it = nack_rtp.begin(); it != nack_rtp.end();) { for (auto it = nack_rtp.begin(); it != nack_rtp.end();) {
if (pid == -1) { if (pid == -1) {
pid = *it; pid = *it;
vec.resize(FCI_NACK::kBitSize, false); vec.assign(FCI_NACK::kBitSize, false);
++it; ++it;
continue; continue;
} }

View File

@ -90,7 +90,7 @@ const char* sockTypeStr(Session* session) {
WebRtcTransport::WebRtcTransport(const EventPoller::Ptr &poller) { WebRtcTransport::WebRtcTransport(const EventPoller::Ptr &poller) {
_poller = poller; _poller = poller;
_identifier = "zlm_" + to_string(++s_key); _identifier = "zlm" + to_string(++s_key);
_packet_pool.setSize(64); _packet_pool.setSize(64);
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

16
www/swagger/index.css Normal file
View File

@ -0,0 +1,16 @@
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}

19
www/swagger/index.html Normal file
View File

@ -0,0 +1,19 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="stylesheet" type="text/css" href="index.css" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script src="./swagger-initializer.js" charset="UTF-8"> </script>
</body>
</html>

View File

@ -0,0 +1,79 @@
<!doctype html>
<html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1).replace('?', '&');
} else {
qp = location.search.substring(1);
}
arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value);
}
) : {};
isValid = qp.state === sentState;
if ((
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
if (document.readyState !== 'loading') {
run();
} else {
document.addEventListener('DOMContentLoaded', function () {
run();
});
}
</script>
</body>
</html>

4233
www/swagger/openapi.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
window.onload = function() {
//<editor-fold desc="Changeable Configuration Block">
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
url: "/swagger/openapi.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
//</editor-fold>
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -155,44 +155,47 @@
audioEnable:document.getElementById('audioEnable').checked, audioEnable:document.getElementById('audioEnable').checked,
videoEnable:document.getElementById('videoEnable').checked, videoEnable:document.getElementById('videoEnable').checked,
recvOnly:recvOnly, recvOnly:recvOnly,
resolution:{w:w,h:h}, resolution:{w,h},
usedatachannel:document.getElementById('datachannel').checked, usedatachannel:document.getElementById('datachannel').checked,
} }
); );
player.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR,function(e) player.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR,function(e)
{// ICE 协商出错 {
// ICE 协商出错
console.log('ICE 协商出错'); console.log('ICE 协商出错');
}); });
player.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,function(e) player.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,function(e)
{//获取到了远端流,可以播放 {
//获取到了远端流,可以播放
console.log('播放成功',e.streams); console.log('播放成功',e.streams);
}); });
player.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED,function(e) player.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED,function(e)
{// offer anwser 交换失败 {
// offer anwser 交换失败
console.log('offer anwser 交换失败',e); console.log('offer anwser 交换失败',e);
stop(); stop();
}); });
player.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM,function(s) player.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM,function(s)
{// 获取到了本地流 {
// 获取到了本地流
document.getElementById('selfVideo').srcObject=s; document.getElementById('selfVideo').srcObject=s;
document.getElementById('selfVideo').muted = true; document.getElementById('selfVideo').muted = true;
//console.log('offer anwser 交换失败',e) //console.log('offer anwser 交换失败',e)
}); });
player.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED,function(s) player.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED,function(s)
{// 获取本地流失败 {
// 获取本地流失败
console.log('获取本地流失败'); console.log('获取本地流失败');
}); });
player.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE,function(state) player.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE,function(state)
{// RTC 状态变化 ,详情参考 https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionState {
// RTC 状态变化 ,详情参考 https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionState
console.log('当前状态==>',state); console.log('当前状态==>',state);
}); });
@ -267,19 +270,20 @@
} }
} }
function on_click_to_play(app, stream) { function on_click_to_play(app, stream) {
console.log(`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`; var url = `${document.location.protocol}//${window.location.host}/index/api/webrtc?app=${app}&stream=${stream}&type=play`;
document.getElementById('streamUrl').value = url; document.getElementById('streamUrl').value = url;
start(); start();
} }
function clearStreamList() { function clearStreamList() {
let content = document.getElementById("olstreamlist"); let content = document.getElementById("olstreamlist");
while (content.hasChildNodes()) { while (content.hasChildNodes()) {
content.removeChild(content.firstChild); content.removeChild(content.firstChild);
} }
} }
function fillStreamList(json) { function fillStreamList(json) {
clearStreamList(); clearStreamList();
if (json.code != 0 || !json.data) { if (json.code != 0 || !json.data) {
@ -308,6 +312,7 @@
} }
} }
} }
async function getData(url) { async function getData(url) {
const response = await fetch(url, { const response = await fetch(url, {
method: 'GET' method: 'GET'
@ -316,11 +321,13 @@
//console.log(data); //console.log(data);
return data; return data;
} }
function get_media_list() { function get_media_list() {
let url = document.location.protocol+"//"+window.location.host+"/index/api/getMediaList?secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc"; let url = document.location.protocol+"//"+window.location.host+"/index/api/getMediaList?secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
let json = getData(url); let json = getData(url);
json.then((json)=> fillStreamList(json)); json.then((json)=> fillStreamList(json));
} }
setInterval(() => { setInterval(() => {
// get_media_list(); // get_media_list();
}, 5000); }, 5000);