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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  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. * @returns {Object} - The properties for the device selection dialog.
  28. */
  29. export function getDeviceSelectionDialogProps(stateful: Object | Function) {
  30. const state = toState(stateful);
  31. const settings = state['features/base/settings'];
  32. const { conference } = state['features/base/conference'];
  33. const { permissions } = state['features/base/devices'];
  34. const isMobileSafari = isIosMobileBrowser();
  35. let disableAudioInputChange = !JitsiMeetJS.mediaDevices.isMultipleAudioInputSupported();
  36. let selectedAudioInputId = settings.micDeviceId;
  37. let selectedAudioOutputId = getAudioOutputDeviceId();
  38. let selectedVideoInputId = settings.cameraDeviceId;
  39. // audio input change will be a problem only when we are in a
  40. // conference and this is not supported, when we open device selection on
  41. // welcome page changing input devices will not be a problem
  42. // on welcome page we also show only what we have saved as user selected devices
  43. if (!conference) {
  44. disableAudioInputChange = false;
  45. selectedAudioInputId = getUserSelectedMicDeviceId(state);
  46. selectedAudioOutputId = getUserSelectedOutputDeviceId(state);
  47. selectedVideoInputId = getUserSelectedCameraDeviceId(state);
  48. }
  49. // we fill the device selection dialog with the devices that are currently
  50. // used or if none are currently used with what we have in settings(user selected)
  51. return {
  52. availableDevices: state['features/base/devices'].availableDevices,
  53. disableAudioInputChange,
  54. disableDeviceChange:
  55. !JitsiMeetJS.mediaDevices.isDeviceChangeAvailable(),
  56. hasAudioPermission: permissions.audio,
  57. hasVideoPermission: permissions.video,
  58. hideAudioInputPreview:
  59. !JitsiMeetJS.isCollectingLocalStats(),
  60. hideAudioOutputSelect: !JitsiMeetJS.mediaDevices
  61. .isDeviceChangeAvailable('output'),
  62. hideVideoInputPreview: isMobileSafari,
  63. hideVideoOutputSelect: isMobileSafari,
  64. selectedAudioInputId,
  65. selectedAudioOutputId,
  66. selectedVideoInputId
  67. };
  68. }
  69. /**
  70. * Processes device requests from external applications.
  71. *
  72. * @param {Dispatch} dispatch - The redux {@code dispatch} function.
  73. * @param {Function} getState - The redux function that gets/retrieves the redux
  74. * state.
  75. * @param {Object} request - The request to be processed.
  76. * @param {Function} responseCallback - The callback that will send the
  77. * response.
  78. * @returns {boolean} - True if the request has been processed and false otherwise.
  79. */
  80. export function processExternalDeviceRequest( // eslint-disable-line max-params
  81. dispatch: Dispatch<any>,
  82. getState: Function,
  83. request: Object,
  84. responseCallback: Function) {
  85. if (request.type !== 'devices') {
  86. return false;
  87. }
  88. const state = getState();
  89. const settings = state['features/base/settings'];
  90. let result = true;
  91. switch (request.name) {
  92. case 'isDeviceListAvailable':
  93. responseCallback(JitsiMeetJS.mediaDevices.isDeviceListAvailable());
  94. break;
  95. case 'isDeviceChangeAvailable':
  96. responseCallback(
  97. JitsiMeetJS.mediaDevices.isDeviceChangeAvailable(
  98. request.deviceType));
  99. break;
  100. case 'isMultipleAudioInputSupported':
  101. responseCallback(JitsiMeetJS.isMultipleAudioInputSupported());
  102. break;
  103. case 'getCurrentDevices':
  104. dispatch(getAvailableDevices()).then(devices => {
  105. if (areDeviceLabelsInitialized(state)) {
  106. const deviceDescriptions = {
  107. audioInput: undefined,
  108. audioOutput: undefined,
  109. videoInput: undefined
  110. };
  111. const currentlyUsedDeviceIds = new Set([
  112. getAudioOutputDeviceId(),
  113. settings.micDeviceId,
  114. settings.cameraDeviceId
  115. ]);
  116. devices.forEach(device => {
  117. const { deviceId, kind } = device;
  118. if (currentlyUsedDeviceIds.has(deviceId)) {
  119. switch (kind) {
  120. case 'audioinput':
  121. deviceDescriptions.audioInput = device;
  122. break;
  123. case 'audiooutput':
  124. deviceDescriptions.audioOutput = device;
  125. break;
  126. case 'videoinput':
  127. deviceDescriptions.videoInput = device;
  128. break;
  129. }
  130. }
  131. });
  132. responseCallback(deviceDescriptions);
  133. } else {
  134. // The labels are not available if the A/V permissions are
  135. // not yet granted.
  136. dispatch(addPendingDeviceRequest({
  137. type: 'devices',
  138. name: 'getCurrentDevices',
  139. responseCallback
  140. }));
  141. }
  142. });
  143. break;
  144. case 'getAvailableDevices':
  145. dispatch(getAvailableDevices()).then(devices => {
  146. if (areDeviceLabelsInitialized(state)) {
  147. responseCallback(groupDevicesByKind(devices));
  148. } else {
  149. // The labels are not available if the A/V permissions are
  150. // not yet granted.
  151. dispatch(addPendingDeviceRequest({
  152. type: 'devices',
  153. name: 'getAvailableDevices',
  154. responseCallback
  155. }));
  156. }
  157. });
  158. break;
  159. case 'setDevice': {
  160. const { device } = request;
  161. if (!areDeviceLabelsInitialized(state)) {
  162. dispatch(addPendingDeviceRequest({
  163. type: 'devices',
  164. name: 'setDevice',
  165. device,
  166. responseCallback
  167. }));
  168. return true;
  169. }
  170. const { label, id } = device;
  171. const deviceId = label
  172. ? getDeviceIdByLabel(state, device.label, device.kind)
  173. : id;
  174. if (deviceId) {
  175. switch (device.kind) {
  176. case 'audioinput':
  177. dispatch(setAudioInputDeviceAndUpdateSettings(deviceId));
  178. break;
  179. case 'audiooutput':
  180. dispatch(setAudioOutputDevice(deviceId));
  181. break;
  182. case 'videoinput':
  183. dispatch(setVideoInputDeviceAndUpdateSettings(deviceId));
  184. break;
  185. default:
  186. result = false;
  187. }
  188. } else {
  189. result = false;
  190. }
  191. responseCallback(result);
  192. break;
  193. }
  194. default:
  195. return false;
  196. }
  197. return true;
  198. }