srs.sdk.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  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. if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {
  52. throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`);
  53. }
  54. var stream = await navigator.mediaDevices.getUserMedia(self.constraints);
  55. // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
  56. stream.getTracks().forEach(function (track) {
  57. self.pc.addTrack(track);
  58. // Notify about local track when stream is ok.
  59. self.ontrack && self.ontrack({track: track});
  60. });
  61. var offer = await self.pc.createOffer();
  62. await self.pc.setLocalDescription(offer);
  63. var session = await new Promise(function (resolve, reject) {
  64. // @see https://github.com/rtcdn/rtcdn-draft
  65. var data = {
  66. api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,
  67. clientip: null, sdp: offer.sdp
  68. };
  69. console.log("Generated offer: ", data);
  70. $.ajax({
  71. type: "POST", url: conf.apiUrl, data: JSON.stringify(data),
  72. contentType: 'application/json', dataType: 'json'
  73. }).done(function (data) {
  74. console.log("Got answer: ", data);
  75. if (data.code) {
  76. reject(data);
  77. return;
  78. }
  79. resolve(data);
  80. }).fail(function (reason) {
  81. reject(reason);
  82. });
  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. 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. var 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).substr(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.substr(1, a.pathname.lastIndexOf("/") - 1);
  140. var stream = a.pathname.substr(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.substr(app.indexOf("?"));
  145. app = app.substr(0, app.indexOf("?"));
  146. if (params.indexOf("vhost=") > 0) {
  147. vhost = params.substr(params.indexOf("vhost=") + "vhost=".length);
  148. if (vhost.indexOf("&") > 0) {
  149. vhost = vhost.substr(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.substr(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. var offer = await self.pc.createOffer();
  265. await self.pc.setLocalDescription(offer);
  266. var session = await new Promise(function(resolve, reject) {
  267. // @see https://github.com/rtcdn/rtcdn-draft
  268. var data = {
  269. api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,
  270. clientip: null, sdp: offer.sdp
  271. };
  272. console.log("Generated offer: ", data);
  273. $.ajax({
  274. type: "POST", url: conf.apiUrl, data: JSON.stringify(data),
  275. contentType:'application/json', dataType: 'json'
  276. }).done(function(data) {
  277. console.log("Got answer: ", data);
  278. if (data.code) {
  279. reject(data); return;
  280. }
  281. resolve(data);
  282. }).fail(function(reason){
  283. reject(reason);
  284. });
  285. });
  286. await self.pc.setRemoteDescription(
  287. new RTCSessionDescription({type: 'answer', sdp: session.sdp})
  288. );
  289. session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/';
  290. return session;
  291. };
  292. // Close the player.
  293. self.close = function() {
  294. self.pc && self.pc.close();
  295. self.pc = null;
  296. };
  297. // The callback when got remote track.
  298. // Note that the onaddstream is deprecated, @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/onaddstream
  299. self.ontrack = function (event) {
  300. // https://webrtc.org/getting-started/remote-streams
  301. self.stream.addTrack(event.track);
  302. };
  303. // Internal APIs.
  304. self.__internal = {
  305. defaultPath: '/rtc/v1/play/',
  306. prepareUrl: function (webrtcUrl) {
  307. var urlObject = self.__internal.parse(webrtcUrl);
  308. // If user specifies the schema, use it as API schema.
  309. var schema = urlObject.user_query.schema;
  310. schema = schema ? schema + ':' : window.location.protocol;
  311. var port = urlObject.port || 1985;
  312. if (schema === 'https:') {
  313. port = urlObject.port || 443;
  314. }
  315. // @see https://github.com/rtcdn/rtcdn-draft
  316. var api = urlObject.user_query.play || self.__internal.defaultPath;
  317. if (api.lastIndexOf('/') !== api.length - 1) {
  318. api += '/';
  319. }
  320. apiUrl = schema + '//' + urlObject.server + ':' + port + api;
  321. for (var key in urlObject.user_query) {
  322. if (key !== 'api' && key !== 'play') {
  323. apiUrl += '&' + key + '=' + urlObject.user_query[key];
  324. }
  325. }
  326. // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v
  327. var apiUrl = apiUrl.replace(api + '&', api + '?');
  328. var streamUrl = urlObject.url;
  329. return {
  330. apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port,
  331. tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).substr(0, 7)
  332. };
  333. },
  334. parse: function (url) {
  335. // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
  336. var a = document.createElement("a");
  337. a.href = url.replace("rtmp://", "http://")
  338. .replace("webrtc://", "http://")
  339. .replace("rtc://", "http://");
  340. var vhost = a.hostname;
  341. var app = a.pathname.substr(1, a.pathname.lastIndexOf("/") - 1);
  342. var stream = a.pathname.substr(a.pathname.lastIndexOf("/") + 1);
  343. // parse the vhost in the params of app, that srs supports.
  344. app = app.replace("...vhost...", "?vhost=");
  345. if (app.indexOf("?") >= 0) {
  346. var params = app.substr(app.indexOf("?"));
  347. app = app.substr(0, app.indexOf("?"));
  348. if (params.indexOf("vhost=") > 0) {
  349. vhost = params.substr(params.indexOf("vhost=") + "vhost=".length);
  350. if (vhost.indexOf("&") > 0) {
  351. vhost = vhost.substr(0, vhost.indexOf("&"));
  352. }
  353. }
  354. }
  355. // when vhost equals to server, and server is ip,
  356. // the vhost is __defaultVhost__
  357. if (a.hostname === vhost) {
  358. var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
  359. if (re.test(a.hostname)) {
  360. vhost = "__defaultVhost__";
  361. }
  362. }
  363. // parse the schema
  364. var schema = "rtmp";
  365. if (url.indexOf("://") > 0) {
  366. schema = url.substr(0, url.indexOf("://"));
  367. }
  368. var port = a.port;
  369. if (!port) {
  370. // Finger out by webrtc url, if contains http or https port, to overwrite default 1985.
  371. if (schema === 'webrtc' && url.indexOf(`webrtc://${a.host}:`) === 0) {
  372. port = (url.indexOf(`webrtc://${a.host}:80`) === 0) ? 80 : 443;
  373. }
  374. // Guess by schema.
  375. if (schema === 'http') {
  376. port = 80;
  377. } else if (schema === 'https') {
  378. port = 443;
  379. } else if (schema === 'rtmp') {
  380. port = 1935;
  381. }
  382. }
  383. var ret = {
  384. url: url,
  385. schema: schema,
  386. server: a.hostname, port: port,
  387. vhost: vhost, app: app, stream: stream
  388. };
  389. self.__internal.fill_query(a.search, ret);
  390. // For webrtc API, we use 443 if page is https, or schema specified it.
  391. if (!ret.port) {
  392. if (schema === 'webrtc' || schema === 'rtc') {
  393. if (ret.user_query.schema === 'https') {
  394. ret.port = 443;
  395. } else if (window.location.href.indexOf('https://') === 0) {
  396. ret.port = 443;
  397. } else {
  398. // For WebRTC, SRS use 1985 as default API port.
  399. ret.port = 1985;
  400. }
  401. }
  402. }
  403. return ret;
  404. },
  405. fill_query: function (query_string, obj) {
  406. // pure user query object.
  407. obj.user_query = {};
  408. if (query_string.length === 0) {
  409. return;
  410. }
  411. // split again for angularjs.
  412. if (query_string.indexOf("?") >= 0) {
  413. query_string = query_string.split("?")[1];
  414. }
  415. var queries = query_string.split("&");
  416. for (var i = 0; i < queries.length; i++) {
  417. var elem = queries[i];
  418. var query = elem.split("=");
  419. obj[query[0]] = query[1];
  420. obj.user_query[query[0]] = query[1];
  421. }
  422. // alias domain for vhost.
  423. if (obj.domain) {
  424. obj.vhost = obj.domain;
  425. }
  426. }
  427. };
  428. self.pc = new RTCPeerConnection(null);
  429. // Create a stream to add track to the stream, @see https://webrtc.org/getting-started/remote-streams
  430. self.stream = new MediaStream();
  431. // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack
  432. self.pc.ontrack = function(event) {
  433. if (self.ontrack) {
  434. self.ontrack(event);
  435. }
  436. };
  437. return self;
  438. }
  439. // Format the codec of RTCRtpSender, kind(audio/video) is optional filter.
  440. // https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#getting_the_supported_codecs
  441. function SrsRtcFormatSenders(senders, kind) {
  442. var codecs = [];
  443. senders.forEach(function (sender) {
  444. var params = sender.getParameters();
  445. params && params.codecs && params.codecs.forEach(function(c) {
  446. if (kind && sender.track.kind !== kind) {
  447. return;
  448. }
  449. if (c.mimeType.indexOf('/red') > 0 || c.mimeType.indexOf('/rtx') > 0 || c.mimeType.indexOf('/fec') > 0) {
  450. return;
  451. }
  452. var s = '';
  453. s += c.mimeType.replace('audio/', '').replace('video/', '');
  454. s += ', ' + c.clockRate + 'HZ';
  455. if (sender.track.kind === "audio") {
  456. s += ', channels: ' + c.channels;
  457. }
  458. s += ', pt: ' + c.payloadType;
  459. codecs.push(s);
  460. });
  461. });
  462. return codecs.join(", ");
  463. }