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.

mediaDeviceHelper.js 8.9KB

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