Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

actions.js 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. // @flow
  2. import i18next from 'i18next';
  3. import _ from 'lodash';
  4. import type { Dispatch } from 'redux';
  5. import { createBreakoutRoomsEvent, sendAnalytics } from '../analytics';
  6. import {
  7. conferenceLeft,
  8. conferenceWillLeave,
  9. createConference,
  10. getCurrentConference
  11. } from '../base/conference';
  12. import { setAudioMuted, setVideoMuted } from '../base/media';
  13. import { getRemoteParticipants } from '../base/participants';
  14. import { createDesiredLocalTracks } from '../base/tracks/actions';
  15. import {
  16. NOTIFICATION_TIMEOUT_TYPE,
  17. clearNotifications,
  18. showNotification
  19. } from '../notifications';
  20. import { _RESET_BREAKOUT_ROOMS, _UPDATE_ROOM_COUNTER } from './actionTypes';
  21. import { FEATURE_KEY } from './constants';
  22. import {
  23. getBreakoutRooms,
  24. getMainRoom
  25. } from './functions';
  26. import logger from './logger';
  27. declare var APP: Object;
  28. /**
  29. * Action to create a breakout room.
  30. *
  31. * @param {string} name - Name / subject for the breakout room.
  32. * @returns {Function}
  33. */
  34. export function createBreakoutRoom(name?: string) {
  35. return (dispatch: Dispatch<any>, getState: Function) => {
  36. const state = getState();
  37. let { roomCounter } = state[FEATURE_KEY];
  38. const subject = name || i18next.t('breakoutRooms.defaultName', { index: ++roomCounter });
  39. sendAnalytics(createBreakoutRoomsEvent('create'));
  40. dispatch({
  41. type: _UPDATE_ROOM_COUNTER,
  42. roomCounter
  43. });
  44. // $FlowExpectedError
  45. getCurrentConference(state)?.getBreakoutRooms()
  46. ?.createBreakoutRoom(subject);
  47. };
  48. }
  49. /**
  50. * Action to close a room and send participants to the main room.
  51. *
  52. * @param {string} roomId - The id of the room to close.
  53. * @returns {Function}
  54. */
  55. export function closeBreakoutRoom(roomId: string) {
  56. return (dispatch: Dispatch<any>, getState: Function) => {
  57. const rooms = getBreakoutRooms(getState);
  58. const room = rooms[roomId];
  59. const mainRoom = getMainRoom(getState);
  60. sendAnalytics(createBreakoutRoomsEvent('close'));
  61. if (room && mainRoom) {
  62. Object.values(room.participants).forEach(p => {
  63. // $FlowExpectedError
  64. dispatch(sendParticipantToRoom(p.jid, mainRoom.id));
  65. });
  66. }
  67. };
  68. }
  69. /**
  70. * Action to remove a breakout room.
  71. *
  72. * @param {string} breakoutRoomJid - The jid of the breakout room to remove.
  73. * @returns {Function}
  74. */
  75. export function removeBreakoutRoom(breakoutRoomJid: string) {
  76. return (dispatch: Dispatch<any>, getState: Function) => {
  77. sendAnalytics(createBreakoutRoomsEvent('remove'));
  78. // $FlowExpectedError
  79. getCurrentConference(getState)?.getBreakoutRooms()
  80. ?.removeBreakoutRoom(breakoutRoomJid);
  81. };
  82. }
  83. /**
  84. * Action to auto-assign the participants to breakout rooms.
  85. *
  86. * @returns {Function}
  87. */
  88. export function autoAssignToBreakoutRooms() {
  89. return (dispatch: Dispatch<any>, getState: Function) => {
  90. const rooms = getBreakoutRooms(getState);
  91. const breakoutRooms = _.filter(rooms, (room: Object) => !room.isMainRoom);
  92. if (breakoutRooms) {
  93. sendAnalytics(createBreakoutRoomsEvent('auto.assign'));
  94. const participantIds = Array.from(getRemoteParticipants(getState).keys());
  95. const length = Math.ceil(participantIds.length / breakoutRooms.length);
  96. _.chunk(_.shuffle(participantIds), length).forEach((group, index) =>
  97. group.forEach(participantId => {
  98. dispatch(sendParticipantToRoom(participantId, breakoutRooms[index].id));
  99. })
  100. );
  101. }
  102. };
  103. }
  104. /**
  105. * Action to send a participant to a room.
  106. *
  107. * @param {string} participantId - The participant id.
  108. * @param {string} roomId - The room id.
  109. * @returns {Function}
  110. */
  111. export function sendParticipantToRoom(participantId: string, roomId: string) {
  112. return (dispatch: Dispatch<any>, getState: Function) => {
  113. const rooms = getBreakoutRooms(getState);
  114. const room = rooms[roomId];
  115. if (!room) {
  116. logger.warn(`Invalid room: ${roomId}`);
  117. return;
  118. }
  119. // Get the full JID of the participant. We could be getting the endpoint ID or
  120. // a participant JID. We want to find the connection JID.
  121. const participantJid = _findParticipantJid(getState, participantId);
  122. if (!participantJid) {
  123. logger.warn(`Could not find participant ${participantId}`);
  124. return;
  125. }
  126. // $FlowExpectedError
  127. getCurrentConference(getState)?.getBreakoutRooms()
  128. ?.sendParticipantToRoom(participantJid, room.jid);
  129. };
  130. }
  131. /**
  132. * Action to move to a room.
  133. *
  134. * @param {string} roomId - The room id to move to. If omitted move to the main room.
  135. * @returns {Function}
  136. */
  137. export function moveToRoom(roomId?: string) {
  138. return async (dispatch: Dispatch<any>, getState: Function) => {
  139. const mainRoomId = getMainRoom(getState)?.id;
  140. let _roomId = roomId || mainRoomId;
  141. // Check if we got a full JID.
  142. // $FlowExpectedError
  143. if (_roomId?.indexOf('@') !== -1) {
  144. // $FlowExpectedError
  145. const [ id, ...domainParts ] = _roomId.split('@');
  146. // On mobile we first store the room and the connection is created
  147. // later, so let's attach the domain to the room String object as
  148. // a little hack.
  149. // eslint-disable-next-line no-new-wrappers
  150. _roomId = new String(id);
  151. // $FlowExpectedError
  152. _roomId.domain = domainParts.join('@');
  153. }
  154. // $FlowExpectedError
  155. const roomIdStr = _roomId?.toString();
  156. const goToMainRoom = roomIdStr === mainRoomId;
  157. const rooms = getBreakoutRooms(getState);
  158. const targetRoom = rooms[roomIdStr];
  159. if (!targetRoom) {
  160. logger.warn(`Unknown room: ${targetRoom}`);
  161. return;
  162. }
  163. dispatch({
  164. type: _RESET_BREAKOUT_ROOMS
  165. });
  166. if (navigator.product === 'ReactNative') {
  167. const conference = getCurrentConference(getState);
  168. const { audio, video } = getState()['features/base/media'];
  169. dispatch(conferenceWillLeave(conference));
  170. try {
  171. await conference.leave();
  172. } catch (error) {
  173. logger.warn('JitsiConference.leave() rejected with:', error);
  174. dispatch(conferenceLeft(conference));
  175. }
  176. dispatch(clearNotifications());
  177. // dispatch(setRoom(_roomId));
  178. dispatch(createConference(_roomId));
  179. dispatch(setAudioMuted(audio.muted));
  180. dispatch(setVideoMuted(video.muted));
  181. dispatch(createDesiredLocalTracks());
  182. } else {
  183. try {
  184. await APP.conference.leaveRoom(false /* doDisconnect */);
  185. } catch (error) {
  186. logger.warn('APP.conference.leaveRoom() rejected with:', error);
  187. // TODO: revisit why we don't dispatch CONFERENCE_LEFT here.
  188. }
  189. APP.conference.joinRoom(_roomId);
  190. }
  191. if (goToMainRoom) {
  192. dispatch(showNotification({
  193. titleKey: 'breakoutRooms.notifications.joinedTitle',
  194. descriptionKey: 'breakoutRooms.notifications.joinedMainRoom',
  195. concatText: true,
  196. maxLines: 2
  197. }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
  198. } else {
  199. dispatch(showNotification({
  200. titleKey: 'breakoutRooms.notifications.joinedTitle',
  201. descriptionKey: 'breakoutRooms.notifications.joined',
  202. descriptionArguments: { name: targetRoom.name },
  203. concatText: true,
  204. maxLines: 2
  205. }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
  206. }
  207. };
  208. }
  209. /**
  210. * Finds a participant's connection JID given its ID.
  211. *
  212. * @param {Function} getState - The redux store state getter.
  213. * @param {string} participantId - ID of the given participant.
  214. * @returns {string|undefined} - The participant connection JID if found.
  215. */
  216. function _findParticipantJid(getState: Function, participantId: string) {
  217. const conference = getCurrentConference(getState);
  218. if (!conference) {
  219. return;
  220. }
  221. // Get the full JID of the participant. We could be getting the endpoint ID or
  222. // a participant JID. We want to find the connection JID.
  223. let _participantId = participantId;
  224. let participantJid;
  225. if (!participantId.includes('@')) {
  226. const p = conference.getParticipantById(participantId);
  227. // $FlowExpectedError
  228. _participantId = p?.getJid(); // This will be the room JID.
  229. }
  230. if (_participantId) {
  231. const rooms = getBreakoutRooms(getState);
  232. for (const room of Object.values(rooms)) {
  233. // $FlowExpectedError
  234. const participants = room.participants || {};
  235. const p = participants[_participantId]
  236. // $FlowExpectedError
  237. || Object.values(participants).find(item => item.jid === _participantId);
  238. if (p) {
  239. participantJid = p.jid;
  240. break;
  241. }
  242. }
  243. }
  244. return participantJid;
  245. }