srs.sdk.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. /**
  2. * The MIT License (MIT)
  3. *
  4. * Copyright (c) 2013-2021 Winlin
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy of
  7. * this software and associated documentation files (the "Software"), to deal in
  8. * the Software without restriction, including without limitation the rights to
  9. * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  10. * the Software, and to permit persons to whom the Software is furnished to do so,
  11. * subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in all
  14. * copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  18. * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  19. * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  20. * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  21. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. */
  23. 'use strict';
  24. // Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
  25. // Async-awat-prmise based SRS RTC Publisher.
  26. function SrsRtcPublisherAsync() {
  27. var self = {};
  28. // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
  29. self.constraints = {
  30. audio: true,
  31. video: {
  32. width: {ideal: 320, max: 576}
  33. }
  34. };
  35. // @see https://github.com/rtcdn/rtcdn-draft
  36. // @url The WebRTC url to play with, for example:
  37. // webrtc://r.ossrs.net/live/livestream
  38. // or specifies the API port:
  39. // webrtc://r.ossrs.net:11985/live/livestream
  40. // or autostart the publish:
  41. // webrtc://r.ossrs.net/live/livestream?autostart=true
  42. // or change the app from live to myapp:
  43. // webrtc://r.ossrs.net:11985/myapp/livestream
  44. // or change the stream from livestream to mystream:
  45. // webrtc://r.ossrs.net:11985/live/mystream
  46. // or set the api server to myapi.domain.com:
  47. // webrtc://myapi.domain.com/live/livestream
  48. // or set the candidate(ip) of answer:
  49. // webrtc://r.ossrs.net/live/livestream?eip=39.107.238.185
  50. // or force to access https API:
  51. // webrtc://r.ossrs.net/live/livestream?schema=https
  52. // or use plaintext, without SRTP:
  53. // webrtc://r.ossrs.net/live/livestream?encrypt=false
  54. // or any other information, will pass-by in the query:
  55. // webrtc://r.ossrs.net/live/livestream?vhost=xxx
  56. // webrtc://r.ossrs.net/live/livestream?token=xxx
  57. self.publish = async function (url) {
  58. var conf = self.__internal.prepareUrl(url);
  59. self.pc.addTransceiver("audio", {direction: "sendonly"});
  60. self.pc.addTransceiver("video", {direction: "sendonly"});
  61. var stream = await navigator.mediaDevices.getUserMedia(self.constraints);
  62. // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
  63. stream.getTracks().forEach(function (track) {
  64. self.pc.addTrack(track);
  65. // Notify about local track when stream is ok.
  66. self.ontrack && self.ontrack({track: track});
  67. });
  68. var offer = await self.pc.createOffer();
  69. await self.pc.setLocalDescription(offer);
  70. var session = await new Promise(function (resolve, reject) {
  71. // @see https://github.com/rtcdn/rtcdn-draft
  72. var data = {
  73. api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,
  74. clientip: null, sdp: offer.sdp
  75. };
  76. console.log("Generated offer: ", data);
  77. $.ajax({
  78. type: "POST", url: conf.apiUrl, data: JSON.stringify(data),
  79. contentType: 'application/json', dataType: 'json'
  80. }).done(function (data) {
  81. console.log("Got answer: ", data);
  82. if (data.code) {
  83. reject(data);
  84. return;
  85. }
  86. resolve(data);
  87. }).fail(function (reason) {
  88. reject(reason);
  89. });
  90. });
  91. await self.pc.setRemoteDescription(
  92. new RTCSessionDescription({type: 'answer', sdp: session.sdp})
  93. );
  94. session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/';
  95. return session;
  96. };
  97. // Close the publisher.
  98. self.close = function () {
  99. self.pc && self.pc.close();
  100. self.pc = null;
  101. };
  102. // The callback when got local stream.
  103. // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
  104. self.ontrack = function (event) {
  105. // Add track to stream of SDK.
  106. self.stream.addTrack(event.track);
  107. };
  108. // Internal APIs.
  109. self.__internal = {
  110. defaultPath: '/rtc/v1/publish/',
  111. prepareUrl: function (webrtcUrl) {
  112. var urlObject = self.__internal.parse(webrtcUrl);
  113. // If user specifies the schema, use it as API schema.
  114. var schema = urlObject.user_query.schema;
  115. schema = schema ? schema + ':' : window.location.protocol;
  116. var port = urlObject.port || 1985;
  117. if (schema === 'https:') {
  118. port = urlObject.port || 443;
  119. }
  120. // @see https://github.com/rtcdn/rtcdn-draft
  121. var api = urlObject.user_query.play || self.__internal.defaultPath;
  122. if (api.lastIndexOf('/') !== api.length - 1) {
  123. api += '/';
  124. }
  125. apiUrl = schema + '//' + urlObject.server + ':' + port + api;
  126. for (var key in urlObject.user_query) {
  127. if (key !== 'api' && key !== 'play') {
  128. apiUrl += '&' + key + '=' + urlObject.user_query[key];
  129. }
  130. }
  131. // Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v
  132. var apiUrl = apiUrl.replace(api + '&', api + '?');
  133. var streamUrl = urlObject.url;
  134. return {
  135. apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port,
  136. tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7)
  137. };
  138. },
  139. parse: function (url) {
  140. // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
  141. var a = document.createElement("a");
  142. a.href = url.replace("rtmp://", "http://")
  143. .replace("webrtc://", "http://")
  144. .replace("rtc://", "http://");
  145. var vhost = a.hostname;
  146. var app = a.pathname.substring(1, a.pathname.lastIndexOf("/"));
  147. var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1);
  148. // parse the vhost in the params of app, that srs supports.
  149. app = app.replace("...vhost...", "?vhost=");
  150. if (app.indexOf("?") >= 0) {
  151. var params = app.slice(app.indexOf("?"));
  152. app = app.slice(0, app.indexOf("?"));
  153. if (params.indexOf("vhost=") > 0) {
  154. vhost = params.slice(params.indexOf("vhost=") + "vhost=".length);
  155. if (vhost.indexOf("&") > 0) {
  156. vhost = vhost.slice(0, vhost.indexOf("&"));
  157. }
  158. }
  159. }
  160. // when vhost equals to server, and server is ip,
  161. // the vhost is __defaultVhost__
  162. if (a.hostname === vhost) {
  163. var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
  164. if (re.test(a.hostname)) {
  165. vhost = "__defaultVhost__";
  166. }
  167. }
  168. // parse the schema
  169. var schema = "rtmp";
  170. if (url.indexOf("://") > 0) {
  171. schema = url.slice(0, url.indexOf("://"));
  172. }
  173. var port = a.port;
  174. if (!port) {
  175. if (schema === 'http') {
  176. port = 80;
  177. } else if (schema === 'https') {
  178. port = 443;
  179. } else if (schema === 'rtmp') {
  180. port = 1935;
  181. }
  182. }
  183. var ret = {
  184. url: url,
  185. schema: schema,
  186. server: a.hostname, port: port,
  187. vhost: vhost, app: app, stream: stream
  188. };
  189. self.__internal.fill_query(a.search, ret);
  190. // For webrtc API, we use 443 if page is https, or schema specified it.
  191. if (!ret.port) {
  192. if (schema === 'webrtc' || schema === 'rtc') {
  193. if (ret.user_query.schema === 'https') {
  194. ret.port = 443;
  195. } else if (window.location.href.indexOf('https://') === 0) {
  196. ret.port = 443;
  197. } else {
  198. // For WebRTC, SRS use 1985 as default API port.
  199. ret.port = 1985;
  200. }
  201. }
  202. }
  203. return ret;
  204. },
  205. fill_query: function (query_string, obj) {
  206. // pure user query object.
  207. obj.user_query = {};
  208. if (query_string.length === 0) {
  209. return;
  210. }
  211. // split again for angularjs.
  212. if (query_string.indexOf("?") >= 0) {
  213. query_string = query_string.split("?")[1];
  214. }
  215. var queries = query_string.split("&");
  216. for (var i = 0; i < queries.length; i++) {
  217. var elem = queries[i];
  218. var query = elem.split("=");
  219. obj[query[0]] = query[1];
  220. obj.user_query[query[0]] = query[1];
  221. }
  222. // alias domain for vhost.
  223. if (obj.domain) {
  224. obj.vhost = obj.domain;
  225. }
  226. }
  227. };
  228. self.pc = new RTCPeerConnection(null);
  229. // To keep api consistent between player and publisher.
  230. // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
  231. // @see https://webrtc.org/getting-started/media-devices
  232. self.stream = new MediaStream();
  233. return self;
  234. }
  235. // Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
  236. // Async-await-promise based SRS RTC Player.
  237. function SrsRtcPlayerAsync() {
  238. var self = {};
  239. // @see https://github.com/rtcdn/rtcdn-draft
  240. // @url The WebRTC url to play with, for example:
  241. // webrtc://r.ossrs.net/live/livestream
  242. // or specifies the API port:
  243. // webrtc://r.ossrs.net:11985/live/livestream
  244. // or autostart the play:
  245. // webrtc://r.ossrs.net/live/livestream?autostart=true
  246. // or change the app from live to myapp:
  247. // webrtc://r.ossrs.net:11985/myapp/livestream
  248. // or change the stream from livestream to mystream:
  249. // webrtc://r.ossrs.net:11985/live/mystream
  250. // or set the api server to myapi.domain.com:
  251. // webrtc://myapi.domain.com/live/livestream
  252. // or set the candidate(ip) of answer:
  253. // webrtc://r.ossrs.net/live/livestream?eip=39.107.238.185
  254. // or force to access https API:
  255. // webrtc://r.ossrs.net/live/livestream?schema=https
  256. // or use plaintext, without SRTP:
  257. // webrtc://r.ossrs.net/live/livestream?encrypt=false
  258. // or any other information, will pass-by in the query:
  259. // webrtc://r.ossrs.net/live/livestream?vhost=xxx
  260. // webrtc://r.ossrs.net/live/livestream?token=xxx
  261. self.play = async function(url) {
  262. var conf = self.__internal.prepareUrl(url);
  263. self.pc.addTransceiver("audio", {direction: "recvonly"});
  264. self.pc.addTransceiver("video", {direction: "recvonly"});
  265. var offer = await self.pc.createOffer();
  266. await self.pc.setLocalDescription(offer);
  267. var session = await new Promise(function(resolve, reject) {
  268. // @see https://github.com/rtcdn/rtcdn-draft
  269. var data = {
  270. api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl,
  271. clientip: null, sdp: offer.sdp
  272. };
  273. console.log("Generated offer: ", data);
  274. $.ajax({
  275. type: "POST", url: conf.apiUrl, data: JSON.stringify(data),
  276. contentType:'application/json', dataType: 'json'
  277. }).done(function(data) {
  278. console.log("Got answer: ", data);
  279. if (data.code) {
  280. reject(data); return;
  281. }
  282. resolve(data);
  283. }).fail(function(reason){
  284. reject(reason);
  285. });
  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. 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. var 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. if (schema === 'http') {
  372. port = 80;
  373. } else if (schema === 'https') {
  374. port = 443;
  375. } else if (schema === 'rtmp') {
  376. port = 1935;
  377. }
  378. }
  379. var ret = {
  380. url: url,
  381. schema: schema,
  382. server: a.hostname, port: port,
  383. vhost: vhost, app: app, stream: stream
  384. };
  385. self.__internal.fill_query(a.search, ret);
  386. // For webrtc API, we use 443 if page is https, or schema specified it.
  387. if (!ret.port) {
  388. if (schema === 'webrtc' || schema === 'rtc') {
  389. if (ret.user_query.schema === 'https') {
  390. ret.port = 443;
  391. } else if (window.location.href.indexOf('https://') === 0) {
  392. ret.port = 443;
  393. } else {
  394. // For WebRTC, SRS use 1985 as default API port.
  395. ret.port = 1985;
  396. }
  397. }
  398. }
  399. return ret;
  400. },
  401. fill_query: function (query_string, obj) {
  402. // pure user query object.
  403. obj.user_query = {};
  404. if (query_string.length === 0) {
  405. return;
  406. }
  407. // split again for angularjs.
  408. if (query_string.indexOf("?") >= 0) {
  409. query_string = query_string.split("?")[1];
  410. }
  411. var queries = query_string.split("&");
  412. for (var i = 0; i < queries.length; i++) {
  413. var elem = queries[i];
  414. var query = elem.split("=");
  415. obj[query[0]] = query[1];
  416. obj.user_query[query[0]] = query[1];
  417. }
  418. // alias domain for vhost.
  419. if (obj.domain) {
  420. obj.vhost = obj.domain;
  421. }
  422. }
  423. };
  424. self.pc = new RTCPeerConnection(null);
  425. // Create a stream to add track to the stream, @see https://webrtc.org/getting-started/remote-streams
  426. self.stream = new MediaStream();
  427. // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack
  428. self.pc.ontrack = function(event) {
  429. if (self.ontrack) {
  430. self.ontrack(event);
  431. }
  432. };
  433. return self;
  434. }
  435. // Format the codec of RTCRtpSender, kind(audio/video) is optional filter.
  436. // https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#getting_the_supported_codecs
  437. function SrsRtcFormatSenders(senders, kind) {
  438. var codecs = [];
  439. senders.forEach(function (sender) {
  440. var params = sender.getParameters();
  441. params && params.codecs && params.codecs.forEach(function(c) {
  442. if (kind && sender.track.kind !== kind) {
  443. return;
  444. }
  445. if (c.mimeType.indexOf('/red') > 0 || c.mimeType.indexOf('/rtx') > 0 || c.mimeType.indexOf('/fec') > 0) {
  446. return;
  447. }
  448. var s = '';
  449. s += c.mimeType.replace('audio/', '').replace('video/', '');
  450. s += ', ' + c.clockRate + 'HZ';
  451. if (sender.track.kind === "audio") {
  452. s += ', channels: ' + c.channels;
  453. }
  454. s += ', pt: ' + c.payloadType;
  455. codecs.push(s);
  456. });
  457. });
  458. return codecs.join(", ");
  459. }