2022-07-15 20:01:00 +08:00
/*
2023-12-09 16:23:51 +08:00
* Copyright ( c ) 2016 - present The ZLMediaKit project authors . All Rights Reserved .
2019-06-11 09:25:54 +08:00
*
2023-12-09 16:23:51 +08:00
* This file is part of ZLMediaKit ( https : //github.com/ZLMediaKit/ZLMediaKit).
2019-06-11 09:25:54 +08:00
*
2023-12-09 16:23:51 +08:00
* Use of this source code is governed by MIT - like license that can be found in the
2020-04-04 20:30:09 +08:00
* LICENSE file in the root of the source tree . All contributing project authors
* may be found in the AUTHORS file in the root of the source tree .
2019-06-11 09:25:54 +08:00
*/
2024-06-19 14:06:02 +08:00
# include <exception>
2020-06-10 10:33:48 +08:00
# include <sys/stat.h>
2020-12-06 22:36:44 +08:00
# include <math.h>
2019-05-20 11:22:59 +08:00
# include <signal.h>
2023-11-24 10:44:08 +08:00
# ifdef _WIN32
# include <io.h>
# include <iostream>
# include <tchar.h>
# endif // _WIN32
2019-05-20 11:22:59 +08:00
# include <functional>
# include <unordered_map>
2024-01-26 10:26:30 +08:00
# include <regex>
2023-11-24 10:44:08 +08:00
# include "Util/MD5.h"
2019-05-20 11:22:59 +08:00
# include "Util/util.h"
2023-11-24 10:44:08 +08:00
# include "Util/File.h"
2019-05-20 11:22:59 +08:00
# include "Util/logger.h"
# include "Util/onceToken.h"
# include "Util/NoticeCenter.h"
2023-11-24 10:44:08 +08:00
# include "Network/TcpServer.h"
# include "Network/UdpServer.h"
# include "Thread/WorkThreadPool.h"
2019-05-21 09:26:24 +08:00
# ifdef ENABLE_MYSQL
2019-05-20 11:22:59 +08:00
# include "Util/SqlPool.h"
2019-05-21 09:26:24 +08:00
# endif //ENABLE_MYSQL
2023-11-24 10:44:08 +08:00
# include "WebApi.h"
# include "WebHook.h"
# include "FFmpegSource.h"
2019-05-20 11:22:59 +08:00
# include "Common/config.h"
# include "Common/MediaSource.h"
# include "Http/HttpSession.h"
2023-11-24 10:44:08 +08:00
# include "Http/HttpRequester.h"
2019-05-20 16:26:04 +08:00
# include "Player/PlayerProxy.h"
2021-06-16 19:40:08 +08:00
# include "Pusher/PusherProxy.h"
2024-06-09 10:52:10 +08:00
# include "Rtp/RtpProcess.h"
2023-11-24 10:44:08 +08:00
# include "Record/MP4Reader.h"
2020-07-02 22:23:43 +08:00
# if defined(ENABLE_RTPPROXY)
# include "Rtp/RtpServer.h"
# endif
2023-11-24 10:44:08 +08:00
2021-03-24 16:52:41 +08:00
# ifdef ENABLE_WEBRTC
2021-10-15 16:27:17 +08:00
# include "../webrtc/WebRtcPlayer.h"
# include "../webrtc/WebRtcPusher.h"
2021-10-16 10:52:28 +08:00
# include "../webrtc/WebRtcEchoTest.h"
2025-09-20 16:23:30 +08:00
# include "../webrtc/WebRtcSignalingPeer.h"
# include "../webrtc/WebRtcSignalingSession.h"
# include "../webrtc/WebRtcProxyPlayer.h"
# include "../webrtc/WebRtcProxyPlayerImp.h"
2021-03-24 16:52:41 +08:00
# endif
2021-12-19 17:39:50 +08:00
2022-08-12 18:09:44 +08:00
# if defined(ENABLE_VERSION)
2024-03-24 17:18:18 +08:00
# include "ZLMVersion.h"
2022-08-12 18:09:44 +08:00
# endif
2024-04-13 20:35:59 +08:00
# if defined(ENABLE_VIDEOSTACK) && defined(ENABLE_X264) && defined (ENABLE_FFMPEG)
2024-03-16 22:56:32 +08:00
# include "VideoStack.h"
# endif
2025-11-17 12:58:45 +08:00
# include "Onvif/Onvif.h"
# include "Onvif/SoapUtil.h"
2022-02-02 20:34:50 +08:00
using namespace std ;
using namespace Json ;
2019-05-20 11:22:59 +08:00
using namespace toolkit ;
using namespace mediakit ;
namespace API {
# define API_FIELD "api."
2019-06-24 14:51:49 +08:00
const string kApiDebug = API_FIELD " apiDebug " ;
const string kSecret = API_FIELD " secret " ;
2020-05-09 00:06:36 +08:00
const string kSnapRoot = API_FIELD " snapRoot " ;
2020-06-10 10:33:48 +08:00
const string kDefaultSnap = API_FIELD " defaultSnap " ;
2023-12-02 15:58:40 +08:00
const string kDownloadRoot = API_FIELD " downloadRoot " ;
2019-05-20 17:12:00 +08:00
2019-05-20 11:22:59 +08:00
static onceToken token ( [ ] ( ) {
2019-05-20 16:26:04 +08:00
mINI : : Instance ( ) [ kApiDebug ] = " 1 " ;
2019-05-20 17:12:00 +08:00
mINI : : Instance ( ) [ kSecret ] = " 035c73f7-bb6b-4889-a715-d9eb2d1925cc " ;
2020-05-09 00:06:36 +08:00
mINI : : Instance ( ) [ kSnapRoot ] = " ./www/snap/ " ;
2020-06-10 10:33:48 +08:00
mINI : : Instance ( ) [ kDefaultSnap ] = " ./www/logo.png " ;
2023-12-02 15:58:40 +08:00
mINI : : Instance ( ) [ kDownloadRoot ] = " ./www " ;
2019-05-20 11:22:59 +08:00
} ) ;
} //namespace API
2020-12-27 22:14:59 +08:00
using HttpApi = function < void ( const Parser & parser , const HttpSession : : HttpResponseInvoker & invoker , SockInfo & sender ) > ;
2024-09-19 14:53:50 +08:00
// http api列表 [AUTO-TRANSLATED:a05e9d9d]
// http api list
2023-12-02 14:56:59 +08:00
static map < string , HttpApi , StrCaseCompare > s_map_api ;
2020-01-17 11:44:20 +08:00
2026-02-19 22:56:23 +08:00
static void responseApi ( int code , const string & msg , const HttpSession : : HttpResponseInvoker & invoker , ApiRetException * ex = nullptr ) {
2020-12-06 21:50:41 +08:00
Json : : Value res ;
2026-02-19 22:56:23 +08:00
HttpSession : : KeyValue headerOut ;
if ( ex ) {
res = ex - > getBody ( ) ;
headerOut = ex - > getHeaders ( ) ;
}
2020-12-06 21:50:41 +08:00
res [ " code " ] = code ;
res [ " msg " ] = msg ;
2026-02-19 22:56:23 +08:00
GET_CONFIG ( string , charSet , Http : : kCharSet ) ;
headerOut [ " Content-Type " ] = string ( " application/json; charset= " ) + charSet ;
invoker ( 200 , headerOut , res . toStyledString ( ) ) ;
2020-12-06 21:50:41 +08:00
}
2020-12-27 22:14:59 +08:00
static HttpApi toApi ( const function < void ( API_ARGS_MAP_ASYNC ) > & cb ) {
2020-12-06 21:50:41 +08:00
return [ cb ] ( const Parser & parser , const HttpSession : : HttpResponseInvoker & invoker , SockInfo & sender ) {
GET_CONFIG ( string , charSet , Http : : kCharSet ) ;
HttpSession : : KeyValue headerOut ;
headerOut [ " Content-Type " ] = string ( " application/json; charset= " ) + charSet ;
Json : : Value val ;
val [ " code " ] = API : : Success ;
2024-09-19 14:53:50 +08:00
// 参数解析成map [AUTO-TRANSLATED:20e11ff3]
// Parse parameters into a map
2020-12-06 21:50:41 +08:00
auto args = getAllArgs ( parser ) ;
2024-03-23 11:46:30 -03:00
cb ( sender , headerOut , ArgsMap ( parser , args ) , val , invoker ) ;
2020-12-06 21:50:41 +08:00
} ;
}
2020-12-27 22:14:59 +08:00
static HttpApi toApi ( const function < void ( API_ARGS_MAP ) > & cb ) {
return toApi ( [ cb ] ( API_ARGS_MAP_ASYNC ) {
cb ( API_ARGS_VALUE ) ;
2021-01-02 21:24:06 +08:00
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
2020-01-17 11:44:20 +08:00
} ) ;
}
2020-12-27 22:14:59 +08:00
static HttpApi toApi ( const function < void ( API_ARGS_JSON_ASYNC ) > & cb ) {
return [ cb ] ( const Parser & parser , const HttpSession : : HttpResponseInvoker & invoker , SockInfo & sender ) {
GET_CONFIG ( string , charSet , Http : : kCharSet ) ;
HttpSession : : KeyValue headerOut ;
headerOut [ " Content-Type " ] = string ( " application/json; charset= " ) + charSet ;
2021-08-12 16:07:31 +08:00
Json : : Value val ;
val [ " code " ] = API : : Success ;
2020-12-27 22:14:59 +08:00
if ( parser [ " Content-Type " ] . find ( " application/json " ) = = string : : npos ) {
throw InvalidArgsException ( " 该接口只支持json格式的请求 " ) ;
}
2024-09-19 14:53:50 +08:00
// 参数解析成json对象然后处理 [AUTO-TRANSLATED:6f23397b]
// Parse parameters into a JSON object and then process
2021-08-12 16:07:31 +08:00
Json : : Value args ;
2020-12-27 22:14:59 +08:00
Json : : Reader reader ;
2023-06-10 11:04:52 +08:00
reader . parse ( parser . content ( ) , args ) ;
2020-12-27 22:14:59 +08:00
2024-03-23 11:46:30 -03:00
cb ( sender , headerOut , ArgsJson ( parser , args ) , val , invoker ) ;
2020-12-27 22:14:59 +08:00
} ;
}
static HttpApi toApi ( const function < void ( API_ARGS_JSON ) > & cb ) {
return toApi ( [ cb ] ( API_ARGS_JSON_ASYNC ) {
2021-08-12 16:07:31 +08:00
cb ( API_ARGS_VALUE ) ;
2021-01-02 21:24:06 +08:00
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
2020-12-27 22:14:59 +08:00
} ) ;
}
2021-03-31 17:15:26 +08:00
static HttpApi toApi ( const function < void ( API_ARGS_STRING_ASYNC ) > & cb ) {
return [ cb ] ( const Parser & parser , const HttpSession : : HttpResponseInvoker & invoker , SockInfo & sender ) {
GET_CONFIG ( string , charSet , Http : : kCharSet ) ;
HttpSession : : KeyValue headerOut ;
headerOut [ " Content-Type " ] = string ( " application/json; charset= " ) + charSet ;
Json : : Value val ;
val [ " code " ] = API : : Success ;
2024-03-23 11:46:30 -03:00
cb ( sender , headerOut , ArgsString ( parser , ( string & ) parser . content ( ) ) , val , invoker ) ;
2021-03-31 17:15:26 +08:00
} ;
}
static HttpApi toApi ( const function < void ( API_ARGS_STRING ) > & cb ) {
return toApi ( [ cb ] ( API_ARGS_STRING_ASYNC ) {
cb ( API_ARGS_VALUE ) ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
}
2020-12-27 22:14:59 +08:00
void api_regist ( const string & api_path , const function < void ( API_ARGS_MAP ) > & func ) {
s_map_api . emplace ( api_path , toApi ( func ) ) ;
}
void api_regist ( const string & api_path , const function < void ( API_ARGS_MAP_ASYNC ) > & func ) {
s_map_api . emplace ( api_path , toApi ( func ) ) ;
}
void api_regist ( const string & api_path , const function < void ( API_ARGS_JSON ) > & func ) {
s_map_api . emplace ( api_path , toApi ( func ) ) ;
2020-01-17 11:44:20 +08:00
}
2019-05-20 11:22:59 +08:00
2020-12-27 22:14:59 +08:00
void api_regist ( const string & api_path , const function < void ( API_ARGS_JSON_ASYNC ) > & func ) {
s_map_api . emplace ( api_path , toApi ( func ) ) ;
}
2020-12-06 21:50:41 +08:00
2021-03-31 17:15:26 +08:00
void api_regist ( const string & api_path , const function < void ( API_ARGS_STRING ) > & func ) {
s_map_api . emplace ( api_path , toApi ( func ) ) ;
}
void api_regist ( const string & api_path , const function < void ( API_ARGS_STRING_ASYNC ) > & func ) {
s_map_api . emplace ( api_path , toApi ( func ) ) ;
}
2024-09-19 14:53:50 +08:00
// 获取HTTP请求中url参数、content参数 [AUTO-TRANSLATED:d161a1e1]
// Get URL parameters and content parameters from the HTTP request
2026-02-10 13:28:42 +08:00
ApiArgsType getAllArgs ( const Parser & parser ) {
2019-05-20 16:26:04 +08:00
ApiArgsType allArgs ;
2019-12-29 10:49:04 +08:00
if ( parser [ " Content-Type " ] . find ( " application/x-www-form-urlencoded " ) = = 0 ) {
2023-06-10 11:04:52 +08:00
auto contentArgs = parser . parseArgs ( parser . content ( ) ) ;
2019-05-20 11:22:59 +08:00
for ( auto & pr : contentArgs ) {
2024-04-20 22:25:21 +08:00
allArgs [ pr . first ] = strCoding : : UrlDecodeComponent ( pr . second ) ;
2019-05-20 11:22:59 +08:00
}
2019-12-29 10:49:04 +08:00
} else if ( parser [ " Content-Type " ] . find ( " application/json " ) = = 0 ) {
2026-02-25 12:21:43 +08:00
if ( ! parser . content ( ) . empty ( ) ) {
try {
stringstream ss ( parser . content ( ) ) ;
Value jsonArgs ;
ss > > jsonArgs ;
auto keys = jsonArgs . getMemberNames ( ) ;
for ( auto key = keys . begin ( ) ; key ! = keys . end ( ) ; + + key ) {
allArgs [ * key ] = jsonArgs [ * key ] . asString ( ) ;
}
} catch ( std : : exception & ex ) {
WarnL < < ex . what ( ) ;
2019-05-20 16:26:04 +08:00
}
2019-05-20 11:22:59 +08:00
}
2019-12-29 10:49:04 +08:00
} else if ( ! parser [ " Content-Type " ] . empty ( ) ) {
2019-05-20 16:26:04 +08:00
WarnL < < " invalid Content-Type: " < < parser [ " Content-Type " ] ;
2019-05-20 11:22:59 +08:00
}
2019-05-20 16:26:04 +08:00
2019-12-29 10:49:04 +08:00
for ( auto & pr : parser . getUrlArgs ( ) ) {
allArgs [ pr . first ] = pr . second ;
2019-05-20 16:26:04 +08:00
}
2020-09-21 14:32:56 +08:00
return allArgs ;
2019-05-20 11:22:59 +08:00
}
2021-02-21 21:28:17 +08:00
extern uint64_t getTotalMemUsage ( ) ;
2021-12-29 20:48:15 +08:00
extern uint64_t getTotalMemBlock ( ) ;
2021-12-27 17:40:15 +08:00
extern uint64_t getThisThreadMemUsage ( ) ;
2021-12-29 20:48:15 +08:00
extern uint64_t getThisThreadMemBlock ( ) ;
extern std : : vector < size_t > getBlockTypeSize ( ) ;
extern uint64_t getTotalMemBlockByType ( int type ) ;
extern uint64_t getThisThreadMemBlockByType ( int type ) ;
2021-02-21 21:28:17 +08:00
2022-07-29 16:23:35 +08:00
static void * web_api_tag = nullptr ;
2019-05-20 11:22:59 +08:00
static inline void addHttpListener ( ) {
2019-05-28 17:14:36 +08:00
GET_CONFIG ( bool , api_debug , API : : kApiDebug ) ;
2024-09-19 14:53:50 +08:00
// 注册监听kBroadcastHttpRequest事件 [AUTO-TRANSLATED:4af22c90]
// Register to listen for the kBroadcastHttpRequest event
2022-07-29 16:23:35 +08:00
NoticeCenter : : Instance ( ) . addListener ( & web_api_tag , Broadcast : : kBroadcastHttpRequest , [ ] ( BroadcastHttpRequestArgs ) {
2023-06-10 11:04:52 +08:00
auto it = s_map_api . find ( parser . url ( ) ) ;
2019-05-20 11:22:59 +08:00
if ( it = = s_map_api . end ( ) ) {
return ;
}
2024-09-19 14:53:50 +08:00
// 该api已被消费 [AUTO-TRANSLATED:db0872fc]
// This API has been consumed
2019-05-20 11:22:59 +08:00
consumed = true ;
2020-12-06 21:50:41 +08:00
2019-05-20 17:34:39 +08:00
if ( api_debug ) {
2021-08-12 21:02:07 +08:00
auto newInvoker = [ invoker , parser ] ( int code , const HttpSession : : KeyValue & headerOut , const HttpBody : : Ptr & body ) {
2024-09-19 14:53:50 +08:00
// body默认为空 [AUTO-TRANSLATED:4fd4ecc8]
// The body is empty by default
2021-01-19 16:05:38 +08:00
ssize_t size = 0 ;
2019-10-28 17:23:16 +08:00
if ( body & & body - > remainSize ( ) ) {
2024-09-19 14:53:50 +08:00
// 有body, 获取body大小 [AUTO-TRANSLATED:ab1c417d]
// If there is a body, get the body size
2019-10-28 17:23:16 +08:00
size = body - > remainSize ( ) ;
}
2019-05-20 17:34:39 +08:00
2023-07-02 12:45:07 +08:00
LogContextCapture log ( getLogger ( ) , toolkit : : LDebug , __FILE__ , " http api debug " , __LINE__ ) ;
2023-06-10 11:04:52 +08:00
log < < " \r \n # request: \r \n " < < parser . method ( ) < < " " < < parser . fullUrl ( ) < < " \r \n " ;
2021-08-12 21:02:07 +08:00
log < < " # header: \r \n " ;
for ( auto & pr : parser . getHeader ( ) ) {
log < < pr . first < < " : " < < pr . second < < " \r \n " ;
}
2023-06-10 11:04:52 +08:00
auto & content = parser . content ( ) ;
2021-08-12 21:02:07 +08:00
log < < " # content: \r \n " < < ( content . size ( ) > 4 * 1024 ? content . substr ( 0 , 4 * 1024 ) : content ) < < " \r \n " ;
2021-08-23 11:00:20 +08:00
if ( size > 0 & & size < 4 * 1024 ) {
2021-08-12 21:02:07 +08:00
auto response = body - > readData ( size ) ;
log < < " # response: \r \n " < < response - > data ( ) < < " \r \n " ;
invoker ( code , headerOut , response ) ;
2020-12-06 21:50:41 +08:00
} else {
2021-08-12 21:02:07 +08:00
log < < " # response size: " < < size < < " \r \n " ;
2021-01-02 21:24:06 +08:00
invoker ( code , headerOut , body ) ;
2019-10-28 17:23:16 +08:00
}
2019-05-20 17:34:39 +08:00
} ;
2020-12-06 21:50:41 +08:00
( ( HttpSession : : HttpResponseInvoker & ) invoker ) = newInvoker ;
2019-05-20 17:34:39 +08:00
}
2025-02-26 11:32:10 +08:00
auto helper = static_cast < SocketHelper & > ( sender ) . shared_from_this ( ) ;
// 在本poller线程下一次事件循环时执行http api, 防止占用NoticeCenter的锁
helper - > getPoller ( ) - > async ( [ it , parser , invoker , helper ] ( ) {
try {
it - > second ( parser , invoker , * helper ) ;
} catch ( ApiRetException & ex ) {
2026-02-19 22:56:23 +08:00
responseApi ( ex . code ( ) , ex . what ( ) , invoker , & ex ) ;
2025-02-26 11:32:10 +08:00
helper - > getPoller ( ) - > async ( [ helper , ex ] ( ) { helper - > shutdown ( SockException ( Err_shutdown , ex . what ( ) ) ) ; } , false ) ;
}
2019-05-21 09:26:24 +08:00
# ifdef ENABLE_MYSQL
2025-02-26 11:32:10 +08:00
catch ( SqlException & ex ) {
2026-02-19 22:56:23 +08:00
responseApi ( API : : SqlFailed , StrPrinter < < " 操作数据库失败: " < < ex . what ( ) < < " : " < < ex . getSql ( ) , invoker , & ex ) ;
2025-02-26 11:32:10 +08:00
}
# endif // ENABLE_MYSQL
catch ( std : : exception & ex ) {
responseApi ( API : : Exception , ex . what ( ) , invoker ) ;
}
} , false ) ;
2019-05-20 11:22:59 +08:00
} ) ;
}
2024-09-19 14:53:50 +08:00
// 拉流代理器列表 [AUTO-TRANSLATED:6dcfb11f]
// Pull stream proxy list
2024-03-10 05:31:20 -03:00
static ServiceController < PlayerProxy > s_player_proxy ;
2019-05-20 17:03:04 +08:00
2024-09-19 14:53:50 +08:00
// 推流代理器列表 [AUTO-TRANSLATED:539a1bcf]
// Push stream proxy list
2024-03-10 05:31:20 -03:00
static ServiceController < PusherProxy > s_pusher_proxy ;
2021-06-16 19:40:08 +08:00
2024-09-19 14:53:50 +08:00
// FFmpeg拉流代理器列表 [AUTO-TRANSLATED:4bdedf10]
// FFmpeg pull stream proxy list
2024-03-10 05:31:20 -03:00
static ServiceController < FFmpegSource > s_ffmpeg_src ;
2019-06-06 18:28:33 +08:00
2020-07-02 22:23:43 +08:00
# if defined(ENABLE_RTPPROXY)
2024-09-19 14:53:50 +08:00
// rtp服务器列表 [AUTO-TRANSLATED:2e362a8c]
// RTP server list
2024-03-10 05:31:20 -03:00
static ServiceController < RtpServer > s_rtp_server ;
2020-07-02 22:23:43 +08:00
# endif
2021-06-17 10:41:26 +08:00
static inline string getPusherKey ( const string & schema , const string & vhost , const string & app , const string & stream ,
const string & dst_url ) {
return schema + " / " + vhost + " / " + app + " / " + stream + " / " + MD5 ( dst_url ) . hexdigest ( ) ;
}
2026-02-10 13:28:42 +08:00
void fillSockInfo ( Value & val , SockInfo * info ) {
2022-09-07 11:06:39 +08:00
val [ " peer_ip " ] = info - > get_peer_ip ( ) ;
val [ " peer_port " ] = info - > get_peer_port ( ) ;
val [ " local_port " ] = info - > get_local_port ( ) ;
val [ " local_ip " ] = info - > get_local_ip ( ) ;
val [ " identifier " ] = info - > getIdentifier ( ) ;
}
2023-06-17 10:28:01 +08:00
void dumpMediaTuple ( const MediaTuple & tuple , Json : : Value & item ) {
item [ VHOST_KEY ] = tuple . vhost ;
item [ " app " ] = tuple . app ;
item [ " stream " ] = tuple . stream ;
2024-03-30 14:46:39 +08:00
item [ " params " ] = tuple . params ;
2023-06-17 10:28:01 +08:00
}
2024-11-01 10:47:18 +08:00
Value ToJson ( const PusherProxy : : Ptr & p ) {
Value item ;
item [ " url " ] = p - > getUrl ( ) ;
item [ " status " ] = p - > getStatus ( ) ;
item [ " liveSecs " ] = p - > getLiveSecs ( ) ;
2026-02-19 22:56:23 +08:00
item [ " rePublishCount " ] = p - > getRePublishCount ( ) ;
2025-05-02 16:23:25 +08:00
item [ " bytesSpeed " ] = ( Json : : UInt64 ) p - > getSendSpeed ( ) ;
item [ " totalBytes " ] = ( Json : : UInt64 ) p - > getSendTotalBytes ( ) ;
2024-11-01 10:47:18 +08:00
if ( auto src = p - > getSrc ( ) ) {
dumpMediaTuple ( src - > getMediaTuple ( ) , item [ " src " ] ) ;
}
return item ;
}
2026-03-13 15:13:18 +08:00
Json : : Value dumpTracks ( const std : : vector < Track : : Ptr > & tracks ) {
Json : : Value ret ( arrayValue ) ;
for ( auto & track : tracks ) {
Value obj ;
auto codec_type = track - > getTrackType ( ) ;
obj [ " codec_id " ] = track - > getCodecId ( ) ;
obj [ " codec_id_name " ] = track - > getCodecName ( ) ;
obj [ " ready " ] = track - > ready ( ) ;
obj [ " codec_type " ] = codec_type ;
obj [ " frames " ] = track - > getFrames ( ) ;
obj [ " duration " ] = track - > getDuration ( ) ;
switch ( codec_type ) {
case TrackAudio : {
auto audio_track = dynamic_pointer_cast < AudioTrack > ( track ) ;
obj [ " sample_rate " ] = audio_track - > getAudioSampleRate ( ) ;
obj [ " channels " ] = audio_track - > getAudioChannel ( ) ;
obj [ " sample_bit " ] = audio_track - > getAudioSampleBit ( ) ;
break ;
}
case TrackVideo : {
auto video_track = dynamic_pointer_cast < VideoTrack > ( track ) ;
obj [ " width " ] = video_track - > getVideoWidth ( ) ;
obj [ " height " ] = video_track - > getVideoHeight ( ) ;
obj [ " key_frames " ] = video_track - > getVideoKeyFrames ( ) ;
int gop_size = video_track - > getVideoGopSize ( ) ;
int gop_interval_ms = video_track - > getVideoGopInterval ( ) ;
float fps = video_track - > getVideoFps ( ) ;
if ( fps < = 1 & & gop_interval_ms ) {
fps = gop_size * 1000.0 / gop_interval_ms ;
}
obj [ " fps " ] = round ( fps ) ;
obj [ " gop_size " ] = gop_size ;
obj [ " gop_interval_ms " ] = gop_interval_ms ;
break ;
}
default : break ;
}
ret . append ( obj ) ;
}
return ret ;
}
2024-11-01 10:47:18 +08:00
Value ToJson ( const PlayerProxy : : Ptr & p ) {
Value item ;
item [ " url " ] = p - > getUrl ( ) ;
item [ " status " ] = p - > getStatus ( ) ;
2026-03-13 15:13:18 +08:00
item [ " status_str " ] = p - > getStatusStr ( ) ;
2024-11-01 10:47:18 +08:00
item [ " liveSecs " ] = p - > getLiveSecs ( ) ;
item [ " rePullCount " ] = p - > getRePullCount ( ) ;
item [ " totalReaderCount " ] = p - > totalReaderCount ( ) ;
2025-05-02 16:23:25 +08:00
item [ " bytesSpeed " ] = ( Json : : UInt64 ) p - > getRecvSpeed ( ) ;
item [ " totalBytes " ] = ( Json : : UInt64 ) p - > getRecvTotalBytes ( ) ;
2024-11-01 10:47:18 +08:00
dumpMediaTuple ( p - > getMediaTuple ( ) , item [ " src " ] ) ;
2026-03-13 15:13:18 +08:00
item [ " tracks " ] = dumpTracks ( p - > getTracks ( false ) ) ;
2024-11-01 10:47:18 +08:00
return item ;
}
2026-03-13 15:13:18 +08:00
Value makeMediaSourceJson ( MediaSource & media ) {
2021-04-08 17:34:13 +08:00
Value item ;
item [ " schema " ] = media . getSchema ( ) ;
2023-06-17 10:28:01 +08:00
dumpMediaTuple ( media . getMediaTuple ( ) , item ) ;
2021-04-08 17:34:13 +08:00
item [ " createStamp " ] = ( Json : : UInt64 ) media . getCreateStamp ( ) ;
2025-10-18 13:00:54 +08:00
item [ " currentStamp " ] = ( Json : : UInt64 ) media . getTimeStamp ( TrackInvalid ) ;
2021-04-08 17:34:13 +08:00
item [ " aliveSecond " ] = ( Json : : UInt64 ) media . getAliveSecond ( ) ;
2025-05-02 16:23:25 +08:00
item [ " bytesSpeed " ] = ( Json : : UInt64 ) media . getBytesSpeed ( ) ;
item [ " totalBytes " ] = ( Json : : UInt64 ) media . getTotalBytes ( ) ;
2021-04-08 17:34:13 +08:00
item [ " readerCount " ] = media . readerCount ( ) ;
item [ " totalReaderCount " ] = media . totalReaderCount ( ) ;
item [ " originType " ] = ( int ) media . getOriginType ( ) ;
item [ " originTypeStr " ] = getOriginTypeString ( media . getOriginType ( ) ) ;
item [ " originUrl " ] = media . getOriginUrl ( ) ;
2021-05-22 09:31:59 +08:00
item [ " isRecordingMP4 " ] = media . isRecording ( Recorder : : type_mp4 ) ;
item [ " isRecordingHLS " ] = media . isRecording ( Recorder : : type_hls ) ;
2021-04-08 17:34:13 +08:00
auto originSock = media . getOriginSock ( ) ;
if ( originSock ) {
2022-09-07 11:06:39 +08:00
fillSockInfo ( item [ " originSock " ] , originSock . get ( ) ) ;
2021-04-08 17:34:13 +08:00
} else {
item [ " originSock " ] = Json : : nullValue ;
}
2024-09-19 14:53:50 +08:00
// getLossRate有线程安全问题; 使用getMediaInfo接口才能获取丢包率; getMediaList接口将忽略丢包率 [AUTO-TRANSLATED:b2e927c6]
// getLossRate has thread safety issues; use the getMediaInfo interface to get the packet loss rate; the getMediaList interface will ignore the packet loss rate
2022-11-26 10:14:37 +08:00
auto current_thread = false ;
try { current_thread = media . getOwnerPoller ( ) - > isCurrentThread ( ) ; } catch ( . . . ) { }
2022-09-03 16:47:37 +08:00
float last_loss = - 1 ;
2026-03-13 15:13:18 +08:00
auto tracks = dumpTracks ( media . getTracks ( false ) ) ;
if ( current_thread ) {
for ( auto & obj : tracks ) {
2024-09-19 14:53:50 +08:00
// rtp推流只有一个统计器, 但是可能有多个track, 如果短时间多次获取间隔丢包率, 第二次会获取为-1 [AUTO-TRANSLATED:5bfbc951]
2026-03-13 15:13:18 +08:00
// RTP push stream has only one statistics, but may have multiple tracks. If you get the interval packet loss rate multiple times in a short time,
// the second time will get -1
auto loss = media . getLossRate ( getTrackType ( static_cast < CodecId > ( obj [ " codec_type " ] . asInt ( ) ) ) ) ;
2022-09-03 16:47:37 +08:00
if ( loss = = - 1 ) {
loss = last_loss ;
} else {
last_loss = loss ;
}
obj [ " loss " ] = loss ;
2022-06-11 14:45:56 +08:00
}
2021-04-08 17:34:13 +08:00
}
2026-03-13 15:13:18 +08:00
item [ " tracks " ] = std : : move ( tracks ) ;
2021-04-08 17:34:13 +08:00
return item ;
}
2022-07-24 22:30:59 +08:00
# if defined(ENABLE_RTPPROXY)
2024-07-09 10:42:10 +08:00
uint16_t openRtpServer ( uint16_t local_port , const mediakit : : MediaTuple & tuple , int tcp_mode , const string & local_ip , bool re_use_port , uint32_t ssrc , int only_track , bool multiplex ) {
auto key = tuple . shortUrl ( ) ;
if ( s_rtp_server . find ( key ) ) {
2024-09-19 14:53:50 +08:00
// 为了防止RtpProcess所有权限混乱的问题, 不允许重复添加相同的key [AUTO-TRANSLATED:06c7b14c]
// To prevent the problem of all permissions being messed up in RtpProcess, duplicate keys are not allowed to be added
2022-06-22 10:31:53 +08:00
return 0 ;
}
2024-07-09 10:42:10 +08:00
auto server = s_rtp_server . makeWithAction ( key , [ & ] ( RtpServer : : Ptr server ) {
2024-08-01 11:03:26 +08:00
server - > start ( local_port , local_ip . c_str ( ) , tuple , ( RtpServer : : TcpMode ) tcp_mode , re_use_port , ssrc , only_track , multiplex ) ;
2024-03-10 05:31:20 -03:00
} ) ;
2024-07-09 10:42:10 +08:00
server - > setOnDetach ( [ key ] ( const SockException & ex ) {
2024-09-19 14:53:50 +08:00
// 设置rtp超时移除事件 [AUTO-TRANSLATED:98d42cf3]
// Set RTP timeout removal event
2024-07-09 10:42:10 +08:00
s_rtp_server . erase ( key ) ;
2022-10-30 21:36:35 +08:00
} ) ;
2022-06-22 10:31:53 +08:00
2024-09-19 14:53:50 +08:00
// 回复json [AUTO-TRANSLATED:0c443c6a]
// Reply JSON
2022-06-22 10:31:53 +08:00
return server - > getPort ( ) ;
}
2022-07-24 22:30:59 +08:00
# endif
2022-06-22 10:31:53 +08:00
2021-12-27 17:40:15 +08:00
void getStatisticJson ( const function < void ( Value & val ) > & cb ) {
auto obj = std : : make_shared < Value > ( objectValue ) ;
auto & val = * obj ;
2021-08-21 19:11:20 +08:00
val [ " MediaSource " ] = ( Json : : UInt64 ) ( ObjectStatistic < MediaSource > : : count ( ) ) ;
val [ " MultiMediaSourceMuxer " ] = ( Json : : UInt64 ) ( ObjectStatistic < MultiMediaSourceMuxer > : : count ( ) ) ;
val [ " TcpServer " ] = ( Json : : UInt64 ) ( ObjectStatistic < TcpServer > : : count ( ) ) ;
val [ " TcpSession " ] = ( Json : : UInt64 ) ( ObjectStatistic < TcpSession > : : count ( ) ) ;
val [ " UdpServer " ] = ( Json : : UInt64 ) ( ObjectStatistic < UdpServer > : : count ( ) ) ;
val [ " UdpSession " ] = ( Json : : UInt64 ) ( ObjectStatistic < UdpSession > : : count ( ) ) ;
val [ " TcpClient " ] = ( Json : : UInt64 ) ( ObjectStatistic < TcpClient > : : count ( ) ) ;
val [ " Socket " ] = ( Json : : UInt64 ) ( ObjectStatistic < Socket > : : count ( ) ) ;
val [ " FrameImp " ] = ( Json : : UInt64 ) ( ObjectStatistic < FrameImp > : : count ( ) ) ;
val [ " Frame " ] = ( Json : : UInt64 ) ( ObjectStatistic < Frame > : : count ( ) ) ;
val [ " Buffer " ] = ( Json : : UInt64 ) ( ObjectStatistic < Buffer > : : count ( ) ) ;
val [ " BufferRaw " ] = ( Json : : UInt64 ) ( ObjectStatistic < BufferRaw > : : count ( ) ) ;
val [ " BufferLikeString " ] = ( Json : : UInt64 ) ( ObjectStatistic < BufferLikeString > : : count ( ) ) ;
val [ " BufferList " ] = ( Json : : UInt64 ) ( ObjectStatistic < BufferList > : : count ( ) ) ;
val [ " RtpPacket " ] = ( Json : : UInt64 ) ( ObjectStatistic < RtpPacket > : : count ( ) ) ;
val [ " RtmpPacket " ] = ( Json : : UInt64 ) ( ObjectStatistic < RtmpPacket > : : count ( ) ) ;
# ifdef ENABLE_MEM_DEBUG
auto bytes = getTotalMemUsage ( ) ;
2021-12-29 20:48:15 +08:00
val [ " totalMemUsage " ] = ( Json : : UInt64 ) bytes ;
2024-11-29 23:07:02 +08:00
val [ " totalMemUsageMB " ] = ( int ) ( bytes > > 20 ) ;
2021-12-29 20:48:15 +08:00
val [ " totalMemBlock " ] = ( Json : : UInt64 ) getTotalMemBlock ( ) ;
static auto block_type_size = getBlockTypeSize ( ) ;
{
int i = 0 ;
string str ;
size_t last = 0 ;
for ( auto sz : block_type_size ) {
str . append ( to_string ( last ) + " ~ " + to_string ( sz ) + " : " + to_string ( getTotalMemBlockByType ( i + + ) ) + " ; " ) ;
last = sz ;
}
str . pop_back ( ) ;
val [ " totalMemBlockTypeCount " ] = str ;
}
2021-12-27 17:40:15 +08:00
auto thread_size = EventPollerPool : : Instance ( ) . getExecutorSize ( ) + WorkThreadPool : : Instance ( ) . getExecutorSize ( ) ;
std : : shared_ptr < vector < Value > > thread_mem_info = std : : make_shared < vector < Value > > ( thread_size ) ;
2021-12-29 20:48:15 +08:00
shared_ptr < void > finished ( nullptr , [ thread_mem_info , cb , obj ] ( void * ) {
2021-12-27 17:40:15 +08:00
for ( auto & val : * thread_mem_info ) {
( * obj ) [ " threadMem " ] . append ( val ) ;
}
2024-09-19 14:53:50 +08:00
// 触发回调 [AUTO-TRANSLATED:08ea452d]
// Trigger callback
2021-12-27 17:40:15 +08:00
cb ( * obj ) ;
} ) ;
auto pos = 0 ;
2021-12-29 20:48:15 +08:00
auto lam0 = [ & ] ( TaskExecutor & executor ) {
2021-12-27 17:40:15 +08:00
auto & val = ( * thread_mem_info ) [ pos + + ] ;
2021-12-29 20:48:15 +08:00
executor . async ( [ finished , & val ] ( ) {
2021-12-27 17:40:15 +08:00
auto bytes = getThisThreadMemUsage ( ) ;
val [ " threadName " ] = getThreadName ( ) ;
val [ " threadMemUsage " ] = ( Json : : UInt64 ) bytes ;
2024-11-29 23:07:02 +08:00
val [ " threadMemUsageMB " ] = ( Json : : UInt64 ) ( bytes > > 20 ) ;
2021-12-29 20:48:15 +08:00
val [ " threadMemBlock " ] = ( Json : : UInt64 ) getThisThreadMemBlock ( ) ;
{
int i = 0 ;
string str ;
size_t last = 0 ;
for ( auto sz : block_type_size ) {
str . append ( to_string ( last ) + " ~ " + to_string ( sz ) + " : " + to_string ( getThisThreadMemBlockByType ( i + + ) ) + " ; " ) ;
last = sz ;
}
str . pop_back ( ) ;
val [ " threadMemBlockTypeCount " ] = str ;
}
2021-12-27 17:40:15 +08:00
} ) ;
} ;
2021-12-29 20:48:15 +08:00
auto lam1 = [ lam0 ] ( const TaskExecutor : : Ptr & executor ) {
lam0 ( * executor ) ;
} ;
EventPollerPool : : Instance ( ) . for_each ( lam1 ) ;
WorkThreadPool : : Instance ( ) . for_each ( lam1 ) ;
2021-12-27 17:40:15 +08:00
# else
cb ( * obj ) ;
2021-08-21 19:11:20 +08:00
# endif
}
2026-03-20 21:59:55 +08:00
void updateStreamProxy ( const mediakit : : MediaTuple & tuple , const std : : string & url , const toolkit : : mINI & args ) {
auto key = tuple . shortUrl ( ) ;
auto player = s_player_proxy . find ( key ) ;
if ( ! player ) {
throw std : : runtime_error ( " proxy player not found: " + key ) ;
}
player - > getPoller ( ) - > async ( [ url , args , player ] ( ) {
player - > update ( url , args ) ;
} ) ;
}
2026-03-17 19:30:08 +08:00
void addStreamProxy ( const MediaTuple & tuple , const string & url , int retry_count , bool force ,
2026-03-18 17:48:36 +08:00
const ProtocolOption & option , float timeout_sec , const mINI & args ,
2022-01-12 16:45:47 +08:00
const function < void ( const SockException & ex , const string & key ) > & cb ) {
2024-07-14 09:32:41 +08:00
auto key = tuple . shortUrl ( ) ;
2024-03-10 05:31:20 -03:00
if ( s_player_proxy . find ( key ) ) {
2024-09-19 14:53:50 +08:00
// 已经在拉流了 [AUTO-TRANSLATED:e06c57d7]
// Already pulling stream
2023-08-26 23:02:19 +08:00
cb ( SockException ( Err_other , " This stream already exists " ) , key ) ;
2022-01-12 16:45:47 +08:00
return ;
}
2024-09-19 14:53:50 +08:00
// 添加拉流代理 [AUTO-TRANSLATED:aa516f44]
// Add pull stream proxy
2024-07-14 09:32:41 +08:00
auto player = s_player_proxy . make ( key , tuple , option , retry_count ) ;
2022-01-12 16:45:47 +08:00
2024-09-19 14:53:50 +08:00
// 先透传拷贝参数 [AUTO-TRANSLATED:22b5605e]
// First pass-through copy parameters
2024-03-22 20:34:07 +08:00
for ( auto & pr : args ) {
( * player ) [ pr . first ] = pr . second ;
}
2023-12-07 22:05:20 +08:00
2024-03-10 05:31:20 -03:00
if ( timeout_sec > 0.1f ) {
2024-09-19 14:53:50 +08:00
// 播放握手超时时间 [AUTO-TRANSLATED:5a29ae1f]
// Play handshake timeout
2022-02-02 20:34:50 +08:00
( * player ) [ Client : : kTimeoutMS ] = timeout_sec * 1000 ;
2022-01-12 16:45:47 +08:00
}
2024-09-19 14:53:50 +08:00
// 开始播放,如果播放失败或者播放中止,将会自动重试若干次,默认一直重试 [AUTO-TRANSLATED:ac8499e5]
// Start playing. If playback fails or is stopped, it will automatically retry several times, by default it will retry indefinitely
2026-03-17 19:30:08 +08:00
player - > setPlayCallbackOnce ( [ cb , key , force ] ( const SockException & ex ) {
if ( force ) {
// 强制添加成功
cb ( SockException ( ) , key ) ;
} else {
// 非强制添加
if ( ex ) {
// 失败则移除记录
s_player_proxy . erase ( key ) ;
}
cb ( ex , key ) ;
2022-01-12 16:45:47 +08:00
}
} ) ;
2024-09-19 14:53:50 +08:00
// 被主动关闭拉流 [AUTO-TRANSLATED:41a19476]
// The pull stream was actively closed
2022-01-12 16:45:47 +08:00
player - > setOnClose ( [ key ] ( const SockException & ex ) {
2024-03-10 05:31:20 -03:00
s_player_proxy . erase ( key ) ;
2022-01-12 16:45:47 +08:00
} ) ;
player - > play ( url ) ;
} ;
2024-03-10 05:31:20 -03:00
void addStreamPusherProxy ( const string & schema ,
const string & vhost ,
const string & app ,
const string & stream ,
const string & url ,
int retry_count ,
int rtp_type ,
float timeout_sec ,
2024-12-28 20:21:29 +08:00
const mINI & args ,
2024-03-10 05:31:20 -03:00
const function < void ( const SockException & ex , const string & key ) > & cb ) {
auto key = getPusherKey ( schema , vhost , app , stream , url ) ;
auto src = MediaSource : : find ( schema , vhost , app , stream ) ;
if ( ! src ) {
cb ( SockException ( Err_other , " can not find the source stream " ) , key ) ;
return ;
}
if ( s_pusher_proxy . find ( key ) ) {
2024-09-19 14:53:50 +08:00
// 已经在推流了 [AUTO-TRANSLATED:81fcd202]
// Already pushing stream
2024-03-10 05:31:20 -03:00
cb ( SockException ( Err_success ) , key ) ;
return ;
}
2024-09-19 14:53:50 +08:00
// 添加推流代理 [AUTO-TRANSLATED:f9dbc76d]
// Add push stream proxy
2024-03-10 05:31:20 -03:00
auto pusher = s_pusher_proxy . make ( key , src , retry_count ) ;
2024-12-28 20:21:29 +08:00
// 先透传拷贝参数 [AUTO-TRANSLATED:22b5605e]
// First pass-through copy parameters
for ( auto & pr : args ) {
( * pusher ) [ pr . first ] = pr . second ;
}
2024-09-19 14:53:50 +08:00
// 指定RTP over TCP(播放rtsp时有效) [AUTO-TRANSLATED:1a062656]
// Specify RTP over TCP (effective when playing RTSP)
2024-12-28 20:21:29 +08:00
( * pusher ) [ Client : : kRtpType ] = rtp_type ;
2024-03-10 05:31:20 -03:00
if ( timeout_sec > 0.1f ) {
2024-09-19 14:53:50 +08:00
// 推流握手超时时间 [AUTO-TRANSLATED:00762fc1]
// Push stream handshake timeout
2024-12-28 20:21:29 +08:00
( * pusher ) [ Client : : kTimeoutMS ] = timeout_sec * 1000 ;
2024-03-10 05:31:20 -03:00
}
2024-09-19 14:53:50 +08:00
// 开始推流,如果推流失败或者推流中止,将会自动重试若干次,默认一直重试 [AUTO-TRANSLATED:c8b95088]
// Start pushing stream. If the push stream fails or is stopped, it will automatically retry several times, by default it will retry indefinitely
2024-03-10 05:31:20 -03:00
pusher - > setPushCallbackOnce ( [ cb , key , url ] ( const SockException & ex ) {
if ( ex ) {
WarnL < < " Push " < < url < < " failed, key: " < < key < < " , err: " < < ex ;
s_pusher_proxy . erase ( key ) ;
}
cb ( ex , key ) ;
} ) ;
2024-09-19 14:53:50 +08:00
// 被主动关闭推流 [AUTO-TRANSLATED:bf216f82]
// Stream closed actively
2024-03-10 05:31:20 -03:00
pusher - > setOnClose ( [ key , url ] ( const SockException & ex ) {
WarnL < < " Push " < < url < < " failed, key: " < < key < < " , err: " < < ex ;
s_pusher_proxy . erase ( key ) ;
} ) ;
pusher - > publish ( url ) ;
}
2025-08-10 11:55:11 +08:00
void getThreadsLoad ( TaskExecutorGetterImp & getter , API_ARGS_MAP_ASYNC ) {
getter . getExecutorDelay ( [ & getter , invoker , headerOut ] ( const vector < int > & vecDelay ) {
Value val ;
auto vec = getter . getExecutorLoad ( ) ;
std : : vector < EventPoller : : Ptr > pollers ;
getter . for_each ( [ & ] ( const TaskExecutor : : Ptr & exe ) { pollers . emplace_back ( std : : static_pointer_cast < EventPoller > ( exe ) ) ; } ) ;
int i = API : : Success ;
for ( auto load : vec ) {
Value obj ( objectValue ) ;
obj [ " load " ] = load ;
auto & poller = pollers [ i ] ;
obj [ " name " ] = poller - > getThreadName ( ) ;
obj [ " fd_count " ] = static_cast < Json : : UInt64 > ( poller - > fdCount ( ) ) ;
obj [ " delay " ] = vecDelay [ i + + ] ;
val [ " data " ] . append ( obj ) ;
}
val [ " code " ] = API : : Success ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
}
2022-03-12 15:07:01 +08:00
2026-02-19 22:56:23 +08:00
static constexpr char kLoginCookiePath [ ] = " / " ;
static constexpr char kUnLoginCookieName [ ] = " ZLM_UNLOGIN " ;
static constexpr char kLoginedCookieName [ ] = " ZLM_LOGINED " ;
static constexpr size_t kUnLoginCookieLifeSeconds = 60 ;
static constexpr size_t kLoginedCookieLifeSeconds = 24 * 3600 ;
2026-02-22 18:45:16 +08:00
template < typename T >
void check_secret ( toolkit : : SockInfo & sender , mediakit : : HttpSession : : KeyValue & headerOut , const HttpAllArgs < T > & allArgs , Json : : Value & val ) {
2026-02-19 22:56:23 +08:00
GET_CONFIG ( std : : string , api_secret , API : : kSecret ) ;
auto ip = sender . get_peer_ip ( ) ;
if ( ! HttpFileManager : : isIPAllowed ( ip ) ) {
throw AuthException ( " Your ip is not allowed to access the service. " ) ;
}
2026-03-19 19:32:58 +08:00
try {
2026-02-19 22:56:23 +08:00
auto logined_cookie = HttpCookieManager : : Instance ( ) . getCookie ( kLoginedCookieName , allArgs . getParser ( ) . getHeader ( ) ) ;
if ( ! logined_cookie ) {
auto unlogin_cookie = HttpCookieManager : : Instance ( ) . getCookie ( kUnLoginCookieName , allArgs . getParser ( ) . getHeader ( ) ) ;
if ( ! unlogin_cookie ) {
unlogin_cookie = HttpCookieManager : : Instance ( ) . addCookie ( kUnLoginCookieName , " " , kUnLoginCookieLifeSeconds ) ;
headerOut [ " Set-Cookie " ] = unlogin_cookie - > getCookie ( kLoginCookiePath ) ;
}
val [ " cookie " ] = unlogin_cookie - > getCookie ( ) ;
throw AuthException ( " Please login first " , headerOut , val ) ;
}
2026-03-19 19:32:58 +08:00
// 优先cookie登陆鉴权
} catch ( . . . ) {
try {
// cookie登陆鉴权失败了再比对secret
CHECK_ARGS ( " secret " ) ;
if ( api_secret ! = allArgs [ " secret " ] ) {
throw AuthException ( " Incorrect secret " ) ;
}
return ;
} catch ( . . . ) {
// 未提供secret或secret不匹配, 这个异常隐藏
}
// secret鉴权模式失败, 抛出要求cookie登录的异常
throw ;
2026-02-19 22:56:23 +08:00
}
}
2026-02-22 18:45:16 +08:00
template void check_secret < ApiArgsType > ( toolkit : : SockInfo & , mediakit : : HttpSession : : KeyValue & , const HttpAllArgs < ApiArgsType > & , Json : : Value & ) ;
template void check_secret < Json : : Value > ( toolkit : : SockInfo & , mediakit : : HttpSession : : KeyValue & , const HttpAllArgs < Json : : Value > & , Json : : Value & ) ;
template void check_secret < std : : string > ( toolkit : : SockInfo & , mediakit : : HttpSession : : KeyValue & , const HttpAllArgs < std : : string > & , Json : : Value & ) ;
2019-05-27 15:09:44 +08:00
/**
* 安 装 api接口
* 所 有 api都支持GET和POST两种方式
* POST方式参数支持application / json和application / x - www - form - urlencoded方式
2024-09-19 14:53:50 +08:00
* Install api interface
* All apis support GET and POST methods
* POST method parameters support application / json and application / x - www - form - urlencoded methods
2025-11-17 12:58:45 +08:00
2024-09-19 14:53:50 +08:00
* [ AUTO - TRANSLATED : 62e68 c43 ]
2019-05-27 15:09:44 +08:00
*/
2019-05-20 11:22:59 +08:00
void installWebApi ( ) {
addHttpListener ( ) ;
2019-05-20 17:12:00 +08:00
2024-09-19 14:53:50 +08:00
// 获取线程负载 [AUTO-TRANSLATED:3b0ece5c]
// Get thread load
// 测试url http://127.0.0.1/index/api/getThreadsLoad [AUTO-TRANSLATED:de1c93e7]
// Test url http://127.0.0.1/index/api/getThreadsLoad
2023-07-26 17:18:33 +08:00
api_regist ( " /index/api/getThreadsLoad " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
2025-08-10 11:55:11 +08:00
getThreadsLoad ( EventPollerPool : : Instance ( ) , API_ARGS_VALUE , invoker ) ;
2019-10-24 11:21:55 +08:00
} ) ;
2024-09-19 14:53:50 +08:00
// 获取后台工作线程负载 [AUTO-TRANSLATED:6166e265]
// Get background worker thread load
// 测试url http://127.0.0.1/index/api/getWorkThreadsLoad [AUTO-TRANSLATED:209a8bc1]
// Test url http://127.0.0.1/index/api/getWorkThreadsLoad
2023-07-26 17:18:33 +08:00
api_regist ( " /index/api/getWorkThreadsLoad " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
2025-08-10 11:55:11 +08:00
getThreadsLoad ( WorkThreadPool : : Instance ( ) , API_ARGS_VALUE , invoker ) ;
2019-05-20 11:22:59 +08:00
} ) ;
2024-09-19 14:53:50 +08:00
// 获取服务器配置 [AUTO-TRANSLATED:7dd2f3da]
// Get server configuration
// 测试url http://127.0.0.1/index/api/getServerConfig [AUTO-TRANSLATED:59cd0d71]
// Test url http://127.0.0.1/index/api/getServerConfig
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/getServerConfig " , [ ] ( API_ARGS_MAP ) {
2019-05-20 17:12:00 +08:00
CHECK_SECRET ( ) ;
2019-05-20 11:22:59 +08:00
Value obj ;
for ( auto & pr : mINI : : Instance ( ) ) {
obj [ pr . first ] = ( string & ) pr . second ;
}
val [ " data " ] . append ( obj ) ;
} ) ;
2024-09-19 14:53:50 +08:00
// 设置服务器配置 [AUTO-TRANSLATED:3de7bd37]
// Set server configuration
// 测试url(比如关闭http api调试) http://127.0.0.1/index/api/setServerConfig?api.apiDebug=0 [AUTO-TRANSLATED:9471d218]
// Test url (e.g. disable http api debugging) http://127.0.0.1/index/api/setServerConfig?api.apiDebug=0
// 你也可以通过http post方式传参, 可以通过application/x-www-form-urlencoded或application/json方式传参 [AUTO-TRANSLATED:d493a7c0]
// You can also pass parameters through http post method, you can pass parameters through application/x-www-form-urlencoded or application/json methods
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/setServerConfig " , [ ] ( API_ARGS_MAP ) {
2019-05-20 17:12:00 +08:00
CHECK_SECRET ( ) ;
2019-05-20 11:22:59 +08:00
auto & ini = mINI : : Instance ( ) ;
2019-05-28 09:38:15 +08:00
int changed = API : : Success ;
2024-03-23 11:46:30 -03:00
for ( auto & pr : allArgs . args ) {
2019-05-20 11:22:59 +08:00
if ( ini . find ( pr . first ) = = ini . end ( ) ) {
2021-06-08 14:49:32 +08:00
# if 1
2024-09-19 14:53:50 +08:00
// 没有这个key [AUTO-TRANSLATED:d6855e02]
// This key does not exist
2021-06-08 14:49:32 +08:00
continue ;
# else
2024-09-19 14:53:50 +08:00
// 新增配置选项,为了动态添加多个ffmpeg cmd 模板 [AUTO-TRANSLATED:0f977fcd]
// Add configuration options to dynamically add multiple ffmpeg cmd templates
2021-06-04 18:06:40 +08:00
ini [ pr . first ] = pr . second ;
2024-09-19 14:53:50 +08:00
// 防止changed变化 [AUTO-TRANSLATED:f8ad7e59]
// Prevent changed changes
2021-06-08 14:49:32 +08:00
continue ;
# endif
2019-05-20 11:22:59 +08:00
}
2023-07-26 16:40:10 +08:00
if ( pr . first = = FFmpeg : : kBin ) {
WarnL < < " Configuration named " < < FFmpeg : : kBin < < " is not allowed to be set by setServerConfig api. " ;
continue ;
}
2019-05-20 11:22:59 +08:00
if ( ini [ pr . first ] = = pr . second ) {
continue ;
}
ini [ pr . first ] = pr . second ;
2024-09-19 14:53:50 +08:00
// 替换成功 [AUTO-TRANSLATED:b5d4fec1]
// Replacement successful
2019-05-20 11:22:59 +08:00
+ + changed ;
}
if ( changed > 0 ) {
2023-09-02 10:52:07 +08:00
NOTICE_EMIT ( BroadcastReloadConfigArgs , Broadcast : : kBroadcastReloadConfig ) ;
2019-11-04 09:21:11 +08:00
ini . dumpFile ( g_ini_file ) ;
2019-05-20 11:22:59 +08:00
}
val [ " changed " ] = changed ;
} ) ;
2019-05-27 15:09:44 +08:00
2020-12-27 22:14:59 +08:00
static auto s_get_api_list = [ ] ( API_ARGS_MAP ) {
2019-05-20 17:12:00 +08:00
CHECK_SECRET ( ) ;
2019-05-20 11:22:59 +08:00
for ( auto & pr : s_map_api ) {
val [ " data " ] . append ( pr . first ) ;
}
2020-01-17 11:44:20 +08:00
} ;
2024-09-19 14:53:50 +08:00
// 获取服务器api列表 [AUTO-TRANSLATED:e4c0dd9d]
// Get server api list
// 测试url http://127.0.0.1/index/api/getApiList [AUTO-TRANSLATED:df09e368]
// Test url http://127.0.0.1/index/api/getApiList
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/getApiList " , [ ] ( API_ARGS_MAP ) {
s_get_api_list ( API_ARGS_VALUE ) ;
2020-01-17 11:44:20 +08:00
} ) ;
2024-09-19 14:53:50 +08:00
// 获取服务器api列表 [AUTO-TRANSLATED:e4c0dd9d]
// Get server api list
// 测试url http://127.0.0.1/index/ [AUTO-TRANSLATED:76934dd3]
// Test url http://127.0.0.1/index/
2020-12-27 22:14:59 +08:00
api_regist ( " /index/ " , [ ] ( API_ARGS_MAP ) {
s_get_api_list ( API_ARGS_VALUE ) ;
2019-05-20 11:22:59 +08:00
} ) ;
2019-06-15 17:07:10 +08:00
# if !defined(_WIN32)
2024-09-19 14:53:50 +08:00
// 重启服务器,只有Daemon方式才能重启, 否则是直接关闭! [AUTO-TRANSLATED:9d8a1c32]
// Restart server, only Daemon mode can restart, otherwise it will be closed directly!
// 测试url http://127.0.0.1/index/api/restartServer [AUTO-TRANSLATED:8beaaa8a]
// Test url http://127.0.0.1/index/api/restartServer
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/restartServer " , [ ] ( API_ARGS_MAP ) {
2019-05-20 17:12:00 +08:00
CHECK_SECRET ( ) ;
2019-05-20 11:22:59 +08:00
EventPollerPool : : Instance ( ) . getPoller ( ) - > doDelayTask ( 1000 , [ ] ( ) {
2024-09-19 14:53:50 +08:00
// 尝试正常退出 [AUTO-TRANSLATED:93828d0f]
// Try to exit normally
2019-05-20 11:22:59 +08:00
: : kill ( getpid ( ) , SIGINT ) ;
2024-09-19 14:53:50 +08:00
// 3秒后强制退出 [AUTO-TRANSLATED:fdc82920]
// Force exit after 3 seconds
2019-05-20 11:22:59 +08:00
EventPollerPool : : Instance ( ) . getPoller ( ) - > doDelayTask ( 3000 , [ ] ( ) {
exit ( 0 ) ;
return 0 ;
} ) ;
return 0 ;
} ) ;
2023-12-02 15:02:00 +08:00
val [ " msg " ] = " MediaServer will reboot in on 1 second " ;
2019-05-20 11:22:59 +08:00
} ) ;
2021-12-19 17:39:50 +08:00
# else
2024-09-19 14:53:50 +08:00
// 增加Windows下的重启代码 [AUTO-TRANSLATED:dcba12d5]
// Add restart code for Windows
2021-12-19 17:39:50 +08:00
api_regist ( " /index/api/restartServer " , [ ] ( API_ARGS_MAP ) {
CHECK_SECRET ( ) ;
2024-09-19 14:53:50 +08:00
// 创建重启批处理脚本文件 [AUTO-TRANSLATED:cc18c259]
// Create a restart batch script file
2021-12-19 17:39:50 +08:00
FILE * pf ;
errno_t err = : : _wfopen_s ( & pf , L " RestartServer.cmd " , L " w " ) ; //“w”如果该文件存在, 其内容将被覆盖
if ( err = = 0 ) {
char szExeName [ 1024 ] ;
char drive [ _MAX_DRIVE ] = { 0 } ;
char dir [ _MAX_DIR ] = { 0 } ;
char fname [ _MAX_FNAME ] = { 0 } ;
char ext [ _MAX_EXT ] = { 0 } ;
char exeName [ _MAX_FNAME ] = { 0 } ;
GetModuleFileNameA ( NULL , szExeName , 1024 ) ; //获取进程的全路径
_splitpath ( szExeName , drive , dir , fname , ext ) ;
strcpy ( exeName , fname ) ;
strcat ( exeName , ext ) ;
fprintf ( pf , " @echo off \n taskkill /f /im %s \n start \" \" \" %s \" \n del %%0 " , exeName , szExeName ) ;
fclose ( pf ) ;
2024-09-19 14:53:50 +08:00
// 1秒后执行创建的批处理脚本 [AUTO-TRANSLATED:596dbca9]
// Execute the created batch script after 1 second
2021-12-19 17:39:50 +08:00
EventPollerPool : : Instance ( ) . getPoller ( ) - > doDelayTask ( 1000 , [ ] ( ) {
STARTUPINFO si ;
PROCESS_INFORMATION pi ;
ZeroMemory ( & si , sizeof si ) ;
ZeroMemory ( & pi , sizeof pi ) ;
si . cb = sizeof si ;
si . dwFlags = STARTF_USESHOWWINDOW ;
si . wShowWindow = SW_HIDE ;
TCHAR winSysDir [ 1024 ] ;
ZeroMemory ( winSysDir , sizeof winSysDir ) ;
GetSystemDirectory ( winSysDir , 1024 ) ;
TCHAR appName [ 1024 ] ;
ZeroMemory ( appName , sizeof appName ) ;
_stprintf ( appName , " %s \\ cmd.exe " , winSysDir ) ;
BOOL bRet = CreateProcess ( appName , " /c RestartServer.cmd " , NULL , NULL , FALSE , 0 , NULL , NULL , & si , & pi ) ;
if ( bRet = = FALSE ) {
int err = GetLastError ( ) ;
cout < < endl < < " 无法执行重启操作,错误代码: " < < err < < endl ;
}
WaitForSingleObject ( pi . hProcess , INFINITE ) ;
CloseHandle ( pi . hProcess ) ;
CloseHandle ( pi . hThread ) ;
return 0 ;
} ) ;
val [ " msg " ] = " 服务器将在一秒后自动重启 " ;
} else {
val [ " msg " ] = " 创建重启脚本文件失败 " ;
val [ " code " ] = API : : OtherFailed ;
}
} ) ;
2019-06-15 17:07:10 +08:00
# endif //#if !defined(_WIN32)
2019-05-20 11:22:59 +08:00
2024-09-19 14:53:50 +08:00
// 获取流列表,可选筛选参数 [AUTO-TRANSLATED:68ffc6b6]
// Get stream list, optional filtering parameters
// 测试url0(获取所有流) http://127.0.0.1/index/api/getMediaList [AUTO-TRANSLATED:434652ea]
// Test url0 (get all streams) http://127.0.0.1/index/api/getMediaList
// 测试url1(获取虚拟主机为"__defaultVost__"的流) http://127.0.0.1/index/api/getMediaList?vhost=__defaultVost__ [AUTO-TRANSLATED:5d9bd1ee]
// Test url1 (get streams with virtual host "__defaultVost__") http://127.0.0.1/index/api/getMediaList?vhost=__defaultVost__
// 测试url2(获取rtsp类型的流) http://127.0.0.1/index/api/getMediaList?schema=rtsp [AUTO-TRANSLATED:21c2c15d]
// Test url2 (get rtsp type streams) http://127.0.0.1/index/api/getMediaList?schema=rtsp
2025-10-18 13:00:54 +08:00
api_regist ( " /index/api/getMediaList " , [ ] ( API_ARGS_MAP_ASYNC ) {
2019-05-20 17:12:00 +08:00
CHECK_SECRET ( ) ;
2024-09-19 14:53:50 +08:00
// 获取所有MediaSource列表 [AUTO-TRANSLATED:7bf16dc2]
// Get all MediaSource lists
2025-10-30 11:20:04 +08:00
std : : list < MediaSource : : Ptr > lst ;
2021-06-30 21:06:29 +08:00
MediaSource : : for_each_media ( [ & ] ( const MediaSource : : Ptr & media ) {
2025-10-30 11:20:04 +08:00
lst . emplace_back ( media ) ;
2021-06-30 21:06:29 +08:00
} , allArgs [ " schema " ] , allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] ) ;
2025-10-30 11:20:04 +08:00
if ( lst . size ( ) = = 1 ) {
// 如果是搜索单一流,那么在它的归属线程中执行,用于获取丢包率参数
auto front = std : : move ( lst . front ( ) ) ;
front - > getOwnerPoller ( ) - > async ( [ = ] ( ) mutable {
val [ " data " ] . append ( makeMediaSourceJson ( * front ) ) ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
} else {
for ( auto & media : lst ) {
val [ " data " ] . append ( makeMediaSourceJson ( * media ) ) ;
}
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
}
2019-05-20 11:22:59 +08:00
} ) ;
2024-09-19 14:53:50 +08:00
// 测试url http://127.0.0.1/index/api/isMediaOnline?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs [AUTO-TRANSLATED:126a75e8]
// Test url http://127.0.0.1/index/api/isMediaOnline?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/isMediaOnline " , [ ] ( API_ARGS_MAP ) {
2019-11-13 14:26:12 +08:00
CHECK_SECRET ( ) ;
CHECK_ARGS ( " schema " , " vhost " , " app " , " stream " ) ;
2020-05-26 10:11:58 +08:00
val [ " online " ] = ( bool ) ( MediaSource : : find ( allArgs [ " schema " ] , allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] ) ) ;
2019-11-13 14:26:12 +08:00
} ) ;
2024-09-19 14:53:50 +08:00
// 获取媒体流播放器列表 [AUTO-TRANSLATED:bcadf31c]
// Get media stream player list
// 测试url http://127.0.0.1/index/api/getMediaPlayerList?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs [AUTO-TRANSLATED:2aab7522]
// Test url http://127.0.0.1/index/api/getMediaPlayerList?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
2022-08-30 21:05:19 +08:00
api_regist ( " /index/api/getMediaPlayerList " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " schema " , " vhost " , " app " , " stream " ) ;
auto src = MediaSource : : find ( allArgs [ " schema " ] , allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] ) ;
if ( ! src ) {
throw ApiRetException ( " can not find the stream " , API : : NotFound ) ;
}
src - > getPlayerList (
2023-09-02 12:06:35 +08:00
[ = ] ( const std : : list < toolkit : : Any > & info_list ) mutable {
2022-08-30 21:05:19 +08:00
val [ " code " ] = API : : Success ;
auto & data = val [ " data " ] ;
2022-09-01 17:45:06 +08:00
data = Value ( arrayValue ) ;
2022-08-30 21:05:19 +08:00
for ( auto & info : info_list ) {
2023-09-02 12:06:35 +08:00
auto & obj = info . get < Value > ( ) ;
data . append ( std : : move ( obj ) ) ;
2022-08-30 21:05:19 +08:00
}
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ,
2023-09-02 12:06:35 +08:00
[ ] ( toolkit : : Any & & info ) - > toolkit : : Any {
2022-08-30 21:05:19 +08:00
auto obj = std : : make_shared < Value > ( ) ;
2025-03-21 15:22:48 +08:00
auto & session = info . get < Session > ( ) ;
fillSockInfo ( * obj , & session ) ;
( * obj ) [ " typeid " ] = toolkit : : demangle ( typeid ( session ) . name ( ) ) ;
2023-09-02 12:06:35 +08:00
toolkit : : Any ret ;
ret . set ( obj ) ;
return ret ;
2022-08-30 21:05:19 +08:00
} ) ;
} ) ;
2023-09-02 12:53:56 +08:00
api_regist ( " /index/api/broadcastMessage " , [ ] ( API_ARGS_MAP ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " schema " , " vhost " , " app " , " stream " , " msg " ) ;
auto src = MediaSource : : find ( allArgs [ " schema " ] , allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] ) ;
if ( ! src ) {
throw ApiRetException ( " can not find the stream " , API : : NotFound ) ;
}
Any any ;
Buffer : : Ptr buffer = std : : make_shared < BufferLikeString > ( allArgs [ " msg " ] ) ;
any . set ( std : : move ( buffer ) ) ;
src - > broadcastMessage ( any ) ;
} ) ;
2024-09-19 14:53:50 +08:00
// 测试url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs [AUTO-TRANSLATED:9402e811]
// Test url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
2022-06-11 14:17:43 +08:00
api_regist ( " /index/api/getMediaInfo " , [ ] ( API_ARGS_MAP_ASYNC ) {
2019-12-13 13:35:27 +08:00
CHECK_SECRET ( ) ;
CHECK_ARGS ( " schema " , " vhost " , " app " , " stream " ) ;
2020-05-26 10:11:58 +08:00
auto src = MediaSource : : find ( allArgs [ " schema " ] , allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] ) ;
2019-12-13 13:35:27 +08:00
if ( ! src ) {
2022-06-11 14:17:43 +08:00
throw ApiRetException ( " can not find the stream " , API : : NotFound ) ;
2019-12-13 13:35:27 +08:00
}
2022-06-11 14:17:43 +08:00
src - > getOwnerPoller ( ) - > async ( [ = ] ( ) mutable {
auto val = makeMediaSourceJson ( * src ) ;
val [ " code " ] = API : : Success ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
2019-12-13 13:35:27 +08:00
} ) ;
2024-09-19 14:53:50 +08:00
// 主动关断流,包括关断拉流、推流 [AUTO-TRANSLATED:80506955]
// Actively close the stream, including closing the pull stream and push stream
// 测试url http://127.0.0.1/index/api/close_stream?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1 [AUTO-TRANSLATED:c3831592]
// Test url http://127.0.0.1/index/api/close_stream?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
2022-06-11 14:17:43 +08:00
api_regist ( " /index/api/close_stream " , [ ] ( API_ARGS_MAP_ASYNC ) {
2019-05-20 17:12:00 +08:00
CHECK_SECRET ( ) ;
2019-05-20 16:53:29 +08:00
CHECK_ARGS ( " schema " , " vhost " , " app " , " stream " ) ;
2024-09-19 14:53:50 +08:00
// 踢掉推流器 [AUTO-TRANSLATED:61e39b14]
// Kick out the pusher
2019-05-20 11:22:59 +08:00
auto src = MediaSource : : find ( allArgs [ " schema " ] ,
allArgs [ " vhost " ] ,
allArgs [ " app " ] ,
allArgs [ " stream " ] ) ;
2022-06-11 14:17:43 +08:00
if ( ! src ) {
throw ApiRetException ( " can not find the stream " , API : : NotFound ) ;
}
bool force = allArgs [ " force " ] . as < bool > ( ) ;
src - > getOwnerPoller ( ) - > async ( [ = ] ( ) mutable {
bool flag = src - > close ( force ) ;
2019-11-18 16:28:56 +08:00
val [ " result " ] = flag ? 0 : - 1 ;
2019-05-27 14:15:21 +08:00
val [ " msg " ] = flag ? " success " : " close failed " ;
2020-10-24 23:31:40 +08:00
val [ " code " ] = flag ? API : : Success : API : : OtherFailed ;
2022-06-14 16:29:04 +08:00
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
2022-06-11 14:17:43 +08:00
} ) ;
2019-05-20 11:22:59 +08:00
} ) ;
2024-09-19 14:53:50 +08:00
// 批量主动关断流,包括关断拉流、推流 [AUTO-TRANSLATED:5d180cd8]
// Batch actively close the stream, including closing the pull stream and push stream
// 测试url http://127.0.0.1/index/api/close_streams?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1 [AUTO-TRANSLATED:786933db]
// Test url http://127.0.0.1/index/api/close_streams?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/close_streams " , [ ] ( API_ARGS_MAP ) {
2019-11-18 10:46:59 +08:00
CHECK_SECRET ( ) ;
2024-09-19 14:53:50 +08:00
// 筛选命中个数 [AUTO-TRANSLATED:6db1e8c7]
// Filter hit count
2019-11-18 10:46:59 +08:00
int count_hit = 0 ;
int count_closed = 0 ;
2019-11-19 10:55:44 +08:00
list < MediaSource : : Ptr > media_list ;
2021-06-30 21:06:29 +08:00
MediaSource : : for_each_media ( [ & ] ( const MediaSource : : Ptr & media ) {
2019-11-18 10:46:59 +08:00
+ + count_hit ;
2019-11-19 10:55:44 +08:00
media_list . emplace_back ( media ) ;
2021-06-30 21:06:29 +08:00
} , allArgs [ " schema " ] , allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] ) ;
2019-11-19 10:55:44 +08:00
bool force = allArgs [ " force " ] . as < bool > ( ) ;
2022-06-11 14:17:43 +08:00
for ( auto & media : media_list ) {
2025-09-09 21:59:43 +08:00
media - > getOwnerPoller ( ) - > async ( [ media , force ] ( ) { media - > close ( force ) ; } ) ;
+ + count_closed ;
2019-11-19 10:55:44 +08:00
}
2019-11-18 10:46:59 +08:00
val [ " count_hit " ] = count_hit ;
val [ " count_closed " ] = count_closed ;
} ) ;
2024-09-19 14:53:50 +08:00
// 获取所有Session列表信息 [AUTO-TRANSLATED:e785052d]
// Get all Session list information
// 可以根据本地端口和远端ip来筛选 [AUTO-TRANSLATED:4d4c9d61]
// You can filter by local port and remote ip
// 测试url(筛选某端口下的tcp会话) http://127.0.0.1/index/api/getAllSession?local_port=1935 [AUTO-TRANSLATED:ef845193]
// Test url (filter tcp session under a certain port) http://127.0.0.1/index/api/getAllSession?local_port=1935
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/getAllSession " , [ ] ( API_ARGS_MAP ) {
2019-05-27 15:09:44 +08:00
CHECK_SECRET ( ) ;
Value jsession ;
uint16_t local_port = allArgs [ " local_port " ] . as < uint16_t > ( ) ;
2021-08-12 16:07:31 +08:00
string peer_ip = allArgs [ " peer_ip " ] ;
2019-05-27 15:09:44 +08:00
2021-06-08 11:29:32 +08:00
SessionMap : : Instance ( ) . for_each_session ( [ & ] ( const string & id , const Session : : Ptr & session ) {
2019-11-18 16:16:56 +08:00
if ( local_port ! = 0 & & local_port ! = session - > get_local_port ( ) ) {
2019-05-27 15:09:44 +08:00
return ;
}
if ( ! peer_ip . empty ( ) & & peer_ip ! = session - > get_peer_ip ( ) ) {
return ;
}
2022-09-07 11:06:39 +08:00
fillSockInfo ( jsession , session . get ( ) ) ;
2019-05-27 15:09:44 +08:00
jsession [ " id " ] = id ;
2026-01-09 11:34:40 +08:00
jsession [ " type " ] = session - > getSock ( ) - > sockType ( ) = = SockNum : : Sock_TCP ? " tcp " : " udp " ;
2022-06-11 15:03:28 +08:00
jsession [ " typeid " ] = toolkit : : demangle ( typeid ( * session ) . name ( ) ) ;
2019-05-27 15:09:44 +08:00
val [ " data " ] . append ( jsession ) ;
} ) ;
} ) ;
2024-09-19 14:53:50 +08:00
// 断开tcp连接, 比如说可以断开rtsp、rtmp播放器等 [AUTO-TRANSLATED:9147ffec]
// Disconnect the tcp connection, for example, you can disconnect the rtsp, rtmp player, etc.
// 测试url http://127.0.0.1/index/api/kick_session?id=123456 [AUTO-TRANSLATED:c2880cb5]
// Test url http://127.0.0.1/index/api/kick_session?id=123456
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/kick_session " , [ ] ( API_ARGS_MAP ) {
2019-05-20 17:12:00 +08:00
CHECK_SECRET ( ) ;
2019-05-20 16:53:29 +08:00
CHECK_ARGS ( " id " ) ;
2024-09-19 14:53:50 +08:00
// 踢掉tcp会话 [AUTO-TRANSLATED:f6f318bd]
// Kick out the tcp session
2019-05-28 09:38:15 +08:00
auto session = SessionMap : : Instance ( ) . get ( allArgs [ " id " ] ) ;
2019-05-20 11:22:59 +08:00
if ( ! session ) {
2019-11-13 14:33:19 +08:00
throw ApiRetException ( " can not find the target " , API : : OtherFailed ) ;
2019-05-20 11:22:59 +08:00
}
session - > safeShutdown ( ) ;
} ) ;
2019-11-18 16:34:39 +08:00
2024-09-19 14:53:50 +08:00
// 批量断开tcp连接, 比如说可以断开rtsp、rtmp播放器等 [AUTO-TRANSLATED:fef59eb8]
// Batch disconnect tcp connections, for example, you can disconnect rtsp, rtmp players, etc.
// 测试url http://127.0.0.1/index/api/kick_sessions?local_port=1935 [AUTO-TRANSLATED:5891b482]
// Test url http://127.0.0.1/index/api/kick_sessions?local_port=1935
2023-12-17 18:13:51 +08:00
api_regist ( " /index/api/kick_sessions " , [ ] ( API_ARGS_MAP ) {
2019-11-18 16:34:39 +08:00
CHECK_SECRET ( ) ;
uint16_t local_port = allArgs [ " local_port " ] . as < uint16_t > ( ) ;
2021-08-12 16:07:31 +08:00
string peer_ip = allArgs [ " peer_ip " ] ;
2021-01-17 18:31:50 +08:00
size_t count_hit = 0 ;
2019-11-18 16:34:39 +08:00
2021-06-08 11:29:32 +08:00
list < Session : : Ptr > session_list ;
2023-12-17 18:13:51 +08:00
SessionMap : : Instance ( ) . for_each_session ( [ & ] ( const string & id , const Session : : Ptr & session ) {
if ( local_port ! = 0 & & local_port ! = session - > get_local_port ( ) ) {
2019-11-18 16:34:39 +08:00
return ;
}
2023-12-17 18:13:51 +08:00
if ( ! peer_ip . empty ( ) & & peer_ip ! = session - > get_peer_ip ( ) ) {
return ;
}
if ( session - > getIdentifier ( ) = = sender . getIdentifier ( ) ) {
2024-09-19 14:53:50 +08:00
// 忽略本http链接 [AUTO-TRANSLATED:9fb4bf76]
// Ignore this http link
2019-11-18 16:34:39 +08:00
return ;
}
2019-11-19 10:55:44 +08:00
session_list . emplace_back ( session ) ;
2019-11-18 16:34:39 +08:00
+ + count_hit ;
} ) ;
2019-11-19 10:55:44 +08:00
2023-12-17 18:13:51 +08:00
for ( auto & session : session_list ) {
2019-11-19 10:55:44 +08:00
session - > safeShutdown ( ) ;
}
2019-11-18 16:34:39 +08:00
val [ " count_hit " ] = ( Json : : UInt64 ) count_hit ;
} ) ;
2024-09-19 14:53:50 +08:00
// 动态添加rtsp/rtmp推流代理 [AUTO-TRANSLATED:2eb09bc9]
// Dynamically add rtsp/rtmp push stream proxy
// 测试url http://127.0.0.1/index/api/addStreamPusherProxy?schema=rtmp&vhost=__defaultVhost__&app=proxy&stream=0&dst_url=rtmp://127.0.0.1/live/obs [AUTO-TRANSLATED:25d7d4b0]
// Test url http://127.0.0.1/index/api/addStreamPusherProxy?schema=rtmp&vhost=__defaultVhost__&app=proxy&stream=0&dst_url=rtmp://127.0.0.1/live/obs
2021-06-16 19:40:08 +08:00
api_regist ( " /index/api/addStreamPusherProxy " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
2021-06-17 10:12:34 +08:00
CHECK_ARGS ( " schema " , " vhost " , " app " , " stream " , " dst_url " ) ;
2024-12-28 20:21:29 +08:00
mINI args ;
for ( auto & pr : allArgs . args ) {
args . emplace ( pr . first , pr . second ) ;
}
2021-06-17 10:12:34 +08:00
auto dst_url = allArgs [ " dst_url " ] ;
2023-04-01 23:54:11 +08:00
auto retry_count = allArgs [ " retry_count " ] . empty ( ) ? - 1 : allArgs [ " retry_count " ] . as < int > ( ) ;
2025-04-15 09:48:00 +08:00
EventPollerPool : : Instance ( ) . getPoller ( false ) - > async ( [ = ] ( ) mutable {
2025-04-13 19:39:37 +08:00
addStreamPusherProxy ( allArgs [ " schema " ] ,
allArgs [ " vhost " ] ,
allArgs [ " app " ] ,
allArgs [ " stream " ] ,
allArgs [ " dst_url " ] ,
retry_count ,
allArgs [ " rtp_type " ] ,
allArgs [ " timeout_sec " ] ,
args ,
[ invoker , val , headerOut , dst_url ] ( const SockException & ex , const string & key ) mutable {
if ( ex ) {
val [ " code " ] = API : : OtherFailed ;
val [ " msg " ] = ex . what ( ) ;
} else {
val [ " data " ] [ " key " ] = key ;
InfoL < < " Publish success, please play with player: " < < dst_url ;
}
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
} ) ;
2021-06-16 19:40:08 +08:00
} ) ;
2024-09-19 14:53:50 +08:00
// 关闭推流代理 [AUTO-TRANSLATED:91602b75]
// Close the push stream proxy
// 测试url http://127.0.0.1/index/api/delStreamPusherProxy?key=__defaultVhost__/proxy/0 [AUTO-TRANSLATED:2671206c]
// Test url http://127.0.0.1/index/api/delStreamPusherProxy?key=__defaultVhost__/proxy/0
2021-06-17 10:12:34 +08:00
api_regist ( " /index/api/delStreamPusherProxy " , [ ] ( API_ARGS_MAP ) {
2021-06-16 19:40:08 +08:00
CHECK_SECRET ( ) ;
CHECK_ARGS ( " key " ) ;
2024-03-10 05:31:20 -03:00
val [ " data " ] [ " flag " ] = s_pusher_proxy . erase ( allArgs [ " key " ] ) = = 1 ;
2021-06-16 19:40:08 +08:00
} ) ;
2024-11-01 10:47:18 +08:00
api_regist ( " /index/api/listStreamPusherProxy " , [ ] ( API_ARGS_MAP ) {
CHECK_SECRET ( ) ;
2026-03-18 11:23:22 +08:00
s_pusher_proxy . for_each ( [ & val ] ( const std : : string & key , const PusherProxy : : Ptr & p ) {
2024-11-01 10:47:18 +08:00
Json : : Value item = ToJson ( p ) ;
item [ " key " ] = key ;
val [ " data " ] . append ( item ) ;
2026-03-18 11:23:22 +08:00
} , allArgs [ " key " ] ) ;
2024-11-01 10:47:18 +08:00
} ) ;
api_regist ( " /index/api/listStreamProxy " , [ ] ( API_ARGS_MAP ) {
CHECK_SECRET ( ) ;
2026-03-18 11:23:22 +08:00
s_player_proxy . for_each ( [ & val ] ( const std : : string & key , const PlayerProxy : : Ptr & p ) {
2024-11-01 10:47:18 +08:00
Json : : Value item = ToJson ( p ) ;
item [ " key " ] = key ;
val [ " data " ] . append ( item ) ;
2026-03-18 11:23:22 +08:00
} , allArgs [ " key " ] ) ;
2024-11-01 10:47:18 +08:00
} ) ;
2024-09-19 14:53:50 +08:00
// 动态添加rtsp/rtmp拉流代理 [AUTO-TRANSLATED:2616537c]
// Dynamically add rtsp/rtmp pull stream proxy
// 测试url http://127.0.0.1/index/api/addStreamProxy?vhost=__defaultVhost__&app=proxy&enable_rtsp=1&enable_rtmp=1&stream=0&url=rtmp://127.0.0.1/live/obs [AUTO-TRANSLATED:71ddce15]
// Test url http://127.0.0.1/index/api/addStreamProxy?vhost=__defaultVhost__&app=proxy&enable_rtsp=1&enable_rtmp=1&stream=0&url=rtmp://127.0.0.1/live/obs
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/addStreamProxy " , [ ] ( API_ARGS_MAP_ASYNC ) {
2019-05-27 23:07:12 +08:00
CHECK_SECRET ( ) ;
2020-09-20 09:26:00 +08:00
CHECK_ARGS ( " vhost " , " app " , " stream " , " url " ) ;
2022-03-12 15:07:01 +08:00
2023-12-01 14:33:07 +08:00
mINI args ;
2024-03-23 11:46:30 -03:00
for ( auto & pr : allArgs . args ) {
2023-12-01 14:33:07 +08:00
args . emplace ( pr . first , pr . second ) ;
}
2022-09-16 23:30:55 +08:00
ProtocolOption option ( allArgs ) ;
2023-03-22 15:52:56 +08:00
auto retry_count = allArgs [ " retry_count " ] . empty ( ) ? - 1 : allArgs [ " retry_count " ] . as < int > ( ) ;
2024-07-14 09:32:41 +08:00
std : : string vhost = DEFAULT_VHOST ;
if ( ! allArgs [ " vhost " ] . empty ( ) ) {
vhost = allArgs [ " vhost " ] ;
}
auto tuple = MediaTuple { vhost , allArgs [ " app " ] , allArgs [ " stream " ] , " " } ;
2025-04-15 09:48:00 +08:00
EventPollerPool : : Instance ( ) . getPoller ( false ) - > async ( [ = ] ( ) mutable {
2025-04-13 19:39:37 +08:00
addStreamProxy ( tuple ,
allArgs [ " url " ] ,
retry_count ,
2026-03-17 19:30:08 +08:00
allArgs [ " force " ] ,
2025-04-13 19:39:37 +08:00
option ,
allArgs [ " timeout_sec " ] ,
args ,
2025-04-15 09:48:00 +08:00
[ invoker , val , headerOut ] ( const SockException & ex , const string & key ) mutable {
2025-04-13 19:39:37 +08:00
if ( ex ) {
val [ " code " ] = API : : OtherFailed ;
val [ " msg " ] = ex . what ( ) ;
} else {
val [ " data " ] [ " key " ] = key ;
}
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
} ) ;
2019-05-20 16:26:04 +08:00
} ) ;
2024-09-19 14:53:50 +08:00
// 关闭拉流代理 [AUTO-TRANSLATED:5204f128]
// Close the pull stream proxy
// 测试url http://127.0.0.1/index/api/delStreamProxy?key=__defaultVhost__/proxy/0 [AUTO-TRANSLATED:2b0903ef]
// Test url http://127.0.0.1/index/api/delStreamProxy?key=__defaultVhost__/proxy/0
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/delStreamProxy " , [ ] ( API_ARGS_MAP ) {
2019-05-20 17:12:00 +08:00
CHECK_SECRET ( ) ;
2019-05-20 18:17:53 +08:00
CHECK_ARGS ( " key " ) ;
2024-03-10 05:31:20 -03:00
val [ " data " ] [ " flag " ] = s_player_proxy . erase ( allArgs [ " key " ] ) = = 1 ;
2019-05-20 16:26:04 +08:00
} ) ;
2021-01-23 09:42:15 +08:00
static auto addFFmpegSource = [ ] ( const string & ffmpeg_cmd_key ,
const string & src_url ,
2019-06-06 18:28:33 +08:00
const string & dst_url ,
int timeout_ms ,
2020-12-20 20:25:44 +08:00
bool enable_hls ,
bool enable_mp4 ,
const function < void ( const SockException & ex , const string & key ) > & cb ) {
2019-06-06 18:28:33 +08:00
auto key = MD5 ( dst_url ) . hexdigest ( ) ;
2024-03-10 05:31:20 -03:00
if ( s_ffmpeg_src . find ( key ) ) {
2024-09-19 14:53:50 +08:00
// 已经在拉流了 [AUTO-TRANSLATED:e06c57d7]
// Already pulling
2020-12-20 20:25:44 +08:00
cb ( SockException ( Err_success ) , key ) ;
2019-06-06 18:28:33 +08:00
return ;
}
2024-03-10 05:31:20 -03:00
auto ffmpeg = s_ffmpeg_src . make ( key ) ;
2019-06-06 18:28:33 +08:00
2020-12-20 20:25:44 +08:00
ffmpeg - > setOnClose ( [ key ] ( ) {
2024-03-10 05:31:20 -03:00
s_ffmpeg_src . erase ( key ) ;
2019-06-06 18:28:33 +08:00
} ) ;
2021-01-17 20:15:08 +08:00
ffmpeg - > setupRecordFlag ( enable_hls , enable_mp4 ) ;
2021-01-23 09:42:15 +08:00
ffmpeg - > play ( ffmpeg_cmd_key , src_url , dst_url , timeout_ms , [ cb , key ] ( const SockException & ex ) {
2020-12-20 20:25:44 +08:00
if ( ex ) {
2024-03-10 05:31:20 -03:00
s_ffmpeg_src . erase ( key ) ;
2019-06-06 18:28:33 +08:00
}
2020-12-20 20:25:44 +08:00
cb ( ex , key ) ;
2019-06-06 18:28:33 +08:00
} ) ;
} ;
2024-09-19 14:53:50 +08:00
// 动态添加rtsp/rtmp拉流代理 [AUTO-TRANSLATED:2616537c]
// Dynamically add rtsp/rtmp pull stream proxy
// 测试url http://127.0.0.1/index/api/addFFmpegSource?src_url=http://live.hkstv.hk.lxdns.com/live/hks2/playlist.m3u8&dst_url=rtmp://127.0.0.1/live/hks2&timeout_ms=10000 [AUTO-TRANSLATED:501cdd89]
// // Test url http://127.0.0.1/index/api/addFFmpegSource?src_url=http://live.hkstv.hk.lxdns.com/live/hks2/playlist.m3u8&dst_url=rtmp://127.0.0.1/live/hks2&timeout_ms=10000
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/addFFmpegSource " , [ ] ( API_ARGS_MAP_ASYNC ) {
2019-06-06 18:28:33 +08:00
CHECK_SECRET ( ) ;
CHECK_ARGS ( " src_url " , " dst_url " , " timeout_ms " ) ;
auto src_url = allArgs [ " src_url " ] ;
auto dst_url = allArgs [ " dst_url " ] ;
int timeout_ms = allArgs [ " timeout_ms " ] ;
2020-12-20 20:25:44 +08:00
auto enable_hls = allArgs [ " enable_hls " ] . as < int > ( ) ;
auto enable_mp4 = allArgs [ " enable_mp4 " ] . as < int > ( ) ;
2019-06-06 18:28:33 +08:00
2021-01-23 09:42:15 +08:00
addFFmpegSource ( allArgs [ " ffmpeg_cmd_key " ] , src_url , dst_url , timeout_ms , enable_hls , enable_mp4 ,
2021-03-14 10:29:17 +08:00
[ invoker , val , headerOut ] ( const SockException & ex , const string & key ) mutable {
2020-12-20 20:25:44 +08:00
if ( ex ) {
2021-03-14 10:29:17 +08:00
val [ " code " ] = API : : OtherFailed ;
val [ " msg " ] = ex . what ( ) ;
2020-12-20 20:25:44 +08:00
} else {
2021-03-14 10:29:17 +08:00
val [ " data " ] [ " key " ] = key ;
2019-06-06 18:28:33 +08:00
}
2021-01-02 21:24:06 +08:00
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
2019-06-06 18:28:33 +08:00
} ) ;
} ) ;
2024-09-19 14:53:50 +08:00
// 关闭拉流代理 [AUTO-TRANSLATED:5204f128]
// Close the pull stream proxy
// 测试url http://127.0.0.1/index/api/delFFmepgSource?key=key [AUTO-TRANSLATED:ed6fa147]
// Test url http://127.0.0.1/index/api/delFFmepgSource?key=key
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/delFFmpegSource " , [ ] ( API_ARGS_MAP ) {
2019-06-06 18:28:33 +08:00
CHECK_SECRET ( ) ;
CHECK_ARGS ( " key " ) ;
2024-03-10 05:31:20 -03:00
val [ " data " ] [ " flag " ] = s_ffmpeg_src . erase ( allArgs [ " key " ] ) = = 1 ;
2019-06-06 18:28:33 +08:00
} ) ;
2024-11-01 10:47:18 +08:00
api_regist ( " /index/api/listFFmpegSource " , [ ] ( API_ARGS_MAP ) {
CHECK_SECRET ( ) ;
s_ffmpeg_src . for_each ( [ & val ] ( const std : : string & key , const FFmpegSource : : Ptr & src ) {
Json : : Value item ;
item [ " src_url " ] = src - > getSrcUrl ( ) ;
item [ " dst_url " ] = src - > getDstUrl ( ) ;
item [ " cmd " ] = src - > getCmd ( ) ;
item [ " ffmpeg_cmd_key " ] = src - > getCmdKey ( ) ;
item [ " key " ] = key ;
val [ " data " ] . append ( item ) ;
} ) ;
} ) ;
2024-09-19 14:53:50 +08:00
// 新增http api下载可执行程序文件接口 [AUTO-TRANSLATED:d6e44e84]
// Add a new http api to download executable files
// 测试url http://127.0.0.1/index/api/downloadBin [AUTO-TRANSLATED:9525e834]
// Test url http://127.0.0.1/index/api/downloadBin
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/downloadBin " , [ ] ( API_ARGS_MAP_ASYNC ) {
2019-10-28 17:23:16 +08:00
CHECK_SECRET ( ) ;
2024-03-23 11:46:30 -03:00
invoker . responseFile ( allArgs . parser . getHeader ( ) , StrCaseMap ( ) , exePath ( ) ) ;
2019-10-28 17:23:16 +08:00
} ) ;
2019-12-06 11:54:10 +08:00
# if defined(ENABLE_RTPPROXY)
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/getRtpInfo " , [ ] ( API_ARGS_MAP ) {
2019-12-05 19:53:55 +08:00
CHECK_SECRET ( ) ;
2020-07-07 10:01:12 +08:00
CHECK_ARGS ( " stream_id " ) ;
2024-07-09 10:42:10 +08:00
std : : string vhost = DEFAULT_VHOST ;
if ( ! allArgs [ " vhost " ] . empty ( ) ) {
vhost = allArgs [ " vhost " ] ;
}
std : : string app = kRtpAppName ;
if ( ! allArgs [ " app " ] . empty ( ) ) {
app = allArgs [ " app " ] ;
}
auto src = MediaSource : : find ( vhost , app , allArgs [ " stream_id " ] ) ;
2024-06-09 10:52:10 +08:00
auto process = src ? src - > getRtpProcess ( ) : nullptr ;
2020-07-07 10:01:12 +08:00
if ( ! process ) {
2019-12-05 19:53:55 +08:00
val [ " exist " ] = false ;
return ;
}
val [ " exist " ] = true ;
2022-09-07 11:06:39 +08:00
fillSockInfo ( val , process . get ( ) ) ;
2019-12-05 19:53:55 +08:00
} ) ;
2020-07-02 22:23:43 +08:00
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/openRtpServer " , [ ] ( API_ARGS_MAP ) {
2020-07-02 22:23:43 +08:00
CHECK_SECRET ( ) ;
2022-09-09 10:56:28 +08:00
CHECK_ARGS ( " port " , " stream_id " ) ;
2024-07-09 10:42:10 +08:00
std : : string vhost = DEFAULT_VHOST ;
if ( ! allArgs [ " vhost " ] . empty ( ) ) {
vhost = allArgs [ " vhost " ] ;
}
std : : string app = kRtpAppName ;
if ( ! allArgs [ " app " ] . empty ( ) ) {
app = allArgs [ " app " ] ;
}
2020-07-09 10:38:57 +08:00
auto stream_id = allArgs [ " stream_id " ] ;
2024-07-09 10:42:10 +08:00
auto tuple = MediaTuple { vhost , app , stream_id , " " } ;
2022-09-09 10:56:28 +08:00
auto tcp_mode = allArgs [ " tcp_mode " ] . as < int > ( ) ;
if ( allArgs [ " enable_tcp " ] . as < int > ( ) & & ! tcp_mode ) {
2024-09-19 14:53:50 +08:00
// 兼容老版本请求, 新版本去除enable_tcp参数并新增tcp_mode参数 [AUTO-TRANSLATED:3b6a5ab5]
// Compatible with old version requests, the new version removes the enable_tcp parameter and adds the tcp_mode parameter
2022-09-09 10:56:28 +08:00
tcp_mode = 1 ;
}
2024-03-05 17:06:31 +08:00
auto only_track = allArgs [ " only_track " ] . as < int > ( ) ;
if ( allArgs [ " only_audio " ] . as < bool > ( ) ) {
2024-09-19 14:53:50 +08:00
// 兼容老版本请求, 新版本去除only_audio参数并新增only_track参数 [AUTO-TRANSLATED:a7a40942]
// Compatible with old version requests, the new version removes the only_audio parameter and adds the only_track parameter
2024-03-05 17:06:31 +08:00
only_track = 1 ;
}
2024-08-01 11:03:26 +08:00
GET_CONFIG ( std : : string , local_ip , General : : kListenIP )
2024-01-18 10:59:09 +08:00
if ( ! allArgs [ " local_ip " ] . empty ( ) ) {
local_ip = allArgs [ " local_ip " ] ;
}
2024-07-09 10:42:10 +08:00
auto port = openRtpServer ( allArgs [ " port " ] , tuple , tcp_mode , local_ip , allArgs [ " re_use_port " ] . as < bool > ( ) ,
2024-03-05 17:06:31 +08:00
allArgs [ " ssrc " ] . as < uint32_t > ( ) , only_track ) ;
2022-09-09 10:56:28 +08:00
if ( port = = 0 ) {
2024-07-09 10:42:10 +08:00
throw InvalidArgsException ( " This stream already exists " ) ;
2020-07-09 10:57:17 +08:00
}
2024-09-19 14:53:50 +08:00
// 回复json [AUTO-TRANSLATED:0c443c6a]
// Reply json
2022-06-22 10:31:53 +08:00
val [ " port " ] = port ;
2020-07-02 22:23:43 +08:00
} ) ;
2023-11-09 20:36:51 +08:00
2024-07-09 10:42:10 +08:00
api_regist ( " /index/api/openRtpServerMultiplex " , [ ] ( API_ARGS_MAP ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " port " , " stream_id " ) ;
std : : string vhost = DEFAULT_VHOST ;
if ( ! allArgs [ " vhost " ] . empty ( ) ) {
vhost = allArgs [ " vhost " ] ;
}
std : : string app = kRtpAppName ;
if ( ! allArgs [ " app " ] . empty ( ) ) {
app = allArgs [ " app " ] ;
}
auto stream_id = allArgs [ " stream_id " ] ;
auto tuple = MediaTuple { vhost , app , stream_id , " " } ;
auto tcp_mode = allArgs [ " tcp_mode " ] . as < int > ( ) ;
if ( allArgs [ " enable_tcp " ] . as < int > ( ) & & ! tcp_mode ) {
2024-09-19 14:53:50 +08:00
// 兼容老版本请求, 新版本去除enable_tcp参数并新增tcp_mode参数 [AUTO-TRANSLATED:b5f8f5df]
// Compatible with old version requests, the new version removes the enable_tcp parameter and adds the tcp_mode parameter
2024-07-09 10:42:10 +08:00
tcp_mode = 1 ;
}
auto only_track = allArgs [ " only_track " ] . as < int > ( ) ;
if ( allArgs [ " only_audio " ] . as < bool > ( ) ) {
2024-09-19 14:53:50 +08:00
// 兼容老版本请求, 新版本去除only_audio参数并新增only_track参数 [AUTO-TRANSLATED:a7a40942]
// Compatible with old version requests, the new version removes the only_audio parameter and adds the only_track parameter
2024-07-09 10:42:10 +08:00
only_track = 1 ;
}
std : : string local_ip = " :: " ;
if ( ! allArgs [ " local_ip " ] . empty ( ) ) {
local_ip = allArgs [ " local_ip " ] ;
}
auto port = openRtpServer ( allArgs [ " port " ] , tuple , tcp_mode , local_ip , true , 0 , only_track , true ) ;
if ( port = = 0 ) {
throw InvalidArgsException ( " This stream already exists " ) ;
}
2024-09-19 14:53:50 +08:00
// 回复json [AUTO-TRANSLATED:e80815cd]
// Reply json
2024-07-09 10:42:10 +08:00
val [ " port " ] = port ;
} ) ;
2020-07-02 22:23:43 +08:00
2022-09-09 10:56:28 +08:00
api_regist ( " /index/api/connectRtpServer " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " stream_id " , " dst_url " , " dst_port " ) ;
2024-03-10 05:31:20 -03:00
auto cb = [ val , headerOut , invoker ] ( const SockException & ex ) mutable {
if ( ex ) {
val [ " code " ] = API : : OtherFailed ;
val [ " msg " ] = ex . what ( ) ;
}
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ;
2024-07-09 10:42:10 +08:00
std : : string vhost = DEFAULT_VHOST ;
if ( ! allArgs [ " vhost " ] . empty ( ) ) {
vhost = allArgs [ " vhost " ] ;
}
std : : string app = kRtpAppName ;
if ( ! allArgs [ " app " ] . empty ( ) ) {
app = allArgs [ " app " ] ;
}
auto stream_id = allArgs [ " stream_id " ] ;
auto tuple = MediaTuple { vhost , app , stream_id , " " } ;
auto server = s_rtp_server . find ( tuple . shortUrl ( ) ) ;
2024-03-10 05:31:20 -03:00
if ( ! server ) {
2024-07-09 10:42:10 +08:00
cb ( SockException ( Err_other , " can not find the stream " ) ) ;
2024-03-10 05:31:20 -03:00
return ;
}
server - > connectToServer ( allArgs [ " dst_url " ] , allArgs [ " dst_port " ] , cb ) ;
2022-09-09 10:56:28 +08:00
} ) ;
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/closeRtpServer " , [ ] ( API_ARGS_MAP ) {
2020-07-02 22:23:43 +08:00
CHECK_SECRET ( ) ;
2020-07-09 10:38:57 +08:00
CHECK_ARGS ( " stream_id " ) ;
2020-07-02 22:23:43 +08:00
2024-07-09 10:42:10 +08:00
std : : string vhost = DEFAULT_VHOST ;
if ( ! allArgs [ " vhost " ] . empty ( ) ) {
vhost = allArgs [ " vhost " ] ;
}
std : : string app = kRtpAppName ;
if ( ! allArgs [ " app " ] . empty ( ) ) {
app = allArgs [ " app " ] ;
}
auto stream_id = allArgs [ " stream_id " ] ;
auto tuple = MediaTuple { vhost , app , stream_id , " " } ;
if ( s_rtp_server . erase ( tuple . shortUrl ( ) ) = = 0 ) {
2020-08-08 12:20:44 +08:00
val [ " hit " ] = 0 ;
return ;
}
val [ " hit " ] = 1 ;
2020-07-02 22:23:43 +08:00
} ) ;
2023-04-21 23:08:48 +08:00
api_regist ( " /index/api/updateRtpServerSSRC " , [ ] ( API_ARGS_MAP ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " stream_id " , " ssrc " ) ;
2024-07-09 10:42:10 +08:00
std : : string vhost = DEFAULT_VHOST ;
if ( ! allArgs [ " vhost " ] . empty ( ) ) {
vhost = allArgs [ " vhost " ] ;
}
std : : string app = kRtpAppName ;
if ( ! allArgs [ " app " ] . empty ( ) ) {
app = allArgs [ " app " ] ;
}
auto stream_id = allArgs [ " stream_id " ] ;
auto tuple = MediaTuple { vhost , app , stream_id , " " } ;
auto server = s_rtp_server . find ( tuple . shortUrl ( ) ) ;
2024-03-10 05:31:20 -03:00
if ( ! server ) {
2023-04-21 23:08:48 +08:00
throw ApiRetException ( " RtpServer not found by stream_id " , API : : NotFound ) ;
}
2024-03-10 05:31:20 -03:00
server - > updateSSRC ( allArgs [ " ssrc " ] ) ;
2023-04-21 23:08:48 +08:00
} ) ;
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/listRtpServer " , [ ] ( API_ARGS_MAP ) {
2020-07-02 22:26:38 +08:00
CHECK_SECRET ( ) ;
2025-09-08 19:00:45 +08:00
s_rtp_server . for_each ( [ & val ] ( const std : : string & key , const RtpServer : : Ptr & rtps ) {
auto vec = split ( key , " / " ) ;
2020-07-09 10:38:57 +08:00
Value obj ;
2024-07-09 10:42:10 +08:00
obj [ " vhost " ] = vec [ 0 ] ;
obj [ " app " ] = vec [ 1 ] ;
obj [ " stream_id " ] = vec [ 2 ] ;
2024-11-01 10:47:18 +08:00
obj [ " port " ] = rtps - > getPort ( ) ;
obj [ " ssrc " ] = rtps - > getSSRC ( ) ;
obj [ " tcp_mode " ] = rtps - > getTcpMode ( ) ;
obj [ " only_track " ] = rtps - > getOnlyTrack ( ) ;
2020-07-09 10:38:57 +08:00
val [ " data " ] . append ( obj ) ;
2025-09-08 19:00:45 +08:00
} ) ;
2020-07-02 22:26:38 +08:00
} ) ;
2020-07-02 22:23:43 +08:00
2024-07-28 22:55:33 +08:00
static auto start_send_rtp = [ ] ( bool passive , API_ARGS_MAP_ASYNC ) {
2021-10-21 10:21:52 +08:00
auto src = MediaSource : : find ( allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] , allArgs [ " from_mp4 " ] . as < int > ( ) ) ;
2021-08-12 16:07:31 +08:00
if ( ! src ) {
2022-06-11 14:17:43 +08:00
throw ApiRetException ( " can not find the source stream " , API : : NotFound ) ;
2020-09-06 17:56:05 +08:00
}
2024-04-10 13:14:53 +08:00
auto type = allArgs [ " type " ] . empty ( ) ? ( int ) MediaSourceEvent : : SendRtpArgs : : kRtpPS : allArgs [ " type " ] . as < int > ( ) ;
2024-03-02 16:53:53 +08:00
if ( ! allArgs [ " use_ps " ] . empty ( ) ) {
2024-09-19 14:53:50 +08:00
// 兼容之前的use_ps参数 [AUTO-TRANSLATED:0193f489]
// Compatible with the previous use_ps parameter
2024-03-10 16:17:29 +08:00
type = allArgs [ " use_ps " ] . as < int > ( ) ;
2024-03-02 16:53:53 +08:00
}
2022-04-03 18:25:36 +08:00
MediaSourceEvent : : SendRtpArgs args ;
2024-07-28 22:55:33 +08:00
if ( passive ) {
args . con_type = allArgs [ " is_udp " ] . as < bool > ( ) ? mediakit : : MediaSourceEvent : : SendRtpArgs : : kUdpPassive : mediakit : : MediaSourceEvent : : SendRtpArgs : : kTcpPassive ;
} else {
args . con_type = allArgs [ " is_udp " ] . as < bool > ( ) ? mediakit : : MediaSourceEvent : : SendRtpArgs : : kUdpActive : mediakit : : MediaSourceEvent : : SendRtpArgs : : kTcpActive ;
}
2022-04-03 18:25:36 +08:00
args . dst_url = allArgs [ " dst_url " ] ;
args . dst_port = allArgs [ " dst_port " ] ;
2023-11-07 23:38:58 +08:00
args . ssrc_multi_send = allArgs [ " ssrc_multi_send " ] . empty ( ) ? false : allArgs [ " ssrc_multi_send " ] . as < bool > ( ) ;
2022-04-03 18:25:36 +08:00
args . ssrc = allArgs [ " ssrc " ] ;
args . src_port = allArgs [ " src_port " ] ;
args . pt = allArgs [ " pt " ] . empty ( ) ? 96 : allArgs [ " pt " ] . as < int > ( ) ;
2024-07-28 22:55:33 +08:00
args . data_type = ( MediaSourceEvent : : SendRtpArgs : : DataType ) type ;
2022-08-20 12:48:27 +08:00
args . only_audio = allArgs [ " only_audio " ] . as < bool > ( ) ;
args . udp_rtcp_timeout = allArgs [ " udp_rtcp_timeout " ] ;
2023-01-07 22:36:30 +08:00
args . recv_stream_id = allArgs [ " recv_stream_id " ] ;
2024-07-28 22:55:33 +08:00
args . close_delay_ms = allArgs [ " close_delay_ms " ] ;
2024-09-19 14:53:50 +08:00
// 记录发送流的app和vhost [AUTO-TRANSLATED:ee1b41d5]
// Record the app and vhost of the sending stream
2024-08-22 19:50:29 +08:00
args . recv_stream_app = allArgs [ " app " ] ;
2024-08-28 12:28:01 +08:00
args . recv_stream_vhost = allArgs [ " vhost " ] ;
2025-08-02 21:14:42 +08:00
args . enable_origin_recv_limit = allArgs [ " enable_origin_recv_limit " ] ;
2022-06-11 14:17:43 +08:00
src - > getOwnerPoller ( ) - > async ( [ = ] ( ) mutable {
2024-07-28 22:55:33 +08:00
try {
src - > startSendRtp ( args , [ val , headerOut , invoker ] ( uint16_t local_port , const SockException & ex ) mutable {
if ( ex ) {
val [ " code " ] = API : : OtherFailed ;
val [ " msg " ] = ex . what ( ) ;
}
val [ " local_port " ] = local_port ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
} catch ( std : : exception & ex ) {
val [ " code " ] = API : : Exception ;
val [ " msg " ] = ex . what ( ) ;
2022-06-11 14:17:43 +08:00
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
2024-07-28 22:55:33 +08:00
}
2022-04-03 18:25:36 +08:00
} ) ;
2024-07-28 22:55:33 +08:00
} ;
api_regist ( " /index/api/startSendRtp " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " vhost " , " app " , " stream " , " ssrc " , " dst_url " , " dst_port " , " is_udp " ) ;
start_send_rtp ( false , API_ARGS_VALUE , invoker ) ;
} ) ;
api_regist ( " /index/api/startSendRtpPassive " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " vhost " , " app " , " stream " , " ssrc " ) ;
start_send_rtp ( true , API_ARGS_VALUE , invoker ) ;
2020-09-06 17:56:05 +08:00
} ) ;
2024-11-09 19:17:54 +08:00
api_regist ( " /index/api/startSendRtpTalk " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " vhost " , " app " , " stream " , " ssrc " , " recv_stream_id " ) ;
auto src = MediaSource : : find ( allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] , allArgs [ " from_mp4 " ] . as < int > ( ) ) ;
if ( ! src ) {
throw ApiRetException ( " can not find the source stream " , API : : NotFound ) ;
}
MediaSourceEvent : : SendRtpArgs args ;
args . con_type = mediakit : : MediaSourceEvent : : SendRtpArgs : : kVoiceTalk ;
args . ssrc = allArgs [ " ssrc " ] ;
args . pt = allArgs [ " pt " ] . empty ( ) ? 96 : allArgs [ " pt " ] . as < int > ( ) ;
args . data_type = allArgs [ " type " ] . empty ( ) ? MediaSourceEvent : : SendRtpArgs : : kRtpPS : ( MediaSourceEvent : : SendRtpArgs : : DataType ) ( allArgs [ " type " ] . as < int > ( ) ) ;
args . only_audio = allArgs [ " only_audio " ] . as < bool > ( ) ;
args . recv_stream_id = allArgs [ " recv_stream_id " ] ;
args . recv_stream_app = allArgs [ " app " ] ;
args . recv_stream_vhost = allArgs [ " vhost " ] ;
2025-08-02 21:14:42 +08:00
args . enable_origin_recv_limit = allArgs [ " enable_origin_recv_limit " ] ;
2024-11-09 19:17:54 +08:00
src - > getOwnerPoller ( ) - > async ( [ = ] ( ) mutable {
try {
src - > startSendRtp ( args , [ val , headerOut , invoker ] ( uint16_t local_port , const SockException & ex ) mutable {
if ( ex ) {
val [ " code " ] = API : : OtherFailed ;
val [ " msg " ] = ex . what ( ) ;
}
val [ " local_port " ] = local_port ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
} catch ( std : : exception & ex ) {
val [ " code " ] = API : : Exception ;
val [ " msg " ] = ex . what ( ) ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
}
} ) ;
} ) ;
2024-04-21 11:31:43 +08:00
api_regist ( " /index/api/listRtpSender " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " vhost " , " app " , " stream " ) ;
auto src = MediaSource : : find ( allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] ) ;
if ( ! src ) {
throw ApiRetException ( " can not find the source stream " , API : : NotFound ) ;
}
auto muxer = src - > getMuxer ( ) ;
CHECK ( muxer , " get muxer from media source failed " ) ;
src - > getOwnerPoller ( ) - > async ( [ = ] ( ) mutable {
2025-05-02 16:23:25 +08:00
muxer - > forEachRtpSender ( [ & ] ( const std : : string & ssrc , const RtpSender & sender ) mutable {
2024-04-21 11:31:43 +08:00
val [ " data " ] . append ( ssrc ) ;
2025-05-02 16:23:25 +08:00
val [ " bytesSpeed " ] = ( Json : : UInt64 ) sender . getSendSpeed ( ) ;
val [ " totalBytes " ] = ( Json : : UInt64 ) sender . getSendTotalBytes ( ) ;
2024-04-21 11:31:43 +08:00
} ) ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
} ) ;
2022-06-11 14:17:43 +08:00
api_regist ( " /index/api/stopSendRtp " , [ ] ( API_ARGS_MAP_ASYNC ) {
2020-09-06 17:56:05 +08:00
CHECK_SECRET ( ) ;
CHECK_ARGS ( " vhost " , " app " , " stream " ) ;
2020-09-06 18:19:54 +08:00
auto src = MediaSource : : find ( allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] ) ;
2020-09-06 17:56:05 +08:00
if ( ! src ) {
2022-06-11 14:17:43 +08:00
throw ApiRetException ( " can not find the stream " , API : : NotFound ) ;
2020-09-06 17:56:05 +08:00
}
2022-06-11 14:17:43 +08:00
src - > getOwnerPoller ( ) - > async ( [ = ] ( ) mutable {
2024-09-19 14:53:50 +08:00
// ssrc如果为空, 关闭全部 [AUTO-TRANSLATED:e0955dab]
// If ssrc is empty, close all
2022-06-11 14:17:43 +08:00
if ( ! src - > stopSendRtp ( allArgs [ " ssrc " ] ) ) {
val [ " code " ] = API : : OtherFailed ;
val [ " msg " ] = " stopSendRtp failed " ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
return ;
}
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
2020-09-06 17:56:05 +08:00
} ) ;
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/pauseRtpCheck " , [ ] ( API_ARGS_MAP ) {
2020-12-25 16:05:38 +08:00
CHECK_SECRET ( ) ;
CHECK_ARGS ( " stream_id " ) ;
2024-07-09 10:42:10 +08:00
std : : string vhost = DEFAULT_VHOST ;
if ( ! allArgs [ " vhost " ] . empty ( ) ) {
vhost = allArgs [ " vhost " ] ;
}
std : : string app = kRtpAppName ;
if ( ! allArgs [ " app " ] . empty ( ) ) {
app = allArgs [ " app " ] ;
}
2024-09-19 14:53:50 +08:00
// 只是暂停流的检查, 流媒体服务器做为流负载服务, 收流就转发, RTSP/RTMP有自己暂停协议 [AUTO-TRANSLATED:dda6ee31]
// Only pause the stream check, the media server acts as a stream load balancing service, receiving the stream and forwarding it, RTSP/RTMP has its own pause protocol
2024-07-09 10:42:10 +08:00
auto src = MediaSource : : find ( vhost , app , allArgs [ " stream_id " ] ) ;
2024-06-09 10:52:10 +08:00
auto process = src ? src - > getRtpProcess ( ) : nullptr ;
if ( process ) {
2025-09-09 23:11:19 +08:00
process - > pauseRtpTimeout ( true , allArgs [ " pause_seconds " ] ) ;
2021-03-16 11:30:51 +08:00
} else {
val [ " code " ] = API : : NotFound ;
2020-12-25 16:05:38 +08:00
}
} ) ;
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/resumeRtpCheck " , [ ] ( API_ARGS_MAP ) {
2020-12-25 16:05:38 +08:00
CHECK_SECRET ( ) ;
CHECK_ARGS ( " stream_id " ) ;
2024-07-09 10:42:10 +08:00
std : : string vhost = DEFAULT_VHOST ;
if ( ! allArgs [ " vhost " ] . empty ( ) ) {
vhost = allArgs [ " vhost " ] ;
}
std : : string app = kRtpAppName ;
if ( ! allArgs [ " app " ] . empty ( ) ) {
app = allArgs [ " app " ] ;
}
auto src = MediaSource : : find ( vhost , app , allArgs [ " stream_id " ] ) ;
2024-06-09 10:52:10 +08:00
auto process = src ? src - > getRtpProcess ( ) : nullptr ;
if ( process ) {
2025-09-09 23:11:19 +08:00
process - > pauseRtpTimeout ( false ) ;
2021-03-16 11:30:51 +08:00
} else {
val [ " code " ] = API : : NotFound ;
2020-12-25 16:05:38 +08:00
}
} ) ;
2020-09-06 17:56:05 +08:00
2019-12-06 11:54:10 +08:00
# endif //ENABLE_RTPPROXY
2019-12-05 19:53:55 +08:00
2024-09-19 14:53:50 +08:00
// 开始录制hls或MP4 [AUTO-TRANSLATED:0818775e]
// Start recording hls or MP4
2022-06-11 14:17:43 +08:00
api_regist ( " /index/api/startRecord " , [ ] ( API_ARGS_MAP_ASYNC ) {
2020-04-05 09:26:29 +08:00
CHECK_SECRET ( ) ;
CHECK_ARGS ( " type " , " vhost " , " app " , " stream " ) ;
2022-06-11 14:17:43 +08:00
auto src = MediaSource : : find ( allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] ) ;
if ( ! src ) {
throw ApiRetException ( " can not find the stream " , API : : NotFound ) ;
}
src - > getOwnerPoller ( ) - > async ( [ = ] ( ) mutable {
auto result = src - > setupRecord ( ( Recorder : : type ) allArgs [ " type " ] . as < int > ( ) , true , allArgs [ " customized_path " ] , allArgs [ " max_second " ] . as < size_t > ( ) ) ;
val [ " result " ] = result ;
val [ " code " ] = result ? API : : Success : API : : OtherFailed ;
val [ " msg " ] = result ? " success " : " start record failed " ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
2020-04-05 09:26:29 +08:00
} ) ;
2023-04-01 23:54:11 +08:00
2025-08-02 20:47:49 +08:00
api_regist ( " /index/api/startRecordTask " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " vhost " , " app " , " stream " , " path " , " back_ms " , " forward_ms " ) ;
auto src = MediaSource : : find ( allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] ) ;
if ( ! src ) {
throw ApiRetException ( " can not find the stream " , API : : NotFound ) ;
}
src - > getOwnerPoller ( ) - > async ( [ = ] ( ) mutable {
std : : string err ;
std : : string path ;
try {
path = src - > getMuxer ( ) - > startRecord ( allArgs [ " path " ] , allArgs [ " back_ms " ] , allArgs [ " forward_ms " ] ) ;
} catch ( std : : exception & ex ) {
err = ex . what ( ) ;
}
val [ " code " ] = err . empty ( ) ? API : : Success : API : : OtherFailed ;
val [ " data " ] [ " path " ] = path ;
val [ " msg " ] = err ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
} ) ;
2024-09-19 14:53:50 +08:00
// 设置录像流播放速度 [AUTO-TRANSLATED:a8d82298]
// Set the playback speed of the recording stream
2022-06-11 14:17:43 +08:00
api_regist ( " /index/api/setRecordSpeed " , [ ] ( API_ARGS_MAP_ASYNC ) {
2021-10-25 15:13:21 +08:00
CHECK_SECRET ( ) ;
CHECK_ARGS ( " schema " , " vhost " , " app " , " stream " , " speed " ) ;
auto src = MediaSource : : find ( allArgs [ " schema " ] ,
allArgs [ " vhost " ] ,
allArgs [ " app " ] ,
allArgs [ " stream " ] ) ;
2022-06-11 14:17:43 +08:00
if ( ! src ) {
throw ApiRetException ( " can not find the stream " , API : : NotFound ) ;
}
auto speed = allArgs [ " speed " ] . as < float > ( ) ;
src - > getOwnerPoller ( ) - > async ( [ = ] ( ) mutable {
bool flag = src - > speed ( speed ) ;
2021-10-25 15:13:21 +08:00
val [ " result " ] = flag ? 0 : - 1 ;
val [ " msg " ] = flag ? " success " : " set failed " ;
val [ " code " ] = flag ? API : : Success : API : : OtherFailed ;
2022-06-11 14:17:43 +08:00
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
2021-10-25 15:13:21 +08:00
} ) ;
2022-06-11 14:17:43 +08:00
api_regist ( " /index/api/seekRecordStamp " , [ ] ( API_ARGS_MAP_ASYNC ) {
2021-10-25 15:13:21 +08:00
CHECK_SECRET ( ) ;
CHECK_ARGS ( " schema " , " vhost " , " app " , " stream " , " stamp " ) ;
auto src = MediaSource : : find ( allArgs [ " schema " ] ,
allArgs [ " vhost " ] ,
allArgs [ " app " ] ,
allArgs [ " stream " ] ) ;
2022-06-11 14:17:43 +08:00
if ( ! src ) {
throw ApiRetException ( " can not find the stream " , API : : NotFound ) ;
}
auto stamp = allArgs [ " stamp " ] . as < size_t > ( ) ;
src - > getOwnerPoller ( ) - > async ( [ = ] ( ) mutable {
bool flag = src - > seekTo ( stamp ) ;
2021-10-25 15:13:21 +08:00
val [ " result " ] = flag ? 0 : - 1 ;
val [ " msg " ] = flag ? " success " : " seek failed " ;
val [ " code " ] = flag ? API : : Success : API : : OtherFailed ;
2022-06-11 14:17:43 +08:00
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
2021-10-25 15:13:21 +08:00
} ) ;
2020-04-05 09:26:29 +08:00
2024-09-19 14:53:50 +08:00
// 停止录制hls或MP4 [AUTO-TRANSLATED:24d11a0c]
// Stop recording hls or MP4
2022-06-11 14:17:43 +08:00
api_regist ( " /index/api/stopRecord " , [ ] ( API_ARGS_MAP_ASYNC ) {
2020-04-05 09:26:29 +08:00
CHECK_SECRET ( ) ;
CHECK_ARGS ( " type " , " vhost " , " app " , " stream " ) ;
2022-06-11 14:17:43 +08:00
auto src = MediaSource : : find ( allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] ) ;
if ( ! src ) {
throw ApiRetException ( " can not find the stream " , API : : NotFound ) ;
}
auto type = ( Recorder : : type ) allArgs [ " type " ] . as < int > ( ) ;
src - > getOwnerPoller ( ) - > async ( [ = ] ( ) mutable {
auto result = src - > setupRecord ( type , false , " " , 0 ) ;
val [ " result " ] = result ;
val [ " code " ] = result ? API : : Success : API : : OtherFailed ;
val [ " msg " ] = result ? " success " : " stop record failed " ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
2020-04-05 09:26:29 +08:00
} ) ;
2024-09-19 14:53:50 +08:00
// 获取hls或MP4录制状态 [AUTO-TRANSLATED:a08a2f1a]
// Get the recording status of hls or MP4
2022-06-11 14:17:43 +08:00
api_regist ( " /index/api/isRecording " , [ ] ( API_ARGS_MAP_ASYNC ) {
2020-04-05 09:26:29 +08:00
CHECK_SECRET ( ) ;
CHECK_ARGS ( " type " , " vhost " , " app " , " stream " ) ;
2022-06-11 14:17:43 +08:00
auto src = MediaSource : : find ( allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] ) ;
if ( ! src ) {
throw ApiRetException ( " can not find the stream " , API : : NotFound ) ;
}
auto type = ( Recorder : : type ) allArgs [ " type " ] . as < int > ( ) ;
src - > getOwnerPoller ( ) - > async ( [ = ] ( ) mutable {
val [ " status " ] = src - > isRecording ( type ) ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
2020-04-05 09:26:29 +08:00
} ) ;
2023-04-01 23:54:11 +08:00
2023-05-03 18:52:11 +08:00
api_regist ( " /index/api/getProxyPusherInfo " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " key " ) ;
2024-03-10 05:31:20 -03:00
auto pusher = s_pusher_proxy . find ( allArgs [ " key " ] ) ;
if ( ! pusher ) {
2023-05-03 18:52:11 +08:00
throw ApiRetException ( " can not find pusher " , API : : NotFound ) ;
}
2024-11-01 10:47:18 +08:00
val [ " data " ] = ToJson ( pusher ) ;
2023-05-03 18:52:11 +08:00
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
api_regist ( " /index/api/getProxyInfo " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " key " ) ;
2024-03-10 05:31:20 -03:00
auto proxy = s_player_proxy . find ( allArgs [ " key " ] ) ;
if ( ! proxy ) {
2023-05-03 18:52:11 +08:00
throw ApiRetException ( " can not find the proxy " , API : : NotFound ) ;
}
2024-11-01 10:47:18 +08:00
val [ " data " ] = ToJson ( proxy ) ;
2023-05-03 18:52:11 +08:00
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
2024-09-19 14:53:50 +08:00
// 删除录像文件夹 [AUTO-TRANSLATED:821aed07]
// Delete the recording folder
2022-08-04 10:15:07 +08:00
// http://127.0.0.1/index/api/deleteRecordDirectroy?vhost=__defaultVhost__&app=live&stream=ss&period=2020-01-01
2022-08-05 16:01:31 +08:00
api_regist ( " /index/api/deleteRecordDirectory " , [ ] ( API_ARGS_MAP ) {
2022-08-04 10:15:07 +08:00
CHECK_SECRET ( ) ;
2025-08-02 20:47:49 +08:00
CHECK_ARGS ( " vhost " , " app " , " stream " ) ;
2024-03-30 14:59:28 +08:00
auto tuple = MediaTuple { allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] , " " } ;
2023-05-25 16:23:24 +08:00
auto record_path = Recorder : : getRecordPath ( Recorder : : type_mp4 , tuple , allArgs [ " customized_path " ] ) ;
2022-08-04 10:15:07 +08:00
auto period = allArgs [ " period " ] ;
2025-08-02 20:47:49 +08:00
if ( ! period . empty ( ) ) {
record_path = record_path + period + " / " ;
}
2024-05-15 11:30:16 +08:00
bool recording = false ;
2023-10-28 10:22:43 +08:00
auto name = allArgs [ " name " ] ;
if ( ! name . empty ( ) ) {
2024-09-19 14:53:50 +08:00
// 删除指定文件 [AUTO-TRANSLATED:e8ee7bfa]
// Delete the specified file
2023-10-28 10:22:43 +08:00
record_path + = name ;
2024-05-15 11:30:16 +08:00
} else {
2024-09-19 14:53:50 +08:00
// 删除文件夹,先判断该流是否正在录制中 [AUTO-TRANSLATED:9f124786]
// Delete the folder, first check if the stream is being recorded
2023-12-02 15:22:16 +08:00
auto src = MediaSource : : find ( allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] ) ;
if ( src & & src - > isRecording ( Recorder : : type_mp4 ) ) {
recording = true ;
}
2022-08-04 10:15:07 +08:00
}
2022-08-05 16:01:31 +08:00
val [ " path " ] = record_path ;
2023-12-02 15:22:16 +08:00
if ( ! recording ) {
2023-12-02 21:12:26 +08:00
val [ " code " ] = File : : delete_file ( record_path , true ) ;
2023-12-02 15:22:16 +08:00
return ;
}
File : : scanDir ( record_path , [ ] ( const string & path , bool is_dir ) {
if ( is_dir ) {
return true ;
}
2023-12-02 21:12:26 +08:00
if ( path . find ( " /. " ) = = std : : string : : npos ) {
2023-12-02 19:49:28 +08:00
File : : delete_file ( path ) ;
2023-12-02 15:22:16 +08:00
} else {
TraceL < < " Ignore tmp mp4 file: " < < path ;
}
return true ;
2023-12-02 21:12:26 +08:00
} , true , true ) ;
File : : deleteEmptyDir ( record_path ) ;
2022-08-04 10:15:07 +08:00
} ) ;
2023-04-01 23:54:11 +08:00
2025-08-02 20:45:36 +08:00
api_regist ( " /index/api/deleteSnapDirectory " , [ ] ( API_ARGS_MAP ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " vhost " , " app " , " stream " ) ;
GET_CONFIG ( std : : string , root , API : : kSnapRoot ) ;
auto path = File : : absolutePath ( allArgs [ " vhost " ] + " / " + allArgs [ " app " ] + " / " + allArgs [ " stream " ] + " / " + allArgs [ " file " ] , root ) ;
InfoL < < " delete " < < path ;
File : : delete_file ( path , true ) ;
} ) ;
2024-09-19 14:53:50 +08:00
// 获取录像文件夹列表或mp4文件列表 [AUTO-TRANSLATED:f7e299bc]
// Get the list of recording folders or mp4 files
2023-12-02 14:56:59 +08:00
//http://127.0.0.1/index/api/getMP4RecordFile?vhost=__defaultVhost__&app=live&stream=ss&period=2020-01
api_regist ( " /index/api/getMP4RecordFile " , [ ] ( API_ARGS_MAP ) {
2020-02-01 23:26:33 +08:00
CHECK_SECRET ( ) ;
CHECK_ARGS ( " vhost " , " app " , " stream " ) ;
2024-03-30 14:59:28 +08:00
auto tuple = MediaTuple { allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] , " " } ;
2023-05-25 16:23:24 +08:00
auto record_path = Recorder : : getRecordPath ( Recorder : : type_mp4 , tuple , allArgs [ " customized_path " ] ) ;
2020-02-01 23:26:33 +08:00
auto period = allArgs [ " period " ] ;
2024-09-19 14:53:50 +08:00
// 判断是获取mp4文件列表还是获取文件夹列表 [AUTO-TRANSLATED:b9c86d2f]
// Determine whether to get the mp4 file list or the folder list
2020-02-01 23:26:33 +08:00
bool search_mp4 = period . size ( ) = = sizeof ( " 2020-02-01 " ) - 1 ;
if ( search_mp4 ) {
record_path = record_path + period + " / " ;
}
Json : : Value paths ( arrayValue ) ;
2024-09-19 14:53:50 +08:00
// 这是筛选日期,获取文件夹列表 [AUTO-TRANSLATED:786fa49d]
// This is to filter the date and get the folder list
2020-02-01 23:26:33 +08:00
File : : scanDir ( record_path , [ & ] ( const string & path , bool isDir ) {
2021-01-17 18:31:50 +08:00
auto pos = path . rfind ( ' / ' ) ;
2020-02-01 23:26:33 +08:00
if ( pos ! = string : : npos ) {
string relative_path = path . substr ( pos + 1 ) ;
if ( search_mp4 ) {
if ( ! isDir ) {
2024-09-19 14:53:50 +08:00
// 我们只收集mp4文件, 对文件夹不感兴趣 [AUTO-TRANSLATED:254d9f25]
// We only collect mp4 files, we are not interested in folders
2020-02-01 23:26:33 +08:00
paths . append ( relative_path ) ;
}
} else if ( isDir & & relative_path . find ( period ) = = 0 ) {
2024-09-19 14:53:50 +08:00
// 匹配到对应日期的文件夹 [AUTO-TRANSLATED:cd3d10b9]
// Match the folder for the corresponding date
2020-02-01 23:26:33 +08:00
paths . append ( relative_path ) ;
}
}
return true ;
} , false ) ;
2020-01-19 14:54:31 +08:00
2020-02-01 23:26:33 +08:00
val [ " data " ] [ " rootPath " ] = record_path ;
val [ " data " ] [ " paths " ] = paths ;
2020-03-20 11:51:24 +08:00
} ) ;
2020-01-19 14:54:31 +08:00
2020-06-10 10:50:37 +08:00
static auto responseSnap = [ ] ( const string & snap_path ,
const HttpSession : : KeyValue & headerIn ,
2022-02-24 11:28:48 +08:00
const HttpSession : : HttpResponseInvoker & invoker ,
const string & err_msg = " " ) {
static bool s_snap_success_once = false ;
2020-06-10 10:50:37 +08:00
StrCaseMap headerOut ;
GET_CONFIG ( string , defaultSnap , API : : kDefaultSnap ) ;
2023-12-02 19:49:28 +08:00
if ( ! File : : fileSize ( snap_path ) ) {
2022-02-24 11:28:48 +08:00
if ( ! err_msg . empty ( ) & & ( ! s_snap_success_once | | defaultSnap . empty ( ) ) ) {
2024-09-19 14:53:50 +08:00
// 重来没截图成功过或者默认截图图片为空, 那么直接返回FFmpeg错误日志 [AUTO-TRANSLATED:5bde510f]
// If the screenshot has never been successful or the default screenshot image is empty, then directly return the FFmpeg error log
2022-02-24 11:28:48 +08:00
headerOut [ " Content-Type " ] = HttpFileManager : : getContentType ( " .txt " ) ;
invoker . responseFile ( headerIn , headerOut , err_msg , false , false ) ;
return ;
}
2024-09-19 14:53:50 +08:00
// 截图成功过一次,那么认为配置无错误,截图失败时,返回预设默认图片 [AUTO-TRANSLATED:ffe4d807]
// If the screenshot has been successful once, then it is considered that the configuration is error-free, and when the screenshot fails, the preset default image is returned
2022-02-24 11:28:48 +08:00
const_cast < string & > ( snap_path ) = File : : absolutePath ( " " , defaultSnap ) ;
2020-06-10 10:50:37 +08:00
headerOut [ " Content-Type " ] = HttpFileManager : : getContentType ( snap_path . data ( ) ) ;
} else {
2022-02-24 11:28:48 +08:00
s_snap_success_once = true ;
2024-09-19 14:53:50 +08:00
// 之前生成的截图文件, 我们默认为jpeg格式 [AUTO-TRANSLATED:5cc5c1ff]
// The previously generated screenshot file, we default to jpeg format
2020-06-10 10:50:37 +08:00
headerOut [ " Content-Type " ] = HttpFileManager : : getContentType ( " .jpeg " ) ;
}
2024-09-19 14:53:50 +08:00
// 返回图片给http客户端 [AUTO-TRANSLATED:58a1f64e]
// Return image to http client
2020-06-10 10:50:37 +08:00
invoker . responseFile ( headerIn , headerOut , snap_path ) ;
} ;
2020-05-09 00:06:36 +08:00
2024-09-19 14:53:50 +08:00
// 获取截图缓存或者实时截图 [AUTO-TRANSLATED:78e2fe1e]
// Get screenshot cache or real-time screenshot
2020-05-09 00:06:36 +08:00
//http://127.0.0.1/index/api/getSnap?url=rtmp://127.0.0.1/record/robot.mp4&timeout_sec=10&expire_sec=3
2020-12-27 22:14:59 +08:00
api_regist ( " /index/api/getSnap " , [ ] ( API_ARGS_MAP_ASYNC ) {
2020-05-09 00:06:36 +08:00
CHECK_SECRET ( ) ;
CHECK_ARGS ( " url " , " timeout_sec " , " expire_sec " ) ;
2020-06-10 10:50:37 +08:00
GET_CONFIG ( string , snap_root , API : : kSnapRoot ) ;
2020-09-26 10:07:22 +08:00
bool have_old_snap = false , res_old_snap = false ;
2020-05-09 00:06:36 +08:00
int expire_sec = allArgs [ " expire_sec " ] ;
2020-05-09 09:29:45 +08:00
auto scan_path = File : : absolutePath ( MD5 ( allArgs [ " url " ] ) . hexdigest ( ) , snap_root ) + " / " ;
2020-09-26 10:07:22 +08:00
string new_snap = StrPrinter < < scan_path < < time ( NULL ) < < " .jpeg " ;
2020-05-09 09:29:45 +08:00
File : : scanDir ( scan_path , [ & ] ( const string & path , bool isDir ) {
2020-09-26 10:07:22 +08:00
if ( isDir | | ! end_with ( path , " .jpeg " ) ) {
2024-09-19 14:53:50 +08:00
// 忽略文件夹或其他类型的文件 [AUTO-TRANSLATED:3ecffcae]
// Ignore folders or other types of files
2020-05-09 09:29:45 +08:00
return true ;
2020-05-09 00:06:36 +08:00
}
2020-05-09 09:29:45 +08:00
2024-09-19 14:53:50 +08:00
// 找到截图 [AUTO-TRANSLATED:b784cfec]
// Find screenshot
2023-06-10 12:28:49 +08:00
auto tm = findSubString ( path . data ( ) + scan_path . size ( ) , nullptr , " .jpeg " ) ;
2020-05-09 09:29:45 +08:00
if ( atoll ( tm . data ( ) ) + expire_sec < time ( NULL ) ) {
2024-09-19 14:53:50 +08:00
// 截图已经过期,改名,以便再次请求时,可以返回老截图 [AUTO-TRANSLATED:94fac79b]
// Screenshot has expired, rename it so that it can be returned when requested again
2020-09-26 10:07:22 +08:00
rename ( path . data ( ) , new_snap . data ( ) ) ;
have_old_snap = true ;
2020-05-09 09:29:45 +08:00
return true ;
}
2024-09-19 14:53:50 +08:00
// 截图存在,且未过期,那么返回之 [AUTO-TRANSLATED:6f53d3d1]
// Screenshot exists and has not expired, so return it
2020-09-26 10:07:22 +08:00
res_old_snap = true ;
2024-03-23 11:46:30 -03:00
responseSnap ( path , allArgs . parser . getHeader ( ) , invoker ) ;
2024-09-19 14:53:50 +08:00
// 中断遍历 [AUTO-TRANSLATED:7893aab3]
// Interrupt traversal
2020-05-09 09:29:45 +08:00
return false ;
2020-05-09 00:06:36 +08:00
} ) ;
2020-09-26 10:07:22 +08:00
if ( res_old_snap ) {
2024-09-19 14:53:50 +08:00
// 已经回复了旧的截图 [AUTO-TRANSLATED:9051a3e6]
// Old screenshot has been replied
2020-06-10 10:33:48 +08:00
return ;
2020-05-09 00:06:36 +08:00
}
2024-09-19 14:53:50 +08:00
// 无截图或者截图已经过期 [AUTO-TRANSLATED:89c46415]
// No screenshot or screenshot has expired
2020-09-26 10:07:22 +08:00
if ( ! have_old_snap ) {
2024-09-19 14:53:50 +08:00
// 无过期截图,生成一个空文件,目的是顺便创建文件夹路径 [AUTO-TRANSLATED:bdbfdbcb]
// No expired screenshot, generate an empty file, the purpose is to create the folder path by the way
// 同时防止在FFmpeg生成截图途中不停的尝试调用该api多次启动FFmpeg进程 [AUTO-TRANSLATED:a04e1ee2]
// At the same time, prevent the FFmpeg process from being started multiple times by continuously trying to call this API during the FFmpeg screenshot generation process
2023-12-02 19:49:28 +08:00
auto file = File : : create_file ( new_snap , " wb " ) ;
2020-09-26 10:07:22 +08:00
if ( file ) {
fclose ( file ) ;
}
2020-05-09 09:39:36 +08:00
}
2024-09-19 14:53:50 +08:00
// 启动FFmpeg进程, 开始截图, 生成临时文件, 截图成功后替换为正式文件 [AUTO-TRANSLATED:7d589e3f]
// Start the FFmpeg process, start taking screenshots, generate temporary files, replace them with formal files after successful screenshots
2020-09-26 10:07:22 +08:00
auto new_snap_tmp = new_snap + " .tmp " ;
2025-01-19 20:18:32 +08:00
FFmpegSnap : : makeSnap ( allArgs [ " async " ] , allArgs [ " url " ] , new_snap_tmp , allArgs [ " timeout_sec " ] , [ invoker , allArgs , new_snap , new_snap_tmp ] ( bool success , const string & err_msg ) {
2020-09-26 10:07:22 +08:00
if ( ! success ) {
2024-09-19 14:53:50 +08:00
// 生成截图失败,可能残留空文件 [AUTO-TRANSLATED:c96a4468]
// Screenshot generation failed, there may be residual empty files
2023-12-02 19:49:28 +08:00
File : : delete_file ( new_snap_tmp ) ;
2020-09-26 10:07:22 +08:00
} else {
2024-09-19 14:53:50 +08:00
// 临时文件改成正式文件 [AUTO-TRANSLATED:eca24dfd]
// Temporary file changed to formal file
2023-12-02 19:49:28 +08:00
File : : delete_file ( new_snap ) ;
2020-09-26 10:07:22 +08:00
rename ( new_snap_tmp . data ( ) , new_snap . data ( ) ) ;
2020-05-09 00:06:36 +08:00
}
2024-03-23 11:46:30 -03:00
responseSnap ( new_snap , allArgs . parser . getHeader ( ) , invoker , err_msg ) ;
2020-05-09 00:06:36 +08:00
} ) ;
} ) ;
2021-12-27 17:40:15 +08:00
api_regist ( " /index/api/getStatistic " , [ ] ( API_ARGS_MAP_ASYNC ) {
2021-01-23 09:44:37 +08:00
CHECK_SECRET ( ) ;
2021-12-27 17:40:15 +08:00
getStatisticJson ( [ headerOut , val , invoker ] ( const Value & data ) mutable {
val [ " data " ] = data ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
2021-01-23 09:44:37 +08:00
} ) ;
2021-03-24 16:52:41 +08:00
# ifdef ENABLE_WEBRTC
2021-10-19 15:23:12 +08:00
api_regist ( " /index/api/webrtc " , [ ] ( API_ARGS_STRING_ASYNC ) {
CHECK_ARGS ( " type " ) ;
auto type = allArgs [ " type " ] ;
2024-03-23 11:46:30 -03:00
auto offer = allArgs . args ;
2021-10-19 15:23:12 +08:00
CHECK ( ! offer . empty ( ) , " http body(webrtc offer sdp) is empty " ) ;
2024-03-23 11:46:30 -03:00
auto & session = static_cast < Session & > ( sender ) ;
2025-09-20 16:23:30 +08:00
auto args = std : : make_shared < WebRtcArgsImp < std : : string > > ( allArgs , sender . getIdentifier ( ) ) ;
2024-03-23 22:52:40 +08:00
WebRtcPluginManager : : Instance ( ) . negotiateSdp ( session , type , * args , [ invoker , val , offer , headerOut ] ( const WebRtcInterface & exchanger ) mutable {
2024-03-23 11:46:30 -03:00
auto & handler = const_cast < WebRtcInterface & > ( exchanger ) ;
2021-10-19 15:23:12 +08:00
try {
2024-03-23 11:46:30 -03:00
val [ " sdp " ] = handler . getAnswerSdp ( offer ) ;
2021-10-19 15:23:12 +08:00
val [ " id " ] = exchanger . getIdentifier ( ) ;
val [ " type " ] = " answer " ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} catch ( std : : exception & ex ) {
val [ " code " ] = API : : Exception ;
val [ " msg " ] = ex . what ( ) ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
}
} ) ;
2021-03-24 16:52:41 +08:00
} ) ;
2023-04-08 21:28:17 +08:00
2023-04-28 22:06:46 +08:00
static constexpr char delete_webrtc_url [ ] = " /index/api/delete_webrtc " ;
2023-04-08 21:28:17 +08:00
static auto whip_whep_func = [ ] ( const char * type , API_ARGS_STRING_ASYNC ) {
2024-03-23 11:46:30 -03:00
auto offer = allArgs . args ;
2023-04-08 21:28:17 +08:00
CHECK ( ! offer . empty ( ) , " http body(webrtc offer sdp) is empty " ) ;
2023-04-28 22:06:46 +08:00
auto & session = static_cast < Session & > ( sender ) ;
2024-03-23 11:46:30 -03:00
auto location = std : : string ( session . overSsl ( ) ? " https:// " : " http:// " ) + allArgs [ " host " ] + delete_webrtc_url ;
2025-09-20 16:23:30 +08:00
auto args = std : : make_shared < WebRtcArgsImp < std : : string > > ( allArgs , sender . getIdentifier ( ) ) ;
2024-03-23 22:52:40 +08:00
WebRtcPluginManager : : Instance ( ) . negotiateSdp ( session , type , * args , [ invoker , offer , headerOut , location ] ( const WebRtcInterface & exchanger ) mutable {
2024-03-23 11:46:30 -03:00
auto & handler = const_cast < WebRtcInterface & > ( exchanger ) ;
try {
2026-04-21 11:37:08 +08:00
// Encode query params since transport id/token may contain '+' or '/'.
HttpArgs delete_args ;
delete_args [ " id " ] = exchanger . getIdentifier ( ) ;
delete_args [ " token " ] = exchanger . deleteRandStr ( ) ;
2024-09-19 14:53:50 +08:00
// 设置返回类型 [AUTO-TRANSLATED:ffc2a31a]
// Set return type
2024-03-23 11:46:30 -03:00
headerOut [ " Content-Type " ] = " application/sdp " ;
2026-04-21 11:37:08 +08:00
headerOut [ " Location " ] = location + " ? " + delete_args . make ( ) ;
2024-03-23 11:46:30 -03:00
invoker ( 201 , headerOut , handler . getAnswerSdp ( offer ) ) ;
} catch ( std : : exception & ex ) {
headerOut [ " Content-Type " ] = " text/plain " ;
invoker ( 406 , headerOut , ex . what ( ) ) ;
}
} ) ;
2023-04-08 21:28:17 +08:00
} ;
api_regist ( " /index/api/whip " , [ ] ( API_ARGS_STRING_ASYNC ) { whip_whep_func ( " push " , API_ARGS_VALUE , invoker ) ; } ) ;
api_regist ( " /index/api/whep " , [ ] ( API_ARGS_STRING_ASYNC ) { whip_whep_func ( " play " , API_ARGS_VALUE , invoker ) ; } ) ;
2023-04-28 22:06:46 +08:00
api_regist ( delete_webrtc_url , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_ARGS ( " id " , " token " ) ;
2024-03-23 11:46:30 -03:00
CHECK ( allArgs . parser . method ( ) = = " DELETE " , " http method is not DELETE: " + allArgs . parser . method ( ) ) ;
2023-04-28 22:06:46 +08:00
auto obj = WebRtcTransportManager : : Instance ( ) . getItem ( allArgs [ " id " ] ) ;
if ( ! obj ) {
invoker ( 404 , headerOut , " id not found " ) ;
return ;
}
if ( obj - > deleteRandStr ( ) ! = allArgs [ " token " ] ) {
invoker ( 401 , headerOut , " token incorrect " ) ;
return ;
}
obj - > safeShutdown ( SockException ( Err_shutdown , " deleted by http api " ) ) ;
invoker ( 200 , headerOut , " " ) ;
} ) ;
2025-09-20 16:23:30 +08:00
// 获取WebRTCProxyPlayer 连接信息
api_regist ( " /index/api/getWebrtcProxyPlayerInfo " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " key " ) ;
auto player_proxy = s_player_proxy . find ( allArgs [ " key " ] ) ;
if ( ! player_proxy ) {
throw ApiRetException ( " Stream proxy not found " , API : : NotFound ) ;
}
auto media_player = player_proxy - > getDelegate ( ) ;
if ( ! media_player ) {
throw ApiRetException ( " Media player not found " , API : : OtherFailed ) ;
}
auto webrtc_player_imp = std : : dynamic_pointer_cast < WebRtcProxyPlayerImp > ( media_player ) ;
if ( ! webrtc_player_imp ) {
throw ApiRetException ( " Stream proxy is not WebRTC type " , API : : OtherFailed ) ;
}
auto webrtc_transport = webrtc_player_imp - > getWebRtcTransport ( ) ;
if ( ! webrtc_transport ) {
throw ApiRetException ( " WebRTC transport not available " , API : : OtherFailed ) ;
}
std : : string stream_key = allArgs [ " key " ] ;
webrtc_transport - > getTransportInfo ( [ val , headerOut , invoker , stream_key ] ( Json : : Value transport_info ) mutable {
transport_info [ " stream_key " ] = stream_key ;
if ( transport_info . isMember ( " error " ) ) {
Json : : Value error_val ;
error_val [ " code " ] = API : : OtherFailed ;
error_val [ " msg " ] = transport_info [ " error " ] . asString ( ) ;
invoker ( 200 , headerOut , error_val . toStyledString ( ) ) ;
return ;
}
// 成功返回结果
Json : : Value success_val ;
success_val [ " code " ] = API : : Success ;
success_val [ " msg " ] = " success " ;
success_val [ " data " ] = transport_info ;
invoker ( 200 , headerOut , success_val . toStyledString ( ) ) ;
} ) ;
} ) ;
api_regist ( " /index/api/addWebrtcRoomKeeper " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " server_host " , " server_port " , " room_id " , " ssl " ) ;
//server_host: 信令服务器host
//server_post: 信令服务器host
//room_id: 注册的id,信令服务器会对该id进行唯一性检查
addWebrtcRoomKeeper ( allArgs [ " server_host " ] , allArgs [ " server_port " ] , allArgs [ " room_id " ] , allArgs [ " ssl " ] ,
[ val , headerOut , invoker ] ( const SockException & ex , const string & key ) mutable {
if ( ex ) {
val [ " code " ] = API : : OtherFailed ;
val [ " msg " ] = ex . what ( ) ;
} else {
val [ " msg " ] = " success " ;
val [ " data " ] [ " room_key " ] = key ;
}
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
} ) ;
api_regist ( " /index/api/delWebrtcRoomKeeper " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " room_key " ) ;
delWebrtcRoomKeeper ( allArgs [ " room_key " ] ,
[ val , headerOut , invoker ] ( const SockException & ex ) mutable {
if ( ex ) {
val [ " code " ] = API : : OtherFailed ;
val [ " msg " ] = ex . what ( ) ;
}
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
} ) ;
api_regist ( " /index/api/listWebrtcRoomKeepers " , [ ] ( API_ARGS_MAP ) {
CHECK_SECRET ( ) ;
listWebrtcRoomKeepers ( [ & val ] ( const std : : string & key , const WebRtcSignalingPeer : : Ptr & p ) {
Json : : Value item = ToJson ( p ) ;
item [ " room_key " ] = key ;
val [ " data " ] . append ( item ) ;
} ) ;
} ) ;
api_regist ( " /index/api/listWebrtcRooms " , [ ] ( API_ARGS_MAP ) {
CHECK_SECRET ( ) ;
listWebrtcRooms ( [ & val ] ( const std : : string & key , const WebRtcSignalingSession : : Ptr & p ) {
Json : : Value item = ToJson ( p ) ;
item [ " room_id " ] = key ;
val [ " data " ] . append ( item ) ;
} ) ;
} ) ;
2021-03-24 16:52:41 +08:00
# endif
2022-08-12 18:09:44 +08:00
# if defined(ENABLE_VERSION)
api_regist ( " /index/api/version " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
Value ver ;
ver [ " buildTime " ] = BUILD_TIME ;
ver [ " branchName " ] = BRANCH_NAME ;
ver [ " commitHash " ] = COMMIT_HASH ;
val [ " data " ] = ver ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
# endif
2024-07-09 04:43:34 +02:00
# if ENABLE_MP4
2023-11-24 10:44:08 +08:00
api_regist ( " /index/api/loadMP4File " , [ ] ( API_ARGS_MAP ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " vhost " , " app " , " stream " , " file_path " ) ;
ProtocolOption option ;
2024-09-19 14:53:50 +08:00
// mp4支持多track [AUTO-TRANSLATED:b9688762]
// mp4 supports multiple tracks
2023-12-09 22:34:22 +08:00
option . max_track = 16 ;
2024-09-19 14:53:50 +08:00
// 默认解复用mp4不生成mp4 [AUTO-TRANSLATED:11f2dcee]
// By default, demultiplexing mp4 does not generate mp4
2023-11-24 10:44:08 +08:00
option . enable_mp4 = false ;
2024-09-19 14:53:50 +08:00
// 但是如果参数明确指定开启mp4, 那么也允许之 [AUTO-TRANSLATED:b143a9e3]
// But if the parameter explicitly specifies to enable mp4, then it is also allowed
2023-11-24 10:44:08 +08:00
option . load ( allArgs ) ;
2024-09-19 14:53:50 +08:00
// 强制无人观看时自动关闭 [AUTO-TRANSLATED:f7c85948]
// Force automatic shutdown when no one is watching
2023-11-24 10:44:08 +08:00
option . auto_close = true ;
2024-07-14 09:32:41 +08:00
auto tuple = MediaTuple { allArgs [ " vhost " ] , allArgs [ " app " ] , allArgs [ " stream " ] , " " } ;
auto reader = std : : make_shared < MP4Reader > ( tuple , allArgs [ " file_path " ] , option ) ;
2024-09-19 14:53:50 +08:00
// sample_ms设置为0, 从配置文件加载; file_repeat可以指定, 如果配置文件也指定循环解复用, 那么强制开启 [AUTO-TRANSLATED:23e826b4]
// sample_ms is set to 0, loaded from the configuration file; file_repeat can be specified, if the configuration file also specifies loop demultiplexing, then force it to be enabled
2023-11-24 10:44:08 +08:00
reader - > startReadMP4 ( 0 , true , allArgs [ " file_repeat " ] ) ;
2025-10-18 12:36:46 +08:00
auto seek_ms = allArgs [ " seek_ms " ] . as < uint32_t > ( ) ;
auto speed = allArgs [ " speed " ] . as < float > ( ) ;
if ( seek_ms | | speed ) {
auto p = static_pointer_cast < MediaSourceEvent > ( reader ) ;
p - > getOwnerPoller ( MediaSource : : NullMediaSource ( ) ) - > async ( [ seek_ms , speed , p ] ( ) {
if ( seek_ms ) {
p - > seekTo ( MediaSource : : NullMediaSource ( ) , seek_ms ) ;
}
if ( speed & & speed ! = 1.0 ) {
p - > speed ( MediaSource : : NullMediaSource ( ) , speed ) ;
}
} ) ;
}
2025-04-26 19:58:48 +08:00
val [ " data " ] [ " duration_ms " ] = ( Json : : UInt64 ) reader - > getDemuxer ( ) - > getDurationMS ( ) ;
2023-11-24 10:44:08 +08:00
} ) ;
2024-07-09 04:43:34 +02:00
# endif
2023-12-02 15:58:40 +08:00
GET_CONFIG_FUNC ( std : : set < std : : string > , download_roots , API : : kDownloadRoot , [ ] ( const string & str ) - > std : : set < std : : string > {
std : : set < std : : string > ret ;
auto vec = toolkit : : split ( str , " ; " ) ;
for ( auto & item : vec ) {
2024-03-15 22:24:45 +08:00
auto root = File : : absolutePath ( " " , item , true ) ;
2023-12-02 15:58:40 +08:00
ret . emplace ( std : : move ( root ) ) ;
}
return ret ;
} ) ;
api_regist ( " /index/api/downloadFile " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_ARGS ( " file_path " ) ;
auto file_path = allArgs [ " file_path " ] ;
if ( file_path . find ( " .. " ) ! = std : : string : : npos ) {
invoker ( 401 , StrCaseMap { } , " You can not access parent directory " ) ;
return ;
}
bool safe = false ;
for ( auto & root : download_roots ) {
if ( start_with ( file_path , root ) ) {
safe = true ;
break ;
}
}
if ( ! safe ) {
invoker ( 401 , StrCaseMap { } , " You can not download files outside the root directory " ) ;
return ;
}
2024-09-19 14:53:50 +08:00
// 通过on_http_access完成文件下载鉴权, 请务必确认访问鉴权url参数以及访问文件路径是否合法 [AUTO-TRANSLATED:73507988]
// File download authentication is completed through on_http_access. Please make sure that the access authentication URL parameters and the access file path are legal
2023-12-02 15:58:40 +08:00
HttpSession : : HttpAccessPathInvoker file_invoker = [ allArgs , invoker ] ( const string & err_msg , const string & cookie_path_in , int life_second ) mutable {
if ( ! err_msg . empty ( ) ) {
invoker ( 401 , StrCaseMap { } , err_msg ) ;
} else {
StrCaseMap res_header ;
auto save_name = allArgs [ " save_name " ] ;
if ( ! save_name . empty ( ) ) {
res_header . emplace ( " Content-Disposition " , " attachment;filename= \" " + save_name + " \" " ) ;
}
2024-03-23 11:46:30 -03:00
invoker . responseFile ( allArgs . parser . getHeader ( ) , res_header , allArgs [ " file_path " ] ) ;
2023-12-02 15:58:40 +08:00
}
} ;
2026-03-09 16:43:14 +08:00
try {
CHECK_SECRET ( ) ;
// 校验secret成功, 文件下载鉴权成功
file_invoker ( " " , " " , 0 ) ;
} catch ( . . . ) {
2026-03-09 18:00:12 +08:00
bool flag = NOTICE_EMIT ( BroadcastHttpAccessArgs , Broadcast : : kBroadcastHttpAccess , allArgs . parser , allArgs . parser . url ( ) , file_path , false , file_invoker , sender ) ;
2026-03-09 16:43:14 +08:00
if ( ! flag ) {
// 文件下载鉴权事件无人监听,不允许下载 [AUTO-TRANSLATED:5e02f0ce]
// No one is listening to the file download authentication event, download is not allowed
invoker ( 401 , StrCaseMap { } , " None http access event listener " ) ;
}
2023-12-02 15:58:40 +08:00
}
} ) ;
2024-03-16 22:56:32 +08:00
2025-11-17 12:58:45 +08:00
api_regist ( " /index/api/searchOnvifDevice " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " timeout_ms " ) ;
2026-02-12 17:28:00 +08:00
string subnet_prefix = allArgs [ " subnet_prefix " ] ;
auto result = std : : make_shared < Value > ( std : : move ( val ) ) ;
auto complete_token = std : : make_shared < onceToken > ( nullptr , [ result , headerOut , invoker ] ( ) { invoker ( 200 , headerOut , result - > toStyledString ( ) ) ; } ) ;
auto lam_search = [ complete_token , result ] ( const std : : map < string , string > & device_info , const std : : string & onvif_url ) {
Value obj ;
obj [ " onvif_url " ] = onvif_url ;
for ( auto & pr : device_info ) {
obj [ pr . first ] = pr . second ;
}
( * result ) [ " data " ] . append ( std : : move ( obj ) ) ;
//继续等待扫描
return true ;
} ;
2026-02-19 22:56:23 +08:00
OnvifSearcher : : Instance ( ) . sendSearchBroadcast ( std : : move ( subnet_prefix ) , std : : move ( lam_search ) , allArgs [ " timeout_ms " ] ) ;
2026-02-12 17:28:00 +08:00
} ) ;
2025-11-17 12:58:45 +08:00
api_regist ( " /index/api/getStreamUrl " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " onvif_url " ) ;
SoapUtil : : asyncGetStreamUri ( allArgs [ " onvif_url " ] , [ val , headerOut , allArgs , invoker ]
( const SoapErr & err , const SoapUtil : : GetStreamUriRetryInvoker & retry_invoker ,
int retry_count , const std : : string & url ) mutable {
if ( err & & retry_count = = 0 & & ! allArgs [ " user_name " ] . empty ( ) /* &&
( err . httpCode ( ) = = 400 | | err . httpCode ( ) = = 401 ) */ ) {
//第一次失败,且提供了用户密码,且确定是鉴权失败
retry_invoker ( allArgs [ " user_name " ] , allArgs [ " passwd " ] ) ;
return ;
}
val [ " code " ] = err ? API : : OtherFailed : API : : Success ;
if ( err ) {
val [ " http_code " ] = err . httpCode ( ) ;
val [ " msg " ] = ( string ) err ;
} else {
val [ " url " ] = url ;
}
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
} ) ;
2026-02-19 22:56:23 +08:00
api_regist ( " /index/api/login " , [ ] ( API_ARGS_MAP ) {
auto logined_cookie = HttpCookieManager : : Instance ( ) . getCookie ( kLoginedCookieName , allArgs . getParser ( ) . getHeader ( ) ) ;
2026-02-25 11:49:38 +08:00
2026-02-19 22:56:23 +08:00
CHECK_ARGS ( " digest " ) ;
GET_CONFIG ( std : : string , api_secret , API : : kSecret ) ;
auto unlogin_cookie = HttpCookieManager : : Instance ( ) . getCookie ( kUnLoginCookieName , allArgs . getParser ( ) . getHeader ( ) ) ;
// MD5("zlmediakit:"+${secret}+":" +${cookie})
auto digest_ok = unlogin_cookie ? MD5 ( " zlmediakit: " + api_secret + " : " + unlogin_cookie - > getCookie ( ) ) . hexdigest ( ) : " " ;
if ( ! unlogin_cookie | | digest_ok ! = allArgs [ " digest " ] ) {
if ( ! unlogin_cookie ) {
unlogin_cookie = HttpCookieManager : : Instance ( ) . addCookie ( kUnLoginCookieName , " " , kUnLoginCookieLifeSeconds ) ;
headerOut [ " Set-Cookie " ] = unlogin_cookie - > getCookie ( kLoginCookiePath ) ;
}
val [ " cookie " ] = unlogin_cookie - > getCookie ( ) ;
2026-02-25 11:49:38 +08:00
if ( logined_cookie ) {
// secret校验失败, 注销登录
logined_cookie - > setExpired ( ) ;
HttpCookieManager : : Instance ( ) . delCookie ( logined_cookie ) ;
headerOut . emplace_force ( " Set-Cookie " , logined_cookie - > getCookie ( kLoginCookiePath ) ) ;
}
2026-02-19 22:56:23 +08:00
throw AuthException ( " Digest does not match, incorrect secret? " , headerOut , val ) ;
}
2026-02-25 11:49:38 +08:00
if ( ! logined_cookie ) {
// 未登陆状态,设置登录成功, cookie保持24小时
logined_cookie = HttpCookieManager : : Instance ( ) . addCookie ( kLoginedCookieName , " " , kLoginedCookieLifeSeconds ) ;
headerOut [ " Set-Cookie " ] = logined_cookie - > getCookie ( kLoginCookiePath ) ;
}
2026-02-19 22:56:23 +08:00
// 删除未登录状态的cookie
unlogin_cookie - > setExpired ( ) ;
HttpCookieManager : : Instance ( ) . delCookie ( unlogin_cookie ) ;
headerOut . emplace_force ( " Set-Cookie " , unlogin_cookie - > getCookie ( kLoginCookiePath ) ) ;
val [ " code " ] = API : : Success ;
} ) ;
api_regist ( " /index/api/logout " , [ ] ( API_ARGS_MAP ) {
auto logined_cookie = HttpCookieManager : : Instance ( ) . getCookie ( kLoginedCookieName , allArgs . getParser ( ) . getHeader ( ) ) ;
if ( logined_cookie ) {
// 已经登录成功, 删除cookie
logined_cookie - > setExpired ( ) ;
HttpCookieManager : : Instance ( ) . delCookie ( logined_cookie ) ;
headerOut [ " Set-Cookie " ] = logined_cookie - > getCookie ( kLoginCookiePath ) ;
} else {
val [ " msg " ] = " You are not logined " ;
}
auto unlogin_cookie = HttpCookieManager : : Instance ( ) . getCookie ( kUnLoginCookieName , allArgs . getParser ( ) . getHeader ( ) ) ;
if ( ! unlogin_cookie ) {
unlogin_cookie = HttpCookieManager : : Instance ( ) . addCookie ( kUnLoginCookieName , " " , kUnLoginCookieLifeSeconds ) ;
headerOut [ " Set-Cookie " ] = unlogin_cookie - > getCookie ( kLoginCookiePath ) ;
}
val [ " cookie " ] = unlogin_cookie - > getCookie ( ) ;
} ) ;
2024-04-13 20:35:59 +08:00
# if defined(ENABLE_VIDEOSTACK) && defined(ENABLE_X264) && defined(ENABLE_FFMPEG)
2024-03-16 22:56:32 +08:00
VideoStackManager : : Instance ( ) . loadBgImg ( " novideo.yuv " ) ;
NoticeCenter : : Instance ( ) . addListener ( nullptr , Broadcast : : kBroadcastStreamNoneReader , [ ] ( BroadcastStreamNoneReaderArgs ) {
auto id = sender . getMediaTuple ( ) . stream ;
VideoStackManager : : Instance ( ) . stopVideoStack ( id ) ;
} ) ;
api_regist ( " /index/api/stack/start " , [ ] ( API_ARGS_JSON_ASYNC ) {
CHECK_SECRET ( ) ;
2024-06-19 14:06:02 +08:00
int ret = 0 ;
try {
ret = VideoStackManager : : Instance ( ) . startVideoStack ( allArgs . args ) ;
val [ " code " ] = ret ;
val [ " msg " ] = ret ? " failed " : " success " ;
} catch ( const std : : exception & e ) {
val [ " code " ] = - 1 ;
val [ " msg " ] = e . what ( ) ;
}
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
api_regist ( " /index/api/stack/reset " , [ ] ( API_ARGS_JSON_ASYNC ) {
CHECK_SECRET ( ) ;
int ret = 0 ;
try {
auto ret = VideoStackManager : : Instance ( ) . resetVideoStack ( allArgs . args ) ;
val [ " code " ] = ret ;
val [ " msg " ] = ret ? " failed " : " success " ;
} catch ( const std : : exception & e ) {
val [ " code " ] = - 1 ;
val [ " msg " ] = e . what ( ) ;
}
2024-03-23 20:47:38 +08:00
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
2024-03-16 22:56:32 +08:00
} ) ;
api_regist ( " /index/api/stack/stop " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " id " ) ;
auto ret = VideoStackManager : : Instance ( ) . stopVideoStack ( allArgs [ " id " ] ) ;
2024-03-23 20:47:38 +08:00
val [ " code " ] = ret ;
val [ " msg " ] = ret ? " failed " : " success " ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
2024-03-16 22:56:32 +08:00
} ) ;
# endif
2026-04-01 20:42:32 +08:00
// 设置流播放速度
// Set stream playback speed
api_regist ( " /index/api/setStreamSpeed " , [ ] ( API_ARGS_JSON_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " vhost " , " app " , " stream " , " speed " ) ;
std : : string vhost = allArgs [ " vhost " ] ;
std : : string app = allArgs [ " app " ] ;
std : : string stream = allArgs [ " stream " ] ;
float speed = allArgs [ " speed " ] . as < float > ( ) ;
auto tuple = MediaTuple { vhost , app , stream , " " } ;
std : : string key = tuple . shortUrl ( ) ;
auto player_proxy = s_player_proxy . find ( key ) ;
if ( ! player_proxy ) {
throw ApiRetException ( " can not find the stream proxy " , API : : NotFound ) ;
}
player_proxy - > getPoller ( ) - > async ( [ = ] ( ) mutable {
player_proxy - > MediaPlayer : : speed ( speed ) ;
val [ " result " ] = 0 ;
val [ " msg " ] = " success " ;
val [ " code " ] = API : : Success ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
} ) ;
// 暂停/恢复流播放
// Pause/Resume stream playback
api_regist ( " /index/api/pauseStream " , [ ] ( API_ARGS_JSON_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " vhost " , " app " , " stream " ) ;
std : : string vhost = allArgs [ " vhost " ] ;
std : : string app = allArgs [ " app " ] ;
std : : string stream = allArgs [ " stream " ] ;
auto tuple = MediaTuple { vhost , app , stream , " " } ;
std : : string key = tuple . shortUrl ( ) ;
auto player_proxy = s_player_proxy . find ( key ) ;
if ( ! player_proxy ) {
throw ApiRetException ( " can not find the stream proxy " , API : : NotFound ) ;
}
player_proxy - > getPoller ( ) - > async ( [ = ] ( ) mutable {
player_proxy - > MediaPlayer : : pause ( true ) ;
val [ " result " ] = 0 ;
val [ " msg " ] = " success " ;
val [ " code " ] = API : : Success ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
} ) ;
// 跳转到指定位置
// Seek to specified position
api_regist ( " /index/api/seekStream " , [ ] ( API_ARGS_JSON_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " vhost " , " app " , " stream " ) ;
std : : string vhost = allArgs [ " vhost " ] ;
std : : string app = allArgs [ " app " ] ;
std : : string stream = allArgs [ " stream " ] ;
uint32_t pos = allArgs [ " position " ] . as < uint32_t > ( ) ;
auto tuple = MediaTuple { vhost , app , stream , " " } ;
std : : string key = tuple . shortUrl ( ) ;
auto player_proxy = s_player_proxy . find ( key ) ;
if ( ! player_proxy ) {
throw ApiRetException ( " can not find the stream proxy " , API : : NotFound ) ;
}
player_proxy - > getPoller ( ) - > async ( [ = ] ( ) mutable {
player_proxy - > MediaPlayer : : seekTo ( pos ) ;
val [ " result " ] = 0 ;
val [ " msg " ] = " success " ;
val [ " code " ] = API : : Success ;
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
} ) ;
2026-05-03 13:34:18 +08:00
api_regist ( " /index/api/addProbe " , [ ] ( API_ARGS_MAP_ASYNC ) {
CHECK_SECRET ( ) ;
CHECK_ARGS ( " vhost " , " app " , " stream " , " probe_ms " ) ;
std : : string vhost = allArgs [ " vhost " ] ;
std : : string app = allArgs [ " app " ] ;
std : : string stream = allArgs [ " stream " ] ;
uint32_t probe_ms = allArgs [ " probe_ms " ] ;
auto src = MediaSource : : find ( vhost , app , stream ) ;
if ( ! src ) {
throw ApiRetException ( " can not find the stream " , API : : NotFound ) ;
}
src - > getOwnerPoller ( ) - > async ( [ = ] ( ) mutable {
src - > getMuxer ( ) - > addProbe ( probe_ms , [ = ] ( const std : : list < FrameInfo > & info_list ) mutable {
for ( const auto & info : info_list ) {
Json : : Value item ;
item [ " codec " ] = getCodecName ( info . codec_id ) ;
item [ " track_type " ] = getTrackString ( getTrackType ( info . codec_id ) ) ;
item [ " dts " ] = ( Json : : Int64 ) info . dts ;
item [ " pts " ] = ( Json : : Int64 ) info . pts ;
item [ " recv_stamp " ] = ( Json : : Int64 ) info . recv_stamp ;
item [ " frame_size " ] = ( Json : : UInt ) info . frame_size ;
item [ " index " ] = info . index ;
item [ " key_frame " ] = info . key_frame ;
item [ " config_frame " ] = info . config_frame ;
val [ " data " ] . append ( std : : move ( item ) ) ;
}
invoker ( 200 , headerOut , val . toStyledString ( ) ) ;
} ) ;
} ) ;
} ) ;
2019-05-20 17:03:04 +08:00
}
2019-05-20 16:26:04 +08:00
2019-05-20 17:03:04 +08:00
void unInstallWebApi ( ) {
2024-03-10 05:31:20 -03:00
s_player_proxy . clear ( ) ;
s_ffmpeg_src . clear ( ) ;
s_pusher_proxy . clear ( ) ;
2020-07-02 22:23:43 +08:00
# if defined(ENABLE_RTPPROXY)
2024-03-10 05:31:20 -03:00
s_rtp_server . clear ( ) ;
2020-07-02 22:23:43 +08:00
# endif
2024-06-19 14:06:02 +08:00
# if defined(ENABLE_VIDEOSTACK) && defined(ENABLE_FFMPEG) && defined(ENABLE_X264)
VideoStackManager : : Instance ( ) . clear ( ) ;
# endif
2024-03-10 05:31:20 -03:00
2022-07-29 16:23:35 +08:00
NoticeCenter : : Instance ( ) . delListener ( & web_api_tag ) ;
2021-10-25 15:13:21 +08:00
}