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.

actions.web.ts 10KB

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