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 7.9KB

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