srs.sdk.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  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. self.publish = async function (url) {
  455. if (url.indexOf('/whip/') === -1) throw new Error(`invalid WHIP url ${url}`);
  456. self.pc.addTransceiver("audio", {direction: "sendonly"});
  457. self.pc.addTransceiver("video", {direction: "sendonly"});
  458. if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {
  459. throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`);
  460. }
  461. var stream = await navigator.mediaDevices.getUserMedia(self.constraints);
  462. // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
  463. stream.getTracks().forEach(function (track) {
  464. self.pc.addTrack(track);
  465. // Notify about local track when stream is ok.
  466. self.ontrack && self.ontrack({track: track});
  467. });
  468. var offer = await self.pc.createOffer();
  469. await self.pc.setLocalDescription(offer);
  470. const answer = await new Promise(function (resolve, reject) {
  471. console.log("Generated offer: ", offer);
  472. const xhr = new XMLHttpRequest();
  473. xhr.onload = function() {
  474. if (xhr.readyState !== xhr.DONE) return;
  475. if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
  476. const data = xhr.responseText;
  477. console.log("Got answer: ", data);
  478. return data.code ? reject(xhr) : resolve(data);
  479. }
  480. xhr.open('POST', url, true);
  481. xhr.setRequestHeader('Content-type', 'application/sdp');
  482. xhr.send(offer.sdp);
  483. });
  484. await self.pc.setRemoteDescription(
  485. new RTCSessionDescription({type: 'answer', sdp: answer})
  486. );
  487. return self.__internal.parseId(url, offer.sdp, answer);
  488. };
  489. // See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/
  490. // @url The WebRTC url to play with, for example:
  491. // http://localhost:1985/rtc/v1/whep/?app=live&stream=livestream
  492. self.play = async function(url) {
  493. if (url.indexOf('/whip-play/') === -1 && url.indexOf('/whep/') === -1) throw new Error(`invalid WHEP url ${url}`);
  494. self.pc.addTransceiver("audio", {direction: "recvonly"});
  495. self.pc.addTransceiver("video", {direction: "recvonly"});
  496. var offer = await self.pc.createOffer();
  497. await self.pc.setLocalDescription(offer);
  498. const answer = await new Promise(function(resolve, reject) {
  499. console.log("Generated offer: ", offer);
  500. const xhr = new XMLHttpRequest();
  501. xhr.onload = function() {
  502. if (xhr.readyState !== xhr.DONE) return;
  503. if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr);
  504. const data = xhr.responseText;
  505. console.log("Got answer: ", data);
  506. return data.code ? reject(xhr) : resolve(data);
  507. }
  508. xhr.open('POST', url, true);
  509. xhr.setRequestHeader('Content-type', 'application/sdp');
  510. xhr.send(offer.sdp);
  511. });
  512. await self.pc.setRemoteDescription(
  513. new RTCSessionDescription({type: 'answer', sdp: answer})
  514. );
  515. return self.__internal.parseId(url, offer.sdp, answer);
  516. };
  517. // Close the publisher.
  518. self.close = function () {
  519. self.pc && self.pc.close();
  520. self.pc = null;
  521. };
  522. // The callback when got local stream.
  523. // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
  524. self.ontrack = function (event) {
  525. // Add track to stream of SDK.
  526. self.stream.addTrack(event.track);
  527. };
  528. self.pc = new RTCPeerConnection(null);
  529. // To keep api consistent between player and publisher.
  530. // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
  531. // @see https://webrtc.org/getting-started/media-devices
  532. self.stream = new MediaStream();
  533. // Internal APIs.
  534. self.__internal = {
  535. parseId: (url, offer, answer) => {
  536. let sessionid = offer.substr(offer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length);
  537. sessionid = sessionid.substr(0, sessionid.indexOf('\n') - 1) + ':';
  538. sessionid += answer.substr(answer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length);
  539. sessionid = sessionid.substr(0, sessionid.indexOf('\n'));
  540. const a = document.createElement("a");
  541. a.href = url;
  542. return {
  543. sessionid: sessionid, // Should be ice-ufrag of answer:offer.
  544. simulator: a.protocol + '//' + a.host + '/rtc/v1/nack/',
  545. };
  546. },
  547. };
  548. // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack
  549. self.pc.ontrack = function(event) {
  550. if (self.ontrack) {
  551. self.ontrack(event);
  552. }
  553. };
  554. return self;
  555. }
  556. // Format the codec of RTCRtpSender, kind(audio/video) is optional filter.
  557. // https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#getting_the_supported_codecs
  558. function SrsRtcFormatSenders(senders, kind) {
  559. var codecs = [];
  560. senders.forEach(function (sender) {
  561. var params = sender.getParameters();
  562. params && params.codecs && params.codecs.forEach(function(c) {
  563. if (kind && sender.track.kind !== kind) {
  564. return;
  565. }
  566. if (c.mimeType.indexOf('/red') > 0 || c.mimeType.indexOf('/rtx') > 0 || c.mimeType.indexOf('/fec') > 0) {
  567. return;
  568. }
  569. var s = '';
  570. s += c.mimeType.replace('audio/', '').replace('video/', '');
  571. s += ', ' + c.clockRate + 'HZ';
  572. if (sender.track.kind === "audio") {
  573. s += ', channels: ' + c.channels;
  574. }
  575. s += ', pt: ' + c.payloadType;
  576. codecs.push(s);
  577. });
  578. });
  579. return codecs.join(", ");
  580. }