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.js 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. // @flow
  2. import { 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. const urlParams
  50. = parseURLParams(state['features/base/connection'].locationURL);
  51. const value = urlParams[`config.${propertyName}`];
  52. if (typeof value !== 'undefined') {
  53. return value;
  54. }
  55. }
  56. // settings
  57. if (sources.settings) {
  58. const value = state['features/base/settings'][propertyName];
  59. if (typeof value !== 'undefined') {
  60. return value;
  61. }
  62. }
  63. // config
  64. if (sources.config) {
  65. const value = state['features/base/config'][propertyName];
  66. if (typeof value !== 'undefined') {
  67. return value;
  68. }
  69. }
  70. return undefined;
  71. }
  72. /**
  73. * Gets the currently configured server URL.
  74. *
  75. * @param {Object|Function} stateful - The redux state object or
  76. * {@code getState} function.
  77. * @returns {string} - The currently configured server URL.
  78. */
  79. export function getServerURL(stateful: Object | Function) {
  80. const state = toState(stateful);
  81. return state['features/base/settings'].serverURL || DEFAULT_SERVER_URL;
  82. }
  83. /**
  84. * Searches known devices for a matching deviceId and fall back to matching on
  85. * label. Returns the stored preferred cameraDeviceId if a match is not found.
  86. *
  87. * @param {Object|Function} stateful - The redux state object or
  88. * {@code getState} function.
  89. * @returns {string}
  90. */
  91. export function getUserSelectedCameraDeviceId(stateful: Object | Function) {
  92. const state = toState(stateful);
  93. const {
  94. userSelectedCameraDeviceId,
  95. userSelectedCameraDeviceLabel
  96. } = state['features/base/settings'];
  97. const { videoInput } = state['features/base/devices'].availableDevices;
  98. return _getUserSelectedDeviceId({
  99. availableDevices: videoInput,
  100. // Operating systems may append " #{number}" somewhere in the label so
  101. // find and strip that bit.
  102. matchRegex: /\s#\d*(?!.*\s#\d*)/,
  103. userSelectedDeviceId: userSelectedCameraDeviceId,
  104. userSelectedDeviceLabel: userSelectedCameraDeviceLabel,
  105. replacement: ''
  106. });
  107. }
  108. /**
  109. * Searches known devices for a matching deviceId and fall back to matching on
  110. * label. Returns the stored preferred micDeviceId if a match is not found.
  111. *
  112. * @param {Object|Function} stateful - The redux state object or
  113. * {@code getState} function.
  114. * @returns {string}
  115. */
  116. export function getUserSelectedMicDeviceId(stateful: Object | Function) {
  117. const state = toState(stateful);
  118. const {
  119. userSelectedMicDeviceId,
  120. userSelectedMicDeviceLabel
  121. } = state['features/base/settings'];
  122. const { audioInput } = state['features/base/devices'].availableDevices;
  123. return _getUserSelectedDeviceId({
  124. availableDevices: audioInput,
  125. // Operating systems may append " ({number}-" somewhere in the label so
  126. // find and strip that bit.
  127. matchRegex: /\s\(\d*-\s(?!.*\s\(\d*-\s)/,
  128. userSelectedDeviceId: userSelectedMicDeviceId,
  129. userSelectedDeviceLabel: userSelectedMicDeviceLabel,
  130. replacement: ' ('
  131. });
  132. }
  133. /**
  134. * Searches known devices for a matching deviceId and fall back to matching on
  135. * label. Returns the stored preferred audioOutputDeviceId if a match is not found.
  136. *
  137. * @param {Object|Function} stateful - The redux state object or
  138. * {@code getState} function.
  139. * @returns {string}
  140. */
  141. export function getUserSelectedOutputDeviceId(stateful: Object | Function) {
  142. const state = toState(stateful);
  143. const {
  144. userSelectedAudioOutputDeviceId,
  145. userSelectedAudioOutputDeviceLabel
  146. } = state['features/base/settings'];
  147. const { audioOutput } = state['features/base/devices'].availableDevices;
  148. return _getUserSelectedDeviceId({
  149. availableDevices: audioOutput,
  150. matchRegex: undefined,
  151. userSelectedDeviceId: userSelectedAudioOutputDeviceId,
  152. userSelectedDeviceLabel: userSelectedAudioOutputDeviceLabel,
  153. replacement: undefined
  154. });
  155. }
  156. /**
  157. * A helper function to abstract the logic for choosing which device ID to
  158. * use. Falls back to fuzzy matching on label if a device ID match is not found.
  159. *
  160. * @param {Object} options - The arguments used to match find the preferred
  161. * device ID from available devices.
  162. * @param {Array<string>} options.availableDevices - The array of currently
  163. * available devices to match against.
  164. * @param {Object} options.matchRegex - The regex to use to find strings
  165. * appended to the label by the operating system. The matches will be replaced
  166. * with options.replacement, with the intent of matching the same device that
  167. * might have a modified label.
  168. * @param {string} options.userSelectedDeviceId - The device ID the participant
  169. * prefers to use.
  170. * @param {string} options.userSelectedDeviceLabel - The label associated with the
  171. * device ID the participant prefers to use.
  172. * @param {string} options.replacement - The string to use with
  173. * options.matchRegex to remove identifies added to the label by the operating
  174. * system.
  175. * @private
  176. * @returns {string} The preferred device ID to use for media.
  177. */
  178. function _getUserSelectedDeviceId(options) {
  179. const {
  180. availableDevices,
  181. matchRegex,
  182. userSelectedDeviceId,
  183. userSelectedDeviceLabel,
  184. replacement
  185. } = options;
  186. // If there is no label at all, there is no need to fall back to checking
  187. // the label for a fuzzy match.
  188. if (!userSelectedDeviceLabel || !userSelectedDeviceId) {
  189. return userSelectedDeviceId;
  190. }
  191. const foundMatchingBasedonDeviceId = availableDevices.find(
  192. candidate => candidate.deviceId === userSelectedDeviceId);
  193. // Prioritize matching the deviceId
  194. if (foundMatchingBasedonDeviceId) {
  195. return userSelectedDeviceId;
  196. }
  197. const strippedDeviceLabel
  198. = matchRegex ? userSelectedDeviceLabel.replace(matchRegex, replacement)
  199. : userSelectedDeviceLabel;
  200. const foundMatchBasedOnLabel = availableDevices.find(candidate => {
  201. const { label } = candidate;
  202. if (!label) {
  203. return false;
  204. } else if (strippedDeviceLabel === label) {
  205. return true;
  206. }
  207. const strippedCandidateLabel
  208. = label.replace(matchRegex, replacement);
  209. return strippedDeviceLabel === strippedCandidateLabel;
  210. });
  211. return foundMatchBasedOnLabel
  212. ? foundMatchBasedOnLabel.deviceId : userSelectedDeviceId;
  213. }