選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

actions.js 9.8KB

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