2018-11-07 07:10:56 +08:00
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import React, { Component } from 'react';
|
|
|
|
import { defineMessages, injectIntl, intlShape } from 'react-intl';
|
|
|
|
import Button from '/imports/ui/components/button/component';
|
2018-11-16 04:00:11 +08:00
|
|
|
import { notify } from '/imports/ui/services/notification';
|
2018-11-17 01:59:15 +08:00
|
|
|
import logger from '/imports/startup/client/logger';
|
2018-12-03 14:01:19 +08:00
|
|
|
import Modal from '/imports/ui/components/modal/simple/component';
|
2018-11-07 07:10:56 +08:00
|
|
|
import { styles } from './styles';
|
|
|
|
|
2018-11-15 05:12:02 +08:00
|
|
|
const VIDEO_CONSTRAINTS = Meteor.settings.public.kurento.cameraConstraints;
|
|
|
|
|
2018-11-07 07:10:56 +08:00
|
|
|
const propTypes = {
|
|
|
|
intl: intlShape.isRequired,
|
|
|
|
closeModal: PropTypes.func.isRequired,
|
|
|
|
startSharing: PropTypes.func.isRequired,
|
|
|
|
changeWebcam: PropTypes.func.isRequired,
|
|
|
|
};
|
|
|
|
|
|
|
|
const intlMessages = defineMessages({
|
|
|
|
webcamSettingsTitle: {
|
|
|
|
id: 'app.videoPreview.webcamSettingsTitle',
|
|
|
|
description: 'Title for the video preview modal',
|
|
|
|
},
|
|
|
|
closeLabel: {
|
|
|
|
id: 'app.videoPreview.closeLabel',
|
|
|
|
description: 'Close button label',
|
|
|
|
},
|
|
|
|
webcamPreviewLabel: {
|
|
|
|
id: 'app.videoPreview.webcamPreviewLabel',
|
|
|
|
description: 'Webcam preview label',
|
|
|
|
},
|
|
|
|
cameraLabel: {
|
|
|
|
id: 'app.videoPreview.cameraLabel',
|
|
|
|
description: 'Camera dropdown label',
|
|
|
|
},
|
|
|
|
cancelLabel: {
|
|
|
|
id: 'app.videoPreview.cancelLabel',
|
|
|
|
description: 'Cancel button label',
|
|
|
|
},
|
|
|
|
startSharingLabel: {
|
|
|
|
id: 'app.videoPreview.startSharingLabel',
|
|
|
|
description: 'Start Sharing button label',
|
|
|
|
},
|
|
|
|
webcamOptionLabel: {
|
|
|
|
id: 'app.videoPreview.webcamOptionLabel',
|
|
|
|
description: 'Default webcam option label',
|
|
|
|
},
|
|
|
|
webcamNotFoundLabel: {
|
|
|
|
id: 'app.videoPreview.webcamNotFoundLabel',
|
|
|
|
description: 'Webcam not found label',
|
|
|
|
},
|
2018-12-18 01:45:57 +08:00
|
|
|
permissionError: {
|
|
|
|
id: 'app.video.permissionError',
|
|
|
|
description: 'Error message for webcam permission',
|
|
|
|
},
|
|
|
|
NotFoundError: {
|
|
|
|
id: 'app.video.notFoundError',
|
|
|
|
description: 'error message when can not get webcam video',
|
|
|
|
},
|
|
|
|
NotAllowedError: {
|
|
|
|
id: 'app.video.notAllowed',
|
|
|
|
description: 'error message when webcam had permission denied',
|
|
|
|
},
|
|
|
|
NotSupportedError: {
|
|
|
|
id: 'app.video.notSupportedError',
|
|
|
|
description: 'error message when origin do not have ssl valid',
|
|
|
|
},
|
|
|
|
NotReadableError: {
|
|
|
|
id: 'app.video.notReadableError',
|
|
|
|
description: 'error message When the webcam is being used by other software',
|
2018-11-16 04:00:11 +08:00
|
|
|
},
|
2018-11-07 07:10:56 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
class VideoPreview extends Component {
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
const {
|
2018-11-17 02:56:39 +08:00
|
|
|
webcamDeviceId,
|
2018-11-07 07:10:56 +08:00
|
|
|
} = props;
|
|
|
|
|
|
|
|
this.handleJoinVideo = this.handleJoinVideo.bind(this);
|
2018-11-16 03:38:04 +08:00
|
|
|
this.handleProceed = this.handleProceed.bind(this);
|
|
|
|
this.handleStartSharing = this.handleStartSharing.bind(this);
|
2018-11-08 05:16:36 +08:00
|
|
|
|
|
|
|
this.deviceStream = null;
|
2018-11-17 02:56:39 +08:00
|
|
|
|
|
|
|
this.state = {
|
2019-01-18 00:33:43 +08:00
|
|
|
webcamDeviceId,
|
2018-11-17 02:56:39 +08:00
|
|
|
availableWebcams: null,
|
|
|
|
isStartSharingDisabled: false,
|
|
|
|
};
|
2018-11-08 05:16:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
stopTracks() {
|
|
|
|
if (this.deviceStream) {
|
|
|
|
this.deviceStream.getTracks().forEach((track) => {
|
|
|
|
track.stop();
|
|
|
|
});
|
|
|
|
}
|
2018-11-07 07:10:56 +08:00
|
|
|
}
|
|
|
|
|
2018-12-18 01:45:57 +08:00
|
|
|
handlegUMError(error) {
|
|
|
|
const {
|
|
|
|
intl,
|
|
|
|
} = this.props;
|
|
|
|
const errorMessage = intlMessages[error.name]
|
|
|
|
|| intlMessages.permissionError;
|
|
|
|
notify(intl.formatMessage(errorMessage), 'error', 'video');
|
2019-02-02 03:12:06 +08:00
|
|
|
logger.error({ logCode: 'videopreview_component_gum_error' }, error);
|
2018-12-18 01:45:57 +08:00
|
|
|
}
|
|
|
|
|
2018-11-07 07:10:56 +08:00
|
|
|
handleSelectWebcam(event) {
|
2018-11-16 04:00:11 +08:00
|
|
|
const {
|
2019-01-18 00:33:43 +08:00
|
|
|
changeWebcam,
|
2018-11-16 04:00:11 +08:00
|
|
|
} = this.props;
|
|
|
|
|
2018-11-07 07:10:56 +08:00
|
|
|
const webcamValue = event.target.value;
|
|
|
|
this.setState({ webcamDeviceId: webcamValue });
|
2019-01-18 00:33:43 +08:00
|
|
|
changeWebcam(webcamValue);
|
2018-11-15 05:12:02 +08:00
|
|
|
VIDEO_CONSTRAINTS.deviceId = webcamValue ? { exact: webcamValue } : undefined;
|
2018-11-07 07:10:56 +08:00
|
|
|
const constraints = {
|
2018-11-15 05:12:02 +08:00
|
|
|
video: VIDEO_CONSTRAINTS,
|
2018-11-07 07:10:56 +08:00
|
|
|
};
|
2018-11-08 05:16:36 +08:00
|
|
|
this.stopTracks();
|
2018-11-07 07:10:56 +08:00
|
|
|
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
|
|
|
|
this.video.srcObject = stream;
|
2018-11-08 05:16:36 +08:00
|
|
|
this.deviceStream = stream;
|
|
|
|
}).catch((error) => {
|
2018-12-18 01:45:57 +08:00
|
|
|
this.handlegUMError(error);
|
2018-11-07 07:10:56 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-11-08 05:16:36 +08:00
|
|
|
handleStartSharing() {
|
2019-01-18 00:33:43 +08:00
|
|
|
const { resolve, startSharing } = this.props;
|
2018-11-08 05:16:36 +08:00
|
|
|
this.stopTracks();
|
2019-01-18 00:33:43 +08:00
|
|
|
startSharing();
|
2018-11-16 03:38:04 +08:00
|
|
|
if (resolve) resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
handleProceed() {
|
2019-01-18 00:33:43 +08:00
|
|
|
const { resolve, closeModal } = this.props;
|
|
|
|
closeModal();
|
2018-11-16 03:38:04 +08:00
|
|
|
if (resolve) resolve();
|
2018-11-08 05:16:36 +08:00
|
|
|
}
|
|
|
|
|
2018-11-07 07:10:56 +08:00
|
|
|
componentDidMount() {
|
2019-01-18 00:33:43 +08:00
|
|
|
const { webcamDeviceId, changeWebcam } = this.props;
|
2018-11-07 07:10:56 +08:00
|
|
|
const constraints = {
|
2018-11-15 05:12:02 +08:00
|
|
|
video: VIDEO_CONSTRAINTS,
|
2018-11-07 07:10:56 +08:00
|
|
|
};
|
2019-01-15 04:18:28 +08:00
|
|
|
|
|
|
|
navigator.mediaDevices.enumerateDevices().then((devices) => {
|
|
|
|
let isInitialDeviceSet = false;
|
|
|
|
const webcams = [];
|
|
|
|
|
|
|
|
// set webcam
|
|
|
|
if (webcamDeviceId) {
|
2019-01-18 02:53:32 +08:00
|
|
|
changeWebcam(webcamDeviceId);
|
2019-01-15 04:18:28 +08:00
|
|
|
this.setState({ webcamDeviceId });
|
|
|
|
isInitialDeviceSet = true;
|
|
|
|
}
|
|
|
|
devices.forEach((device) => {
|
|
|
|
if (device.kind === 'videoinput') {
|
|
|
|
if (!isInitialDeviceSet) {
|
2019-01-18 02:53:32 +08:00
|
|
|
changeWebcam(device.deviceId);
|
2019-01-15 04:18:28 +08:00
|
|
|
this.setState({ webcamDeviceId: device.deviceId });
|
|
|
|
isInitialDeviceSet = true;
|
|
|
|
}
|
2018-11-17 02:56:39 +08:00
|
|
|
}
|
2019-01-15 04:18:28 +08:00
|
|
|
});
|
|
|
|
if (webcams.length > 0) {
|
|
|
|
this.setState({ availableWebcams: webcams });
|
|
|
|
}
|
|
|
|
|
2019-01-24 02:45:36 +08:00
|
|
|
constraints.video.deviceId = { exact: this.state.webcamDeviceId };
|
2019-01-15 04:18:28 +08:00
|
|
|
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
|
|
|
|
// display the preview
|
|
|
|
this.video.srcObject = stream;
|
|
|
|
this.deviceStream = stream;
|
|
|
|
|
|
|
|
navigator.mediaDevices.enumerateDevices().then((devices) => {
|
|
|
|
// get the list of webcams (labels are available at this point)
|
|
|
|
devices.forEach((device) => {
|
|
|
|
if (device.kind === 'videoinput') {
|
|
|
|
webcams.push(device);
|
2018-11-07 07:10:56 +08:00
|
|
|
}
|
2019-01-15 04:18:28 +08:00
|
|
|
});
|
|
|
|
if (webcams.length > 0) {
|
|
|
|
this.setState({ availableWebcams: webcams });
|
2018-11-07 07:10:56 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
handleJoinVideo() {
|
|
|
|
const {
|
|
|
|
joinVideo,
|
|
|
|
} = this.props;
|
|
|
|
|
|
|
|
joinVideo();
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const {
|
|
|
|
intl,
|
|
|
|
} = this.props;
|
|
|
|
|
2019-01-18 00:33:43 +08:00
|
|
|
const {
|
|
|
|
webcamDeviceId,
|
|
|
|
availableWebcams,
|
|
|
|
isStartSharingDisabled,
|
|
|
|
} = this.state;
|
2018-11-07 07:10:56 +08:00
|
|
|
return (
|
2018-12-03 14:01:19 +08:00
|
|
|
<Modal
|
|
|
|
overlayClassName={styles.overlay}
|
|
|
|
className={styles.modal}
|
|
|
|
onRequestClose={this.handleProceed}
|
|
|
|
hideBorder
|
|
|
|
>
|
|
|
|
<div className={styles.title}>
|
|
|
|
{intl.formatMessage(intlMessages.webcamSettingsTitle)}
|
|
|
|
</div>
|
|
|
|
<div className={styles.content}>
|
|
|
|
<div className={styles.col}>
|
|
|
|
<video
|
|
|
|
id="preview"
|
|
|
|
className={styles.preview}
|
|
|
|
ref={(ref) => { this.video = ref; }}
|
|
|
|
autoPlay
|
|
|
|
playsInline
|
2018-11-07 07:10:56 +08:00
|
|
|
/>
|
2018-12-03 14:01:19 +08:00
|
|
|
</div>
|
2019-02-15 02:53:57 +08:00
|
|
|
<div className={styles}>
|
2018-12-03 14:01:19 +08:00
|
|
|
<label className={styles.label}>
|
|
|
|
{intl.formatMessage(intlMessages.cameraLabel)}
|
|
|
|
</label>
|
2019-01-18 00:33:43 +08:00
|
|
|
{availableWebcams && availableWebcams.length > 0 ? (
|
2018-12-03 14:01:19 +08:00
|
|
|
<select
|
2019-01-18 00:33:43 +08:00
|
|
|
value={webcamDeviceId}
|
2018-12-03 14:01:19 +08:00
|
|
|
className={styles.select}
|
|
|
|
onChange={this.handleSelectWebcam.bind(this)}
|
|
|
|
>
|
|
|
|
<option disabled>
|
|
|
|
{intl.formatMessage(intlMessages.webcamOptionLabel)}
|
|
|
|
</option>
|
2019-01-18 00:33:43 +08:00
|
|
|
{availableWebcams.map((webcam, index) => (
|
2018-12-03 14:01:19 +08:00
|
|
|
<option key={index} value={webcam.deviceId}>
|
|
|
|
{webcam.label}
|
2018-11-07 07:10:56 +08:00
|
|
|
</option>
|
2019-01-18 00:33:43 +08:00
|
|
|
))}
|
2018-12-03 14:01:19 +08:00
|
|
|
</select>
|
2019-01-18 00:33:43 +08:00
|
|
|
)
|
|
|
|
: (
|
|
|
|
<select
|
|
|
|
className={styles.select}
|
|
|
|
>
|
|
|
|
<option disabled>
|
|
|
|
{intl.formatMessage(intlMessages.webcamNotFoundLabel)}
|
|
|
|
</option>
|
|
|
|
</select>
|
|
|
|
)}
|
2018-11-07 07:10:56 +08:00
|
|
|
</div>
|
2018-12-03 14:01:19 +08:00
|
|
|
</div>
|
|
|
|
<div className={styles.footer}>
|
|
|
|
<div className={styles.actions}>
|
|
|
|
<Button
|
|
|
|
label={intl.formatMessage(intlMessages.cancelLabel)}
|
|
|
|
onClick={this.handleProceed}
|
|
|
|
/>
|
|
|
|
<Button
|
|
|
|
color="primary"
|
|
|
|
label={intl.formatMessage(intlMessages.startSharingLabel)}
|
|
|
|
onClick={() => this.handleStartSharing()}
|
2019-01-18 00:33:43 +08:00
|
|
|
disabled={isStartSharingDisabled}
|
2018-12-03 14:01:19 +08:00
|
|
|
/>
|
2018-11-07 07:10:56 +08:00
|
|
|
</div>
|
2018-12-03 14:01:19 +08:00
|
|
|
</div>
|
|
|
|
</Modal>
|
2018-11-07 07:10:56 +08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VideoPreview.propTypes = propTypes;
|
|
|
|
|
|
|
|
export default injectIntl(VideoPreview);
|