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.web.ts 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import { IStore } from '../../app/types';
  2. import { IStateful } from '../app/types';
  3. import { isMobileBrowser } from '../environment/utils';
  4. import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
  5. import { setAudioMuted } from '../media/actions';
  6. import { MEDIA_TYPE } from '../media/constants';
  7. import { getStartWithAudioMuted } from '../media/functions';
  8. import { toState } from '../redux/functions';
  9. import {
  10. getUserSelectedCameraDeviceId,
  11. getUserSelectedMicDeviceId
  12. } from '../settings/functions.web';
  13. import loadEffects from './loadEffects';
  14. import logger from './logger';
  15. import { ITrackOptions } from './types';
  16. export * from './functions.any';
  17. /**
  18. * Create local tracks of specific types.
  19. *
  20. * @param {Object} options - The options with which the local tracks are to be
  21. * created.
  22. * @param {string|null} [options.cameraDeviceId] - Camera device id or
  23. * {@code undefined} to use app's settings.
  24. * @param {string[]} options.devices - Required track types such as 'audio'
  25. * and/or 'video'.
  26. * @param {string|null} [options.micDeviceId] - Microphone device id or
  27. * {@code undefined} to use app's settings.
  28. * @param {number|undefined} [oprions.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
  29. * @param {boolean} [options.firePermissionPromptIsShownEvent] - Whether lib-jitsi-meet
  30. * should check for a {@code getUserMedia} permission prompt and fire a
  31. * corresponding event.
  32. * @param {IStore} store - The redux store in the context of which the function
  33. * is to execute and from which state such as {@code config} is to be retrieved.
  34. * @returns {Promise<JitsiLocalTrack[]>}
  35. */
  36. export function createLocalTracksF(options: ITrackOptions = {}, store?: IStore) {
  37. let { cameraDeviceId, micDeviceId } = options;
  38. const {
  39. desktopSharingSourceDevice,
  40. desktopSharingSources,
  41. firePermissionPromptIsShownEvent,
  42. timeout
  43. } = options;
  44. // TODO The app's settings should go in the redux store and then the
  45. // reliance on the global variable APP will go away.
  46. store = store || APP.store; // eslint-disable-line no-param-reassign
  47. const state = store.getState();
  48. if (typeof cameraDeviceId === 'undefined' || cameraDeviceId === null) {
  49. cameraDeviceId = getUserSelectedCameraDeviceId(state);
  50. }
  51. if (typeof micDeviceId === 'undefined' || micDeviceId === null) {
  52. micDeviceId = getUserSelectedMicDeviceId(state);
  53. }
  54. const {
  55. desktopSharingFrameRate,
  56. firefox_fake_device, // eslint-disable-line camelcase
  57. resolution
  58. } = state['features/base/config'];
  59. const constraints = options.constraints ?? state['features/base/config'].constraints;
  60. return (
  61. loadEffects(store).then((effectsArray: Object[]) => {
  62. // Filter any undefined values returned by Promise.resolve().
  63. const effects = effectsArray.filter(effect => Boolean(effect));
  64. return JitsiMeetJS.createLocalTracks(
  65. {
  66. cameraDeviceId,
  67. constraints,
  68. desktopSharingFrameRate,
  69. desktopSharingSourceDevice,
  70. desktopSharingSources,
  71. // Copy array to avoid mutations inside library.
  72. devices: options.devices?.slice(0),
  73. effects,
  74. firefox_fake_device, // eslint-disable-line camelcase
  75. firePermissionPromptIsShownEvent,
  76. micDeviceId,
  77. resolution,
  78. timeout
  79. })
  80. .catch((err: Error) => {
  81. logger.error('Failed to create local tracks', options.devices, err);
  82. return Promise.reject(err);
  83. });
  84. }));
  85. }
  86. /**
  87. * Returns an object containing a promise which resolves with the created tracks and the errors resulting from that
  88. * process.
  89. *
  90. * @returns {Promise<JitsiLocalTrack[]>}
  91. *
  92. * @todo Refactor to not use APP.
  93. */
  94. export function createPrejoinTracks() {
  95. const errors: any = {};
  96. const initialDevices = [ 'audio' ];
  97. const requestedAudio = true;
  98. let requestedVideo = false;
  99. const { startAudioOnly, startWithVideoMuted } = APP.store.getState()['features/base/settings'];
  100. const startWithAudioMuted = getStartWithAudioMuted(APP.store.getState());
  101. // On Electron there is no permission prompt for granting permissions. That's why we don't need to
  102. // spend much time displaying the overlay screen. If GUM is not resolved within 15 seconds it will
  103. // probably never resolve.
  104. const timeout = browser.isElectron() ? 15000 : 60000;
  105. // Always get a handle on the audio input device so that we have statistics even if the user joins the
  106. // conference muted. Previous implementation would only acquire the handle when the user first unmuted,
  107. // which would results in statistics ( such as "No audio input" or "Are you trying to speak?") being available
  108. // only after that point.
  109. if (startWithAudioMuted) {
  110. APP.store.dispatch(setAudioMuted(true));
  111. }
  112. if (!startWithVideoMuted && !startAudioOnly) {
  113. initialDevices.push('video');
  114. requestedVideo = true;
  115. }
  116. let tryCreateLocalTracks: any = Promise.resolve([]);
  117. if (requestedAudio || requestedVideo) {
  118. tryCreateLocalTracks = createLocalTracksF({
  119. devices: initialDevices,
  120. firePermissionPromptIsShownEvent: true,
  121. timeout
  122. }, APP.store)
  123. .catch(async (err: Error) => {
  124. if (err.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
  125. errors.audioAndVideoError = err;
  126. return [];
  127. }
  128. // Retry with separate gUM calls.
  129. const gUMPromises: any = [];
  130. const tracks: any = [];
  131. if (requestedAudio) {
  132. gUMPromises.push(createLocalTracksF({
  133. devices: [ MEDIA_TYPE.AUDIO ],
  134. firePermissionPromptIsShownEvent: true,
  135. timeout
  136. }));
  137. }
  138. if (requestedVideo) {
  139. gUMPromises.push(createLocalTracksF({
  140. devices: [ MEDIA_TYPE.VIDEO ],
  141. firePermissionPromptIsShownEvent: true,
  142. timeout
  143. }));
  144. }
  145. const results = await Promise.allSettled(gUMPromises);
  146. let errorMsg;
  147. results.forEach((result, idx) => {
  148. if (result.status === 'fulfilled') {
  149. tracks.push(result.value[0]);
  150. } else {
  151. errorMsg = result.reason;
  152. const isAudio = idx === 0;
  153. logger.error(`${isAudio ? 'Audio' : 'Video'} track creation failed with error ${errorMsg}`);
  154. if (isAudio) {
  155. errors.audioOnlyError = errorMsg;
  156. } else {
  157. errors.videoOnlyError = errorMsg;
  158. }
  159. }
  160. });
  161. if (errors.audioOnlyError && errors.videoOnlyError) {
  162. errors.audioAndVideoError = errorMsg;
  163. }
  164. return tracks;
  165. });
  166. }
  167. return {
  168. tryCreateLocalTracks,
  169. errors
  170. };
  171. }
  172. /**
  173. * Determines whether toggle camera should be enabled or not.
  174. *
  175. * @param {Function|Object} stateful - The redux store or {@code getState} function.
  176. * @returns {boolean} - Whether toggle camera should be enabled.
  177. */
  178. export function isToggleCameraEnabled(stateful: IStateful) {
  179. const state = toState(stateful);
  180. const { videoInput } = state['features/base/devices'].availableDevices;
  181. return isMobileBrowser() && Number(videoInput?.length) > 1;
  182. }