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

functions.web.ts 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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. disableDesktopShareSettings: isMobileBrowser(),
  120. disableDeviceChange: !JitsiMeetJS.mediaDevices.isDeviceChangeAvailable(),
  121. disableVideoInputSelect,
  122. disableLocalVideoFlip,
  123. hasVideoPermission: permissions.video,
  124. hideAdditionalSettings,
  125. hideVideoInputPreview: !inputDeviceChangeSupported || disablePreviews,
  126. localFlipX: Boolean(localFlipX),
  127. selectedVideoInputId
  128. };
  129. }
  130. /**
  131. * Processes device requests from external applications.
  132. *
  133. * @param {Dispatch} dispatch - The redux {@code dispatch} function.
  134. * @param {Function} getState - The redux function that gets/retrieves the redux
  135. * state.
  136. * @param {Object} request - The request to be processed.
  137. * @param {Function} responseCallback - The callback that will send the
  138. * response.
  139. * @returns {boolean} - True if the request has been processed and false otherwise.
  140. */
  141. export function processExternalDeviceRequest( // eslint-disable-line max-params
  142. dispatch: IStore['dispatch'],
  143. getState: IStore['getState'],
  144. request: any,
  145. responseCallback: Function) {
  146. if (request.type !== 'devices') {
  147. return false;
  148. }
  149. const state = getState();
  150. const settings = state['features/base/settings'];
  151. let result = true;
  152. switch (request.name) {
  153. case 'isDeviceListAvailable':
  154. responseCallback(JitsiMeetJS.mediaDevices.isDeviceListAvailable());
  155. break;
  156. case 'isDeviceChangeAvailable':
  157. responseCallback(
  158. JitsiMeetJS.mediaDevices.isDeviceChangeAvailable(
  159. request.deviceType));
  160. break;
  161. case 'isMultipleAudioInputSupported':
  162. responseCallback(JitsiMeetJS.isMultipleAudioInputSupported());
  163. break;
  164. case 'getCurrentDevices': // @ts-ignore
  165. dispatch(getAvailableDevices()).then((devices: MediaDeviceInfo[]) => {
  166. if (areDeviceLabelsInitialized(state)) {
  167. const deviceDescriptions: any = {
  168. audioInput: undefined,
  169. audioOutput: undefined,
  170. videoInput: undefined
  171. };
  172. const currentlyUsedDeviceIds = new Set([
  173. getAudioOutputDeviceId(),
  174. settings.micDeviceId ?? getUserSelectedMicDeviceId(state),
  175. settings.cameraDeviceId ?? getUserSelectedCameraDeviceId(state)
  176. ]);
  177. devices.forEach(device => {
  178. const { deviceId, kind } = device;
  179. if (currentlyUsedDeviceIds.has(deviceId)) {
  180. switch (kind) {
  181. case 'audioinput':
  182. deviceDescriptions.audioInput = device;
  183. break;
  184. case 'audiooutput':
  185. deviceDescriptions.audioOutput = device;
  186. break;
  187. case 'videoinput':
  188. deviceDescriptions.videoInput = device;
  189. break;
  190. }
  191. }
  192. });
  193. responseCallback(deviceDescriptions);
  194. } else {
  195. // The labels are not available if the A/V permissions are
  196. // not yet granted.
  197. dispatch(addPendingDeviceRequest({
  198. type: 'devices',
  199. name: 'getCurrentDevices',
  200. responseCallback
  201. }));
  202. }
  203. });
  204. break;
  205. case 'getAvailableDevices': // @ts-ignore
  206. dispatch(getAvailableDevices()).then((devices: MediaDeviceInfo[]) => {
  207. if (areDeviceLabelsInitialized(state)) {
  208. responseCallback(groupDevicesByKind(devices));
  209. } else {
  210. // The labels are not available if the A/V permissions are
  211. // not yet granted.
  212. dispatch(addPendingDeviceRequest({
  213. type: 'devices',
  214. name: 'getAvailableDevices',
  215. responseCallback
  216. }));
  217. }
  218. });
  219. break;
  220. case 'setDevice': {
  221. const { device } = request;
  222. if (!areDeviceLabelsInitialized(state)) {
  223. dispatch(addPendingDeviceRequest({
  224. type: 'devices',
  225. name: 'setDevice',
  226. device,
  227. responseCallback
  228. }));
  229. return true;
  230. }
  231. const { label, id } = device;
  232. const deviceId = label
  233. ? getDeviceIdByLabel(state, device.label, device.kind)
  234. : id;
  235. if (deviceId) {
  236. switch (device.kind) {
  237. case 'audioinput':
  238. dispatch(setAudioInputDeviceAndUpdateSettings(deviceId));
  239. break;
  240. case 'audiooutput':
  241. dispatch(setAudioOutputDevice(deviceId));
  242. break;
  243. case 'videoinput':
  244. dispatch(setVideoInputDeviceAndUpdateSettings(deviceId));
  245. break;
  246. default:
  247. result = false;
  248. }
  249. } else {
  250. result = false;
  251. }
  252. responseCallback(result);
  253. break;
  254. }
  255. default:
  256. return false;
  257. }
  258. return true;
  259. }