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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. import { JitsiConferenceEvents } from '../lib-jitsi-meet';
  2. import {
  3. changeParticipantLastNStatus,
  4. dominantSpeakerChanged,
  5. getLocalParticipant,
  6. participantJoined,
  7. participantLeft,
  8. participantRoleChanged,
  9. participantUpdated
  10. } from '../participants';
  11. import { trackAdded, trackRemoved } from '../tracks';
  12. import {
  13. CONFERENCE_FAILED,
  14. CONFERENCE_JOINED,
  15. CONFERENCE_LEFT,
  16. CONFERENCE_WILL_JOIN,
  17. CONFERENCE_WILL_LEAVE,
  18. LOCK_STATE_CHANGED,
  19. SET_LASTN,
  20. SET_PASSWORD,
  21. SET_ROOM
  22. } from './actionTypes';
  23. import {
  24. AVATAR_ID_COMMAND,
  25. AVATAR_URL_COMMAND,
  26. EMAIL_COMMAND
  27. } from './constants';
  28. import { _addLocalTracksToConference } from './functions';
  29. import type { Dispatch } from 'redux';
  30. /**
  31. * Adds conference (event) listeners.
  32. *
  33. * @param {JitsiConference} conference - The JitsiConference instance.
  34. * @param {Dispatch} dispatch - The Redux dispatch function.
  35. * @private
  36. * @returns {void}
  37. */
  38. function _addConferenceListeners(conference, dispatch) {
  39. conference.on(
  40. JitsiConferenceEvents.CONFERENCE_FAILED,
  41. (...args) => dispatch(conferenceFailed(conference, ...args)));
  42. conference.on(
  43. JitsiConferenceEvents.CONFERENCE_JOINED,
  44. (...args) => dispatch(conferenceJoined(conference, ...args)));
  45. conference.on(
  46. JitsiConferenceEvents.CONFERENCE_LEFT,
  47. (...args) => dispatch(conferenceLeft(conference, ...args)));
  48. conference.on(
  49. JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
  50. (...args) => dispatch(dominantSpeakerChanged(...args)));
  51. conference.on(
  52. JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
  53. (...args) => _lastNEndpointsChanged(dispatch, ...args));
  54. conference.on(
  55. JitsiConferenceEvents.LOCK_STATE_CHANGED,
  56. (...args) => dispatch(_lockStateChanged(conference, ...args)));
  57. conference.on(
  58. JitsiConferenceEvents.TRACK_ADDED,
  59. t => t && !t.isLocal() && dispatch(trackAdded(t)));
  60. conference.on(
  61. JitsiConferenceEvents.TRACK_REMOVED,
  62. t => t && !t.isLocal() && dispatch(trackRemoved(t)));
  63. conference.on(
  64. JitsiConferenceEvents.USER_JOINED,
  65. (id, user) => dispatch(participantJoined({
  66. id,
  67. name: user.getDisplayName(),
  68. role: user.getRole()
  69. })));
  70. conference.on(
  71. JitsiConferenceEvents.USER_LEFT,
  72. (...args) => dispatch(participantLeft(...args)));
  73. conference.on(
  74. JitsiConferenceEvents.USER_ROLE_CHANGED,
  75. (...args) => dispatch(participantRoleChanged(...args)));
  76. conference.addCommandListener(
  77. AVATAR_ID_COMMAND,
  78. (data, id) => dispatch(participantUpdated({
  79. id,
  80. avatarID: data.value
  81. })));
  82. conference.addCommandListener(
  83. AVATAR_URL_COMMAND,
  84. (data, id) => dispatch(participantUpdated({
  85. id,
  86. avatarURL: data.value
  87. })));
  88. conference.addCommandListener(
  89. EMAIL_COMMAND,
  90. (data, id) => dispatch(participantUpdated({
  91. id,
  92. email: data.value
  93. })));
  94. }
  95. /**
  96. * Sets the data for the local participant to the conference.
  97. *
  98. * @param {JitsiConference} conference - The JitsiConference instance.
  99. * @param {Object} state - The Redux state.
  100. * @returns {void}
  101. */
  102. function _setLocalParticipantData(conference, state) {
  103. const localParticipant
  104. = getLocalParticipant(state['features/base/participants']);
  105. conference.removeCommand(AVATAR_ID_COMMAND);
  106. conference.sendCommand(AVATAR_ID_COMMAND, {
  107. value: localParticipant.avatarID
  108. });
  109. }
  110. /**
  111. * Signals that a specific conference has failed.
  112. *
  113. * @param {JitsiConference} conference - The JitsiConference that has failed.
  114. * @param {string} error - The error describing/detailing the cause of the
  115. * failure.
  116. * @returns {{
  117. * type: CONFERENCE_FAILED,
  118. * conference: JitsiConference,
  119. * error: string
  120. * }}
  121. * @public
  122. */
  123. export function conferenceFailed(conference, error) {
  124. return {
  125. type: CONFERENCE_FAILED,
  126. conference,
  127. error
  128. };
  129. }
  130. /**
  131. * Attach any pre-existing local media to the conference once the conference has
  132. * been joined.
  133. *
  134. * @param {JitsiConference} conference - The JitsiConference instance which was
  135. * joined by the local participant.
  136. * @returns {Function}
  137. */
  138. export function conferenceJoined(conference) {
  139. return (dispatch, getState) => {
  140. const localTracks
  141. = getState()['features/base/tracks']
  142. .filter(t => t.local)
  143. .map(t => t.jitsiTrack);
  144. if (localTracks.length) {
  145. _addLocalTracksToConference(conference, localTracks);
  146. }
  147. dispatch({
  148. type: CONFERENCE_JOINED,
  149. conference
  150. });
  151. };
  152. }
  153. /**
  154. * Signals that a specific conference has been left.
  155. *
  156. * @param {JitsiConference} conference - The JitsiConference instance which was
  157. * left by the local participant.
  158. * @returns {{
  159. * type: CONFERENCE_LEFT,
  160. * conference: JitsiConference
  161. * }}
  162. */
  163. export function conferenceLeft(conference) {
  164. return {
  165. type: CONFERENCE_LEFT,
  166. conference
  167. };
  168. }
  169. /**
  170. * Signals the intention of the application to have the local participant join a
  171. * conference with a specific room (name). Similar in fashion
  172. * to CONFERENCE_JOINED.
  173. *
  174. * @param {string} room - The room (name) which identifies the conference the
  175. * local participant will (try to) join.
  176. * @returns {{
  177. * type: CONFERENCE_WILL_JOIN,
  178. * room: string
  179. * }}
  180. */
  181. function _conferenceWillJoin(room) {
  182. return {
  183. type: CONFERENCE_WILL_JOIN,
  184. room
  185. };
  186. }
  187. /**
  188. * Signals the intention of the application to have the local participant leave
  189. * a specific conference. Similar in fashion to CONFERENCE_LEFT. Contrary to it
  190. * though, it's not guaranteed because CONFERENCE_LEFT may be triggered by
  191. * lib-jitsi-meet and not the application.
  192. *
  193. * @param {JitsiConference} conference - The JitsiConference instance which will
  194. * be left by the local participant.
  195. * @returns {{
  196. * type: CONFERENCE_LEFT,
  197. * conference: JitsiConference
  198. * }}
  199. */
  200. export function conferenceWillLeave(conference) {
  201. return {
  202. type: CONFERENCE_WILL_LEAVE,
  203. conference
  204. };
  205. }
  206. /**
  207. * Initializes a new conference.
  208. *
  209. * @returns {Function}
  210. */
  211. export function createConference() {
  212. return (dispatch, getState) => {
  213. const state = getState();
  214. const connection = state['features/base/connection'].connection;
  215. if (!connection) {
  216. throw new Error('Cannot create conference without connection');
  217. }
  218. const { password, room } = state['features/base/conference'];
  219. if (typeof room === 'undefined' || room === '') {
  220. throw new Error('Cannot join conference without room name');
  221. }
  222. dispatch(_conferenceWillJoin(room));
  223. // TODO Take options from config.
  224. const conference
  225. = connection.initJitsiConference(
  226. // XXX Lib-jitsi-meet does not accept uppercase letters.
  227. room.toLowerCase(),
  228. {
  229. openSctp: true
  230. // FIXME I tested H.264 from iPhone 6S during a morning
  231. // standup but, unfortunately, the other participants who
  232. // happened to be running the Web app saw only black.
  233. //
  234. // preferH264: true
  235. });
  236. _addConferenceListeners(conference, dispatch);
  237. _setLocalParticipantData(conference, state);
  238. conference.join(password);
  239. };
  240. }
  241. /**
  242. * Handles the lastN status changes for participants in the current conference.
  243. * Signals that a participant's lastN status has changed, for each participant
  244. * who entered or left the last N set.
  245. *
  246. * @param {Dispatch} dispatch - Redux dispatch function.
  247. * @param {Array} leavingIds - Ids of participants who are leaving the last N
  248. * set.
  249. * @param {Array} enteringIds - Ids of participants who are entering the last N
  250. * set.
  251. * @returns {void}
  252. *
  253. * @private
  254. */
  255. function _lastNEndpointsChanged(dispatch, leavingIds = [], enteringIds = []) {
  256. for (const id of leavingIds) {
  257. dispatch(changeParticipantLastNStatus(id, false));
  258. }
  259. for (const id of enteringIds) {
  260. dispatch(changeParticipantLastNStatus(id, true));
  261. }
  262. }
  263. /**
  264. * Signals that the lock state of a specific JitsiConference changed.
  265. *
  266. * @param {JitsiConference} conference - The JitsiConference which had its lock
  267. * state changed.
  268. * @param {boolean} locked - If the specified conference became locked, true;
  269. * otherwise, false.
  270. * @returns {{
  271. * type: LOCK_STATE_CHANGED,
  272. * conference: JitsiConference,
  273. * locked: boolean
  274. * }}
  275. */
  276. function _lockStateChanged(conference, locked) {
  277. return {
  278. type: LOCK_STATE_CHANGED,
  279. conference,
  280. locked
  281. };
  282. }
  283. /**
  284. * Sets the video channel's last N (value) of the current conference. A value of
  285. * undefined shall be used to reset it to the default value.
  286. *
  287. * @param {(number|undefined)} lastN - The last N value to be set.
  288. * @returns {Function}
  289. */
  290. export function setLastN(lastN: ?number) {
  291. return (dispatch: Dispatch<*>, getState: Function) => {
  292. if (typeof lastN === 'undefined') {
  293. const { config } = getState()['features/base/lib-jitsi-meet'];
  294. /* eslint-disable no-param-reassign */
  295. lastN = config.channelLastN;
  296. if (typeof lastN === 'undefined') {
  297. lastN = -1;
  298. }
  299. /* eslint-enable no-param-reassign */
  300. }
  301. dispatch({
  302. type: SET_LASTN,
  303. lastN
  304. });
  305. };
  306. }
  307. /**
  308. * Sets the password to join or lock a specific JitsiConference.
  309. *
  310. * @param {JitsiConference} conference - The JitsiConference which requires a
  311. * password to join or is to be locked with the specified password.
  312. * @param {Function} method - The JitsiConference method of password protection
  313. * such as join or lock.
  314. * @param {string} password - The password with which the specified conference
  315. * is to be joined or locked.
  316. * @returns {Function}
  317. */
  318. export function setPassword(conference, method, password) {
  319. return (dispatch, getState) => {
  320. switch (method) {
  321. case conference.join: {
  322. let state = getState()['features/base/conference'];
  323. // Make sure that the action will set a password for a conference
  324. // that the application wants joined.
  325. if (state.passwordRequired === conference) {
  326. dispatch({
  327. type: SET_PASSWORD,
  328. conference,
  329. method,
  330. password
  331. });
  332. // Join the conference with the newly-set password.
  333. // Make sure that the action did set the password.
  334. state = getState()['features/base/conference'];
  335. if (state.password === password
  336. && !state.passwordRequired
  337. // Make sure that the application still wants the
  338. // conference joined.
  339. && !state.conference) {
  340. method.call(conference, password);
  341. }
  342. }
  343. break;
  344. }
  345. case conference.lock: {
  346. const state = getState()['features/base/conference'];
  347. if (state.conference === conference) {
  348. return (
  349. method.call(conference, password)
  350. .then(() => dispatch({
  351. type: SET_PASSWORD,
  352. conference,
  353. method,
  354. password
  355. })));
  356. }
  357. return Promise.reject();
  358. }
  359. }
  360. };
  361. }
  362. /**
  363. * Sets (the name of) the room of the conference to be joined.
  364. *
  365. * @param {(string|undefined)} room - The name of the room of the conference to
  366. * be joined.
  367. * @returns {{
  368. * type: SET_ROOM,
  369. * room: string
  370. * }}
  371. */
  372. export function setRoom(room) {
  373. return {
  374. type: SET_ROOM,
  375. room
  376. };
  377. }