Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

mediaDeviceHelper.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. /* global APP, JitsiMeetJS */
  2. import {
  3. notifyCameraError,
  4. notifyMicError
  5. } from '../../react/features/base/devices/actions.web';
  6. import {
  7. getAudioOutputDeviceId
  8. } from '../../react/features/base/devices/functions.web';
  9. import { updateSettings } from '../../react/features/base/settings/actions';
  10. import {
  11. getUserSelectedCameraDeviceId,
  12. getUserSelectedMicDeviceId,
  13. getUserSelectedOutputDeviceId
  14. } from '../../react/features/base/settings/functions';
  15. /**
  16. * Determines if currently selected audio output device should be changed after
  17. * list of available devices has been changed.
  18. * @param {MediaDeviceInfo[]} newDevices
  19. * @returns {string|undefined} - ID of new audio output device to use, undefined
  20. * if audio output device should not be changed.
  21. */
  22. function getNewAudioOutputDevice(newDevices) {
  23. if (!JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
  24. return;
  25. }
  26. const selectedAudioOutputDeviceId = getAudioOutputDeviceId();
  27. const availableAudioOutputDevices = newDevices.filter(
  28. d => d.kind === 'audiooutput');
  29. // Switch to 'default' audio output device if we don't have the selected one
  30. // available anymore.
  31. if (selectedAudioOutputDeviceId !== 'default'
  32. && !availableAudioOutputDevices.find(d =>
  33. d.deviceId === selectedAudioOutputDeviceId)) {
  34. return 'default';
  35. }
  36. const preferredAudioOutputDeviceId = getUserSelectedOutputDeviceId(APP.store.getState());
  37. // if the preferred one is not the selected and is available in the new devices
  38. // we want to use it as it was just added
  39. if (preferredAudioOutputDeviceId
  40. && preferredAudioOutputDeviceId !== selectedAudioOutputDeviceId
  41. && availableAudioOutputDevices.find(d => d.deviceId === preferredAudioOutputDeviceId)) {
  42. return preferredAudioOutputDeviceId;
  43. }
  44. }
  45. /**
  46. * Determines if currently selected audio input device should be changed after
  47. * list of available devices has been changed.
  48. * @param {MediaDeviceInfo[]} newDevices
  49. * @param {JitsiLocalTrack} localAudio
  50. * @param {boolean} newLabel
  51. * @returns {string|undefined} - ID of new microphone device to use, undefined
  52. * if audio input device should not be changed.
  53. */
  54. function getNewAudioInputDevice(newDevices, localAudio, newLabel) {
  55. const availableAudioInputDevices = newDevices.filter(
  56. d => d.kind === 'audioinput');
  57. const selectedAudioInputDeviceId = getUserSelectedMicDeviceId(APP.store.getState());
  58. const selectedAudioInputDevice = availableAudioInputDevices.find(
  59. d => d.deviceId === selectedAudioInputDeviceId);
  60. const localAudioDeviceId = localAudio?.getDeviceId();
  61. const localAudioDevice = availableAudioInputDevices.find(
  62. d => d.deviceId === localAudioDeviceId);
  63. // Here we handle case when no device was initially plugged, but
  64. // then it's connected OR new device was connected when previous
  65. // track has ended.
  66. if (!localAudio || localAudio.disposed || localAudio.isEnded()) {
  67. // If we have new audio device and permission to use it was granted
  68. // (label is not an empty string), then we will try to use the first
  69. // available device.
  70. if (selectedAudioInputDevice && selectedAudioInputDeviceId) {
  71. return selectedAudioInputDeviceId;
  72. } else if (availableAudioInputDevices.length
  73. && availableAudioInputDevices[0].label !== '') {
  74. return availableAudioInputDevices[0].deviceId;
  75. }
  76. } else if (selectedAudioInputDevice
  77. && selectedAudioInputDeviceId !== localAudioDeviceId) {
  78. if (newLabel) {
  79. // If a Firefox user with manual permission prompt chose a different
  80. // device from what we have stored as the preferred device we accept
  81. // and store that as the new preferred device.
  82. APP.store.dispatch(updateSettings({
  83. userSelectedMicDeviceId: localAudioDeviceId,
  84. userSelectedMicDeviceLabel: localAudioDevice.label
  85. }));
  86. } else {
  87. // And here we handle case when we already have some device working,
  88. // but we plug-in a "preferred" (previously selected in settings, stored
  89. // in local storage) device.
  90. return selectedAudioInputDeviceId;
  91. }
  92. }
  93. }
  94. /**
  95. * Determines if currently selected video input device should be changed after
  96. * list of available devices has been changed.
  97. * @param {MediaDeviceInfo[]} newDevices
  98. * @param {JitsiLocalTrack} localVideo
  99. * @param {boolean} newLabel
  100. * @returns {string|undefined} - ID of new camera device to use, undefined
  101. * if video input device should not be changed.
  102. */
  103. function getNewVideoInputDevice(newDevices, localVideo, newLabel) {
  104. const availableVideoInputDevices = newDevices.filter(
  105. d => d.kind === 'videoinput');
  106. const selectedVideoInputDeviceId = getUserSelectedCameraDeviceId(APP.store.getState());
  107. const selectedVideoInputDevice = availableVideoInputDevices.find(
  108. d => d.deviceId === selectedVideoInputDeviceId);
  109. const localVideoDeviceId = localVideo?.getDeviceId();
  110. const localVideoDevice = availableVideoInputDevices.find(
  111. d => d.deviceId === localVideoDeviceId);
  112. // Here we handle case when no video input device was initially plugged,
  113. // but then device is connected OR new device was connected when
  114. // previous track has ended.
  115. if (!localVideo || localVideo.disposed || localVideo.isEnded()) {
  116. // If we have new video device and permission to use it was granted
  117. // (label is not an empty string), then we will try to use the first
  118. // available device.
  119. if (selectedVideoInputDevice && selectedVideoInputDeviceId) {
  120. return selectedVideoInputDeviceId;
  121. } else if (availableVideoInputDevices.length
  122. && availableVideoInputDevices[0].label !== '') {
  123. return availableVideoInputDevices[0].deviceId;
  124. }
  125. } else if (selectedVideoInputDevice
  126. && selectedVideoInputDeviceId !== localVideoDeviceId) {
  127. if (newLabel) {
  128. // If a Firefox user with manual permission prompt chose a different
  129. // device from what we have stored as the preferred device we accept
  130. // and store that as the new preferred device.
  131. APP.store.dispatch(updateSettings({
  132. userSelectedCameraDeviceId: localVideoDeviceId,
  133. userSelectedCameraDeviceLabel: localVideoDevice.label
  134. }));
  135. } else {
  136. // And here we handle case when we already have some device working,
  137. // but we plug-in a "preferred" (previously selected in settings, stored
  138. // in local storage) device.
  139. return selectedVideoInputDeviceId;
  140. }
  141. }
  142. }
  143. export default {
  144. /**
  145. * Determines if currently selected media devices should be changed after
  146. * list of available devices has been changed.
  147. * @param {MediaDeviceInfo[]} newDevices
  148. * @param {JitsiLocalTrack} localVideo
  149. * @param {JitsiLocalTrack} localAudio
  150. * @returns {{
  151. * audioinput: (string|undefined),
  152. * videoinput: (string|undefined),
  153. * audiooutput: (string|undefined)
  154. * }}
  155. */
  156. getNewMediaDevicesAfterDeviceListChanged( // eslint-disable-line max-params
  157. newDevices,
  158. localVideo,
  159. localAudio,
  160. newLabels) {
  161. return {
  162. audioinput: getNewAudioInputDevice(newDevices, localAudio, newLabels),
  163. videoinput: getNewVideoInputDevice(newDevices, localVideo, newLabels),
  164. audiooutput: getNewAudioOutputDevice(newDevices)
  165. };
  166. },
  167. /**
  168. * Checks if the only difference between an object of known devices compared
  169. * to an array of new devices are only the labels for the devices.
  170. * @param {Object} oldDevices
  171. * @param {MediaDeviceInfo[]} newDevices
  172. * @returns {boolean}
  173. */
  174. newDeviceListAddedLabelsOnly(oldDevices, newDevices) {
  175. const oldDevicesFlattend = oldDevices.audioInput.concat(oldDevices.audioOutput).concat(oldDevices.videoInput);
  176. if (oldDevicesFlattend.length !== newDevices.length) {
  177. return false;
  178. }
  179. oldDevicesFlattend.forEach(oldDevice => {
  180. if (oldDevice.label !== '') {
  181. return false;
  182. }
  183. const newDevice = newDevices.find(nd => nd.deviceId === oldDevice.deviceId);
  184. if (!newDevice || newDevice.label === '') {
  185. return false;
  186. }
  187. });
  188. return true;
  189. },
  190. /**
  191. * Tries to create new local tracks for new devices obtained after device
  192. * list changed. Shows error dialog in case of failures.
  193. * @param {function} createLocalTracks
  194. * @param {string} (cameraDeviceId)
  195. * @param {string} (micDeviceId)
  196. * @returns {Promise.<JitsiLocalTrack[]>}
  197. */
  198. createLocalTracksAfterDeviceListChanged(
  199. createLocalTracks,
  200. cameraDeviceId,
  201. micDeviceId) {
  202. let audioTrackError;
  203. let videoTrackError;
  204. const audioRequested = Boolean(micDeviceId);
  205. const videoRequested = Boolean(cameraDeviceId);
  206. if (audioRequested && videoRequested) {
  207. // First we try to create both audio and video tracks together.
  208. return (
  209. createLocalTracks({
  210. devices: [ 'audio', 'video' ],
  211. cameraDeviceId,
  212. micDeviceId
  213. })
  214. // If we fail to do this, try to create them separately.
  215. .catch(() => Promise.all([
  216. createAudioTrack(false).then(([ stream ]) => stream),
  217. createVideoTrack(false).then(([ stream ]) => stream)
  218. ]))
  219. .then(tracks => {
  220. if (audioTrackError) {
  221. APP.store.dispatch(notifyMicError(audioTrackError));
  222. }
  223. if (videoTrackError) {
  224. APP.store.dispatch(notifyCameraError(videoTrackError));
  225. }
  226. return tracks.filter(t => typeof t !== 'undefined');
  227. }));
  228. } else if (videoRequested && !audioRequested) {
  229. return createVideoTrack();
  230. } else if (audioRequested && !videoRequested) {
  231. return createAudioTrack();
  232. }
  233. return Promise.resolve([]);
  234. /**
  235. *
  236. */
  237. function createAudioTrack(showError = true) {
  238. return (
  239. createLocalTracks({
  240. devices: [ 'audio' ],
  241. cameraDeviceId: null,
  242. micDeviceId
  243. })
  244. .catch(err => {
  245. audioTrackError = err;
  246. showError && APP.store.dispatch(notifyMicError(err));
  247. return [];
  248. }));
  249. }
  250. /**
  251. *
  252. */
  253. function createVideoTrack(showError = true) {
  254. return (
  255. createLocalTracks({
  256. devices: [ 'video' ],
  257. cameraDeviceId,
  258. micDeviceId: null
  259. })
  260. .catch(err => {
  261. videoTrackError = err;
  262. showError && APP.store.dispatch(notifyCameraError(err));
  263. return [];
  264. }));
  265. }
  266. }
  267. };