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.

DeviceSelectionPopup.js 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. /* global JitsiMeetJS */
  2. import { AtlasKitThemeProvider } from '@atlaskit/theme';
  3. import React from 'react';
  4. import ReactDOM from 'react-dom';
  5. import { I18nextProvider } from 'react-i18next';
  6. import {
  7. getAvailableDevices,
  8. getCurrentDevices,
  9. isDeviceChangeAvailable,
  10. isDeviceListAvailable,
  11. isMultipleAudioInputSupported,
  12. setAudioInputDevice,
  13. setAudioOutputDevice,
  14. setVideoInputDevice
  15. } from '../../../../../modules/API/external/functions';
  16. import {
  17. PostMessageTransportBackend,
  18. Transport
  19. } from '../../../../../modules/transport';
  20. import DialogWithTabs from '../../../base/dialog/components/web/DialogWithTabs';
  21. import { parseURLParams } from '../../../base/util/parseURLParams';
  22. import DeviceSelection from '../../../device-selection/components/DeviceSelection';
  23. /**
  24. * Implements a class that renders the React components for the device selection
  25. * popup page and handles the communication between the components and Jitsi
  26. * Meet.
  27. */
  28. export default class DeviceSelectionPopup {
  29. /**
  30. * Initializes a new DeviceSelectionPopup instance.
  31. *
  32. * @param {Object} i18next - The i18next instance used for translation.
  33. */
  34. constructor(i18next) {
  35. this.close = this.close.bind(this);
  36. this._i18next = i18next;
  37. this._onSubmit = this._onSubmit.bind(this);
  38. const { scope } = parseURLParams(window.location);
  39. this._transport = new Transport({
  40. backend: new PostMessageTransportBackend({
  41. postisOptions: {
  42. scope,
  43. window: window.opener
  44. }
  45. })
  46. });
  47. this._transport.on('event', event => {
  48. if (event.name === 'deviceListChanged') {
  49. this._updateAvailableDevices();
  50. return true;
  51. }
  52. return false;
  53. });
  54. this._dialogProps = {
  55. availableDevices: {},
  56. selectedAudioInputId: '',
  57. selectedAudioOutputId: '',
  58. selectedVideoInputId: '',
  59. disableAudioInputChange: true,
  60. disableBlanketClickDismiss: true,
  61. disableDeviceChange: true,
  62. hideAudioInputPreview: !JitsiMeetJS.isCollectingLocalStats(),
  63. hideAudioOutputSelect: true
  64. };
  65. this._initState();
  66. }
  67. /**
  68. * Sends event to Jitsi Meet to close the popup dialog.
  69. *
  70. * @returns {void}
  71. */
  72. close() {
  73. this._transport.sendEvent({
  74. type: 'devices-dialog',
  75. name: 'close'
  76. });
  77. }
  78. /**
  79. * Changes the properties of the react component and re-renders it.
  80. *
  81. * @param {Object} newProps - The new properties that will be assigned to
  82. * the current ones.
  83. * @returns {void}
  84. */
  85. _changeDialogProps(newProps) {
  86. this._dialogProps = {
  87. ...this._dialogProps,
  88. ...newProps
  89. };
  90. this._render();
  91. }
  92. /**
  93. * Returns Promise that resolves with result an list of available devices.
  94. *
  95. * @returns {Promise}
  96. */
  97. _getAvailableDevices() {
  98. return getAvailableDevices(this._transport);
  99. }
  100. /**
  101. * Returns Promise that resolves with current selected devices.
  102. *
  103. * @returns {Promise}
  104. */
  105. _getCurrentDevices() {
  106. return getCurrentDevices(this._transport).then(currentDevices => {
  107. const {
  108. audioInput = {},
  109. audioOutput = {},
  110. videoInput = {}
  111. } = currentDevices;
  112. return {
  113. audioInput: audioInput.deviceId,
  114. audioOutput: audioOutput.deviceId,
  115. videoInput: videoInput.deviceId
  116. };
  117. });
  118. }
  119. /**
  120. * Initializes the state.
  121. *
  122. * @returns {void}
  123. */
  124. _initState() {
  125. return Promise.all([
  126. this._getAvailableDevices(),
  127. this._isDeviceListAvailable(),
  128. this._isDeviceChangeAvailable(),
  129. this._isDeviceChangeAvailable('output'),
  130. this._getCurrentDevices(),
  131. this._isMultipleAudioInputSupported()
  132. ]).then(([
  133. availableDevices,
  134. listAvailable,
  135. changeAvailable,
  136. changeOutputAvailable,
  137. currentDevices,
  138. multiAudioInputSupported
  139. ]) => {
  140. this._changeDialogProps({
  141. availableDevices,
  142. selectedAudioInputId: currentDevices.audioInput,
  143. selectedAudioOutputId: currentDevices.audioOutput,
  144. selectedVideoInputId: currentDevices.videoInput,
  145. disableAudioInputChange: !multiAudioInputSupported,
  146. disableDeviceChange: !listAvailable || !changeAvailable,
  147. hideAudioOutputSelect: !changeOutputAvailable
  148. });
  149. });
  150. }
  151. /**
  152. * Returns Promise that resolves with true if the device change is available
  153. * and with false if not.
  154. *
  155. * @param {string} [deviceType] - Values - 'output', 'input' or undefined.
  156. * Default - 'input'.
  157. * @returns {Promise}
  158. */
  159. _isDeviceChangeAvailable(deviceType) {
  160. return isDeviceChangeAvailable(this._transport, deviceType).catch(() => false);
  161. }
  162. /**
  163. * Returns Promise that resolves with true if the device list is available
  164. * and with false if not.
  165. *
  166. * @returns {Promise}
  167. */
  168. _isDeviceListAvailable() {
  169. return isDeviceListAvailable(this._transport).catch(() => false);
  170. }
  171. /**
  172. * Returns Promise that resolves with true if multiple audio input is supported
  173. * and with false if not.
  174. *
  175. * @returns {Promise}
  176. */
  177. _isMultipleAudioInputSupported() {
  178. return isMultipleAudioInputSupported(this._transport).catch(() => false);
  179. }
  180. /**
  181. * Callback invoked to save changes to selected devices and close the
  182. * dialog.
  183. *
  184. * @param {Object} newSettings - The chosen device IDs.
  185. * @private
  186. * @returns {void}
  187. */
  188. _onSubmit(newSettings) {
  189. const promises = [];
  190. if (newSettings.selectedVideoInputId
  191. !== this._dialogProps.selectedVideoInputId) {
  192. promises.push(
  193. this._setVideoInputDevice(newSettings.selectedVideoInputId));
  194. }
  195. if (newSettings.selectedAudioInputId
  196. !== this._dialogProps.selectedAudioInputId) {
  197. promises.push(
  198. this._setAudioInputDevice(newSettings.selectedAudioInputId));
  199. }
  200. if (newSettings.selectedAudioOutputId
  201. !== this._dialogProps.selectedAudioOutputId) {
  202. promises.push(
  203. this._setAudioOutputDevice(newSettings.selectedAudioOutputId));
  204. }
  205. Promise.all(promises).then(this.close, this.close);
  206. }
  207. /**
  208. * Renders the React components for the popup page.
  209. *
  210. * @returns {void}
  211. */
  212. _render() {
  213. const onSubmit = this.close;
  214. ReactDOM.render(
  215. <I18nextProvider i18n = { this._i18next }>
  216. <AtlasKitThemeProvider mode = 'dark'>
  217. <DialogWithTabs
  218. closeDialog = { this.close }
  219. cssClassName = 'settings-dialog'
  220. onSubmit = { onSubmit }
  221. tabs = { [ {
  222. component: DeviceSelection,
  223. label: 'settings.devices',
  224. props: this._dialogProps,
  225. submit: this._onSubmit
  226. } ] }
  227. titleKey = 'settings.title' />
  228. </AtlasKitThemeProvider>
  229. </I18nextProvider>,
  230. document.getElementById('react'));
  231. }
  232. /**
  233. * Sets the audio input device to the one with the id that is passed.
  234. *
  235. * @param {string} id - The id of the new device.
  236. * @returns {Promise}
  237. */
  238. _setAudioInputDevice(id) {
  239. return setAudioInputDevice(this._transport, undefined, id);
  240. }
  241. /**
  242. * Sets the audio output device to the one with the id that is passed.
  243. *
  244. * @param {string} id - The id of the new device.
  245. * @returns {Promise}
  246. */
  247. _setAudioOutputDevice(id) {
  248. return setAudioOutputDevice(this._transport, undefined, id);
  249. }
  250. /**
  251. * Sets the video input device to the one with the id that is passed.
  252. *
  253. * @param {string} id - The id of the new device.
  254. * @returns {Promise}
  255. */
  256. _setVideoInputDevice(id) {
  257. return setVideoInputDevice(this._transport, undefined, id);
  258. }
  259. /**
  260. * Updates the available devices.
  261. *
  262. * @returns {void}
  263. */
  264. _updateAvailableDevices() {
  265. this._getAvailableDevices().then(devices =>
  266. this._changeDialogProps({ availableDevices: devices })
  267. );
  268. }
  269. }