diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx index 7c7925eac0613794cda6e726ed5dd8679d9dce2e..2708acbea7e346a8746a9c314d3a1244cd2e41f7 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx @@ -13,8 +13,6 @@ class ActionsBar extends PureComponent { render() { const { amIPresenter, - handleExitVideo, - handleJoinVideo, handleShareScreen, handleUnshareScreen, isVideoBroadcasting, @@ -82,10 +80,7 @@ class ActionsBar extends PureComponent { <AudioControlsContainer /> {enableVideo ? ( - <JoinVideoOptionsContainer - handleJoinVideo={handleJoinVideo} - handleCloseVideo={handleExitVideo} - /> + <JoinVideoOptionsContainer /> ) : null} <DesktopShare {...{ diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx index 789b17db65b432fb68f0583d7cd548911d8b3d7a..f75cf3d683bbec1cff4d92ddc7d4fc99f8ed7c89 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx @@ -8,7 +8,6 @@ import PresentationService from '/imports/ui/components/presentation/service'; import Presentations from '/imports/api/presentations'; import ActionsBar from './component'; import Service from './service'; -import VideoService from '../video-provider/service'; import ExternalVideoService from '/imports/ui/components/external-video-player/service'; import CaptionsService from '/imports/ui/components/captions/service'; import { @@ -31,8 +30,6 @@ export default withTracker(() => ({ amIPresenter: Service.amIPresenter(), amIModerator: Service.amIModerator(), stopExternalVideoShare: ExternalVideoService.stopWatching, - handleExitVideo: () => VideoService.exitVideo(), - handleJoinVideo: () => VideoService.joinVideo(), handleShareScreen: onFail => shareScreen(onFail), handleUnshareScreen: () => unshareScreen(), isVideoBroadcasting: isVideoBroadcasting(), diff --git a/bigbluebutton-html5/imports/ui/components/video-preview/component.jsx b/bigbluebutton-html5/imports/ui/components/video-preview/component.jsx index ad26ed98ad5bf6df7ceaed8c53a9a8174248226a..4023d0ba77ade296ea04672e684e789e439b58f2 100755 --- a/bigbluebutton-html5/imports/ui/components/video-preview/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-preview/component.jsx @@ -7,6 +7,7 @@ import Button from '/imports/ui/components/button/component'; // import { notify } from '/imports/ui/services/notification'; import logger from '/imports/startup/client/logger'; import Modal from '/imports/ui/components/modal/simple/component'; +import VideoService from '../video-provider/service'; import browser from 'browser-detect'; import { styles } from './styles'; @@ -62,7 +63,15 @@ const intlMessages = defineMessages({ }, startSharingLabel: { id: 'app.videoPreview.startSharingLabel', - description: 'Start Sharing button label', + description: 'Start sharing button label', + }, + stopSharingLabel: { + id: 'app.videoPreview.stopSharingLabel', + description: 'Stop sharing button label', + }, + sharedCameraLabel: { + id: 'app.videoPreview.sharedCameraLabel', + description: 'Already Shared camera label', }, findingWebcamsLabel: { id: 'app.videoPreview.findingWebcamsLabel', @@ -187,6 +196,8 @@ class VideoPreview extends Component { deviceError: null, previewError: null, }; + + this.userParameterProfile = VideoService.getUserParameterProfile(); } componentDidMount() { @@ -225,8 +236,7 @@ class VideoPreview extends Component { // set webcam devices.forEach((device) => { - const shared = sharedDevices.includes(device.deviceId); - if (device.kind === 'videoinput' && !shared) { + if (device.kind === 'videoinput') { webcams.push(device); if (!initialDeviceId || (webcamDeviceId && webcamDeviceId === device.deviceId) @@ -347,11 +357,11 @@ class VideoPreview extends Component { if (resolve) resolve(); } - handleStopSharing(event) { + handleStopSharing() { const { resolve, stopSharing } = this.props; - const { target } = event; + const { webcamDeviceId } = this.state; this.stopTracks(); - stopSharing(target.value); + stopSharing(webcamDeviceId); if (resolve) resolve(); } @@ -363,10 +373,7 @@ class VideoPreview extends Component { } displayInitialPreview(deviceId) { - const { - changeWebcam, - userParameterProfile, - } = this.props; + const { changeWebcam } = this.props; const availableProfiles = CAMERA_PROFILES; this.setState({ @@ -377,7 +384,7 @@ class VideoPreview extends Component { changeWebcam(deviceId); if (availableProfiles.length > 0) { - const defaultProfile = availableProfiles.find(profile => profile.id === userParameterProfile) + const defaultProfile = availableProfiles.find(profile => profile.id === this.userParameterProfile) || availableProfiles.find(profile => profile.default) || availableProfiles[0]; this.displayPreview(deviceId, defaultProfile); @@ -453,7 +460,8 @@ class VideoPreview extends Component { renderDeviceSelectors() { const { intl, - skipVideoPreview + skipVideoPreview, + sharedDevices } = this.props; const { @@ -463,100 +471,75 @@ class VideoPreview extends Component { selectedProfile, } = this.state; + const shared = sharedDevices.includes(webcamDeviceId); + return ( <div className={styles.col}> <label className={styles.label} htmlFor="setCam"> {intl.formatMessage(intlMessages.cameraLabel)} </label> - { - availableWebcams && availableWebcams.length > 0 - ? ( - <select - id="setCam" - value={webcamDeviceId || ''} - className={styles.select} - onChange={this.handleSelectWebcam} - disabled={skipVideoPreview} - > - {availableWebcams.map(webcam => ( - <option key={webcam.deviceId} value={webcam.deviceId}> - {webcam.label} - </option> - ))} - </select> - ) - : ( - <span> - {intl.formatMessage(intlMessages.webcamNotFoundLabel)} - </span> - ) + { availableWebcams && availableWebcams.length > 0 + ? ( + <select + id="setCam" + value={webcamDeviceId || ''} + className={styles.select} + onChange={this.handleSelectWebcam} + disabled={skipVideoPreview} + > + {availableWebcams.map(webcam => ( + <option key={webcam.deviceId} value={webcam.deviceId}> + {webcam.label} + </option> + ))} + </select> + ) + : ( + <span> + {intl.formatMessage(intlMessages.webcamNotFoundLabel)} + </span> + ) } - <label className={styles.label} htmlFor="setQuality"> - {intl.formatMessage(intlMessages.qualityLabel)} - </label> - { - availableProfiles && availableProfiles.length > 0 - ? ( - <select - id="setQuality" - value={selectedProfile || ''} - className={styles.select} - onChange={this.handleSelectProfile} - disabled={skipVideoPreview} - > - {availableProfiles.map(profile => ( - <option key={profile.id} value={profile.id}> - {profile.name} - </option> - ))} - </select> - ) - : ( - <span> - {intl.formatMessage(intlMessages.profileNotFoundLabel)} - </span> - ) + { shared + ? ( + <span className={styles.label}> + {intl.formatMessage(intlMessages.sharedCameraLabel)} + </span> + ) + : ( + <span> + <label className={styles.label} htmlFor="setQuality"> + {intl.formatMessage(intlMessages.qualityLabel)} + </label> + { availableProfiles && availableProfiles.length > 0 + ? ( + <select + id="setQuality" + value={selectedProfile || ''} + className={styles.select} + onChange={this.handleSelectProfile} + disabled={skipVideoPreview} + > + {availableProfiles.map(profile => ( + <option key={profile.id} value={profile.id}> + {profile.name} + </option> + ))} + </select> + ) + : ( + <span> + {intl.formatMessage(intlMessages.profileNotFoundLabel)} + </span> + ) + } + </span> + ) } </div> ); } - renderSharedDeviceSelectors() { - const { - intl, - sharedDevices, - } = this.props; - - if (sharedDevices && sharedDevices.length == 0) return null; - - return ( - <div className={styles.col}> - <label className={styles.label} htmlFor="stopCam"> - Stop Camera - </label> - <select - id="stopCam" - value={'default'} - className={styles.select} - onChange={this.handleStopSharing} - > - <option - disabled - key={-1} - value={'default'} - > - Shared Cameras - </option> - {sharedDevices.map(deviceId => ( - <option key={deviceId} value={deviceId}> - {deviceId} - </option> - ))} - </select> - </div> - ); - } - renderContent() { const { intl, @@ -609,7 +592,6 @@ class VideoPreview extends Component { } </div> {this.renderDeviceSelectors()} - {this.renderSharedDeviceSelectors()} </div> ); } @@ -619,14 +601,19 @@ class VideoPreview extends Component { const { intl, skipVideoPreview, + sharedDevices, } = this.props; const { isStartSharingDisabled, + webcamDeviceId, deviceError, previewError, } = this.state; const shouldDisableButtons = skipVideoPreview && !(deviceError || previewError); + + const shared = sharedDevices.includes(webcamDeviceId); + return ( <div> {browser().name === 'edge' || browser().name === 'ie' ? ( @@ -655,9 +642,9 @@ class VideoPreview extends Component { disabled={shouldDisableButtons} /> <Button - color="primary" - label={intl.formatMessage(intlMessages.startSharingLabel)} - onClick={this.handleStartSharing} + color={shared ? "danger" : "primary"} + label={intl.formatMessage(shared ? intlMessages.stopSharingLabel : intlMessages.startSharingLabel)} + onClick={shared ? this.handleStopSharing : this.handleStartSharing} disabled={isStartSharingDisabled || isStartSharingDisabled === null || shouldDisableButtons} /> </div> diff --git a/bigbluebutton-html5/imports/ui/components/video-preview/container.jsx b/bigbluebutton-html5/imports/ui/components/video-preview/container.jsx index 45f87d8ff45eb26e6f28931b020226b3013afa9f..05e72a1bfa2a256a968ffa3b19845749e71e7b09 100755 --- a/bigbluebutton-html5/imports/ui/components/video-preview/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-preview/container.jsx @@ -2,15 +2,9 @@ import React from 'react'; import { withModalMounter } from '/imports/ui/components/modal/service'; import { withTracker } from 'meteor/react-meteor-data'; import deviceInfo from '/imports/utils/deviceInfo'; -import getFromUserSettings from '/imports/ui/services/users-settings'; import Service from './service'; import VideoPreview from './component'; import VideoService from '../video-provider/service'; -import Auth from '/imports/ui/services/auth'; - -const KURENTO_CONFIG = Meteor.settings.public.kurento; -const CAMERA_PROFILES = KURENTO_CONFIG.cameraProfiles; -const SKIP_VIDEO_PREVIEW = KURENTO_CONFIG.skipVideoPreview; const VideoPreviewContainer = props => <VideoPreview {...props} />; @@ -21,7 +15,8 @@ export default withModalMounter(withTracker(({ mountModal, fromInterface }) => ( }, stopSharing: deviceId => { mountModal(null); - VideoService.stopVideo(`${Auth.userID}_${deviceId}`); + const stream = VideoService.getMyStream(deviceId); + if (stream) VideoService.stopVideo(stream); }, sharedDevices: VideoService.getSharedDevices(), closeModal: () => mountModal(null), @@ -29,6 +24,5 @@ export default withModalMounter(withTracker(({ mountModal, fromInterface }) => ( webcamDeviceId: Service.webcamDeviceId(), changeProfile: profileId => Service.changeProfile(profileId), hasMediaDevices: deviceInfo.hasMediaDevices, - userParameterProfile: getFromUserSettings('bbb_preferred_camera_profile', (CAMERA_PROFILES.filter(i => i.default) || {}).id), - skipVideoPreview: (getFromUserSettings('bbb_skip_video_preview', false) || SKIP_VIDEO_PREVIEW) && !fromInterface, + skipVideoPreview: VideoService.getSkipVideoPreview(fromInterface), }))(VideoPreviewContainer)); diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/service.js b/bigbluebutton-html5/imports/ui/components/video-provider/service.js index c754bc7d53ad554a163f5443d6cab27aa8bc0581..8ad00634b9455cc4ff708490741304b4eeca4c14 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/service.js +++ b/bigbluebutton-html5/imports/ui/components/video-provider/service.js @@ -9,9 +9,12 @@ import UserListService from '/imports/ui/components/user-list/service'; import { makeCall } from '/imports/ui/services/api'; import { notify } from '/imports/ui/services/notification'; import { monitorVideoConnection } from '/imports/utils/stats'; +import getFromUserSettings from '/imports/ui/services/users-settings'; import logger from '/imports/startup/client/logger'; const CAMERA_PROFILES = Meteor.settings.public.kurento.cameraProfiles; +const MULTIPLE_CAMERAS = Meteor.settings.public.app.enableMultipleCameras; +const SKIP_VIDEO_PREVIEW = Meteor.settings.public.kurento.skipVideoPreview; const SFU_URL = Meteor.settings.public.kurento.wsUrl; const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; @@ -25,6 +28,11 @@ class VideoService { isConnecting: false, isConnected: false, }); + this.skipVideoPreview = getFromUserSettings('bbb_skip_video_preview', false) || SKIP_VIDEO_PREVIEW; + this.userParameterProfile = getFromUserSettings( + 'bbb_preferred_camera_profile', + (CAMERA_PROFILES.filter(i => i.default) || {}).id + ); } defineProperties(obj) { @@ -204,6 +212,17 @@ class VideoService { }; } + getMyStream(deviceId) { + const videoStream = VideoStreams.findOne( + { + meetingId: Auth.meetingID, + userId: Auth.userID, + deviceId: deviceId + }, { fields: { stream: 1 } } + ); + return videoStream ? videoStream.stream : null; + } + isUserLocked() { return !!Users.findOne({ userId: Auth.userID, @@ -282,6 +301,19 @@ class VideoService { return isLocal ? 'share' : 'viewer'; } + getSkipVideoPreview(fromInterface) { + return this.skipVideoPreview && !fromInterface; + } + + getUserParameterProfile() { + return this.userParameterProfile; + } + + isMultipleCamerasEnabled() { + // Multiple cameras shouldn't be enabled with video preview skipping + return MULTIPLE_CAMERAS && !this.skipVideoPreview; + } + monitor(conn) { if (ENABLE_NETWORK_MONITORING) monitorVideoConnection(conn); } @@ -295,6 +327,7 @@ export default { stopVideo: cameraId => videoService.stopVideo(cameraId), getVideoStreams: () => videoService.getVideoStreams(), getInfo: () => videoService.getInfo(), + getMyStream: deviceId => videoService.getMyStream(deviceId), isUserLocked: () => videoService.isUserLocked(), lockUser: () => videoService.lockUser(), getAuthenticatedURL: () => videoService.getAuthenticatedURL(), @@ -307,6 +340,9 @@ export default { processInboundIceQueue: (peer, cameraId) => videoService.processInboundIceQueue(peer, cameraId), getRole: isLocal => videoService.getRole(isLocal), getSharedDevices: () => videoService.getSharedDevices(), + getSkipVideoPreview: fromInterface => videoService.getSkipVideoPreview(fromInterface), + getUserParameterProfile: () => videoService.getUserParameterProfile(), + isMultipleCamerasEnabled: () => videoService.isMultipleCamerasEnabled(), monitor: conn => videoService.monitor(conn), onBeforeUnload: () => videoService.onBeforeUnload(), notify: message => notify(message, 'error', 'video'), diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-button/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-button/component.jsx index 8be3ea1e52e0dec5667b0bcec557fc5c3dfe906f..488328eed7c687559fcac5d82dcd11d9f40873f8 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-button/component.jsx @@ -12,6 +12,10 @@ const intlMessages = defineMessages({ id: 'app.video.joinVideo', description: 'Join video button label', }, + leaveVideo: { + id: 'app.video.leaveVideo', + description: 'Leave video button label', + }, videoButtonDesc: { id: 'app.video.videoButtonDesc', description: 'video button description', @@ -30,30 +34,36 @@ const propTypes = { intl: intlShape.isRequired, hasVideoStream: PropTypes.bool.isRequired, isDisabled: PropTypes.bool.isRequired, - handleJoinVideo: PropTypes.func.isRequired, + mountVideoPreview: PropTypes.func.isRequired, }; const JoinVideoButton = ({ intl, hasVideoStream, isDisabled, - handleJoinVideo, + mountVideoPreview, }) => { + const exitVideo = () => hasVideoStream && !VideoService.isMultipleCamerasEnabled(); + const handleOnClick = () => { if (!validIOSVersion()) { return VideoService.notify(intl.formatMessage(intlMessages.iOSWarning)); } - handleJoinVideo(); - }; - const sharingVideoLabel = intl.formatMessage(intlMessages.joinVideo); + if (exitVideo()) { + VideoService.exitVideo(); + } else { + mountVideoPreview(); + } + }; - const disabledLabel = isDisabled - ? intl.formatMessage(intlMessages.videoLocked) : sharingVideoLabel; + const label = exitVideo() ? + intl.formatMessage(intlMessages.leaveVideo) : + intl.formatMessage(intlMessages.joinVideo); return ( <Button - label={disabledLabel} + label={isDisabled ? intl.formatMessage(intlMessages.videoLocked) : label} className={cx(styles.button, hasVideoStream || styles.btn)} onClick={handleOnClick} hideLabel @@ -69,4 +79,5 @@ const JoinVideoButton = ({ }; JoinVideoButton.propTypes = propTypes; + export default injectIntl(memo(JoinVideoButton)); diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-button/container.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-button/container.jsx index bd6f7ef2ae3c0f015dc6ff01444504d9ed3aff09..28b44f9707f7e54382090a0c3faa5b7c3a196997 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/video-button/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-button/container.jsx @@ -10,17 +10,16 @@ const JoinVideoOptionsContainer = (props) => { const { hasVideoStream, isDisabled, - handleJoinVideo, intl, mountModal, ...restProps } = props; - const mountPreview = () => { mountModal(<VideoPreviewContainer fromInterface />); }; + const mountVideoPreview = () => { mountModal(<VideoPreviewContainer fromInterface />); }; return ( <JoinVideoButton {...{ - handleJoinVideo: mountPreview, hasVideoStream, isDisabled, ...restProps, + mountVideoPreview, hasVideoStream, isDisabled, ...restProps, }} /> ); diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 46a59ac168f0c068745fac8b623cec4caf7173d8..b4a102a77fe00b5867bd3a50ca1ffb9883fe25c8 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -21,6 +21,7 @@ public: allowUserLookup: false enableNetworkInformation: false enableLimitOfViewersInWebcam: false + enableMultipleCameras: false enableTalkingIndicator: true viewersInWebcam: 8 ipv4FallbackDomain: "" diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json index 629b63ce7691f2e3f665924f03f40af2e7171e6d..67cde7c700982b8124d62c86a029f3201abc8b43 100755 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/private/locales/en.json @@ -536,6 +536,8 @@ "app.videoPreview.closeLabel": "Close", "app.videoPreview.findingWebcamsLabel": "Finding webcams", "app.videoPreview.startSharingLabel": "Start sharing", + "app.videoPreview.stopSharingLabel": "Stop sharing", + "app.videoPreview.sharedCameraLabel": "This camera is already being shared", "app.videoPreview.webcamOptionLabel": "Choose webcam", "app.videoPreview.webcamPreviewLabel": "Webcam preview", "app.videoPreview.webcamSettingsTitle": "Webcam settings",