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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /* @flow */
  2. import uuid from 'uuid';
  3. import {
  4. APP_WILL_MOUNT,
  5. APP_WILL_UNMOUNT,
  6. appNavigate
  7. } from '../../app';
  8. import {
  9. CONFERENCE_FAILED,
  10. CONFERENCE_LEFT,
  11. CONFERENCE_WILL_JOIN,
  12. CONFERENCE_JOINED
  13. } from '../../base/conference';
  14. import { getInviteURL } from '../../base/connection';
  15. import {
  16. SET_AUDIO_MUTED,
  17. SET_VIDEO_MUTED,
  18. isVideoMutedByAudioOnly,
  19. setAudioMuted
  20. } from '../../base/media';
  21. import { MiddlewareRegistry, toState } from '../../base/redux';
  22. import { _SET_CALLKIT_LISTENERS } from './actionTypes';
  23. import CallKit from './CallKit';
  24. /**
  25. * Middleware that captures several system actions and hooks up CallKit.
  26. *
  27. * @param {Store} store - The redux store.
  28. * @returns {Function}
  29. */
  30. MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
  31. const result = next(action);
  32. switch (action.type) {
  33. case _SET_CALLKIT_LISTENERS: {
  34. const { listeners } = getState()['features/callkit'];
  35. if (listeners) {
  36. for (const [ event, listener ] of listeners) {
  37. CallKit.removeEventListener(event, listener);
  38. }
  39. }
  40. if (action.listeners) {
  41. for (const [ event, listener ] of action.listeners) {
  42. CallKit.addEventListener(event, listener);
  43. }
  44. }
  45. break;
  46. }
  47. case APP_WILL_MOUNT: {
  48. CallKit.setup(); // TODO: set app icon.
  49. const listeners = new Map();
  50. const callEndListener = data => {
  51. const conference = getCurrentConference(getState);
  52. if (conference && conference.callUUID === data.callUUID) {
  53. // We arrive here when a call is ended by the system, for
  54. // for example when another incoming call is received and the
  55. // user selects "End & Accept".
  56. delete conference.callUUID;
  57. dispatch(appNavigate(undefined));
  58. }
  59. };
  60. listeners.set('performEndCallAction', callEndListener);
  61. // Set the same listener for providerDidReset. According to the docs,
  62. // when the system resets we should terminate all calls.
  63. listeners.set('providerDidReset', callEndListener);
  64. const setMutedListener = data => {
  65. const conference = getCurrentConference(getState);
  66. if (conference && conference.callUUID === data.callUUID) {
  67. // Break the loop. Audio can be muted both from the CallKit
  68. // interface and from the Jitsi Meet interface. We must keep
  69. // them in sync, but at some point the loop needs to be broken.
  70. // We are doing it here, on the CallKit handler.
  71. const { muted } = getState()['features/base/media'].audio;
  72. if (muted !== data.muted) {
  73. dispatch(setAudioMuted(Boolean(data.muted)));
  74. }
  75. }
  76. };
  77. listeners.set('performSetMutedCallAction', setMutedListener);
  78. dispatch({
  79. type: _SET_CALLKIT_LISTENERS,
  80. listeners
  81. });
  82. break;
  83. }
  84. case APP_WILL_UNMOUNT:
  85. dispatch({
  86. type: _SET_CALLKIT_LISTENERS,
  87. listeners: null
  88. });
  89. break;
  90. case CONFERENCE_FAILED: {
  91. const { callUUID } = action.conference;
  92. if (callUUID) {
  93. CallKit.reportCallFailed(callUUID);
  94. }
  95. break;
  96. }
  97. case CONFERENCE_LEFT: {
  98. const { callUUID } = action.conference;
  99. if (callUUID) {
  100. CallKit.endCall(callUUID);
  101. }
  102. break;
  103. }
  104. case CONFERENCE_JOINED: {
  105. const { callUUID } = action.conference;
  106. if (callUUID) {
  107. CallKit.reportConnectedOutgoingCall(callUUID);
  108. }
  109. break;
  110. }
  111. case CONFERENCE_WILL_JOIN: {
  112. const conference = action.conference;
  113. const url = getInviteURL(getState);
  114. const hasVideo = !isVideoMutedByAudioOnly({ getState });
  115. // When assigning the call UUID, do so in upper case, since iOS will
  116. // return it upper cased.
  117. conference.callUUID = uuid.v4().toUpperCase();
  118. CallKit.startCall(conference.callUUID, url.toString(), hasVideo)
  119. .then(() => {
  120. const { room } = getState()['features/base/conference'];
  121. CallKit.updateCall(conference.callUUID, { displayName: room });
  122. });
  123. break;
  124. }
  125. case SET_AUDIO_MUTED: {
  126. const conference = getCurrentConference(getState);
  127. if (conference && conference.callUUID) {
  128. CallKit.setMuted(conference.callUUID, action.muted);
  129. }
  130. break;
  131. }
  132. case SET_VIDEO_MUTED: {
  133. const conference = getCurrentConference(getState);
  134. if (conference && conference.callUUID) {
  135. const hasVideo = !isVideoMutedByAudioOnly({ getState });
  136. CallKit.updateCall(conference.callUUID, { hasVideo });
  137. }
  138. break;
  139. }
  140. }
  141. return result;
  142. });
  143. /**
  144. * Returns the currently active conference.
  145. *
  146. * @param {Function|Object} stateOrGetState - The redux state or redux's
  147. * {@code getState} function.
  148. * @returns {Conference|undefined}
  149. */
  150. function getCurrentConference(stateOrGetState: Function | Object): ?Object {
  151. const state = toState(stateOrGetState);
  152. const { conference, joining } = state['features/base/conference'];
  153. return conference || joining;
  154. }