From af8bf67606eab76f1b7ed48e2b3375be8474b087 Mon Sep 17 00:00:00 2001 From: winlin Date: Thu, 24 Dec 2020 17:19:33 +0800 Subject: [PATCH 1/2] Refine player --- trunk/research/players/rtc_player.html | 437 ++++++++++------------ trunk/research/players/rtc_publisher.html | 299 +++++++++++---- trunk/research/players/srs_player.html | 4 + trunk/research/players/srs_publisher.html | 13 +- 4 files changed, 447 insertions(+), 306 deletions(-) diff --git a/trunk/research/players/rtc_player.html b/trunk/research/players/rtc_player.html index 71417914d..b59fee4ec 100644 --- a/trunk/research/players/rtc_player.html +++ b/trunk/research/players/rtc_player.html @@ -66,167 +66,208 @@ $(function(){ // Async-await-promise based SRS RTC Player. function SrsRtcPlayerAsync() { - var self = { - play: async function(apiUrl, streamUrl) { - self.pc.addTransceiver("audio", {direction: "recvonly"}); - self.pc.addTransceiver("video", {direction: "recvonly"}); - - var offer = await self.pc.createOffer(); - await self.pc.setLocalDescription(offer); - var session = await new Promise(function(resolve, reject) { - // @see https://github.com/rtcdn/rtcdn-draft - var data = { - api: apiUrl, streamurl: streamUrl, clientip: null, sdp: offer.sdp - }; - console.log("Generated offer: ", data); - - $.ajax({ - type: "POST", url: apiUrl, data: JSON.stringify(data), - contentType:'application/json', dataType: 'json' - }).done(function(data) { - console.log("Got answer: ", data); - if (data.code) { - reject(data); return; - } - - resolve(data); - }).fail(function(reason){ - reject(reason); - }); + var self = {}; + + // @see https://github.com/rtcdn/rtcdn-draft + // @url The WebRTC url to play with, for example: + // webrtc://r.ossrs.net/live/livestream + // or specifies the API port: + // webrtc://r.ossrs.net:11985/live/livestream + // or autostart the play: + // webrtc://r.ossrs.net/live/livestream?autostart=true + // or change the app from live to myapp: + // webrtc://r.ossrs.net:11985/myapp/livestream + // or change the stream from livestream to mystream: + // webrtc://r.ossrs.net:11985/live/mystream + // or set the api server to myapi.domain.com: + // webrtc://myapi.domain.com/live/livestream + // or set the candidate(ip) of answer: + // webrtc://r.ossrs.net/live/livestream?eip=39.107.238.185 + // or force to access https API: + // webrtc://r.ossrs.net/live/livestream?schema=https + // or use plaintext, without SRTP: + // webrtc://r.ossrs.net/live/livestream?encrypt=false + // or any other information, will pass-by in the query: + // webrtc://r.ossrs.net/live/livestream?vhost=xxx + // webrtc://r.ossrs.net/live/livestream?token=xxx + self.play = async function(url) { + var conf = self.__internal.prepareUrl(url); + self.pc.addTransceiver("audio", {direction: "recvonly"}); + self.pc.addTransceiver("video", {direction: "recvonly"}); + + var offer = await self.pc.createOffer(); + await self.pc.setLocalDescription(offer); + var session = await new Promise(function(resolve, reject) { + // @see https://github.com/rtcdn/rtcdn-draft + var data = { + api: conf.apiUrl, streamurl: conf.streamUrl, clientip: null, sdp: offer.sdp + }; + console.log("Generated offer: ", data); + + $.ajax({ + type: "POST", url: conf.apiUrl, data: JSON.stringify(data), + contentType:'application/json', dataType: 'json' + }).done(function(data) { + console.log("Got answer: ", data); + if (data.code) { + reject(data); return; + } + + resolve(data); + }).fail(function(reason){ + reject(reason); }); - await self.pc.setRemoteDescription( - new RTCSessionDescription({type: 'answer', sdp: session.sdp}) - ); - return session; - }, - close: function() { - self.pc.close(); - }, - // callbacks. - onaddstream: function (event) {} + }); + await self.pc.setRemoteDescription( + new RTCSessionDescription({type: 'answer', sdp: session.sdp}) + ); + return session; }; - self.pc = new RTCPeerConnection(null); - self.pc.onaddstream = function (event) { - if (self.onaddstream) { - self.onaddstream(event); - } + // Close the publisher. + self.close = function() { + self.pc.close(); }; - return self; - } - // Promise based SRS RTC Player. - function SrsRtcPlayerPromise() { - var self = { - play: function(apiUrl, streamUrl) { - self.pc.addTransceiver("audio", {direction: "recvonly"}); - self.pc.addTransceiver("video", {direction: "recvonly"}); - - return self.pc.createOffer().then(function(offer) { - return self.pc.setLocalDescription(offer).then(function(){ return offer; }); - }).then(function(offer) { - return new Promise(function(resolve, reject) { - // @see https://github.com/rtcdn/rtcdn-draft - var data = { - api: apiUrl, streamurl: streamUrl, clientip: null, sdp: offer.sdp - }; - console.log("Generated offer: ", data); - - $.ajax({ - type: "POST", url: apiUrl, data: JSON.stringify(data), - contentType:'application/json', dataType: 'json' - }).done(function(data) { - console.log("Got answer: ", data); - if (data.code) { - reject(data); return; - } - - resolve(data); - }).fail(function(reason){ - reject(reason); - }); - }); - }).then(function(session) { - return self.pc.setRemoteDescription( - new RTCSessionDescription({type: 'answer', sdp: session.sdp}) - ).then(function(){ return session; }); - }); - }, - close: function() { - self.pc.close(); - }, - // callbacks. - onaddstream: function (event) {} - }; + // The callback when got remote stream. + self.onaddstream = function (event) {}; - self.pc = new RTCPeerConnection(null); - self.pc.onaddstream = function (event) { - if (self.onaddstream) { - self.onaddstream(event); - } - }; - return self; - } + // Internal APIs. + self.__internal = { + defaultPath: '/rtc/v1/play/', + prepareUrl: function (webrtcUrl) { + var urlObject = self.__internal.parse(webrtcUrl); - // Callback based SRS RTC Player. - function SrsRtcPlayerCallbacks() { - var self = { - play: function(apiUrl, streamUrl, success, fail) { - self.pc.addTransceiver("audio", {direction: "recvonly"}); - self.pc.addTransceiver("video", {direction: "recvonly"}); - - self.pc.createOffer(function(offer){ - onOffer(offer); - }, function(reason){ - fail(reason); - }); + // If user specifies the schema, use it as API schema. + var schema = urlObject.user_query.schema; + schema = schema ? schema + ':' : window.location.protocol; - var onOffer = function(offer) { - self.pc.setLocalDescription(offer, function(){ - onOfferDone(offer); - }, function(reason) { - fail(reason); - }); - }; + var port = urlObject.port || 1985; + if (schema === 'https:') { + port = urlObject.port || 443; + } + + // @see https://github.com/rtcdn/rtcdn-draft + var api = urlObject.user_query.play || self.__internal.defaultPath; + if (api.lastIndexOf('/') !== api.length - 1) { + api += '/'; + } + + apiUrl = schema + '//' + urlObject.server + ':' + port + api; + for (var key in urlObject.user_query) { + if (key !== 'api' && key !== 'play') { + apiUrl += '&' + key + '=' + urlObject.user_query[key]; + } + } + // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v + var apiUrl = apiUrl.replace(api + '&', api + '?'); + + var streamUrl = urlObject.url; - var onOfferDone = function (offer) { - // @see https://github.com/rtcdn/rtcdn-draft - var data = { - api: apiUrl, streamurl: streamUrl, clientip: null, sdp: offer.sdp - }; - console.log("Generated offer: ", data); - - $.ajax({ - type: "POST", url: apiUrl, data: JSON.stringify(data), - contentType:'application/json', dataType: 'json' - }).done(function(data) { - console.log("Got answer: ", data); - if (data.code) { - fail(data); return; + return {apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port}; + }, + parse: function (url) { + // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri + var a = document.createElement("a"); + a.href = url.replace("rtmp://", "http://") + .replace("webrtc://", "http://") + .replace("rtc://", "http://"); + + var vhost = a.hostname; + var app = a.pathname.substr(1, a.pathname.lastIndexOf("/") - 1); + var stream = a.pathname.substr(a.pathname.lastIndexOf("/") + 1); + + // parse the vhost in the params of app, that srs supports. + app = app.replace("...vhost...", "?vhost="); + if (app.indexOf("?") >= 0) { + var params = app.substr(app.indexOf("?")); + app = app.substr(0, app.indexOf("?")); + + if (params.indexOf("vhost=") > 0) { + vhost = params.substr(params.indexOf("vhost=") + "vhost=".length); + if (vhost.indexOf("&") > 0) { + vhost = vhost.substr(0, vhost.indexOf("&")); } + } + } - onAnswer(data); - }).fail(function(reason){ - fail(reason); - }); - }; + // when vhost equals to server, and server is ip, + // the vhost is __defaultVhost__ + if (a.hostname === vhost) { + var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; + if (re.test(a.hostname)) { + vhost = "__defaultVhost__"; + } + } + + // parse the schema + var schema = "rtmp"; + if (url.indexOf("://") > 0) { + schema = url.substr(0, url.indexOf("://")); + } + + var port = a.port; + if (!port) { + if (schema === 'http') { + port = 80; + } else if (schema === 'https') { + port = 443; + } else if (schema === 'rtmp') { + port = 1935; + } + } - var onAnswer = function(session) { - var answer = session.sdp; - self.pc.setRemoteDescription( - new RTCSessionDescription({type: 'answer', sdp: answer}) - ).then(function(){ - success(session); - }).catch(function(reason) { - fail(reason); - }); + var ret = { + url: url, + schema: schema, + server: a.hostname, port: port, + vhost: vhost, app: app, stream: stream }; + self.__internal.fill_query(a.search, ret); + + // For webrtc API, we use 443 if page is https, or schema specified it. + if (!ret.port) { + if (schema === 'webrtc' || schema === 'rtc') { + if (ret.user_query.schema === 'https') { + ret.port = 443; + } else if (window.location.href.indexOf('https://') === 0) { + ret.port = 443; + } else { + // For WebRTC, SRS use 1985 as default API port. + ret.port = 1985; + } + } + } + + return ret; }, - close: function() { - self.pc.close(); - }, - // callbacks. - onaddstream: function (event) {} + fill_query: function (query_string, obj) { + // pure user query object. + obj.user_query = {}; + + if (query_string.length === 0) { + return; + } + + // split again for angularjs. + if (query_string.indexOf("?") >= 0) { + query_string = query_string.split("?")[1]; + } + + var queries = query_string.split("&"); + for (var i = 0; i < queries.length; i++) { + var elem = queries[i]; + + var query = elem.split("="); + obj[query[0]] = query[1]; + obj.user_query[query[0]] = query[1]; + } + + // alias domain for vhost. + if (obj.domain) { + obj.vhost = obj.domain; + } + } }; self.pc = new RTCPeerConnection(null); @@ -235,98 +276,36 @@ self.onaddstream(event); } }; + return self; } - // Build RTC api url. - var prepareUrl = function () { - var apiUrl, streamUrl; - - if (true) { - var urlObject = parse_rtmp_url($("#txt_url").val()); - - // If user specifies the schema, use it as API schema. - var schema = urlObject.user_query.schema; - schema = schema? schema + ':' : window.location.protocol; - - var port = urlObject.port || 1985; - if (schema === 'https:') { - port = urlObject.port || 443; - } - - // @see https://github.com/rtcdn/rtcdn-draft - var api = urlObject.user_query.play || '/rtc/v1/play/'; - if (api.lastIndexOf('/') !== api.length - 1) { - api += '/'; - } - - apiUrl = schema + '//' + urlObject.server + ':' + port + api; - for (var key in urlObject.user_query) { - if (key !== 'api' && key !== 'play') { - apiUrl += '&' + key + '=' + urlObject.user_query[key]; - } - } - // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v - apiUrl = apiUrl.replace(api + '&', api + '?'); - - streamUrl = urlObject.url; - } - - return {apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port}; - }; + var sdk = null; // Global handler to do cleanup when replaying. + var startPlay = function() { + $('#rtc_media_player').show(); - // Start play with conf. - var playStream = function (conf) { // Close PC when user replay. if (sdk) { sdk.close(); } - // Use Callback style. - if (true) { - if (true) { - sdk = new SrsRtcPlayerAsync(); - } else { - sdk = new SrsRtcPlayerPromise(); - } - sdk.onaddstream = function (event) { - console.log('Start play, event: ', event); - $('#rtc_media_player').prop('srcObject', event.stream); - }; - - sdk.play(conf.apiUrl, conf.streamUrl).then(function(session){ - var simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/'; - $('#sessionid').html(session.sessionid); - $('#simulator-drop').attr('href', simulator + '?drop=1&username=' + session.sessionid); - }).catch(function (reason) { - sdk.close(); - $('#rtc_media_player').hide(); - console.error(reason); - }); - } else if (false) { - sdk = new SrsRtcPlayerCallbacks(); - sdk.onaddstream = function (event) { - console.log('Start play, event: ', event); - $('#rtc_media_player').prop('srcObject', event.stream); - }; - - sdk.play(conf.apiUrl, conf.streamUrl, function (session) { - var simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/'; - $('#sessionid').html(session.sessionid); - $('#simulator-drop').attr('href', simulator + '?drop=1&username=' + session.sessionid); - }, function (reason) { - sdk.close(); - $('#rtc_media_player').hide(); - throw reason; - }); - } - }; + sdk = new SrsRtcPlayerAsync(); + sdk.onaddstream = function (event) { + console.log('Start play, event: ', event); + $('#rtc_media_player').prop('srcObject', event.stream); + }; - var sdk = null; // Global handler to do cleanup when replaying. - var startPlay = function() { - $('#rtc_media_player').show(); - var conf = prepareUrl(); - playStream(conf); + // For example: + // webrtc://r.ossrs.net/live/livestream + var url = $("#txt_url").val(); + sdk.play(url).then(function(session){ + $('#sessionid').html(session.sessionid); + $('#simulator-drop').attr('href', session.simulator + '?drop=1&username=' + session.sessionid); + }).catch(function (reason) { + sdk.close(); + $('#rtc_media_player').hide(); + console.error(reason); + }); }; $('#rtc_media_player').hide(); diff --git a/trunk/research/players/rtc_publisher.html b/trunk/research/players/rtc_publisher.html index 1cabf9733..fb62f0bf8 100644 --- a/trunk/research/players/rtc_publisher.html +++ b/trunk/research/players/rtc_publisher.html @@ -65,94 +65,257 @@ From 0ccbd7c40ac6ea1c70b7eb781ed09516ccb1ba1e Mon Sep 17 00:00:00 2001 From: winlin Date: Thu, 24 Dec 2020 18:49:56 +0800 Subject: [PATCH 2/2] Support get schema for HTTP message --- trunk/src/protocol/srs_service_http_conn.cpp | 10 +++++++++- trunk/src/protocol/srs_service_http_conn.hpp | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/trunk/src/protocol/srs_service_http_conn.cpp b/trunk/src/protocol/srs_service_http_conn.cpp index 23a2591a0..d239972b2 100644 --- a/trunk/src/protocol/srs_service_http_conn.cpp +++ b/trunk/src/protocol/srs_service_http_conn.cpp @@ -325,6 +325,8 @@ SrsHttpMessage::SrsHttpMessage(ISrsReader* reader, SrsFastStream* buffer) : ISrs // From HTTP/1.1, default to keep alive. _keep_alive = true; type_ = 0; + + schema_ = "http"; } SrsHttpMessage::~SrsHttpMessage() @@ -416,7 +418,8 @@ srs_error_t SrsHttpMessage::set_url(string url, bool allow_jsonp) void SrsHttpMessage::set_https(bool v) { - _uri->set_schema(v? "https" : "http"); + schema_ = v? "https" : "http"; + _uri->set_schema(schema_); } ISrsConnection* SrsHttpMessage::connection() @@ -429,6 +432,11 @@ void SrsHttpMessage::set_connection(ISrsConnection* conn) owner_conn = conn; } +string SrsHttpMessage::schema() +{ + return schema_; +} + uint8_t SrsHttpMessage::method() { if (jsonp && !jsonp_method.empty()) { diff --git a/trunk/src/protocol/srs_service_http_conn.hpp b/trunk/src/protocol/srs_service_http_conn.hpp index fa048d4b4..0181f9294 100644 --- a/trunk/src/protocol/srs_service_http_conn.hpp +++ b/trunk/src/protocol/srs_service_http_conn.hpp @@ -120,6 +120,7 @@ private: // Whether the body is chunked. bool chunked; private: + std::string schema_; // The parsed url. std::string _url; // The extension of file, for example, .flv @@ -152,6 +153,8 @@ public: virtual ISrsConnection* connection(); virtual void set_connection(ISrsConnection* conn); public: + // The schema, http or https. + virtual std::string schema(); virtual uint8_t method(); virtual uint16_t status_code(); // The method helpers.