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.1KB

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