srs.sdk.js 24 KB

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