Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

JitsiMediaDevices.js 9.1KB

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