From 849e59b05d6718458d3dadc20e028e27c88bb972 Mon Sep 17 00:00:00 2001 From: winlin Date: Sat, 21 Feb 2015 23:09:21 +0800 Subject: [PATCH] for #179, support http api to start dvr. --- trunk/conf/full.conf | 11 +- trunk/src/app/srs_app_config.cpp | 46 ++++++ trunk/src/app/srs_app_config.hpp | 10 ++ trunk/src/app/srs_app_dvr.cpp | 211 ++++++++++++++++++++++++-- trunk/src/app/srs_app_dvr.hpp | 28 +++- trunk/src/app/srs_app_http_api.cpp | 15 +- trunk/src/app/srs_app_json.cpp | 15 ++ trunk/src/app/srs_app_json.hpp | 3 + trunk/src/kernel/srs_kernel_error.hpp | 2 + 9 files changed, 320 insertions(+), 21 deletions(-) diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index 1d138345a..cc6feb699 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -296,15 +296,14 @@ vhost dvr.srs.com { # request params, for example ?vhost=__defaultVhost__, where: # vhost, query all dvr of this vhost. # response in json, where: - # {code:0, dvrs: [{plan:"api", path:"./objs/nginx/html", - # autostart:true, wait_keyframe:true, jitter:"full" + # {code:0, dvrs: [{path_tmpl:"./[15].[04].[05].[999].flv", path_dvr:"./22.7.43.312.flv", + # wait_keyframe:true, vhost:"__defaultVhost", callback:"http://dvr/callback" # }]} # method=POST # to start dvr of specified vhost. # request should encode in json, specifies the dvr to create, where: - # {plan:"api", path:"./objs/nginx/html", - # autostart:true, wait_keyframe:true, jitter:"full", - # vhost:"__defaultVhost", callback:"http://dvr/callback" + # {path_tmpl:"./[15].[04].[05].[999].flv", + # wait_keyframe:true, vhost:"__defaultVhost", callback:"http://dvr/callback" # } # response in json, where: # {code:0} @@ -329,7 +328,7 @@ vhost dvr.srs.com { # [05], repleace this const to current second. # [999], repleace this const to current millisecond. # [timestamp],replace this const to current UNIX timestamp in ms. - # @remark we use golang time format "2006-01-02 15:04:05.999" + # @remark we use golang time format "2006-01-02 15:04:05.999" as "[2006]-[01]-[02]_[15].[04].[05]_[999]" # for example, for url rtmp://ossrs.net/live/livestream and time 2015-01-03 10:57:30.776 # 1. No variables, the rule of SRS1.0(auto add [stream].[timestamp].flv as filename): # dvr_path ./objs/nginx/html; diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index 2387cb652..c0059ad8c 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -1977,6 +1977,38 @@ int SrsConfig::get_stream_caster_rtp_port_max(SrsConfDirective* sc) return ::atoi(conf->arg0().c_str()); } +SrsConfDirective* SrsConfig::create_directive(string vhost, string directive, string sub_directive) +{ + SrsConfDirective* vhost_conf = get_vhost(vhost); + + if (!vhost_conf) { + vhost_conf = new SrsConfDirective(); + root->directives.push_back(vhost_conf); + } + + if (directive.empty()) { + return vhost_conf; + } + + SrsConfDirective* dir = vhost_conf->get(directive); + if (!dir) { + dir = new SrsConfDirective(); + vhost_conf->directives.push_back(dir); + } + + if (sub_directive.empty()) { + return dir; + } + + SrsConfDirective* sdir = dir->get(sub_directive); + if (!sdir) { + sdir = new SrsConfDirective(); + dir->directives.push_back(sdir); + } + + return sdir; +} + SrsConfDirective* SrsConfig::get_vhost(string vhost) { srs_assert(root); @@ -3327,6 +3359,13 @@ string SrsConfig::get_dvr_path(string vhost) return conf->arg0(); } +void SrsConfig::set_dvr_path(string vhost, string path) +{ + SrsConfDirective* conf = create_directive(vhost, "dvr", "dvr_path"); + conf->args.clear(); + conf->args.push_back(path); +} + string SrsConfig::get_dvr_plan(string vhost) { SrsConfDirective* dvr = get_dvr(vhost); @@ -3378,6 +3417,13 @@ bool SrsConfig::get_dvr_wait_keyframe(string vhost) return false; } +void SrsConfig::set_dvr_wait_keyframe(string vhost, bool wait_keyframe) +{ + SrsConfDirective* conf = create_directive(vhost, "dvr", "dvr_wait_keyframe"); + conf->args.clear(); + conf->args.push_back(wait_keyframe? "on":"off"); +} + bool SrsConfig::get_dvr_autostart(string vhost) { SrsConfDirective* dvr = get_dvr(vhost); diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index bcef5eac4..827daf2ac 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -452,6 +452,14 @@ public: * get the max udp port for rtp of stream caster rtsp. */ virtual int get_stream_caster_rtp_port_max(SrsConfDirective* sc); +private: + /** + * create directive under vhost. + * @param directive, get the directive of vhost. get vhost if directive is empty. + * @param sub_directive, get the sub directive of vhost. get directive if sub-directive is empty. + * @return the vhost(empty directive and sub-directive); the directive(empty sub-directive); the sub-directive. + */ + virtual SrsConfDirective* create_directive(std::string vhost, std::string directive, std::string sub_directive); // vhost specified section public: /** @@ -919,6 +927,7 @@ public: * get the dvr path, the flv file to save in. */ virtual std::string get_dvr_path(std::string vhost); + virtual void set_dvr_path(std::string vhost, std::string path); /** * get the plan of dvr, how to reap the flv file. */ @@ -931,6 +940,7 @@ public: * whether wait keyframe to reap segment. */ virtual bool get_dvr_wait_keyframe(std::string vhost); + virtual void set_dvr_wait_keyframe(std::string vhost, bool wait_keyframe); /** * whether autostart for dvr. wait api to start dvr if false. */ diff --git a/trunk/src/app/srs_app_dvr.cpp b/trunk/src/app/srs_app_dvr.cpp index 97d60d8c5..494f06ca9 100644 --- a/trunk/src/app/srs_app_dvr.cpp +++ b/trunk/src/app/srs_app_dvr.cpp @@ -194,6 +194,11 @@ int SrsFlvSegment::close() return ret; } } + + if ((ret = plan->on_reap_segment()) != ERROR_SUCCESS) { + srs_error("dvr: notify plan to reap segment failed. ret=%d", ret); + return ret; + } #ifdef SRS_AUTO_HTTP_CALLBACK if (_srs_config->get_vhost_http_hooks_enabled(req->vhost)) { @@ -405,6 +410,11 @@ int SrsFlvSegment::update_flv_metadata() return ret; } +string SrsFlvSegment::get_path() +{ + return path; +} + string SrsFlvSegment::generate_path() { // the path in config, for example, @@ -657,6 +667,11 @@ int SrsDvrPlan::on_video(SrsSharedPtrMessage* __video) return ret; } +int SrsDvrPlan::on_reap_segment() +{ + return ERROR_SUCCESS; +} + SrsDvrPlan* SrsDvrPlan::create_plan(string vhost) { std::string plan = _srs_config->get_dvr_plan(vhost); @@ -726,12 +741,32 @@ void SrsDvrSessionPlan::on_unpublish() SrsDvrApiPlan::SrsDvrApiPlan() { + autostart = false; + started = false; } SrsDvrApiPlan::~SrsDvrApiPlan() { } +int SrsDvrApiPlan::initialize(SrsSource* s, SrsRequest* r) +{ + int ret = ERROR_SUCCESS; + + if ((ret = SrsDvrPlan::initialize(s, 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; @@ -745,6 +780,13 @@ int SrsDvrApiPlan::on_publish() 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; + } + if ((ret = segment->close()) != ERROR_SUCCESS) { return ret; } @@ -760,18 +802,76 @@ int SrsDvrApiPlan::on_publish() void SrsDvrApiPlan::on_unpublish() { - // support multiple publish. - if (!dvr_enabled) { - return; +} + +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) +{ + callback = 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; } + + // stop dvr + if (dvr_enabled) { + // ignore error. + int ret = segment->close(); + if (ret != ERROR_SUCCESS) { + srs_warn("ignore flv close error. ret=%d", ret); + } - // ignore error. - int ret = segment->close(); - if (ret != ERROR_SUCCESS) { - srs_warn("ignore flv close error. ret=%d", ret); + dvr_enabled = false; } - - 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); + + 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("callback", callback) + << __SRS_JOBJECT_END; + + return ret; +} + +int SrsDvrApiPlan::on_reap_segment() +{ + // TODO: FIXME: implements it. + return ERROR_SUCCESS; } SrsDvrAppendPlan::SrsDvrAppendPlan() @@ -1043,16 +1143,103 @@ SrsApiDvrPool::SrsApiDvrPool() SrsApiDvrPool::~SrsApiDvrPool() { + dvrs.clear(); } -int SrsApiDvrPool::dumps(stringstream& ss) +int SrsApiDvrPool::add_dvr(SrsDvrApiPlan* dvr) +{ + dvrs.push_back(dvr); + return ERROR_SUCCESS; +} + +int SrsApiDvrPool::dumps(string vhost, stringstream& ss) { int ret = ERROR_SUCCESS; - ss << __SRS_JARRAY_START - << __SRS_JARRAY_END; + + ss << __SRS_JARRAY_START; + + std::vector 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); + } + + 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)dvrs.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(); + SrsDvrApiPlan* dvr = NULL; + for (int i = 0; i < (int)dvrs.size(); i++) { + SrsDvrApiPlan* plan = dvrs.at(i); + if (!vhost.empty() && plan->req->vhost != vhost) { + continue; + } + dvr = plan; + break; + } + + if (!dvr) { + ret = ERROR_HTTP_DVR_CREATE_REQUEST; + srs_error("dvr: api create dvr request vhost invalid. vhost=%s. ret=%d", vhost.c_str(), ret); + return ret; + } + + // update optional parameters for plan. + 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; + } + } + + return dvr->start(); +} + SrsDvr::SrsDvr(SrsSource* s) { source = s; diff --git a/trunk/src/app/srs_app_dvr.hpp b/trunk/src/app/srs_app_dvr.hpp index f3e6ce1bb..17c9822da 100644 --- a/trunk/src/app/srs_app_dvr.hpp +++ b/trunk/src/app/srs_app_dvr.hpp @@ -43,6 +43,7 @@ class SrsSharedPtrMessage; class SrsFileWriter; class SrsFlvEncoder; class SrsDvrPlan; +class SrsJsonAny; #include #include @@ -149,6 +150,10 @@ public: * update the flv metadata. */ virtual int update_flv_metadata(); + /** + * get the current dvr path. + */ + virtual std::string get_path(); private: /** * generate the flv segment path. @@ -179,9 +184,10 @@ class SrsDvrPlan { public: friend class SrsFlvSegment; +public: + SrsRequest* req; protected: SrsSource* source; - SrsRequest* req; SrsFlvSegment* segment; bool dvr_enabled; public: @@ -204,6 +210,7 @@ public: */ virtual int on_video(SrsSharedPtrMessage* __video); protected: + virtual int on_reap_segment(); virtual int on_dvr_request_sh(); virtual int on_video_keyframe(); virtual int64_t filter_timestamp(int64_t timestamp); @@ -229,12 +236,25 @@ public: */ class SrsDvrApiPlan : public SrsDvrPlan { +private: + std::string callback; + bool autostart; + bool started; public: SrsDvrApiPlan(); virtual ~SrsDvrApiPlan(); public: + virtual int initialize(SrsSource* s, SrsRequest* r); virtual int on_publish(); virtual void on_unpublish(); +public: + virtual int set_path_tmpl(std::string path_tmpl); + virtual int set_callback(std::string value); + virtual int set_wait_keyframe(bool wait_keyframe); + virtual int start(); + virtual int dumps(std::stringstream& ss); +protected: + virtual int on_reap_segment(); }; /** @@ -302,6 +322,7 @@ private: class SrsApiDvrPool { private: + std::vector dvrs; static SrsApiDvrPool* _instance; private: SrsApiDvrPool(); @@ -309,7 +330,10 @@ public: static SrsApiDvrPool* instance(); virtual ~SrsApiDvrPool(); public: - virtual int dumps(std::stringstream& ss); + virtual int add_dvr(SrsDvrApiPlan* dvr); +public: + virtual int dumps(std::string vhost, std::stringstream& ss); + virtual int create(SrsJsonAny* json); }; /** diff --git a/trunk/src/app/srs_app_http_api.cpp b/trunk/src/app/srs_app_http_api.cpp index 9657a41af..fde565e33 100644 --- a/trunk/src/app/srs_app_http_api.cpp +++ b/trunk/src/app/srs_app_http_api.cpp @@ -494,12 +494,25 @@ int SrsGoApiDvrs::serve_http(ISrsGoHttpResponseWriter* w, SrsHttpMessage* r) SrsApiDvrPool* pool = SrsApiDvrPool::instance(); if (r->is_http_get()) { std::stringstream data; - int ret = pool->dumps(data); + int ret = pool->dumps(r->query_get("vhost"), data); ss << __SRS_JOBJECT_START << __SRS_JFIELD_ERROR(ret) << __SRS_JFIELD_CONT << __SRS_JFIELD_ORG("dvrs", data.str()) << __SRS_JOBJECT_END; + } else if (r->is_http_post()) { + char* body = (char*)r->body().c_str(); + SrsJsonAny* json = SrsJsonAny::loads(body); + int ret = ERROR_SUCCESS; + if (!json) { + ret = ERROR_HTTP_JSON_REQUIRED; + } else { + SrsAutoFree(SrsJsonAny, json); + ret = pool->create(json); + } + ss << __SRS_JOBJECT_START + << __SRS_JFIELD_ERROR(ret) + << __SRS_JOBJECT_END; } else { ss << __SRS_JOBJECT_START << __SRS_JFIELD_ERROR(ERROR_HTTP_DVR_REQUEST) diff --git a/trunk/src/app/srs_app_json.cpp b/trunk/src/app/srs_app_json.cpp index 0d9414f0c..27b51fae9 100644 --- a/trunk/src/app/srs_app_json.cpp +++ b/trunk/src/app/srs_app_json.cpp @@ -457,6 +457,21 @@ SrsJsonAny* SrsJsonObject::ensure_property_string(string name) return prop; } +SrsJsonAny* SrsJsonObject::ensure_property_boolean(string name) +{ + SrsJsonAny* prop = get_property(name); + + if (!prop) { + return NULL; + } + + if (!prop->is_boolean()) { + return NULL; + } + + return prop; +} + SrsJsonArray::SrsJsonArray() { marker = SRS_JSON_Array; diff --git a/trunk/src/app/srs_app_json.hpp b/trunk/src/app/srs_app_json.hpp index 0366bca93..002492fdf 100644 --- a/trunk/src/app/srs_app_json.hpp +++ b/trunk/src/app/srs_app_json.hpp @@ -123,6 +123,7 @@ public: public: /** * read json tree from str:char* + * @return json object. NULL if error. */ static SrsJsonAny* loads(char* str); }; @@ -148,6 +149,7 @@ public: virtual void set(std::string key, SrsJsonAny* value); virtual SrsJsonAny* get_property(std::string name); virtual SrsJsonAny* ensure_property_string(std::string name); + virtual SrsJsonAny* ensure_property_boolean(std::string name); }; class SrsJsonArray : public SrsJsonAny @@ -214,6 +216,7 @@ that is: #define __SRS_JFIELD_NAME(k) "\"" << k << "\":" #define __SRS_JFIELD_STR(k, v) "\"" << k << "\":\"" << v << "\"" #define __SRS_JFIELD_ORG(k, v) "\"" << k << "\":" << std::dec << v +#define __SRS_JFIELD_BOOL(k, v) __SRS_JFIELD_ORG(k, (v? "true":"false")) #define __SRS_JFIELD_ERROR(ret) "\"" << "code" << "\":" << ret #define __SRS_JFIELD_CONT "," #define __SRS_JOBJECT_END "}" diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index 994fe92d6..49f3553a5 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -211,6 +211,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define ERROR_HLS_TRY_MP3 3049 #define ERROR_HTTP_DVR_DISABLED 3050 #define ERROR_HTTP_DVR_REQUEST 3051 +#define ERROR_HTTP_JSON_REQUIRED 3052 +#define ERROR_HTTP_DVR_CREATE_REQUEST 3053 /////////////////////////////////////////////////////// // HTTP/StreamCaster protocol error.