srs.sdk.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718
  1. //
  2. // Copyright (c) 2013-2021 Winlin
  3. //
  4. // SPDX-License-Identifier: MIT
  5. //
  6. 'use strict';
  7. function SrsError(name, message) {
  8. this.name = name;
  9. this.message = message;
  10. this.stack = (new Error()).stack;
  11. }
  12. SrsError.prototype = Object.create(Error.prototype);
  13. SrsError.prototype.constructor = SrsError;
  14. // Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
  15. // Async-awat-prmise based SRS RTC Publisher.
  16. function SrsRtcPublisherAsync() {
  17. var self = {};
  18. // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
  19. self.constraints = {
  20. audio: true,
  21. video: {
  22. width: {ideal: 320, max: 576}
  23. }
  24. };
  25. // @see https://github.com/rtcdn/rtcdn-draft
  26. // @url The WebRTC url to play with, for example:
  27. // webrtc://r.ossrs.net/live/livestream
  28. // or specifies the API port:
  29. // webrtc://r.ossrs.net:11985/live/livestream
  30. // or autostart the publish:
  31. // webrtc://r.ossrs.net/live/livestream?autostart=true
  32. // or change the app from live to myapp:
  33. // webrtc://r.ossrs.net:11985/myapp/livestream
  34. // or change the stream from livestream to mystream:
  35. // webrtc://r.ossrs.net:11985/live/mystream
  36. // or set the api server to myapi.domain.com:
  37. // webrtc://myapi.domain.com/live/livestream
  38. // or set the candidate(eip) of answer:
  39. // webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185
  40. // or force to access https API:
  41. // webrtc://r.ossrs.net/live/livestream?schema=https
  42. // or use plaintext, without SRTP:
  43. // webrtc://r.ossrs.net/live/livestream?encrypt=false
  44. // or any other information, will pass-by in the query:
  45. // webrtc://r.ossrs.net/live/livestream?vhost=xxx
  46. // webrtc://r.ossrs.net/live/livestream?token=xxx
  47. self.publish = async function (url) {
  48. var conf = self.__internal.prepareUrl(url);
  49. self.pc.addTransceiver("audio", {direction: "sendonly"});
  50. self.pc.addTransceiver("video", {direction: "sendonly"});
  51. //self.pc.addTransceiver("video", {direction: "sendonly"});
  52. //self.pc.addTransceiver("audio", {direction: "sendonly"});
  53. if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {
  54. throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`);
  55. }
  56. var stream = await navigator.mediaDevices.getUserMedia(self.constraints);
  57. // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
  58. stream.getTracks().forEach(function (track) {
  59. self.pc.addTrack(track);
  60. // Notify about local track when stream is ok.
  61. self.ontrack && self.ontrack({track: track});
  62. });
  63. var offer = await self.pc.createOffer();
  64. await self.pc.setLocalDescription(offer);
  65. var session = await new Promise(function (resolve, reject) {
  66. // @see https://github.com/rtcdn/rtcdn-draft
  67. var data = {
  68. api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,
  69. clientip: null, sdp: offer.sdp
  70. };
  71. console.log("Generated offer: ", data);
  72. const xhr = new XMLHttpRequest();
  73. xhr.onload = function() {
  74. if (xhr.readyState !== xhr.DONE) return;
  75. if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
  76. const data = JSON.parse(xhr.responseText);
  77. console.log("Got answer: ", data);
  78. return data.code ? reject(xhr) : resolve(data);
  79. }
  80. xhr.open('POST', conf.apiUrl, true);
  81. xhr.setRequestHeader('Content-type', 'application/json');
  82. xhr.send(JSON.stringify(data));
  83. });
  84. await self.pc.setRemoteDescription(
  85. new RTCSessionDescription({type: 'answer', sdp: session.sdp})
  86. );
  87. session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/';
  88. return session;
  89. };
  90. // Close the publisher.
  91. self.close = function () {
  92. self.pc && self.pc.close();
  93. self.pc = null;
  94. };
  95. // The callback when got local stream.
  96. // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
  97. self.ontrack = function (event) {
  98. // Add track to stream of SDK.
  99. self.stream.addTrack(event.track);
  100. };
  101. // Internal APIs.
  102. self.__internal = {
  103. defaultPath: '/rtc/v1/publish/',
  104. prepareUrl: function (webrtcUrl) {
  105. var urlObject = self.__internal.parse(webrtcUrl);
  106. // If user specifies the schema, use it as API schema.
  107. var schema = urlObject.user_query.schema;
  108. schema = schema ? schema + ':' : window.location.protocol;
  109. var port = urlObject.port || 1985;
  110. if (schema === 'https:') {
  111. port = urlObject.port || 443;
  112. }
  113. // @see https://github.com/rtcdn/rtcdn-draft
  114. var api = urlObject.user_query.play || self.__internal.defaultPath;
  115. if (api.lastIndexOf('/') !== api.length - 1) {
  116. api += '/';
  117. }
  118. var apiUrl = schema + '//' + urlObject.server + ':' + port + api;
  119. for (var key in urlObject.user_query) {
  120. if (key !== 'api' && key !== 'play') {
  121. apiUrl += '&' + key + '=' + urlObject.user_query[key];
  122. }
  123. }
  124. // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v
  125. apiUrl = apiUrl.replace(api + '&', api + '?');
  126. var streamUrl = urlObject.url;
  127. return {
  128. apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port,
  129. tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7)
  130. };
  131. },
  132. parse: function (url) {
  133. // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
  134. var a = document.createElement("a");
  135. a.href = url.replace("rtmp://", "http://")
  136. .replace("webrtc://", "http://")
  137. .replace("rtc://", "http://");
  138. var vhost = a.hostname;
  139. var app = a.pathname.substring(1, a.pathname.lastIndexOf("/"));
  140. var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1);
  141. // parse the vhost in the params of app, that srs supports.
  142. app = app.replace("...vhost...", "?vhost=");
  143. if (app.indexOf("?") >= 0) {
  144. var params = app.slice(app.indexOf("?"));
  145. app = app.slice(0, app.indexOf("?"));
  146. if (params.indexOf("vhost=") > 0) {
  147. vhost = params.slice(params.indexOf("vhost=") + "vhost=".length);
  148. if (vhost.indexOf("&") > 0) {
  149. vhost = vhost.slice(0, vhost.indexOf("&"));
  150. }
  151. }
  152. }
  153. // when vhost equals to server, and server is ip,
  154. // the vhost is __defaultVhost__
  155. if (a.hostname === vhost) {
  156. var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
  157. if (re.test(a.hostname)) {
  158. vhost = "__defaultVhost__";
  159. }
  160. }
  161. // parse the schema
  162. var schema = "rtmp";
  163. if (url.indexOf("://") > 0) {
  164. schema = url.slice(0, url.indexOf("://"));
  165. }
  166. var port = a.port;
  167. if (!port) {
  168. // Finger out by webrtc url, if contains http or https port, to overwrite default 1985.
  169. if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) {
  170. port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443;
  171. }
  172. // Guess by schema.
  173. if (schema === 'http') {
  174. port = 80;
  175. } else if (schema === 'https') {
  176. port = 443;
  177. } else if (schema === 'rtmp') {
  178. port = 1935;
  179. }
  180. }
  181. var ret = {
  182. url: url,
  183. schema: schema,
  184. server: a.hostname, port: port,
  185. vhost: vhost, app: app, stream: stream
  186. };
  187. self.__internal.fill_query(a.search, ret);
  188. // For webrtc API, we use 443 if page is https, or schema specified it.
  189. if (!ret.port) {
  190. if (schema === 'webrtc' || schema === 'rtc') {
  191. if (ret.user_query.schema === 'https') {
  192. ret.port = 443;
  193. } else if (window.location.href.indexOf('https://') === 0) {
  194. ret.port = 443;
  195. } else {
  196. // For WebRTC, SRS use 1985 as default API port.
  197. ret.port = 1985;
  198. }
  199. }
  200. }
  201. return ret;
  202. },
  203. fill_query: function (query_string, obj) {
  204. // pure user query object.
  205. obj.user_query = {};
  206. if (query_string.length === 0) {
  207. return;
  208. }
  209. // split again for angularjs.
  210. if (query_string.indexOf("?") >= 0) {
  211. query_string = query_string.split("?")[1];
  212. }
  213. var queries = query_string.split("&");
  214. for (var i = 0; i < queries.length; i++) {
  215. var elem = queries[i];
  216. var query = elem.split("=");
  217. obj[query[0]] = query[1];
  218. obj.user_query[query[0]] = query[1];
  219. }
  220. // alias domain for vhost.
  221. if (obj.domain) {
  222. obj.vhost = obj.domain;
  223. }
  224. }
  225. };
  226. self.pc = new RTCPeerConnection(null);
  227. // To keep api consistent between player and publisher.
  228. // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
  229. // @see https://webrtc.org/getting-started/media-devices
  230. self.stream = new MediaStream();
  231. return self;
  232. }
  233. // Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
  234. // Async-await-promise based SRS RTC Player.
  235. function SrsRtcPlayerAsync() {
  236. var self = {};
  237. // @see https://github.com/rtcdn/rtcdn-draft
  238. // @url The WebRTC url to play with, for example:
  239. // webrtc://r.ossrs.net/live/livestream
  240. // or specifies the API port:
  241. // webrtc://r.ossrs.net:11985/live/livestream
  242. // webrtc://r.ossrs.net:80/live/livestream
  243. // or autostart the play:
  244. // webrtc://r.ossrs.net/live/livestream?autostart=true
  245. // or change the app from live to myapp:
  246. // webrtc://r.ossrs.net:11985/myapp/livestream
  247. // or change the stream from livestream to mystream:
  248. // webrtc://r.ossrs.net:11985/live/mystream
  249. // or set the api server to myapi.domain.com:
  250. // webrtc://myapi.domain.com/live/livestream
  251. // or set the candidate(eip) of answer:
  252. // webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185
  253. // or force to access https API:
  254. // webrtc://r.ossrs.net/live/livestream?schema=https
  255. // or use plaintext, without SRTP:
  256. // webrtc://r.ossrs.net/live/livestream?encrypt=false
  257. // or any other information, will pass-by in the query:
  258. // webrtc://r.ossrs.net/live/livestream?vhost=xxx
  259. // webrtc://r.ossrs.net/live/livestream?token=xxx
  260. self.play = async function(url) {
  261. var conf = self.__internal.prepareUrl(url);
  262. self.pc.addTransceiver("audio", {direction: "recvonly"});
  263. self.pc.addTransceiver("video", {direction: "recvonly"});
  264. //self.pc.addTransceiver("video", {direction: "recvonly"});
  265. //self.pc.addTransceiver("audio", {direction: "recvonly"});
  266. var offer = await self.pc.createOffer();
  267. await self.pc.setLocalDescription(offer);
  268. var session = await new Promise(function(resolve, reject) {
  269. // @see https://github.com/rtcdn/rtcdn-draft
  270. var data = {
  271. api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,
  272. clientip: null, sdp: offer.sdp
  273. };
  274. console.log("Generated offer: ", data);
  275. const xhr = new XMLHttpRequest();
  276. xhr.onload = function() {
  277. if (xhr.readyState !== xhr.DONE) return;
  278. if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
  279. const data = JSON.parse(xhr.responseText);
  280. console.log("Got answer: ", data);
  281. return data.code ? reject(xhr) : resolve(data);
  282. }
  283. xhr.open('POST', conf.apiUrl, true);
  284. xhr.setRequestHeader('Content-type', 'application/json');
  285. xhr.send(JSON.stringify(data));
  286. });
  287. await self.pc.setRemoteDescription(
  288. new RTCSessionDescription({type: 'answer', sdp: session.sdp})
  289. );
  290. session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/';
  291. return session;
  292. };
  293. // Close the player.
  294. self.close = function() {
  295. self.pc && self.pc.close();
  296. self.pc = null;
  297. };
  298. // The callback when got remote track.
  299. // Note that the onaddstream is deprecated, @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/onaddstream
  300. self.ontrack = function (event) {
  301. // https://webrtc.org/getting-started/remote-streams
  302. self.stream.addTrack(event.track);
  303. };
  304. // Internal APIs.
  305. self.__internal = {
  306. defaultPath: '/rtc/v1/play/',
  307. prepareUrl: function (webrtcUrl) {
  308. var urlObject = self.__internal.parse(webrtcUrl);
  309. // If user specifies the schema, use it as API schema.
  310. var schema = urlObject.user_query.schema;
  311. schema = schema ? schema + ':' : window.location.protocol;
  312. var port = urlObject.port || 1985;
  313. if (schema === 'https:') {
  314. port = urlObject.port || 443;
  315. }
  316. // @see https://github.com/rtcdn/rtcdn-draft
  317. var api = urlObject.user_query.play || self.__internal.defaultPath;
  318. if (api.lastIndexOf('/') !== api.length - 1) {
  319. api += '/';
  320. }
  321. var apiUrl = schema + '//' + urlObject.server + ':' + port + api;
  322. for (var key in urlObject.user_query) {
  323. if (key !== 'api' && key !== 'play') {
  324. apiUrl += '&' + key + '=' + urlObject.user_query[key];
  325. }
  326. }
  327. // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v
  328. apiUrl = apiUrl.replace(api + '&', api + '?');
  329. var streamUrl = urlObject.url;
  330. return {
  331. apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port,
  332. tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7)
  333. };
  334. },
  335. parse: function (url) {
  336. // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
  337. var a = document.createElement("a");
  338. a.href = url.replace("rtmp://", "http://")
  339. .replace("webrtc://", "http://")
  340. .replace("rtc://", "http://");
  341. var vhost = a.hostname;
  342. var app = a.pathname.substring(1, a.pathname.lastIndexOf("/"));
  343. var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1);
  344. // parse the vhost in the params of app, that srs supports.
  345. app = app.replace("...vhost...", "?vhost=");
  346. if (app.indexOf("?") >= 0) {
  347. var params = app.slice(app.indexOf("?"));
  348. app = app.slice(0, app.indexOf("?"));
  349. if (params.indexOf("vhost=") > 0) {
  350. vhost = params.slice(params.indexOf("vhost=") + "vhost=".length);
  351. if (vhost.indexOf("&") > 0) {
  352. vhost = vhost.slice(0, vhost.indexOf("&"));
  353. }
  354. }
  355. }
  356. // when vhost equals to server, and server is ip,
  357. // the vhost is __defaultVhost__
  358. if (a.hostname === vhost) {
  359. var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
  360. if (re.test(a.hostname)) {
  361. vhost = "__defaultVhost__";
  362. }
  363. }
  364. // parse the schema
  365. var schema = "rtmp";
  366. if (url.indexOf("://") > 0) {
  367. schema = url.slice(0, url.indexOf("://"));
  368. }
  369. var port = a.port;
  370. if (!port) {
  371. // Finger out by webrtc url, if contains http or https port, to overwrite default 1985.
  372. if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) {
  373. port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443;
  374. }
  375. // Guess by schema.
  376. if (schema === 'http') {
  377. port = 80;
  378. } else if (schema === 'https') {
  379. port = 443;
  380. } else if (schema === 'rtmp') {
  381. port = 1935;
  382. }
  383. }
  384. var ret = {
  385. url: url,
  386. schema: schema,
  387. server: a.hostname, port: port,
  388. vhost: vhost, app: app, stream: stream
  389. };
  390. self.__internal.fill_query(a.search, ret);
  391. // For webrtc API, we use 443 if page is https, or schema specified it.
  392. if (!ret.port) {
  393. if (schema === 'webrtc' || schema === 'rtc') {
  394. if (ret.user_query.schema === 'https') {
  395. ret.port = 443;
  396. } else if (window.location.href.indexOf('https://') === 0) {
  397. ret.port = 443;
  398. } else {
  399. // For WebRTC, SRS use 1985 as default API port.
  400. ret.port = 1985;
  401. }
  402. }
  403. }
  404. return ret;
  405. },
  406. fill_query: function (query_string, obj) {
  407. // pure user query object.
  408. obj.user_query = {};
  409. if (query_string.length === 0) {
  410. return;
  411. }
  412. // split again for angularjs.
  413. if (query_string.indexOf("?") >= 0) {
  414. query_string = query_string.split("?")[1];
  415. }
  416. var queries = query_string.split("&");
  417. for (var i = 0; i < queries.length; i++) {
  418. var elem = queries[i];
  419. var query = elem.split("=");
  420. obj[query[0]] = query[1];
  421. obj.user_query[query[0]] = query[1];
  422. }
  423. // alias domain for vhost.
  424. if (obj.domain) {
  425. obj.vhost = obj.domain;
  426. }
  427. }
  428. };
  429. self.pc = new RTCPeerConnection(null);
  430. // Create a stream to add track to the stream, @see https://webrtc.org/getting-started/remote-streams
  431. self.stream = new MediaStream();
  432. // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack
  433. self.pc.ontrack = function(event) {
  434. if (self.ontrack) {
  435. self.ontrack(event);
  436. }
  437. };
  438. return self;
  439. }
  440. // Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
  441. // Async-awat-prmise based SRS RTC Publisher by WHIP.
  442. function SrsRtcWhipWhepAsync() {
  443. var self = {};
  444. // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
  445. self.constraints = {
  446. audio: true,
  447. video: {
  448. width: {ideal: 320, max: 576}
  449. }
  450. };
  451. // See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/
  452. // @url The WebRTC url to publish with, for example:
  453. // http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream
  454. // @options The options to control playing, supports:
  455. // camera: boolean, whether capture video from camera, default to true.
  456. // screen: boolean, whether capture video from screen, default to false.
  457. // audio: boolean, whether play audio, default to true.
  458. self.publish = async function (url, options) {
  459. if (url.indexOf('/whip/') === -1) throw new Error(`invalid WHIP url ${url}`);
  460. const hasAudio = options?.audio ?? true;
  461. const useCamera = options?.camera ?? true;
  462. const useScreen = options?.screen ?? false;
  463. if (!hasAudio && !useCamera && !useScreen) throw new Error(`The camera, screen and audio can't be false at the same time`);
  464. if (hasAudio) {
  465. self.pc.addTransceiver("audio", {direction: "sendonly"});
  466. } else {
  467. self.constraints.audio = false;
  468. }
  469. if (useCamera || useScreen) {
  470. self.pc.addTransceiver("video", {direction: "sendonly"});
  471. }
  472. if (!useCamera) {
  473. self.constraints.video = false;
  474. }
  475. if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {
  476. throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`);
  477. }
  478. if (useScreen) {
  479. const displayStream = await navigator.mediaDevices.getDisplayMedia({
  480. video: true
  481. });
  482. // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
  483. displayStream.getTracks().forEach(function (track) {
  484. self.pc.addTrack(track);
  485. // Notify about local track when stream is ok.
  486. self.ontrack && self.ontrack({track: track});
  487. });
  488. }
  489. if (useCamera || hasAudio) {
  490. const userStream = await navigator.mediaDevices.getUserMedia(self.constraints);
  491. userStream.getTracks().forEach(function (track) {
  492. self.pc.addTrack(track);
  493. // Notify about local track when stream is ok.
  494. self.ontrack && self.ontrack({track: track});
  495. });
  496. }
  497. var offer = await self.pc.createOffer();
  498. await self.pc.setLocalDescription(offer);
  499. const answer = await new Promise(function (resolve, reject) {
  500. console.log(`Generated offer: ${offer.sdp}`);
  501. const xhr = new XMLHttpRequest();
  502. xhr.onload = function() {
  503. if (xhr.readyState !== xhr.DONE) return;
  504. if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
  505. const data = xhr.responseText;
  506. console.log("Got answer: ", data);
  507. return data.code ? reject(xhr) : resolve(data);
  508. }
  509. xhr.open('POST', url, true);
  510. xhr.setRequestHeader('Content-type', 'application/sdp');
  511. xhr.send(offer.sdp);
  512. });
  513. await self.pc.setRemoteDescription(
  514. new RTCSessionDescription({type: 'answer', sdp: answer})
  515. );
  516. return self.__internal.parseId(url, offer.sdp, answer);
  517. };
  518. // See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/
  519. // @url The WebRTC url to play with, for example:
  520. // http://localhost:1985/rtc/v1/whep/?app=live&stream=livestream
  521. // @options The options to control playing, supports:
  522. // videoOnly: boolean, whether only play video, default to false.
  523. // audioOnly: boolean, whether only play audio, default to false.
  524. self.play = async function(url, options) {
  525. if (url.indexOf('/whip-play/') === -1 && url.indexOf('/whep/') === -1) throw new Error(`invalid WHEP url ${url}`);
  526. if (options?.videoOnly && options?.audioOnly) throw new Error(`The videoOnly and audioOnly in options can't be true at the same time`);
  527. if (!options?.videoOnly) self.pc.addTransceiver("audio", {direction: "recvonly"});
  528. if (!options?.audioOnly) self.pc.addTransceiver("video", {direction: "recvonly"});
  529. var offer = await self.pc.createOffer();
  530. await self.pc.setLocalDescription(offer);
  531. const answer = await new Promise(function(resolve, reject) {
  532. console.log(`Generated offer: ${offer.sdp}`);
  533. const xhr = new XMLHttpRequest();
  534. xhr.onload = function() {
  535. if (xhr.readyState !== xhr.DONE) return;
  536. if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
  537. const data = xhr.responseText;
  538. console.log("Got answer: ", data);
  539. return data.code ? reject(xhr) : resolve(data);
  540. }
  541. xhr.open('POST', url, true);
  542. xhr.setRequestHeader('Content-type', 'application/sdp');
  543. xhr.send(offer.sdp);
  544. });
  545. await self.pc.setRemoteDescription(
  546. new RTCSessionDescription({type: 'answer', sdp: answer})
  547. );
  548. return self.__internal.parseId(url, offer.sdp, answer);
  549. };
  550. // Close the publisher.
  551. self.close = function () {
  552. self.pc && self.pc.close();
  553. self.pc = null;
  554. };
  555. // The callback when got local stream.
  556. // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
  557. self.ontrack = function (event) {
  558. // Add track to stream of SDK.
  559. self.stream.addTrack(event.track);
  560. };
  561. self.pc = new RTCPeerConnection(null);
  562. // To keep api consistent between player and publisher.
  563. // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
  564. // @see https://webrtc.org/getting-started/media-devices
  565. self.stream = new MediaStream();
  566. // Internal APIs.
  567. self.__internal = {
  568. parseId: (url, offer, answer) => {
  569. let sessionid = offer.substr(offer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length);
  570. sessionid = sessionid.substr(0, sessionid.indexOf('\n') - 1) + ':';
  571. sessionid += answer.substr(answer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length);
  572. sessionid = sessionid.substr(0, sessionid.indexOf('\n'));
  573. const a = document.createElement("a");
  574. a.href = url;
  575. return {
  576. sessionid: sessionid, // Should be ice-ufrag of answer:offer.
  577. simulator: a.protocol + '//' + a.host + '/rtc/v1/nack/',
  578. };
  579. },
  580. };
  581. // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack
  582. self.pc.ontrack = function(event) {
  583. if (self.ontrack) {
  584. self.ontrack(event);
  585. }
  586. };
  587. return self;
  588. }
  589. // Format the codec of RTCRtpSender, kind(audio/video) is optional filter.
  590. // https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#getting_the_supported_codecs
  591. function SrsRtcFormatSenders(senders, kind) {
  592. var codecs = [];
  593. senders.forEach(function (sender) {
  594. var params = sender.getParameters();
  595. params && params.codecs && params.codecs.forEach(function(c) {
  596. if (kind && sender.track.kind !== kind) {
  597. return;
  598. }
  599. if (c.mimeType.indexOf('/red') > 0 || c.mimeType.indexOf('/rtx') > 0 || c.mimeType.indexOf('/fec') > 0) {
  600. return;
  601. }
  602. var s = '';
  603. s += c.mimeType.replace('audio/', '').replace('video/', '');
  604. s += ', ' + c.clockRate + 'HZ';
  605. if (sender.track.kind === "audio") {
  606. s += ', channels: ' + c.channels;
  607. }
  608. s += ', pt: ' + c.payloadType;
  609. codecs.push(s);
  610. });
  611. });
  612. return codecs.join(", ");
  613. }