Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

JitsiMediaDevices.js 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import * as JitsiMediaDevicesEvents from './JitsiMediaDevicesEvents';
  2. import RTC from './modules/RTC/RTC';
  3. import browser from './modules/browser';
  4. import Listenable from './modules/util/Listenable';
  5. import { MediaType } from './service/RTC/MediaType';
  6. import RTCEvents from './service/RTC/RTCEvents';
  7. const AUDIO_PERMISSION_NAME = 'microphone';
  8. const PERMISSION_GRANTED_STATUS = 'granted';
  9. const VIDEO_PERMISSION_NAME = 'camera';
  10. /**
  11. * Media devices utilities for Jitsi.
  12. */
  13. class JitsiMediaDevices extends Listenable {
  14. /**
  15. * Initializes a {@code JitsiMediaDevices} object. There will be a single
  16. * instance of this class.
  17. */
  18. constructor() {
  19. super();
  20. this._permissions = {};
  21. }
  22. /**
  23. * Initialize. Start listening for device changes and initialize permissions checks.
  24. */
  25. init() {
  26. RTC.addListener(
  27. RTCEvents.DEVICE_LIST_CHANGED,
  28. devices =>
  29. this.eventEmitter.emit(
  30. JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
  31. devices));
  32. // We would still want to update the permissions cache in case the permissions API is not supported.
  33. RTC.addListener(
  34. RTCEvents.PERMISSIONS_CHANGED,
  35. permissions => this._handlePermissionsChange(permissions));
  36. // Test if the W3C Permissions API is implemented and the 'camera' and 'microphone' permissions are
  37. // implemented. If supported add onchange listeners.
  38. this._permissionsApiSupported = new Promise(resolve => {
  39. if (!navigator.permissions) {
  40. resolve(false);
  41. return;
  42. }
  43. const self = this;
  44. const promises = [];
  45. promises.push(navigator.permissions.query({ name: VIDEO_PERMISSION_NAME })
  46. .then(status => {
  47. this._handlePermissionsChange({
  48. [MediaType.VIDEO]: this._parsePermissionState(status)
  49. });
  50. status.onchange = function() {
  51. try {
  52. self._handlePermissionsChange({
  53. [MediaType.VIDEO]: self._parsePermissionState(this)
  54. });
  55. } catch (error) {
  56. // Nothing to do.
  57. }
  58. };
  59. return true;
  60. })
  61. .catch(() => false));
  62. promises.push(navigator.permissions.query({ name: AUDIO_PERMISSION_NAME })
  63. .then(status => {
  64. this._handlePermissionsChange({
  65. [MediaType.AUDIO]: this._parsePermissionState(status)
  66. });
  67. status.onchange = function() {
  68. try {
  69. self._handlePermissionsChange({
  70. [MediaType.AUDIO]: self._parsePermissionState(this)
  71. });
  72. } catch (error) {
  73. // Nothing to do.
  74. }
  75. };
  76. return true;
  77. })
  78. .catch(() => false));
  79. Promise.all(promises).then(results => resolve(results.every(supported => supported)));
  80. });
  81. }
  82. /**
  83. * Parses a PermissionState object and returns true for granted and false otherwise.
  84. *
  85. * @param {PermissionState} permissionStatus - The PermissionState object retrieved from the Permissions API.
  86. * @returns {boolean} - True for granted and false for denied.
  87. * @throws {TypeError}
  88. */
  89. _parsePermissionState(permissionStatus = {}) {
  90. // The status attribute is deprecated, and state
  91. // should be used instead, but check both for now
  92. // for backwards compatibility.
  93. const status = permissionStatus.state || permissionStatus.status;
  94. if (typeof status !== 'string') {
  95. throw new TypeError();
  96. }
  97. return status === PERMISSION_GRANTED_STATUS;
  98. }
  99. /**
  100. * Updates the local granted/denied permissions cache. A permissions might be
  101. * granted, denied, or undefined. This is represented by having its media
  102. * type key set to {@code true} or {@code false} respectively.
  103. *
  104. * @param {Object} permissions - Object with the permissions.
  105. */
  106. _handlePermissionsChange(permissions) {
  107. const hasPermissionsChanged
  108. = [ MediaType.AUDIO, MediaType.VIDEO ]
  109. .some(type => type in permissions && permissions[type] !== this._permissions[type]);
  110. if (hasPermissionsChanged) {
  111. this._permissions = {
  112. ...this._permissions,
  113. ...permissions
  114. };
  115. this.eventEmitter.emit(JitsiMediaDevicesEvents.PERMISSIONS_CHANGED, this._permissions);
  116. if (this._permissions[MediaType.AUDIO] || this._permissions[MediaType.VIDEO]) {
  117. // Triggering device list update when the permissions are granted in order to update
  118. // the labels the devices.
  119. // eslint-disable-next-line no-empty-function
  120. this.enumerateDevices(() => {});
  121. }
  122. }
  123. }
  124. /**
  125. * Executes callback with list of media devices connected.
  126. * @param {function} callback
  127. */
  128. enumerateDevices(callback) {
  129. RTC.enumerateDevices(callback);
  130. }
  131. /**
  132. * Checks if its possible to enumerate available cameras/micropones.
  133. * @returns {Promise<boolean>} a Promise which will be resolved only once
  134. * the WebRTC stack is ready, either with true if the device listing is
  135. * available available or with false otherwise.
  136. */
  137. isDeviceListAvailable() {
  138. return RTC.isDeviceListAvailable();
  139. }
  140. /**
  141. * Returns true if changing the input (camera / microphone) or output
  142. * (audio) device is supported and false if not.
  143. * @param {string} [deviceType] - type of device to change. Default is
  144. * undefined or 'input', 'output' - for audio output device change.
  145. * @returns {boolean} true if available, false otherwise.
  146. */
  147. isDeviceChangeAvailable(deviceType) {
  148. return RTC.isDeviceChangeAvailable(deviceType);
  149. }
  150. /**
  151. * Checks if the permission for the given device was granted.
  152. *
  153. * @param {'audio'|'video'} [type] - type of devices to check,
  154. * undefined stands for both 'audio' and 'video' together
  155. * @returns {Promise<boolean>}
  156. */
  157. isDevicePermissionGranted(type) {
  158. return new Promise(resolve => {
  159. // Shortcut: first check if we already know the permission was
  160. // granted.
  161. if (type in this._permissions) {
  162. resolve(this._permissions[type]);
  163. return;
  164. }
  165. // Check using the Permissions API.
  166. this._permissionsApiSupported.then(supported => {
  167. if (!supported) {
  168. resolve(false);
  169. return;
  170. }
  171. const promises = [];
  172. switch (type) {
  173. case MediaType.VIDEO:
  174. promises.push(
  175. navigator.permissions.query({
  176. name: VIDEO_PERMISSION_NAME
  177. }));
  178. break;
  179. case MediaType.AUDIO:
  180. promises.push(
  181. navigator.permissions.query({
  182. name: AUDIO_PERMISSION_NAME
  183. }));
  184. break;
  185. default:
  186. promises.push(
  187. navigator.permissions.query({
  188. name: VIDEO_PERMISSION_NAME
  189. }));
  190. promises.push(
  191. navigator.permissions.query({
  192. name: AUDIO_PERMISSION_NAME
  193. }));
  194. }
  195. Promise.all(promises).then(
  196. results => resolve(results.every(permissionStatus => {
  197. try {
  198. return this._parsePermissionState(permissionStatus);
  199. } catch {
  200. return false;
  201. }
  202. })),
  203. () => resolve(false)
  204. );
  205. });
  206. });
  207. }
  208. /**
  209. * Returns true if it is possible to be simultaneously capturing audio from more than one device.
  210. *
  211. * @returns {boolean}
  212. */
  213. isMultipleAudioInputSupported() {
  214. return !(
  215. (browser.isFirefox() && browser.isVersionLessThan('101'))
  216. || browser.isIosBrowser()
  217. );
  218. }
  219. /**
  220. * Returns currently used audio output device id, 'default' stands
  221. * for default device
  222. * @returns {string}
  223. */
  224. getAudioOutputDevice() {
  225. return RTC.getAudioOutputDevice();
  226. }
  227. /**
  228. * Sets current audio output device.
  229. * @param {string} deviceId - id of 'audiooutput' device from
  230. * navigator.mediaDevices.enumerateDevices(), 'default' is for
  231. * default device
  232. * @returns {Promise} - resolves when audio output is changed, is rejected
  233. * otherwise
  234. */
  235. setAudioOutputDevice(deviceId) {
  236. return RTC.setAudioOutputDevice(deviceId);
  237. }
  238. }
  239. export default new JitsiMediaDevices();