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.

ConnectionIndicator.js 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. import AKInlineDialog 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._onStatsUpdated = this._onStatsUpdated.bind(this);
  107. this._onStatsClose = this._onStatsClose.bind(this);
  108. this._onStatsToggle = this._onStatsToggle.bind(this);
  109. this._onStatsUpdated = this._onStatsUpdated.bind(this);
  110. this._onToggleShowMore = this._onToggleShowMore.bind(this);
  111. }
  112. /**
  113. * Starts listening for stat updates.
  114. *
  115. * @inheritdoc
  116. * returns {void}
  117. */
  118. componentDidMount() {
  119. statsEmitter.subscribeToClientStats(
  120. this.props.userID, this._onStatsUpdated);
  121. }
  122. /**
  123. * Updates which user's stats are being listened to.
  124. *
  125. * @inheritdoc
  126. * returns {void}
  127. */
  128. componentDidUpdate(prevProps) {
  129. if (prevProps.userID !== this.props.userID) {
  130. statsEmitter.unsubscribeToClientStats(
  131. prevProps.userID, this._onStatsUpdated);
  132. statsEmitter.subscribeToClientStats(
  133. this.props.userID, this._onStatsUpdated);
  134. }
  135. }
  136. /**
  137. * Sets the state to hide the Statistics Table popover.
  138. *
  139. * @private
  140. * @returns {void}
  141. */
  142. componentWillUnmount() {
  143. statsEmitter.unsubscribeToClientStats(
  144. this.props.userID, this._onStatsUpdated);
  145. }
  146. /**
  147. * Implements React's {@link Component#render()}.
  148. *
  149. * @inheritdoc
  150. * @returns {ReactElement}
  151. */
  152. render() {
  153. return (
  154. <div className = 'connection-indicator-container'>
  155. <AKInlineDialog
  156. content = { this._renderStatisticsTable() }
  157. isOpen = { this.state.showStats }
  158. onClose = { this._onStatsClose }
  159. position = { this.props.statsPopoverPosition }>
  160. <div
  161. className = 'popover-trigger'
  162. onClick = { this._onStatsToggle }>
  163. <div className = 'connection-indicator indicator'>
  164. <div className = 'connection indicatoricon'>
  165. { this._renderIcon() }
  166. </div>
  167. </div>
  168. </div>
  169. </AKInlineDialog>
  170. </div>
  171. );
  172. }
  173. /**
  174. * Sets the state not to show the Statistics Table popover.
  175. *
  176. * @private
  177. * @returns {void}
  178. */
  179. _onStatsClose() {
  180. this.setState({ showStats: false });
  181. }
  182. /**
  183. * Sets the state to show or hide the Statistics Table popover.
  184. *
  185. * @private
  186. * @returns {void}
  187. */
  188. _onStatsToggle() {
  189. if (this.props.enableStatsDisplay) {
  190. this.setState({ showStats: !this.state.showStats });
  191. }
  192. }
  193. /**
  194. * Callback invoked when new connection stats associated with the passed in
  195. * user ID are available. Will update the component's display of current
  196. * statistics.
  197. *
  198. * @param {Object} stats - Connection stats from the library.
  199. * @private
  200. * @returns {void}
  201. */
  202. _onStatsUpdated(stats = {}) {
  203. const { connectionQuality } = stats;
  204. const newPercentageState = typeof connectionQuality === 'undefined'
  205. ? {} : { percent: connectionQuality };
  206. const newStats = Object.assign(
  207. {},
  208. this.state.stats,
  209. stats,
  210. newPercentageState);
  211. this.setState({
  212. stats: newStats
  213. });
  214. }
  215. /**
  216. * Callback to invoke when the show more link in the popover content is
  217. * clicked. Sets the state which will determine if the popover should show
  218. * additional statistics about the connection.
  219. *
  220. * @returns {void}
  221. */
  222. _onToggleShowMore() {
  223. this.setState({ showMoreStats: !this.state.showMoreStats });
  224. }
  225. /**
  226. * Creates a ReactElement for displaying an icon that represents the current
  227. * connection quality.
  228. *
  229. * @returns {ReactElement}
  230. */
  231. _renderIcon() {
  232. switch (this.props.connectionStatus) {
  233. case JitsiParticipantConnectionStatus.INTERRUPTED:
  234. return (
  235. <span className = 'connection_lost'>
  236. <i className = 'icon-connection-lost' />
  237. </span>
  238. );
  239. case JitsiParticipantConnectionStatus.INACTIVE:
  240. return (
  241. <span className = 'connection_ninja'>
  242. <i className = 'icon-ninja' />
  243. </span>
  244. );
  245. default: {
  246. const { percent } = this.state.stats;
  247. const width = QUALITY_TO_WIDTH.find(x => percent >= x.percent);
  248. const iconWidth = width && width.width
  249. ? { width: width && width.width } : {};
  250. return [
  251. <span
  252. className = 'connection_empty'
  253. key = 'icon-empty'>
  254. <i className = 'icon-connection' />
  255. </span>,
  256. <span
  257. className = 'connection_full'
  258. key = 'icon-full'
  259. style = { iconWidth }>
  260. <i className = 'icon-connection' />
  261. </span>
  262. ];
  263. }
  264. }
  265. }
  266. /**
  267. * Creates a {@code ConnectionStatisticsTable} instance.
  268. *
  269. * @returns {ReactElement}
  270. */
  271. _renderStatisticsTable() {
  272. const {
  273. bandwidth,
  274. bitrate,
  275. framerate,
  276. packetLoss,
  277. resolution,
  278. transport
  279. } = this.state.stats;
  280. return (
  281. <ConnectionStatsTable
  282. bandwidth = { bandwidth }
  283. bitrate = { bitrate }
  284. framerate = { framerate }
  285. isLocalVideo = { this.props.isLocalVideo }
  286. onShowMore = { this._onToggleShowMore }
  287. packetLoss = { packetLoss }
  288. resolution = { resolution }
  289. shouldShowMore = { this.state.showMoreStats }
  290. transport = { transport } />
  291. );
  292. }
  293. }
  294. export default ConnectionIndicator;