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.

AlwaysOnTop.js 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. // @flow
  2. import React, { Component } from 'react';
  3. import ToolboxAlwaysOnTop from './ToolboxAlwaysOnTop';
  4. const { api } = window.alwaysOnTop;
  5. /**
  6. * The timeout in ms for hidding the toolbar.
  7. */
  8. const TOOLBAR_TIMEOUT = 4000;
  9. /**
  10. * The type of the React {@code Component} state of {@link FeedbackButton}.
  11. */
  12. type State = {
  13. audioAvailable: boolean,
  14. audioMuted: boolean,
  15. avatarURL: string,
  16. displayName: string,
  17. isVideoDisplayed: boolean,
  18. videoAvailable: boolean,
  19. videoMuted: boolean,
  20. visible: boolean
  21. };
  22. /**
  23. * Represents the always on top page.
  24. *
  25. * @class AlwaysOnTop
  26. * @extends Component
  27. */
  28. export default class AlwaysOnTop extends Component<*, State> {
  29. _hovered: boolean;
  30. /**
  31. * Initializes new AlwaysOnTop instance.
  32. *
  33. * @param {*} props - The read-only properties with which the new instance
  34. * is to be initialized.
  35. */
  36. constructor(props: *) {
  37. super(props);
  38. this.state = {
  39. visible: true,
  40. audioMuted: false,
  41. videoMuted: false,
  42. audioAvailable: false,
  43. videoAvailable: false,
  44. displayName: '',
  45. isVideoDisplayed: true,
  46. avatarURL: ''
  47. };
  48. // Bind event handlers so they are only bound once per instance.
  49. this._audioAvailabilityListener
  50. = this._audioAvailabilityListener.bind(this);
  51. this._audioMutedListener = this._audioMutedListener.bind(this);
  52. this._avatarChangedListener = this._avatarChangedListener.bind(this);
  53. this._largeVideoChangedListener
  54. = this._largeVideoChangedListener.bind(this);
  55. this._displayNameChangedListener
  56. = this._displayNameChangedListener.bind(this);
  57. this._mouseMove = this._mouseMove.bind(this);
  58. this._onMouseOut = this._onMouseOut.bind(this);
  59. this._onMouseOver = this._onMouseOver.bind(this);
  60. this._videoAvailabilityListener
  61. = this._videoAvailabilityListener.bind(this);
  62. this._videoMutedListener = this._videoMutedListener.bind(this);
  63. }
  64. _audioAvailabilityListener: ({ available: boolean }) => void;
  65. /**
  66. * Handles audio available api events.
  67. *
  68. * @param {{ available: boolean }} status - The new available status.
  69. * @returns {void}
  70. */
  71. _audioAvailabilityListener({ available }) {
  72. this.setState({ audioAvailable: available });
  73. }
  74. _audioMutedListener: ({ muted: boolean }) => void;
  75. /**
  76. * Handles audio muted api events.
  77. *
  78. * @param {{ muted: boolean }} status - The new muted status.
  79. * @returns {void}
  80. */
  81. _audioMutedListener({ muted }) {
  82. this.setState({ audioMuted: muted });
  83. }
  84. _avatarChangedListener: () => void;
  85. /**
  86. * Handles avatar changed api events.
  87. *
  88. * @returns {void}
  89. */
  90. _avatarChangedListener({ avatarURL, id }) {
  91. if (api._getOnStageParticipant() !== id) {
  92. return;
  93. }
  94. if (avatarURL !== this.state.avatarURL) {
  95. this.setState({ avatarURL });
  96. }
  97. }
  98. _displayNameChangedListener: () => void;
  99. /**
  100. * Handles display name changed api events.
  101. *
  102. * @returns {void}
  103. */
  104. _displayNameChangedListener({ formattedDisplayName, id }) {
  105. if (api._getOnStageParticipant() !== id) {
  106. return;
  107. }
  108. if (formattedDisplayName !== this.state.displayName) {
  109. this.setState({ displayName: formattedDisplayName });
  110. }
  111. }
  112. /**
  113. * Hides the toolbar after a timeout.
  114. *
  115. * @returns {void}
  116. */
  117. _hideToolbarAfterTimeout() {
  118. setTimeout(() => {
  119. if (this._hovered) {
  120. this._hideToolbarAfterTimeout();
  121. return;
  122. }
  123. this.setState({ visible: false });
  124. }, TOOLBAR_TIMEOUT);
  125. }
  126. _largeVideoChangedListener: () => void;
  127. /**
  128. * Handles large video changed api events.
  129. *
  130. * @returns {void}
  131. */
  132. _largeVideoChangedListener() {
  133. const userID = api._getOnStageParticipant();
  134. const displayName = api._getFormattedDisplayName(userID);
  135. const avatarURL = api.getAvatarURL(userID);
  136. const isVideoDisplayed = Boolean(api._getLargeVideo());
  137. this.setState({
  138. avatarURL,
  139. displayName,
  140. isVideoDisplayed
  141. });
  142. }
  143. _mouseMove: () => void;
  144. /**
  145. * Handles mouse move events.
  146. *
  147. * @returns {void}
  148. */
  149. _mouseMove() {
  150. if (!this.state.visible) {
  151. this.setState({ visible: true });
  152. }
  153. }
  154. _onMouseOut: () => void;
  155. /**
  156. * Toolbar mouse out handler.
  157. *
  158. * @returns {void}
  159. */
  160. _onMouseOut() {
  161. this._hovered = false;
  162. }
  163. _onMouseOver: () => void;
  164. /**
  165. * Toolbar mouse over handler.
  166. *
  167. * @returns {void}
  168. */
  169. _onMouseOver() {
  170. this._hovered = true;
  171. }
  172. _videoAvailabilityListener: ({ available: boolean }) => void;
  173. /**
  174. * Renders display name and avatar for the on stage participant.
  175. *
  176. * @returns {ReactElement}
  177. */
  178. _renderVideoNotAvailableScreen() {
  179. const { avatarURL, displayName, isVideoDisplayed } = this.state;
  180. if (isVideoDisplayed) {
  181. return null;
  182. }
  183. return (
  184. <div id = 'videoNotAvailableScreen'>
  185. {
  186. avatarURL
  187. ? <div id = 'avatarContainer'>
  188. <img
  189. id = 'avatar'
  190. src = { avatarURL } />
  191. </div>
  192. : null
  193. }
  194. <div
  195. className = 'displayname'
  196. id = 'displayname'>
  197. { displayName }
  198. </div>
  199. </div>
  200. );
  201. }
  202. /**
  203. * Handles audio available api events.
  204. *
  205. * @param {{ available: boolean }} status - The new available status.
  206. * @returns {void}
  207. */
  208. _videoAvailabilityListener({ available }) {
  209. this.setState({ videoAvailable: available });
  210. }
  211. _videoMutedListener: ({ muted: boolean }) => void;
  212. /**
  213. * Handles video muted api events.
  214. *
  215. * @param {{ muted: boolean }} status - The new muted status.
  216. * @returns {void}
  217. */
  218. _videoMutedListener({ muted }) {
  219. this.setState({ videoMuted: muted });
  220. }
  221. /**
  222. * Sets mouse move listener and initial toolbar timeout.
  223. *
  224. * @inheritdoc
  225. * @returns {void}
  226. */
  227. componentDidMount() {
  228. api.on('audioMuteStatusChanged', this._audioMutedListener);
  229. api.on('videoMuteStatusChanged', this._videoMutedListener);
  230. api.on('audioAvailabilityChanged', this._audioAvailabilityListener);
  231. api.on('videoAvailabilityChanged', this._videoAvailabilityListener);
  232. api.on('largeVideoChanged', this._largeVideoChangedListener);
  233. api.on('displayNameChange', this._displayNameChangedListener);
  234. api.on('avatarChanged', this._avatarChangedListener);
  235. this._largeVideoChangedListener();
  236. Promise.all([
  237. api.isAudioMuted(),
  238. api.isVideoMuted(),
  239. api.isAudioAvailable(),
  240. api.isVideoAvailable()
  241. ])
  242. .then(([
  243. audioMuted = false,
  244. videoMuted = false,
  245. audioAvailable = false,
  246. videoAvailable = false
  247. ]) =>
  248. this.setState({
  249. audioMuted,
  250. videoMuted,
  251. audioAvailable,
  252. videoAvailable
  253. })
  254. )
  255. .catch(console.error);
  256. window.addEventListener('mousemove', this._mouseMove);
  257. this._hideToolbarAfterTimeout();
  258. }
  259. /**
  260. * Removes all listeners.
  261. *
  262. * @inheritdoc
  263. * @returns {void}
  264. */
  265. componentWillUnmount() {
  266. api.removeListener('audioMuteStatusChanged',
  267. this._audioMutedListener);
  268. api.removeListener('videoMuteStatusChanged',
  269. this._videoMutedListener);
  270. api.removeListener('audioAvailabilityChanged',
  271. this._audioAvailabilityListener);
  272. api.removeListener('videoAvailabilityChanged',
  273. this._videoAvailabilityListener);
  274. api.removeListener('largeVideoChanged',
  275. this._largeVideoChangedListener);
  276. api.removeListener('displayNameChange',
  277. this._displayNameChangedListener);
  278. api.removeListener('avatarChanged', this._avatarChangedListener);
  279. window.removeEventListener('mousemove', this._mouseMove);
  280. }
  281. /**
  282. * Sets a timeout to hide the toolbar when the toolbar is shown.
  283. *
  284. * @inheritdoc
  285. * @returns {void}
  286. */
  287. componentWillUpdate(nextProps: *, nextState: State) {
  288. if (!this.state.visible && nextState.visible) {
  289. this._hideToolbarAfterTimeout();
  290. }
  291. }
  292. /**
  293. * Implements React's {@link Component#render()}.
  294. *
  295. * @inheritdoc
  296. * @returns {ReactElement}
  297. */
  298. render() {
  299. return (
  300. <div id = 'alwaysOnTop'>
  301. <ToolboxAlwaysOnTop
  302. audioAvailable = { this.state.audioAvailable }
  303. audioMuted = { this.state.audioMuted }
  304. className = { this.state.visible ? 'fadeIn' : 'fadeOut' }
  305. onMouseOut = { this._onMouseOut }
  306. onMouseOver = { this._onMouseOver }
  307. videoAvailable = { this.state.videoAvailable }
  308. videoMuted = { this.state.videoMuted } />
  309. {
  310. this._renderVideoNotAvailableScreen()
  311. }
  312. </div>
  313. );
  314. }
  315. }