Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

ConnectionIndicator.js 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. import { default as Popover } from '@atlaskit/inline-dialog';
  2. import React, { Component } from 'react';
  3. import { JitsiParticipantConnectionStatus } from '../../base/lib-jitsi-meet';
  4. import { ConnectionStatsTable } from '../../connection-stats';
  5. import statsEmitter from '../statsEmitter';
  6. declare var $: Object;
  7. declare var interfaceConfig: Object;
  8. // Converts the percent for connection quality into a string recognized for CSS.
  9. const QUALITY_TO_WIDTH = [
  10. // Full (5 bars)
  11. {
  12. percent: 80,
  13. width: '100%'
  14. },
  15. // 4 bars
  16. {
  17. percent: 60,
  18. width: '80%'
  19. },
  20. // 3 bars
  21. {
  22. percent: 40,
  23. width: '55%'
  24. },
  25. // 2 bars
  26. {
  27. percent: 20,
  28. width: '40%'
  29. },
  30. // 1 bar
  31. {
  32. percent: 0,
  33. width: '20%'
  34. }
  35. // Note: we never show 0 bars.
  36. ];
  37. /**
  38. * Implements a React {@link Component} which displays the current connection
  39. * quality percentage and has a popover to show more detailed connection stats.
  40. *
  41. * @extends {Component}
  42. */
  43. class ConnectionIndicator extends Component {
  44. /**
  45. * {@code ConnectionIndicator} component's property types.
  46. *
  47. * @static
  48. */
  49. static propTypes = {
  50. /**
  51. * The current condition of the user's connection, matching one of the
  52. * enumerated values in the library.
  53. *
  54. * @type {JitsiParticipantConnectionStatus}
  55. */
  56. connectionStatus: React.PropTypes.string,
  57. /**
  58. * Whether or not clicking the indicator should display a popover for
  59. * more details.
  60. */
  61. enableStatsDisplay: React.PropTypes.bool,
  62. /**
  63. * Whether or not the displays stats are for local video.
  64. */
  65. isLocalVideo: React.PropTypes.bool,
  66. /**
  67. * Relative to the icon from where the popover for more connection
  68. * details should display.
  69. */
  70. statsPopoverPosition: React.PropTypes.string,
  71. /**
  72. * Invoked to obtain translated strings.
  73. */
  74. t: React.PropTypes.func,
  75. /**
  76. * The user ID associated with the displayed connection indication and
  77. * stats.
  78. */
  79. userID: React.PropTypes.string
  80. };
  81. /**
  82. * Initializes a new {@code ConnectionIndicator} instance.
  83. *
  84. * @param {Object} props - The read-only properties with which the new
  85. * instance is to be initialized.
  86. */
  87. constructor(props) {
  88. super(props);
  89. this.state = {
  90. /**
  91. * Whether or not the popover content should display additional
  92. * statistics.
  93. *
  94. * @type {boolean}
  95. */
  96. showMoreStats: false,
  97. /**
  98. * Cache of the stats received from subscribing to stats emitting.
  99. * The keys should be the name of the stat. With each stat update,
  100. * updates stats are mixed in with cached stats and a new stats
  101. * object is set in state.
  102. */
  103. stats: {}
  104. };
  105. // Bind event handlers so they are only bound once for every instance.
  106. this._onHideStats = this._onHideStats.bind(this);
  107. this._onShowStats = this._onShowStats.bind(this);
  108. this._onStatsUpdated = this._onStatsUpdated.bind(this);
  109. this._onToggleShowMore = this._onToggleShowMore.bind(this);
  110. }
  111. /**
  112. * Starts listening for stat updates.
  113. *
  114. * @inheritdoc
  115. * returns {void}
  116. */
  117. componentDidMount() {
  118. statsEmitter.subscribeToClientStats(
  119. this.props.userID, this._onStatsUpdated);
  120. }
  121. /**
  122. * Updates which user's stats are being listened to.
  123. *
  124. * @inheritdoc
  125. * returns {void}
  126. */
  127. componentDidUpdate(prevProps) {
  128. if (prevProps.userID !== this.props.userID) {
  129. statsEmitter.unsubscribeToClientStats(
  130. prevProps.userID, this._onStatsUpdated);
  131. statsEmitter.subscribeToClientStats(
  132. this.props.userID, this._onStatsUpdated);
  133. }
  134. }
  135. /**
  136. * Sets the state to hide the Statistics Table popover.
  137. *
  138. * @private
  139. * @returns {void}
  140. */
  141. componentWillUnmount() {
  142. statsEmitter.unsubscribeToClientStats(
  143. this.props.userID, this._onStatsUpdated);
  144. }
  145. /**
  146. * Implements React's {@link Component#render()}.
  147. *
  148. * @inheritdoc
  149. * @returns {ReactElement}
  150. */
  151. render() {
  152. return (
  153. <div
  154. className = 'indicator-container'
  155. onMouseEnter = { this._onShowStats }
  156. onMouseLeave = { this._onHideStats }>
  157. <Popover
  158. content = { this._renderStatisticsTable() }
  159. isOpen = { this.state.showStats }
  160. position = { this.props.statsPopoverPosition }>
  161. <div className = 'popover-trigger'>
  162. <div className = 'connection-indicator indicator'>
  163. <div className = 'connection indicatoricon'>
  164. { this._renderIcon() }
  165. </div>
  166. </div>
  167. </div>
  168. </Popover>
  169. </div>
  170. );
  171. }
  172. /**
  173. * Sets the state not to show the Statistics Table popover.
  174. *
  175. * @private
  176. * @returns {void}
  177. */
  178. _onHideStats() {
  179. this.setState({ showStats: false });
  180. }
  181. /**
  182. * Sets the state to show the Statistics Table popover.
  183. *
  184. * @private
  185. * @returns {void}
  186. */
  187. _onShowStats() {
  188. if (this.props.enableStatsDisplay) {
  189. this.setState({ showStats: true });
  190. }
  191. }
  192. /**
  193. * Callback invoked when new connection stats associated with the passed in
  194. * user ID are available. Will update the component's display of current
  195. * statistics.
  196. *
  197. * @param {Object} stats - Connection stats from the library.
  198. * @private
  199. * @returns {void}
  200. */
  201. _onStatsUpdated(stats = {}) {
  202. const { connectionQuality } = stats;
  203. const newPercentageState = typeof connectionQuality === 'undefined'
  204. ? {} : { percent: connectionQuality };
  205. const newStats = Object.assign(
  206. {},
  207. this.state.stats,
  208. stats,
  209. newPercentageState);
  210. this.setState({
  211. stats: newStats
  212. });
  213. }
  214. /**
  215. * Callback to invoke when the show more link in the popover content is
  216. * clicked. Sets the state which will determine if the popover should show
  217. * additional statistics about the connection.
  218. *
  219. * @returns {void}
  220. */
  221. _onToggleShowMore() {
  222. this.setState({ showMoreStats: !this.state.showMoreStats });
  223. }
  224. /**
  225. * Creates a ReactElement for displaying an icon that represents the current
  226. * connection quality.
  227. *
  228. * @returns {ReactElement}
  229. */
  230. _renderIcon() {
  231. switch (this.props.connectionStatus) {
  232. case JitsiParticipantConnectionStatus.INTERRUPTED:
  233. return (
  234. <span className = 'connection_lost'>
  235. <i className = 'icon-connection-lost' />
  236. </span>
  237. );
  238. case JitsiParticipantConnectionStatus.INACTIVE:
  239. return (
  240. <span className = 'connection_ninja'>
  241. <i className = 'icon-ninja' />
  242. </span>
  243. );
  244. default: {
  245. const { percent } = this.state.stats;
  246. const width = QUALITY_TO_WIDTH.find(x => percent >= x.percent);
  247. const iconWidth = width && width.width
  248. ? { width: width && width.width } : {};
  249. return [
  250. <span
  251. className = 'connection_empty'
  252. key = 'icon-empty'>
  253. <i className = 'icon-connection' />
  254. </span>,
  255. <span
  256. className = 'connection_full'
  257. key = 'icon-full'
  258. style = { iconWidth }>
  259. <i className = 'icon-connection' />
  260. </span>
  261. ];
  262. }
  263. }
  264. }
  265. /**
  266. * Creates a {@code ConnectionStatisticsTable} instance and an empty div
  267. * for preventing mouseleave events when moving from the icon to the
  268. * popover.
  269. *
  270. * @returns {ReactElement}
  271. */
  272. _renderStatisticsTable() {
  273. const {
  274. bandwidth,
  275. bitrate,
  276. framerate,
  277. packetLoss,
  278. resolution,
  279. transport
  280. } = this.state.stats;
  281. return (
  282. <div>
  283. <ConnectionStatsTable
  284. bandwidth = { bandwidth }
  285. bitrate = { bitrate }
  286. framerate = { framerate }
  287. isLocalVideo = { this.props.isLocalVideo }
  288. onShowMore = { this._onToggleShowMore }
  289. packetLoss = { packetLoss }
  290. resolution = { resolution }
  291. shouldShowMore = { this.state.showMoreStats }
  292. transport = { transport } />
  293. <div className = 'popover-mouse-top-padding' />
  294. <div
  295. className = { interfaceConfig.VERTICAL_FILMSTRIP
  296. ? 'popover-mousemove-padding-right'
  297. : 'popover-mousemove-padding-bottom' } />
  298. </div>
  299. );
  300. }
  301. }
  302. export default ConnectionIndicator;