jiti-meet/react/features/settings/components/web/video/VideoSettingsContent.js

264 lines
6.9 KiB
JavaScript
Raw Normal View History

// @flow
import React, { Component } from 'react';
import { translate } from '../../../../base/i18n';
import Video from '../../../../base/media/components/Video';
2020-05-20 10:57:03 +00:00
import { equals } from '../../../../base/redux';
import { createLocalVideoTracks } from '../../../functions';
const videoClassName = 'video-preview-video flipVideoX';
/**
* The type of the React {@code Component} props of {@link VideoSettingsContent}.
*/
export type Props = {
/**
* The deviceId of the camera device currently being used.
*/
currentCameraDeviceId: string,
/**
* Callback invoked to change current camera.
*/
setVideoInputDevice: Function,
/**
* Invoked to obtain translated strings.
*/
t: Function,
/**
* Callback invoked to toggle the settings popup visibility.
*/
toggleVideoSettings: Function,
/**
* All the camera device ids currently connected.
*/
videoDeviceIds: string[],
};
/**
* The type of the React {@code Component} state of {@link VideoSettingsContent}.
*/
type State = {
/**
* An array of all the jitsiTracks and eventual errors.
*/
trackData: Object[],
};
/**
* Implements a React {@link Component} which displays a list of video
* previews to choose from.
*
2021-11-04 21:10:43 +00:00
* @augments Component
*/
class VideoSettingsContent extends Component<Props, State> {
_componentWasUnmounted: boolean;
_videoContentRef: Object;
/**
* Initializes a new {@code VideoSettingsContent} instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props) {
super(props);
this._onEscClick = this._onEscClick.bind(this);
this._videoContentRef = React.createRef();
this.state = {
trackData: new Array(props.videoDeviceIds.length).fill({
jitsiTrack: null
})
};
}
_onEscClick: (KeyboardEvent) => void;
/**
* Click handler for the video entries.
*
* @param {KeyboardEvent} event - Esc key click to close the popup.
* @returns {void}
*/
_onEscClick(event) {
if (event.key === 'Escape') {
event.preventDefault();
event.stopPropagation();
this._videoContentRef.current.style.display = 'none';
}
}
/**
* Creates and updates the track data.
*
* @returns {void}
*/
async _setTracks() {
this._disposeTracks(this.state.trackData);
const trackData = await createLocalVideoTracks(this.props.videoDeviceIds, 5000);
// In case the component gets unmounted before the tracks are created
// avoid a leak by not setting the state
if (this._componentWasUnmounted) {
this._disposeTracks(trackData);
} else {
this.setState({
trackData
});
}
}
/**
* Destroys all the tracks from trackData object.
*
* @param {Object[]} trackData - An array of tracks that are to be disposed.
* @returns {Promise<void>}
*/
_disposeTracks(trackData) {
trackData.forEach(({ jitsiTrack }) => {
jitsiTrack && jitsiTrack.dispose();
});
}
/**
* Returns the click handler used when selecting the video preview.
*
* @param {string} deviceId - The id of the camera device.
* @returns {Function}
*/
_onEntryClick(deviceId) {
return () => {
this.props.setVideoInputDevice(deviceId);
this.props.toggleVideoSettings();
};
}
/**
* Renders a preview entry.
*
* @param {Object} data - The track data.
* @param {number} index - The index of the entry.
* @returns {React$Node}
*/
_renderPreviewEntry(data, index) {
const { error, jitsiTrack, deviceId } = data;
const { currentCameraDeviceId, t } = this.props;
const isSelected = deviceId === currentCameraDeviceId;
const key = `vp-${index}`;
const className = 'video-preview-entry';
const tabIndex = '0';
if (error) {
return (
<div
className = { className }
key = { key }
tabIndex = { -1 } >
<div className = 'video-preview-error'>{t(error)}</div>
</div>
);
}
const props: Object = {
className,
key,
tabIndex
};
const label = jitsiTrack && jitsiTrack.getTrackLabel();
if (isSelected) {
props['aria-checked'] = true;
props.className = `${className} video-preview-entry--selected`;
} else {
props.onClick = this._onEntryClick(deviceId);
props.onKeyPress = e => {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
props.onClick();
}
};
}
return (
<div
{ ...props }
role = 'radio'>
2021-02-23 11:09:22 +00:00
<div className = 'video-preview-label'>
{label && <div className = 'video-preview-label-container'>
<div className = 'video-preview-label-text'>
<span>{label}</span></div>
</div>}
2021-02-23 11:09:22 +00:00
</div>
<div className = 'video-preview-overlay' />
<Video
className = { videoClassName }
playsinline = { true }
videoTrack = {{ jitsiTrack }} />
</div>
);
}
/**
* Implements React's {@link Component#componentDidMount}.
*
* @inheritdoc
*/
componentDidMount() {
this._setTracks();
}
/**
* Implements React's {@link Component#componentWillUnmount}.
*
* @inheritdoc
*/
componentWillUnmount() {
this._componentWasUnmounted = true;
this._disposeTracks(this.state.trackData);
}
/**
* Implements React's {@link Component#componentDidUpdate}.
*
* @inheritdoc
*/
componentDidUpdate(prevProps) {
if (!equals(this.props.videoDeviceIds, prevProps.videoDeviceIds)) {
this._setTracks();
}
}
/**
* Implements React's {@link Component#render}.
*
* @inheritdoc
*/
render() {
const { trackData } = this.state;
return (
<div
aria-labelledby = 'video-settings-button'
className = 'video-preview-container'
id = 'video-settings-dialog'
onKeyDown = { this._onEscClick }
ref = { this._videoContentRef }
role = 'radiogroup'
tabIndex = '-1'>
{trackData.map((data, i) => this._renderPreviewEntry(data, i))}
</div>
);
}
}
export default translate(VideoSettingsContent);