winlin

for #179, enable http api crossdomain for dvr api.

@@ -109,6 +109,9 @@ http_api { @@ -109,6 +109,9 @@ http_api {
109 # the http api port 109 # the http api port
110 # default: 1985 110 # default: 1985
111 listen 1985; 111 listen 1985;
  112 + # whether enable crossdomain request.
  113 + # default: on
  114 + crossdomain on;
112 } 115 }
113 # embeded http server in srs. 116 # embeded http server in srs.
114 # the http streaming config, for HLS/HDS/DASH/HTTPProgressive 117 # the http streaming config, for HLS/HDS/DASH/HTTPProgressive
@@ -286,6 +289,31 @@ vhost dvr.srs.com { @@ -286,6 +289,31 @@ vhost dvr.srs.com {
286 # segment reap flv when flv duration exceed the specified dvr_duration. 289 # segment reap flv when flv duration exceed the specified dvr_duration.
287 # append always append to flv file, never reap it. 290 # append always append to flv file, never reap it.
288 # api reap flv when api required. 291 # api reap flv when api required.
  292 + # about the api plan, the HTTP api to dvr,
  293 + # http url to control dvr, for example, http://dev:1985/api/v1/dvrs
  294 + # method=GET
  295 + # to query dvrs of server.
  296 + # request params, for example ?vhost=__defaultVhost__, where:
  297 + # vhost, query all dvr of this vhost.
  298 + # response in json, where:
  299 + # {code:0, dvrs: [{plan:"api", path:"./objs/nginx/html",
  300 + # autostart:true, wait_keyframe:true, jitter:"full"
  301 + # }]}
  302 + # method=POST
  303 + # to start dvr of specified vhost.
  304 + # request should encode in json, specifies the dvr to create, where:
  305 + # {plan:"api", path:"./objs/nginx/html",
  306 + # autostart:true, wait_keyframe:true, jitter:"full",
  307 + # vhost:"__defaultVhost", callback:"http://dvr/callback"
  308 + # }
  309 + # response in json, where:
  310 + # {code:0}
  311 + # method=DELETE, to stop dvr
  312 + # to stop dvr of specified vhost.
  313 + # request params, for example ?vhost=__defaultVhost__, where:
  314 + # vhost, stop all dvr of this vhost.
  315 + # response in json, where:
  316 + # {code:0}
289 # default: session 317 # default: session
290 dvr_plan session; 318 dvr_plan session;
291 # the dvr output path. 319 # the dvr output path.
@@ -1334,7 +1334,7 @@ int SrsConfig::check_config() @@ -1334,7 +1334,7 @@ int SrsConfig::check_config()
1334 SrsConfDirective* conf = get_http_api(); 1334 SrsConfDirective* conf = get_http_api();
1335 for (int i = 0; conf && i < (int)conf->directives.size(); i++) { 1335 for (int i = 0; conf && i < (int)conf->directives.size(); i++) {
1336 string n = conf->at(i)->name; 1336 string n = conf->at(i)->name;
1337 - if (n != "enabled" && n != "listen") { 1337 + if (n != "enabled" && n != "listen" && n != "crossdomain") {
1338 ret = ERROR_SYSTEM_CONFIG_INVALID; 1338 ret = ERROR_SYSTEM_CONFIG_INVALID;
1339 srs_error("unsupported http_api directive %s, ret=%d", n.c_str(), ret); 1339 srs_error("unsupported http_api directive %s, ret=%d", n.c_str(), ret);
1340 return ret; 1340 return ret;
@@ -3453,6 +3453,22 @@ int SrsConfig::get_http_api_listen() @@ -3453,6 +3453,22 @@ int SrsConfig::get_http_api_listen()
3453 return ::atoi(conf->arg0().c_str()); 3453 return ::atoi(conf->arg0().c_str());
3454 } 3454 }
3455 3455
  3456 +bool SrsConfig::get_http_api_crossdomain()
  3457 +{
  3458 + SrsConfDirective* conf = get_http_api();
  3459 +
  3460 + if (!conf) {
  3461 + return SRS_CONF_DEFAULT_HTTP_API_CROSSDOMAIN;
  3462 + }
  3463 +
  3464 + conf = conf->get("crossdomain");
  3465 + if (!conf || conf->arg0().empty()) {
  3466 + return SRS_CONF_DEFAULT_HTTP_API_CROSSDOMAIN;
  3467 + }
  3468 +
  3469 + return conf->arg0() != "off";
  3470 +}
  3471 +
3456 bool SrsConfig::get_http_stream_enabled() 3472 bool SrsConfig::get_http_stream_enabled()
3457 { 3473 {
3458 SrsConfDirective* conf = get_http_stream(); 3474 SrsConfDirective* conf = get_http_stream();
@@ -79,6 +79,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @@ -79,6 +79,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
79 79
80 #define SRS_CONF_DEFAULT_HTTP_STREAM_PORT 8080 80 #define SRS_CONF_DEFAULT_HTTP_STREAM_PORT 8080
81 #define SRS_CONF_DEFAULT_HTTP_API_PORT 1985 81 #define SRS_CONF_DEFAULT_HTTP_API_PORT 1985
  82 +#define SRS_CONF_DEFAULT_HTTP_API_CROSSDOMAIN true
82 83
83 #define SRS_CONF_DEFAULT_HTTP_HEAETBEAT_ENABLED false 84 #define SRS_CONF_DEFAULT_HTTP_HEAETBEAT_ENABLED false
84 #define SRS_CONF_DEFAULT_HTTP_HEAETBEAT_INTERVAL 9.9 85 #define SRS_CONF_DEFAULT_HTTP_HEAETBEAT_INTERVAL 9.9
@@ -957,6 +958,10 @@ public: @@ -957,6 +958,10 @@ public:
957 * get the http api listen port. 958 * get the http api listen port.
958 */ 959 */
959 virtual int get_http_api_listen(); 960 virtual int get_http_api_listen();
  961 + /**
  962 + * whether enable crossdomain for http api.
  963 + */
  964 + virtual bool get_http_api_crossdomain();
960 // http stream section 965 // http stream section
961 private: 966 private:
962 /** 967 /**
@@ -40,6 +40,7 @@ using namespace std; @@ -40,6 +40,7 @@ using namespace std;
40 #include <srs_kernel_file.hpp> 40 #include <srs_kernel_file.hpp>
41 #include <srs_rtmp_amf0.hpp> 41 #include <srs_rtmp_amf0.hpp>
42 #include <srs_kernel_stream.hpp> 42 #include <srs_kernel_stream.hpp>
  43 +#include <srs_app_json.hpp>
43 44
44 // update the flv duration and filesize every this interval in ms. 45 // update the flv duration and filesize every this interval in ms.
45 #define __SRS_DVR_UPDATE_DURATION_INTERVAL 60000 46 #define __SRS_DVR_UPDATE_DURATION_INTERVAL 60000
@@ -665,6 +666,8 @@ SrsDvrPlan* SrsDvrPlan::create_plan(string vhost) @@ -665,6 +666,8 @@ SrsDvrPlan* SrsDvrPlan::create_plan(string vhost)
665 return new SrsDvrSessionPlan(); 666 return new SrsDvrSessionPlan();
666 } else if (plan == SRS_CONF_DEFAULT_DVR_PLAN_APPEND) { 667 } else if (plan == SRS_CONF_DEFAULT_DVR_PLAN_APPEND) {
667 return new SrsDvrAppendPlan(); 668 return new SrsDvrAppendPlan();
  669 + } else if (plan == SRS_CONF_DEFAULT_DVR_PLAN_API) {
  670 + return new SrsDvrApiPlan();
668 } else { 671 } else {
669 srs_error("invalid dvr plan=%s, vhost=%s", plan.c_str(), vhost.c_str()); 672 srs_error("invalid dvr plan=%s, vhost=%s", plan.c_str(), vhost.c_str());
670 srs_assert(false); 673 srs_assert(false);
@@ -721,6 +724,56 @@ void SrsDvrSessionPlan::on_unpublish() @@ -721,6 +724,56 @@ void SrsDvrSessionPlan::on_unpublish()
721 dvr_enabled = false; 724 dvr_enabled = false;
722 } 725 }
723 726
  727 +SrsDvrApiPlan::SrsDvrApiPlan()
  728 +{
  729 +}
  730 +
  731 +SrsDvrApiPlan::~SrsDvrApiPlan()
  732 +{
  733 +}
  734 +
  735 +int SrsDvrApiPlan::on_publish()
  736 +{
  737 + int ret = ERROR_SUCCESS;
  738 +
  739 + // support multiple publish.
  740 + if (dvr_enabled) {
  741 + return ret;
  742 + }
  743 +
  744 + if (!_srs_config->get_dvr_enabled(req->vhost)) {
  745 + return ret;
  746 + }
  747 +
  748 + if ((ret = segment->close()) != ERROR_SUCCESS) {
  749 + return ret;
  750 + }
  751 +
  752 + if ((ret = segment->open()) != ERROR_SUCCESS) {
  753 + return ret;
  754 + }
  755 +
  756 + dvr_enabled = true;
  757 +
  758 + return ret;
  759 +}
  760 +
  761 +void SrsDvrApiPlan::on_unpublish()
  762 +{
  763 + // support multiple publish.
  764 + if (!dvr_enabled) {
  765 + return;
  766 + }
  767 +
  768 + // ignore error.
  769 + int ret = segment->close();
  770 + if (ret != ERROR_SUCCESS) {
  771 + srs_warn("ignore flv close error. ret=%d", ret);
  772 + }
  773 +
  774 + dvr_enabled = false;
  775 +}
  776 +
724 SrsDvrAppendPlan::SrsDvrAppendPlan() 777 SrsDvrAppendPlan::SrsDvrAppendPlan()
725 { 778 {
726 last_update_time = 0; 779 last_update_time = 0;
@@ -977,6 +1030,29 @@ int SrsDvrSegmentPlan::update_duration(SrsSharedPtrMessage* msg) @@ -977,6 +1030,29 @@ int SrsDvrSegmentPlan::update_duration(SrsSharedPtrMessage* msg)
977 return ret; 1030 return ret;
978 } 1031 }
979 1032
  1033 +SrsApiDvrPool* SrsApiDvrPool::_instance = new SrsApiDvrPool();
  1034 +
  1035 +SrsApiDvrPool* SrsApiDvrPool::instance()
  1036 +{
  1037 + return SrsApiDvrPool::_instance;
  1038 +}
  1039 +
  1040 +SrsApiDvrPool::SrsApiDvrPool()
  1041 +{
  1042 +}
  1043 +
  1044 +SrsApiDvrPool::~SrsApiDvrPool()
  1045 +{
  1046 +}
  1047 +
  1048 +int SrsApiDvrPool::dumps(stringstream& ss)
  1049 +{
  1050 + int ret = ERROR_SUCCESS;
  1051 + ss << __SRS_JARRAY_START
  1052 + << __SRS_JARRAY_END;
  1053 + return ret;
  1054 +}
  1055 +
980 SrsDvr::SrsDvr(SrsSource* s) 1056 SrsDvr::SrsDvr(SrsSource* s)
981 { 1057 {
982 source = s; 1058 source = s;
@@ -30,6 +30,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @@ -30,6 +30,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 #include <srs_core.hpp> 30 #include <srs_core.hpp>
31 31
32 #include <string> 32 #include <string>
  33 +#include <sstream>
33 34
34 #ifdef SRS_AUTO_DVR 35 #ifdef SRS_AUTO_DVR
35 36
@@ -224,6 +225,19 @@ public: @@ -224,6 +225,19 @@ public:
224 }; 225 };
225 226
226 /** 227 /**
  228 +* api plan: reap flv by api.
  229 +*/
  230 +class SrsDvrApiPlan : public SrsDvrPlan
  231 +{
  232 +public:
  233 + SrsDvrApiPlan();
  234 + virtual ~SrsDvrApiPlan();
  235 +public:
  236 + virtual int on_publish();
  237 + virtual void on_unpublish();
  238 +};
  239 +
  240 +/**
227 * always append to flv file, never reap it. 241 * always append to flv file, never reap it.
228 */ 242 */
229 class SrsDvrAppendPlan : public SrsDvrPlan 243 class SrsDvrAppendPlan : public SrsDvrPlan
@@ -283,6 +297,22 @@ private: @@ -283,6 +297,22 @@ private:
283 }; 297 };
284 298
285 /** 299 /**
  300 +* the api dvr pool.
  301 +*/
  302 +class SrsApiDvrPool
  303 +{
  304 +private:
  305 + static SrsApiDvrPool* _instance;
  306 +private:
  307 + SrsApiDvrPool();
  308 +public:
  309 + static SrsApiDvrPool* instance();
  310 + virtual ~SrsApiDvrPool();
  311 +public:
  312 + virtual int dumps(std::stringstream& ss);
  313 +};
  314 +
  315 +/**
286 * dvr(digital video recorder) to record RTMP stream to flv file. 316 * dvr(digital video recorder) to record RTMP stream to flv file.
287 * TODO: FIXME: add utest for it. 317 * TODO: FIXME: add utest for it.
288 */ 318 */
@@ -139,6 +139,9 @@ bool srs_go_http_body_allowd(int status) @@ -139,6 +139,9 @@ bool srs_go_http_body_allowd(int status)
139 // returns "application/octet-stream". 139 // returns "application/octet-stream".
140 string srs_go_http_detect(char* data, int size) 140 string srs_go_http_detect(char* data, int size)
141 { 141 {
  142 + // detect only when data specified.
  143 + if (data) {
  144 + }
142 return "application/octet-stream"; // fallback 145 return "application/octet-stream"; // fallback
143 } 146 }
144 147
@@ -715,8 +718,8 @@ int SrsGoHttpResponseWriter::final_request() @@ -715,8 +718,8 @@ int SrsGoHttpResponseWriter::final_request()
715 return skt->write((void*)ch.data(), (int)ch.length(), NULL); 718 return skt->write((void*)ch.data(), (int)ch.length(), NULL);
716 } 719 }
717 720
718 - // ignore when send with content length  
719 - return ERROR_SUCCESS; 721 + // flush when send with content length
  722 + return write(NULL, 0);
720 } 723 }
721 724
722 SrsGoHttpHeader* SrsGoHttpResponseWriter::header() 725 SrsGoHttpHeader* SrsGoHttpResponseWriter::header()
@@ -743,6 +746,11 @@ int SrsGoHttpResponseWriter::write(char* data, int size) @@ -743,6 +746,11 @@ int SrsGoHttpResponseWriter::write(char* data, int size)
743 srs_error("http: send header failed. ret=%d", ret); 746 srs_error("http: send header failed. ret=%d", ret);
744 return ret; 747 return ret;
745 } 748 }
  749 +
  750 + // ignore NULL content.
  751 + if (!data) {
  752 + return ret;
  753 + }
746 754
747 // directly send with content length 755 // directly send with content length
748 if (content_length != -1) { 756 if (content_length != -1) {
@@ -123,6 +123,29 @@ public: @@ -123,6 +123,29 @@ public:
123 123
124 // A ResponseWriter interface is used by an HTTP handler to 124 // A ResponseWriter interface is used by an HTTP handler to
125 // construct an HTTP response. 125 // construct an HTTP response.
  126 +// Usage 1, response with specified length content:
  127 +// ISrsGoHttpResponseWriter* w; // create or get response.
  128 +// std::string msg = "Hello, HTTP!";
  129 +// w->header()->set_content_type("text/plain; charset=utf-8");
  130 +// w->header()->set_content_length(msg.length());
  131 +// w->write_header(SRS_CONSTS_HTTP_OK);
  132 +// w->write((char*)msg.data(), (int)msg.length());
  133 +// w->final_request(); // optional flush.
  134 +// Usage 2, response with HTTP code only, zero content length.
  135 +// ISrsGoHttpResponseWriter* w; // create or get response.
  136 +// w->header()->set_content_length(0);
  137 +// w->write_header(SRS_CONSTS_HTTP_OK);
  138 +// w->final_request();
  139 +// Usage 3, response in chunked encoding.
  140 +// ISrsGoHttpResponseWriter* w; // create or get response.
  141 +// std::string msg = "Hello, HTTP!";
  142 +// w->header()->set_content_type("application/octet-stream");
  143 +// w->write_header(SRS_CONSTS_HTTP_OK);
  144 +// w->write((char*)msg.data(), (int)msg.length());
  145 +// w->write((char*)msg.data(), (int)msg.length());
  146 +// w->write((char*)msg.data(), (int)msg.length());
  147 +// w->write((char*)msg.data(), (int)msg.length());
  148 +// w->final_request(); // required to end the chunked and flush.
126 class ISrsGoHttpResponseWriter 149 class ISrsGoHttpResponseWriter
127 { 150 {
128 public: 151 public:
@@ -143,6 +166,7 @@ public: @@ -143,6 +166,7 @@ public:
143 // before writing the data. If the Header does not contain a 166 // before writing the data. If the Header does not contain a
144 // Content-Type line, Write adds a Content-Type set to the result of passing 167 // Content-Type line, Write adds a Content-Type set to the result of passing
145 // the initial 512 bytes of written data to DetectContentType. 168 // the initial 512 bytes of written data to DetectContentType.
  169 + // @param data, the data to send. NULL to flush header only.
146 virtual int write(char* data, int size) = 0; 170 virtual int write(char* data, int size) = 0;
147 171
148 // WriteHeader sends an HTTP response header with status code. 172 // WriteHeader sends an HTTP response header with status code.
@@ -37,6 +37,8 @@ using namespace std; @@ -37,6 +37,8 @@ using namespace std;
37 #include <srs_app_utility.hpp> 37 #include <srs_app_utility.hpp>
38 #include <srs_app_statistic.hpp> 38 #include <srs_app_statistic.hpp>
39 #include <srs_rtmp_sdk.hpp> 39 #include <srs_rtmp_sdk.hpp>
  40 +#include <srs_app_dvr.hpp>
  41 +#include <srs_app_config.hpp>
40 42
41 SrsGoApiRoot::SrsGoApiRoot() 43 SrsGoApiRoot::SrsGoApiRoot()
42 { 44 {
@@ -472,11 +474,48 @@ int SrsGoApiStreams::serve_http(ISrsGoHttpResponseWriter* w, SrsHttpMessage* r) @@ -472,11 +474,48 @@ int SrsGoApiStreams::serve_http(ISrsGoHttpResponseWriter* w, SrsHttpMessage* r)
472 return srs_go_http_response_json(w, ss.str()); 474 return srs_go_http_response_json(w, ss.str());
473 } 475 }
474 476
  477 +SrsGoApiDvrs::SrsGoApiDvrs()
  478 +{
  479 +}
  480 +
  481 +SrsGoApiDvrs::~SrsGoApiDvrs()
  482 +{
  483 +}
  484 +
  485 +int SrsGoApiDvrs::serve_http(ISrsGoHttpResponseWriter* w, SrsHttpMessage* r)
  486 +{
  487 + std::stringstream ss;
  488 +
  489 +#ifndef SRS_AUTO_DVR
  490 + ss << __SRS_JOBJECT_START
  491 + << __SRS_JFIELD_ERROR(ERROR_HTTP_DVR_DISABLED)
  492 + << __SRS_JOBJECT_END;
  493 +#else
  494 + SrsApiDvrPool* pool = SrsApiDvrPool::instance();
  495 + if (r->is_http_get()) {
  496 + std::stringstream data;
  497 + int ret = pool->dumps(data);
  498 +
  499 + ss << __SRS_JOBJECT_START
  500 + << __SRS_JFIELD_ERROR(ret) << __SRS_JFIELD_CONT
  501 + << __SRS_JFIELD_ORG("dvrs", data.str())
  502 + << __SRS_JOBJECT_END;
  503 + } else {
  504 + ss << __SRS_JOBJECT_START
  505 + << __SRS_JFIELD_ERROR(ERROR_HTTP_DVR_REQUEST)
  506 + << __SRS_JOBJECT_END;
  507 + }
  508 +#endif
  509 +
  510 + return srs_go_http_response_json(w, ss.str());
  511 +}
  512 +
475 SrsHttpApi::SrsHttpApi(SrsServer* svr, st_netfd_t fd, SrsGoHttpServeMux* m) 513 SrsHttpApi::SrsHttpApi(SrsServer* svr, st_netfd_t fd, SrsGoHttpServeMux* m)
476 : SrsConnection(svr, fd) 514 : SrsConnection(svr, fd)
477 { 515 {
478 mux = m; 516 mux = m;
479 parser = new SrsHttpParser(); 517 parser = new SrsHttpParser();
  518 + crossdomain_required = false;
480 } 519 }
481 520
482 SrsHttpApi::~SrsHttpApi() 521 SrsHttpApi::~SrsHttpApi()
@@ -549,6 +588,29 @@ int SrsHttpApi::process_request(ISrsGoHttpResponseWriter* w, SrsHttpMessage* r) @@ -549,6 +588,29 @@ int SrsHttpApi::process_request(ISrsGoHttpResponseWriter* w, SrsHttpMessage* r)
549 srs_trace("HTTP %s %s, content-length=%"PRId64"", 588 srs_trace("HTTP %s %s, content-length=%"PRId64"",
550 r->method_str().c_str(), r->url().c_str(), r->content_length()); 589 r->method_str().c_str(), r->url().c_str(), r->content_length());
551 590
  591 + // method is OPTIONS and enable crossdomain, required crossdomain header.
  592 + if (r->is_http_options() && _srs_config->get_http_api_crossdomain()) {
  593 + crossdomain_required = true;
  594 + }
  595 +
  596 + // whenever crossdomain required, set crossdomain header.
  597 + if (crossdomain_required) {
  598 + w->header()->set("Access-Control-Allow-Origin", "*");
  599 + w->header()->set("Access-Control-Allow-Methods", "GET, POST, HEAD, PUT, DELETE");
  600 + w->header()->set("Access-Control-Allow-Headers", "Cache-Control,X-Proxy-Authorization,X-Requested-With,Content-Type");
  601 + }
  602 +
  603 + // handle the http options.
  604 + if (r->is_http_options()) {
  605 + w->header()->set_content_length(0);
  606 + if (_srs_config->get_http_api_crossdomain()) {
  607 + w->write_header(SRS_CONSTS_HTTP_OK);
  608 + } else {
  609 + w->write_header(SRS_CONSTS_HTTP_MethodNotAllowed);
  610 + }
  611 + return w->final_request();
  612 + }
  613 +
552 // use default server mux to serve http request. 614 // use default server mux to serve http request.
553 if ((ret = mux->serve_http(w, r)) != ERROR_SUCCESS) { 615 if ((ret = mux->serve_http(w, r)) != ERROR_SUCCESS) {
554 if (!srs_is_client_gracefully_close(ret)) { 616 if (!srs_is_client_gracefully_close(ret)) {
@@ -159,11 +159,21 @@ public: @@ -159,11 +159,21 @@ public:
159 virtual int serve_http(ISrsGoHttpResponseWriter* w, SrsHttpMessage* r); 159 virtual int serve_http(ISrsGoHttpResponseWriter* w, SrsHttpMessage* r);
160 }; 160 };
161 161
  162 +class SrsGoApiDvrs : public ISrsGoHttpHandler
  163 +{
  164 +public:
  165 + SrsGoApiDvrs();
  166 + virtual ~SrsGoApiDvrs();
  167 +public:
  168 + virtual int serve_http(ISrsGoHttpResponseWriter* w, SrsHttpMessage* r);
  169 +};
  170 +
162 class SrsHttpApi : public SrsConnection 171 class SrsHttpApi : public SrsConnection
163 { 172 {
164 private: 173 private:
165 SrsHttpParser* parser; 174 SrsHttpParser* parser;
166 SrsGoHttpServeMux* mux; 175 SrsGoHttpServeMux* mux;
  176 + bool crossdomain_required;
167 public: 177 public:
168 SrsHttpApi(SrsServer* svr, st_netfd_t fd, SrsGoHttpServeMux* m); 178 SrsHttpApi(SrsServer* svr, st_netfd_t fd, SrsGoHttpServeMux* m);
169 virtual ~SrsHttpApi(); 179 virtual ~SrsHttpApi();
@@ -529,6 +529,9 @@ int SrsServer::initialize() @@ -529,6 +529,9 @@ int SrsServer::initialize()
529 if ((ret = http_api_mux->handle("/api/v1/streams", new SrsGoApiStreams())) != ERROR_SUCCESS) { 529 if ((ret = http_api_mux->handle("/api/v1/streams", new SrsGoApiStreams())) != ERROR_SUCCESS) {
530 return ret; 530 return ret;
531 } 531 }
  532 + if ((ret = http_api_mux->handle("/api/v1/dvrs", new SrsGoApiDvrs())) != ERROR_SUCCESS) {
  533 + return ret;
  534 + }
532 #endif 535 #endif
533 536
534 #ifdef SRS_AUTO_HTTP_SERVER 537 #ifdef SRS_AUTO_HTTP_SERVER
@@ -209,6 +209,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @@ -209,6 +209,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
209 #define ERROR_AAC_ADTS_HEADER 3047 209 #define ERROR_AAC_ADTS_HEADER 3047
210 #define ERROR_AAC_DATA_INVALID 3048 210 #define ERROR_AAC_DATA_INVALID 3048
211 #define ERROR_HLS_TRY_MP3 3049 211 #define ERROR_HLS_TRY_MP3 3049
  212 +#define ERROR_HTTP_DVR_DISABLED 3050
  213 +#define ERROR_HTTP_DVR_REQUEST 3051
212 214
213 /////////////////////////////////////////////////////// 215 ///////////////////////////////////////////////////////
214 // HTTP/StreamCaster protocol error. 216 // HTTP/StreamCaster protocol error.