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.

VideoQualityLabel.web.js 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import Tooltip from '@atlaskit/tooltip';
  2. import PropTypes from 'prop-types';
  3. import React, { Component } from 'react';
  4. import { connect } from 'react-redux';
  5. import { VIDEO_QUALITY_LEVELS } from '../../base/conference';
  6. import { translate } from '../../base/i18n';
  7. const { HIGH, STANDARD, LOW } = VIDEO_QUALITY_LEVELS;
  8. /**
  9. * Expected video resolutions placed into an array, sorted from lowest to
  10. * highest resolution.
  11. *
  12. * @type {number[]}
  13. */
  14. const RESOLUTIONS
  15. = Object.values(VIDEO_QUALITY_LEVELS).sort((a, b) => a - b);
  16. /**
  17. * A map of video resolution (number) to translation key.
  18. *
  19. * @type {Object}
  20. */
  21. const RESOLUTION_TO_TRANSLATION_KEY = {
  22. [HIGH]: 'videoStatus.hd',
  23. [STANDARD]: 'videoStatus.sd',
  24. [LOW]: 'videoStatus.ld'
  25. };
  26. /**
  27. * React {@code Component} responsible for displaying a label that indicates
  28. * the displayed video state of the current conference. {@code AudioOnlyLabel}
  29. * will display when the conference is in audio only mode. {@code HDVideoLabel}
  30. * will display if not in audio only mode and a high-definition large video is
  31. * being displayed.
  32. */
  33. export class VideoQualityLabel extends Component {
  34. /**
  35. * {@code VideoQualityLabel}'s property types.
  36. *
  37. * @static
  38. */
  39. static propTypes = {
  40. /**
  41. * Whether or not the conference is in audio only mode.
  42. */
  43. _audioOnly: PropTypes.bool,
  44. /**
  45. * Whether or not a connection to a conference has been established.
  46. */
  47. _conferenceStarted: PropTypes.bool,
  48. /**
  49. * Whether or not the filmstrip is displayed with remote videos. Used to
  50. * determine display classes to set.
  51. */
  52. _filmstripVisible: PropTypes.bool,
  53. /**
  54. * The current video resolution (height) to display a label for.
  55. */
  56. _resolution: PropTypes.number,
  57. /**
  58. * Invoked to obtain translated strings.
  59. */
  60. t: PropTypes.func
  61. };
  62. /**
  63. * Initializes a new {@code VideoQualityLabel} instance.
  64. *
  65. * @param {Object} props - The read-only React Component props with which
  66. * the new instance is to be initialized.
  67. */
  68. constructor(props) {
  69. super(props);
  70. this.state = {
  71. /**
  72. * Whether or not the filmstrip is transitioning from not visible
  73. * to visible. Used to set a transition class for animation.
  74. *
  75. * @type {boolean}
  76. */
  77. togglingToVisible: false
  78. };
  79. }
  80. /**
  81. * Updates the state for whether or not the filmstrip is being toggled to
  82. * display after having being hidden.
  83. *
  84. * @inheritdoc
  85. * @param {Object} nextProps - The read-only props which this Component will
  86. * receive.
  87. * @returns {void}
  88. */
  89. componentWillReceiveProps(nextProps) {
  90. this.setState({
  91. togglingToVisible: nextProps._filmstripVisible
  92. && !this.props._filmstripVisible
  93. });
  94. }
  95. /**
  96. * Implements React's {@link Component#render()}.
  97. *
  98. * @inheritdoc
  99. * @returns {ReactElement}
  100. */
  101. render() {
  102. const {
  103. _audioOnly,
  104. _conferenceStarted,
  105. _filmstripVisible,
  106. _resolution,
  107. t
  108. } = this.props;
  109. // FIXME The _conferenceStarted check is used to be defensive against
  110. // toggling audio only mode while there is no conference and hides the
  111. // need for error handling around audio only mode toggling.
  112. if (!_conferenceStarted) {
  113. return null;
  114. }
  115. // Determine which classes should be set on the component. These classes
  116. // will used to help with animations and setting position.
  117. const baseClasses = 'video-state-indicator moveToCorner';
  118. const filmstrip
  119. = _filmstripVisible ? 'with-filmstrip' : 'without-filmstrip';
  120. const opening = this.state.togglingToVisible ? 'opening' : '';
  121. const classNames
  122. = `${baseClasses} ${filmstrip} ${opening}`;
  123. const tooltipKey
  124. = `videoStatus.labelTooltip${_audioOnly ? 'AudioOnly' : 'Video'}`;
  125. return (
  126. <div
  127. className = { classNames }
  128. id = 'videoResolutionLabel'>
  129. <Tooltip
  130. description = { t(tooltipKey) }
  131. position = { 'left' }>
  132. <div className = 'video-quality-label-status'>
  133. { _audioOnly
  134. ? <i className = 'icon-visibility-off' />
  135. : this._mapResolutionToTranslation(_resolution) }
  136. </div>
  137. </Tooltip>
  138. </div>
  139. );
  140. }
  141. /**
  142. * Matches the passed in resolution with a translation key for describing
  143. * the resolution. The passed in resolution will be matched with a known
  144. * resolution that it is at least greater than or equal to.
  145. *
  146. * @param {number} resolution - The video height to match with a
  147. * translation.
  148. * @private
  149. * @returns {string}
  150. */
  151. _mapResolutionToTranslation(resolution) {
  152. // Set the default matching resolution of the lowest just in case a
  153. // match is not found.
  154. let highestMatchingResolution = RESOLUTIONS[0];
  155. for (let i = 0; i < RESOLUTIONS.length; i++) {
  156. const knownResolution = RESOLUTIONS[i];
  157. if (resolution >= knownResolution) {
  158. highestMatchingResolution = knownResolution;
  159. } else {
  160. break;
  161. }
  162. }
  163. return this.props.t(
  164. RESOLUTION_TO_TRANSLATION_KEY[highestMatchingResolution]);
  165. }
  166. }
  167. /**
  168. * Maps (parts of) the Redux state to the associated {@code VideoQualityLabel}'s
  169. * props.
  170. *
  171. * @param {Object} state - The Redux state.
  172. * @private
  173. * @returns {{
  174. * _audioOnly: boolean,
  175. * _conferenceStarted: boolean,
  176. * _filmstripVisible: true,
  177. * _resolution: number
  178. * }}
  179. */
  180. function _mapStateToProps(state) {
  181. const { audioOnly, conference } = state['features/base/conference'];
  182. const { visible } = state['features/filmstrip'];
  183. const { resolution } = state['features/large-video'];
  184. return {
  185. _audioOnly: audioOnly,
  186. _conferenceStarted: Boolean(conference),
  187. _filmstripVisible: visible,
  188. _resolution: resolution
  189. };
  190. }
  191. export default translate(connect(_mapStateToProps)(VideoQualityLabel));