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.

middleware.js 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. /* @flow */
  2. import {
  3. createRecordingEvent,
  4. sendAnalytics
  5. } from '../analytics';
  6. import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
  7. import { CONFERENCE_WILL_JOIN, getCurrentConference } from '../base/conference';
  8. import JitsiMeetJS, {
  9. JitsiConferenceEvents,
  10. JitsiRecordingConstants
  11. } from '../base/lib-jitsi-meet';
  12. import { getParticipantDisplayName } from '../base/participants';
  13. import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
  14. import {
  15. playSound,
  16. registerSound,
  17. stopSound,
  18. unregisterSound
  19. } from '../base/sounds';
  20. import {
  21. clearRecordingSessions,
  22. hidePendingRecordingNotification,
  23. showPendingRecordingNotification,
  24. showRecordingError,
  25. showStartedRecordingNotification,
  26. showStoppedRecordingNotification,
  27. updateRecordingSessionData
  28. } from './actions';
  29. import { RECORDING_SESSION_UPDATED } from './actionTypes';
  30. import {
  31. LIVE_STREAMING_OFF_SOUND_ID,
  32. LIVE_STREAMING_ON_SOUND_ID,
  33. RECORDING_OFF_SOUND_ID,
  34. RECORDING_ON_SOUND_ID
  35. } from './constants';
  36. import { getSessionById } from './functions';
  37. import {
  38. LIVE_STREAMING_OFF_SOUND_FILE,
  39. LIVE_STREAMING_ON_SOUND_FILE,
  40. RECORDING_OFF_SOUND_FILE,
  41. RECORDING_ON_SOUND_FILE
  42. } from './sounds';
  43. /**
  44. * StateListenerRegistry provides a reliable way to detect the leaving of a
  45. * conference, where we need to clean up the recording sessions.
  46. */
  47. StateListenerRegistry.register(
  48. /* selector */ state => getCurrentConference(state),
  49. /* listener */ (conference, { dispatch }) => {
  50. if (!conference) {
  51. dispatch(clearRecordingSessions());
  52. }
  53. }
  54. );
  55. /**
  56. * The redux middleware to handle the recorder updates in a React way.
  57. *
  58. * @param {Store} store - The redux store.
  59. * @returns {Function}
  60. */
  61. MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
  62. let oldSessionData;
  63. if (action.type === RECORDING_SESSION_UPDATED) {
  64. oldSessionData
  65. = getSessionById(getState(), action.sessionData.id);
  66. }
  67. const result = next(action);
  68. switch (action.type) {
  69. case APP_WILL_MOUNT:
  70. dispatch(registerSound(
  71. LIVE_STREAMING_OFF_SOUND_ID,
  72. LIVE_STREAMING_OFF_SOUND_FILE));
  73. dispatch(registerSound(
  74. LIVE_STREAMING_ON_SOUND_ID,
  75. LIVE_STREAMING_ON_SOUND_FILE));
  76. dispatch(registerSound(
  77. RECORDING_OFF_SOUND_ID,
  78. RECORDING_OFF_SOUND_FILE));
  79. dispatch(registerSound(
  80. RECORDING_ON_SOUND_ID,
  81. RECORDING_ON_SOUND_FILE));
  82. break;
  83. case APP_WILL_UNMOUNT:
  84. dispatch(unregisterSound(LIVE_STREAMING_OFF_SOUND_ID));
  85. dispatch(unregisterSound(LIVE_STREAMING_ON_SOUND_ID));
  86. dispatch(unregisterSound(RECORDING_OFF_SOUND_ID));
  87. dispatch(unregisterSound(RECORDING_ON_SOUND_ID));
  88. break;
  89. case CONFERENCE_WILL_JOIN: {
  90. const { conference } = action;
  91. conference.on(
  92. JitsiConferenceEvents.RECORDER_STATE_CHANGED,
  93. recorderSession => {
  94. if (recorderSession) {
  95. recorderSession.getID()
  96. && dispatch(
  97. updateRecordingSessionData(recorderSession));
  98. recorderSession.getError()
  99. && _showRecordingErrorNotification(
  100. recorderSession, dispatch);
  101. }
  102. return;
  103. });
  104. break;
  105. }
  106. case RECORDING_SESSION_UPDATED: {
  107. // When in recorder mode no notifications are shown
  108. // or extra sounds are also not desired
  109. // but we want to indicate those in case of sip gateway
  110. const {
  111. iAmRecorder,
  112. iAmSipGateway
  113. } = getState()['features/base/config'];
  114. if (iAmRecorder && !iAmSipGateway) {
  115. break;
  116. }
  117. const updatedSessionData
  118. = getSessionById(getState(), action.sessionData.id);
  119. const { initiator, mode, terminator } = updatedSessionData;
  120. const { PENDING, OFF, ON } = JitsiRecordingConstants.status;
  121. if (updatedSessionData.status === PENDING
  122. && (!oldSessionData || oldSessionData.status !== PENDING)) {
  123. dispatch(showPendingRecordingNotification(mode));
  124. } else if (updatedSessionData.status !== PENDING) {
  125. dispatch(hidePendingRecordingNotification(mode));
  126. if (updatedSessionData.status === ON
  127. && (!oldSessionData || oldSessionData.status !== ON)) {
  128. const initiatorName = initiator && getParticipantDisplayName(getState, initiator.getId());
  129. initiatorName && dispatch(showStartedRecordingNotification(mode, initiatorName));
  130. let soundID;
  131. if (mode === JitsiRecordingConstants.mode.FILE) {
  132. soundID = RECORDING_ON_SOUND_ID;
  133. } else if (mode === JitsiRecordingConstants.mode.STREAM) {
  134. soundID = LIVE_STREAMING_ON_SOUND_ID;
  135. }
  136. if (soundID) {
  137. sendAnalytics(createRecordingEvent('start', mode));
  138. dispatch(playSound(soundID));
  139. }
  140. } else if (updatedSessionData.status === OFF
  141. && (!oldSessionData || oldSessionData.status !== OFF)) {
  142. dispatch(showStoppedRecordingNotification(
  143. mode, terminator && getParticipantDisplayName(getState, terminator.getId())));
  144. let duration = 0, soundOff, soundOn;
  145. if (oldSessionData && oldSessionData.timestamp) {
  146. duration
  147. = (Date.now() / 1000) - oldSessionData.timestamp;
  148. }
  149. if (mode === JitsiRecordingConstants.mode.FILE) {
  150. soundOff = RECORDING_OFF_SOUND_ID;
  151. soundOn = RECORDING_ON_SOUND_ID;
  152. } else if (mode === JitsiRecordingConstants.mode.STREAM) {
  153. soundOff = LIVE_STREAMING_OFF_SOUND_ID;
  154. soundOn = LIVE_STREAMING_ON_SOUND_ID;
  155. }
  156. if (soundOff && soundOn) {
  157. sendAnalytics(createRecordingEvent('stop', mode, duration));
  158. dispatch(stopSound(soundOn));
  159. dispatch(playSound(soundOff));
  160. }
  161. }
  162. }
  163. break;
  164. }
  165. }
  166. return result;
  167. });
  168. /**
  169. * Shows a notification about an error in the recording session. A
  170. * default notification will display if no error is specified in the passed
  171. * in recording session.
  172. *
  173. * @private
  174. * @param {Object} recorderSession - The recorder session model from the
  175. * lib.
  176. * @param {Dispatch} dispatch - The Redux Dispatch function.
  177. * @returns {void}
  178. */
  179. function _showRecordingErrorNotification(recorderSession, dispatch) {
  180. const isStreamMode
  181. = recorderSession.getMode()
  182. === JitsiMeetJS.constants.recording.mode.STREAM;
  183. switch (recorderSession.getError()) {
  184. case JitsiMeetJS.constants.recording.error.SERVICE_UNAVAILABLE:
  185. dispatch(showRecordingError({
  186. descriptionKey: 'recording.unavailable',
  187. descriptionArguments: {
  188. serviceName: isStreamMode
  189. ? '$t(liveStreaming.serviceName)'
  190. : '$t(recording.serviceName)'
  191. },
  192. titleKey: isStreamMode
  193. ? 'liveStreaming.unavailableTitle'
  194. : 'recording.unavailableTitle'
  195. }));
  196. break;
  197. case JitsiMeetJS.constants.recording.error.RESOURCE_CONSTRAINT:
  198. dispatch(showRecordingError({
  199. descriptionKey: isStreamMode
  200. ? 'liveStreaming.busy'
  201. : 'recording.busy',
  202. titleKey: isStreamMode
  203. ? 'liveStreaming.busyTitle'
  204. : 'recording.busyTitle'
  205. }));
  206. break;
  207. default:
  208. dispatch(showRecordingError({
  209. descriptionKey: isStreamMode
  210. ? 'liveStreaming.error'
  211. : 'recording.error',
  212. titleKey: isStreamMode
  213. ? 'liveStreaming.failedToStart'
  214. : 'recording.failedToStart'
  215. }));
  216. break;
  217. }
  218. }