From dfef94411f3c19dfb38ec0f0bc76fd7dcf62c1eb Mon Sep 17 00:00:00 2001 From: Winlin Date: Tue, 21 Mar 2023 08:49:07 +0800 Subject: [PATCH] Support WHIP and WHEP player. v5.0.147 and v6.0.35 (#3460) PICK c001acaae9680c149f4edac581a0f8ffcbca1bb8 Co-authored-by: chundonglinlin Co-authored-by: panda <542638787@qq.com> --- trunk/doc/CHANGELOG.md | 1 + trunk/doc/Features.md | 2 + trunk/research/players/js/srs.page.js | 42 +++++++ trunk/research/players/js/srs.sdk.js | 147 +++++++++++++++++++++- trunk/research/players/rtc_player.html | 5 +- trunk/research/players/rtc_publisher.html | 2 + trunk/research/players/srs_player.html | 2 + trunk/research/players/whep.html | 114 +++++++++++++++++ trunk/research/players/whip.html | 142 +++++++++++++++++++++ trunk/src/app/srs_app_rtc_api.cpp | 5 +- trunk/src/core/srs_core_version5.hpp | 2 +- 11 files changed, 455 insertions(+), 9 deletions(-) create mode 100644 trunk/research/players/whep.html create mode 100644 trunk/research/players/whip.html diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 3e11f63f19..957db8314f 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -8,6 +8,7 @@ The changelog for SRS. ## SRS 5.0 Changelog +* v5.0, 2023-03-20, Merge [#3460](https://github.com/ossrs/srs/pull/3460): WebRTC: Support WHIP/WHEP players. v5.0.147 (#3460) * v5.0, 2023-03-07, Merge [#3446](https://github.com/ossrs/srs/pull/3446): WebRTC: Warning if no ideal profile. v5.0.146 (#3446) * v5.0, 2023-03-06, Merge [#3445](https://github.com/ossrs/srs/pull/3445): Support configure for generic linux. v5.0.145 (#3445) * v5.0, 2023-03-04, Merge [#3105](https://github.com/ossrs/srs/pull/3105): Kickoff publisher when stream is idle, which means no players. v5.0.144 (#3105) diff --git a/trunk/doc/Features.md b/trunk/doc/Features.md index ea709149c3..bce3fbbefd 100644 --- a/trunk/doc/Features.md +++ b/trunk/doc/Features.md @@ -67,8 +67,10 @@ The features of SRS. - [x] Other: [Experimental] Support pushing MPEG-TS over UDP, please read [bug #250](https://github.com/ossrs/srs/issues/250). - [x] Other: [Experimental] Support pushing FLV over HTTP POST, please read wiki([CN](https://ossrs.net/lts/zh-cn/docs/v4/doc/streamer#push-http-flv-to-srs), [EN](https://ossrs.io/lts/en-us/docs/v4/doc/streamer#push-http-flv-to-srs)). - [x] Other: [Experimental] Support push stream by GB28181, [#3176](https://github.com/ossrs/srs/issues/3176). v5.0.74+ +- [x] Other: Support WHIP/WHEP player, [#3460](https://github.com/ossrs/srs/pull/3460). v5.0.147+ - [ ] System: Support Windows/Cygwin 64bits, [#2532](https://github.com/ossrs/srs/issues/2532). - [ ] System: Support H.265 over RTMP and HLS, [#465](https://github.com/ossrs/srs/issues/465). +- [ ] System: Proxy to extend origin servers, [#3138](https://github.com/ossrs/srs/issues/3138). - [ ] System: Support source cleanup for idle streams, [#413](https://github.com/ossrs/srs/issues/413). - [ ] Live: Support HLS variant, [#463](https://github.com/ossrs/srs/issues/463). - [ ] RTC: Support IETF-QUIC for WebRTC Cluster, [#2091](https://github.com/ossrs/srs/issues/2091). diff --git a/trunk/research/players/js/srs.page.js b/trunk/research/players/js/srs.page.js index cf7a823d6d..26b50a8503 100755 --- a/trunk/research/players/js/srs.page.js +++ b/trunk/research/players/js/srs.page.js @@ -17,6 +17,8 @@ function update_nav() { $("#nav_srs_player").attr("href", "srs_player.html" + window.location.search); $("#nav_rtc_player").attr("href", "rtc_player.html" + window.location.search); $("#nav_rtc_publisher").attr("href", "rtc_publisher.html" + window.location.search); + $("#nav_whip").attr("href", "whip.html" + window.location.search); + $("#nav_whep").attr("href", "whep.html" + window.location.search); $("#nav_srs_publisher").attr("href", "srs_publisher.html" + window.location.search); $("#nav_srs_chat").attr("href", "srs_chat.html" + window.location.search); $("#nav_srs_bwt").attr("href", "srs_bwt.html" + window.location.search); @@ -116,6 +118,38 @@ function build_default_rtc_url(query) { return uri; }; +function build_default_whip_whep_url(query, apiPath) { + // The format for query string to overwrite configs of server. + console.log('?eip=x.x.x.x to overwrite candidate. 覆盖服务器candidate(外网IP)配置'); + console.log('?api=x to overwrite WebRTC API(1985).'); + console.log('?schema=http|https to overwrite WebRTC API protocol.'); + + var server = (!query.server)? window.location.hostname:query.server; + var vhost = (!query.vhost)? window.location.hostname:query.vhost; + var app = (!query.app)? "live":query.app; + var stream = (!query.stream)? "livestream":query.stream; + var api = ':' + (query.api || (window.location.protocol === 'http:' ? '1985' : '1990')); + + var queries = []; + if (server !== vhost && vhost !== "__defaultVhost__") { + queries.push("vhost=" + vhost); + } + if (query.schema && window.location.protocol !== query.schema + ':') { + queries.push('schema=' + query.schema); + } + queries = user_extra_params(query, queries, true); + + var uri = window.location.protocol + "//" + server + api + apiPath + "?app=" + app + "&stream=" + stream + "&" + queries.join('&'); + while (uri.lastIndexOf("?") === uri.length - 1) { + uri = uri.slice(0, uri.length - 1); + } + while (uri.lastIndexOf("&") === uri.length - 1) { + uri = uri.slice(0, uri.length - 1); + } + + return uri; +} + /** * initialize the page. * @param flv_url the div id contains the flv stream url to play @@ -136,3 +170,11 @@ function srs_init_rtc(id, query) { update_nav(); $(id).val(build_default_rtc_url(query)); } +function srs_init_whip(id, query) { + update_nav(); + $(id).val(build_default_whip_whep_url(query, '/rtc/v1/whip/')); +} +function srs_init_whep(id, query) { + update_nav(); + $(id).val(build_default_whip_whep_url(query, '/rtc/v1/whip-play/')); +} diff --git a/trunk/research/players/js/srs.sdk.js b/trunk/research/players/js/srs.sdk.js index f2ea96c9b5..bba86ba799 100644 --- a/trunk/research/players/js/srs.sdk.js +++ b/trunk/research/players/js/srs.sdk.js @@ -134,14 +134,14 @@ function SrsRtcPublisherAsync() { api += '/'; } - apiUrl = schema + '//' + urlObject.server + ':' + port + api; + var 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 + '?'); + apiUrl = apiUrl.replace(api + '&', api + '?'); var streamUrl = urlObject.url; @@ -369,14 +369,14 @@ function SrsRtcPlayerAsync() { api += '/'; } - apiUrl = schema + '//' + urlObject.server + ':' + port + api; + var 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 + '?'); + apiUrl = apiUrl.replace(api + '&', api + '?'); var streamUrl = urlObject.url; @@ -510,6 +510,145 @@ function SrsRtcPlayerAsync() { return self; } +// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter +// Async-awat-prmise based SRS RTC Publisher by WHIP. +function SrsRtcWhipWhepAsync() { + var self = {}; + + // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia + self.constraints = { + audio: true, + video: { + width: {ideal: 320, max: 576} + } + }; + + // See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/ + // @url The WebRTC url to publish with, for example: + // http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream + self.publish = async function (url) { + if (url.indexOf('/whip/') === -1) throw new Error(`invalid WHIP url ${url}`); + + self.pc.addTransceiver("audio", {direction: "sendonly"}); + self.pc.addTransceiver("video", {direction: "sendonly"}); + + if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') { + throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`); + } + var stream = await navigator.mediaDevices.getUserMedia(self.constraints); + + // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack + stream.getTracks().forEach(function (track) { + self.pc.addTrack(track); + + // Notify about local track when stream is ok. + self.ontrack && self.ontrack({track: track}); + }); + + var offer = await self.pc.createOffer(); + await self.pc.setLocalDescription(offer); + const answer = await new Promise(function (resolve, reject) { + console.log("Generated offer: ", offer); + + const xhr = new XMLHttpRequest(); + xhr.onload = function() { + if (xhr.readyState !== xhr.DONE) return; + if (xhr.status !== 200) return reject(xhr); + const data = xhr.responseText; + console.log("Got answer: ", data); + return data.code ? reject(xhr) : resolve(data); + } + xhr.open('POST', url, true); + xhr.setRequestHeader('Content-type', 'application/sdp'); + xhr.send(offer.sdp); + }); + await self.pc.setRemoteDescription( + new RTCSessionDescription({type: 'answer', sdp: answer}) + ); + + return self.__internal.parseId(url, offer.sdp, answer); + }; + + // See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/ + // @url The WebRTC url to play with, for example: + // http://localhost:1985/rtc/v1/whip-play/?app=live&stream=livestream + self.play = async function(url) { + if (url.indexOf('/whip-play/') === -1 && url.indexOf('/whep/') === -1) throw new Error(`invalid WHEP url ${url}`); + + self.pc.addTransceiver("audio", {direction: "recvonly"}); + self.pc.addTransceiver("video", {direction: "recvonly"}); + + var offer = await self.pc.createOffer(); + await self.pc.setLocalDescription(offer); + const answer = await new Promise(function(resolve, reject) { + console.log("Generated offer: ", offer); + + const xhr = new XMLHttpRequest(); + xhr.onload = function() { + if (xhr.readyState !== xhr.DONE) return; + if (xhr.status !== 200) return reject(xhr); + const data = xhr.responseText; + console.log("Got answer: ", data); + return data.code ? reject(xhr) : resolve(data); + } + xhr.open('POST', url, true); + xhr.setRequestHeader('Content-type', 'application/sdp'); + xhr.send(offer.sdp); + }); + await self.pc.setRemoteDescription( + new RTCSessionDescription({type: 'answer', sdp: answer}) + ); + + return self.__internal.parseId(url, offer.sdp, answer); + }; + + // Close the publisher. + self.close = function () { + self.pc && self.pc.close(); + self.pc = null; + }; + + // The callback when got local stream. + // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack + self.ontrack = function (event) { + // Add track to stream of SDK. + self.stream.addTrack(event.track); + }; + + self.pc = new RTCPeerConnection(null); + + // To keep api consistent between player and publisher. + // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack + // @see https://webrtc.org/getting-started/media-devices + self.stream = new MediaStream(); + + // Internal APIs. + self.__internal = { + parseId: (url, offer, answer) => { + let sessionid = offer.substr(offer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length); + sessionid = sessionid.substr(0, sessionid.indexOf('\n') - 1) + ':'; + sessionid += answer.substr(answer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length); + sessionid = sessionid.substr(0, sessionid.indexOf('\n')); + + const a = document.createElement("a"); + a.href = url; + return { + sessionid: sessionid, // Should be ice-ufrag of answer:offer. + simulator: a.protocol + '//' + a.host + '/rtc/v1/nack/', + }; + }, + }; + + // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack + self.pc.ontrack = function(event) { + if (self.ontrack) { + self.ontrack(event); + } + }; + + return self; +} + // Format the codec of RTCRtpSender, kind(audio/video) is optional filter. // https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#getting_the_supported_codecs function SrsRtcFormatSenders(senders, kind) { diff --git a/trunk/research/players/rtc_player.html b/trunk/research/players/rtc_player.html index 3eb77b0f70..8941338a5c 100644 --- a/trunk/research/players/rtc_player.html +++ b/trunk/research/players/rtc_player.html @@ -26,6 +26,8 @@
  • SRS播放器
  • RTC播放器
  • RTC推流
  • +
  • WHIP
  • +
  • WHEP
  • iOS/Andriod
  • @@ -105,8 +107,7 @@ $('#rtc_media_player').prop('muted', true); console.warn('For autostart, we should mute it, see https://www.jianshu.com/p/c3c6944eed5a ' + 'or https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#audiovideo_elements'); - - startPlay(); + window.addEventListener("load", function(){ startPlay(); }); } }); diff --git a/trunk/research/players/rtc_publisher.html b/trunk/research/players/rtc_publisher.html index a7dbd424dc..f78fb84a33 100644 --- a/trunk/research/players/rtc_publisher.html +++ b/trunk/research/players/rtc_publisher.html @@ -26,6 +26,8 @@
  • SRS播放器
  • RTC播放器
  • RTC推流
  • +
  • WHIP
  • +
  • WHEP
  • iOS/Andriod
  • diff --git a/trunk/research/players/srs_player.html b/trunk/research/players/srs_player.html index 4893fb431f..fa1d7c1db7 100755 --- a/trunk/research/players/srs_player.html +++ b/trunk/research/players/srs_player.html @@ -21,6 +21,8 @@
  • SRS播放器
  • RTC播放器
  • RTC推流
  • +
  • WHIP
  • +
  • WHEP
  • iOS/Andriod
  • diff --git a/trunk/research/players/whep.html b/trunk/research/players/whep.html new file mode 100644 index 0000000000..e6aa2f305e --- /dev/null +++ b/trunk/research/players/whep.html @@ -0,0 +1,114 @@ + + + + SRS + + + + + + + + + + + + +
    +
    + URL: + + +
    + + + + + + SessionID: + + + Simulator: Drop + + +
    + + + + diff --git a/trunk/research/players/whip.html b/trunk/research/players/whip.html new file mode 100644 index 0000000000..fa6f83fa53 --- /dev/null +++ b/trunk/research/players/whip.html @@ -0,0 +1,142 @@ + + + + SRS + + + + + + + + + + + + +
    +
    + URL: + + +
    + + + + + + SessionID: + + + Audio:
    + Video: + + + Simulator: Drop + + +
    + + + + diff --git a/trunk/src/app/srs_app_rtc_api.cpp b/trunk/src/app/srs_app_rtc_api.cpp index 1d5a657625..f8ca69e54d 100644 --- a/trunk/src/app/srs_app_rtc_api.cpp +++ b/trunk/src/app/srs_app_rtc_api.cpp @@ -638,6 +638,7 @@ srs_error_t SrsGoApiRtcWhip::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessa ruc.req_->vhost = ruc.req_->host; ruc.req_->app = app.empty() ? "live" : app; ruc.req_->stream = stream.empty() ? "livestream" : stream; + ruc.req_->param = r->query(); // discovery vhost, resolve the vhost from config SrsConfDirective* parsed_vhost = _srs_config->get_vhost(ruc.req_->vhost); @@ -645,9 +646,9 @@ srs_error_t SrsGoApiRtcWhip::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessa ruc.req_->vhost = parsed_vhost->arg0(); } - srs_trace("RTC whip %s %s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, codec=%s", + srs_trace("RTC whip %s %s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, codec=%s, param=%s", action.c_str(), ruc.req_->get_stream_url().c_str(), clientip.c_str(), ruc.req_->app.c_str(), ruc.req_->stream.c_str(), - remote_sdp_str.length(), eip.c_str(), codec.c_str() + remote_sdp_str.length(), eip.c_str(), codec.c_str(), ruc.req_->param.c_str() ); ruc.eip_ = eip; diff --git a/trunk/src/core/srs_core_version5.hpp b/trunk/src/core/srs_core_version5.hpp index f6ada503f1..61ea271f71 100644 --- a/trunk/src/core/srs_core_version5.hpp +++ b/trunk/src/core/srs_core_version5.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 5 #define VERSION_MINOR 0 -#define VERSION_REVISION 146 +#define VERSION_REVISION 147 #endif