正在显示
5 个修改的文件
包含
252 行增加
和
41 行删除
| @@ -304,7 +304,7 @@ vhost dvr.srs.com { | @@ -304,7 +304,7 @@ vhost dvr.srs.com { | ||
| 304 | # to start dvr of specified vhost. | 304 | # to start dvr of specified vhost. |
| 305 | # request should encode in json, specifies the dvr to create, where: | 305 | # request should encode in json, specifies the dvr to create, where: |
| 306 | # {path_tmpl:"./[15].[04].[05].[999].flv", | 306 | # {path_tmpl:"./[15].[04].[05].[999].flv", |
| 307 | - # wait_keyframe:true, vhost:"__defaultVhost", callback:"http://dvr/callback" | 307 | + # wait_keyframe:true, vhost:"__defaultVhost", callback:"http://127.0.0.1:8085/api/v1/dvrs" |
| 308 | # } | 308 | # } |
| 309 | # response in json, where: | 309 | # response in json, where: |
| 310 | # {code:0} | 310 | # {code:0} |
| @@ -315,7 +315,12 @@ vhost dvr.srs.com { | @@ -315,7 +315,12 @@ vhost dvr.srs.com { | ||
| 315 | # response in json, where: | 315 | # response in json, where: |
| 316 | # {code:0} | 316 | # {code:0} |
| 317 | # when reap segment, the callback POST request in json: | 317 | # when reap segment, the callback POST request in json: |
| 318 | - # {action:"on_dvr_reap_segment"} | 318 | + # {action:"on_dvr_reap_segment", client_id:100, vhost:"__defaultVhost__", |
| 319 | + # app:"live", stream:"livestream", cwd:"/home/winlin/srs", file:"./dvr.flv" | ||
| 320 | + # } | ||
| 321 | + # for the dvr http callback, @see http_hooks.on_dvr of vhost hooks.callback.srs.com | ||
| 322 | + # @read https://github.com/winlinvip/simple-rtmp-server/wiki/v2_CN_DVR#http-callback | ||
| 323 | + # @read https://github.com/winlinvip/simple-rtmp-server/wiki/v2_EN_DVR#http-callback | ||
| 319 | # default: session | 324 | # default: session |
| 320 | dvr_plan session; | 325 | dvr_plan session; |
| 321 | # the dvr output path. | 326 | # the dvr output path. |
| @@ -45,6 +45,9 @@ using namespace std; | @@ -45,6 +45,9 @@ using namespace std; | ||
| 45 | // update the flv duration and filesize every this interval in ms. | 45 | // update the flv duration and filesize every this interval in ms. |
| 46 | #define __SRS_DVR_UPDATE_DURATION_INTERVAL 60000 | 46 | #define __SRS_DVR_UPDATE_DURATION_INTERVAL 60000 |
| 47 | 47 | ||
| 48 | +// the sleep interval for http async callback. | ||
| 49 | +#define SRS_AUTO_ASYNC_CALLBACL_SLEEP_US 300000 | ||
| 50 | + | ||
| 48 | SrsFlvSegment::SrsFlvSegment(SrsDvrPlan* p) | 51 | SrsFlvSegment::SrsFlvSegment(SrsDvrPlan* p) |
| 49 | { | 52 | { |
| 50 | req = NULL; | 53 | req = NULL; |
| @@ -201,29 +204,6 @@ int SrsFlvSegment::close() | @@ -201,29 +204,6 @@ int SrsFlvSegment::close() | ||
| 201 | srs_error("dvr: notify plan to reap segment failed. ret=%d", ret); | 204 | srs_error("dvr: notify plan to reap segment failed. ret=%d", ret); |
| 202 | return ret; | 205 | return ret; |
| 203 | } | 206 | } |
| 204 | - | ||
| 205 | -#ifdef SRS_AUTO_HTTP_CALLBACK | ||
| 206 | - if (_srs_config->get_vhost_http_hooks_enabled(req->vhost)) { | ||
| 207 | - // HTTP: on_dvr | ||
| 208 | - SrsConfDirective* on_dvr = _srs_config->get_vhost_on_dvr(req->vhost); | ||
| 209 | - if (!on_dvr) { | ||
| 210 | - srs_info("ignore the empty http callback: on_dvr"); | ||
| 211 | - return ret; | ||
| 212 | - } | ||
| 213 | - | ||
| 214 | - int connection_id = _srs_context->get_id(); | ||
| 215 | - std::string ip = req->ip; | ||
| 216 | - std::string cwd = _srs_config->cwd(); | ||
| 217 | - std::string file = path; | ||
| 218 | - for (int i = 0; i < (int)on_dvr->args.size(); i++) { | ||
| 219 | - std::string url = on_dvr->args.at(i); | ||
| 220 | - if ((ret = SrsHttpHooks::on_dvr(url, connection_id, ip, req, cwd, file)) != ERROR_SUCCESS) { | ||
| 221 | - srs_error("hook client on_dvr failed. url=%s, ret=%d", url.c_str(), ret); | ||
| 222 | - return ret; | ||
| 223 | - } | ||
| 224 | - } | ||
| 225 | - } | ||
| 226 | -#endif | ||
| 227 | 207 | ||
| 228 | return ret; | 208 | return ret; |
| 229 | } | 209 | } |
| @@ -578,6 +558,160 @@ int SrsFlvSegment::on_reload_vhost_dvr(std::string /*vhost*/) | @@ -578,6 +558,160 @@ int SrsFlvSegment::on_reload_vhost_dvr(std::string /*vhost*/) | ||
| 578 | return ret; | 558 | return ret; |
| 579 | } | 559 | } |
| 580 | 560 | ||
| 561 | +ISrsDvrAsyncCall::ISrsDvrAsyncCall() | ||
| 562 | +{ | ||
| 563 | +} | ||
| 564 | + | ||
| 565 | +ISrsDvrAsyncCall::~ISrsDvrAsyncCall() | ||
| 566 | +{ | ||
| 567 | +} | ||
| 568 | + | ||
| 569 | +SrsDvrAsyncCallOnDvr::SrsDvrAsyncCallOnDvr(SrsRequest* r, string p) | ||
| 570 | +{ | ||
| 571 | + req = r; | ||
| 572 | + path = p; | ||
| 573 | +} | ||
| 574 | + | ||
| 575 | +SrsDvrAsyncCallOnDvr::~SrsDvrAsyncCallOnDvr() | ||
| 576 | +{ | ||
| 577 | +} | ||
| 578 | + | ||
| 579 | +int SrsDvrAsyncCallOnDvr::call() | ||
| 580 | +{ | ||
| 581 | + int ret = ERROR_SUCCESS; | ||
| 582 | + | ||
| 583 | +#ifdef SRS_AUTO_HTTP_CALLBACK | ||
| 584 | + // http callback for on_dvr in config. | ||
| 585 | + if (_srs_config->get_vhost_http_hooks_enabled(req->vhost)) { | ||
| 586 | + // HTTP: on_dvr | ||
| 587 | + SrsConfDirective* on_dvr = _srs_config->get_vhost_on_dvr(req->vhost); | ||
| 588 | + if (!on_dvr) { | ||
| 589 | + srs_info("ignore the empty http callback: on_dvr"); | ||
| 590 | + return ret; | ||
| 591 | + } | ||
| 592 | + | ||
| 593 | + int connection_id = _srs_context->get_id(); | ||
| 594 | + std::string ip = req->ip; | ||
| 595 | + std::string cwd = _srs_config->cwd(); | ||
| 596 | + std::string file = path; | ||
| 597 | + for (int i = 0; i < (int)on_dvr->args.size(); i++) { | ||
| 598 | + std::string url = on_dvr->args.at(i); | ||
| 599 | + if ((ret = SrsHttpHooks::on_dvr(url, connection_id, ip, req, cwd, file)) != ERROR_SUCCESS) { | ||
| 600 | + srs_error("hook client on_dvr failed. url=%s, ret=%d", url.c_str(), ret); | ||
| 601 | + return ret; | ||
| 602 | + } | ||
| 603 | + } | ||
| 604 | + } | ||
| 605 | +#endif | ||
| 606 | + | ||
| 607 | + return ret; | ||
| 608 | +} | ||
| 609 | + | ||
| 610 | +string SrsDvrAsyncCallOnDvr::to_string() | ||
| 611 | +{ | ||
| 612 | + std::stringstream ss; | ||
| 613 | + ss << "vhost=" << req->vhost << ", file=" << path; | ||
| 614 | + return ss.str(); | ||
| 615 | +} | ||
| 616 | + | ||
| 617 | +SrsDvrAsyncCallOnSegment::SrsDvrAsyncCallOnSegment(SrsRequest* r, string c, string p) | ||
| 618 | +{ | ||
| 619 | + req = r; | ||
| 620 | + callback = c; | ||
| 621 | + path = p; | ||
| 622 | +} | ||
| 623 | + | ||
| 624 | +SrsDvrAsyncCallOnSegment::~SrsDvrAsyncCallOnSegment() | ||
| 625 | +{ | ||
| 626 | +} | ||
| 627 | + | ||
| 628 | +int SrsDvrAsyncCallOnSegment::call() | ||
| 629 | +{ | ||
| 630 | + int ret = ERROR_SUCCESS; | ||
| 631 | + | ||
| 632 | +#ifdef SRS_AUTO_HTTP_CALLBACK | ||
| 633 | + // HTTP: callback | ||
| 634 | + if (callback.empty()) { | ||
| 635 | + srs_warn("dvr: ignore for callback empty, vhost=%s", req->vhost.c_str()); | ||
| 636 | + return ret; | ||
| 637 | + } | ||
| 638 | + | ||
| 639 | + int connection_id = _srs_context->get_id(); | ||
| 640 | + std::string cwd = _srs_config->cwd(); | ||
| 641 | + std::string file = path; | ||
| 642 | + std::string url = callback; | ||
| 643 | + if ((ret = SrsHttpHooks::on_dvr_reap_segment(url, connection_id, req, cwd, file)) != ERROR_SUCCESS) { | ||
| 644 | + srs_error("hook client on_dvr_reap_segment failed. url=%s, ret=%d", url.c_str(), ret); | ||
| 645 | + return ret; | ||
| 646 | + } | ||
| 647 | +#endif | ||
| 648 | + | ||
| 649 | + return ret; | ||
| 650 | +} | ||
| 651 | + | ||
| 652 | +string SrsDvrAsyncCallOnSegment::to_string() | ||
| 653 | +{ | ||
| 654 | + std::stringstream ss; | ||
| 655 | + ss << "vhost=" << req->vhost << ", file=" << path << "callback=" << callback; | ||
| 656 | + return ss.str(); | ||
| 657 | +} | ||
| 658 | + | ||
| 659 | +SrsDvrAsyncCallThread::SrsDvrAsyncCallThread() | ||
| 660 | +{ | ||
| 661 | + pthread = new SrsThread("async", this, SRS_AUTO_ASYNC_CALLBACL_SLEEP_US, true); | ||
| 662 | +} | ||
| 663 | + | ||
| 664 | +SrsDvrAsyncCallThread::~SrsDvrAsyncCallThread() | ||
| 665 | +{ | ||
| 666 | + stop(); | ||
| 667 | + srs_freep(pthread); | ||
| 668 | + | ||
| 669 | + std::vector<ISrsDvrAsyncCall*>::iterator it; | ||
| 670 | + for (it = callbacks.begin(); it != callbacks.end(); ++it) { | ||
| 671 | + ISrsDvrAsyncCall* call = *it; | ||
| 672 | + srs_freep(call); | ||
| 673 | + } | ||
| 674 | + callbacks.clear(); | ||
| 675 | +} | ||
| 676 | + | ||
| 677 | +int SrsDvrAsyncCallThread::call(ISrsDvrAsyncCall* c) | ||
| 678 | +{ | ||
| 679 | + int ret = ERROR_SUCCESS; | ||
| 680 | + | ||
| 681 | + callbacks.push_back(c); | ||
| 682 | + | ||
| 683 | + return ret; | ||
| 684 | +} | ||
| 685 | + | ||
| 686 | +int SrsDvrAsyncCallThread::start() | ||
| 687 | +{ | ||
| 688 | + return pthread->start(); | ||
| 689 | +} | ||
| 690 | + | ||
| 691 | +void SrsDvrAsyncCallThread::stop() | ||
| 692 | +{ | ||
| 693 | + pthread->stop(); | ||
| 694 | +} | ||
| 695 | + | ||
| 696 | +int SrsDvrAsyncCallThread::cycle() | ||
| 697 | +{ | ||
| 698 | + int ret = ERROR_SUCCESS; | ||
| 699 | + | ||
| 700 | + std::vector<ISrsDvrAsyncCall*> copies = callbacks; | ||
| 701 | + callbacks.clear(); | ||
| 702 | + | ||
| 703 | + std::vector<ISrsDvrAsyncCall*>::iterator it; | ||
| 704 | + for (it = copies.begin(); it != copies.end(); ++it) { | ||
| 705 | + ISrsDvrAsyncCall* call = *it; | ||
| 706 | + if ((ret = call->call()) != ERROR_SUCCESS) { | ||
| 707 | + srs_warn("dvr: ignore callback %s, ret=%d", call->to_string().c_str(), ret); | ||
| 708 | + } | ||
| 709 | + srs_freep(call); | ||
| 710 | + } | ||
| 711 | + | ||
| 712 | + return ret; | ||
| 713 | +} | ||
| 714 | + | ||
| 581 | SrsDvrPlan::SrsDvrPlan() | 715 | SrsDvrPlan::SrsDvrPlan() |
| 582 | { | 716 | { |
| 583 | source = NULL; | 717 | source = NULL; |
| @@ -585,11 +719,13 @@ SrsDvrPlan::SrsDvrPlan() | @@ -585,11 +719,13 @@ SrsDvrPlan::SrsDvrPlan() | ||
| 585 | 719 | ||
| 586 | dvr_enabled = false; | 720 | dvr_enabled = false; |
| 587 | segment = new SrsFlvSegment(this); | 721 | segment = new SrsFlvSegment(this); |
| 722 | + async = new SrsDvrAsyncCallThread(); | ||
| 588 | } | 723 | } |
| 589 | 724 | ||
| 590 | SrsDvrPlan::~SrsDvrPlan() | 725 | SrsDvrPlan::~SrsDvrPlan() |
| 591 | { | 726 | { |
| 592 | srs_freep(segment); | 727 | srs_freep(segment); |
| 728 | + srs_freep(async); | ||
| 593 | } | 729 | } |
| 594 | 730 | ||
| 595 | int SrsDvrPlan::initialize(SrsSource* s, SrsRequest* r) | 731 | int SrsDvrPlan::initialize(SrsSource* s, SrsRequest* r) |
| @@ -603,6 +739,10 @@ int SrsDvrPlan::initialize(SrsSource* s, SrsRequest* r) | @@ -603,6 +739,10 @@ int SrsDvrPlan::initialize(SrsSource* s, SrsRequest* r) | ||
| 603 | return ret; | 739 | return ret; |
| 604 | } | 740 | } |
| 605 | 741 | ||
| 742 | + if ((ret = async->start()) != ERROR_SUCCESS) { | ||
| 743 | + return ret; | ||
| 744 | + } | ||
| 745 | + | ||
| 606 | return ret; | 746 | return ret; |
| 607 | } | 747 | } |
| 608 | 748 | ||
| @@ -671,7 +811,13 @@ int SrsDvrPlan::on_video(SrsSharedPtrMessage* __video) | @@ -671,7 +811,13 @@ int SrsDvrPlan::on_video(SrsSharedPtrMessage* __video) | ||
| 671 | 811 | ||
| 672 | int SrsDvrPlan::on_reap_segment() | 812 | int SrsDvrPlan::on_reap_segment() |
| 673 | { | 813 | { |
| 674 | - return ERROR_SUCCESS; | 814 | + int ret = ERROR_SUCCESS; |
| 815 | + | ||
| 816 | + if ((ret = async->call(new SrsDvrAsyncCallOnDvr(req, segment->get_path()))) != ERROR_SUCCESS) { | ||
| 817 | + return ret; | ||
| 818 | + } | ||
| 819 | + | ||
| 820 | + return ret; | ||
| 675 | } | 821 | } |
| 676 | 822 | ||
| 677 | SrsDvrPlan* SrsDvrPlan::create_plan(string vhost) | 823 | SrsDvrPlan* SrsDvrPlan::create_plan(string vhost) |
| @@ -958,23 +1104,14 @@ int SrsDvrApiPlan::stop() | @@ -958,23 +1104,14 @@ int SrsDvrApiPlan::stop() | ||
| 958 | int SrsDvrApiPlan::on_reap_segment() | 1104 | int SrsDvrApiPlan::on_reap_segment() |
| 959 | { | 1105 | { |
| 960 | int ret = ERROR_SUCCESS; | 1106 | int ret = ERROR_SUCCESS; |
| 961 | - | ||
| 962 | -#ifdef SRS_AUTO_HTTP_CALLBACK | ||
| 963 | - // HTTP: callback | ||
| 964 | - if (callback.empty()) { | ||
| 965 | - srs_warn("dvr: ignore for callback empty, vhost=%s", req->vhost.c_str()); | 1107 | + |
| 1108 | + if ((ret = SrsDvrPlan::on_reap_segment()) != ERROR_SUCCESS) { | ||
| 966 | return ret; | 1109 | return ret; |
| 967 | } | 1110 | } |
| 968 | - | ||
| 969 | - int connection_id = _srs_context->get_id(); | ||
| 970 | - std::string cwd = _srs_config->cwd(); | ||
| 971 | - std::string file = segment->get_path(); | ||
| 972 | - std::string url = callback; | ||
| 973 | - if ((ret = SrsHttpHooks::on_dvr_reap_segment(url, connection_id, req, cwd, file)) != ERROR_SUCCESS) { | ||
| 974 | - srs_error("hook client on_dvr_reap_segment failed. url=%s, ret=%d", url.c_str(), ret); | 1111 | + |
| 1112 | + if ((ret = async->call(new SrsDvrAsyncCallOnSegment(req, callback, segment->get_path()))) != ERROR_SUCCESS) { | ||
| 975 | return ret; | 1113 | return ret; |
| 976 | } | 1114 | } |
| 977 | -#endif | ||
| 978 | 1115 | ||
| 979 | return ret; | 1116 | return ret; |
| 980 | } | 1117 | } |
| @@ -44,9 +44,11 @@ class SrsFileWriter; | @@ -44,9 +44,11 @@ class SrsFileWriter; | ||
| 44 | class SrsFlvEncoder; | 44 | class SrsFlvEncoder; |
| 45 | class SrsDvrPlan; | 45 | class SrsDvrPlan; |
| 46 | class SrsJsonAny; | 46 | class SrsJsonAny; |
| 47 | +class SrsThread; | ||
| 47 | 48 | ||
| 48 | #include <srs_app_source.hpp> | 49 | #include <srs_app_source.hpp> |
| 49 | #include <srs_app_reload.hpp> | 50 | #include <srs_app_reload.hpp> |
| 51 | +#include <srs_app_thread.hpp> | ||
| 50 | 52 | ||
| 51 | /** | 53 | /** |
| 52 | * a piece of flv segment. | 54 | * a piece of flv segment. |
| @@ -174,6 +176,63 @@ public: | @@ -174,6 +176,63 @@ public: | ||
| 174 | }; | 176 | }; |
| 175 | 177 | ||
| 176 | /** | 178 | /** |
| 179 | +* the dvr async call. | ||
| 180 | +*/ | ||
| 181 | +class ISrsDvrAsyncCall | ||
| 182 | +{ | ||
| 183 | +public: | ||
| 184 | + ISrsDvrAsyncCall(); | ||
| 185 | + virtual ~ISrsDvrAsyncCall(); | ||
| 186 | +public: | ||
| 187 | + virtual int call() = 0; | ||
| 188 | + virtual std::string to_string() = 0; | ||
| 189 | +}; | ||
| 190 | +class SrsDvrAsyncCallOnDvr : public ISrsDvrAsyncCall | ||
| 191 | +{ | ||
| 192 | +private: | ||
| 193 | + std::string path; | ||
| 194 | + SrsRequest* req; | ||
| 195 | +public: | ||
| 196 | + SrsDvrAsyncCallOnDvr(SrsRequest* r, std::string p); | ||
| 197 | + virtual ~SrsDvrAsyncCallOnDvr(); | ||
| 198 | +public: | ||
| 199 | + virtual int call(); | ||
| 200 | + virtual std::string to_string(); | ||
| 201 | +}; | ||
| 202 | +class SrsDvrAsyncCallOnSegment : public ISrsDvrAsyncCall | ||
| 203 | +{ | ||
| 204 | +private: | ||
| 205 | + std::string callback; | ||
| 206 | + std::string path; | ||
| 207 | + SrsRequest* req; | ||
| 208 | +public: | ||
| 209 | + SrsDvrAsyncCallOnSegment(SrsRequest* r, std::string c, std::string p); | ||
| 210 | + virtual ~SrsDvrAsyncCallOnSegment(); | ||
| 211 | +public: | ||
| 212 | + virtual int call(); | ||
| 213 | + virtual std::string to_string(); | ||
| 214 | +}; | ||
| 215 | + | ||
| 216 | +/** | ||
| 217 | +* the async callback for dvr. | ||
| 218 | +*/ | ||
| 219 | +class SrsDvrAsyncCallThread : public ISrsThreadHandler | ||
| 220 | +{ | ||
| 221 | +private: | ||
| 222 | + SrsThread* pthread; | ||
| 223 | + std::vector<ISrsDvrAsyncCall*> callbacks; | ||
| 224 | +public: | ||
| 225 | + SrsDvrAsyncCallThread(); | ||
| 226 | + virtual ~SrsDvrAsyncCallThread(); | ||
| 227 | +public: | ||
| 228 | + virtual int call(ISrsDvrAsyncCall* c); | ||
| 229 | +public: | ||
| 230 | + virtual int start(); | ||
| 231 | + virtual void stop(); | ||
| 232 | + virtual int cycle(); | ||
| 233 | +}; | ||
| 234 | + | ||
| 235 | +/** | ||
| 177 | * the plan for dvr. | 236 | * the plan for dvr. |
| 178 | * use to control the following dvr params: | 237 | * use to control the following dvr params: |
| 179 | * 1. filename: the filename for record file. | 238 | * 1. filename: the filename for record file. |
| @@ -189,6 +248,7 @@ public: | @@ -189,6 +248,7 @@ public: | ||
| 189 | protected: | 248 | protected: |
| 190 | SrsSource* source; | 249 | SrsSource* source; |
| 191 | SrsFlvSegment* segment; | 250 | SrsFlvSegment* segment; |
| 251 | + SrsDvrAsyncCallThread* async; | ||
| 192 | bool dvr_enabled; | 252 | bool dvr_enabled; |
| 193 | public: | 253 | public: |
| 194 | SrsDvrPlan(); | 254 | SrsDvrPlan(); |
| @@ -851,9 +851,18 @@ SrsHttpMessage::~SrsHttpMessage() | @@ -851,9 +851,18 @@ SrsHttpMessage::~SrsHttpMessage() | ||
| 851 | int SrsHttpMessage::initialize() | 851 | int SrsHttpMessage::initialize() |
| 852 | { | 852 | { |
| 853 | int ret = ERROR_SUCCESS; | 853 | int ret = ERROR_SUCCESS; |
| 854 | + | ||
| 855 | + std::string host = get_request_header("Host"); | ||
| 856 | + | ||
| 857 | + // donot parse the empty host for uri, | ||
| 858 | + // for example, the response contains no host, | ||
| 859 | + // ignore it is ok. | ||
| 860 | + if (host.empty()) { | ||
| 861 | + return ret; | ||
| 862 | + } | ||
| 854 | 863 | ||
| 855 | // parse uri to schema/server:port/path?query | 864 | // parse uri to schema/server:port/path?query |
| 856 | - std::string uri = "http://" + get_request_header("Host") + _url; | 865 | + std::string uri = "http://" + host + _url; |
| 857 | if ((ret = _uri->initialize(uri)) != ERROR_SUCCESS) { | 866 | if ((ret = _uri->initialize(uri)) != ERROR_SUCCESS) { |
| 858 | return ret; | 867 | return ret; |
| 859 | } | 868 | } |
| @@ -31,7 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | @@ -31,7 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| 31 | // current release version | 31 | // current release version |
| 32 | #define VERSION_MAJOR 2 | 32 | #define VERSION_MAJOR 2 |
| 33 | #define VERSION_MINOR 0 | 33 | #define VERSION_MINOR 0 |
| 34 | -#define VERSION_REVISION 122 | 34 | +#define VERSION_REVISION 123 |
| 35 | 35 | ||
| 36 | // server info. | 36 | // server info. |
| 37 | #define RTMP_SIG_SRS_KEY "SRS" | 37 | #define RTMP_SIG_SRS_KEY "SRS" |
-
请 注册 或 登录 后发表评论