|
|
|
@ -785,17 +785,6 @@ SrsDvrPlan* SrsDvrPlan::create_plan(string vhost)
|
|
|
|
|
return new SrsDvrSessionPlan();
|
|
|
|
|
} else if (plan == SRS_CONF_DEFAULT_DVR_PLAN_APPEND) {
|
|
|
|
|
return new SrsDvrAppendPlan();
|
|
|
|
|
} else if (plan == SRS_CONF_DEFAULT_DVR_PLAN_API) {
|
|
|
|
|
/**
|
|
|
|
|
* @remark the api plan maybe create by publish event or http api post create dvr event.
|
|
|
|
|
* so when we got from pool first when create it.
|
|
|
|
|
*/
|
|
|
|
|
SrsApiDvrPool* pool = SrsApiDvrPool::instance();
|
|
|
|
|
SrsDvrApiPlan* plan = pool->get_dvr(vhost);
|
|
|
|
|
if (plan) {
|
|
|
|
|
return plan;
|
|
|
|
|
}
|
|
|
|
|
return new SrsDvrApiPlan();
|
|
|
|
|
} else {
|
|
|
|
|
srs_error("invalid dvr plan=%s, vhost=%s", plan.c_str(), vhost.c_str());
|
|
|
|
|
srs_assert(false);
|
|
|
|
@ -852,318 +841,6 @@ void SrsDvrSessionPlan::on_unpublish()
|
|
|
|
|
dvr_enabled = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SrsDvrApiPlan::SrsDvrApiPlan()
|
|
|
|
|
{
|
|
|
|
|
autostart = false;
|
|
|
|
|
started = false;
|
|
|
|
|
|
|
|
|
|
metadata = sh_audio = sh_video = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SrsDvrApiPlan::~SrsDvrApiPlan()
|
|
|
|
|
{
|
|
|
|
|
SrsApiDvrPool* pool = SrsApiDvrPool::instance();
|
|
|
|
|
pool->detach_dvr(this);
|
|
|
|
|
|
|
|
|
|
srs_freep(metadata);
|
|
|
|
|
srs_freep(sh_audio);
|
|
|
|
|
srs_freep(sh_video);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsDvrApiPlan::initialize(SrsRequest* r)
|
|
|
|
|
{
|
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
|
|
if ((ret = SrsDvrPlan::initialize(r)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SrsApiDvrPool* pool = SrsApiDvrPool::instance();
|
|
|
|
|
if ((ret = pool->add_dvr(this)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
autostart = _srs_config->get_dvr_autostart(r->vhost);
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsDvrApiPlan::on_publish()
|
|
|
|
|
{
|
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
|
|
// support multiple publish.
|
|
|
|
|
if (dvr_enabled) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!_srs_config->get_dvr_enabled(req->vhost)) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// api disabled dvr when not autostart.
|
|
|
|
|
bool autostart = _srs_config->get_dvr_autostart(req->vhost);
|
|
|
|
|
if (!autostart && !started) {
|
|
|
|
|
srs_warn("dvr: api not start and disabled for not autostart.");
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dvr_enabled = true;
|
|
|
|
|
|
|
|
|
|
if ((ret = segment->close()) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((ret = segment->open()) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// update sequence header
|
|
|
|
|
if (metadata && (ret = SrsDvrPlan::on_meta_data(metadata)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
if (sh_video && (ret = SrsDvrPlan::on_video(sh_video)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
if (sh_audio && (ret = SrsDvrPlan::on_audio(sh_audio)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SrsDvrApiPlan::on_unpublish()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsDvrApiPlan::on_meta_data(SrsSharedPtrMessage* __metadata)
|
|
|
|
|
{
|
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
|
|
srs_freep(metadata);
|
|
|
|
|
metadata = __metadata->copy();
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsDvrApiPlan::on_audio(SrsSharedPtrMessage* __audio)
|
|
|
|
|
{
|
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
|
|
if (SrsFlvCodec::audio_is_sequence_header(__audio->payload, __audio->size)) {
|
|
|
|
|
srs_freep(sh_audio);
|
|
|
|
|
sh_audio = __audio->copy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((ret = SrsDvrPlan::on_audio(__audio)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsDvrApiPlan::on_video(SrsSharedPtrMessage* __video)
|
|
|
|
|
{
|
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
|
|
if (SrsFlvCodec::video_is_sequence_header(__video->payload, __video->size)) {
|
|
|
|
|
srs_freep(sh_video);
|
|
|
|
|
sh_video = __video->copy();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((ret = check_user_actions(__video)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((ret = SrsDvrPlan::on_video(__video)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsDvrApiPlan::set_plan()
|
|
|
|
|
{
|
|
|
|
|
_srs_config->set_dvr_plan(req->vhost, SRS_CONF_DEFAULT_DVR_PLAN_API);
|
|
|
|
|
return ERROR_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsDvrApiPlan::set_path_tmpl(string path_tmpl)
|
|
|
|
|
{
|
|
|
|
|
_srs_config->set_dvr_path(req->vhost, path_tmpl);
|
|
|
|
|
return ERROR_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsDvrApiPlan::set_callback(string value)
|
|
|
|
|
{
|
|
|
|
|
_srs_config->set_vhost_http_hooks_enabled(req->vhost, true);
|
|
|
|
|
_srs_config->set_vhost_on_dvr(req->vhost, value);
|
|
|
|
|
return ERROR_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsDvrApiPlan::set_wait_keyframe(bool wait_keyframe)
|
|
|
|
|
{
|
|
|
|
|
_srs_config->set_dvr_wait_keyframe(req->vhost, wait_keyframe);
|
|
|
|
|
return ERROR_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsDvrApiPlan::start()
|
|
|
|
|
{
|
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
|
|
if (started) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// enable the config.
|
|
|
|
|
_srs_config->set_dvr_enabled(req->vhost, true);
|
|
|
|
|
|
|
|
|
|
// stop dvr
|
|
|
|
|
if (dvr_enabled) {
|
|
|
|
|
// ignore error.
|
|
|
|
|
int ret = segment->close();
|
|
|
|
|
if (ret != ERROR_SUCCESS) {
|
|
|
|
|
srs_warn("ignore flv close error. ret=%d", ret);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dvr_enabled = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// start dvr
|
|
|
|
|
if ((ret = on_publish()) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
started = true;
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsDvrApiPlan::dumps(stringstream& ss)
|
|
|
|
|
{
|
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
|
|
bool wait_keyframe = _srs_config->get_dvr_wait_keyframe(req->vhost);
|
|
|
|
|
std::string path_template = _srs_config->get_dvr_path(req->vhost);
|
|
|
|
|
SrsConfDirective* callbacks = _srs_config->get_vhost_on_dvr(req->vhost);
|
|
|
|
|
|
|
|
|
|
ss << __SRS_JOBJECT_START
|
|
|
|
|
<< __SRS_JFIELD_STR("path_tmpl", path_template) << __SRS_JFIELD_CONT
|
|
|
|
|
<< __SRS_JFIELD_STR("path_dvr", segment->get_path()) << __SRS_JFIELD_CONT
|
|
|
|
|
<< __SRS_JFIELD_BOOL("wait_keyframe", wait_keyframe) << __SRS_JFIELD_CONT
|
|
|
|
|
<< __SRS_JFIELD_STR("vhost", req->vhost) << __SRS_JFIELD_CONT
|
|
|
|
|
<< __SRS_JFIELD_STR("app", req->app) << __SRS_JFIELD_CONT
|
|
|
|
|
<< __SRS_JFIELD_STR("stream", req->stream) << __SRS_JFIELD_CONT
|
|
|
|
|
<< __SRS_JFIELD_STR("callback", callbacks->arg0()) << __SRS_JFIELD_CONT
|
|
|
|
|
<< __SRS_JFIELD_STR("status", (dvr_enabled? "start":"stop"))
|
|
|
|
|
<< __SRS_JOBJECT_END;
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsDvrApiPlan::stop()
|
|
|
|
|
{
|
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
|
|
_srs_config->set_dvr_enabled(req->vhost, false);
|
|
|
|
|
started = false;
|
|
|
|
|
|
|
|
|
|
// stop dvr
|
|
|
|
|
if (dvr_enabled) {
|
|
|
|
|
// ignore error.
|
|
|
|
|
int ret = segment->close();
|
|
|
|
|
if (ret != ERROR_SUCCESS) {
|
|
|
|
|
srs_warn("ignore flv close error. ret=%d", ret);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dvr_enabled = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
srs_trace("dvr: stop dvr of vhost=%s", req->vhost.c_str());
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsDvrApiPlan::rpc(SrsJsonObject* obj)
|
|
|
|
|
{
|
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
|
|
SrsJsonAny* prop = NULL;
|
|
|
|
|
if ((prop = obj->ensure_property_string("action")) == NULL) {
|
|
|
|
|
ret = ERROR_HTTP_DVR_REQUEST;
|
|
|
|
|
srs_error("dvr: rpc required action request. ret=%d", ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
action = prop->to_str();
|
|
|
|
|
if (action == SRS_DVR_USER_ACTION_REAP_SEGMENT) {
|
|
|
|
|
if ((prop = obj->ensure_property_string("path_tmpl")) != NULL) {
|
|
|
|
|
path_template = prop->to_str();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
ret = ERROR_HTTP_DVR_REQUEST;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsDvrApiPlan::check_user_actions(SrsSharedPtrMessage* msg)
|
|
|
|
|
{
|
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
|
|
srs_assert(segment);
|
|
|
|
|
|
|
|
|
|
if (action == SRS_DVR_USER_ACTION_REAP_SEGMENT) {
|
|
|
|
|
// when wait keyframe, ignore if no frame arrived.
|
|
|
|
|
// @see https://github.com/winlinvip/simple-rtmp-server/issues/177
|
|
|
|
|
if (_srs_config->get_dvr_wait_keyframe(req->vhost)) {
|
|
|
|
|
if (!msg->is_video()) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char* payload = msg->payload;
|
|
|
|
|
int size = msg->size;
|
|
|
|
|
bool is_key_frame = SrsFlvCodec::video_is_h264(payload, size)
|
|
|
|
|
&& SrsFlvCodec::video_is_keyframe(payload, size)
|
|
|
|
|
&& !SrsFlvCodec::video_is_sequence_header(payload, size);
|
|
|
|
|
if (!is_key_frame) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// reap segment
|
|
|
|
|
if ((ret = segment->close()) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// use new path template if user specified.
|
|
|
|
|
if (!path_template.empty() && (ret = set_path_tmpl(path_template)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// open new flv file
|
|
|
|
|
if ((ret = segment->open()) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// update sequence header
|
|
|
|
|
if (metadata && (ret = SrsDvrPlan::on_meta_data(metadata)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
if (sh_video && (ret = SrsDvrPlan::on_video(sh_video)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
if (sh_audio && (ret = SrsDvrPlan::on_audio(sh_audio)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// reset rcp params.
|
|
|
|
|
action = "";
|
|
|
|
|
path_template = "";
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SrsDvrAppendPlan::SrsDvrAppendPlan()
|
|
|
|
|
{
|
|
|
|
|
last_update_time = 0;
|
|
|
|
@ -1420,290 +1097,6 @@ int SrsDvrSegmentPlan::update_duration(SrsSharedPtrMessage* msg)
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SrsApiDvrPool* SrsApiDvrPool::_instance = new SrsApiDvrPool();
|
|
|
|
|
|
|
|
|
|
SrsApiDvrPool* SrsApiDvrPool::instance()
|
|
|
|
|
{
|
|
|
|
|
return SrsApiDvrPool::_instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SrsApiDvrPool::SrsApiDvrPool()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SrsApiDvrPool::~SrsApiDvrPool()
|
|
|
|
|
{
|
|
|
|
|
dvrs.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SrsDvrApiPlan* SrsApiDvrPool::get_dvr(string vhost)
|
|
|
|
|
{
|
|
|
|
|
std::vector<SrsDvrApiPlan*>::iterator it;
|
|
|
|
|
for (it = dvrs.begin(); it != dvrs.end(); ++it) {
|
|
|
|
|
SrsDvrApiPlan* plan = *it;
|
|
|
|
|
if (plan->req->vhost == vhost) {
|
|
|
|
|
return plan;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsApiDvrPool::add_dvr(SrsDvrApiPlan* dvr)
|
|
|
|
|
{
|
|
|
|
|
dvrs.push_back(dvr);
|
|
|
|
|
return ERROR_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SrsApiDvrPool::detach_dvr(SrsDvrApiPlan* dvr)
|
|
|
|
|
{
|
|
|
|
|
std::vector<SrsDvrApiPlan*>::iterator it;
|
|
|
|
|
it = ::find(dvrs.begin(), dvrs.end(), dvr);
|
|
|
|
|
|
|
|
|
|
if (it != dvrs.end()) {
|
|
|
|
|
dvrs.erase(it);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsApiDvrPool::dumps(string vhost, string app, string stream, stringstream& ss)
|
|
|
|
|
{
|
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
|
|
ss << __SRS_JARRAY_START;
|
|
|
|
|
|
|
|
|
|
std::vector<SrsDvrApiPlan*> plans;
|
|
|
|
|
for (int i = 0; i < (int)dvrs.size(); i++) {
|
|
|
|
|
SrsDvrApiPlan* plan = dvrs.at(i);
|
|
|
|
|
if (!vhost.empty() && plan->req->vhost != vhost) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!app.empty() && plan->req->app != app) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!stream.empty() && plan->req->stream != stream) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
plans.push_back(plan);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < (int)plans.size(); i++) {
|
|
|
|
|
SrsDvrApiPlan* plan = plans.at(i);
|
|
|
|
|
|
|
|
|
|
if ((ret = plan->dumps(ss)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (i < (int)plans.size() - 1) {
|
|
|
|
|
ss << __SRS_JFIELD_CONT;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ss << __SRS_JARRAY_END;
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsApiDvrPool::create(SrsJsonAny* json)
|
|
|
|
|
{
|
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
|
|
srs_assert(json);
|
|
|
|
|
if (!json->is_object()) {
|
|
|
|
|
ret = ERROR_HTTP_DVR_CREATE_REQUEST;
|
|
|
|
|
srs_error("dvr: api create dvr request requires json object. ret=%d", ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SrsJsonObject* obj = json->to_object();
|
|
|
|
|
SrsJsonAny* prop = NULL;
|
|
|
|
|
if ((prop = obj->ensure_property_string("vhost")) == NULL) {
|
|
|
|
|
ret = ERROR_HTTP_DVR_CREATE_REQUEST;
|
|
|
|
|
srs_error("dvr: api create dvr request requires vhost. ret=%d", ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
std::string vhost = prop->to_str();
|
|
|
|
|
|
|
|
|
|
if ((prop = obj->ensure_property_string("app")) == NULL) {
|
|
|
|
|
ret = ERROR_HTTP_DVR_CREATE_REQUEST;
|
|
|
|
|
srs_error("dvr: api create dvr request requires app. ret=%d", ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
std::string app = prop->to_str();
|
|
|
|
|
|
|
|
|
|
if ((prop = obj->ensure_property_string("stream")) == NULL) {
|
|
|
|
|
ret = ERROR_HTTP_DVR_CREATE_REQUEST;
|
|
|
|
|
srs_error("dvr: api create dvr request requires stream. ret=%d", ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
std::string stream = prop->to_str();
|
|
|
|
|
|
|
|
|
|
if (vhost.empty() || app.empty() || stream.empty()) {
|
|
|
|
|
ret = ERROR_HTTP_DVR_CREATE_REQUEST;
|
|
|
|
|
srs_error("dvr: api create dvr request requires vhost/app/stream. ret=%d", ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SrsDvrApiPlan* dvr = NULL;
|
|
|
|
|
for (int i = 0; i < (int)dvrs.size(); i++) {
|
|
|
|
|
SrsDvrApiPlan* plan = dvrs.at(i);
|
|
|
|
|
if (plan->req->vhost != vhost || plan->req->app != app || plan->req->stream != stream) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
dvr = plan;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// mock the client request for dvr.
|
|
|
|
|
SrsRequest* req = new SrsRequest();
|
|
|
|
|
SrsAutoFree(SrsRequest, req);
|
|
|
|
|
|
|
|
|
|
// should notice the source to reload dvr when already publishing.
|
|
|
|
|
SrsSource* source = NULL;
|
|
|
|
|
|
|
|
|
|
// create if not exists
|
|
|
|
|
if (!dvr) {
|
|
|
|
|
dvr = new SrsDvrApiPlan();
|
|
|
|
|
|
|
|
|
|
req->vhost = vhost;
|
|
|
|
|
req->app = app;
|
|
|
|
|
req->stream = stream;
|
|
|
|
|
req->tcUrl = "rtmp://" + vhost + "/" + app + "/" + stream;
|
|
|
|
|
|
|
|
|
|
// fetch source from pool.
|
|
|
|
|
// NULL, create without source, ignore.
|
|
|
|
|
// start dvr when already publishing.
|
|
|
|
|
source = SrsSource::fetch(req);
|
|
|
|
|
|
|
|
|
|
// initialize for dvr pool to create it.
|
|
|
|
|
if ((ret = dvr->initialize(req)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// update optional parameters for plan.
|
|
|
|
|
if ((ret = dvr->set_plan()) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
if ((prop = obj->ensure_property_string("path_tmpl")) != NULL) {
|
|
|
|
|
if ((ret = dvr->set_path_tmpl(prop->to_str())) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ((prop = obj->ensure_property_boolean("wait_keyframe")) != NULL) {
|
|
|
|
|
if ((ret = dvr->set_wait_keyframe(prop->to_boolean())) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ((prop = obj->ensure_property_string("callback")) != NULL) {
|
|
|
|
|
if ((ret = dvr->set_callback(prop->to_str())) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((ret = dvr->start()) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// do reload for source when already publishing.
|
|
|
|
|
// when reload, the source will use the request instead.
|
|
|
|
|
if (source) {
|
|
|
|
|
if ((ret = source->on_reload_vhost_dvr(vhost)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsApiDvrPool::stop(string vhost, string app, string stream)
|
|
|
|
|
{
|
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
|
|
std::vector<SrsDvrApiPlan*> plans;
|
|
|
|
|
for (int i = 0; i < (int)dvrs.size(); i++) {
|
|
|
|
|
SrsDvrApiPlan* plan = dvrs.at(i);
|
|
|
|
|
if (!vhost.empty() && plan->req->vhost != vhost) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!app.empty() && plan->req->app != app) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!stream.empty() && plan->req->stream != stream) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
plans.push_back(plan);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (plans.empty()) {
|
|
|
|
|
ret = ERROR_HTTP_DVR_NO_TAEGET;
|
|
|
|
|
srs_error("dvr: stop not found for url=%s/%s/%s, ret=%d", vhost.c_str(), app.c_str(), stream.c_str(), ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < (int)plans.size(); i++) {
|
|
|
|
|
SrsDvrApiPlan* plan = plans.at(i);
|
|
|
|
|
|
|
|
|
|
if ((ret = plan->stop()) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SrsApiDvrPool::rpc(SrsJsonAny* json)
|
|
|
|
|
{
|
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
|
|
if (!json->is_object()) {
|
|
|
|
|
ret = ERROR_HTTP_DVR_REQUEST;
|
|
|
|
|
srs_error("dvr: rpc required object request. ret=%d", ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SrsJsonObject* obj = json->to_object();
|
|
|
|
|
|
|
|
|
|
SrsJsonAny* prop = NULL;
|
|
|
|
|
if ((prop = obj->ensure_property_string("vhost")) == NULL) {
|
|
|
|
|
ret = ERROR_HTTP_DVR_REQUEST;
|
|
|
|
|
srs_error("dvr: rpc required vhost request. ret=%d", ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
std::string vhost = prop->to_str();
|
|
|
|
|
std::string app, stream;
|
|
|
|
|
if ((prop = obj->ensure_property_string("app")) != NULL) {
|
|
|
|
|
app = prop->to_str();
|
|
|
|
|
}
|
|
|
|
|
if ((prop = obj->ensure_property_string("stream")) != NULL) {
|
|
|
|
|
stream = prop->to_str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<SrsDvrApiPlan*> plans;
|
|
|
|
|
for (int i = 0; i < (int)dvrs.size(); i++) {
|
|
|
|
|
SrsDvrApiPlan* plan = dvrs.at(i);
|
|
|
|
|
if (!vhost.empty() && plan->req->vhost != vhost) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
plans.push_back(plan);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (plans.empty()) {
|
|
|
|
|
ret = ERROR_HTTP_DVR_NO_TAEGET;
|
|
|
|
|
srs_error("dvr: rpc not found for url=%s/%s/%s, ret=%d", vhost.c_str(), app.c_str(), stream.c_str(), ret);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < (int)plans.size(); i++) {
|
|
|
|
|
SrsDvrApiPlan* plan = plans.at(i);
|
|
|
|
|
|
|
|
|
|
if ((ret = plan->rpc(obj)) != ERROR_SUCCESS) {
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SrsDvr::SrsDvr()
|
|
|
|
|
{
|
|
|
|
|
source = NULL;
|
|
|
|
|