123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- import EventEmitter from 'events';
-
- import * as JitsiMediaDevicesEvents from './JitsiMediaDevicesEvents';
- import RTC from './modules/RTC/RTC';
- import browser from './modules/browser';
- import Statistics from './modules/statistics/statistics';
- import * as MediaType from './service/RTC/MediaType';
- import RTCEvents from './service/RTC/RTCEvents';
-
- const AUDIO_PERMISSION_NAME = 'microphone';
- const PERMISSION_GRANTED_STATUS = 'granted';
- const VIDEO_PERMISSION_NAME = 'camera';
-
- /**
- * Media devices utilities for Jitsi.
- */
- class JitsiMediaDevices {
- /**
- * Initializes a {@code JitsiMediaDevices} object. There will be a single
- * instance of this class.
- */
- constructor() {
- this._eventEmitter = new EventEmitter();
- this._grantedPermissions = {};
-
- RTC.addListener(
- RTCEvents.DEVICE_LIST_CHANGED,
- devices =>
- this._eventEmitter.emit(
- JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
- devices));
- RTC.addListener(
- RTCEvents.DEVICE_LIST_AVAILABLE,
- devices =>
- this._logOutputDevice(
- this.getAudioOutputDevice(),
- devices));
- RTC.addListener(
- RTCEvents.GRANTED_PERMISSIONS,
- grantedPermissions =>
- this._handleGrantedPermissions(grantedPermissions));
-
- // Test if the W3C Permissions API is implemented and the 'camera' and
- // 'microphone' permissions are implemented. (Testing for at least one
- // of them seems sufficient).
- this._permissionsApiSupported = new Promise(resolve => {
- if (!navigator.permissions) {
- resolve(false);
-
- return;
- }
-
- navigator.permissions.query({ name: VIDEO_PERMISSION_NAME })
- .then(() => resolve(true), () => resolve(false));
- });
- }
-
- /**
- * Updated the local granted permissions cache. A permissions might be
- * granted, denied, or undefined. This is represented by having its media
- * type key set to {@code true} or {@code false} respectively.
- *
- * @param {Object} grantedPermissions - Array with the permissions
- * which were granted.
- */
- _handleGrantedPermissions(grantedPermissions) {
- this._grantedPermissions = {
- ...this._grantedPermissions,
- ...grantedPermissions
- };
- }
-
- /**
- * Gathers data and sends it to statistics.
- * @param deviceID the device id to log
- * @param devices list of devices
- */
- _logOutputDevice(deviceID, devices) {
- const device
- = devices.find(
- d => d.kind === 'audiooutput' && d.deviceId === deviceID);
-
- if (device) {
- Statistics.sendActiveDeviceListEvent(
- RTC.getEventDataForActiveDevice(device));
- }
- }
-
- /**
- * Executes callback with list of media devices connected.
- * @param {function} callback
- */
- enumerateDevices(callback) {
- RTC.enumerateDevices(callback);
- }
-
- /**
- * Checks if its possible to enumerate available cameras/micropones.
- * @returns {Promise<boolean>} a Promise which will be resolved only once
- * the WebRTC stack is ready, either with true if the device listing is
- * available available or with false otherwise.
- */
- isDeviceListAvailable() {
- return RTC.isDeviceListAvailable();
- }
-
- /**
- * Returns true if changing the input (camera / microphone) or output
- * (audio) device is supported and false if not.
- * @param {string} [deviceType] - type of device to change. Default is
- * undefined or 'input', 'output' - for audio output device change.
- * @returns {boolean} true if available, false otherwise.
- */
- isDeviceChangeAvailable(deviceType) {
- return RTC.isDeviceChangeAvailable(deviceType);
- }
-
- /**
- * Checks if the permission for the given device was granted.
- *
- * @param {'audio'|'video'} [type] - type of devices to check,
- * undefined stands for both 'audio' and 'video' together
- * @returns {Promise<boolean>}
- */
- isDevicePermissionGranted(type) {
- return new Promise(resolve => {
- // Shortcut: first check if we already know the permission was
- // granted.
- if (type in this._grantedPermissions) {
- resolve(this._grantedPermissions[type]);
-
- return;
- }
-
- // Check using the Permissions API.
- this._permissionsApiSupported.then(supported => {
- if (!supported) {
- // Workaround on Safari for audio input device
- // selection to work. Safari doesn't support the
- // permissions query.
- if (browser.isSafari()) {
- resolve(true);
-
- return;
- }
- resolve(false);
-
- return;
- }
-
- const promises = [];
-
- switch (type) {
- case MediaType.VIDEO:
- promises.push(
- navigator.permissions.query({
- name: VIDEO_PERMISSION_NAME
- }));
- break;
- case MediaType.AUDIO:
- promises.push(
- navigator.permissions.query({
- name: AUDIO_PERMISSION_NAME
- }));
- break;
- default:
- promises.push(
- navigator.permissions.query({
- name: VIDEO_PERMISSION_NAME
- }));
- promises.push(
- navigator.permissions.query({
- name: AUDIO_PERMISSION_NAME
- }));
- }
-
- Promise.all(promises).then(
- results => resolve(results.every(permissionStatus => {
- // The status attribute is deprecated, and state
- // should be used instead, but check both for now
- // for backwards compatibility.
- const grantStatus = permissionStatus.state
- || permissionStatus.status;
-
- return grantStatus === PERMISSION_GRANTED_STATUS;
- })),
- () => resolve(false)
- );
- });
- });
- }
-
- /**
- * Returns true if it is possible to be simultaneously capturing audio
- * from more than one device.
- *
- * @returns {boolean}
- */
- isMultipleAudioInputSupported() {
- return !browser.isFirefox();
- }
-
- /**
- * Returns currently used audio output device id, 'default' stands
- * for default device
- * @returns {string}
- */
- getAudioOutputDevice() {
- return RTC.getAudioOutputDevice();
- }
-
- /**
- * Sets current audio output device.
- * @param {string} deviceId - id of 'audiooutput' device from
- * navigator.mediaDevices.enumerateDevices(), 'default' is for
- * default device
- * @returns {Promise} - resolves when audio output is changed, is rejected
- * otherwise
- */
- setAudioOutputDevice(deviceId) {
- const availableDevices = RTC.getCurrentlyAvailableMediaDevices();
-
- if (availableDevices.length > 0) {
- // if we have devices info report device to stats
- // normally this will not happen on startup as this method is called
- // too early. This will happen only on user selection of new device
- this._logOutputDevice(
- deviceId, RTC.getCurrentlyAvailableMediaDevices());
- }
-
- return RTC.setAudioOutputDevice(deviceId);
- }
-
- /**
- * Adds an event handler.
- * @param {string} event - event name
- * @param {function} handler - event handler
- */
- addEventListener(event, handler) {
- this._eventEmitter.addListener(event, handler);
- }
-
- /**
- * Removes event handler.
- * @param {string} event - event name
- * @param {function} handler - event handler
- */
- removeEventListener(event, handler) {
- this._eventEmitter.removeListener(event, handler);
- }
-
- /**
- * Emits an event.
- * @param {string} event - event name
- */
- emitEvent(event, ...args) {
- this._eventEmitter.emit(event, ...args);
- }
-
- /**
- * Returns whether or not the current browser can support capturing video,
- * be it camera or desktop, and displaying received video.
- *
- * @returns {boolean}
- */
- supportsVideo() {
- // Defer to the browser capabilities to allow exposure of the api to the
- // consumer but prevent other files from having to import
- // JitsiMediaDevices.
- return browser.supportsVideo();
- }
- }
-
- export default new JitsiMediaDevices();
|