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

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