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.

JitsiMediaDevices.js 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import EventEmitter from 'events';
  2. import * as JitsiMediaDevicesEvents from './JitsiMediaDevicesEvents';
  3. import RTC from './modules/RTC/RTC';
  4. import browser from './modules/browser';
  5. import Statistics from './modules/statistics/statistics';
  6. import * as MediaType from './service/RTC/MediaType';
  7. import RTCEvents from './service/RTC/RTCEvents';
  8. const AUDIO_PERMISSION_NAME = 'microphone';
  9. const PERMISSION_GRANTED_STATUS = 'granted';
  10. const VIDEO_PERMISSION_NAME = 'camera';
  11. /**
  12. * Media devices utilities for Jitsi.
  13. */
  14. class JitsiMediaDevices {
  15. /**
  16. * Initializes a {@code JitsiMediaDevices} object. There will be a single
  17. * instance of this class.
  18. */
  19. constructor() {
  20. this._eventEmitter = new EventEmitter();
  21. this._grantedPermissions = {};
  22. RTC.addListener(
  23. RTCEvents.DEVICE_LIST_CHANGED,
  24. devices =>
  25. this._eventEmitter.emit(
  26. JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
  27. devices));
  28. RTC.addListener(
  29. RTCEvents.DEVICE_LIST_AVAILABLE,
  30. devices =>
  31. this._logOutputDevice(
  32. this.getAudioOutputDevice(),
  33. devices));
  34. RTC.addListener(
  35. RTCEvents.GRANTED_PERMISSIONS,
  36. grantedPermissions =>
  37. this._handleGrantedPermissions(grantedPermissions));
  38. // Test if the W3C Permissions API is implemented and the 'camera' and
  39. // 'microphone' permissions are implemented. (Testing for at least one
  40. // of them seems sufficient).
  41. this._permissionsApiSupported = new Promise(resolve => {
  42. if (!navigator.permissions) {
  43. resolve(false);
  44. return;
  45. }
  46. navigator.permissions.query({ name: VIDEO_PERMISSION_NAME })
  47. .then(() => resolve(true), () => resolve(false));
  48. });
  49. }
  50. /**
  51. * Updated the local granted permissions cache. A permissions might be
  52. * granted, denied, or undefined. This is represented by having its media
  53. * type key set to {@code true} or {@code false} respectively.
  54. *
  55. * @param {Object} grantedPermissions - Array with the permissions
  56. * which were granted.
  57. */
  58. _handleGrantedPermissions(grantedPermissions) {
  59. this._grantedPermissions = {
  60. ...this._grantedPermissions,
  61. ...grantedPermissions
  62. };
  63. }
  64. /**
  65. * Gathers data and sends it to statistics.
  66. * @param deviceID the device id to log
  67. * @param devices list of devices
  68. */
  69. _logOutputDevice(deviceID, devices) {
  70. const device
  71. = devices.find(
  72. d => d.kind === 'audiooutput' && d.deviceId === deviceID);
  73. if (device) {
  74. Statistics.sendActiveDeviceListEvent(
  75. RTC.getEventDataForActiveDevice(device));
  76. }
  77. }
  78. /**
  79. * Executes callback with list of media devices connected.
  80. * @param {function} callback
  81. */
  82. enumerateDevices(callback) {
  83. RTC.enumerateDevices(callback);
  84. }
  85. /**
  86. * Checks if its possible to enumerate available cameras/micropones.
  87. * @returns {Promise<boolean>} a Promise which will be resolved only once
  88. * the WebRTC stack is ready, either with true if the device listing is
  89. * available available or with false otherwise.
  90. */
  91. isDeviceListAvailable() {
  92. return RTC.isDeviceListAvailable();
  93. }
  94. /**
  95. * Returns true if changing the input (camera / microphone) or output
  96. * (audio) device is supported and false if not.
  97. * @param {string} [deviceType] - type of device to change. Default is
  98. * undefined or 'input', 'output' - for audio output device change.
  99. * @returns {boolean} true if available, false otherwise.
  100. */
  101. isDeviceChangeAvailable(deviceType) {
  102. return RTC.isDeviceChangeAvailable(deviceType);
  103. }
  104. /**
  105. * Checks if the permission for the given device was granted.
  106. *
  107. * @param {'audio'|'video'} [type] - type of devices to check,
  108. * undefined stands for both 'audio' and 'video' together
  109. * @returns {Promise<boolean>}
  110. */
  111. isDevicePermissionGranted(type) {
  112. return new Promise(resolve => {
  113. // Shortcut: first check if we already know the permission was
  114. // granted.
  115. if (type in this._grantedPermissions) {
  116. resolve(this._grantedPermissions[type]);
  117. return;
  118. }
  119. // Check using the Permissions API.
  120. this._permissionsApiSupported.then(supported => {
  121. if (!supported) {
  122. // Workaround on Safari for audio input device
  123. // selection to work. Safari doesn't support the
  124. // permissions query.
  125. if (browser.isSafari()) {
  126. resolve(true);
  127. return;
  128. }
  129. resolve(false);
  130. return;
  131. }
  132. const promises = [];
  133. switch (type) {
  134. case MediaType.VIDEO:
  135. promises.push(
  136. navigator.permissions.query({
  137. name: VIDEO_PERMISSION_NAME
  138. }));
  139. break;
  140. case MediaType.AUDIO:
  141. promises.push(
  142. navigator.permissions.query({
  143. name: AUDIO_PERMISSION_NAME
  144. }));
  145. break;
  146. default:
  147. promises.push(
  148. navigator.permissions.query({
  149. name: VIDEO_PERMISSION_NAME
  150. }));
  151. promises.push(
  152. navigator.permissions.query({
  153. name: AUDIO_PERMISSION_NAME
  154. }));
  155. }
  156. Promise.all(promises).then(
  157. results => resolve(results.every(permissionStatus => {
  158. // The status attribute is deprecated, and state
  159. // should be used instead, but check both for now
  160. // for backwards compatibility.
  161. const grantStatus = permissionStatus.state
  162. || permissionStatus.status;
  163. return grantStatus === PERMISSION_GRANTED_STATUS;
  164. })),
  165. () => resolve(false)
  166. );
  167. });
  168. });
  169. }
  170. /**
  171. * Returns true if it is possible to be simultaneously capturing audio
  172. * from more than one device.
  173. *
  174. * @returns {boolean}
  175. */
  176. isMultipleAudioInputSupported() {
  177. return !browser.isFirefox();
  178. }
  179. /**
  180. * Returns currently used audio output device id, 'default' stands
  181. * for default device
  182. * @returns {string}
  183. */
  184. getAudioOutputDevice() {
  185. return RTC.getAudioOutputDevice();
  186. }
  187. /**
  188. * Sets current audio output device.
  189. * @param {string} deviceId - id of 'audiooutput' device from
  190. * navigator.mediaDevices.enumerateDevices(), 'default' is for
  191. * default device
  192. * @returns {Promise} - resolves when audio output is changed, is rejected
  193. * otherwise
  194. */
  195. setAudioOutputDevice(deviceId) {
  196. const availableDevices = RTC.getCurrentlyAvailableMediaDevices();
  197. if (availableDevices.length > 0) {
  198. // if we have devices info report device to stats
  199. // normally this will not happen on startup as this method is called
  200. // too early. This will happen only on user selection of new device
  201. this._logOutputDevice(
  202. deviceId, RTC.getCurrentlyAvailableMediaDevices());
  203. }
  204. return RTC.setAudioOutputDevice(deviceId);
  205. }
  206. /**
  207. * Adds an event handler.
  208. * @param {string} event - event name
  209. * @param {function} handler - event handler
  210. */
  211. addEventListener(event, handler) {
  212. this._eventEmitter.addListener(event, handler);
  213. }
  214. /**
  215. * Removes event handler.
  216. * @param {string} event - event name
  217. * @param {function} handler - event handler
  218. */
  219. removeEventListener(event, handler) {
  220. this._eventEmitter.removeListener(event, handler);
  221. }
  222. /**
  223. * Emits an event.
  224. * @param {string} event - event name
  225. */
  226. emitEvent(event, ...args) {
  227. this._eventEmitter.emit(event, ...args);
  228. }
  229. /**
  230. * Returns whether or not the current browser can support capturing video,
  231. * be it camera or desktop, and displaying received video.
  232. *
  233. * @returns {boolean}
  234. */
  235. supportsVideo() {
  236. // Defer to the browser capabilities to allow exposure of the api to the
  237. // consumer but prevent other files from having to import
  238. // JitsiMediaDevices.
  239. return browser.supportsVideo();
  240. }
  241. }
  242. export default new JitsiMediaDevices();