This commit is contained in:
20
react/features/web-hid/actionTypes.ts
Normal file
20
react/features/web-hid/actionTypes.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Action type to INIT_DEVICE.
|
||||
*/
|
||||
export const INIT_DEVICE = 'INIT_DEVICE';
|
||||
|
||||
/**
|
||||
* Action type to CLOSE_HID_DEVICE.
|
||||
*/
|
||||
export const CLOSE_HID_DEVICE = 'CLOSE_HID_DEVICE';
|
||||
|
||||
/**
|
||||
* Action type to REQUEST_HID_DEVICE.
|
||||
*/
|
||||
export const REQUEST_HID_DEVICE = 'REQUEST_HID_DEVICE';
|
||||
|
||||
/**
|
||||
* Action type to UPDATE_DEVICE.
|
||||
*/
|
||||
export const UPDATE_DEVICE = 'UPDATE_DEVICE';
|
||||
|
||||
51
react/features/web-hid/actions.ts
Normal file
51
react/features/web-hid/actions.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { CLOSE_HID_DEVICE, INIT_DEVICE, REQUEST_HID_DEVICE, UPDATE_DEVICE } from './actionTypes';
|
||||
import { IDeviceInfo } from './types';
|
||||
|
||||
/**
|
||||
* Action used to init device.
|
||||
*
|
||||
* @param {IDeviceInfo} deviceInfo - Telephony device information.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function initDeviceInfo(deviceInfo: IDeviceInfo) {
|
||||
return {
|
||||
type: INIT_DEVICE,
|
||||
deviceInfo
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Request hid device.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function closeHidDevice() {
|
||||
return {
|
||||
type: CLOSE_HID_DEVICE
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Request hid device.
|
||||
*
|
||||
* @param {IDeviceInfo} deviceInfo - Telephony device information.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function requestHidDevice() {
|
||||
return {
|
||||
type: REQUEST_HID_DEVICE
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to init device.
|
||||
*
|
||||
* @param {IDeviceInfo} deviceInfo - Telephony device information.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function updateDeviceInfo(deviceInfo: IDeviceInfo) {
|
||||
return {
|
||||
type: UPDATE_DEVICE,
|
||||
updates: deviceInfo
|
||||
};
|
||||
}
|
||||
121
react/features/web-hid/functions.ts
Normal file
121
react/features/web-hid/functions.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { IReduxState, IStore } from '../app/types';
|
||||
import { MEDIA_TYPE } from '../base/media/constants';
|
||||
import { muteLocal } from '../video-menu/actions.any';
|
||||
|
||||
import { updateDeviceInfo } from './actions';
|
||||
import { ACTION_HOOK_TYPE_NAME, EVENT_TYPE, IDeviceInfo } from './types';
|
||||
import WebHidManager from './webhid-manager';
|
||||
|
||||
/**
|
||||
* Attach web hid event listeners.
|
||||
*
|
||||
* @param {Function} initDeviceListener - Init hid device listener.
|
||||
* @param {Function} updateDeviceListener - Update hid device listener.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function attachHidEventListeners(
|
||||
initDeviceListener: EventListenerOrEventListenerObject,
|
||||
updateDeviceListener: EventListenerOrEventListenerObject
|
||||
) {
|
||||
const hidManager = getWebHidInstance();
|
||||
|
||||
if (typeof initDeviceListener === 'function') {
|
||||
hidManager.addEventListener(EVENT_TYPE.INIT_DEVICE, initDeviceListener);
|
||||
}
|
||||
if (typeof updateDeviceListener === 'function') {
|
||||
hidManager.addEventListener(EVENT_TYPE.UPDATE_DEVICE, updateDeviceListener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns instance of web hid manager.
|
||||
*
|
||||
* @returns {WebHidManager} - WebHidManager instance.
|
||||
*/
|
||||
export function getWebHidInstance(): WebHidManager {
|
||||
const hidManager = WebHidManager.getInstance();
|
||||
|
||||
return hidManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns root conference state.
|
||||
*
|
||||
* @param {IReduxState} state - Global state.
|
||||
* @returns {Object} Conference state.
|
||||
*/
|
||||
export const getWebHidState = (state: IReduxState) => state['features/web-hid'];
|
||||
|
||||
/**
|
||||
* Returns true if hid is supported.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isDeviceHidSupported(): boolean {
|
||||
const hidManager = getWebHidInstance();
|
||||
|
||||
return hidManager.isSupported();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns device info from state.
|
||||
*
|
||||
* @param {IReduxState} state - Global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getDeviceInfo(state: IReduxState): IDeviceInfo {
|
||||
const hidState = getWebHidState(state);
|
||||
|
||||
return hidState.deviceInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles updating hid device.
|
||||
*
|
||||
* @param {Function} dispatch - Redux dispatch.
|
||||
* @param {Function} customEventData - Custom event data.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function handleUpdateHidDevice(
|
||||
dispatch: IStore['dispatch'],
|
||||
customEventData: CustomEvent<{ actionResult?: { eventName: string; }; deviceInfo: IDeviceInfo; }>
|
||||
) {
|
||||
dispatch(updateDeviceInfo(customEventData.detail.deviceInfo));
|
||||
|
||||
if (customEventData.detail?.actionResult?.eventName === ACTION_HOOK_TYPE_NAME.MUTE_SWITCH_ON) {
|
||||
dispatch(muteLocal(true, MEDIA_TYPE.AUDIO));
|
||||
} else if (customEventData.detail?.actionResult?.eventName === ACTION_HOOK_TYPE_NAME.MUTE_SWITCH_OFF) {
|
||||
dispatch(muteLocal(false, MEDIA_TYPE.AUDIO));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove web hid event listeners.
|
||||
*
|
||||
* @param {Function} initDeviceListener - Init hid device listener.
|
||||
* @param {Function} updateDeviceListener - Update hid device listener.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function removeHidEventListeners(
|
||||
initDeviceListener: EventListenerOrEventListenerObject,
|
||||
updateDeviceListener: EventListenerOrEventListenerObject
|
||||
) {
|
||||
const hidManager = getWebHidInstance();
|
||||
|
||||
if (typeof initDeviceListener === 'function') {
|
||||
hidManager.removeEventListener(EVENT_TYPE.INIT_DEVICE, initDeviceListener);
|
||||
}
|
||||
if (typeof updateDeviceListener === 'function') {
|
||||
hidManager.removeEventListener(EVENT_TYPE.UPDATE_DEVICE, updateDeviceListener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is no device info provided.
|
||||
*
|
||||
* @param {IDeviceInfo} deviceInfo - Device info state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function shouldRequestHIDDevice(deviceInfo: IDeviceInfo): boolean {
|
||||
return !deviceInfo?.device || Object.keys(deviceInfo).length === 0;
|
||||
}
|
||||
3
react/features/web-hid/logger.ts
Normal file
3
react/features/web-hid/logger.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
export default getLogger('features/hid');
|
||||
145
react/features/web-hid/middleware.ts
Normal file
145
react/features/web-hid/middleware.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
|
||||
import { getWebHIDFeatureConfig } from '../base/config/functions.web';
|
||||
import { SET_AUDIO_MUTED } from '../base/media/actionTypes';
|
||||
import { isAudioMuted } from '../base/media/functions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
|
||||
import { CLOSE_HID_DEVICE, REQUEST_HID_DEVICE } from './actionTypes';
|
||||
import { initDeviceInfo } from './actions';
|
||||
import {
|
||||
attachHidEventListeners,
|
||||
getWebHidInstance,
|
||||
handleUpdateHidDevice,
|
||||
isDeviceHidSupported,
|
||||
removeHidEventListeners
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
import { COMMANDS, IDeviceInfo } from './types';
|
||||
|
||||
/**
|
||||
* A listener for initialising the webhid device.
|
||||
*/
|
||||
let initDeviceListener: (e: any) => void;
|
||||
|
||||
/**
|
||||
* A listener for updating the webhid device.
|
||||
*/
|
||||
let updateDeviceListener: (e: any) => void;
|
||||
|
||||
/**
|
||||
* The redux middleware for {@link WebHid}.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register((store: IStore) => next => action => {
|
||||
const { dispatch, getState } = store;
|
||||
|
||||
if (!getWebHIDFeatureConfig(getState())) {
|
||||
return next(action);
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT: {
|
||||
const hidManager = getWebHidInstance();
|
||||
|
||||
if (!hidManager.isSupported()) {
|
||||
logger.warn('HID is not supported');
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
const _initDeviceListener = (e: CustomEvent<{ deviceInfo: IDeviceInfo; }>) =>
|
||||
dispatch(initDeviceInfo(e.detail.deviceInfo));
|
||||
const _updateDeviceListener
|
||||
= (e: CustomEvent<{ actionResult: { eventName: string; }; deviceInfo: IDeviceInfo; }>) =>
|
||||
handleUpdateHidDevice(dispatch, e);
|
||||
|
||||
|
||||
initDeviceListener = _initDeviceListener;
|
||||
updateDeviceListener = _updateDeviceListener;
|
||||
|
||||
hidManager.listenToConnectedHid();
|
||||
attachHidEventListeners(initDeviceListener, updateDeviceListener);
|
||||
|
||||
break;
|
||||
}
|
||||
case APP_WILL_UNMOUNT: {
|
||||
const hidManager = getWebHidInstance();
|
||||
|
||||
if (!isDeviceHidSupported()) {
|
||||
break;
|
||||
}
|
||||
|
||||
removeHidEventListeners(initDeviceListener, updateDeviceListener);
|
||||
hidManager.close();
|
||||
|
||||
break;
|
||||
}
|
||||
case CLOSE_HID_DEVICE: {
|
||||
const hidManager = getWebHidInstance();
|
||||
|
||||
// cleanup event handlers when hid device is removed from Settings.
|
||||
removeHidEventListeners(initDeviceListener, updateDeviceListener);
|
||||
|
||||
hidManager.close();
|
||||
|
||||
break;
|
||||
}
|
||||
case REQUEST_HID_DEVICE: {
|
||||
_onRequestHIDDevice(store);
|
||||
|
||||
break;
|
||||
}
|
||||
case SET_AUDIO_MUTED: {
|
||||
const hidManager = getWebHidInstance();
|
||||
|
||||
if (!isDeviceHidSupported()) {
|
||||
break;
|
||||
}
|
||||
|
||||
hidManager.sendDeviceReport({ command: action.muted ? COMMANDS.MUTE_ON : COMMANDS.MUTE_OFF });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles HID device requests.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function _onRequestHIDDevice(store: IStore) {
|
||||
const { dispatch } = store;
|
||||
const hidManager = getWebHidInstance();
|
||||
const availableDevices = await hidManager.requestHidDevices();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
||||
if (!availableDevices || !availableDevices.length) {
|
||||
logger.info('HID device not available');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const _initDeviceListener = (e: CustomEvent<{ deviceInfo: IDeviceInfo; }>) =>
|
||||
dispatch(initDeviceInfo(e.detail.deviceInfo));
|
||||
const _updateDeviceListener
|
||||
= (e: CustomEvent<{ actionResult: { eventName: string; }; deviceInfo: IDeviceInfo; }>) => {
|
||||
handleUpdateHidDevice(dispatch, e);
|
||||
};
|
||||
|
||||
initDeviceListener = _initDeviceListener;
|
||||
updateDeviceListener = _updateDeviceListener;
|
||||
|
||||
attachHidEventListeners(initDeviceListener, updateDeviceListener);
|
||||
await hidManager.listenToConnectedHid();
|
||||
|
||||
// sync headset to mute if participant is already muted.
|
||||
if (isAudioMuted(store.getState())) {
|
||||
hidManager.sendDeviceReport({ command: COMMANDS.MUTE_ON });
|
||||
}
|
||||
}
|
||||
43
react/features/web-hid/reducer.ts
Normal file
43
react/features/web-hid/reducer.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
|
||||
import { CLOSE_HID_DEVICE, INIT_DEVICE, UPDATE_DEVICE } from './actionTypes';
|
||||
import { IDeviceInfo } from './types';
|
||||
|
||||
/**
|
||||
* The initial state of the web-hid feature.
|
||||
*/
|
||||
const DEFAULT_STATE = {
|
||||
deviceInfo: {} as IDeviceInfo
|
||||
};
|
||||
|
||||
export interface IWebHid {
|
||||
deviceInfo: IDeviceInfo;
|
||||
}
|
||||
|
||||
|
||||
ReducerRegistry.register<IWebHid>(
|
||||
'features/web-hid',
|
||||
(state: IWebHid = DEFAULT_STATE, action): IWebHid => {
|
||||
switch (action.type) {
|
||||
case INIT_DEVICE:
|
||||
return {
|
||||
...state,
|
||||
deviceInfo: action.deviceInfo
|
||||
};
|
||||
case UPDATE_DEVICE:
|
||||
return {
|
||||
...state,
|
||||
deviceInfo: {
|
||||
...state.deviceInfo,
|
||||
...action.updates
|
||||
}
|
||||
};
|
||||
case CLOSE_HID_DEVICE:
|
||||
return {
|
||||
...state,
|
||||
deviceInfo: DEFAULT_STATE.deviceInfo
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
});
|
||||
44
react/features/web-hid/types.ts
Normal file
44
react/features/web-hid/types.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
export const EVENT_TYPE = {
|
||||
INIT_DEVICE: 'INIT_DEVICE',
|
||||
UPDATE_DEVICE: 'UPDATE_DEVICE'
|
||||
};
|
||||
|
||||
export const HOOK_STATUS = {
|
||||
ON: 'on',
|
||||
OFF: 'off'
|
||||
};
|
||||
|
||||
export const COMMANDS = {
|
||||
ON_HOOK: 'onHook',
|
||||
OFF_HOOK: 'offHook',
|
||||
MUTE_OFF: 'muteOff',
|
||||
MUTE_ON: 'muteOn',
|
||||
ON_RING: 'onRing',
|
||||
OFF_RING: 'offRing',
|
||||
ON_HOLD: 'onHold',
|
||||
OFF_HOLD: 'offHold'
|
||||
};
|
||||
|
||||
export const INPUT_REPORT_EVENT_NAME = {
|
||||
ON_DEVICE_HOOK_SWITCH: 'ondevicehookswitch',
|
||||
ON_DEVICE_MUTE_SWITCH: 'ondevicemuteswitch'
|
||||
};
|
||||
|
||||
export const ACTION_HOOK_TYPE_NAME = {
|
||||
HOOK_SWITCH_ON: 'HOOK_SWITCH_ON',
|
||||
HOOK_SWITCH_OFF: 'HOOK_SWITCH_OFF',
|
||||
MUTE_SWITCH_ON: 'MUTE_SWITCH_ON',
|
||||
MUTE_SWITCH_OFF: 'MUTE_SWITCH_OFF',
|
||||
VOLUME_CHANGE_UP: 'VOLUME_CHANGE_UP',
|
||||
VOLUME_CHANGE_DOWN: 'VOLUME_CHANGE_DOWN'
|
||||
};
|
||||
|
||||
export interface IDeviceInfo {
|
||||
|
||||
// @ts-ignore
|
||||
device: HIDDevice;
|
||||
hold: boolean;
|
||||
hookStatus: string;
|
||||
muted: boolean;
|
||||
ring: boolean;
|
||||
}
|
||||
52
react/features/web-hid/utils.ts
Normal file
52
react/features/web-hid/utils.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Telephony usage actions based on HID Usage tables for Universal Serial Bus (page 112.).
|
||||
*
|
||||
*/
|
||||
export const TELEPHONY_DEVICE_USAGE_PAGE = 11;
|
||||
|
||||
/** Telephony usages
|
||||
* - used to parse HIDDevice UsageId collections
|
||||
** - outputReports has mute and offHook
|
||||
** - inputReports exists hookSwitch and phoneMute.
|
||||
**/
|
||||
export const DEVICE_USAGE = {
|
||||
/* outputReports. */
|
||||
mute: {
|
||||
usageId: 0x080009,
|
||||
usageName: 'Mute'
|
||||
},
|
||||
offHook: {
|
||||
usageId: 0x080017,
|
||||
usageName: 'Off Hook'
|
||||
},
|
||||
ring: {
|
||||
usageId: 0x080018,
|
||||
usageName: 'Ring'
|
||||
},
|
||||
hold: {
|
||||
usageId: 0x080020,
|
||||
usageName: 'Hold'
|
||||
},
|
||||
|
||||
/* inputReports. */
|
||||
hookSwitch: {
|
||||
usageId: 0x0b0020,
|
||||
usageName: 'Hook Switch'
|
||||
},
|
||||
phoneMute: {
|
||||
usageId: 0x0b002f,
|
||||
usageName: 'Phone Mute'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter with telephony devices based on HID Usage tables for Universal Serial Bus (page 17).
|
||||
*
|
||||
* @type {{ filters: { usagePage: string }; exclusionFilters: {}; }}
|
||||
*/
|
||||
export const requestTelephonyHID = {
|
||||
filters: [ {
|
||||
usagePage: TELEPHONY_DEVICE_USAGE_PAGE
|
||||
} ],
|
||||
exclusionFilters: []
|
||||
};
|
||||
1018
react/features/web-hid/webhid-manager.ts
Normal file
1018
react/features/web-hid/webhid-manager.ts
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user