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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. import { IStore } from '../../app/types';
  2. import JitsiMeetJS from '../lib-jitsi-meet';
  3. import { updateSettings } from '../settings/actions';
  4. import { getUserSelectedOutputDeviceId } from '../settings/functions.web';
  5. import {
  6. ADD_PENDING_DEVICE_REQUEST,
  7. CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
  8. DEVICE_PERMISSIONS_CHANGED,
  9. NOTIFY_CAMERA_ERROR,
  10. NOTIFY_MIC_ERROR,
  11. REMOVE_PENDING_DEVICE_REQUESTS,
  12. SET_AUDIO_INPUT_DEVICE,
  13. SET_VIDEO_INPUT_DEVICE,
  14. UPDATE_DEVICE_LIST
  15. } from './actionTypes';
  16. import {
  17. areDeviceLabelsInitialized,
  18. areDevicesDifferent,
  19. filterIgnoredDevices,
  20. flattenAvailableDevices,
  21. getDeviceIdByLabel,
  22. getDeviceLabelById,
  23. getDevicesFromURL,
  24. logDevices,
  25. setAudioOutputDeviceId
  26. } from './functions';
  27. import logger from './logger';
  28. /**
  29. * Maps the WebRTC string for device type to the keys used to store configure,
  30. * within redux, which devices should be used by default.
  31. */
  32. const DEVICE_TYPE_TO_SETTINGS_KEYS = {
  33. audioInput: {
  34. currentDeviceId: 'micDeviceId',
  35. userSelectedDeviceId: 'userSelectedMicDeviceId',
  36. userSelectedDeviceLabel: 'userSelectedMicDeviceLabel'
  37. },
  38. audioOutput: {
  39. currentDeviceId: 'audioOutputDeviceId',
  40. userSelectedDeviceId: 'userSelectedAudioOutputDeviceId',
  41. userSelectedDeviceLabel: 'userSelectedAudioOutputDeviceLabel'
  42. },
  43. videoInput: {
  44. currentDeviceId: 'audioOutputDeviceId',
  45. userSelectedDeviceId: 'userSelectedCameraDeviceId',
  46. userSelectedDeviceLabel: 'userSelectedCameraDeviceLabel'
  47. }
  48. };
  49. /**
  50. * Adds a pending device request.
  51. *
  52. * @param {Object} request - The request to be added.
  53. * @returns {{
  54. * type: ADD_PENDING_DEVICE_REQUEST,
  55. * request: Object
  56. * }}
  57. */
  58. export function addPendingDeviceRequest(request: Object) {
  59. return {
  60. type: ADD_PENDING_DEVICE_REQUEST,
  61. request
  62. };
  63. }
  64. /**
  65. * Configures the initial A/V devices before the conference has started.
  66. *
  67. * @returns {Function}
  68. */
  69. export function configureInitialDevices() {
  70. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  71. const deviceLabels = getDevicesFromURL(getState());
  72. let updateSettingsPromise;
  73. if (deviceLabels) {
  74. updateSettingsPromise = dispatch(getAvailableDevices()).then(() => {
  75. const state = getState();
  76. if (!areDeviceLabelsInitialized(state)) {
  77. // The labels are not available if the A/V permissions are
  78. // not yet granted.
  79. Object.keys(deviceLabels).forEach(key => {
  80. dispatch(addPendingDeviceRequest({
  81. type: 'devices',
  82. name: 'setDevice',
  83. device: {
  84. kind: key.toLowerCase(),
  85. label: deviceLabels[key as keyof typeof deviceLabels]
  86. },
  87. // eslint-disable-next-line @typescript-eslint/no-empty-function
  88. responseCallback() {}
  89. }));
  90. });
  91. return;
  92. }
  93. const newSettings: any = {};
  94. Object.keys(deviceLabels).forEach(key => {
  95. const label = deviceLabels[key as keyof typeof deviceLabels];
  96. // @ts-ignore
  97. const deviceId = getDeviceIdByLabel(state, label, key);
  98. if (deviceId) {
  99. const settingsTranslationMap = DEVICE_TYPE_TO_SETTINGS_KEYS[
  100. key as keyof typeof DEVICE_TYPE_TO_SETTINGS_KEYS];
  101. newSettings[settingsTranslationMap.currentDeviceId] = deviceId;
  102. newSettings[settingsTranslationMap.userSelectedDeviceId] = deviceId;
  103. newSettings[settingsTranslationMap.userSelectedDeviceLabel] = label;
  104. }
  105. });
  106. dispatch(updateSettings(newSettings));
  107. });
  108. } else {
  109. updateSettingsPromise = Promise.resolve();
  110. }
  111. return updateSettingsPromise
  112. .then(() => {
  113. const userSelectedAudioOutputDeviceId = getUserSelectedOutputDeviceId(getState());
  114. return setAudioOutputDeviceId(userSelectedAudioOutputDeviceId, dispatch)
  115. .catch(ex => logger.warn(`Failed to set audio output device.
  116. Default audio output device will be used instead ${ex}`));
  117. });
  118. };
  119. }
  120. /**
  121. * Queries for connected A/V input and output devices and updates the redux
  122. * state of known devices.
  123. *
  124. * @returns {Function}
  125. */
  126. export function getAvailableDevices() {
  127. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => new Promise(resolve => {
  128. const { mediaDevices } = JitsiMeetJS;
  129. if (mediaDevices.isDeviceListAvailable()
  130. && mediaDevices.isDeviceChangeAvailable()) {
  131. mediaDevices.enumerateDevices((devices: MediaDeviceInfo[]) => {
  132. const { filteredDevices, ignoredDevices } = filterIgnoredDevices(devices);
  133. const oldDevices = flattenAvailableDevices(getState()['features/base/devices'].availableDevices);
  134. if (areDevicesDifferent(oldDevices, filteredDevices)) {
  135. logDevices(ignoredDevices, 'Ignored devices on device list changed:');
  136. dispatch(updateDeviceList(filteredDevices));
  137. }
  138. resolve(filteredDevices);
  139. });
  140. } else {
  141. resolve([]);
  142. }
  143. });
  144. }
  145. /**
  146. * Signals that an error occurred while trying to obtain a track from a camera.
  147. *
  148. * @param {Object} error - The device error, as provided by lib-jitsi-meet.
  149. * @param {string} error.name - The constant for the type of the error.
  150. * @param {string} error.message - Optional additional information about the
  151. * error.
  152. * @returns {{
  153. * type: NOTIFY_CAMERA_ERROR,
  154. * error: Object
  155. * }}
  156. */
  157. export function notifyCameraError(error: Error) {
  158. return {
  159. type: NOTIFY_CAMERA_ERROR,
  160. error
  161. };
  162. }
  163. /**
  164. * Signals that an error occurred while trying to obtain a track from a mic.
  165. *
  166. * @param {Object} error - The device error, as provided by lib-jitsi-meet.
  167. * @param {Object} error.name - The constant for the type of the error.
  168. * @param {string} error.message - Optional additional information about the
  169. * error.
  170. * @returns {{
  171. * type: NOTIFY_MIC_ERROR,
  172. * error: Object
  173. * }}
  174. */
  175. export function notifyMicError(error: Error) {
  176. return {
  177. type: NOTIFY_MIC_ERROR,
  178. error
  179. };
  180. }
  181. /**
  182. * Remove all pending device requests.
  183. *
  184. * @returns {{
  185. * type: REMOVE_PENDING_DEVICE_REQUESTS
  186. * }}
  187. */
  188. export function removePendingDeviceRequests() {
  189. return {
  190. type: REMOVE_PENDING_DEVICE_REQUESTS
  191. };
  192. }
  193. /**
  194. * Signals to update the currently used audio input device.
  195. *
  196. * @param {string} deviceId - The id of the new audio input device.
  197. * @returns {{
  198. * type: SET_AUDIO_INPUT_DEVICE,
  199. * deviceId: string
  200. * }}
  201. */
  202. export function setAudioInputDevice(deviceId: string) {
  203. return {
  204. type: SET_AUDIO_INPUT_DEVICE,
  205. deviceId
  206. };
  207. }
  208. /**
  209. * Sets the audio input device id and updates the settings
  210. * so they are persisted across sessions.
  211. *
  212. * @param {string} deviceId - The id of the new audio input device.
  213. * @returns {Function}
  214. */
  215. export function setAudioInputDeviceAndUpdateSettings(deviceId: string) {
  216. return function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
  217. const deviceLabel = getDeviceLabelById(getState(), deviceId, 'audioInput');
  218. dispatch(setAudioInputDevice(deviceId));
  219. dispatch(updateSettings({
  220. userSelectedMicDeviceId: deviceId,
  221. userSelectedMicDeviceLabel: deviceLabel
  222. }));
  223. };
  224. }
  225. /**
  226. * Updates the output device id.
  227. *
  228. * @param {string} deviceId - The id of the new output device.
  229. * @returns {Function}
  230. */
  231. export function setAudioOutputDevice(deviceId: string) {
  232. return function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
  233. const deviceLabel = getDeviceLabelById(getState(), deviceId, 'audioOutput');
  234. return setAudioOutputDeviceId(deviceId, dispatch, true, deviceLabel);
  235. };
  236. }
  237. /**
  238. * Signals to update the currently used video input device.
  239. *
  240. * @param {string} deviceId - The id of the new video input device.
  241. * @returns {{
  242. * type: SET_VIDEO_INPUT_DEVICE,
  243. * deviceId: string
  244. * }}
  245. */
  246. export function setVideoInputDevice(deviceId: string) {
  247. return {
  248. type: SET_VIDEO_INPUT_DEVICE,
  249. deviceId
  250. };
  251. }
  252. /**
  253. * Sets the video input device id and updates the settings
  254. * so they are persisted across sessions.
  255. *
  256. * @param {string} deviceId - The id of the new video input device.
  257. * @returns {Function}
  258. */
  259. export function setVideoInputDeviceAndUpdateSettings(deviceId: string) {
  260. return function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
  261. const deviceLabel = getDeviceLabelById(getState(), deviceId, 'videoInput');
  262. dispatch(setVideoInputDevice(deviceId));
  263. dispatch(updateSettings({
  264. userSelectedCameraDeviceId: deviceId,
  265. userSelectedCameraDeviceLabel: deviceLabel
  266. }));
  267. };
  268. }
  269. /**
  270. * Signals to update the list of known audio and video devices.
  271. *
  272. * @param {Array<MediaDeviceInfo>} devices - All known available audio input,
  273. * audio output, and video input devices.
  274. * @returns {{
  275. * type: UPDATE_DEVICE_LIST,
  276. * devices: Array<MediaDeviceInfo>
  277. * }}
  278. */
  279. export function updateDeviceList(devices: MediaDeviceInfo[]) {
  280. return {
  281. type: UPDATE_DEVICE_LIST,
  282. devices
  283. };
  284. }
  285. /**
  286. * Signals to check new and old devices for newly added devices and notify.
  287. *
  288. * @param {Array<MediaDeviceInfo>} newDevices - Array of the new devices.
  289. * @param {Array<MediaDeviceInfo>} oldDevices - Array of the old devices.
  290. * @returns {{
  291. * type: CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
  292. * newDevices: Array<MediaDeviceInfo>,
  293. * oldDevices: Array<MediaDeviceInfo>
  294. * }}
  295. */
  296. export function checkAndNotifyForNewDevice(newDevices: MediaDeviceInfo[], oldDevices: MediaDeviceInfo[]) {
  297. return {
  298. type: CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
  299. newDevices,
  300. oldDevices
  301. };
  302. }
  303. /**
  304. * Signals that the device permissions have changed.
  305. *
  306. * @param {Object} permissions - Object with the permissions.
  307. * @returns {{
  308. * type: DEVICE_PERMISSIONS_CHANGED,
  309. * permissions: Object
  310. * }}
  311. */
  312. export function devicePermissionsChanged(permissions: Object) {
  313. return {
  314. type: DEVICE_PERMISSIONS_CHANGED,
  315. permissions
  316. };
  317. }