Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

functions.web.ts 12KB

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