diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index ba643fe448..af3213621e 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -8,6 +8,7 @@ The changelog for SRS. ## SRS 6.0 Changelog +* v6.0, 2023-03-20, Merge [#3460](https://github.com/ossrs/srs/pull/3460): WebRTC: Support WHIP/WHEP players. v6.0.35 (#3460) * v6.0, 2023-03-07, Merge [#3441](https://github.com/ossrs/srs/pull/3441): HEVC: webrtc support hevc on safari. v6.0.34 (#3441) * v6.0, 2023-03-07, Merge [#3446](https://github.com/ossrs/srs/pull/3446): WebRTC: Warning if no ideal profile. v6.0.33 (#3446) * v6.0, 2023-03-06, Merge [#3445](https://github.com/ossrs/srs/pull/3445): Support configure for generic linux. v6.0.32 (#3445) @@ -48,6 +49,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 fb890fd0f3..32aa06d420 100644 --- a/trunk/doc/Features.md +++ b/trunk/doc/Features.md @@ -74,6 +74,7 @@ The features of SRS. - [x] Other: [Experimental] Support pushing MPEG-TS over UDP, please read [bug #250](https://github.com/ossrs/srs/issues/250). v2.0.111+ - [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)). v2.0.163+ - [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: 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). - [ ] System: Support JT808 and JT1708 for transport, [#3420](https://github.com/ossrs/srs/issues/3420). 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 @@