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.3KB

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