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

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