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.

MicrophoneEntry.tsx 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import React, { useCallback, useEffect, useRef, useState } from 'react';
  2. import { makeStyles } from 'tss-react/mui';
  3. import Icon from '../../../../base/icons/components/Icon';
  4. import { IconCheck, IconExclamationSolid } from '../../../../base/icons/svg';
  5. import JitsiMeetJS from '../../../../base/lib-jitsi-meet/_';
  6. import ContextMenuItem from '../../../../base/ui/components/web/ContextMenuItem';
  7. import { TEXT_OVERFLOW_TYPES } from '../../../../base/ui/constants.any';
  8. import Meter from './Meter';
  9. const JitsiTrackEvents = JitsiMeetJS.events.track;
  10. interface IProps {
  11. /**
  12. * The text for this component.
  13. */
  14. children: string;
  15. /**
  16. * The deviceId of the microphone.
  17. */
  18. deviceId: string;
  19. /**
  20. * Flag indicating if there is a problem with the device.
  21. */
  22. hasError?: boolean;
  23. /**
  24. * Flag indicating if there is a problem with the device.
  25. */
  26. index?: number;
  27. /**
  28. * Flag indicating the selection state.
  29. */
  30. isSelected: boolean;
  31. /**
  32. * The audio track for the current entry.
  33. */
  34. jitsiTrack: any;
  35. /**
  36. * The id for the label, that contains the item text.
  37. */
  38. labelId?: string;
  39. /**
  40. * The length of the microphone list.
  41. */
  42. length: number;
  43. /**
  44. * Used to decide whether to listen to audio level changes.
  45. */
  46. measureAudioLevels: boolean;
  47. /**
  48. * Click handler for component.
  49. */
  50. onClick: Function;
  51. }
  52. const useStyles = makeStyles()(theme => {
  53. return {
  54. container: {
  55. position: 'relative'
  56. },
  57. entryText: {
  58. maxWidth: '238px',
  59. '&.withMeter': {
  60. maxWidth: '178px'
  61. },
  62. '&.left-margin': {
  63. marginLeft: '36px'
  64. }
  65. },
  66. icon: {
  67. borderRadius: '50%',
  68. display: 'inline-block',
  69. width: '14px',
  70. marginLeft: '6px',
  71. '& svg': {
  72. fill: theme.palette.iconError
  73. }
  74. },
  75. meter: {
  76. position: 'absolute',
  77. right: '16px',
  78. top: '14px'
  79. }
  80. };
  81. });
  82. const MicrophoneEntry = ({
  83. deviceId,
  84. children,
  85. hasError,
  86. index,
  87. isSelected,
  88. length,
  89. jitsiTrack,
  90. measureAudioLevels,
  91. onClick: propsClick
  92. }: IProps) => {
  93. const [ level, setLevel ] = useState(-1);
  94. const activeTrackRef = useRef(jitsiTrack);
  95. const { classes, cx } = useStyles();
  96. /**
  97. * Click handler for the entry.
  98. *
  99. * @returns {void}
  100. */
  101. const onClick = useCallback(() => {
  102. propsClick(deviceId);
  103. }, [ propsClick, deviceId ]);
  104. /**
  105. * Key pressed handler for the entry.
  106. *
  107. * @param {Object} e - The event.
  108. * @private
  109. *
  110. * @returns {void}
  111. */
  112. const onKeyPress = useCallback((e: React.KeyboardEvent) => {
  113. if (e.key === 'Enter' || e.key === ' ') {
  114. e.preventDefault();
  115. propsClick(deviceId);
  116. }
  117. }, [ propsClick, deviceId ]);
  118. /**
  119. * Updates the level of the meter.
  120. *
  121. * @param {number} num - The audio level provided by the jitsiTrack.
  122. * @returns {void}
  123. */
  124. const updateLevel = useCallback((num: number) => {
  125. setLevel(Math.floor(num / 0.125));
  126. }, []);
  127. /**
  128. * Subscribes to audio level changes coming from the jitsiTrack.
  129. *
  130. * @returns {void}
  131. */
  132. const startListening = () => {
  133. jitsiTrack && measureAudioLevels && jitsiTrack.on(
  134. JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED,
  135. updateLevel);
  136. };
  137. /**
  138. * Unsubscribes from changes coming from the jitsiTrack.
  139. *
  140. * @param {Object} track - The jitsiTrack to unsubscribe from.
  141. * @returns {void}
  142. */
  143. const stopListening = (track?: any) => {
  144. track?.off(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, updateLevel);
  145. setLevel(-1);
  146. };
  147. useEffect(() => {
  148. startListening();
  149. return () => {
  150. stopListening(jitsiTrack);
  151. };
  152. }, []);
  153. useEffect(() => {
  154. stopListening(activeTrackRef.current);
  155. startListening();
  156. activeTrackRef.current = jitsiTrack;
  157. }, [ jitsiTrack ]);
  158. return (
  159. <li
  160. aria-checked = { isSelected }
  161. aria-posinset = { index }
  162. aria-setsize = { length }
  163. className = { classes.container }
  164. onClick = { onClick }
  165. onKeyPress = { onKeyPress }
  166. role = 'radio'
  167. tabIndex = { 0 }>
  168. <ContextMenuItem
  169. accessibilityLabel = { children }
  170. icon = { isSelected ? IconCheck : undefined }
  171. overflowType = { TEXT_OVERFLOW_TYPES.SCROLL_ON_HOVER }
  172. selected = { isSelected }
  173. text = { children }
  174. textClassName = { cx(classes.entryText,
  175. measureAudioLevels && 'withMeter',
  176. !isSelected && 'left-margin') }>
  177. {hasError && <Icon
  178. className = { classes.icon }
  179. size = { 16 }
  180. src = { IconExclamationSolid } />}
  181. </ContextMenuItem>
  182. {Boolean(jitsiTrack) && measureAudioLevels && <Meter
  183. className = { classes.meter }
  184. isDisabled = { hasError }
  185. level = { level } />
  186. }
  187. </li>
  188. );
  189. };
  190. export default MicrophoneEntry;