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

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