Source: room-module.js

  1. 'use strict';
  2. var SESSION_STATUS = require('./constants').SESSION_STATUS;
  3. var STREAM_STATUS = require('./constants').STREAM_STATUS;
  4. var Promise = require('promise-polyfill');
  5. const { v1: uuid_v1 } = require('uuid');
  6. var util = require('./util');
  7. var ROOM_REST_APP = "roomApp";
  8. var Flashphoner = require('./flashphoner-core');
  9. /**
  10. * Room api based on core api
  11. *
  12. * @namespace roomApi
  13. */
  14. /**
  15. * Initialize connection
  16. *
  17. * @param {Object} options session options
  18. * @param {String} options.urlServer Server address in form of [ws,wss]://host.domain:port
  19. * @param {String} options.username Username to login with
  20. * @param {String} options.token JWT Token
  21. * @param {String} options.appKey Application Key
  22. * @param {Integer=} options.timeout Connection timeout in milliseconds [0]
  23. * @param {Integer=} options.pingInterval Server ping interval in milliseconds [0]
  24. * @param {Integer=} options.receiveProbes A maximum subsequental pings received missing count [0]
  25. * @param {Integer=} options.probesInterval Interval to check subsequental pings received [0]
  26. * @returns {roomApi.RoomSession}
  27. * @memberof roomApi
  28. * @method connect
  29. */
  30. var appSession = function (options) {
  31. /**
  32. * Represents connection to room api app
  33. *
  34. * @namespace roomApi.RoomSession
  35. */
  36. var callbacks = {};
  37. var rooms = {};
  38. var username_ = options.username;
  39. var exports;
  40. var roomHandlers = {};
  41. var session = Flashphoner.createSession({
  42. urlServer: options.urlServer,
  43. mediaOptions: options.mediaOptions,
  44. appKey: (options.appKey && options.appKey.length != 0) ? options.appKey : ROOM_REST_APP,
  45. custom: {
  46. login: options.username,
  47. token: options.token
  48. },
  49. timeout: options.timeout || 0,
  50. pingInterval: options.pingInterval || 0,
  51. receiveProbes: options.receiveProbes || 0,
  52. probesInterval: options.probesInterval || 0
  53. }).on(SESSION_STATUS.ESTABLISHED, function (session) {
  54. if (callbacks[session.status()]) {
  55. callbacks[session.status()](exports);
  56. }
  57. }).on(SESSION_STATUS.APP_DATA, function (data) {
  58. var payload = data.payload;
  59. if (!payload || !payload.roomName) {
  60. console.error("Received app data does not contain 'payload' or 'payload.roomName' field. Received data: " + JSON.stringify(data));
  61. return;
  62. }
  63. var roomName = payload.roomName;
  64. if (roomHandlers[roomName]) {
  65. roomHandlers[roomName](payload);
  66. } else {
  67. console.warn("Failed to find room with name " + roomName);
  68. }
  69. }).on(SESSION_STATUS.DISCONNECTED, sessionDied).on(SESSION_STATUS.FAILED, sessionDied);
  70. //teardown helper
  71. function sessionDied(session) {
  72. if (callbacks[session.status()]) {
  73. callbacks[session.status()](exports);
  74. }
  75. }
  76. /**
  77. * Disconnect session
  78. *
  79. * @memberof roomApi.RoomSession
  80. * @inner
  81. */
  82. var disconnect = function () {
  83. session.disconnect();
  84. };
  85. /**
  86. * Get session status
  87. *
  88. * @returns {string} One of {@link Flashphoner.constants.SESSION_STATUS}
  89. * @memberof roomApi.RoomSession
  90. * @inner
  91. */
  92. var status = function () {
  93. return session.status();
  94. };
  95. /**
  96. * Get session id
  97. *
  98. * @returns {string} session id
  99. * @memberof roomApi.RoomSession
  100. * @inner
  101. */
  102. var id = function () {
  103. return session.id();
  104. };
  105. /**
  106. * Get server address
  107. *
  108. * @returns {string} Server url
  109. * @memberof roomApi.RoomSession
  110. * @inner
  111. */
  112. var getServerUrl = function () {
  113. return session.getServerUrl();
  114. };
  115. /**
  116. * Get session username
  117. *
  118. * @returns {string} username
  119. * @memberof roomApi.RoomSession
  120. * @inner
  121. */
  122. var username = function () {
  123. return username_;
  124. };
  125. /**
  126. * Get rooms
  127. *
  128. * @returns {roomApi.Room[]}
  129. * @memberof roomApi.RoomSession
  130. * @inner
  131. */
  132. var getRooms = function () {
  133. return util.copyObjectToArray(rooms);
  134. };
  135. /**
  136. * Add session event callback.
  137. *
  138. * @param {string} event One of {@link Flashphoner.constants.SESSION_STATUS} events
  139. * @param {RoomSession~eventCallback} callback Callback function
  140. * @returns {roomApi.RoomSession} Room Session
  141. * @throws {TypeError} Error if event is not specified
  142. * @throws {Error} Error if callback is not a valid function
  143. * @memberof roomApi.RoomSession
  144. * @inner
  145. */
  146. var on = function (event, callback) {
  147. if (!event) {
  148. throw new Error("Event can't be null", "TypeError");
  149. }
  150. if (!callback || typeof callback !== 'function') {
  151. throw new Error("Callback needs to be a valid function");
  152. }
  153. callbacks[event] = callback;
  154. return exports;
  155. };
  156. /**
  157. * Join room
  158. *
  159. * @param {Object} options Room options
  160. * @param {String} options.name Room name
  161. * @param {Boolean} options.record Record
  162. * @returns {roomApi.Room}
  163. * @memberof roomApi.RoomSession
  164. * @inner
  165. */
  166. var join = function (options) {
  167. /**
  168. * Room
  169. *
  170. * @namespace roomApi.Room
  171. */
  172. var room = {};
  173. var name_ = options.name;
  174. var record_ = options.record;
  175. var participants = {};
  176. var callbacks = {};
  177. var stateStreams = {};
  178. roomHandlers[name_] = function (data) {
  179. /**
  180. * Room participant
  181. *
  182. * @namespace roomApi.Room.Participant
  183. */
  184. var participant;
  185. if (data.name == "STATE") {
  186. if (data.info) {
  187. for (var i = 0; i < data.info.length; i++) {
  188. participantFromState(data.info[i]);
  189. }
  190. stateStreams = {};
  191. }
  192. if (callbacks["STATE"]) {
  193. callbacks["STATE"](room);
  194. }
  195. } else if (data.name == "JOINED") {
  196. participants[data.info] = {
  197. streams: {},
  198. name: function () {
  199. return data.info;
  200. },
  201. sendMessage: attachSendMessage(data.info),
  202. getStreams: function () {
  203. return util.copyObjectToArray(this.streams);
  204. }
  205. };
  206. if (callbacks["JOINED"]) {
  207. callbacks["JOINED"](participants[data.info]);
  208. }
  209. } else if (data.name == "LEFT") {
  210. participant = participants[data.info];
  211. delete participants[data.info];
  212. if (callbacks["LEFT"]) {
  213. callbacks["LEFT"](participant);
  214. }
  215. } else if (data.name == "PUBLISHED") {
  216. participant = participants[data.info.login];
  217. participant.streams[data.info.name] = {
  218. play: play(data.info.name),
  219. stop: stop(data.info.name),
  220. id: id(data.info.name),
  221. streamName: function () {
  222. return data.info.name
  223. }
  224. };
  225. if (callbacks["PUBLISHED"]) {
  226. callbacks["PUBLISHED"](participant);
  227. }
  228. } else if (data.name == "FAILED" || data.name == "UNPUBLISHED") {
  229. participant = participants[data.info.login];
  230. if (participant != null)
  231. delete participant.streams[data.info.name];
  232. } else if (data.name == "MESSAGE") {
  233. if (callbacks["MESSAGE"]) {
  234. callbacks["MESSAGE"]({
  235. from: participants[data.info.from],
  236. text: data.info.text
  237. });
  238. }
  239. }
  240. };
  241. //participant creation helper
  242. function participantFromState(state) {
  243. var participant = {};
  244. if (state.hasOwnProperty("login")) {
  245. var login = state.login;
  246. var streamName = state.name;
  247. stateStreams[streamName] = {
  248. /**
  249. * Play participant stream
  250. *
  251. * @param {HTMLElement} display Div element stream should be displayed in
  252. * @returns {Stream} Local stream object
  253. * @memberof roomApi.Room.Participant.Stream
  254. * @inner
  255. */
  256. play: play(streamName),
  257. /**
  258. * Stop participant stream
  259. *
  260. * @memberof roomApi.Room.Participant.Stream
  261. * @inner
  262. */
  263. stop: stop(streamName),
  264. /**
  265. * Get participant stream id
  266. *
  267. * @returns {String} Stream id
  268. * @memberof roomApi.Room.Participant.Stream
  269. * @inner
  270. */
  271. id: id(streamName),
  272. /**
  273. * Get participant stream name
  274. *
  275. * @returns {String} Stream name
  276. * @memberof roomApi.Room.Participant.Stream
  277. * @inner
  278. */
  279. streamName: function () {
  280. return streamName
  281. }
  282. };
  283. if (participants[login] != null) {
  284. participant = participants[login];
  285. } else {
  286. participant = {
  287. streams: {},
  288. /**
  289. * Get participant name
  290. *
  291. * @returns {String} Participant name
  292. * @memberof roomApi.Room.Participant
  293. * @inner
  294. */
  295. name: function () {
  296. return login;
  297. },
  298. /**
  299. * Send message to participant
  300. *
  301. * @param {String} message Message to send
  302. * @param {Function} error Error callback
  303. * @memberof roomApi.Room.Participant
  304. * @inner
  305. */
  306. sendMessage: attachSendMessage(login),
  307. /**
  308. * Get participant streams
  309. *
  310. * @returns {Array<roomApi.Room.Participant.Stream>} Streams
  311. * @memberof roomApi.Room.Participant
  312. * @inner
  313. */
  314. getStreams: function () {
  315. return util.copyObjectToArray(this.streams);
  316. }
  317. };
  318. participants[participant.name()] = participant;
  319. }
  320. /**
  321. * Room participant
  322. *
  323. * @namespace roomApi.Room.Participant.Stream
  324. */
  325. } else {
  326. participant = {
  327. streams: {},
  328. name: function () {
  329. return state;
  330. },
  331. sendMessage: attachSendMessage(state),
  332. getStreams: function () {
  333. return util.copyObjectToArray(this.streams);
  334. }
  335. }
  336. }
  337. if (Object.keys(stateStreams).length != 0) {
  338. for (var k in stateStreams) {
  339. if (stateStreams.hasOwnProperty(k)) {
  340. participant.streams[k] = stateStreams[k];
  341. delete stateStreams[k];
  342. }
  343. }
  344. }
  345. participants[participant.name()] = participant;
  346. return participant;
  347. }
  348. /**
  349. * Get room name
  350. *
  351. * @returns {String} Room name
  352. * @memberof roomApi.Room
  353. * @inner
  354. */
  355. var name = function () {
  356. return name_;
  357. };
  358. /**
  359. * Leave room
  360. *
  361. * @returns {Promise<room>}
  362. * @memberof roomApi.Room
  363. * @inner
  364. */
  365. var leave = function () {
  366. return new Promise(function (resolve, reject) {
  367. sendAppCommand("leave", {name: name_}).then(function () {
  368. cleanUp();
  369. resolve(room);
  370. }, function () {
  371. cleanUp();
  372. reject(room);
  373. });
  374. function cleanUp() {
  375. //clear streams
  376. var streams = session.getStreams();
  377. for (var i = 0; i < streams.length; i++) {
  378. if (streams[i].name().indexOf(name_ + "-" + username_) !== -1 && streams[i].status() != STREAM_STATUS.UNPUBLISHED) {
  379. streams[i].stop();
  380. } else if (streams[i].name().indexOf(name_) !== -1 && streams[i].status() != STREAM_STATUS.STOPPED) {
  381. streams[i].stop();
  382. }
  383. }
  384. delete roomHandlers[name_];
  385. delete rooms[name_];
  386. }
  387. });
  388. };
  389. /**
  390. * Publish stream inside room
  391. *
  392. * @param {Object} options Stream options
  393. * @param {string=} options.name Stream name
  394. * @param {Object=} options.constraints Stream constraints
  395. * @param {Boolean=} options.record Enable stream recording
  396. * @param {Boolean=} options.cacheLocalResources Display will contain local video after stream release
  397. * @param {HTMLElement} options.display Div element stream should be displayed in
  398. * @returns {Stream}
  399. * @memberof roomApi.Room
  400. * @inner
  401. */
  402. var publish = function (options) {
  403. options.name = (options.name) ? (name_ + "-" + username_ + "-" + uuid_v1().substr(0, 4) + "-" + options.name) : (name_ + "-" + username_ + "-" + uuid_v1().substr(0, 4));
  404. options.cacheLocalResources = (typeof options.cacheLocalResources === "boolean") ? options.cacheLocalResources : true;
  405. options.custom = {name: name_};
  406. var stream = session.createStream(options);
  407. stream.publish();
  408. return stream;
  409. };
  410. /**
  411. * Add room event callback.
  412. *
  413. * @param {string} event One of {@link roomApi.events} events
  414. * @param {roomApi.Room~eventCallback} callback Callback function
  415. * @returns {roomApi.Room} room
  416. * @throws {TypeError} Error if event is not specified
  417. * @throws {Error} Error if callback is not a valid function
  418. * @memberof roomApi.Room
  419. * @inner
  420. */
  421. var on = function (event, callback) {
  422. if (!event) {
  423. throw new Error("Event can't be null", "TypeError");
  424. }
  425. if (!callback || typeof callback !== 'function') {
  426. throw new Error("Callback needs to be a valid function");
  427. }
  428. callbacks[event] = callback;
  429. return room;
  430. };
  431. /**
  432. * Get participants
  433. *
  434. * @returns {roomApi.Room.Participant}
  435. * @memberof roomApi.Room
  436. * @inner
  437. */
  438. var getParticipants = function () {
  439. return util.copyObjectToArray(participants);
  440. };
  441. //participant helpers
  442. function play(streamName) {
  443. // Pass stream options to play #WCS-3445
  444. return function (display, options = {}) {
  445. var streamOptions = {
  446. ...options,
  447. name: streamName,
  448. display: display,
  449. custom: {name: name_}
  450. };
  451. var stream = session.createStream(streamOptions);
  452. stream.play();
  453. return stream;
  454. }
  455. }
  456. function stop(streamName) {
  457. return function () {
  458. var streams = session.getStreams();
  459. for (var i = 0; i < streams.length; i++) {
  460. if (streams[i].name() == streamName && streams[i].status() != STREAM_STATUS.UNPUBLISHED) {
  461. streams[i].stop();
  462. }
  463. }
  464. }
  465. }
  466. function id(streamName) {
  467. return function () {
  468. var streams = session.getStreams();
  469. for (var i = 0; i < streams.length; i++) {
  470. if (streams[i].name() == streamName)
  471. return streams[i].id();
  472. }
  473. }
  474. }
  475. function attachSendMessage(recipientName) {
  476. return function (text, error) {
  477. var message = {
  478. roomConfig: {
  479. name: name_
  480. },
  481. to: recipientName,
  482. text: text
  483. };
  484. sendAppCommand("sendMessage", message).then(function () {
  485. }, function () {
  486. if (error) {
  487. error();
  488. }
  489. });
  490. }
  491. }
  492. //sendData helper
  493. function sendAppCommand(commandName, data) {
  494. var command = {
  495. command: commandName,
  496. options: data
  497. };
  498. return session.sendData(command);
  499. }
  500. sendAppCommand("join", {name: name_, record: record_}).then(function () {
  501. }, function (info) {
  502. if (callbacks["FAILED"]) {
  503. callbacks["FAILED"](room, info.info);
  504. }
  505. });
  506. room.name = name;
  507. room.leave = leave;
  508. room.publish = publish;
  509. room.getParticipants = getParticipants;
  510. room.on = on;
  511. rooms[name_] = room;
  512. return room;
  513. };
  514. exports = {
  515. disconnect: disconnect,
  516. id: id,
  517. getServerUrl: getServerUrl,
  518. username: username,
  519. status: status,
  520. getRooms: getRooms,
  521. join: join,
  522. on: on
  523. };
  524. return exports;
  525. };
  526. var events = {
  527. STATE: "STATE",
  528. JOINED: "JOINED",
  529. LEFT: "LEFT",
  530. PUBLISHED: "PUBLISHED",
  531. MESSAGE: "MESSAGE",
  532. FAILED: "FAILED"
  533. };
  534. module.exports = {
  535. connect: appSession,
  536. events: events,
  537. sdk: Flashphoner
  538. };