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 9.4KB

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