jswebrtc.min.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. var JSWebrtc = {
  2. Player: null,
  3. VideoElement: null,
  4. CreateVideoElements: function() {
  5. var elements = document.querySelectorAll(".jswebrtc");
  6. for (var i = 0; i < elements.length; i++) {
  7. new JSWebrtc.VideoElement(elements[i])
  8. }
  9. },
  10. FillQuery: function(query_string, obj) {
  11. obj.user_query = {};
  12. if (query_string.length == 0)
  13. return;
  14. if (query_string.indexOf("?") >= 0)
  15. query_string = query_string.split("?")[1];
  16. var queries = query_string.split("&");
  17. for (var i = 0; i < queries.length; i++) {
  18. var query = queries[i].split("=");
  19. obj[query[0]] = query[1];
  20. obj.user_query[query[0]] = query[1]
  21. }
  22. if (obj.domain)
  23. obj.vhost = obj.domain
  24. },
  25. ParseUrl: function(rtmp_url) {
  26. var a = document.createElement("a");
  27. a.href = rtmp_url.replace("rtmp://", "http://").replace("webrtc://", "http://").replace("rtc://", "http://");
  28. var vhost = a.hostname;
  29. var app = a.pathname.substr(1, a.pathname.lastIndexOf("/") - 1);
  30. var stream = a.pathname.substr(a.pathname.lastIndexOf("/") + 1);
  31. app = app.replace("...vhost...", "?vhost=");
  32. if (app.indexOf("?") >= 0) {
  33. var params = app.substr(app.indexOf("?"));
  34. app = app.substr(0, app.indexOf("?"));
  35. if (params.indexOf("vhost=") > 0) {
  36. vhost = params.substr(params.indexOf("vhost=") + "vhost=".length);
  37. if (vhost.indexOf("&") > 0) {
  38. vhost = vhost.substr(0, vhost.indexOf("&"))
  39. }
  40. }
  41. }
  42. if (a.hostname == vhost) {
  43. var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
  44. if (re.test(a.hostname))
  45. vhost = "__defaultVhost__"
  46. }
  47. var schema = "rtmp";
  48. if (rtmp_url.indexOf("://") > 0)
  49. schema = rtmp_url.substr(0, rtmp_url.indexOf("://"));
  50. var port = a.port;
  51. if (!port) {
  52. if (schema === "http") {
  53. port = 80
  54. } else if (schema === "https") {
  55. port = 443
  56. } else if (schema === "rtmp") {
  57. port = 1935
  58. } else if (schema === "webrtc" || schema === "rtc") {
  59. port = 1985
  60. }
  61. }
  62. var ret = {
  63. url: rtmp_url,
  64. schema: schema,
  65. server: a.hostname,
  66. port: port,
  67. vhost: vhost,
  68. app: app,
  69. stream: stream
  70. };
  71. JSWebrtc.FillQuery(a.search, ret);
  72. return ret
  73. },
  74. HttpPost: function(url, data) {
  75. return new Promise(function(resolve, reject) {
  76. var xhr = new XMLHttpRequest;
  77. xhr.onreadystatechange = function() {
  78. if (xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)) {
  79. var respone = JSON.parse(xhr.responseText);
  80. xhr.onreadystatechange = new Function;
  81. xhr = null;
  82. resolve(respone)
  83. }
  84. }
  85. ;
  86. xhr.open("POST", url, true);
  87. xhr.timeout = 5e3;
  88. xhr.responseType = "text";
  89. xhr.setRequestHeader("Content-Type", "application/json");
  90. xhr.send(data)
  91. }
  92. )
  93. }
  94. };
  95. if (document.readyState === "complete") {
  96. JSWebrtc.CreateVideoElements()
  97. } else {
  98. document.addEventListener("DOMContentLoaded", JSWebrtc.CreateVideoElements)
  99. }
  100. JSWebrtc.VideoElement = function() {
  101. "use strict";
  102. var VideoElement = function(element) {
  103. var url = element.dataset.url;
  104. if (!url) {
  105. throw "VideoElement has no `data-url` attribute"
  106. }
  107. var addStyles = function(element, styles) {
  108. for (var name in styles) {
  109. element.style[name] = styles[name]
  110. }
  111. };
  112. this.container = element;
  113. addStyles(this.container, {
  114. display: "inline-block",
  115. position: "relative",
  116. minWidth: "80px",
  117. minHeight: "80px"
  118. });
  119. this.video = document.createElement("video");
  120. this.video.width = 960;
  121. this.video.height = 540;
  122. addStyles(this.video, {
  123. display: "block",
  124. width: "100%"
  125. });
  126. this.container.appendChild(this.video);
  127. this.playButton = document.createElement("div");
  128. this.playButton.innerHTML = VideoElement.PLAY_BUTTON;
  129. addStyles(this.playButton, {
  130. zIndex: 2,
  131. position: "absolute",
  132. top: "0",
  133. bottom: "0",
  134. left: "0",
  135. right: "0",
  136. maxWidth: "75px",
  137. maxHeight: "75px",
  138. margin: "auto",
  139. opacity: "0.7",
  140. cursor: "pointer"
  141. });
  142. this.container.appendChild(this.playButton);
  143. var options = {
  144. video: this.video
  145. };
  146. for (var option in element.dataset) {
  147. try {
  148. options[option] = JSON.parse(element.dataset[option])
  149. } catch (err) {
  150. options[option] = element.dataset[option]
  151. }
  152. }
  153. this.player = new JSWebrtc.Player(url,options);
  154. element.playerInstance = this.player;
  155. if (options.poster && !options.autoplay) {
  156. options.decodeFirstFrame = false;
  157. this.poster = new Image;
  158. this.poster.src = options.poster;
  159. this.poster.addEventListener("load", this.posterLoaded);
  160. addStyles(this.poster, {
  161. display: "block",
  162. zIndex: 1,
  163. position: "absolute",
  164. top: 0,
  165. left: 0,
  166. bottom: 0,
  167. right: 0
  168. });
  169. this.container.appendChild(this.poster)
  170. }
  171. if (!this.player.options.streaming) {
  172. this.container.addEventListener("click", this.onClick.bind(this))
  173. }
  174. if (options.autoplay) {
  175. this.playButton.style.display = "none"
  176. }
  177. if (this.player.audioOut && !this.player.audioOut.unlocked) {
  178. var unlockAudioElement = this.container;
  179. if (options.autoplay) {
  180. this.unmuteButton = document.createElement("div");
  181. this.unmuteButton.innerHTML = VideoElement.UNMUTE_BUTTON;
  182. addStyles(this.unmuteButton, {
  183. zIndex: 2,
  184. position: "absolute",
  185. bottom: "10px",
  186. right: "20px",
  187. width: "75px",
  188. height: "75px",
  189. margin: "auto",
  190. opacity: "0.7",
  191. cursor: "pointer"
  192. });
  193. this.container.appendChild(this.unmuteButton);
  194. unlockAudioElement = this.unmuteButton
  195. }
  196. this.unlockAudioBound = this.onUnlockAudio.bind(this, unlockAudioElement);
  197. unlockAudioElement.addEventListener("touchstart", this.unlockAudioBound, false);
  198. unlockAudioElement.addEventListener("click", this.unlockAudioBound, true)
  199. }
  200. };
  201. VideoElement.prototype.onUnlockAudio = function(element, ev) {
  202. if (this.unmuteButton) {
  203. ev.preventDefault();
  204. ev.stopPropagation()
  205. }
  206. this.player.audioOut.unlock(function() {
  207. if (this.unmuteButton) {
  208. this.unmuteButton.style.display = "none"
  209. }
  210. element.removeEventListener("touchstart", this.unlockAudioBound);
  211. element.removeEventListener("click", this.unlockAudioBound)
  212. }
  213. .bind(this))
  214. }
  215. ;
  216. VideoElement.prototype.onClick = function(ev) {
  217. if (this.player.isPlaying) {
  218. this.player.pause();
  219. this.playButton.style.display = "block"
  220. } else {
  221. this.player.play();
  222. this.playButton.style.display = "none";
  223. if (this.poster) {
  224. this.poster.style.display = "none"
  225. }
  226. }
  227. }
  228. ;
  229. VideoElement.PLAY_BUTTON = '<svg style="max-width: 75px; max-height: 75px;" ' + 'viewBox="0 0 200 200" alt="Play video">' + '<circle cx="100" cy="100" r="90" fill="none" ' + 'stroke-width="15" stroke="#fff"/>' + '<polygon points="70, 55 70, 145 145, 100" fill="#fff"/>' + "</svg>";
  230. VideoElement.UNMUTE_BUTTON = '<svg style="max-width: 75px; max-height: 75px;" viewBox="0 0 75 75">' + '<polygon class="audio-speaker" stroke="none" fill="#fff" ' + 'points="39,13 22,28 6,28 6,47 21,47 39,62 39,13"/>' + '<g stroke="#fff" stroke-width="5">' + '<path d="M 49,50 69,26"/>' + '<path d="M 69,50 49,26"/>' + "</g>" + "</svg>";
  231. return VideoElement
  232. }();
  233. JSWebrtc.Player = function() {
  234. "use strict";
  235. var Player = function(url, options) {
  236. this.options = options || {};
  237. if (!url.match(/^webrtc?:\/\//)) {
  238. throw "JSWebrtc just work with webrtc"
  239. }
  240. if (!this.options.video) {
  241. throw "VideoElement is null"
  242. }
  243. this.urlParams = JSWebrtc.ParseUrl(url);
  244. this.pc = null;
  245. this.autoplay = !!options.autoplay || false;
  246. this.paused = true;
  247. if (this.autoplay)
  248. this.options.video.muted = true;
  249. this.startLoading()
  250. };
  251. Player.prototype.startLoading = function() {
  252. var _self = this;
  253. if (_self.pc) {
  254. _self.pc.close()
  255. }
  256. _self.pc = new RTCPeerConnection(null);
  257. _self.pc.ontrack = function(event) {
  258. _self.options.video["srcObject"] = event.streams[0]
  259. }
  260. ;
  261. _self.pc.addTransceiver("audio", {
  262. direction: "recvonly"
  263. });
  264. _self.pc.addTransceiver("video", {
  265. direction: "recvonly"
  266. });
  267. _self.pc.createOffer().then(function(offer) {
  268. return _self.pc.setLocalDescription(offer).then(function() {
  269. return offer
  270. })
  271. }).then(function(offer) {
  272. return new Promise(function(resolve, reject) {
  273. var port = _self.urlParams.port || 1985;
  274. var api = _self.urlParams.user_query.play || "/rtc/v1/play/";
  275. if (api.lastIndexOf("/") != api.length - 1) {
  276. api += "/"
  277. }
  278. var url = "http://" + _self.urlParams.server + ":" + port + api;
  279. for (var key in _self.urlParams.user_query) {
  280. if (key != "api" && key != "play") {
  281. url += "&" + key + "=" + _self.urlParams.user_query[key]
  282. }
  283. }
  284. var data = {
  285. api: url,
  286. streamurl: _self.urlParams.url,
  287. clientip: null,
  288. sdp: offer.sdp
  289. };
  290. console.log("offer: " + JSON.stringify(data));
  291. JSWebrtc.HttpPost(url, JSON.stringify(data)).then(function(res) {
  292. console.log("answer: " + JSON.stringify(res));
  293. resolve(res.sdp)
  294. }, function(rej) {
  295. reject(rej)
  296. })
  297. }
  298. )
  299. }).then(function(answer) {
  300. return _self.pc.setRemoteDescription(new RTCSessionDescription({
  301. type: "answer",
  302. sdp: answer
  303. }))
  304. }).catch(function(reason) {
  305. throw reason
  306. });
  307. if (this.autoplay) {
  308. this.play()
  309. }
  310. }
  311. ;
  312. Player.prototype.play = function(ev) {
  313. if (this.animationId) {
  314. return
  315. }
  316. this.animationId = requestAnimationFrame(this.update.bind(this));
  317. this.paused = false
  318. }
  319. ;
  320. Player.prototype.pause = function(ev) {
  321. if (this.paused) {
  322. return
  323. }
  324. cancelAnimationFrame(this.animationId);
  325. this.animationId = null;
  326. this.isPlaying = false;
  327. this.paused = true;
  328. this.options.video.pause();
  329. if (this.options.onPause) {
  330. this.options.onPause(this)
  331. }
  332. }
  333. ;
  334. Player.prototype.stop = function(ev) {
  335. this.pause()
  336. }
  337. ;
  338. Player.prototype.destroy = function() {
  339. this.pause();
  340. this.pc && this.pc.close() && this.pc.destroy();
  341. this.audioOut && this.audioOut.destroy()
  342. }
  343. ;
  344. Player.prototype.update = function() {
  345. this.animationId = requestAnimationFrame(this.update.bind(this));
  346. if (this.options.video.readyState < 4) {
  347. return
  348. }
  349. if (!this.isPlaying) {
  350. this.isPlaying = true;
  351. this.options.video.play();
  352. if (this.options.onPlay) {
  353. this.options.onPlay(this)
  354. }
  355. }
  356. }
  357. ;
  358. return Player
  359. }();