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 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  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_JOIN_IN_PROGRESS, 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 { RECORDING_SESSION_UPDATED } from './actionTypes';
  21. import {
  22. clearRecordingSessions,
  23. hidePendingRecordingNotification,
  24. showPendingRecordingNotification,
  25. showRecordingError,
  26. showRecordingLimitNotification,
  27. showRecordingWarning,
  28. showStartedRecordingNotification,
  29. showStoppedRecordingNotification,
  30. updateRecordingSessionData
  31. } from './actions';
  32. import {
  33. LIVE_STREAMING_OFF_SOUND_ID,
  34. LIVE_STREAMING_ON_SOUND_ID,
  35. RECORDING_OFF_SOUND_ID,
  36. RECORDING_ON_SOUND_ID
  37. } from './constants';
  38. import { getSessionById, getResourceId } from './functions';
  39. import {
  40. LIVE_STREAMING_OFF_SOUND_FILE,
  41. LIVE_STREAMING_ON_SOUND_FILE,
  42. RECORDING_OFF_SOUND_FILE,
  43. RECORDING_ON_SOUND_FILE
  44. } from './sounds';
  45. declare var APP: Object;
  46. declare var interfaceConfig: Object;
  47. /**
  48. * StateListenerRegistry provides a reliable way to detect the leaving of a
  49. * conference, where we need to clean up the recording sessions.
  50. */
  51. StateListenerRegistry.register(
  52. /* selector */ state => getCurrentConference(state),
  53. /* listener */ (conference, { dispatch }) => {
  54. if (!conference) {
  55. dispatch(clearRecordingSessions());
  56. }
  57. }
  58. );
  59. /**
  60. * The redux middleware to handle the recorder updates in a React way.
  61. *
  62. * @param {Store} store - The redux store.
  63. * @returns {Function}
  64. */
  65. MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
  66. let oldSessionData;
  67. if (action.type === RECORDING_SESSION_UPDATED) {
  68. oldSessionData
  69. = getSessionById(getState(), action.sessionData.id);
  70. }
  71. const result = next(action);
  72. switch (action.type) {
  73. case APP_WILL_MOUNT:
  74. dispatch(registerSound(
  75. LIVE_STREAMING_OFF_SOUND_ID,
  76. LIVE_STREAMING_OFF_SOUND_FILE));
  77. dispatch(registerSound(
  78. LIVE_STREAMING_ON_SOUND_ID,
  79. LIVE_STREAMING_ON_SOUND_FILE));
  80. dispatch(registerSound(
  81. RECORDING_OFF_SOUND_ID,
  82. RECORDING_OFF_SOUND_FILE));
  83. dispatch(registerSound(
  84. RECORDING_ON_SOUND_ID,
  85. RECORDING_ON_SOUND_FILE));
  86. break;
  87. case APP_WILL_UNMOUNT:
  88. dispatch(unregisterSound(LIVE_STREAMING_OFF_SOUND_ID));
  89. dispatch(unregisterSound(LIVE_STREAMING_ON_SOUND_ID));
  90. dispatch(unregisterSound(RECORDING_OFF_SOUND_ID));
  91. dispatch(unregisterSound(RECORDING_ON_SOUND_ID));
  92. break;
  93. case CONFERENCE_JOIN_IN_PROGRESS: {
  94. const { conference } = action;
  95. conference.on(
  96. JitsiConferenceEvents.RECORDER_STATE_CHANGED,
  97. recorderSession => {
  98. if (recorderSession) {
  99. recorderSession.getID() && dispatch(updateRecordingSessionData(recorderSession));
  100. recorderSession.getError() && _showRecordingErrorNotification(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. recordingLimit
  114. } = getState()['features/base/config'];
  115. if (iAmRecorder && !iAmSipGateway) {
  116. break;
  117. }
  118. const updatedSessionData
  119. = getSessionById(getState(), action.sessionData.id);
  120. const { initiator, mode, terminator } = updatedSessionData;
  121. const { PENDING, OFF, ON } = JitsiRecordingConstants.status;
  122. if (updatedSessionData.status === PENDING
  123. && (!oldSessionData || oldSessionData.status !== PENDING)) {
  124. dispatch(showPendingRecordingNotification(mode));
  125. } else if (updatedSessionData.status !== PENDING) {
  126. dispatch(hidePendingRecordingNotification(mode));
  127. if (updatedSessionData.status === ON
  128. && (!oldSessionData || oldSessionData.status !== ON)) {
  129. if (typeof recordingLimit === 'object') {
  130. // Show notification with additional information to the initiator.
  131. dispatch(showRecordingLimitNotification(mode));
  132. } else {
  133. dispatch(showStartedRecordingNotification(mode, initiator, action.sessionData.id));
  134. }
  135. sendAnalytics(createRecordingEvent('start', mode));
  136. let soundID;
  137. if (mode === JitsiRecordingConstants.mode.FILE) {
  138. soundID = RECORDING_ON_SOUND_ID;
  139. } else if (mode === JitsiRecordingConstants.mode.STREAM) {
  140. soundID = LIVE_STREAMING_ON_SOUND_ID;
  141. }
  142. if (soundID) {
  143. dispatch(playSound(soundID));
  144. }
  145. if (typeof APP !== 'undefined') {
  146. APP.API.notifyRecordingStatusChanged(true, mode);
  147. }
  148. } else if (updatedSessionData.status === OFF
  149. && (!oldSessionData || oldSessionData.status !== OFF)) {
  150. if (terminator) {
  151. dispatch(
  152. showStoppedRecordingNotification(
  153. mode, getParticipantDisplayName(getState, getResourceId(terminator))));
  154. }
  155. let duration = 0, soundOff, soundOn;
  156. if (oldSessionData && oldSessionData.timestamp) {
  157. duration
  158. = (Date.now() / 1000) - oldSessionData.timestamp;
  159. }
  160. sendAnalytics(createRecordingEvent('stop', mode, duration));
  161. if (mode === JitsiRecordingConstants.mode.FILE) {
  162. soundOff = RECORDING_OFF_SOUND_ID;
  163. soundOn = RECORDING_ON_SOUND_ID;
  164. } else if (mode === JitsiRecordingConstants.mode.STREAM) {
  165. soundOff = LIVE_STREAMING_OFF_SOUND_ID;
  166. soundOn = LIVE_STREAMING_ON_SOUND_ID;
  167. }
  168. if (soundOff && soundOn) {
  169. dispatch(stopSound(soundOn));
  170. dispatch(playSound(soundOff));
  171. }
  172. if (typeof APP !== 'undefined') {
  173. APP.API.notifyRecordingStatusChanged(false, mode);
  174. }
  175. }
  176. }
  177. break;
  178. }
  179. }
  180. return result;
  181. });
  182. /**
  183. * Shows a notification about an error in the recording session. A
  184. * default notification will display if no error is specified in the passed
  185. * in recording session.
  186. *
  187. * @private
  188. * @param {Object} recorderSession - The recorder session model from the
  189. * lib.
  190. * @param {Dispatch} dispatch - The Redux Dispatch function.
  191. * @returns {void}
  192. */
  193. function _showRecordingErrorNotification(recorderSession, dispatch) {
  194. const mode = recorderSession.getMode();
  195. const error = recorderSession.getError();
  196. const isStreamMode = mode === JitsiMeetJS.constants.recording.mode.STREAM;
  197. switch (error) {
  198. case JitsiMeetJS.constants.recording.error.SERVICE_UNAVAILABLE:
  199. dispatch(showRecordingError({
  200. descriptionKey: 'recording.unavailable',
  201. descriptionArguments: {
  202. serviceName: isStreamMode
  203. ? '$t(liveStreaming.serviceName)'
  204. : '$t(recording.serviceName)'
  205. },
  206. titleKey: isStreamMode
  207. ? 'liveStreaming.unavailableTitle'
  208. : 'recording.unavailableTitle'
  209. }));
  210. break;
  211. case JitsiMeetJS.constants.recording.error.RESOURCE_CONSTRAINT:
  212. dispatch(showRecordingError({
  213. descriptionKey: isStreamMode
  214. ? 'liveStreaming.busy'
  215. : 'recording.busy',
  216. titleKey: isStreamMode
  217. ? 'liveStreaming.busyTitle'
  218. : 'recording.busyTitle'
  219. }));
  220. break;
  221. case JitsiMeetJS.constants.recording.error.UNEXPECTED_REQUEST:
  222. dispatch(showRecordingWarning({
  223. descriptionKey: isStreamMode
  224. ? 'liveStreaming.sessionAlreadyActive'
  225. : 'recording.sessionAlreadyActive',
  226. titleKey: isStreamMode ? 'liveStreaming.inProgress' : 'recording.inProgress'
  227. }));
  228. break;
  229. default:
  230. dispatch(showRecordingError({
  231. descriptionKey: isStreamMode
  232. ? 'liveStreaming.error'
  233. : 'recording.error',
  234. titleKey: isStreamMode
  235. ? 'liveStreaming.failedToStart'
  236. : 'recording.failedToStart'
  237. }));
  238. break;
  239. }
  240. if (typeof APP !== 'undefined') {
  241. APP.API.notifyRecordingStatusChanged(false, mode, error);
  242. }
  243. }