diff --git a/api/source/mk_events_objects.cpp b/api/source/mk_events_objects.cpp index 72c30ed2..85f0c9df 100644 --- a/api/source/mk_events_objects.cpp +++ b/api/source/mk_events_objects.cpp @@ -18,64 +18,64 @@ #include "Rtsp/RtspSession.h" using namespace mediakit; -///////////////////////////////////////////MP4Info///////////////////////////////////////////// +///////////////////////////////////////////RecordInfo///////////////////////////////////////////// API_EXPORT uint64_t API_CALL mk_mp4_info_get_start_time(const mk_mp4_info ctx){ assert(ctx); - MP4Info *info = (MP4Info *)ctx; + RecordInfo *info = (RecordInfo *)ctx; return info->ui64StartedTime; } API_EXPORT uint64_t API_CALL mk_mp4_info_get_time_len(const mk_mp4_info ctx){ assert(ctx); - MP4Info *info = (MP4Info *)ctx; + RecordInfo *info = (RecordInfo *)ctx; return info->ui64TimeLen; } API_EXPORT uint64_t API_CALL mk_mp4_info_get_file_size(const mk_mp4_info ctx){ assert(ctx); - MP4Info *info = (MP4Info *)ctx; + RecordInfo *info = (RecordInfo *)ctx; return info->ui64FileSize; } API_EXPORT const char* API_CALL mk_mp4_info_get_file_path(const mk_mp4_info ctx){ assert(ctx); - MP4Info *info = (MP4Info *)ctx; + RecordInfo *info = (RecordInfo *)ctx; return info->strFilePath.c_str(); } API_EXPORT const char* API_CALL mk_mp4_info_get_file_name(const mk_mp4_info ctx){ assert(ctx); - MP4Info *info = (MP4Info *)ctx; + RecordInfo *info = (RecordInfo *)ctx; return info->strFileName.c_str(); } API_EXPORT const char* API_CALL mk_mp4_info_get_folder(const mk_mp4_info ctx){ assert(ctx); - MP4Info *info = (MP4Info *)ctx; + RecordInfo *info = (RecordInfo *)ctx; return info->strFolder.c_str(); } API_EXPORT const char* API_CALL mk_mp4_info_get_url(const mk_mp4_info ctx){ assert(ctx); - MP4Info *info = (MP4Info *)ctx; + RecordInfo *info = (RecordInfo *)ctx; return info->strUrl.c_str(); } API_EXPORT const char* API_CALL mk_mp4_info_get_vhost(const mk_mp4_info ctx){ assert(ctx); - MP4Info *info = (MP4Info *)ctx; + RecordInfo *info = (RecordInfo *)ctx; return info->strVhost.c_str(); } API_EXPORT const char* API_CALL mk_mp4_info_get_app(const mk_mp4_info ctx){ assert(ctx); - MP4Info *info = (MP4Info *)ctx; + RecordInfo *info = (RecordInfo *)ctx; return info->strAppName.c_str(); } API_EXPORT const char* API_CALL mk_mp4_info_get_stream(const mk_mp4_info ctx){ assert(ctx); - MP4Info *info = (MP4Info *)ctx; + RecordInfo *info = (RecordInfo *)ctx; return info->strStreamId.c_str(); } diff --git a/conf/config.ini b/conf/config.ini index 72ca1b43..755222a0 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -66,6 +66,8 @@ segDur=2 segNum=3 #HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数 segRetain=5 +# 是否广播 ts 切片完成通知 +broadcastRecordTs=0 [hook] #在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然 @@ -83,6 +85,8 @@ on_play=https://127.0.0.1/index/hook/on_play on_publish=https://127.0.0.1/index/hook/on_publish #录制mp4切片完成事件 on_record_mp4=https://127.0.0.1/index/hook/on_record_mp4 +# 录制 hls ts 切片完成事件 +on_record_ts=https://127.0.0.1/index/hook/on_record_ts #rtsp播放鉴权事件,此事件中比对rtsp的用户名密码 on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth #rtsp播放是否开启专属鉴权事件,置空则关闭rtsp鉴权。rtsp播放鉴权还支持url方式鉴权 diff --git a/server/WebHook.cpp b/server/WebHook.cpp index 05943403..64c084e0 100644 --- a/server/WebHook.cpp +++ b/server/WebHook.cpp @@ -53,6 +53,7 @@ const string kOnRtspAuth = HOOK_FIELD"on_rtsp_auth"; const string kOnStreamChanged = HOOK_FIELD"on_stream_changed"; const string kOnStreamNotFound = HOOK_FIELD"on_stream_not_found"; const string kOnRecordMp4 = HOOK_FIELD"on_record_mp4"; +const string kOnRecordTs = HOOK_FIELD"on_record_ts"; const string kOnShellLogin = HOOK_FIELD"on_shell_login"; const string kOnStreamNoneReader = HOOK_FIELD"on_stream_none_reader"; const string kOnHttpAccess = HOOK_FIELD"on_http_access"; @@ -70,6 +71,7 @@ onceToken token([](){ mINI::Instance()[kOnStreamChanged] = "https://127.0.0.1/index/hook/on_stream_changed"; mINI::Instance()[kOnStreamNotFound] = "https://127.0.0.1/index/hook/on_stream_not_found"; mINI::Instance()[kOnRecordMp4] = "https://127.0.0.1/index/hook/on_record_mp4"; + mINI::Instance()[kOnRecordTs] = "https://127.0.0.1/index/hook/on_record_ts"; mINI::Instance()[kOnShellLogin] = "https://127.0.0.1/index/hook/on_shell_login"; mINI::Instance()[kOnStreamNoneReader] = "https://127.0.0.1/index/hook/on_stream_none_reader"; mINI::Instance()[kOnHttpAccess] = "https://127.0.0.1/index/hook/on_http_access"; @@ -190,6 +192,7 @@ void installWebHook(){ GET_CONFIG(string,hook_stream_chaned,Hook::kOnStreamChanged); GET_CONFIG(string,hook_stream_not_found,Hook::kOnStreamNotFound); GET_CONFIG(string,hook_record_mp4,Hook::kOnRecordMp4); + GET_CONFIG(string,hook_record_ts,Hook::kOnRecordTs); GET_CONFIG(string,hook_shell_login,Hook::kOnShellLogin); GET_CONFIG(string,hook_stream_none_reader,Hook::kOnStreamNoneReader); GET_CONFIG(string,hook_http_access,Hook::kOnHttpAccess); @@ -361,6 +364,25 @@ void installWebHook(){ }); #endif //ENABLE_MP4 + NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastRecordTs, [](BroadcastRecordTsArgs) { + if (!hook_enable || hook_record_ts.empty()) { + return; + } + ArgsType body; + body["start_time"] = (Json::UInt64)info.ui64StartedTime; + body["time_len"] = (Json::UInt64)info.ui64TimeLen; + body["file_size"] = (Json::UInt64)info.ui64FileSize; + body["file_path"] = info.strFilePath; + body["file_name"] = info.strFileName; + body["folder"] = info.strFolder; + body["url"] = info.strUrl; + body["app"] = info.strAppName; + body["stream"] = info.strStreamId; + body["vhost"] = info.strVhost; + // 执行 hook + do_http_hook(hook_record_ts, body, nullptr); + }); + NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastShellLogin,[](BroadcastShellLoginArgs){ if(!hook_enable || hook_shell_login.empty() || sender.get_peer_ip() == "127.0.0.1"){ invoker(""); diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 69e7ed42..f26c504d 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -40,6 +40,7 @@ bool loadIniConfig(const char *ini_path){ namespace Broadcast { const string kBroadcastMediaChanged = "kBroadcastMediaChanged"; const string kBroadcastRecordMP4 = "kBroadcastRecordMP4"; +const string kBroadcastRecordTs = "kBroadcastRecoredTs"; const string kBroadcastHttpRequest = "kBroadcastHttpRequest"; const string kBroadcastHttpAccess = "kBroadcastHttpAccess"; const string kBroadcastOnGetRtspRealm = "kBroadcastOnGetRtspRealm"; @@ -251,6 +252,8 @@ const string kSegmentRetain = HLS_FIELD"segRetain"; const string kFileBufSize = HLS_FIELD"fileBufSize"; //录制文件路径 const string kFilePath = HLS_FIELD"filePath"; +// 是否广播 ts 切片完成通知 +const string kBroadcastRecordTs = HLS_FIELD"broadcastRecordTs"; onceToken token([](){ mINI::Instance()[kSegmentDuration] = 2; @@ -258,6 +261,7 @@ onceToken token([](){ mINI::Instance()[kSegmentRetain] = 5; mINI::Instance()[kFileBufSize] = 64 * 1024; mINI::Instance()[kFilePath] = "./www"; + mINI::Instance()[kBroadcastRecordTs] = false; },nullptr); } //namespace Hls diff --git a/src/Common/config.h b/src/Common/config.h index 0ebdf980..7038ad40 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -58,7 +58,11 @@ extern const string kBroadcastMediaChanged; //录制mp4文件成功后广播 extern const string kBroadcastRecordMP4; -#define BroadcastRecordMP4Args const MP4Info &info +#define BroadcastRecordMP4Args const RecordInfo &info + +// 录制 ts 文件后广播 +extern const string kBroadcastRecordTs; +#define BroadcastRecordTsArgs const RecordInfo &info //收到http api请求广播 extern const string kBroadcastHttpRequest; @@ -281,6 +285,8 @@ extern const string kSegmentRetain; extern const string kFileBufSize; //录制文件路径 extern const string kFilePath; +// 是否广播 ts 切片完成通知 +extern const string kBroadcastRecordTs; } //namespace Hls ////////////Rtp代理相关配置/////////// diff --git a/src/Record/HlsMaker.cpp b/src/Record/HlsMaker.cpp index 291db725..26f93e45 100644 --- a/src/Record/HlsMaker.cpp +++ b/src/Record/HlsMaker.cpp @@ -121,6 +121,11 @@ void HlsMaker::flushLastSegment(bool eof){ delOldSegment(); makeIndexFile(eof); _last_file_name.clear(); + + onFlushLastSegment(seg_dur); +} + +void HlsMaker::onFlushLastSegment(uint32_t) { } bool HlsMaker::isLive() { diff --git a/src/Record/HlsMaker.h b/src/Record/HlsMaker.h index 607572c7..72be5a88 100644 --- a/src/Record/HlsMaker.h +++ b/src/Record/HlsMaker.h @@ -22,6 +22,20 @@ using namespace toolkit; namespace mediakit { +class TsInfo { +public: + time_t ui64StartedTime; // GMT 标准时间,单位秒 + time_t ui64TimeLen; // 录像长度,单位毫秒 + off_t ui64FileSize; // 文件大小,单位 BYTE + string strFilePath; // 文件路径 + string strFileName; // 文件名称 + string strFolder; // 文件夹路径 + string strUrl; // 播放路径 + string strAppName; // 应用名称 + string strStreamId; // 流 ID + string strVhost; // vhost +}; + class HlsMaker { public: /** @@ -84,6 +98,12 @@ protected: */ void flushLastSegment(bool eof); + /** + * 上一个 ts 切片写入完成, 可在这里进行通知处理 + * @param duration 上一个 ts 切片的时长, 单位为毫秒 + */ + virtual void onFlushLastSegment(uint32_t duration); + private: /** * 生成m3u8文件 diff --git a/src/Record/HlsMakerImp.cpp b/src/Record/HlsMakerImp.cpp index 10521e36..e20658d5 100644 --- a/src/Record/HlsMakerImp.cpp +++ b/src/Record/HlsMakerImp.cpp @@ -8,7 +8,10 @@ * may be found in the AUTHORS file in the root of the source tree. */ +#include +#include #include "HlsMakerImp.h" +#include "Thread/WorkThreadPool.h" #include "Util/util.h" #include "Util/uv_errno.h" @@ -28,6 +31,8 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file, _file_buf.reset(new char[bufSize], [](char *ptr) { delete[] ptr; }); + + _info.strFolder = _path_prefix; } HlsMakerImp::~HlsMakerImp() { @@ -59,13 +64,19 @@ string HlsMakerImp::onOpenSegment(int index) { } } _file = makeFile(segment_path, true); + + _info.ui64StartedTime = ::time(NULL); + _info.strFileName = segment_name; + _info.strFilePath = segment_path; + _info.strUrl = _info.strAppName + "/" + _info.strStreamId + "/" + segment_name; + if (!_file) { WarnL << "create file failed," << segment_path << " " << get_uv_errmsg(); } if (_params.empty()) { - return std::move(segment_name); + return segment_name; } - return std::move(segment_name + "?" + _params); + return segment_name + "?" + _params; } void HlsMakerImp::onDelSegment(int index) { @@ -97,6 +108,21 @@ void HlsMakerImp::onWriteHls(const char *data, int len) { //DebugL << "\r\n" << string(data,len); } +void HlsMakerImp::onFlushLastSegment(uint32_t duration) { + GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs); + + if (broadcastRecordTs) { + auto info = _info; + info.ui64TimeLen = duration; + WorkThreadPool::Instance().getExecutor()->async([info]() { + struct stat fileData; + stat(info.strFilePath.data(), &fileData); + const_cast(info).ui64FileSize = fileData.st_size; + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordTs, info); + }); + } +} + std::shared_ptr HlsMakerImp::makeFile(const string &file, bool setbuf) { auto file_buf = _file_buf; @@ -113,6 +139,9 @@ std::shared_ptr HlsMakerImp::makeFile(const string &file, bool setbuf) { void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const string &stream_id) { _media_src = std::make_shared(vhost, app, stream_id); + _info.strAppName = app; + _info.strStreamId = stream_id; + _info.strVhost = vhost; } HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const { diff --git a/src/Record/HlsMakerImp.h b/src/Record/HlsMakerImp.h index 7764644f..cc0b3d31 100644 --- a/src/Record/HlsMakerImp.h +++ b/src/Record/HlsMakerImp.h @@ -16,6 +16,8 @@ #include #include "HlsMaker.h" #include "HlsMediaSource.h" +#include "RecordInfo.h" + using namespace std; namespace mediakit { @@ -54,6 +56,7 @@ protected: void onDelSegment(int index) override; void onWriteSegment(const char *data, int len) override; void onWriteHls(const char *data, int len) override; + void onFlushLastSegment(uint32_t duration) override; private: std::shared_ptr makeFile(const string &file,bool setbuf = false); @@ -66,6 +69,7 @@ private: std::shared_ptr _file; std::shared_ptr _file_buf; HlsMediaSource::Ptr _media_src; + RecordInfo _info; map _segment_file_paths; }; diff --git a/src/Record/MP4Recorder.cpp b/src/Record/MP4Recorder.cpp index 1f247354..a8118333 100644 --- a/src/Record/MP4Recorder.cpp +++ b/src/Record/MP4Recorder.cpp @@ -73,7 +73,7 @@ void MP4Recorder::asyncClose() { auto info = _info; WorkThreadPool::Instance().getExecutor()->async([muxer,strFileTmp,strFile,info]() { //获取文件录制时间,放在关闭mp4之前是为了忽略关闭mp4执行时间 - const_cast(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime; + const_cast(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime; //关闭mp4非常耗时,所以要放在后台线程执行 muxer->closeMP4(); //临时文件名改成正式文件名,防止mp4未完成时被访问 @@ -81,7 +81,7 @@ void MP4Recorder::asyncClose() { //获取文件大小 struct stat fileData; stat(strFile.data(), &fileData); - const_cast(info).ui64FileSize = fileData.st_size; + const_cast(info).ui64FileSize = fileData.st_size; /////record 业务逻辑////// NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordMP4,info); }); diff --git a/src/Record/MP4Recorder.h b/src/Record/MP4Recorder.h index 60f3c561..cc73363d 100644 --- a/src/Record/MP4Recorder.h +++ b/src/Record/MP4Recorder.h @@ -20,24 +20,12 @@ #include "Util/TimeTicker.h" #include "Common/MediaSink.h" #include "MP4Muxer.h" +#include "RecordInfo.h" + using namespace toolkit; namespace mediakit { -class MP4Info { -public: - time_t ui64StartedTime; //GMT标准时间,单位秒 - time_t ui64TimeLen;//录像长度,单位秒 - off_t ui64FileSize;//文件大小,单位BYTE - string strFilePath;//文件路径 - string strFileName;//文件名称 - string strFolder;//文件夹路径 - string strUrl;//播放路径 - string strAppName;//应用名称 - string strStreamId;//流ID - string strVhost;//vhost -}; - #ifdef ENABLE_MP4 class MP4Recorder : public MediaSinkInterface{ public: @@ -72,7 +60,7 @@ private: string _strFile; string _strFileTmp; Ticker _createFileTicker; - MP4Info _info; + RecordInfo _info; bool _haveVideo = false; MP4Muxer::Ptr _muxer; list _tracks; diff --git a/src/Record/RecordInfo.h b/src/Record/RecordInfo.h new file mode 100644 index 00000000..30184a6a --- /dev/null +++ b/src/Record/RecordInfo.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * 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. + */ + +#ifndef RECORDINFO_H_ +#define RECORDINFO_H_ + +#include "Common/config.h" + +namespace mediakit { + +class RecordInfo { +public: + time_t ui64StartedTime; // GMT 标准时间,单位秒 + time_t ui64TimeLen; // 录像长度,需要注意 mp4 单位是秒,而 hls ts 单位是毫秒 + off_t ui64FileSize; // 文件大小,单位 BYTE + string strFilePath; // 文件路径 + string strFileName; // 文件名称 + string strFolder; // 文件夹路径 + string strUrl; // 播放路径 + string strAppName; // 应用名称 + string strStreamId; // 流 ID + string strVhost; // vhost +}; + +} // namespace mediakit + +#endif // RECORDINFO_H_