Commit 0232caf0 by Johnny Committed by 夏楚

add MediaTuple

parent f4ee607f
......@@ -61,19 +61,19 @@ API_EXPORT const char* API_CALL mk_parser_get_content(const mk_parser ctx, size_
///////////////////////////////////////////MediaInfo/////////////////////////////////////////////
//MediaInfo对象的C映射
typedef struct mk_media_info_t *mk_media_info;
//MediaInfo::_param_strs
//MediaInfo::param_strs
API_EXPORT const char* API_CALL mk_media_info_get_params(const mk_media_info ctx);
//MediaInfo::_schema
//MediaInfo::schema
API_EXPORT const char* API_CALL mk_media_info_get_schema(const mk_media_info ctx);
//MediaInfo::_vhost
//MediaInfo::vhost
API_EXPORT const char* API_CALL mk_media_info_get_vhost(const mk_media_info ctx);
//MediaInfo::_app
//MediaInfo::app
API_EXPORT const char* API_CALL mk_media_info_get_app(const mk_media_info ctx);
//MediaInfo::_streamid
//MediaInfo::stream
API_EXPORT const char* API_CALL mk_media_info_get_stream(const mk_media_info ctx);
//MediaInfo::_host
//MediaInfo::host
API_EXPORT const char* API_CALL mk_media_info_get_host(const mk_media_info ctx);
//MediaInfo::_port
//MediaInfo::port
API_EXPORT uint16_t API_CALL mk_media_info_get_port(const mk_media_info ctx);
......
......@@ -126,43 +126,43 @@ API_EXPORT const char* API_CALL mk_parser_get_content(const mk_parser ctx, size_
API_EXPORT const char* API_CALL mk_media_info_get_params(const mk_media_info ctx){
assert(ctx);
MediaInfo *info = (MediaInfo *)ctx;
return info->_param_strs.c_str();
return info->param_strs.c_str();
}
API_EXPORT const char* API_CALL mk_media_info_get_schema(const mk_media_info ctx){
assert(ctx);
MediaInfo *info = (MediaInfo *)ctx;
return info->_schema.c_str();
return info->schema.c_str();
}
API_EXPORT const char* API_CALL mk_media_info_get_vhost(const mk_media_info ctx){
assert(ctx);
MediaInfo *info = (MediaInfo *)ctx;
return info->_vhost.c_str();
return info->vhost.c_str();
}
API_EXPORT const char* API_CALL mk_media_info_get_host(const mk_media_info ctx){
assert(ctx);
MediaInfo *info = (MediaInfo *)ctx;
return info->_host.c_str();
return info->host.c_str();
}
API_EXPORT uint16_t API_CALL mk_media_info_get_port(const mk_media_info ctx){
assert(ctx);
MediaInfo *info = (MediaInfo *)ctx;
return info->_port;
return info->port;
}
API_EXPORT const char* API_CALL mk_media_info_get_app(const mk_media_info ctx){
assert(ctx);
MediaInfo *info = (MediaInfo *)ctx;
return info->_app.c_str();
return info->app.c_str();
}
API_EXPORT const char* API_CALL mk_media_info_get_stream(const mk_media_info ctx){
assert(ctx);
MediaInfo *info = (MediaInfo *)ctx;
return info->_streamid.c_str();
return info->stream.c_str();
}
///////////////////////////////////////////MediaSource/////////////////////////////////////////////
......
......@@ -22,7 +22,8 @@ public:
MediaHelper(const char *vhost, const char *app, const char *stream, float duration, const ProtocolOption &option) {
_poller = EventPollerPool::Instance().getPoller();
// 在poller线程中创建DevChannel(MultiMediaSourceMuxer)对象,确保严格的线程安全限制
_poller->sync([&]() { _channel = std::make_shared<DevChannel>(vhost, app, stream, duration, option); });
auto tuple = MediaTuple{vhost, app, stream};
_poller->sync([&]() { _channel = std::make_shared<DevChannel>(tuple, duration, option); });
}
~MediaHelper() = default;
......
......@@ -102,9 +102,9 @@ void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,cons
_process.run(cmd, log_file);
InfoL << cmd;
if (is_local_ip(_media_info._host)) {
if (is_local_ip(_media_info.host)) {
//推流给自己的,通过判断流是否注册上来判断是否正常
if(_media_info._schema != RTSP_SCHEMA && _media_info._schema != RTMP_SCHEMA){
if (_media_info.schema != RTSP_SCHEMA && _media_info.schema != RTMP_SCHEMA) {
cb(SockException(Err_other,"本服务只支持rtmp/rtsp推流"));
return;
}
......@@ -154,10 +154,10 @@ void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,cons
}
void FFmpegSource::findAsync(int maxWaitMS, const function<void(const MediaSource::Ptr &src)> &cb) {
auto src = MediaSource::find(_media_info._schema,
_media_info._vhost,
_media_info._app,
_media_info._streamid);
auto src = MediaSource::find(_media_info.schema,
_media_info.vhost,
_media_info.app,
_media_info.stream);
if(src || !maxWaitMS){
cb(src);
return;
......@@ -183,10 +183,10 @@ void FFmpegSource::findAsync(int maxWaitMS, const function<void(const MediaSourc
}
if (!bRegist ||
sender.getSchema() != strongSelf->_media_info._schema ||
sender.getVhost() != strongSelf->_media_info._vhost ||
sender.getApp() != strongSelf->_media_info._app ||
sender.getId() != strongSelf->_media_info._streamid) {
sender.getSchema() != strongSelf->_media_info.schema ||
sender.getVhost() != strongSelf->_media_info.vhost ||
sender.getApp() != strongSelf->_media_info.app ||
sender.getId() != strongSelf->_media_info.stream) {
//不是自己感兴趣的事件,忽略之
return;
}
......@@ -223,7 +223,7 @@ void FFmpegSource::startTimer(int timeout_ms) {
return false;
}
bool needRestart = ffmpeg_restart_sec > 0 && strongSelf->_replay_ticker.elapsedTime() > ffmpeg_restart_sec * 1000;
if (is_local_ip(strongSelf->_media_info._host)) {
if (is_local_ip(strongSelf->_media_info.host)) {
//推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常
strongSelf->findAsync(0, [&](const MediaSource::Ptr &src) {
//同步查找流
......
......@@ -1467,7 +1467,8 @@ void installWebApi() {
api_regist("/index/api/deleteRecordDirectory", [](API_ARGS_MAP) {
CHECK_SECRET();
CHECK_ARGS("vhost", "app", "stream");
auto record_path = Recorder::getRecordPath(Recorder::type_mp4, allArgs["vhost"], allArgs["app"], allArgs["stream"], allArgs["customized_path"]);
auto tuple = MediaTuple{allArgs["vhost"], allArgs["app"], allArgs["stream"]};
auto record_path = Recorder::getRecordPath(Recorder::type_mp4, tuple, allArgs["customized_path"]);
auto period = allArgs["period"];
record_path = record_path + period + "/";
int result = File::delete_file(record_path.data());
......@@ -1484,7 +1485,8 @@ void installWebApi() {
api_regist("/index/api/getMp4RecordFile", [](API_ARGS_MAP){
CHECK_SECRET();
CHECK_ARGS("vhost", "app", "stream");
auto record_path = Recorder::getRecordPath(Recorder::type_mp4, allArgs["vhost"], allArgs["app"], allArgs["stream"], allArgs["customized_path"]);
auto tuple = MediaTuple{allArgs["vhost"], allArgs["app"], allArgs["stream"]};
auto record_path = Recorder::getRecordPath(Recorder::type_mp4, tuple, allArgs["customized_path"]);
auto period = allArgs["period"];
//判断是获取mp4文件列表还是获取文件夹列表
......
......@@ -215,11 +215,11 @@ void do_http_hook(const string &url, const ArgsType &body, const function<void(c
static ArgsType make_json(const MediaInfo &args) {
ArgsType body;
body["schema"] = args._schema;
body[VHOST_KEY] = args._vhost;
body["app"] = args._app;
body["stream"] = args._streamid;
body["params"] = args._param_strs;
body["schema"] = args.schema;
body[VHOST_KEY] = args.vhost;
body["app"] = args.app;
body["stream"] = args.stream;
body["params"] = args.param_strs;
return body;
}
......@@ -263,12 +263,12 @@ static const string kEdgeServerParam = "edge=1";
static string getPullUrl(const string &origin_fmt, const MediaInfo &info) {
char url[1024] = { 0 };
if ((ssize_t)origin_fmt.size() > snprintf(url, sizeof(url), origin_fmt.data(), info._app.data(), info._streamid.data())) {
if ((ssize_t)origin_fmt.size() > snprintf(url, sizeof(url), origin_fmt.data(), info.app.data(), info.stream.data())) {
WarnL << "get origin url failed, origin_fmt:" << origin_fmt;
return "";
}
// 告知源站这是来自边沿站的拉流请求,如果未找到流请立即返回拉流失败
return string(url) + '?' + kEdgeServerParam + '&' + VHOST_KEY + '=' + info._vhost + '&' + info._param_strs;
return string(url) + '?' + kEdgeServerParam + '&' + VHOST_KEY + '=' + info.vhost + '&' + info.param_strs;
}
static void pullStreamFromOrigin(const vector<string> &urls, size_t index, size_t failed_cnt, const MediaInfo &args, const function<void()> &closePlayer) {
......@@ -280,10 +280,10 @@ static void pullStreamFromOrigin(const vector<string> &urls, size_t index, size_
InfoL << "pull stream from origin, failed_cnt: " << failed_cnt << ", timeout_sec: " << timeout_sec << ", url: " << url;
ProtocolOption option;
option.enable_hls = option.enable_hls || (args._schema == HLS_SCHEMA);
option.enable_hls = option.enable_hls || (args.schema == HLS_SCHEMA);
option.enable_mp4 = false;
addStreamProxy(args._vhost, args._app, args._streamid, url, retry_count, option, Rtsp::RTP_TCP, timeout_sec, [=](const SockException &ex, const string &key) mutable {
addStreamProxy(args.vhost, args.app, args.stream, url, retry_count, option, Rtsp::RTP_TCP, timeout_sec, [=](const SockException &ex, const string &key) mutable {
if (!ex) {
return;
}
......@@ -321,7 +321,7 @@ void installWebHook() {
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) {
GET_CONFIG(string, hook_publish, Hook::kOnPublish);
if (!hook_enable || args._param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1") {
if (!hook_enable || args.param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1") {
invoker("", ProtocolOption());
return;
}
......@@ -346,7 +346,7 @@ void installWebHook() {
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) {
GET_CONFIG(string, hook_play, Hook::kOnPlay);
if (!hook_enable || args._param_strs == hook_adminparams || hook_play.empty() || sender.get_peer_ip() == "127.0.0.1") {
if (!hook_enable || args.param_strs == hook_adminparams || hook_play.empty() || sender.get_peer_ip() == "127.0.0.1") {
invoker("");
return;
}
......@@ -360,7 +360,7 @@ void installWebHook() {
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) {
GET_CONFIG(string, hook_flowreport, Hook::kOnFlowReport);
if (!hook_enable || args._param_strs == hook_adminparams || hook_flowreport.empty() || sender.get_peer_ip() == "127.0.0.1") {
if (!hook_enable || args.param_strs == hook_adminparams || hook_flowreport.empty() || sender.get_peer_ip() == "127.0.0.1") {
return;
}
auto body = make_json(args);
......@@ -379,7 +379,7 @@ void installWebHook() {
// 监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastOnGetRtspRealm, [](BroadcastOnGetRtspRealmArgs) {
GET_CONFIG(string, hook_rtsp_realm, Hook::kOnRtspRealm);
if (!hook_enable || args._param_strs == hook_adminparams || hook_rtsp_realm.empty() || sender.get_peer_ip() == "127.0.0.1") {
if (!hook_enable || args.param_strs == hook_adminparams || hook_rtsp_realm.empty() || sender.get_peer_ip() == "127.0.0.1") {
// 无需认证
invoker("");
return;
......@@ -467,7 +467,7 @@ void installWebHook() {
return;
}
if (start_with(args._param_strs, kEdgeServerParam)) {
if (start_with(args.param_strs, kEdgeServerParam)) {
// 源站收到来自边沿站的溯源请求,流不存在时立即返回拉流失败
closePlayer();
return;
......
......@@ -47,10 +47,8 @@ public:
using Ptr = std::shared_ptr<DevChannel>;
//fDuration<=0为直播,否则为点播
DevChannel(
const std::string &vhost, const std::string &app, const std::string &stream_id, float duration = 0,
const ProtocolOption &option = ProtocolOption())
: MultiMediaSourceMuxer(vhost, app, stream_id, duration, option) {}
DevChannel(const MediaTuple& tuple, float duration = 0, const ProtocolOption &option = ProtocolOption())
: MultiMediaSourceMuxer(tuple, duration, option) {}
~DevChannel() override = default;
/**
......
......@@ -105,7 +105,7 @@ ProtocolOption::ProtocolOption() {
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct MediaSourceNull : public MediaSource {
MediaSourceNull() : MediaSource("schema", "vhost", "app", "stream") {};
MediaSourceNull() : MediaSource("schema", MediaTuple{"vhost", "app", "stream"}) {};
int readerCount() override { return 0; }
};
......@@ -114,16 +114,12 @@ MediaSource &MediaSource::NullMediaSource() {
return *s_null;
}
MediaSource::MediaSource(const string &schema, const string &vhost, const string &app, const string &stream_id){
MediaSource::MediaSource(const string &schema, const MediaTuple& tuple): _tuple(tuple) {
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
if (!enableVhost) {
_vhost = DEFAULT_VHOST;
} else {
_vhost = vhost.empty() ? DEFAULT_VHOST : vhost;
if (!enableVhost || _tuple.vhost.empty()) {
_tuple.vhost = DEFAULT_VHOST;
}
_schema = schema;
_app = app;
_stream_id = stream_id;
_create_stamp = time(NULL);
}
......@@ -136,16 +132,16 @@ const string& MediaSource::getSchema() const {
}
const string& MediaSource::getVhost() const {
return _vhost;
return _tuple.vhost;
}
const string& MediaSource::getApp() const {
//获取该源的id
return _app;
return _tuple.app;
}
const string& MediaSource::getId() const {
return _stream_id;
return _tuple.stream;
}
std::shared_ptr<void> MediaSource::getOwnership() {
......@@ -424,7 +420,7 @@ static MediaSource::Ptr find_l(const string &schema, const string &vhost_in, con
static void findAsync_l(const MediaInfo &info, const std::shared_ptr<Session> &session, bool retry,
const function<void(const MediaSource::Ptr &src)> &cb){
auto src = find_l(info._schema, info._vhost, info._app, info._streamid, true);
auto src = find_l(info.schema, info.vhost, info.app, info.stream, true);
if (src || !retry) {
cb(src);
return;
......@@ -459,10 +455,10 @@ static void findAsync_l(const MediaInfo &info, const std::shared_ptr<Session> &s
weak_ptr<Session> weak_session = session;
auto on_register = [weak_session, info, cb_once, cancel_all, poller](BroadcastMediaChangedArgs) {
if (!bRegist ||
sender.getSchema() != info._schema ||
sender.getVhost() != info._vhost ||
sender.getApp() != info._app ||
sender.getId() != info._streamid) {
sender.getSchema() != info.schema ||
sender.getVhost() != info.vhost ||
sender.getApp() != info.app ||
sender.getId() != info.stream) {
//不是自己感兴趣的事件,忽略之
return;
}
......@@ -527,7 +523,7 @@ void MediaSource::regist() {
{
//减小互斥锁临界区
lock_guard<recursive_mutex> lock(s_media_source_mtx);
auto &ref = s_media_source_map[_schema][_vhost][_app][_stream_id];
auto &ref = s_media_source_map[_schema][_tuple.vhost][_tuple.app][_tuple.stream];
auto src = ref.lock();
if (src) {
if (src.get() == this) {
......@@ -570,7 +566,7 @@ bool MediaSource::unregist() {
{
//减小互斥锁临界区
lock_guard<recursive_mutex> lock(s_media_source_mtx);
erase_media_source(ret, this, s_media_source_map, _schema, _vhost, _app, _stream_id);
erase_media_source(ret, this, s_media_source_map, _schema, _tuple.vhost, _tuple.app, _tuple.stream);
}
if (ret) {
......@@ -582,31 +578,31 @@ bool MediaSource::unregist() {
/////////////////////////////////////MediaInfo//////////////////////////////////////
void MediaInfo::parse(const std::string &url_in){
_full_url = url_in;
full_url = url_in;
auto url = url_in;
auto pos = url.find("?");
if (pos != string::npos) {
_param_strs = url.substr(pos + 1);
param_strs = url.substr(pos + 1);
url.erase(pos);
}
auto schema_pos = url.find("://");
if (schema_pos != string::npos) {
_schema = url.substr(0, schema_pos);
schema = url.substr(0, schema_pos);
} else {
schema_pos = -3;
}
auto split_vec = split(url.substr(schema_pos + 3), "/");
if (split_vec.size() > 0) {
splitUrl(split_vec[0], _host, _port);
_vhost = _host;
if (_vhost == "localhost" || isIP(_vhost.data())) {
splitUrl(split_vec[0], host, port);
vhost = host;
if (vhost == "localhost" || isIP(vhost.data())) {
//如果访问的是localhost或ip,那么则为默认虚拟主机
_vhost = DEFAULT_VHOST;
vhost = DEFAULT_VHOST;
}
}
if (split_vec.size() > 1) {
_app = split_vec[1];
app = split_vec[1];
}
if (split_vec.size() > 2) {
string stream_id;
......@@ -616,18 +612,18 @@ void MediaInfo::parse(const std::string &url_in){
if (stream_id.back() == '/') {
stream_id.pop_back();
}
_streamid = stream_id;
stream = stream_id;
}
auto params = Parser::parseArgs(_param_strs);
auto params = Parser::parseArgs(param_strs);
if (params.find(VHOST_KEY) != params.end()) {
_vhost = params[VHOST_KEY];
vhost = params[VHOST_KEY];
}
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
if (!enableVhost || _vhost.empty()) {
if (!enableVhost || vhost.empty()) {
//如果关闭虚拟主机或者虚拟主机为空,则设置虚拟主机为默认
_vhost = DEFAULT_VHOST;
vhost = DEFAULT_VHOST;
}
}
......
......@@ -252,24 +252,20 @@ private:
/**
* 解析url获取媒体相关信息
*/
class MediaInfo {
class MediaInfo: public MediaTuple {
public:
~MediaInfo() = default;
MediaInfo() = default;
MediaInfo(const std::string &url) { parse(url); }
void parse(const std::string &url);
std::string shortUrl() const { return _vhost + "/" + _app + "/" + _streamid; }
std::string getUrl() const { return _schema + "://" + shortUrl(); }
std::string getUrl() const { return schema + "://" + shortUrl(); }
public:
uint16_t _port = 0;
std::string _full_url;
std::string _schema;
std::string _host;
std::string _vhost;
std::string _app;
std::string _streamid;
std::string _param_strs;
uint16_t port = 0;
std::string full_url;
std::string schema;
std::string host;
std::string param_strs;
};
/**
......@@ -280,7 +276,7 @@ public:
static MediaSource& NullMediaSource();
using Ptr = std::shared_ptr<MediaSource>;
MediaSource(const std::string &schema, const std::string &vhost, const std::string &app, const std::string &stream_id);
MediaSource(const std::string &schema, const MediaTuple& tuple);
virtual ~MediaSource();
////////////////获取MediaSource相关信息////////////////
......@@ -294,7 +290,11 @@ public:
// 流id
const std::string& getId() const;
std::string shortUrl() const { return _vhost + "/" + _app + "/" + _stream_id; }
const MediaTuple& getMediaTuple() const {
return _tuple;
}
std::string shortUrl() const { return _tuple.shortUrl(); }
std::string getUrl() const { return _schema + "://" + shortUrl(); }
......@@ -369,7 +369,7 @@ public:
// 同步查找流
static Ptr find(const std::string &schema, const std::string &vhost, const std::string &app, const std::string &id, bool from_mp4 = false);
static Ptr find(const MediaInfo &info, bool from_mp4 = false) {
return find(info._schema, info._vhost, info._app, info._streamid, from_mp4);
return find(info.schema, info.vhost, info.app, info.stream, from_mp4);
}
// 忽略schema,同步查找流,可能返回rtmp/rtsp/hls类型
......@@ -394,15 +394,13 @@ private:
protected:
toolkit::BytesSpeed _speed[TrackMax];
MediaTuple _tuple;
private:
std::atomic_flag _owned { false };
time_t _create_stamp;
toolkit::Ticker _ticker;
std::string _schema;
std::string _vhost;
std::string _app;
std::string _stream_id;
std::weak_ptr<MediaSourceEvent> _listener;
// 对象个数统计
toolkit::ObjectStatistic<MediaSource> _statistic;
......
......@@ -25,7 +25,7 @@ namespace {
class MediaSourceForMuxer : public MediaSource {
public:
MediaSourceForMuxer(const MultiMediaSourceMuxer::Ptr &muxer)
: MediaSource("muxer", muxer->getVhost(), muxer->getApp(), muxer->getStreamId()) {
: MediaSource("muxer", muxer->getMediaTuple()) {
MediaSource::setListener(muxer);
}
int readerCount() override { return 0; }
......@@ -33,7 +33,7 @@ public:
} // namespace
static std::shared_ptr<MediaSinkInterface> makeRecorder(MediaSource &sender, const vector<Track::Ptr> &tracks, Recorder::type type, const ProtocolOption &option){
auto recorder = Recorder::createRecorder(type, sender.getVhost(), sender.getApp(), sender.getId(), option);
auto recorder = Recorder::createRecorder(type, sender.getMediaTuple(), option);
for (auto &track : tracks) {
recorder->addTrack(track);
}
......@@ -71,15 +71,15 @@ static string getTrackInfoStr(const TrackSource *track_src){
}
const std::string &MultiMediaSourceMuxer::getVhost() const {
return _vhost;
return _tuple.vhost;
}
const std::string &MultiMediaSourceMuxer::getApp() const {
return _app;
return _tuple.app;
}
const std::string &MultiMediaSourceMuxer::getStreamId() const {
return _stream_id;
return _tuple.stream;
}
std::string MultiMediaSourceMuxer::shortUrl() const {
......@@ -87,35 +87,32 @@ std::string MultiMediaSourceMuxer::shortUrl() const {
if (!ret.empty()) {
return ret;
}
return _vhost + "/" + _app + "/" + _stream_id;
return _tuple.shortUrl();
}
MultiMediaSourceMuxer::MultiMediaSourceMuxer(const string &vhost, const string &app, const string &stream, float dur_sec, const ProtocolOption &option) {
MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_sec, const ProtocolOption &option): _tuple(tuple) {
_poller = EventPollerPool::Instance().getPoller();
_create_in_poller = _poller->isCurrentThread();
_vhost = vhost;
_app = app;
_stream_id = stream;
_option = option;
if (option.enable_rtmp) {
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(vhost, app, stream, option, std::make_shared<TitleMeta>(dur_sec));
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(_tuple, option, std::make_shared<TitleMeta>(dur_sec));
}
if (option.enable_rtsp) {
_rtsp = std::make_shared<RtspMediaSourceMuxer>(vhost, app, stream, option, std::make_shared<TitleSdp>(dur_sec));
_rtsp = std::make_shared<RtspMediaSourceMuxer>(_tuple, option, std::make_shared<TitleSdp>(dur_sec));
}
if (option.enable_hls) {
_hls = dynamic_pointer_cast<HlsRecorder>(Recorder::createRecorder(Recorder::type_hls, vhost, app, stream, option));
_hls = dynamic_pointer_cast<HlsRecorder>(Recorder::createRecorder(Recorder::type_hls, _tuple, option));
}
if (option.enable_mp4) {
_mp4 = Recorder::createRecorder(Recorder::type_mp4, vhost, app, stream, option);
_mp4 = Recorder::createRecorder(Recorder::type_mp4, _tuple, option);
}
if (option.enable_ts) {
_ts = std::make_shared<TSMediaSourceMuxer>(vhost, app, stream, option);
_ts = std::make_shared<TSMediaSourceMuxer>(_tuple, option);
}
#if defined(ENABLE_MP4)
if (option.enable_fmp4) {
_fmp4 = std::make_shared<FMP4MediaSourceMuxer>(vhost, app, stream, option);
_fmp4 = std::make_shared<FMP4MediaSourceMuxer>(_tuple, option);
}
#endif
......
......@@ -37,7 +37,7 @@ public:
virtual void onAllTrackReady() = 0;
};
MultiMediaSourceMuxer(const std::string &vhost, const std::string &app, const std::string &stream, float dur_sec = 0.0,const ProtocolOption &option = ProtocolOption());
MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_sec = 0.0,const ProtocolOption &option = ProtocolOption());
~MultiMediaSourceMuxer() override = default;
/**
......@@ -129,6 +129,9 @@ public:
const std::string& getVhost() const;
const std::string& getApp() const;
const std::string& getStreamId() const;
const MediaTuple& getMediaTuple() const {
return _tuple;
}
std::string shortUrl() const;
protected:
......@@ -159,9 +162,7 @@ private:
bool _is_enable = false;
bool _create_in_poller = false;
bool _video_key_pos = false;
std::string _vhost;
std::string _app;
std::string _stream_id;
MediaTuple _tuple;
ProtocolOption _option;
toolkit::Ticker _last_check;
Stamp _stamp[2];
......
......@@ -39,10 +39,8 @@ public:
using RingDataType = std::shared_ptr<toolkit::List<FMP4Packet::Ptr> >;
using RingType = toolkit::RingBuffer<RingDataType>;
FMP4MediaSource(const std::string &vhost,
const std::string &app,
const std::string &stream_id,
int ring_size = FMP4_GOP_SIZE) : MediaSource(FMP4_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {}
FMP4MediaSource(const MediaTuple& tuple,
int ring_size = FMP4_GOP_SIZE) : MediaSource(FMP4_SCHEMA, tuple), _ring_size(ring_size) {}
~FMP4MediaSource() override { flush(); }
......
......@@ -23,12 +23,9 @@ class FMP4MediaSourceMuxer final : public MP4MuxerMemory, public MediaSourceEven
public:
using Ptr = std::shared_ptr<FMP4MediaSourceMuxer>;
FMP4MediaSourceMuxer(const std::string &vhost,
const std::string &app,
const std::string &stream_id,
const ProtocolOption &option) {
FMP4MediaSourceMuxer(const MediaTuple& tuple, const ProtocolOption &option) {
_option = option;
_media_src = std::make_shared<FMP4MediaSource>(vhost, app, stream_id);
_media_src = std::make_shared<FMP4MediaSource>(tuple);
}
~FMP4MediaSourceMuxer() override { MP4MuxerMemory::flush(); };
......
......@@ -278,7 +278,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
HttpCookieManager::Instance().delCookie(cookie);
}
bool is_hls = media_info._schema == HLS_SCHEMA;
bool is_hls = media_info.schema == HLS_SCHEMA;
SockInfoImp::Ptr info = std::make_shared<SockInfoImp>();
info->_identifier = sender.getIdentifier();
......@@ -363,8 +363,8 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
}
if (is_hls) {
// hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
const_cast<string &>(media_info._schema) = HLS_SCHEMA;
replace(const_cast<string &>(media_info._streamid), kHlsSuffix, "");
const_cast<string &>(media_info.schema) = HLS_SCHEMA;
replace(const_cast<string &>(media_info.stream), kHlsSuffix, "");
}
weak_ptr<Session> weakSession = static_pointer_cast<Session>(sender.shared_from_this());
......@@ -465,11 +465,11 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess
});
string url, path;
auto it = virtualPathMap.find(media_info._app);
auto it = virtualPathMap.find(media_info.app);
if (it != virtualPathMap.end()) {
//访问的是virtualPath
path = it->second;
url = parser.Url().substr(1 + media_info._app.size());
url = parser.Url().substr(1 + media_info.app.size());
} else {
//访问的是rootPath
path = rootPath;
......@@ -481,7 +481,7 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess
ch = '/';
}
}
auto ret = File::absolutePath(enableVhost ? media_info._vhost + url : url, path);
auto ret = File::absolutePath(enableVhost ? media_info.vhost + url : url, path);
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, static_cast<SockInfo &>(sender));
return ret;
}
......
......@@ -202,7 +202,7 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffi
//解析带上协议+参数完整的url
_mediaInfo.parse(schema + "://" + _parser["Host"] + url);
if (_mediaInfo._app.empty() || _mediaInfo._streamid.empty()) {
if (_mediaInfo.app.empty() || _mediaInfo.stream.empty()) {
//url不合法
return false;
}
......
......@@ -29,9 +29,9 @@ PlayerProxy::PlayerProxy(
const EventPoller::Ptr &poller, int reconnect_delay_min, int reconnect_delay_max, int reconnect_delay_step)
: MediaPlayer(poller)
, _option(option) {
_vhost = vhost;
_app = app;
_stream_id = stream_id;
_tuple.vhost = vhost;
_tuple.app = app;
_tuple.stream = stream_id;
_retry_count = retry_count;
setOnClose(nullptr);
......@@ -187,11 +187,11 @@ void PlayerProxy::setDirectProxy() {
// rtsp拉流
GET_CONFIG(bool, directProxy, Rtsp::kDirectProxy);
if (directProxy) {
mediaSource = std::make_shared<RtspMediaSource>(_vhost, _app, _stream_id);
mediaSource = std::make_shared<RtspMediaSource>(_tuple);
}
} else if (dynamic_pointer_cast<RtmpPlayer>(_delegate)) {
// rtmp拉流,rtmp强制直接代理
mediaSource = std::make_shared<RtmpMediaSource>(_vhost, _app, _stream_id);
mediaSource = std::make_shared<RtmpMediaSource>(_tuple);
}
if (mediaSource) {
setMediaSource(mediaSource);
......@@ -277,18 +277,18 @@ void PlayerProxy::onPlaySuccess() {
// rtsp拉流代理
if (reset_when_replay || !_muxer) {
_option.enable_rtsp = false;
_muxer = std::make_shared<MultiMediaSourceMuxer>(_vhost, _app, _stream_id, getDuration(), _option);
_muxer = std::make_shared<MultiMediaSourceMuxer>(_tuple, getDuration(), _option);
}
} else if (dynamic_pointer_cast<RtmpMediaSource>(_media_src)) {
// rtmp拉流代理
if (reset_when_replay || !_muxer) {
_option.enable_rtmp = false;
_muxer = std::make_shared<MultiMediaSourceMuxer>(_vhost, _app, _stream_id, getDuration(), _option);
_muxer = std::make_shared<MultiMediaSourceMuxer>(_tuple, getDuration(), _option);
}
} else {
// 其他拉流代理
if (reset_when_replay || !_muxer) {
_muxer = std::make_shared<MultiMediaSourceMuxer>(_vhost, _app, _stream_id, getDuration(), _option);
_muxer = std::make_shared<MultiMediaSourceMuxer>(_tuple, getDuration(), _option);
}
}
_muxer->setMediaListener(shared_from_this());
......
......@@ -134,9 +134,7 @@ private:
int _reconnect_delay_min;
int _reconnect_delay_max;
int _reconnect_delay_step;
std::string _vhost;
std::string _app;
std::string _stream_id;
MediaTuple _tuple;
std::string _pull_url;
toolkit::Timer::Ptr _timer;
std::function<void()> _on_disconnect;
......
......@@ -163,10 +163,10 @@ std::shared_ptr<FILE> 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<HlsMediaSource>(vhost, app, stream_id);
_info.app = app;
_info.stream = stream_id;
_info.vhost = vhost;
_media_src = std::make_shared<HlsMediaSource>(_info);
}
HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {
......
......@@ -25,8 +25,7 @@ public:
using RingType = toolkit::RingBuffer<std::string>;
using Ptr = std::shared_ptr<HlsMediaSource>;
HlsMediaSource(const std::string &vhost, const std::string &app, const std::string &stream_id)
: MediaSource(HLS_SCHEMA, vhost, app, stream_id) {}
HlsMediaSource(const MediaTuple& tuple): MediaSource(HLS_SCHEMA, tuple) {}
~HlsMediaSource() override = default;
/**
......
......@@ -35,8 +35,8 @@ public:
~HlsRecorder() { MpegMuxer::flush(); };
void setMediaSource(const std::string &vhost, const std::string &app, const std::string &stream_id) {
_hls->setMediaSource(vhost, app, stream_id);
void setMediaSource(const MediaTuple& tuple) {
_hls->setMediaSource(tuple.vhost, tuple.app, tuple.stream);
}
void setListener(const std::weak_ptr<MediaSourceEvent> &listener) {
......
......@@ -20,17 +20,18 @@ using namespace toolkit;
namespace mediakit {
MP4Reader::MP4Reader(const string &vhost, const string &app, const string &stream_id, const string &file_path) {
MP4Reader::MP4Reader(const std::string &vhost, const std::string &app, const std::string &stream_id, const string &file_path) {
//读写文件建议放在后台线程
auto tuple = MediaTuple{vhost, app, stream_id};
_poller = WorkThreadPool::Instance().getPoller();
_file_path = file_path;
if (_file_path.empty()) {
GET_CONFIG(string, recordPath, Protocol::kMP4SavePath);
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
if (enableVhost) {
_file_path = vhost + "/" + app + "/" + stream_id;
_file_path = tuple.shortUrl();
} else {
_file_path = app + "/" + stream_id;
_file_path = tuple.app + "/" + tuple.stream;
}
_file_path = File::absolutePath(_file_path, recordPath);
}
......@@ -38,14 +39,14 @@ MP4Reader::MP4Reader(const string &vhost, const string &app, const string &strea
_demuxer = std::make_shared<MP4Demuxer>();
_demuxer->openMP4(_file_path);
if (stream_id.empty()) {
if (tuple.stream.empty()) {
return;
}
ProtocolOption option;
//读取mp4文件并流化时,不重复生成mp4/hls文件
option.enable_mp4 = false;
option.enable_hls = false;
_muxer = std::make_shared<MultiMediaSourceMuxer>(vhost, app, stream_id, _demuxer->getDurationMS() / 1000.0f, option);
_muxer = std::make_shared<MultiMediaSourceMuxer>(tuple, _demuxer->getDurationMS() / 1000.0f, option);
auto tracks = _demuxer->getTracks(false);
if (tracks.empty()) {
throw std::runtime_error(StrPrinter << "该mp4文件没有有效的track:" << _file_path);
......
......@@ -21,16 +21,16 @@ using namespace toolkit;
namespace mediakit {
string Recorder::getRecordPath(Recorder::type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path) {
string Recorder::getRecordPath(Recorder::type type, const MediaTuple& tuple, const string &customized_path) {
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
switch (type) {
case Recorder::type_hls: {
GET_CONFIG(string, hlsPath, Protocol::kHlsSavePath);
string m3u8FilePath;
if (enableVhost) {
m3u8FilePath = vhost + "/" + app + "/" + stream_id + "/hls.m3u8";
m3u8FilePath = tuple.shortUrl() + "/hls.m3u8";
} else {
m3u8FilePath = app + "/" + stream_id + "/hls.m3u8";
m3u8FilePath = tuple.app + "/" + tuple.stream + "/hls.m3u8";
}
//Here we use the customized file path.
if (!customized_path.empty()) {
......@@ -43,9 +43,9 @@ string Recorder::getRecordPath(Recorder::type type, const string &vhost, const s
GET_CONFIG(string, recordAppName, Record::kAppName);
string mp4FilePath;
if (enableVhost) {
mp4FilePath = vhost + "/" + recordAppName + "/" + app + "/" + stream_id + "/";
mp4FilePath = tuple.vhost + "/" + recordAppName + "/" + tuple.app + "/" + tuple.stream + "/";
} else {
mp4FilePath = recordAppName + "/" + app + "/" + stream_id + "/";
mp4FilePath = recordAppName + "/" + tuple.app + "/" + tuple.stream + "/";
}
//Here we use the customized file path.
if (!customized_path.empty()) {
......@@ -58,14 +58,14 @@ string Recorder::getRecordPath(Recorder::type type, const string &vhost, const s
}
}
std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const string &vhost, const string &app, const string &stream_id, const ProtocolOption &option){
std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const MediaTuple& tuple, const ProtocolOption &option){
switch (type) {
case Recorder::type_hls: {
#if defined(ENABLE_HLS)
auto path = Recorder::getRecordPath(type, vhost, app, stream_id, option.hls_save_path);
auto path = Recorder::getRecordPath(type, tuple, option.hls_save_path);
GET_CONFIG(bool, enable_vhost, General::kEnableVhost);
auto ret = std::make_shared<HlsRecorder>(path, enable_vhost ? string(VHOST_KEY) + "=" + vhost : "", option);
ret->setMediaSource(vhost, app, stream_id);
auto ret = std::make_shared<HlsRecorder>(path, enable_vhost ? string(VHOST_KEY) + "=" + tuple.vhost : "", option);
ret->setMediaSource(tuple);
return ret;
#else
throw std::invalid_argument("hls相关功能未打开,请开启ENABLE_HLS宏后编译再测试");
......@@ -75,8 +75,8 @@ std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const st
case Recorder::type_mp4: {
#if defined(ENABLE_MP4)
auto path = Recorder::getRecordPath(type, vhost, app, stream_id, option.mp4_save_path);
return std::make_shared<MP4Recorder>(path, vhost, app, stream_id, option.mp4_max_second);
auto path = Recorder::getRecordPath(type, tuple, option.mp4_save_path);
return std::make_shared<MP4Recorder>(path, tuple.vhost, tuple.app, tuple.stream, option.mp4_max_second);
#else
throw std::invalid_argument("mp4相关功能未打开,请开启ENABLE_MP4宏后编译再测试");
#endif
......
......@@ -18,7 +18,16 @@ namespace mediakit {
class MediaSinkInterface;
class ProtocolOption;
class RecordInfo {
struct MediaTuple {
std::string vhost;
std::string app;
std::string stream;
std::string shortUrl() const {
return vhost + '/' + app + '/' + stream;
}
};
class RecordInfo: public MediaTuple {
public:
time_t start_time; // GMT 标准时间,单位秒
float time_len; // 录像长度,单位秒
......@@ -27,9 +36,6 @@ public:
std::string file_name; // 文件名称
std::string folder; // 文件夹路径
std::string url; // 播放路径
std::string app; // 应用名称
std::string stream; // 流 ID
std::string vhost; // 虚拟主机
};
class Recorder{
......@@ -50,7 +56,7 @@ public:
* @param customized_path 录像文件保存自定义根目录,为空则采用配置文件设置
* @return 录制文件绝对路径
*/
static std::string getRecordPath(type type, const std::string &vhost, const std::string &app, const std::string &stream_id,const std::string &customized_path = "");
static std::string getRecordPath(type type, const MediaTuple& tuple, const std::string &customized_path = "");
/**
* 创建录制器对象
......@@ -62,7 +68,7 @@ public:
* @param max_second mp4录制最大切片时间,单位秒,置0则采用配置文件配置
* @return 对象指针,可能为nullptr
*/
static std::shared_ptr<MediaSinkInterface> createRecorder(type type, const std::string &vhost, const std::string &app, const std::string &stream_id, const ProtocolOption &option);
static std::shared_ptr<MediaSinkInterface> createRecorder(type type, const MediaTuple& tuple, const ProtocolOption &option);
private:
Recorder() = delete;
......
......@@ -46,12 +46,7 @@ public:
* @param stream_id 流id
* @param ring_size 可以设置固定的环形缓冲大小,0则自适应
*/
RtmpMediaSource(const std::string &vhost,
const std::string &app,
const std::string &stream_id,
int ring_size = RTMP_GOP_SIZE) :
MediaSource(RTMP_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {
}
RtmpMediaSource(const MediaTuple& tuple, int ring_size = RTMP_GOP_SIZE): MediaSource(RTMP_SCHEMA, tuple), _ring_size(ring_size) {}
~RtmpMediaSource() override { flush(); }
......
......@@ -63,7 +63,7 @@ void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/)
}
RtmpMediaSourceImp::RtmpMediaSourceImp(const std::string &vhost, const std::string &app, const std::string &id, int ringSize) : RtmpMediaSource(vhost, app, id, ringSize)
RtmpMediaSourceImp::RtmpMediaSourceImp(const MediaTuple& tuple, int ringSize) : RtmpMediaSource(tuple, ringSize)
{
_demuxer = std::make_shared<RtmpDemuxer>();
_demuxer->setTrackListener(this);
......@@ -99,7 +99,7 @@ void RtmpMediaSourceImp::setProtocolOption(const ProtocolOption &option)
_option = option;
//不重复生成rtmp协议
_option.enable_rtmp = false;
_muxer = std::make_shared<MultiMediaSourceMuxer>(getVhost(), getApp(), getId(), _demuxer->getDuration(), _option);
_muxer = std::make_shared<MultiMediaSourceMuxer>(_tuple, _demuxer->getDuration(), _option);
_muxer->setMediaListener(getListener());
_muxer->setTrackListener(std::static_pointer_cast<RtmpMediaSourceImp>(shared_from_this()));
//让_muxer对象拦截一部分事件(比如说录像相关事件)
......
......@@ -35,7 +35,7 @@ public:
* @param id 流id
* @param ringSize 环形缓存大小
*/
RtmpMediaSourceImp(const std::string &vhost, const std::string &app, const std::string &id, int ringSize = RTMP_GOP_SIZE);
RtmpMediaSourceImp(const MediaTuple& tuple, int ringSize = RTMP_GOP_SIZE);
~RtmpMediaSourceImp() override = default;
......
......@@ -21,13 +21,11 @@ class RtmpMediaSourceMuxer final : public RtmpMuxer, public MediaSourceEventInte
public:
using Ptr = std::shared_ptr<RtmpMediaSourceMuxer>;
RtmpMediaSourceMuxer(const std::string &vhost,
const std::string &strApp,
const std::string &strId,
RtmpMediaSourceMuxer(const MediaTuple& tuple,
const ProtocolOption &option,
const TitleMeta::Ptr &title = nullptr) : RtmpMuxer(title) {
_option = option;
_media_src = std::make_shared<RtmpMediaSource>(vhost, strApp, strId);
_media_src = std::make_shared<RtmpMediaSource>(tuple);
getRtmpRing()->setDelegate(_media_src);
}
......
......@@ -84,7 +84,7 @@ void RtmpSession::onCmd_connect(AMFDecoder &dec) {
auto tc_url = params["tcUrl"].as_string();
if (tc_url.empty()) {
// defaultVhost:默认vhost
tc_url = string(RTMP_SCHEMA) + "://" + DEFAULT_VHOST + "/" + _media_info._app;
tc_url = string(RTMP_SCHEMA) + "://" + DEFAULT_VHOST + "/" + _media_info.app;
} else {
auto pos = tc_url.rfind('?');
if (pos != string::npos) {
......@@ -94,9 +94,9 @@ void RtmpSession::onCmd_connect(AMFDecoder &dec) {
}
// 初步解析,只用于获取vhost信息
_media_info.parse(tc_url);
_media_info._schema = RTMP_SCHEMA;
_media_info.schema = RTMP_SCHEMA;
// 赋值rtmp app
_media_info._app = params["app"].as_string();
_media_info.app = params["app"].as_string();
bool ok = true; //(app == APP_NAME);
AMFValue version(AMF_OBJECT);
......@@ -109,7 +109,7 @@ void RtmpSession::onCmd_connect(AMFDecoder &dec) {
status.set("objectEncoding", params["objectEncoding"]);
sendReply(ok ? "_result" : "_error", version, status);
if (!ok) {
throw std::runtime_error("Unsupported application: " + _media_info._app);
throw std::runtime_error("Unsupported application: " + _media_info.app);
}
AMFEncoder invoke;
......@@ -132,9 +132,9 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) {
}));
dec.load<AMFValue>();/* NULL */
// 赋值为rtmp stream id 信息
_media_info._streamid = getStreamId(dec.load<std::string>());
_media_info.stream = getStreamId(dec.load<std::string>());
// 再解析url,切割url为app/stream_id (不一定符合rtmp url切割规范)
_media_info.parse(_media_info._schema + "://" + _media_info._vhost + '/' + _media_info._app + '/' + _media_info._streamid);
_media_info.parse(_media_info.getUrl());
auto now_stream_index = _now_stream_index;
auto on_res = [this, token, now_stream_index](const string &err, const ProtocolOption &option) {
......@@ -149,7 +149,7 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) {
}
assert(!_push_src);
auto src = MediaSource::find(RTMP_SCHEMA, _media_info._vhost, _media_info._app, _media_info._streamid);
auto src = MediaSource::find(RTMP_SCHEMA, _media_info.vhost, _media_info.app, _media_info.stream);
auto push_failed = (bool)src;
while (src) {
......@@ -180,7 +180,7 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) {
}
if (!_push_src) {
_push_src = std::make_shared<RtmpMediaSourceImp>(_media_info._vhost, _media_info._app, _media_info._streamid);
_push_src = std::make_shared<RtmpMediaSourceImp>(_media_info);
//获取所有权
_push_src_ownership = _push_src->getOwnership();
_push_src->setProtocolOption(option);
......@@ -196,7 +196,7 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) {
setSocketFlags();
};
if(_media_info._app.empty() || _media_info._streamid.empty()){
if(_media_info.app.empty() || _media_info.stream.empty()){
//不允许莫名其妙的推流url
on_res("rtmp推流url非法", ProtocolOption());
return;
......@@ -256,7 +256,7 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr
sendStatus({ "level", (ok ? "status" : "error"),
"code", (ok ? "NetStream.Play.Reset" : (auth_success ? "NetStream.Play.StreamNotFound" : "NetStream.Play.BadAuth")),
"description", (ok ? "Resetting and playing." : (auth_success ? "No such stream." : err.data())),
"details", _media_info._streamid,
"details", _media_info.stream,
"clientid", "0" });
if (!ok) {
......@@ -270,7 +270,7 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr
sendStatus({ "level", "status",
"code", "NetStream.Play.Start",
"description", "Started playing." ,
"details", _media_info._streamid,
"details", _media_info.stream,
"clientid", "0"});
// |RtmpSampleAccess(true, true)
......@@ -289,7 +289,7 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr
sendStatus({ "level", "status",
"code", "NetStream.Play.PublishNotify",
"description", "Now published." ,
"details", _media_info._streamid,
"details", _media_info.stream,
"clientid", "0"});
auto &metadata = src->getMetaData();
......@@ -433,9 +433,9 @@ string RtmpSession::getStreamId(const string &str){
void RtmpSession::onCmd_play(AMFDecoder &dec) {
dec.load<AMFValue>(); /* NULL */
// 赋值为rtmp stream id 信息
_media_info._streamid = getStreamId(dec.load<std::string>());
_media_info.stream = getStreamId(dec.load<std::string>());
// 再解析url,切割url为app/stream_id (不一定符合rtmp url切割规范)
_media_info.parse(_media_info._schema + "://" + _media_info._vhost + '/' + _media_info._app + '/' + _media_info._streamid);
_media_info.parse(_media_info.getUrl());
doPlay(dec);
}
......@@ -589,7 +589,7 @@ MediaOriginType RtmpSession::getOriginType(MediaSource &sender) const{
}
string RtmpSession::getOriginUrl(MediaSource &sender) const {
return _media_info._full_url;
return _media_info.full_url;
}
std::shared_ptr<SockInfo> RtmpSession::getOriginSock(MediaSource &sender) const {
......
......@@ -134,7 +134,7 @@ bool GB28181Process::inputRtp(bool, const char *data, size_t data_len) {
// 设置dump目录
GET_CONFIG(string, dump_dir, RtpProxy::kDumpDir);
if (!dump_dir.empty()) {
auto save_path = File::absolutePath(_media_info._streamid + ".mpeg", dump_dir);
auto save_path = File::absolutePath(_media_info.stream + ".mpeg", dump_dir);
_save_file_ps.reset(File::create_file(save_path.data(), "wb"), [](FILE *fp) {
if (fp) {
fclose(fp);
......@@ -171,11 +171,11 @@ void GB28181Process::onRtpDecode(const Frame::Ptr &frame) {
// 创建解码器
if (checkTS((uint8_t *)frame->data(), frame->size())) {
// 猜测是ts负载
InfoL << _media_info._streamid << " judged to be TS";
InfoL << _media_info.stream << " judged to be TS";
_decoder = DecoderImp::createDecoder(DecoderImp::decoder_ts, _interface);
} else {
// 猜测是ps负载
InfoL << _media_info._streamid << " judged to be PS";
InfoL << _media_info.stream << " judged to be PS";
_decoder = DecoderImp::createDecoder(DecoderImp::decoder_ps, _interface);
}
}
......
......@@ -26,14 +26,14 @@ static constexpr size_t kMaxCachedFrame = 200;
namespace mediakit {
RtpProcess::RtpProcess(const string &stream_id) {
_media_info._schema = kRtpAppName;
_media_info._vhost = DEFAULT_VHOST;
_media_info._app = kRtpAppName;
_media_info._streamid = stream_id;
_media_info.schema = kRtpAppName;
_media_info.vhost = DEFAULT_VHOST;
_media_info.app = kRtpAppName;
_media_info.stream = stream_id;
GET_CONFIG(string, dump_dir, RtpProxy::kDumpDir);
{
FILE *fp = !dump_dir.empty() ? File::create_file(File::absolutePath(_media_info._streamid + ".rtp", dump_dir).data(), "wb") : nullptr;
FILE *fp = !dump_dir.empty() ? File::create_file(File::absolutePath(_media_info.stream + ".rtp", dump_dir).data(), "wb") : nullptr;
if (fp) {
_save_file_rtp.reset(fp, [](FILE *fp) {
fclose(fp);
......@@ -42,7 +42,7 @@ RtpProcess::RtpProcess(const string &stream_id) {
}
{
FILE *fp = !dump_dir.empty() ? File::create_file(File::absolutePath(_media_info._streamid + ".video", dump_dir).data(), "wb") : nullptr;
FILE *fp = !dump_dir.empty() ? File::create_file(File::absolutePath(_media_info.stream + ".video", dump_dir).data(), "wb") : nullptr;
if (fp) {
_save_file_video.reset(fp, [](FILE *fp) {
fclose(fp);
......@@ -234,7 +234,7 @@ uint16_t RtpProcess::get_local_port() {
}
string RtpProcess::getIdentifier() const {
return _media_info._streamid;
return _media_info.stream;
}
void RtpProcess::emitOnPublish() {
......@@ -251,9 +251,7 @@ void RtpProcess::emitOnPublish() {
return;
}
if (err.empty()) {
strong_self->_muxer = std::make_shared<MultiMediaSourceMuxer>(strong_self->_media_info._vhost,
strong_self->_media_info._app,
strong_self->_media_info._streamid,0.0f,
strong_self->_muxer = std::make_shared<MultiMediaSourceMuxer>(strong_self->_media_info, 0.0f,
option);
if (strong_self->_only_audio) {
strong_self->_muxer->setOnlyAudio();
......@@ -291,7 +289,7 @@ toolkit::EventPoller::Ptr RtpProcess::getOwnerPoller(MediaSource &sender) {
if (_sock) {
return _sock->getPoller();
}
throw std::runtime_error("RtpProcess::getOwnerPoller failed:" + _media_info._streamid);
throw std::runtime_error("RtpProcess::getOwnerPoller failed:" + _media_info.stream);
}
float RtpProcess::getLossRate(MediaSource &sender, TrackType type) {
......
......@@ -42,11 +42,7 @@ public:
* @param stream_id 流id
* @param ring_size 可以设置固定的环形缓冲大小,0则自适应
*/
RtspMediaSource(const std::string &vhost,
const std::string &app,
const std::string &stream_id,
int ring_size = RTP_GOP_SIZE) :
MediaSource(RTSP_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {}
RtspMediaSource(const MediaTuple& tuple, int ring_size = RTP_GOP_SIZE): MediaSource(RTSP_SCHEMA, tuple), _ring_size(ring_size) {}
~RtspMediaSource() override { flush(); }
......@@ -76,7 +72,7 @@ public:
return _sdp;
}
virtual RtspMediaSource::Ptr Clone(const std::string& stream) {
virtual RtspMediaSource::Ptr clone(const std::string& stream) {
return nullptr;
}
......
......@@ -74,8 +74,7 @@ void RtspMediaSource::onWrite(RtpPacket::Ptr rtp, bool keyPos) {
PacketCache<RtpPacket>::inputPacket(stamp, is_video, std::move(rtp), keyPos);
}
RtspMediaSourceImp::RtspMediaSourceImp(const std::string &vhost, const std::string &app, const std::string &id, int ringSize)
: RtspMediaSource(vhost, app, id, ringSize)
RtspMediaSourceImp::RtspMediaSourceImp(const MediaTuple& tuple, int ringSize): RtspMediaSource(tuple, ringSize)
{
_demuxer = std::make_shared<RtspDemuxer>();
_demuxer->setTrackListener(this);
......@@ -114,7 +113,7 @@ void RtspMediaSourceImp::setProtocolOption(const ProtocolOption &option)
//导致rtc无法播放,所以在rtsp推流rtc播放时,建议关闭直接代理模式
_option = option;
_option.enable_rtsp = !direct_proxy;
_muxer = std::make_shared<MultiMediaSourceMuxer>(getVhost(), getApp(), getId(), _demuxer->getDuration(), _option);
_muxer = std::make_shared<MultiMediaSourceMuxer>(_tuple, _demuxer->getDuration(), _option);
_muxer->setMediaListener(getListener());
_muxer->setTrackListener(std::static_pointer_cast<RtspMediaSourceImp>(shared_from_this()));
//让_muxer对象拦截一部分事件(比如说录像相关事件)
......@@ -126,8 +125,10 @@ void RtspMediaSourceImp::setProtocolOption(const ProtocolOption &option)
}
}
RtspMediaSource::Ptr RtspMediaSourceImp::Clone(const std::string &stream) {
auto src_imp = std::make_shared<RtspMediaSourceImp>(getVhost(), getApp(), stream);
RtspMediaSource::Ptr RtspMediaSourceImp::clone(const std::string &stream) {
auto tuple = _tuple;
tuple.stream = stream;
auto src_imp = std::make_shared<RtspMediaSourceImp>(tuple);
src_imp->setSdp(getSdp());
src_imp->setProtocolOption(getProtocolOption());
return src_imp;
......
......@@ -28,7 +28,7 @@ public:
* @param id 流id
* @param ringSize 环形缓存大小
*/
RtspMediaSourceImp(const std::string &vhost, const std::string &app, const std::string &id, int ringSize = RTP_GOP_SIZE);
RtspMediaSourceImp(const MediaTuple& tuple, int ringSize = RTP_GOP_SIZE);
~RtspMediaSourceImp() override = default;
......@@ -107,7 +107,7 @@ public:
}
}
RtspMediaSource::Ptr Clone(const std::string& stream) override;
RtspMediaSource::Ptr clone(const std::string& stream) override;
private:
bool _all_track_ready = false;
ProtocolOption _option;
......
......@@ -21,13 +21,11 @@ class RtspMediaSourceMuxer final : public RtspMuxer, public MediaSourceEventInte
public:
using Ptr = std::shared_ptr<RtspMediaSourceMuxer>;
RtspMediaSourceMuxer(const std::string &vhost,
const std::string &strApp,
const std::string &strId,
RtspMediaSourceMuxer(const MediaTuple& tuple,
const ProtocolOption &option,
const TitleSdp::Ptr &title = nullptr) : RtspMuxer(title) {
_option = option;
_media_src = std::make_shared<RtspMediaSource>(vhost,strApp,strId);
_media_src = std::make_shared<RtspMediaSource>(tuple);
getRtpRing()->setDelegate(_media_src);
}
......
......@@ -133,7 +133,7 @@ void RtspSession::onWholeRtspPacket(Parser &parser) {
if (_content_base.empty() && method != "GET") {
_content_base = parser.Url();
_media_info.parse(parser.FullUrl());
_media_info._schema = RTSP_SCHEMA;
_media_info.schema = RTSP_SCHEMA;
}
using rtsp_request_handler = void (RtspSession::*)(const Parser &parser);
......@@ -208,7 +208,7 @@ void RtspSession::handleReq_ANNOUNCE(const Parser &parser) {
_media_info.parse(full_url);
}
if (_media_info._app.empty() || _media_info._streamid.empty()) {
if (_media_info.app.empty() || _media_info.stream.empty()) {
//推流rtsp url必须最少两级(rtsp://host/app/stream_id),不允许莫名其妙的推流url
static constexpr auto err = "rtsp推流url非法,最少确保两级rtsp url";
sendRtspResponse("403 Forbidden", {"Content-Type", "text/plain"}, err);
......@@ -223,7 +223,7 @@ void RtspSession::handleReq_ANNOUNCE(const Parser &parser) {
}
assert(!_push_src);
auto src = MediaSource::find(RTSP_SCHEMA, _media_info._vhost, _media_info._app, _media_info._streamid);
auto src = MediaSource::find(RTSP_SCHEMA, _media_info.vhost, _media_info.app, _media_info.stream);
auto push_failed = (bool)src;
while (src) {
......@@ -266,7 +266,7 @@ void RtspSession::handleReq_ANNOUNCE(const Parser &parser) {
}
if (!_push_src) {
_push_src = std::make_shared<RtspMediaSourceImp>(_media_info._vhost, _media_info._app, _media_info._streamid);
_push_src = std::make_shared<RtspMediaSourceImp>(_media_info);
//获取所有权
_push_src_ownership = _push_src->getOwnership();
_push_src->setProtocolOption(option);
......@@ -717,7 +717,7 @@ void RtspSession::handleReq_Setup(const Parser &parser) {
break;
case Rtsp::RTP_MULTICAST: {
if(!_multicaster){
_multicaster = RtpMultiCaster::get(*this, get_local_ip(), _media_info._vhost, _media_info._app, _media_info._streamid, _multicast_ip, _multicast_video_port, _multicast_audio_port);
_multicaster = RtpMultiCaster::get(*this, get_local_ip(), _media_info.vhost, _media_info.app, _media_info.stream, _multicast_ip, _multicast_video_port, _multicast_audio_port);
if (!_multicaster) {
send_NotAcceptable();
throw SockException(Err_shutdown, "can not get a available udp multicast socket");
......@@ -1145,7 +1145,7 @@ MediaOriginType RtspSession::getOriginType(MediaSource &sender) const{
}
string RtspSession::getOriginUrl(MediaSource &sender) const {
return _media_info._full_url;
return _media_info.full_url;
}
std::shared_ptr<SockInfo> RtspSession::getOriginSock(MediaSource &sender) const {
......
......@@ -39,10 +39,7 @@ public:
using RingDataType = std::shared_ptr<toolkit::List<TSPacket::Ptr> >;
using RingType = toolkit::RingBuffer<RingDataType>;
TSMediaSource(const std::string &vhost,
const std::string &app,
const std::string &stream_id,
int ring_size = TS_GOP_SIZE) : MediaSource(TS_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {}
TSMediaSource(const MediaTuple& tuple, int ring_size = TS_GOP_SIZE): MediaSource(TS_SCHEMA, tuple), _ring_size(ring_size) {}
~TSMediaSource() override { flush(); }
......
......@@ -21,12 +21,9 @@ class TSMediaSourceMuxer final : public MpegMuxer, public MediaSourceEventInterc
public:
using Ptr = std::shared_ptr<TSMediaSourceMuxer>;
TSMediaSourceMuxer(const std::string &vhost,
const std::string &app,
const std::string &stream_id,
const ProtocolOption &option) : MpegMuxer(false) {
TSMediaSourceMuxer(const MediaTuple& tuple, const ProtocolOption &option) : MpegMuxer(false) {
_option = option;
_media_src = std::make_shared<TSMediaSource>(vhost, app, stream_id);
_media_src = std::make_shared<TSMediaSource>(tuple);
}
~TSMediaSourceMuxer() override { MpegMuxer::flush(); };
......
......@@ -35,7 +35,7 @@ void SrtTransportImp::onHandShakeFinished(std::string &streamid, struct sockaddr
return;
}
auto params = Parser::parseArgs(_media_info._param_strs);
auto params = Parser::parseArgs(_media_info.param_strs);
if (params["m"] == "publish") {
_is_pusher = true;
_decoder = DecoderImp::createDecoder(DecoderImp::decoder_ts, this);
......@@ -52,7 +52,7 @@ bool SrtTransportImp::parseStreamid(std::string &streamid) {
if (!toolkit::start_with(streamid, "#!::")) {
return false;
}
_media_info._schema = SRT_SCHEMA;
_media_info.schema = SRT_SCHEMA;
std::string real_streamid = streamid.substr(4);
std::string vhost, app, stream_name;
......@@ -70,10 +70,10 @@ bool SrtTransportImp::parseStreamid(std::string &streamid) {
app = tmps[0];
stream_name = tmps[1];
} else {
if (_media_info._param_strs.empty()) {
_media_info._param_strs = it.first + "=" + it.second;
if (_media_info.param_strs.empty()) {
_media_info.param_strs = it.first + "=" + it.second;
} else {
_media_info._param_strs += "&" + it.first + "=" + it.second;
_media_info.param_strs += "&" + it.first + "=" + it.second;
}
}
}
......@@ -82,15 +82,15 @@ bool SrtTransportImp::parseStreamid(std::string &streamid) {
}
if (vhost != "") {
_media_info._vhost = vhost;
_media_info.vhost = vhost;
} else {
_media_info._vhost = DEFAULT_VHOST;
_media_info.vhost = DEFAULT_VHOST;
}
_media_info._app = app;
_media_info._streamid = stream_name;
_media_info.app = app;
_media_info.stream = stream_name;
TraceL << " mediainfo=" << _media_info.shortUrl() << " params=" << _media_info._param_strs;
TraceL << " mediainfo=" << _media_info.shortUrl() << " params=" << _media_info.param_strs;
return true;
}
......@@ -136,7 +136,7 @@ mediakit::MediaOriginType SrtTransportImp::getOriginType(mediakit::MediaSource &
// 获取媒体源url或者文件路径
std::string SrtTransportImp::getOriginUrl(mediakit::MediaSource &sender) const {
return _media_info._full_url;
return _media_info.full_url;
}
// 获取媒体源客户端相关信息
......@@ -157,9 +157,7 @@ void SrtTransportImp::emitOnPublish() {
return;
}
if (err.empty()) {
strong_self->_muxer = std::make_shared<MultiMediaSourceMuxer>(strong_self->_media_info._vhost,
strong_self->_media_info._app,
strong_self->_media_info._streamid,0.0f,
strong_self->_muxer = std::make_shared<MultiMediaSourceMuxer>(strong_self->_media_info,0.0f,
option);
strong_self->_muxer->setMediaListener(strong_self);
strong_self->doCachedFunc();
......@@ -207,7 +205,7 @@ void SrtTransportImp::emitOnPlay() {
void SrtTransportImp::doPlay() {
// 异步查找直播流
MediaInfo info = _media_info;
info._schema = TS_SCHEMA;
info.schema = TS_SCHEMA;
std::weak_ptr<SrtTransportImp> weak_self = static_pointer_cast<SrtTransportImp>(shared_from_this());
MediaSource::findAsync(info, getSession(), [weak_self](const MediaSource::Ptr &src) {
auto strong_self = weak_self.lock();
......@@ -281,7 +279,7 @@ uint16_t SrtTransportImp::get_local_port() {
}
std::string SrtTransportImp::getIdentifier() const {
return _media_info._streamid;
return _media_info.stream;
}
bool SrtTransportImp::inputFrame(const Frame::Ptr &frame) {
......@@ -293,7 +291,7 @@ bool SrtTransportImp::inputFrame(const Frame::Ptr &frame) {
auto diff = _type_to_stamp[TrackType::TrackVideo].getRelativeStamp() - _type_to_stamp[TrackType::TrackAudio].getRelativeStamp();
if(std::abs(diff) > 5000){
// 超过5s,应该同步 TODO
WarnL << _media_info._full_url<<" video or audio not sync : "<<diff;
WarnL << _media_info.full_url <<" video or audio not sync : "<<diff;
}
}
//TraceL<<"after type "<<frame_tmp->getCodecName()<<" dts "<<frame_tmp->dts()<<" pts "<<frame_tmp->pts();
......
......@@ -90,8 +90,8 @@ void initEventListener() {
static onceToken s_token([]() {
//监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问
NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastOnGetRtspRealm, [](BroadcastOnGetRtspRealmArgs) {
DebugL << "RTSP是否需要鉴权事件:" << args.getUrl() << " " << args._param_strs;
if (string("1") == args._streamid) {
DebugL << "RTSP是否需要鉴权事件:" << args.getUrl() << " " << args.param_strs;
if (string("1") == args.stream) {
// live/1需要认证
//该流需要认证,并且设置realm
invoker(REALM);
......@@ -104,7 +104,7 @@ void initEventListener() {
//监听kBroadcastOnRtspAuth事件返回正确的rtsp鉴权用户密码
NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastOnRtspAuth, [](BroadcastOnRtspAuthArgs) {
DebugL << "RTSP播放鉴权:" << args.getUrl() << " " << args._param_strs;
DebugL << "RTSP播放鉴权:" << args.getUrl() << " " << args.param_strs;
DebugL << "RTSP用户:" << user_name << (must_no_encrypt ? " Base64" : " MD5") << " 方式登录";
string user = user_name;
//假设我们异步读取数据库
......@@ -134,14 +134,14 @@ void initEventListener() {
//监听rtsp/rtmp推流事件,返回结果告知是否有推流权限
NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) {
DebugL << "推流鉴权:" << args.getUrl() << " " << args._param_strs;
DebugL << "推流鉴权:" << args.getUrl() << " " << args.param_strs;
invoker("", ProtocolOption());//鉴权成功
//invoker("this is auth failed message");//鉴权失败
});
//监听rtsp/rtsps/rtmp/http-flv播放事件,返回结果告知是否有播放权限(rtsp通过kBroadcastOnRtspAuth或此事件都可以实现鉴权)
NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) {
DebugL << "播放鉴权:" << args.getUrl() << " " << args._param_strs;
DebugL << "播放鉴权:" << args.getUrl() << " " << args.param_strs;
invoker("");//鉴权成功
//invoker("this is auth failed message");//鉴权失败
});
......@@ -182,14 +182,13 @@ void initEventListener() {
* 你可以在这个事件触发时再去拉流,这样就可以实现按需拉流
* 拉流成功后,ZLMediaKit会把其立即转发给播放器(最大等待时间约为5秒,如果5秒都未拉流成功,播放器会播放失败)
*/
DebugL << "未找到流事件:" << args.getUrl() << " " << args._param_strs;
DebugL << "未找到流事件:" << args.getUrl() << " " << args.param_strs;
});
//监听播放或推流结束时消耗流量事件
NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) {
DebugL << "播放器(推流器)断开连接事件:" << args.getUrl() << " " << args._param_strs
<< "\r\n使用流量:" << totalBytes << " bytes,连接时长:" << totalDuration << "秒";
DebugL << "播放器(推流器)断开连接事件:" << args.getUrl() << " " << args.param_strs << "\r\n使用流量:" << totalBytes << " bytes,连接时长:" << totalDuration << "秒";
});
......
......@@ -71,7 +71,7 @@ MediaOriginType WebRtcPusher::getOriginType(MediaSource &sender) const {
}
string WebRtcPusher::getOriginUrl(MediaSource &sender) const {
return _media_info._full_url;
return _media_info.full_url;
}
std::shared_ptr<SockInfo> WebRtcPusher::getOriginSock(MediaSource &sender) const {
......@@ -99,7 +99,7 @@ void WebRtcPusher::onRecvRtp(MediaTrack &track, const string &rid, RtpPacket::Pt
auto &src = _push_src_sim[rid];
if (!src) {
auto stream_id = rid.empty() ? _push_src->getId() : _push_src->getId() + "_" + rid;
auto src_imp = _push_src->Clone(stream_id);
auto src_imp = _push_src->clone(stream_id);
_push_src_sim_ownership[rid] = src_imp->getOwnership();
src_imp->setListener(static_pointer_cast<WebRtcPusher>(shared_from_this()));
src = src_imp;
......
......@@ -1179,7 +1179,7 @@ void push_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginMana
RtspMediaSourceImp::Ptr push_src;
std::shared_ptr<void> push_src_ownership;
auto src = MediaSource::find(RTSP_SCHEMA, info._vhost, info._app, info._streamid);
auto src = MediaSource::find(RTSP_SCHEMA, info.vhost, info.app, info.stream);
auto push_failed = (bool)src;
while (src) {
......@@ -1206,7 +1206,7 @@ void push_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginMana
}
if (!push_src) {
push_src = std::make_shared<RtspMediaSourceImp>(info._vhost, info._app, info._streamid);
push_src = std::make_shared<RtspMediaSourceImp>(info);
push_src_ownership = push_src->getOwnership();
push_src->setProtocolOption(option);
}
......@@ -1235,7 +1235,7 @@ void play_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginMana
}
// webrtc播放的是rtsp的源
info._schema = RTSP_SCHEMA;
info.schema = RTSP_SCHEMA;
MediaSource::findAsync(info, session_ptr, [=](const MediaSource::Ptr &src_in) mutable {
auto src = dynamic_pointer_cast<RtspMediaSource>(src_in);
if (!src) {
......@@ -1243,7 +1243,7 @@ void play_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginMana
return;
}
// 还原成rtc,目的是为了hook时识别哪种播放协议
info._schema = RTC_SCHEMA;
info.schema = RTC_SCHEMA;
auto rtc = WebRtcPlayer::create(EventPollerPool::Instance().getPoller(), src, info, preferred_tcp);
cb(*rtc);
});
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论