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.any.js 8.3KB

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