You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

390 lines
9.5 KiB

  1. (function(OCA, OC) {
  2. 'use strict';
  3. OCA.SpreedMe = OCA.SpreedMe || {};
  4. function SignalingBase() {
  5. this.sessionId = '';
  6. this.currentCallToken = null;
  7. this.handlers = {};
  8. }
  9. SignalingBase.prototype.on = function(ev, handler) {
  10. if (!this.handlers.hasOwnProperty(ev)) {
  11. this.handlers[ev] = [handler];
  12. } else {
  13. this.handlers[ev].push(handler);
  14. }
  15. };
  16. SignalingBase.prototype.emit = function(/*ev, data*/) {
  17. // Override in subclasses.
  18. };
  19. SignalingBase.prototype._trigger = function(ev, args) {
  20. var handlers = this.handlers[ev];
  21. if (!handlers) {
  22. return;
  23. }
  24. handlers = handlers.slice(0);
  25. for (var i = 0, len = handlers.length; i < len; i++) {
  26. var handler = handlers[i];
  27. handler.apply(handler, args);
  28. }
  29. };
  30. SignalingBase.prototype.getSessionid = function() {
  31. return this.sessionId;
  32. };
  33. SignalingBase.prototype.disconnect = function() {
  34. this.sessionId = '';
  35. this.currentCallToken = null;
  36. };
  37. SignalingBase.prototype.emit = function(ev, data) {
  38. switch (ev) {
  39. case 'join':
  40. var callback = arguments[2];
  41. var token = data;
  42. this.joinCall(token, callback);
  43. break;
  44. case 'leave':
  45. this.leaveCurrentCall();
  46. break;
  47. case 'message':
  48. this.sendCallMessage(data);
  49. break;
  50. }
  51. };
  52. SignalingBase.prototype.leaveCurrentCall = function() {
  53. if (this.currentCallToken) {
  54. this.leaveCall(this.currentCallToken);
  55. this.currentCallToken = null;
  56. }
  57. };
  58. SignalingBase.prototype.leaveAllCalls = function() {
  59. // Override if necessary.
  60. };
  61. SignalingBase.prototype.setRoomCollection = function(rooms) {
  62. this.roomCollection = rooms;
  63. return this.syncRooms();
  64. };
  65. SignalingBase.prototype.syncRooms = function() {
  66. var defer = $.Deferred();
  67. if (this.roomCollection && oc_current_user) {
  68. this.roomCollection.fetch({
  69. success: function(data) {
  70. defer.resolve(data);
  71. }
  72. });
  73. } else {
  74. defer.resolve([]);
  75. }
  76. return defer;
  77. };
  78. // Connection to the internal signaling server provided by the app.
  79. function InternalSignaling() {
  80. SignalingBase.prototype.constructor.apply(this, arguments);
  81. this.spreedArrayConnection = [];
  82. this.pingFails = 0;
  83. this.pingInterval = null;
  84. this.sendInterval = window.setInterval(function(){
  85. this.sendPendingMessages();
  86. }.bind(this), 500);
  87. }
  88. InternalSignaling.prototype = new SignalingBase();
  89. InternalSignaling.prototype.constructor = InternalSignaling;
  90. InternalSignaling.prototype.disconnect = function() {
  91. this.spreedArrayConnection = [];
  92. if (this.source) {
  93. this.source.close();
  94. this.source = null;
  95. }
  96. if (this.sendInterval) {
  97. window.clearInterval(this.sendInterval);
  98. this.sendInterval = null;
  99. }
  100. if (this.pingInterval) {
  101. window.clearInterval(this.pingInterval);
  102. this.pingInterval = null;
  103. }
  104. if (this.roomPoller) {
  105. window.clearInterval(this.roomPoller);
  106. this.roomPoller = null;
  107. }
  108. SignalingBase.prototype.disconnect.apply(this, arguments);
  109. };
  110. InternalSignaling.prototype.on = function(ev/*, handler*/) {
  111. SignalingBase.prototype.on.apply(this, arguments);
  112. switch (ev) {
  113. case 'connect':
  114. // A connection is established if we can perform a request
  115. // through it.
  116. this._sendMessageWithCallback(ev);
  117. break;
  118. case 'stunservers':
  119. case 'turnservers':
  120. // Values are not pushed by the server but have to be explicitly
  121. // requested.
  122. this._sendMessageWithCallback(ev);
  123. break;
  124. }
  125. };
  126. InternalSignaling.prototype._sendMessageWithCallback = function(ev) {
  127. var message = [{
  128. ev: ev
  129. }];
  130. $.post(OC.generateUrl('/apps/spreed/signalling'), {
  131. messages: JSON.stringify(message)
  132. }, function(data) {
  133. this._trigger(ev, [data]);
  134. }.bind(this));
  135. };
  136. InternalSignaling.prototype.joinCall = function(token, callback, password) {
  137. // The client is joining a new call, in this case we need
  138. // to do the following:
  139. //
  140. // 1. Join the call as participant.
  141. // 2. Get a list of other connected clients in the call.
  142. // 3. Pass information about the clients that need to be called by you to the callback.
  143. //
  144. // The clients will then use the message command to exchange
  145. // their signalling information.
  146. $.ajax({
  147. url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
  148. type: 'POST',
  149. beforeSend: function (request) {
  150. request.setRequestHeader('Accept', 'application/json');
  151. },
  152. data: {
  153. password: password
  154. },
  155. success: function (result) {
  156. console.log("Joined", result);
  157. this.sessionId = result.ocs.data.sessionId;
  158. this.currentCallToken = token;
  159. this._startPingCall();
  160. this._openEventSource();
  161. this._getCallPeers(token).then(function(peers) {
  162. var callDescription = {
  163. 'clients': {}
  164. };
  165. peers.forEach(function(element) {
  166. if (element['sessionId'] < this.sessionId) {
  167. callDescription['clients'][element['sessionId']] = {
  168. 'video': true
  169. };
  170. }
  171. }.bind(this));
  172. callback('', callDescription);
  173. }.bind(this));
  174. }.bind(this),
  175. error: function (result) {
  176. if (result.status === 404 || result.status === 503) {
  177. // Room not found or maintenance mode
  178. OC.redirect(OC.generateUrl('apps/spreed'));
  179. }
  180. if (result.status === 403) {
  181. // Invalid password
  182. OC.dialogs.prompt(
  183. t('spreed', 'Please enter the password for this call'),
  184. t('spreed','Password required'),
  185. function (result, password) {
  186. if (result && password !== '') {
  187. this.joinCall(token, callback, password);
  188. }
  189. }.bind(this),
  190. true,
  191. t('spreed','Password'),
  192. true
  193. ).then(function() {
  194. var $dialog = $('.oc-dialog:visible');
  195. $dialog.find('.ui-icon').remove();
  196. var $buttons = $dialog.find('button');
  197. $buttons.eq(0).text(t('core', 'Cancel'));
  198. $buttons.eq(1).text(t('core', 'Submit'));
  199. });
  200. }
  201. }.bind(this)
  202. });
  203. };
  204. InternalSignaling.prototype.leaveCall = function(token) {
  205. if (token === this.currentCallToken) {
  206. this._stopPingCall();
  207. this._closeEventSource();
  208. }
  209. $.ajax({
  210. url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
  211. method: 'DELETE',
  212. async: false
  213. });
  214. };
  215. InternalSignaling.prototype.sendCallMessage = function(data) {
  216. if(data.type === 'answer') {
  217. console.log("ANSWER", data);
  218. } else if(data.type === 'offer') {
  219. console.log("OFFER", data);
  220. }
  221. this.spreedArrayConnection.push({
  222. ev: "message",
  223. fn: JSON.stringify(data),
  224. sessionId: this.sessionId
  225. });
  226. };
  227. InternalSignaling.prototype.setRoomCollection = function(/*rooms*/) {
  228. this._pollForRoomChanges();
  229. return SignalingBase.prototype.setRoomCollection.apply(this, arguments);
  230. };
  231. InternalSignaling.prototype._pollForRoomChanges = function() {
  232. if (this.roomPoller) {
  233. window.clearInterval(this.roomPoller);
  234. }
  235. this.roomPoller = window.setInterval(function() {
  236. this.syncRooms();
  237. }.bind(this), 10000);
  238. };
  239. /**
  240. * @private
  241. */
  242. InternalSignaling.prototype._getCallPeers = function(token) {
  243. var defer = $.Deferred();
  244. $.ajax({
  245. beforeSend: function (request) {
  246. request.setRequestHeader('Accept', 'application/json');
  247. },
  248. url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
  249. success: function (result) {
  250. var peers = result.ocs.data;
  251. defer.resolve(peers);
  252. }
  253. });
  254. return defer;
  255. };
  256. /**
  257. * @private
  258. */
  259. InternalSignaling.prototype._openEventSource = function() {
  260. // Connect to the messages endpoint and pull for new messages
  261. this.source = new OC.EventSource(OC.generateUrl('/apps/spreed/messages'));
  262. this.source.listen('usersInRoom', function(users) {
  263. this._trigger('usersInRoom', [users]);
  264. }.bind(this));
  265. this.source.listen('message', function(message) {
  266. if (typeof(message) === 'string') {
  267. message = JSON.parse(message);
  268. }
  269. this._trigger('message', [message]);
  270. }.bind(this));
  271. this.source.listen('__internal__', function(data) {
  272. if (data === 'close') {
  273. console.log('signaling connection closed - will reopen');
  274. setTimeout(function() {
  275. this._openEventSource();
  276. }.bind(this), 0);
  277. }
  278. }.bind(this));
  279. };
  280. /**
  281. * @private
  282. */
  283. InternalSignaling.prototype._closeEventSource = function() {
  284. if (this.source) {
  285. this.source.close();
  286. this.source = null;
  287. }
  288. };
  289. /**
  290. * @private
  291. */
  292. InternalSignaling.prototype.sendPendingMessages = function() {
  293. if (!this.spreedArrayConnection.length) {
  294. return;
  295. }
  296. $.post(OC.generateUrl('/apps/spreed/signalling'), {
  297. messages: JSON.stringify(this.spreedArrayConnection)
  298. });
  299. this.spreedArrayConnection = [];
  300. };
  301. /**
  302. * @private
  303. */
  304. InternalSignaling.prototype._startPingCall = function() {
  305. this._pingCall();
  306. // Send a ping to the server all 5 seconds to ensure that the connection
  307. // is still alive.
  308. this.pingInterval = window.setInterval(function() {
  309. this._pingCall();
  310. }.bind(this), 5000);
  311. };
  312. /**
  313. * @private
  314. */
  315. InternalSignaling.prototype._stopPingCall = function() {
  316. if (this.pingInterval) {
  317. window.clearInterval(this.pingInterval);
  318. this.pingInterval = null;
  319. }
  320. };
  321. /**
  322. * @private
  323. */
  324. InternalSignaling.prototype._pingCall = function() {
  325. if (!this.currentCallToken) {
  326. return;
  327. }
  328. $.ajax({
  329. url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + this.currentCallToken + '/ping',
  330. method: 'POST'
  331. }).done(function() {
  332. this.pingFails = 0;
  333. }.bind(this)).fail(function(xhr) {
  334. // If there is an error when pinging, retry for 3 times.
  335. if (xhr.status !== 404 && this.pingFails < 3) {
  336. this.pingFails++;
  337. return;
  338. }
  339. OCA.SpreedMe.Calls.leaveCurrentCall(false);
  340. }.bind(this));
  341. };
  342. OCA.SpreedMe.createSignalingConnection = function() {
  343. // TODO(fancycode): Create different type of signaling connection
  344. // depending on configuration.
  345. return new InternalSignaling();
  346. };
  347. })(OCA, OC);