mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-26 12:37:09 +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_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,那么没必要克隆对象
|
||||||
|
@ -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;
|
||||||
|
18
dockerfile
18
dockerfile
@ -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 && \
|
rm -rf /var/lib/apt/lists/*
|
||||||
cd libsrtp-2.2.0 && ./configure --enable-openssl && make -j $(nproc) && make install && \
|
|
||||||
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 && \
|
||||||
|
@ -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": [
|
||||||
|
@ -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){
|
||||||
//开始推流事件
|
//开始推流事件
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
|
@ -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:
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
|
@ -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([](){
|
||||||
|
@ -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; }
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 下次不用再强制同步
|
_need_sync = false;
|
||||||
_sync_master = nullptr;
|
_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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -78,12 +78,15 @@ void HttpClient::sendRequest(const string &url) {
|
|||||||
printer.pop_back();
|
printer.pop_back();
|
||||||
_header.emplace("Cookie", printer);
|
_header.emplace("Cookie", printer);
|
||||||
}
|
}
|
||||||
|
if (isUsedProxy()) {
|
||||||
if (!alive() || host_changed) {
|
startConnect(_proxy_host, _proxy_port, _wait_header_ms / 1000.0f);
|
||||||
startConnect(host, port, _wait_header_ms / 1000.0f);
|
|
||||||
} else {
|
} else {
|
||||||
SockException ex;
|
if (!alive() || host_changed) {
|
||||||
onConnect_l(ex);
|
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);
|
onResponseCompleted_l(ex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_StrPrinter printer;
|
_StrPrinter printer;
|
||||||
printer << _method + " " << _path + " HTTP/1.1\r\n";
|
//不使用代理或者代理服务器已经连接成功
|
||||||
for (auto &pr : _header) {
|
if (_proxy_connected || !isUsedProxy()) {
|
||||||
printer << pr.first + ": ";
|
printer << _method + " " << _path + " HTTP/1.1\r\n";
|
||||||
printer << pr.second + "\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");
|
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 */
|
||||||
|
@ -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 */
|
||||||
|
@ -15,13 +15,30 @@ using namespace toolkit;
|
|||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
void HttpClientImp::onConnect(const SockException &ex) {
|
void HttpClientImp::onConnect(const SockException &ex) {
|
||||||
if (!isHttps()) {
|
if (isUsedProxy() && !isProxyConnected()) {
|
||||||
//https 302跳转 http时,需要关闭ssl
|
// 连接代理服务器
|
||||||
setDoNotUseSSL();
|
setDoNotUseSSL();
|
||||||
HttpClient::onConnect(ex);
|
HttpClient::onConnect(ex);
|
||||||
} else {
|
} 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 */
|
} /* namespace mediakit */
|
||||||
|
@ -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 */
|
||||||
|
@ -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");
|
||||||
|
@ -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()) {
|
||||||
|
@ -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;
|
||||||
|
@ -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");
|
||||||
|
@ -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()) {
|
||||||
|
@ -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());
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
|
@ -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>();
|
||||||
|
@ -139,8 +139,15 @@ void RtpSession::onRtpPacket(const char *data, size_t len) {
|
|||||||
} else {
|
} else {
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
} catch (...) {
|
} catch (std::exception &ex) {
|
||||||
throw;
|
if (!_is_udp) {
|
||||||
|
// tcp情况下立即断开连接
|
||||||
|
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)
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
|
||||||
|
|
||||||
//声明函数
|
|
||||||
//推流失败或断开延迟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) {
|
|
||||||
//设置日志
|
|
||||||
Logger::Instance().add(std::make_shared<ConsoleChannel>());
|
Logger::Instance().add(std::make_shared<ConsoleChannel>());
|
||||||
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
|
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();
|
auto poller = EventPollerPool::Instance().getPoller();
|
||||||
//vhost/app/stream可以随便自己填,现在不限制app应用名了
|
// 创建推流器并绑定一个MediaSource
|
||||||
createPusher(poller, findSubString(pushUrl.data(), nullptr, "://").substr(0, 4), DEFAULT_VHOST, "live", "stream", filePath, pushUrl);
|
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;
|
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
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();) {
|
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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
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 协商出错
|
{
|
||||||
console.log('ICE 协商出错');
|
// 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 交换失败
|
{
|
||||||
console.log('offer anwser 交换失败',e);
|
// offer anwser 交换失败
|
||||||
stop();
|
console.log('offer anwser 交换失败',e);
|
||||||
|
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);
|
||||||
|
Loading…
Reference in New Issue
Block a user