From 80ff36ed6f841c483f3389453d1804b6c1346488 Mon Sep 17 00:00:00 2001 From: mengxiaowei1 Date: Thu, 21 Dec 2023 09:56:22 +0800 Subject: [PATCH] update srs.sdk.js to support whip and whep --- srs-player/public/js/srs.player.js | 14 +++ srs-player/public/js/srs.sdk.js | 185 ++++++++++++++++++++++++++++- 2 files changed, 193 insertions(+), 6 deletions(-) diff --git a/srs-player/public/js/srs.player.js b/srs-player/public/js/srs.player.js index a68ec26..23e9a01 100644 --- a/srs-player/public/js/srs.player.js +++ b/srs-player/public/js/srs.player.js @@ -51,6 +51,13 @@ return console.log(`Play by srs.sdk.js for ${self.url}`); } + if (self.url.indexOf('whep') > 0) { + const sdk = new SrsRtcWhipWhepAsync(); + self.dom.prop('srcObject', sdk.stream); + sdk.play(self.url); + return console.log(`Play by srs.sdk.js for ${self.url}`); + } + console.error(`URL is not supported:${self.url}`); }; @@ -78,6 +85,13 @@ return console.log(`Publish by srs.sdk.js for ${self.url}`); } + if (self.url.indexOf('whip') > 0) { + const sdk = new SrsRtcWhipWhepAsync(); + self.dom.prop('srcObject', sdk.stream); + sdk.publish(self.url); + return console.log(`Publish by srs.sdk.js for ${self.url}`); + } + console.error(`URL is not supported:${self.url}`); }; diff --git a/srs-player/public/js/srs.sdk.js b/srs-player/public/js/srs.sdk.js index b7e6947..ea5189e 100644 --- a/srs-player/public/js/srs.sdk.js +++ b/srs-player/public/js/srs.sdk.js @@ -54,6 +54,8 @@ function SrsRtcPublisherAsync() { var conf = self.__internal.prepareUrl(url); self.pc.addTransceiver("audio", {direction: "sendonly"}); self.pc.addTransceiver("video", {direction: "sendonly"}); + //self.pc.addTransceiver("video", {direction: "sendonly"}); + //self.pc.addTransceiver("audio", {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`); @@ -81,7 +83,7 @@ function SrsRtcPublisherAsync() { const xhr = new XMLHttpRequest(); xhr.onload = function() { if (xhr.readyState !== xhr.DONE) return; - if (xhr.status !== 200) return reject(xhr); + if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr); const data = JSON.parse(xhr.responseText); console.log("Got answer: ", data); return data.code ? reject(xhr) : resolve(data); @@ -132,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; @@ -300,6 +302,8 @@ function SrsRtcPlayerAsync() { var conf = self.__internal.prepareUrl(url); self.pc.addTransceiver("audio", {direction: "recvonly"}); self.pc.addTransceiver("video", {direction: "recvonly"}); + //self.pc.addTransceiver("video", {direction: "recvonly"}); + //self.pc.addTransceiver("audio", {direction: "recvonly"}); var offer = await self.pc.createOffer(); await self.pc.setLocalDescription(offer); @@ -314,7 +318,7 @@ function SrsRtcPlayerAsync() { const xhr = new XMLHttpRequest(); xhr.onload = function() { if (xhr.readyState !== xhr.DONE) return; - if (xhr.status !== 200) return reject(xhr); + if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr); const data = JSON.parse(xhr.responseText); console.log("Got answer: ", data); return data.code ? reject(xhr) : resolve(data); @@ -365,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; @@ -506,3 +510,172 @@ 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 && xhr.status !== 201) 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/whep/?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 && xhr.status !== 201) 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) { + var codecs = []; + senders.forEach(function (sender) { + var params = sender.getParameters(); + params && params.codecs && params.codecs.forEach(function(c) { + if (kind && sender.track.kind !== kind) { + return; + } + + if (c.mimeType.indexOf('/red') > 0 || c.mimeType.indexOf('/rtx') > 0 || c.mimeType.indexOf('/fec') > 0) { + return; + } + + var s = ''; + + s += c.mimeType.replace('audio/', '').replace('video/', ''); + s += ', ' + c.clockRate + 'HZ'; + if (sender.track.kind === "audio") { + s += ', channels: ' + c.channels; + } + s += ', pt: ' + c.payloadType; + + codecs.push(s); + }); + }); + return codecs.join(", "); +} +