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.ts 8.7KB

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