winlin

for #179, support http api to start dvr.

... ... @@ -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;
... ...
... ... @@ -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);
... ...
... ... @@ -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.
*/
... ...
... ... @@ -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<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);
}
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;
... ...
... ... @@ -43,6 +43,7 @@ class SrsSharedPtrMessage;
class SrsFileWriter;
class SrsFlvEncoder;
class SrsDvrPlan;
class SrsJsonAny;
#include <srs_app_source.hpp>
#include <srs_app_reload.hpp>
... ... @@ -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<SrsDvrApiPlan*> 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);
};
/**
... ...
... ... @@ -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)
... ...
... ... @@ -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;
... ...
... ... @@ -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 "}"
... ...
... ... @@ -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.
... ...