mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-22 19:00:01 +08:00
Merge branch 'master' into dev
This commit is contained in:
commit
f5d78c7fb1
@ -1 +1 @@
|
||||
Subproject commit 97871cfa78fcd2fae164243a8c653e323385772d
|
||||
Subproject commit ad44a16c99834540b397774ad6c7f3f8ed619d56
|
@ -1 +1 @@
|
||||
Subproject commit 3dc623a899eee3810587fb267dbff770b626a55b
|
||||
Subproject commit a8a80e0738b052aa5671ef82a295ef388bd28e13
|
@ -13,6 +13,7 @@
|
||||
#include "mk_common.h"
|
||||
#include "mk_tcp.h"
|
||||
#include "mk_track.h"
|
||||
#include "mk_util.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#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_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_do,那么没必要克隆对象
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
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){
|
||||
assert(ctx);
|
||||
Broadcast::PublishAuthInvoker *invoker = (Broadcast::PublishAuthInvoker *)ctx;
|
||||
|
18
dockerfile
18
dockerfile
@ -1,4 +1,4 @@
|
||||
FROM ubuntu:18.04 AS build
|
||||
FROM ubuntu:20.04 AS build
|
||||
ARG MODEL
|
||||
#shell,rtmp,rtsp,rtsps,http,https,rtp
|
||||
EXPOSE 1935/tcp
|
||||
@ -31,24 +31,31 @@ RUN apt-get update && \
|
||||
gcc \
|
||||
g++ \
|
||||
libavcodec-dev libavutil-dev libswscale-dev libresample-dev \
|
||||
libsdl-dev libusrsctp-dev \
|
||||
gdb && \
|
||||
apt-get autoremove -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
|
||||
COPY . /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 mkdir -p build release/linux/${MODEL}/
|
||||
|
||||
WORKDIR /opt/media/ZLMediaKit/build
|
||||
RUN cmake -DCMAKE_BUILD_TYPE=${MODEL} -DENABLE_WEBRTC=true -DENABLE_FFMPEG=true -DENABLE_TESTS=false -DENABLE_API=false .. && \
|
||||
make -j $(nproc)
|
||||
|
||||
FROM ubuntu:18.04
|
||||
FROM ubuntu:20.04
|
||||
ARG MODEL
|
||||
|
||||
# ADD sources.list /etc/apt/sources.list
|
||||
@ -68,6 +75,7 @@ RUN apt-get update && \
|
||||
gcc \
|
||||
g++ \
|
||||
libavcodec-dev libavutil-dev libswscale-dev libresample-dev \
|
||||
libsdl-dev libusrsctp-dev \
|
||||
gdb && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean -y && \
|
||||
|
@ -2048,6 +2048,142 @@
|
||||
}
|
||||
},
|
||||
"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": [
|
||||
|
@ -11,44 +11,52 @@
|
||||
#include <sys/stat.h>
|
||||
#include <math.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
|
||||
#include <io.h>
|
||||
#include <iostream>
|
||||
#include <tchar.h>
|
||||
#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)
|
||||
#include "version.h"
|
||||
#endif
|
||||
@ -1777,6 +1785,23 @@ void installWebApi() {
|
||||
});
|
||||
#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////////////
|
||||
api_regist("/index/hook/on_publish",[](API_ARGS_JSON){
|
||||
//开始推流事件
|
||||
|
@ -577,8 +577,8 @@ void installWebHook() {
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastStreamNoneReader, [](BroadcastStreamNoneReaderArgs) {
|
||||
if (!origin_urls.empty()) {
|
||||
// 边沿站无人观看时立即停止溯源
|
||||
if (!origin_urls.empty() && sender.getOriginType() == MediaOriginType::pull) {
|
||||
// 边沿站无人观看时如果是拉流的则立即停止溯源
|
||||
sender.close(false);
|
||||
WarnL << "无人观看主动关闭流:" << sender.getOriginUrl();
|
||||
return;
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include "JemallocUtil.h"
|
||||
#include "Util/logger.h"
|
||||
#include <cstdint>
|
||||
#ifdef USE_JEMALLOC
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
|
@ -12,6 +12,7 @@
|
||||
#define ZLMEDIAKIT_JEMALLOCUTIL_H
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
namespace mediakit {
|
||||
class JemallocUtil {
|
||||
public:
|
||||
|
@ -202,6 +202,11 @@ public:
|
||||
|
||||
template <typename MAP>
|
||||
ProtocolOption(const MAP &allArgs) : ProtocolOption() {
|
||||
load(allArgs);
|
||||
}
|
||||
|
||||
template <typename MAP>
|
||||
void load(const MAP &allArgs) {
|
||||
#define GET_OPT_VALUE(key) getArgsValue(allArgs, #key, key)
|
||||
GET_OPT_VALUE(modify_stamp);
|
||||
GET_OPT_VALUE(enable_audio);
|
||||
|
@ -529,6 +529,8 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
|
||||
ret = _fmp4->inputFrame(frame) ? true : ret;
|
||||
}
|
||||
if (_ring) {
|
||||
// 此场景由于直接转发,可能存在切换线程引起的数据被缓存在管道,所以需要CacheAbleFrame
|
||||
frame = Frame::getCacheAbleFrame(frame);
|
||||
if (frame->getTrackType() == TrackVideo) {
|
||||
// 视频时,遇到第一帧配置帧或关键帧则标记为gop开始处
|
||||
auto video_key_pos = frame->keyFrame() || frame->configFrame();
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include <cinttypes>
|
||||
#include "Parser.h"
|
||||
#include "strCoding.h"
|
||||
#include "macros.h"
|
||||
#include "Util/base64.h"
|
||||
#include "Network/sockutil.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);
|
||||
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
|
||||
//测试代码
|
||||
static onceToken token([](){
|
||||
|
@ -21,6 +21,8 @@ namespace mediakit {
|
||||
std::string findSubString(const char *buf, const char *start, const char *end, size_t buf_size = 0);
|
||||
// 把url解析为主机地址和端口号,兼容ipv4/ipv6/dns
|
||||
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 {
|
||||
bool operator()(const std::string &__x, const std::string &__y) const { return strcasecmp(__x.data(), __y.data()) < 0; }
|
||||
|
@ -20,8 +20,13 @@ using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
int64_t DeltaStamp::relativeStamp(int64_t stamp) {
|
||||
_relative_stamp += deltaStamp(stamp);
|
||||
DeltaStamp::DeltaStamp() {
|
||||
// 时间戳最大允许跳跃300ms
|
||||
_max_delta = 300;
|
||||
}
|
||||
|
||||
int64_t DeltaStamp::relativeStamp(int64_t stamp, bool enable_rollback) {
|
||||
_relative_stamp += deltaStamp(stamp, enable_rollback);
|
||||
return _relative_stamp;
|
||||
}
|
||||
|
||||
@ -29,7 +34,7 @@ int64_t DeltaStamp::relativeStamp() {
|
||||
return _relative_stamp;
|
||||
}
|
||||
|
||||
int64_t DeltaStamp::deltaStamp(int64_t stamp) {
|
||||
int64_t DeltaStamp::deltaStamp(int64_t stamp, bool enable_rollback) {
|
||||
if (!_last_stamp) {
|
||||
// 第一次计算时间戳增量,时间戳增量为0
|
||||
if (stamp) {
|
||||
@ -43,14 +48,25 @@ int64_t DeltaStamp::deltaStamp(int64_t stamp) {
|
||||
// 时间戳增量为正,返回之
|
||||
_last_stamp = stamp;
|
||||
// 在直播情况下,时间戳增量不得大于MAX_DELTA_STAMP,否则强制相对时间戳加1
|
||||
return ret < MAX_DELTA_STAMP ? ret : 1;
|
||||
if (ret > _max_delta) {
|
||||
needSync();
|
||||
return 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 时间戳增量为负,说明时间戳回环了或回退了
|
||||
_last_stamp = stamp;
|
||||
if (!enable_rollback || -ret > _max_delta) {
|
||||
// 不允许回退或者回退太多了, 强制时间戳加1
|
||||
needSync();
|
||||
return 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 如果时间戳回退不多,那么返回负值,否则返回加1
|
||||
return -ret < MAX_DELTA_STAMP ? ret : 1;
|
||||
void DeltaStamp::setMaxDelta(size_t max_delta) {
|
||||
_max_delta = max_delta;
|
||||
}
|
||||
|
||||
void Stamp::setPlayBack(bool playback) {
|
||||
@ -58,9 +74,18 @@ void Stamp::setPlayBack(bool playback) {
|
||||
}
|
||||
|
||||
void Stamp::syncTo(Stamp &other) {
|
||||
_need_sync = true;
|
||||
_sync_master = &other;
|
||||
}
|
||||
|
||||
void Stamp::needSync() {
|
||||
_need_sync = true;
|
||||
}
|
||||
|
||||
void Stamp::enableRollback(bool flag) {
|
||||
_enable_rollback = flag;
|
||||
}
|
||||
|
||||
// 限制dts回退
|
||||
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);
|
||||
@ -87,15 +112,26 @@ void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_ou
|
||||
return;
|
||||
}
|
||||
|
||||
if (_sync_master && _sync_master->_last_dts_in) {
|
||||
// 需要同步时间戳
|
||||
if (_sync_master && _sync_master->_last_dts_in && (_need_sync || _sync_master->_need_sync)) {
|
||||
// 音视频dts当前时间差
|
||||
int64_t dts_diff = _last_dts_in - _sync_master->_last_dts_in;
|
||||
if (ABS(dts_diff) < 5000) {
|
||||
// 如果绝对时间戳小于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();
|
||||
} else {
|
||||
_relative_stamp += deltaStamp(dts);
|
||||
_relative_stamp += deltaStamp(dts, _enable_rollback);
|
||||
}
|
||||
_last_dts_in = dts;
|
||||
}
|
||||
|
@ -19,19 +19,27 @@ namespace mediakit {
|
||||
|
||||
class DeltaStamp{
|
||||
public:
|
||||
DeltaStamp() = default;
|
||||
DeltaStamp();
|
||||
~DeltaStamp() = default;
|
||||
|
||||
/**
|
||||
* 计算时间戳增量
|
||||
* @param stamp 绝对时间戳
|
||||
* @param enable_rollback 是否允许相当时间戳回退
|
||||
* @return 时间戳增量
|
||||
*/
|
||||
int64_t deltaStamp(int64_t stamp);
|
||||
int64_t relativeStamp(int64_t stamp);
|
||||
int64_t deltaStamp(int64_t stamp, bool enable_rollback = true);
|
||||
int64_t relativeStamp(int64_t stamp, bool enable_rollback = true);
|
||||
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 _relative_stamp = 0;
|
||||
};
|
||||
@ -77,6 +85,11 @@ public:
|
||||
*/
|
||||
void syncTo(Stamp &other);
|
||||
|
||||
/**
|
||||
* 是否允许时间戳回退
|
||||
*/
|
||||
void enableRollback(bool flag);
|
||||
|
||||
private:
|
||||
//主要实现音视频时间戳同步功能
|
||||
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 needSync() override;
|
||||
|
||||
private:
|
||||
bool _playback = false;
|
||||
bool _need_sync = false;
|
||||
// 默认不允许时间戳回滚
|
||||
bool _enable_rollback = false;
|
||||
int64_t _relative_stamp = 0;
|
||||
int64_t _last_dts_in = 0;
|
||||
int64_t _last_dts_out = 0;
|
||||
int64_t _last_pts_out = 0;
|
||||
toolkit::SmoothTicker _ticker;
|
||||
bool _playback = false;
|
||||
Stamp *_sync_master = nullptr;
|
||||
};
|
||||
|
||||
|
@ -353,6 +353,7 @@ const string kBeatIntervalMS = "beat_interval_ms";
|
||||
const string kBenchmarkMode = "benchmark_mode";
|
||||
const string kWaitTrackReady = "wait_track_ready";
|
||||
const string kPlayTrack = "play_track";
|
||||
const string kProxyUrl = "proxy_url";
|
||||
} // namespace Client
|
||||
|
||||
} // namespace mediakit
|
||||
|
@ -403,6 +403,8 @@ extern const std::string kWaitTrackReady;
|
||||
// rtsp播放指定track,可选项有0(不指定,默认)、1(视频)、2(音频)
|
||||
// 设置方法:player[Client::kPlayTrack] = 0/1/2;
|
||||
extern const std::string kPlayTrack;
|
||||
//设置代理url,目前只支持http协议
|
||||
extern const std::string kProxyUrl;
|
||||
} // namespace Client
|
||||
} // namespace mediakit
|
||||
|
||||
|
@ -22,6 +22,7 @@ HlsPlayer::HlsPlayer(const EventPoller::Ptr &poller) {
|
||||
void HlsPlayer::play(const string &url) {
|
||||
_play_result = false;
|
||||
_play_url = url;
|
||||
setProxyUrl((*this)[Client::kProxyUrl]);
|
||||
fetchIndexFile();
|
||||
}
|
||||
|
||||
@ -88,6 +89,7 @@ void HlsPlayer::fetchSegment() {
|
||||
weak_ptr<HlsPlayer> weak_self = static_pointer_cast<HlsPlayer>(shared_from_this());
|
||||
if (!_http_ts_player) {
|
||||
_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) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (strong_self) {
|
||||
|
@ -78,12 +78,15 @@ void HttpClient::sendRequest(const string &url) {
|
||||
printer.pop_back();
|
||||
_header.emplace("Cookie", printer);
|
||||
}
|
||||
|
||||
if (!alive() || host_changed) {
|
||||
startConnect(host, port, _wait_header_ms / 1000.0f);
|
||||
if (isUsedProxy()) {
|
||||
startConnect(_proxy_host, _proxy_port, _wait_header_ms / 1000.0f);
|
||||
} else {
|
||||
SockException ex;
|
||||
onConnect_l(ex);
|
||||
if (!alive() || host_changed) {
|
||||
startConnect(host, port, _wait_header_ms / 1000.0f);
|
||||
} else {
|
||||
SockException ex;
|
||||
onConnect_l(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,15 +161,23 @@ void HttpClient::onConnect_l(const SockException &ex) {
|
||||
onResponseCompleted_l(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
_StrPrinter printer;
|
||||
printer << _method + " " << _path + " HTTP/1.1\r\n";
|
||||
for (auto &pr : _header) {
|
||||
printer << pr.first + ": ";
|
||||
printer << pr.second + "\r\n";
|
||||
//不使用代理或者代理服务器已经连接成功
|
||||
if (_proxy_connected || !isUsedProxy()) {
|
||||
printer << _method + " " << _path + " HTTP/1.1\r\n";
|
||||
for (auto &pr : _header) {
|
||||
printer << pr.first + ": ";
|
||||
printer << pr.second + "\r\n";
|
||||
}
|
||||
_header.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";
|
||||
}
|
||||
}
|
||||
_header.clear();
|
||||
_path.clear();
|
||||
SockSender::send(printer << "\r\n");
|
||||
onFlush();
|
||||
}
|
||||
@ -401,4 +412,28 @@ void HttpClient::setCompleteTimeout(size_t 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 */
|
||||
|
@ -141,6 +141,11 @@ public:
|
||||
*/
|
||||
void setCompleteTimeout(size_t timeout_ms);
|
||||
|
||||
/**
|
||||
* 设置http代理url
|
||||
*/
|
||||
void setProxyUrl(std::string proxy_url);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* 收到http回复头
|
||||
@ -181,11 +186,16 @@ protected:
|
||||
void onFlush() override;
|
||||
void onManager() override;
|
||||
|
||||
void clearResponse();
|
||||
|
||||
bool checkProxyConnected(const char *data, size_t len);
|
||||
bool isUsedProxy() const;
|
||||
bool isProxyConnected() const;
|
||||
|
||||
private:
|
||||
void onResponseCompleted_l(const toolkit::SockException &ex);
|
||||
void onConnect_l(const toolkit::SockException &ex);
|
||||
void checkCookie(HttpHeader &headers);
|
||||
void clearResponse();
|
||||
|
||||
private:
|
||||
//for http response
|
||||
@ -215,6 +225,13 @@ private:
|
||||
toolkit::Ticker _wait_header;
|
||||
toolkit::Ticker _wait_body;
|
||||
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 */
|
||||
|
@ -15,13 +15,30 @@ using namespace toolkit;
|
||||
namespace mediakit {
|
||||
|
||||
void HttpClientImp::onConnect(const SockException &ex) {
|
||||
if (!isHttps()) {
|
||||
//https 302跳转 http时,需要关闭ssl
|
||||
if (isUsedProxy() && !isProxyConnected()) {
|
||||
// 连接代理服务器
|
||||
setDoNotUseSSL();
|
||||
HttpClient::onConnect(ex);
|
||||
} else {
|
||||
TcpClientWithSSL<HttpClient>::onConnect(ex);
|
||||
if (!isHttps()) {
|
||||
// https 302跳转 http时,需要关闭ssl
|
||||
setDoNotUseSSL();
|
||||
HttpClient::onConnect(ex);
|
||||
} else {
|
||||
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 */
|
||||
|
@ -24,6 +24,7 @@ public:
|
||||
|
||||
protected:
|
||||
void onConnect(const toolkit::SockException &ex) override;
|
||||
ssize_t onRecvHeader(const char *data, size_t len) override;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
@ -21,6 +21,7 @@ void TsPlayer::play(const string &url) {
|
||||
TraceL << "play http-ts: " << url;
|
||||
_play_result = false;
|
||||
_benchmark_mode = (*this)[Client::kBenchmarkMode].as<int>();
|
||||
setProxyUrl((*this)[Client::kProxyUrl]);
|
||||
setHeaderTimeout((*this)[Client::kTimeoutMS].as<int>());
|
||||
setBodyTimeout((*this)[Client::kMediaTimeoutMS].as<int>());
|
||||
setMethod("GET");
|
||||
|
@ -21,6 +21,20 @@ using namespace toolkit;
|
||||
namespace mediakit {
|
||||
|
||||
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};
|
||||
_poller = WorkThreadPool::Instance().getPoller();
|
||||
@ -42,10 +56,7 @@ MP4Reader::MP4Reader(const std::string &vhost, const std::string &app, const std
|
||||
if (tuple.stream.empty()) {
|
||||
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);
|
||||
auto tracks = _demuxer->getTracks(false);
|
||||
if (tracks.empty()) {
|
||||
|
@ -28,7 +28,12 @@ public:
|
||||
* @param stream_id 流id,置空时,只解复用mp4,但是不生成MediaSource
|
||||
* @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;
|
||||
|
||||
/**
|
||||
@ -66,6 +71,8 @@ private:
|
||||
void setCurrentStamp(uint32_t stamp);
|
||||
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:
|
||||
bool _file_repeat = false;
|
||||
bool _have_video = false;
|
||||
|
@ -22,6 +22,7 @@ FlvPlayer::FlvPlayer(const EventPoller::Ptr &poller) {
|
||||
void FlvPlayer::play(const string &url) {
|
||||
TraceL << "play http-flv: " << url;
|
||||
_play_result = false;
|
||||
setProxyUrl((*this)[Client::kProxyUrl]);
|
||||
setHeaderTimeout((*this)[Client::kTimeoutMS].as<int>());
|
||||
setBodyTimeout((*this)[Client::kMediaTimeoutMS].as<int>());
|
||||
setMethod("GET");
|
||||
|
@ -69,7 +69,7 @@ void RtmpPlayer::play(const string &url) {
|
||||
}
|
||||
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);
|
||||
|
||||
if (!(*this)[Client::kNetAdapter].empty()) {
|
||||
|
@ -82,7 +82,7 @@ void RtmpPusher::publish(const string &url) {
|
||||
}
|
||||
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);
|
||||
|
||||
weak_ptr<RtmpPusher> weakSelf = static_pointer_cast<RtmpPusher>(shared_from_this());
|
||||
|
@ -79,6 +79,9 @@ bool RtpProcess::inputRtp(bool is_udp, const Socket::Ptr &sock, const char *data
|
||||
WarnP(this) << "Not rtp packet";
|
||||
return false;
|
||||
}
|
||||
if (!_auth_err.empty()) {
|
||||
throw toolkit::SockException(toolkit::Err_other, _auth_err);
|
||||
}
|
||||
if (_sock != sock) {
|
||||
// 第一次运行本函数
|
||||
bool first = !_sock;
|
||||
@ -260,6 +263,7 @@ void RtpProcess::emitOnPublish() {
|
||||
strong_self->doCachedFunc();
|
||||
InfoP(strong_self) << "允许RTP推流";
|
||||
} else {
|
||||
strong_self->_auth_err = err;
|
||||
WarnP(strong_self) << "禁止RTP推流:" << err;
|
||||
}
|
||||
});
|
||||
|
@ -94,6 +94,7 @@ private:
|
||||
|
||||
private:
|
||||
bool _only_audio = false;
|
||||
std::string _auth_err;
|
||||
uint64_t _dts = 0;
|
||||
uint64_t _total_bytes = 0;
|
||||
std::unique_ptr<sockaddr_storage> _addr;
|
||||
|
@ -173,15 +173,8 @@ void RtpSender::createRtcpSocket() {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_storage addr;
|
||||
//目标rtp端口
|
||||
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;
|
||||
}
|
||||
// 绑定目标rtcp端口(目标rtp端口 + 1)
|
||||
auto addr = SockUtil::make_sockaddr(_socket_rtp->get_peer_ip().data(), _socket_rtp->get_peer_port() + 1);
|
||||
_socket_rtcp->bindPeerAddr((struct sockaddr *)&addr, 0, true);
|
||||
|
||||
_rtcp_context = std::make_shared<RtcpContextForSend>();
|
||||
|
@ -139,8 +139,15 @@ void RtpSession::onRtpPacket(const char *data, size_t len) {
|
||||
} else {
|
||||
throw;
|
||||
}
|
||||
} catch (...) {
|
||||
throw;
|
||||
} catch (std::exception &ex) {
|
||||
if (!_is_udp) {
|
||||
// tcp情况下立即断开连接
|
||||
throw;
|
||||
}
|
||||
// udp情况下延时断开连接(等待超时自动关闭),防止频繁创建销毁RtpSession对象
|
||||
WarnP(this) << ex.what();
|
||||
_delay_close = true;
|
||||
return;
|
||||
}
|
||||
_ticker.resetTime();
|
||||
}
|
||||
@ -164,8 +171,23 @@ static const char *findSSRC(const char *data, ssize_t len, uint32_t ssrc) {
|
||||
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
|
||||
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) {
|
||||
if (!_search_rtp) {
|
||||
@ -173,12 +195,26 @@ const char *RtpSession::onSearchPacketTail(const char *data, size_t len) {
|
||||
return RtpSplitter::onSearchPacketTail(data, len);
|
||||
}
|
||||
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
|
||||
auto ssrc_ptr0 = findSSRC(data, len, _ssrc);
|
||||
if (!ssrc_ptr0) {
|
||||
//未搜索到任意rtp,返回数据不够
|
||||
InfoL << "rtp搜索ssrc失败(第一个数据不够),丢弃rtp数据为:" << len;
|
||||
return nullptr;
|
||||
}
|
||||
//这两个字节是第一个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);
|
||||
if (!ssrc_ptr1) {
|
||||
//未搜索到第二个rtp,返回数据不够
|
||||
InfoL << "rtp搜索ssrc失败(第二个数据不够),丢弃rtp数据为:" << len;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//两个ssrc的间隔正好等于rtp的长度(外加rtp长度字段),那么说明找到rtp
|
||||
auto ssrc_offset = ssrc_ptr1 - ssrc_ptr0;
|
||||
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;
|
||||
if (rtp_len_ptr == data) {
|
||||
//停止搜索rtp,否则会进入死循环
|
||||
@ -208,5 +245,32 @@ const char *RtpSession::onSearchPacketTail(const char *data, size_t len) {
|
||||
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
|
||||
#endif//defined(ENABLE_RTPPROXY)
|
@ -41,6 +41,10 @@ protected:
|
||||
void onRtpPacket(const char *data, size_t len) override;
|
||||
// RtpSplitter 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:
|
||||
bool _delay_close = false;
|
||||
|
@ -14,7 +14,7 @@
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
#define ENABLE_NTP_STAMP 0
|
||||
#define ENABLE_NTP_STAMP 1
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
@ -78,6 +78,10 @@ bool RtspMuxer::addTrack(const Track::Ptr &track) {
|
||||
//添加其sdp
|
||||
_sdp.append(sdp->getSdp());
|
||||
trySyncTrack();
|
||||
|
||||
// rtp的时间戳是pts,允许回退
|
||||
_stamp[TrackAudio].enableRollback(true);
|
||||
_stamp[TrackVideo].enableRollback(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -358,7 +358,10 @@ void RtspPusher::updateRtcpContext(const RtpPacket::Ptr &rtp){
|
||||
auto &ticker = _rtcp_send_ticker[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);
|
||||
|
||||
if (!rtp->ntp_stamp && !rtp->getStamp()) {
|
||||
// 忽略时间戳都为0的rtp
|
||||
return;
|
||||
}
|
||||
//send rtcp every 5 second
|
||||
if (ticker.elapsedTime() > 5 * 1000) {
|
||||
ticker.resetTime();
|
||||
|
@ -1242,6 +1242,10 @@ void RtspSession::updateRtcpContext(const RtpPacket::Ptr &rtp){
|
||||
int track_index = getTrackIndexByTrackType(rtp->type);
|
||||
auto &rtcp_ctx = _rtcp_context[track_index];
|
||||
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];
|
||||
//send rtcp every 5 second
|
||||
|
@ -11,12 +11,9 @@
|
||||
#include <signal.h>
|
||||
#include <iostream>
|
||||
#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/Parser.h"
|
||||
#include "Poller/EventPoller.h"
|
||||
#include "Pusher/MediaPusher.h"
|
||||
#include "Record/MP4Reader.h"
|
||||
|
||||
@ -24,113 +21,101 @@ using namespace std;
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
//推流器,保持强引用
|
||||
MediaPusher::Ptr g_pusher;
|
||||
Timer::Ptr g_timer;
|
||||
MediaSource::Ptr g_src;
|
||||
|
||||
//声明函数
|
||||
//推流失败或断开延迟2秒后重试推流
|
||||
void rePushDelay(const EventPoller::Ptr &poller,
|
||||
const string &schema,
|
||||
const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
const string &filePath,
|
||||
const string &url);
|
||||
|
||||
//创建推流器并开始推流
|
||||
void createPusher(const EventPoller::Ptr &poller,
|
||||
const string &schema,
|
||||
const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
const string &filePath,
|
||||
const string &url) {
|
||||
if (!g_src) {
|
||||
//不限制APP名,并且指定文件绝对路径
|
||||
g_src = MediaSource::createFromMP4(schema, vhost, app, stream, filePath, false);
|
||||
}
|
||||
if (!g_src) {
|
||||
//文件不存在
|
||||
WarnL << "MP4文件不存在:" << filePath;
|
||||
return;
|
||||
}
|
||||
|
||||
//创建推流器并绑定一个MediaSource
|
||||
g_pusher.reset(new MediaPusher(g_src, poller));
|
||||
//可以指定rtsp推流方式,支持tcp和udp方式,默认tcp
|
||||
//(*g_pusher)[Client::kRtpType] = Rtsp::RTP_UDP;
|
||||
|
||||
//设置推流中断处理逻辑
|
||||
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;
|
||||
}
|
||||
});
|
||||
g_pusher->publish(url);
|
||||
}
|
||||
|
||||
//推流失败或断开延迟2秒后重试推流
|
||||
void rePushDelay(const EventPoller::Ptr &poller,
|
||||
const string &schema,
|
||||
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) {
|
||||
//设置日志
|
||||
// 这里才是真正执行main函数,你可以把函数名(domain)改成main,然后就可以输入自定义url了
|
||||
int domain(const string &file, const string &url) {
|
||||
// 设置日志
|
||||
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;
|
||||
|
||||
// 关闭所有转协议
|
||||
mINI::Instance()[Protocol::kEnableMP4] = 0;
|
||||
mINI::Instance()[Protocol::kEnableFMP4] = 0;
|
||||
mINI::Instance()[Protocol::kEnableHls] = 0;
|
||||
mINI::Instance()[Protocol::kEnableHlsFmp4] = 0;
|
||||
mINI::Instance()[Protocol::kEnableTS] = 0;
|
||||
mINI::Instance()[Protocol::kEnableRtsp] = 0;
|
||||
mINI::Instance()[Protocol::kEnableRtmp] = 0;
|
||||
|
||||
// 根据url获取媒体协议类型,注意大小写
|
||||
auto schema = strToLower(findSubString(url.data(), nullptr, "://").substr(0, 4));
|
||||
|
||||
// 只开启推流协议对应的转协议
|
||||
mINI::Instance()["protocol.enable_" + schema] = 1;
|
||||
|
||||
// 从mp4文件加载生成MediaSource对象
|
||||
auto reader = std::make_shared<MP4Reader>(DEFAULT_VHOST, "live", "stream", file);
|
||||
// 开始加载mp4,ref_self设置为false,这样reader对象设置为nullptr就能注销了,file_repeat可以设置为空,这样文件读完了就停止推流了
|
||||
reader->startReadMP4(100, false, true);
|
||||
auto src = MediaSource::find(schema, DEFAULT_VHOST, "live", "stream", false);
|
||||
|
||||
if (!src) {
|
||||
// 文件不存在
|
||||
WarnL << "File not existed: " << file;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 选择一个poller线程绑定
|
||||
auto poller = EventPollerPool::Instance().getPoller();
|
||||
//vhost/app/stream可以随便自己填,现在不限制app应用名了
|
||||
createPusher(poller, findSubString(pushUrl.data(), nullptr, "://").substr(0, 4), DEFAULT_VHOST, "live", "stream", filePath, pushUrl);
|
||||
//设置退出信号处理函数
|
||||
// 创建推流器并绑定一个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;
|
||||
}
|
||||
|
||||
WarnL << "Publish fail:" << ex << ", republish after 2 seconds";
|
||||
// 如果发布失败,就重试
|
||||
poller->doDelayTask(2 * 1000, [weak_pusher, url]() {
|
||||
if (auto strong_push = weak_pusher.lock()) {
|
||||
strong_push->publish(url);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
pusher->publish(url);
|
||||
|
||||
// sleep(5);
|
||||
// reader 置空可以终止推流相关资源
|
||||
// reader = nullptr;
|
||||
|
||||
// 设置退出信号处理函数
|
||||
static semaphore sem;
|
||||
signal(SIGINT, [](int) { sem.post(); });// 设置退出信号
|
||||
signal(SIGINT, [](int) { sem.post(); }); // 设置退出信号
|
||||
sem.wait();
|
||||
g_pusher.reset();
|
||||
g_timer.reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
//可以使用test_server生成的mp4文件
|
||||
//文件使用绝对路径,推流url支持rtsp和rtmp
|
||||
return domain("/home/work/test2.mp4", "rtmp://127.0.0.1/live/rtsp_push");
|
||||
// 可以使用test_server生成的mp4文件
|
||||
// 文件使用绝对路径,推流url支持rtsp和rtmp
|
||||
// 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
145
tools/openapi/generates.py
Normal 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()
|
40
tools/openapi/options.json
Normal file
40
tools/openapi/options.json
Normal 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": ""
|
||||
}
|
||||
}
|
@ -258,7 +258,7 @@ uint64_t NackContext::reSendNack() {
|
||||
for (auto it = nack_rtp.begin(); it != nack_rtp.end();) {
|
||||
if (pid == -1) {
|
||||
pid = *it;
|
||||
vec.resize(FCI_NACK::kBitSize, false);
|
||||
vec.assign(FCI_NACK::kBitSize, false);
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ const char* sockTypeStr(Session* session) {
|
||||
|
||||
WebRtcTransport::WebRtcTransport(const EventPoller::Ptr &poller) {
|
||||
_poller = poller;
|
||||
_identifier = "zlm_" + to_string(++s_key);
|
||||
_identifier = "zlm" + to_string(++s_key);
|
||||
_packet_pool.setSize(64);
|
||||
}
|
||||
|
||||
|
BIN
www/swagger/favicon-16x16.png
Normal file
BIN
www/swagger/favicon-16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 665 B |
BIN
www/swagger/favicon-32x32.png
Normal file
BIN
www/swagger/favicon-32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 628 B |
16
www/swagger/index.css
Normal file
16
www/swagger/index.css
Normal 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
19
www/swagger/index.html
Normal 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>
|
79
www/swagger/oauth2-redirect.html
Normal file
79
www/swagger/oauth2-redirect.html
Normal 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
4233
www/swagger/openapi.json
Normal file
File diff suppressed because it is too large
Load Diff
20
www/swagger/swagger-initializer.js
Normal file
20
www/swagger/swagger-initializer.js
Normal 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>
|
||||
};
|
3
www/swagger/swagger-ui-bundle.js
Normal file
3
www/swagger/swagger-ui-bundle.js
Normal file
File diff suppressed because one or more lines are too long
1
www/swagger/swagger-ui-bundle.js.map
Normal file
1
www/swagger/swagger-ui-bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
3
www/swagger/swagger-ui-es-bundle-core.js
Normal file
3
www/swagger/swagger-ui-es-bundle-core.js
Normal file
File diff suppressed because one or more lines are too long
1
www/swagger/swagger-ui-es-bundle-core.js.map
Normal file
1
www/swagger/swagger-ui-es-bundle-core.js.map
Normal file
File diff suppressed because one or more lines are too long
3
www/swagger/swagger-ui-es-bundle.js
Normal file
3
www/swagger/swagger-ui-es-bundle.js
Normal file
File diff suppressed because one or more lines are too long
1
www/swagger/swagger-ui-es-bundle.js.map
Normal file
1
www/swagger/swagger-ui-es-bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
3
www/swagger/swagger-ui-standalone-preset.js
Normal file
3
www/swagger/swagger-ui-standalone-preset.js
Normal file
File diff suppressed because one or more lines are too long
1
www/swagger/swagger-ui-standalone-preset.js.map
Normal file
1
www/swagger/swagger-ui-standalone-preset.js.map
Normal file
File diff suppressed because one or more lines are too long
3
www/swagger/swagger-ui.css
Normal file
3
www/swagger/swagger-ui.css
Normal file
File diff suppressed because one or more lines are too long
1
www/swagger/swagger-ui.css.map
Normal file
1
www/swagger/swagger-ui.css.map
Normal file
File diff suppressed because one or more lines are too long
2
www/swagger/swagger-ui.js
Normal file
2
www/swagger/swagger-ui.js
Normal file
File diff suppressed because one or more lines are too long
1
www/swagger/swagger-ui.js.map
Normal file
1
www/swagger/swagger-ui.js.map
Normal file
File diff suppressed because one or more lines are too long
@ -155,44 +155,47 @@
|
||||
audioEnable:document.getElementById('audioEnable').checked,
|
||||
videoEnable:document.getElementById('videoEnable').checked,
|
||||
recvOnly:recvOnly,
|
||||
resolution:{w:w,h:h},
|
||||
resolution:{w,h},
|
||||
usedatachannel:document.getElementById('datachannel').checked,
|
||||
}
|
||||
);
|
||||
|
||||
player.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR,function(e)
|
||||
{// ICE 协商出错
|
||||
console.log('ICE 协商出错');
|
||||
{
|
||||
// ICE 协商出错
|
||||
console.log('ICE 协商出错');
|
||||
});
|
||||
|
||||
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)
|
||||
{// offer anwser 交换失败
|
||||
console.log('offer anwser 交换失败',e);
|
||||
stop();
|
||||
{
|
||||
// offer anwser 交换失败
|
||||
console.log('offer anwser 交换失败',e);
|
||||
stop();
|
||||
});
|
||||
|
||||
player.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM,function(s)
|
||||
{// 获取到了本地流
|
||||
|
||||
{
|
||||
// 获取到了本地流
|
||||
document.getElementById('selfVideo').srcObject=s;
|
||||
document.getElementById('selfVideo').muted = true;
|
||||
|
||||
//console.log('offer anwser 交换失败',e)
|
||||
//console.log('offer anwser 交换失败',e)
|
||||
});
|
||||
|
||||
player.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED,function(s)
|
||||
{// 获取本地流失败
|
||||
|
||||
{
|
||||
// 获取本地流失败
|
||||
console.log('获取本地流失败');
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@ -267,19 +270,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 || !json.data) {
|
||||
@ -308,6 +312,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getData(url) {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET'
|
||||
@ -316,11 +321,13 @@
|
||||
//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();
|
||||
}, 5000);
|
||||
|
Loading…
Reference in New Issue
Block a user