您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

middleware.js 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. // @flow
  2. import { Platform } from 'react-native';
  3. import * as watch from 'react-native-watch-connectivity';
  4. import { appNavigate } from '../../app';
  5. import { APP_WILL_MOUNT } from '../../base/app';
  6. import { CONFERENCE_JOINED } from '../../base/conference';
  7. import { getInviteURL, isInviteURLReady } from '../../base/connection';
  8. import { setAudioMuted } from '../../base/media';
  9. import {
  10. MiddlewareRegistry,
  11. StateListenerRegistry,
  12. toState
  13. } from '../../base/redux';
  14. import { toURLString } from '../../base/util';
  15. import { setConferenceTimestamp, setSessionId, setWatchReachable } from './actions';
  16. import { CMD_HANG_UP, CMD_JOIN_CONFERENCE, CMD_SET_MUTED, MAX_RECENT_URLS } from './constants';
  17. const logger = require('jitsi-meet-logger').getLogger(__filename);
  18. const watchOSEnabled = Platform.OS === 'ios';
  19. // Handles the recent URLs state sent to the watch
  20. watchOSEnabled && StateListenerRegistry.register(
  21. /* selector */ state => state['features/recent-list'],
  22. /* listener */ (recentListState, { getState }) => {
  23. _updateApplicationContext(getState);
  24. });
  25. // Handles the mic muted state sent to the watch
  26. watchOSEnabled && StateListenerRegistry.register(
  27. /* selector */ state => _isAudioMuted(state),
  28. /* listener */ (isAudioMuted, { getState }) => {
  29. _updateApplicationContext(getState);
  30. });
  31. // Handles the conference URL state sent to the watch
  32. watchOSEnabled && StateListenerRegistry.register(
  33. /* selector */ state => _getCurrentConferenceUrl(state),
  34. /* listener */ (currentUrl, { dispatch, getState }) => {
  35. dispatch(setSessionId());
  36. _updateApplicationContext(getState);
  37. });
  38. /**
  39. * Middleware that captures conference actions.
  40. *
  41. * @param {Store} store - The redux store.
  42. * @returns {Function}
  43. */
  44. watchOSEnabled && MiddlewareRegistry.register(store => next => action => {
  45. switch (action.type) {
  46. case APP_WILL_MOUNT:
  47. _appWillMount(store);
  48. break;
  49. case CONFERENCE_JOINED:
  50. store.dispatch(setConferenceTimestamp(new Date().getTime()));
  51. _updateApplicationContext(store.getState());
  52. break;
  53. }
  54. return next(action);
  55. });
  56. /**
  57. * Registers listeners to the react-native-watch-connectivity lib.
  58. *
  59. * @param {Store} store - The redux store.
  60. * @private
  61. * @returns {void}
  62. */
  63. function _appWillMount({ dispatch, getState }) {
  64. watch.subscribeToWatchReachability((error, reachable) => {
  65. dispatch(setWatchReachable(reachable));
  66. _updateApplicationContext(getState);
  67. });
  68. watch.subscribeToMessages((error, message) => {
  69. if (error) {
  70. logger.error('watch.subscribeToMessages error:', error);
  71. return;
  72. }
  73. const {
  74. command,
  75. sessionID
  76. } = message;
  77. const currentSessionID = _getSessionId(getState());
  78. if (!sessionID || sessionID !== currentSessionID) {
  79. logger.warn(
  80. `Ignoring outdated watch command: ${message.command}`
  81. + ` sessionID: ${sessionID} current session ID: ${currentSessionID}`);
  82. return;
  83. }
  84. switch (command) {
  85. case CMD_HANG_UP:
  86. if (typeof _getCurrentConferenceUrl(getState()) !== undefined) {
  87. dispatch(appNavigate(undefined));
  88. }
  89. break;
  90. case CMD_JOIN_CONFERENCE: {
  91. const newConferenceURL = message.data;
  92. const oldConferenceURL = _getCurrentConferenceUrl(getState());
  93. if (oldConferenceURL !== newConferenceURL) {
  94. dispatch(appNavigate(newConferenceURL));
  95. }
  96. break;
  97. }
  98. case CMD_SET_MUTED:
  99. dispatch(
  100. setAudioMuted(
  101. message.muted === 'true',
  102. /* ensureTrack */ true));
  103. break;
  104. }
  105. });
  106. }
  107. /**
  108. * Figures out what's the current conference URL which is supposed to indicate what conference is currently active.
  109. * When not currently in any conference and not trying to join any then the 'NULL' string value is returned.
  110. *
  111. * @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
  112. * @returns {string}
  113. * @private
  114. */
  115. function _getCurrentConferenceUrl(stateful) {
  116. const state = toState(stateful);
  117. let currentUrl;
  118. if (isInviteURLReady(state)) {
  119. currentUrl = toURLString(getInviteURL(state));
  120. }
  121. // Check if the URL doesn't end with a slash
  122. if (currentUrl && currentUrl.substr(-1) === '/') {
  123. currentUrl = undefined;
  124. }
  125. return currentUrl ? currentUrl : undefined;
  126. }
  127. /**
  128. * Gets the current Apple Watch session's ID. A new session is started whenever the conference URL has changed. It is
  129. * used to filter out outdated commands which may arrive very later if the Apple Watch loses the connectivity.
  130. *
  131. * @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
  132. * @returns {number}
  133. * @private
  134. */
  135. function _getSessionId(stateful) {
  136. const state = toState(stateful);
  137. return state['features/mobile/watchos'].sessionID;
  138. }
  139. /**
  140. * Gets the list of recent URLs to be passed over to the Watch app.
  141. *
  142. * @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
  143. * @returns {Array<Object>}
  144. * @private
  145. */
  146. function _getRecentUrls(stateful) {
  147. const state = toState(stateful);
  148. const recentURLs = state['features/recent-list'];
  149. // Trim to MAX_RECENT_URLS and reverse the list
  150. const reversedList = recentURLs.slice(-MAX_RECENT_URLS);
  151. reversedList.reverse();
  152. return reversedList;
  153. }
  154. /**
  155. * Determines the audio muted state to be sent to the apple watch.
  156. *
  157. * @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
  158. * @returns {boolean}
  159. * @private
  160. */
  161. function _isAudioMuted(stateful) {
  162. const state = toState(stateful);
  163. const { audio } = state['features/base/media'];
  164. return audio.muted;
  165. }
  166. /**
  167. * Sends the context to the watch os app. At the time of this writing it's the entire state of
  168. * the 'features/mobile/watchos' reducer.
  169. *
  170. * @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
  171. * @private
  172. * @returns {void}
  173. */
  174. function _updateApplicationContext(stateful) {
  175. const state = toState(stateful);
  176. const { conferenceTimestamp, sessionID, watchReachable } = state['features/mobile/watchos'];
  177. if (!watchReachable) {
  178. return;
  179. }
  180. try {
  181. watch.updateApplicationContext({
  182. conferenceTimestamp,
  183. conferenceURL: _getCurrentConferenceUrl(state),
  184. micMuted: _isAudioMuted(state),
  185. recentURLs: _getRecentUrls(state),
  186. sessionID
  187. });
  188. } catch (error) {
  189. logger.error('Failed to stringify or send the context', error);
  190. }
  191. }