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.

actions.js 12KB

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