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.

functions.js 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. // @flow
  2. import type { Dispatch } from 'redux';
  3. import {
  4. addPendingDeviceRequest,
  5. areDeviceLabelsInitialized,
  6. getAudioOutputDeviceId,
  7. getAvailableDevices,
  8. getDeviceIdByLabel,
  9. groupDevicesByKind,
  10. setAudioInputDeviceAndUpdateSettings,
  11. setAudioOutputDevice,
  12. setVideoInputDeviceAndUpdateSettings
  13. } from '../base/devices';
  14. import { isIosMobileBrowser } from '../base/environment/utils';
  15. import JitsiMeetJS from '../base/lib-jitsi-meet';
  16. import { toState } from '../base/redux';
  17. import {
  18. getUserSelectedCameraDeviceId,
  19. getUserSelectedMicDeviceId,
  20. getUserSelectedOutputDeviceId
  21. } from '../base/settings';
  22. /**
  23. * Returns the properties for the device selection dialog from Redux state.
  24. *
  25. * @param {(Function|Object)} stateful -The (whole) redux state, or redux's
  26. * {@code getState} function to be used to retrieve the state.
  27. * @param {boolean} isDisplayedOnWelcomePage - Indicates whether the device selection dialog is displayed on the
  28. * welcome page or not.
  29. * @returns {Object} - The properties for the device selection dialog.
  30. */
  31. export function getDeviceSelectionDialogProps(stateful: Object | Function, isDisplayedOnWelcomePage) {
  32. // On mobile Safari because of https://bugs.webkit.org/show_bug.cgi?id=179363#c30, the old track is stopped
  33. // by the browser when a new track is created for preview. That's why we are disabling all previews.
  34. const disablePreviews = isIosMobileBrowser();
  35. const state = toState(stateful);
  36. const settings = state['features/base/settings'];
  37. const { permissions } = state['features/base/devices'];
  38. const inputDeviceChangeSupported = JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('input');
  39. const speakerChangeSupported = JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output');
  40. const userSelectedCamera = getUserSelectedCameraDeviceId(state);
  41. const userSelectedMic = getUserSelectedMicDeviceId(state);
  42. // When the previews are disabled we don't need multiple audio input support in order to chage the mic. This is the
  43. // case for Safari on iOS.
  44. let disableAudioInputChange
  45. = !JitsiMeetJS.mediaDevices.isMultipleAudioInputSupported() && !(disablePreviews && inputDeviceChangeSupported);
  46. let disableVideoInputSelect = !inputDeviceChangeSupported;
  47. let selectedAudioInputId = settings.micDeviceId;
  48. let selectedAudioOutputId = getAudioOutputDeviceId();
  49. let selectedVideoInputId = settings.cameraDeviceId;
  50. // audio input change will be a problem only when we are in a
  51. // conference and this is not supported, when we open device selection on
  52. // welcome page changing input devices will not be a problem
  53. // on welcome page we also show only what we have saved as user selected devices
  54. if (isDisplayedOnWelcomePage) {
  55. disableAudioInputChange = false;
  56. disableVideoInputSelect = false;
  57. selectedAudioInputId = userSelectedMic;
  58. selectedAudioOutputId = getUserSelectedOutputDeviceId(state);
  59. selectedVideoInputId = userSelectedCamera;
  60. }
  61. // we fill the device selection dialog with the devices that are currently
  62. // used or if none are currently used with what we have in settings(user selected)
  63. return {
  64. availableDevices: state['features/base/devices'].availableDevices,
  65. disableAudioInputChange,
  66. disableDeviceChange: !JitsiMeetJS.mediaDevices.isDeviceChangeAvailable(),
  67. disableVideoInputSelect,
  68. hasAudioPermission: permissions.audio,
  69. hasVideoPermission: permissions.video,
  70. hideAudioInputPreview: disableAudioInputChange || !JitsiMeetJS.isCollectingLocalStats() || disablePreviews,
  71. hideAudioOutputPreview: !speakerChangeSupported || disablePreviews,
  72. hideAudioOutputSelect: !speakerChangeSupported,
  73. hideVideoInputPreview: !inputDeviceChangeSupported || disablePreviews,
  74. selectedAudioInputId,
  75. selectedAudioOutputId,
  76. selectedVideoInputId
  77. };
  78. }
  79. /**
  80. * Processes device requests from external applications.
  81. *
  82. * @param {Dispatch} dispatch - The redux {@code dispatch} function.
  83. * @param {Function} getState - The redux function that gets/retrieves the redux
  84. * state.
  85. * @param {Object} request - The request to be processed.
  86. * @param {Function} responseCallback - The callback that will send the
  87. * response.
  88. * @returns {boolean} - True if the request has been processed and false otherwise.
  89. */
  90. export function processExternalDeviceRequest( // eslint-disable-line max-params
  91. dispatch: Dispatch<any>,
  92. getState: Function,
  93. request: Object,
  94. responseCallback: Function) {
  95. if (request.type !== 'devices') {
  96. return false;
  97. }
  98. const state = getState();
  99. const settings = state['features/base/settings'];
  100. let result = true;
  101. switch (request.name) {
  102. case 'isDeviceListAvailable':
  103. responseCallback(JitsiMeetJS.mediaDevices.isDeviceListAvailable());
  104. break;
  105. case 'isDeviceChangeAvailable':
  106. responseCallback(
  107. JitsiMeetJS.mediaDevices.isDeviceChangeAvailable(
  108. request.deviceType));
  109. break;
  110. case 'isMultipleAudioInputSupported':
  111. responseCallback(JitsiMeetJS.isMultipleAudioInputSupported());
  112. break;
  113. case 'getCurrentDevices':
  114. dispatch(getAvailableDevices()).then(devices => {
  115. if (areDeviceLabelsInitialized(state)) {
  116. const deviceDescriptions = {
  117. audioInput: undefined,
  118. audioOutput: undefined,
  119. videoInput: undefined
  120. };
  121. const currentlyUsedDeviceIds = new Set([
  122. getAudioOutputDeviceId(),
  123. settings.micDeviceId,
  124. settings.cameraDeviceId
  125. ]);
  126. devices.forEach(device => {
  127. const { deviceId, kind } = device;
  128. if (currentlyUsedDeviceIds.has(deviceId)) {
  129. switch (kind) {
  130. case 'audioinput':
  131. deviceDescriptions.audioInput = device;
  132. break;
  133. case 'audiooutput':
  134. deviceDescriptions.audioOutput = device;
  135. break;
  136. case 'videoinput':
  137. deviceDescriptions.videoInput = device;
  138. break;
  139. }
  140. }
  141. });
  142. responseCallback(deviceDescriptions);
  143. } else {
  144. // The labels are not available if the A/V permissions are
  145. // not yet granted.
  146. dispatch(addPendingDeviceRequest({
  147. type: 'devices',
  148. name: 'getCurrentDevices',
  149. responseCallback
  150. }));
  151. }
  152. });
  153. break;
  154. case 'getAvailableDevices':
  155. dispatch(getAvailableDevices()).then(devices => {
  156. if (areDeviceLabelsInitialized(state)) {
  157. responseCallback(groupDevicesByKind(devices));
  158. } else {
  159. // The labels are not available if the A/V permissions are
  160. // not yet granted.
  161. dispatch(addPendingDeviceRequest({
  162. type: 'devices',
  163. name: 'getAvailableDevices',
  164. responseCallback
  165. }));
  166. }
  167. });
  168. break;
  169. case 'setDevice': {
  170. const { device } = request;
  171. if (!areDeviceLabelsInitialized(state)) {
  172. dispatch(addPendingDeviceRequest({
  173. type: 'devices',
  174. name: 'setDevice',
  175. device,
  176. responseCallback
  177. }));
  178. return true;
  179. }
  180. const { label, id } = device;
  181. const deviceId = label
  182. ? getDeviceIdByLabel(state, device.label, device.kind)
  183. : id;
  184. if (deviceId) {
  185. switch (device.kind) {
  186. case 'audioinput':
  187. dispatch(setAudioInputDeviceAndUpdateSettings(deviceId));
  188. break;
  189. case 'audiooutput':
  190. dispatch(setAudioOutputDevice(deviceId));
  191. break;
  192. case 'videoinput':
  193. dispatch(setVideoInputDeviceAndUpdateSettings(deviceId));
  194. break;
  195. default:
  196. result = false;
  197. }
  198. } else {
  199. result = false;
  200. }
  201. responseCallback(result);
  202. break;
  203. }
  204. default:
  205. return false;
  206. }
  207. return true;
  208. }