diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/ScreenshareModel.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/ScreenshareModel.scala index 49ee49939e..372d0842bd 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/ScreenshareModel.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/ScreenshareModel.scala @@ -91,6 +91,14 @@ object ScreenshareModel { def getHasAudio(status: ScreenshareModel): Boolean = { status.hasAudio } + + def setContentType(status: ScreenshareModel, contentType: String): Unit = { + status.contentType = contentType + } + + def getContentType(status: ScreenshareModel): String = { + status.contentType + } } class ScreenshareModel { @@ -103,4 +111,5 @@ class ScreenshareModel { private var screenshareConf: String = "" private var timestamp: String = "" private var hasAudio = false + private var contentType = "camera" } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/screenshare/GetScreenshareStatusReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/screenshare/GetScreenshareStatusReqMsgHdlr.scala index 1edea1b84c..9bc604776a 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/screenshare/GetScreenshareStatusReqMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/screenshare/GetScreenshareStatusReqMsgHdlr.scala @@ -26,9 +26,10 @@ trait GetScreenshareStatusReqMsgHdlr { val vidHeight = ScreenshareModel.getScreenshareVideoHeight(liveMeeting.screenshareModel) val timestamp = ScreenshareModel.getTimestamp(liveMeeting.screenshareModel) val hasAudio = ScreenshareModel.getHasAudio(liveMeeting.screenshareModel) + val contentType = ScreenshareModel.getContentType(liveMeeting.screenshareModel) val body = ScreenshareRtmpBroadcastStartedEvtMsgBody(voiceConf, screenshareConf, - stream, vidWidth, vidHeight, timestamp, hasAudio) + stream, vidWidth, vidHeight, timestamp, hasAudio, contentType) val event = ScreenshareRtmpBroadcastStartedEvtMsg(header, body) BbbCommonEnvCoreMsg(envelope, event) } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/screenshare/ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/screenshare/ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgHdlr.scala index 678d09eae8..a042204b28 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/screenshare/ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/screenshare/ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgHdlr.scala @@ -10,7 +10,7 @@ trait ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgHdlr { def handle(msg: ScreenshareRtmpBroadcastStartedVoiceConfEvtMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = { def broadcastEvent(voiceConf: String, screenshareConf: String, stream: String, vidWidth: Int, vidHeight: Int, - timestamp: String, hasAudio: Boolean): BbbCommonEnvCoreMsg = { + timestamp: String, hasAudio: Boolean, contentType: String): BbbCommonEnvCoreMsg = { val routing = Routing.addMsgToClientRouting( MessageTypes.BROADCAST_TO_MEETING, @@ -23,7 +23,7 @@ trait ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgHdlr { ) val body = ScreenshareRtmpBroadcastStartedEvtMsgBody(voiceConf, screenshareConf, - stream, vidWidth, vidHeight, timestamp, hasAudio) + stream, vidWidth, vidHeight, timestamp, hasAudio, contentType) val event = ScreenshareRtmpBroadcastStartedEvtMsg(header, body) BbbCommonEnvCoreMsg(envelope, event) } @@ -45,12 +45,13 @@ trait ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgHdlr { ScreenshareModel.setScreenshareConf(liveMeeting.screenshareModel, msg.body.screenshareConf) ScreenshareModel.setTimestamp(liveMeeting.screenshareModel, msg.body.timestamp) ScreenshareModel.setHasAudio(liveMeeting.screenshareModel, msg.body.hasAudio) + ScreenshareModel.setContentType(liveMeeting.screenshareModel, msg.body.contentType) log.info("START broadcast ALLOWED when isBroadcastingRTMP=false") // Notify viewers in the meeting that there's an rtmp stream to view val msgEvent = broadcastEvent(msg.body.voiceConf, msg.body.screenshareConf, msg.body.stream, - msg.body.vidWidth, msg.body.vidHeight, msg.body.timestamp, msg.body.hasAudio) + msg.body.vidWidth, msg.body.vidHeight, msg.body.timestamp, msg.body.hasAudio, msg.body.contentType) bus.outGW.send(msgEvent) } else { log.info("START broadcast NOT ALLOWED when isBroadcastingRTMP=true") diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/screenshare/SyncGetScreenshareInfoRespMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/screenshare/SyncGetScreenshareInfoRespMsgHdlr.scala index b49077328e..7e5c9d69de 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/screenshare/SyncGetScreenshareInfoRespMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/screenshare/SyncGetScreenshareInfoRespMsgHdlr.scala @@ -27,7 +27,8 @@ trait SyncGetScreenshareInfoRespMsgHdlr { ScreenshareModel.getScreenshareVideoWidth(liveMeeting.screenshareModel), ScreenshareModel.getScreenshareVideoHeight(liveMeeting.screenshareModel), ScreenshareModel.getTimestamp(liveMeeting.screenshareModel), - ScreenshareModel.getHasAudio(liveMeeting.screenshareModel) + ScreenshareModel.getHasAudio(liveMeeting.screenshareModel), + ScreenshareModel.getContentType(liveMeeting.screenshareModel) ) val event = SyncGetScreenshareInfoRespMsg(header, body) val msgEvent = BbbCommonEnvCoreMsg(envelope, event) diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/VoiceConfMsgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/VoiceConfMsgs.scala index 7e54df08ec..5532075273 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/VoiceConfMsgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/VoiceConfMsgs.scala @@ -17,7 +17,7 @@ case class ScreenshareRtmpBroadcastStartedVoiceConfEvtMsg( extends VoiceStandardMsg case class ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgBody(voiceConf: String, screenshareConf: String, stream: String, vidWidth: Int, vidHeight: Int, - timestamp: String, hasAudio: Boolean) + timestamp: String, hasAudio: Boolean, contentType: String) /** * Sent to clients to notify them of an RTMP stream starting. @@ -30,7 +30,7 @@ case class ScreenshareRtmpBroadcastStartedEvtMsg( extends BbbCoreMsg case class ScreenshareRtmpBroadcastStartedEvtMsgBody(voiceConf: String, screenshareConf: String, stream: String, vidWidth: Int, vidHeight: Int, - timestamp: String, hasAudio: Boolean) + timestamp: String, hasAudio: Boolean, contentType: String) /** * Sync screenshare state with bbb-html5 @@ -48,7 +48,8 @@ case class SyncGetScreenshareInfoRespMsgBody( vidWidth: Int, vidHeight: Int, timestamp: String, - hasAudio: Boolean + hasAudio: Boolean, + contentType: String ) /** diff --git a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js index fdc9e1cd43..b327a7c7bb 100755 --- a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js +++ b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js @@ -2,7 +2,7 @@ import Auth from '/imports/ui/services/auth'; import logger from '/imports/startup/client/logger'; import BridgeService from './service'; import ScreenshareBroker from '/imports/ui/services/bbb-webrtc-sfu/screenshare-broker'; -import { setSharingScreen, screenShareEndAlert } from '/imports/ui/components/screenshare/service'; +import { setIsSharing, screenShareEndAlert } from '/imports/ui/components/screenshare/service'; import { SCREENSHARING_ERRORS } from './errors'; import { shouldForceRelay } from '/imports/ui/services/bbb-webrtc-sfu/utils'; import MediaStreamUtils from '/imports/utils/media-stream-utils'; @@ -292,7 +292,7 @@ export default class KurentoScreenshareBridge { screenShareEndAlert(); } - share(stream, onFailure) { + share(stream, onFailure, contentType) { return new Promise(async (resolve, reject) => { this.onerror = onFailure; this.connectionAttempts += 1; @@ -322,6 +322,7 @@ export default class KurentoScreenshareBridge { userName: Auth.fullname, stream, hasAudio: this.hasAudio, + contentType: contentType, bitrate: BridgeService.BASE_BITRATE, offering: true, mediaServer: BridgeService.getMediaServerAdapter(), @@ -365,7 +366,7 @@ export default class KurentoScreenshareBridge { // component tracker to be extra sure we won't have any client-side state // inconsistency - prlanzarin if (this.broker && this.broker.role === SEND_ROLE && !this.reconnecting) { - setSharingScreen(false); + setIsSharing(false); } this.broker = null; } diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx index b1d2f1997f..19502d35dd 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx @@ -10,8 +10,9 @@ import Styled from './styles'; import { colorPrimary } from '/imports/ui/stylesheets/styled-components/palette'; import { PANELS, ACTIONS, LAYOUT_TYPE } from '../../layout/enums'; import { uniqueId } from '/imports/utils/string-utils'; -import { isPresentationEnabled } from '/imports/ui/services/features'; -import {isLayoutsEnabled} from '/imports/ui/services/features'; +import { isPresentationEnabled, isLayoutsEnabled } from '/imports/ui/services/features'; +import VideoPreviewContainer from '/imports/ui/components/video-preview/container'; +import { screenshareHasEnded } from '/imports/ui/components/screenshare/service'; const propTypes = { amIPresenter: PropTypes.bool.isRequired, @@ -95,6 +96,14 @@ const intlMessages = defineMessages({ id: 'app.actionsBar.actionsDropdown.layoutModal', description: 'Label for layouts selection button', }, + shareCameraAsContent: { + id: 'app.actionsBar.actionsDropdown.shareCameraAsContent', + description: 'Label for share camera as content', + }, + unshareCameraAsContent: { + id: 'app.actionsBar.actionsDropdown.unshareCameraAsContent', + description: 'Label for unshare camera as content', + }, }); const handlePresentationClick = () => Session.set('showUploadPresentationView', true); @@ -110,14 +119,18 @@ class ActionsDropdown extends PureComponent { this.state = { isExternalVideoModalOpen: false, isRandomUserSelectModalOpen: false, - isLayoutModalOpen: false, - } + isLayoutModalOpen: false, + isCameraAsContentModalOpen: false, + }; this.handleExternalVideoClick = this.handleExternalVideoClick.bind(this); this.makePresentationItems = this.makePresentationItems.bind(this); this.setExternalVideoModalIsOpen = this.setExternalVideoModalIsOpen.bind(this); this.setRandomUserSelectModalIsOpen = this.setRandomUserSelectModalIsOpen.bind(this); this.setLayoutModalIsOpen = this.setLayoutModalIsOpen.bind(this); + this.setCameraAsContentModalIsOpen = this.setCameraAsContentModalIsOpen.bind(this); + this.setPropsToPassModal = this.setPropsToPassModal.bind(this); + this.setForceOpen = this.setForceOpen.bind(this); } componentDidUpdate(prevProps) { @@ -147,6 +160,9 @@ class ActionsDropdown extends PureComponent { setPushLayout, showPushLayout, amIModerator, + isMobile, + hasCameraAsContent, + isCameraAsContentEnabled, } = this.props; const { @@ -244,7 +260,23 @@ class ActionsDropdown extends PureComponent { dataTest: 'layoutModal', }); } - + + if (isCameraAsContentEnabled && amIPresenter && !isMobile) { + actions.push({ + icon: hasCameraAsContent ? 'video_off' : 'video', + label: hasCameraAsContent + ? intl.formatMessage(intlMessages.unshareCameraAsContent) + : intl.formatMessage(intlMessages.shareCameraAsContent), + key: 'camera as content', + onClick: hasCameraAsContent + ? screenshareHasEnded + : () => { + screenshareHasEnded(); + this.setCameraAsContentModalIsOpen(true); + }, + }); + } + return actions; } @@ -294,6 +326,15 @@ class ActionsDropdown extends PureComponent { setLayoutModalIsOpen(value) { this.setState({isLayoutModalOpen: value}); } + setCameraAsContentModalIsOpen(value) { + this.setState({isCameraAsContentModalOpen: value}); + } + setPropsToPassModal(value) { + this.setState({propsToPassModal: value}); + } + setForceOpen(value){ + this.setState({forceOpen: value}); + } renderModal(isOpen, setIsOpen, priority, Component) { return isOpen ? ( + { + this.setPropsToPassModal({}); + this.setForceOpen(false); + }, + priority: 'low', + setIsOpen: this.setCameraAsContentModalIsOpen, + isOpen: isCameraAsContentModalOpen, + }} + {...propsToPassModal} + /> + ))} ); } diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/container.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/container.jsx index 798289ed24..c0b1d1290c 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/container.jsx @@ -28,6 +28,8 @@ const ActionsDropdownContainer = (props) => { ); }; +const ENABLE_CAMERA_AS_CONTENT = Meteor.settings.public.app.enableCameraAsContent; + export default withTracker(() => { const presentations = Presentations.find({ 'conversion.done': true }).fetch(); return ({ @@ -35,5 +37,6 @@ export default withTracker(() => { isDropdownOpen: Session.get('dropdownOpen'), setPresentation: PresentationUploaderService.setPresentation, podIds: PresentationPodService.getPresentationPodIds(), + isCameraAsContentEnabled: ENABLE_CAMERA_AS_CONTENT, }); })(ActionsDropdownContainer); diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx index 9c0cd6ce55..3d6b74ae70 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx @@ -39,6 +39,8 @@ class ActionsBar extends PureComponent { isSharingVideo, isSharedNotesPinned, hasScreenshare, + hasGenericContent, + hasCameraAsContent, stopExternalVideoShare, isCaptionsAvailable, isMeteorConnected, @@ -84,6 +86,7 @@ class ActionsBar extends PureComponent { setPushLayout, presentationIsOpen, showPushLayout, + hasCameraAsContent, }} /> {isCaptionsAvailable @@ -133,6 +136,8 @@ class ActionsBar extends PureComponent { hasExternalVideo={isSharingVideo} hasScreenshare={hasScreenshare} hasPinnedSharedNotes={isSharedNotesPinned} + hasGenericContent={hasGenericContent} + hasCameraAsContent={hasCameraAsContent} /> : null } diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx index 52b2ca3e3b..eeb1551ff6 100644 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/container.jsx @@ -15,6 +15,7 @@ import CaptionsService from '/imports/ui/components/captions/service'; import { layoutSelectOutput, layoutDispatch } from '../layout/context'; import { isVideoBroadcasting } from '/imports/ui/components/screenshare/service'; import { isExternalVideoEnabled, isPollingEnabled, isPresentationEnabled } from '/imports/ui/services/features'; +import { isScreenBroadcasting, isCameraAsContentBroadcasting } from '/imports/ui/components/screenshare/service'; import MediaService from '../media/service'; @@ -58,7 +59,8 @@ export default withTracker(() => ({ parseCurrentSlideContent: PresentationService.parseCurrentSlideContent, isSharingVideo: Service.isSharingVideo(), isSharedNotesPinned: Service.isSharedNotesPinned(), - hasScreenshare: isVideoBroadcasting(), + hasScreenshare: isScreenBroadcasting(), + hasCameraAsContent: isCameraAsContentBroadcasting(), isCaptionsAvailable: CaptionsService.isCaptionsAvailable(), isMeteorConnected: Meteor.status().connected, isPollingEnabled: isPollingEnabled() && isPresentationEnabled(), diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/presentation-options/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/presentation-options/component.jsx index de88e2e6e9..fabbe6b787 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/presentation-options/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/presentation-options/component.jsx @@ -39,6 +39,8 @@ const PresentationOptionsContainer = ({ hasExternalVideo, hasScreenshare, hasPinnedSharedNotes, + hasGenericContent, + hasCameraAsContent, }) => { let buttonType = 'presentation'; if (hasExternalVideo) { @@ -46,10 +48,13 @@ const PresentationOptionsContainer = ({ buttonType = 'external-video'; } else if (hasScreenshare) { buttonType = 'desktop'; + } else if (hasCameraAsContent) { + buttonType = 'video'; } const isThereCurrentPresentation = hasExternalVideo || hasScreenshare - || hasPresentation || hasPinnedSharedNotes; + || hasPresentation || hasPinnedSharedNotes + || hasGenericContent || hasCameraAsContent; return (