Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. import { IState } from '../../app/types';
  2. import { IStateful } from '../app/types';
  3. import CONFIG_WHITELIST from '../config/configWhitelist';
  4. import { IConfigState } from '../config/reducer';
  5. import { IJwtState } from '../jwt/reducer';
  6. import { getParticipantCount } from '../participants/functions';
  7. import { toState } from '../redux/functions';
  8. import { parseURLParams } from '../util/parseURLParams';
  9. import { DEFAULT_SERVER_URL } from './constants';
  10. import { ISettingsState } from './reducer';
  11. /**
  12. * Returns the effective value of a configuration/preference/setting by applying
  13. * a precedence among the values specified by JWT, URL, settings,
  14. * and config.
  15. *
  16. * @param {Object|Function} stateful - The redux state object or {@code getState} function.
  17. * @param {string} propertyName - The name of the
  18. * configuration/preference/setting (property) to retrieve.
  19. * @param {Object} sources - Flags indicating the configuration/preference/setting sources to
  20. * consider/retrieve values from.
  21. * @param {boolean} sources.config - Config.
  22. * @param {boolean} jwt - JWT.
  23. * @param {boolean} settings - Settings.
  24. * @param {boolean} urlParams - URL parameters.
  25. * @returns {any}
  26. */
  27. export function getPropertyValue(
  28. stateful: IStateful,
  29. propertyName: string,
  30. sources?: any
  31. ) {
  32. // Default values don't play nicely with partial objects and we want to make
  33. // the function easy to use without exhaustively defining all flags:
  34. sources = { // eslint-disable-line no-param-reassign
  35. // Defaults:
  36. config: true,
  37. jwt: true,
  38. settings: true,
  39. urlParams: true,
  40. ...sources
  41. };
  42. // Precedence: jwt -> urlParams -> settings -> config.
  43. const state = toState(stateful);
  44. // jwt
  45. if (sources.jwt) {
  46. const value = state['features/base/jwt'][propertyName as keyof IJwtState];
  47. if (typeof value !== 'undefined') {
  48. return value[propertyName as keyof typeof value];
  49. }
  50. }
  51. // urlParams
  52. if (sources.urlParams) {
  53. if (CONFIG_WHITELIST.indexOf(propertyName) !== -1) {
  54. const urlParams
  55. = parseURLParams(state['features/base/connection'].locationURL ?? '');
  56. const value = urlParams[`config.${propertyName}`];
  57. if (typeof value !== 'undefined') {
  58. return value;
  59. }
  60. }
  61. }
  62. // settings
  63. if (sources.settings) {
  64. const value = state['features/base/settings'][propertyName as keyof ISettingsState];
  65. if (typeof value !== 'undefined') {
  66. return value;
  67. }
  68. }
  69. // config
  70. if (sources.config) {
  71. const value = state['features/base/config'][propertyName as keyof IConfigState];
  72. if (typeof value !== 'undefined') {
  73. return value;
  74. }
  75. }
  76. return undefined;
  77. }
  78. /**
  79. * Gets the currently configured server URL.
  80. *
  81. * @param {Object|Function} stateful - The redux state object or
  82. * {@code getState} function.
  83. * @returns {string} - The currently configured server URL.
  84. */
  85. export function getServerURL(stateful: IStateful) {
  86. const state = toState(stateful);
  87. return state['features/base/settings'].serverURL || DEFAULT_SERVER_URL;
  88. }
  89. /**
  90. * Searches known devices for a matching deviceId and fall back to matching on
  91. * label. Returns the stored preferred cameraDeviceId if a match is not found.
  92. *
  93. * @param {Object|Function} stateful - The redux state object or
  94. * {@code getState} function.
  95. * @returns {string}
  96. */
  97. export function getUserSelectedCameraDeviceId(stateful: IStateful) {
  98. const state = toState(stateful);
  99. const {
  100. userSelectedCameraDeviceId,
  101. userSelectedCameraDeviceLabel
  102. } = state['features/base/settings'];
  103. const { videoInput } = state['features/base/devices'].availableDevices;
  104. return _getUserSelectedDeviceId({
  105. availableDevices: videoInput,
  106. // Operating systems may append " #{number}" somewhere in the label so
  107. // find and strip that bit.
  108. matchRegex: /\s#\d*(?!.*\s#\d*)/,
  109. userSelectedDeviceId: userSelectedCameraDeviceId,
  110. userSelectedDeviceLabel: userSelectedCameraDeviceLabel,
  111. replacement: ''
  112. });
  113. }
  114. /**
  115. * Searches known devices for a matching deviceId and fall back to matching on
  116. * label. Returns the stored preferred micDeviceId if a match is not found.
  117. *
  118. * @param {Object|Function} stateful - The redux state object or
  119. * {@code getState} function.
  120. * @returns {string}
  121. */
  122. export function getUserSelectedMicDeviceId(stateful: IStateful) {
  123. const state = toState(stateful);
  124. const {
  125. userSelectedMicDeviceId,
  126. userSelectedMicDeviceLabel
  127. } = state['features/base/settings'];
  128. const { audioInput } = state['features/base/devices'].availableDevices;
  129. return _getUserSelectedDeviceId({
  130. availableDevices: audioInput,
  131. // Operating systems may append " ({number}-" somewhere in the label so
  132. // find and strip that bit.
  133. matchRegex: /\s\(\d*-\s(?!.*\s\(\d*-\s)/,
  134. userSelectedDeviceId: userSelectedMicDeviceId,
  135. userSelectedDeviceLabel: userSelectedMicDeviceLabel,
  136. replacement: ' ('
  137. });
  138. }
  139. /**
  140. * Searches known devices for a matching deviceId and fall back to matching on
  141. * label. Returns the stored preferred audioOutputDeviceId if a match is not found.
  142. *
  143. * @param {Object|Function} stateful - The redux state object or
  144. * {@code getState} function.
  145. * @returns {string}
  146. */
  147. export function getUserSelectedOutputDeviceId(stateful: IStateful) {
  148. const state = toState(stateful);
  149. const {
  150. userSelectedAudioOutputDeviceId,
  151. userSelectedAudioOutputDeviceLabel
  152. } = state['features/base/settings'];
  153. const { audioOutput } = state['features/base/devices'].availableDevices;
  154. return _getUserSelectedDeviceId({
  155. availableDevices: audioOutput,
  156. matchRegex: undefined,
  157. userSelectedDeviceId: userSelectedAudioOutputDeviceId,
  158. userSelectedDeviceLabel: userSelectedAudioOutputDeviceLabel,
  159. replacement: undefined
  160. });
  161. }
  162. /**
  163. * A helper function to abstract the logic for choosing which device ID to
  164. * use. Falls back to fuzzy matching on label if a device ID match is not found.
  165. *
  166. * @param {Object} options - The arguments used to match find the preferred
  167. * device ID from available devices.
  168. * @param {Array<string>} options.availableDevices - The array of currently
  169. * available devices to match against.
  170. * @param {Object} options.matchRegex - The regex to use to find strings
  171. * appended to the label by the operating system. The matches will be replaced
  172. * with options.replacement, with the intent of matching the same device that
  173. * might have a modified label.
  174. * @param {string} options.userSelectedDeviceId - The device ID the participant
  175. * prefers to use.
  176. * @param {string} options.userSelectedDeviceLabel - The label associated with the
  177. * device ID the participant prefers to use.
  178. * @param {string} options.replacement - The string to use with
  179. * options.matchRegex to remove identifies added to the label by the operating
  180. * system.
  181. * @private
  182. * @returns {string} The preferred device ID to use for media.
  183. */
  184. function _getUserSelectedDeviceId(options: {
  185. availableDevices: MediaDeviceInfo[] | undefined;
  186. matchRegex?: RegExp;
  187. replacement?: string;
  188. userSelectedDeviceId?: string;
  189. userSelectedDeviceLabel?: string;
  190. }) {
  191. const {
  192. availableDevices,
  193. matchRegex = '',
  194. userSelectedDeviceId,
  195. userSelectedDeviceLabel,
  196. replacement = ''
  197. } = options;
  198. // If there is no label at all, there is no need to fall back to checking
  199. // the label for a fuzzy match.
  200. if (!userSelectedDeviceLabel || !userSelectedDeviceId) {
  201. return userSelectedDeviceId;
  202. }
  203. const foundMatchingBasedonDeviceId = availableDevices?.find(
  204. candidate => candidate.deviceId === userSelectedDeviceId);
  205. // Prioritize matching the deviceId
  206. if (foundMatchingBasedonDeviceId) {
  207. return userSelectedDeviceId;
  208. }
  209. const strippedDeviceLabel
  210. = matchRegex ? userSelectedDeviceLabel.replace(matchRegex, replacement)
  211. : userSelectedDeviceLabel;
  212. const foundMatchBasedOnLabel = availableDevices?.find(candidate => {
  213. const { label } = candidate;
  214. if (!label) {
  215. return false;
  216. } else if (strippedDeviceLabel === label) {
  217. return true;
  218. }
  219. const strippedCandidateLabel
  220. = label.replace(matchRegex, replacement);
  221. return strippedDeviceLabel === strippedCandidateLabel;
  222. });
  223. return foundMatchBasedOnLabel
  224. ? foundMatchBasedOnLabel.deviceId : userSelectedDeviceId;
  225. }
  226. /**
  227. * Should we hide the helper dialog when a user tries to do audio only screen sharing.
  228. *
  229. * @param {Object} state - The state of the application.
  230. * @returns {boolean}
  231. */
  232. export function shouldHideShareAudioHelper(state: IState): boolean | undefined {
  233. return state['features/base/settings'].hideShareAudioHelper;
  234. }
  235. /**
  236. * Whether we should hide self view.
  237. *
  238. * @param {Object} state - Redux state.
  239. * @returns {boolean}
  240. */
  241. export function shouldHideSelfView(state: IState) {
  242. return getParticipantCount(state) === 1 ? false : getHideSelfView(state);
  243. }
  244. /**
  245. * Gets the disable self view setting.
  246. *
  247. * @param {Object} state - Redux state.
  248. * @returns {boolean}
  249. */
  250. export function getHideSelfView(state: IState) {
  251. return state['features/base/config'].disableSelfView || state['features/base/settings'].disableSelfView;
  252. }