Merge pull request #17746 from Carloshsc/port-present-webcam-27
feat(camera as content): present webcam
This commit is contained in:
commit
1a1f442d5a
@ -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"
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 ? <Component
|
||||
@ -316,10 +357,15 @@ class ActionsDropdown extends PureComponent {
|
||||
isMobile,
|
||||
isRTL,
|
||||
isSelectRandomUserEnabled,
|
||||
propsToPassModal,
|
||||
} = this.props;
|
||||
|
||||
const { isExternalVideoModalOpen,
|
||||
isRandomUserSelectModalOpen, isLayoutModalOpen } = this.state;
|
||||
const {
|
||||
isExternalVideoModalOpen,
|
||||
isRandomUserSelectModalOpen,
|
||||
isLayoutModalOpen,
|
||||
isCameraAsContentModalOpen,
|
||||
} = this.state;
|
||||
|
||||
const availableActions = this.getAvailableActions();
|
||||
const availablePresentations = this.makePresentationItems();
|
||||
@ -368,8 +414,25 @@ class ActionsDropdown extends PureComponent {
|
||||
ExternalVideoModal)}
|
||||
{(amIPresenter && isSelectRandomUserEnabled) ? this.renderModal(isRandomUserSelectModalOpen, this.setRandomUserSelectModalIsOpen,
|
||||
"low", RandomUserSelectContainer) : null }
|
||||
{this.renderModal(isLayoutModalOpen, this.setLayoutModalIsOpen,
|
||||
{this.renderModal(isLayoutModalOpen, this.setLayoutModalIsOpen,
|
||||
"low", LayoutModalContainer)}
|
||||
{this.renderModal(isCameraAsContentModalOpen, this.setCameraAsContentModalIsOpen,
|
||||
'low', () => (
|
||||
<VideoPreviewContainer
|
||||
cameraAsContent
|
||||
amIPresenter
|
||||
{...{
|
||||
callbackToClose: () => {
|
||||
this.setPropsToPassModal({});
|
||||
this.setForceOpen(false);
|
||||
},
|
||||
priority: 'low',
|
||||
setIsOpen: this.setCameraAsContentModalIsOpen,
|
||||
isOpen: isCameraAsContentModalOpen,
|
||||
}}
|
||||
{...propsToPassModal}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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 (
|
||||
<Button
|
||||
icon={`${buttonType}${!presentationIsOpen ? '_off' : ''}`}
|
||||
|
@ -22,7 +22,7 @@ const propTypes = {
|
||||
intl: PropTypes.objectOf(Object).isRequired,
|
||||
enabled: PropTypes.bool.isRequired,
|
||||
amIPresenter: PropTypes.bool.isRequired,
|
||||
isVideoBroadcasting: PropTypes.bool.isRequired,
|
||||
isScreenBroadcasting: PropTypes.bool.isRequired,
|
||||
isMeteorConnected: PropTypes.bool.isRequired,
|
||||
screenshareDataSavingSetting: PropTypes.bool.isRequired,
|
||||
};
|
||||
@ -114,7 +114,7 @@ const getErrorLocale = (errorCode) => {
|
||||
const ScreenshareButton = ({
|
||||
intl,
|
||||
enabled,
|
||||
isVideoBroadcasting,
|
||||
isScreenBroadcasting,
|
||||
amIPresenter,
|
||||
isMeteorConnected,
|
||||
}) => {
|
||||
@ -155,34 +155,35 @@ const ScreenshareButton = ({
|
||||
|
||||
const screenshareLabel = intlMessages.desktopShareLabel;
|
||||
|
||||
const vLabel = isVideoBroadcasting
|
||||
const vLabel = isScreenBroadcasting
|
||||
? intlMessages.stopDesktopShareLabel : screenshareLabel;
|
||||
|
||||
const vDescr = isVideoBroadcasting
|
||||
const vDescr = isScreenBroadcasting
|
||||
? intlMessages.stopDesktopShareDesc : intlMessages.desktopShareDesc;
|
||||
const amIBroadcasting = isScreenBroadcasting && amIPresenter;
|
||||
|
||||
const shouldAllowScreensharing = enabled
|
||||
&& ( !isMobile || isTabletApp)
|
||||
&& amIPresenter;
|
||||
|
||||
const dataTest = isVideoBroadcasting ? 'stopScreenShare' : 'startScreenShare';
|
||||
const dataTest = isScreenBroadcasting ? 'stopScreenShare' : 'startScreenShare';
|
||||
|
||||
return <>
|
||||
{
|
||||
shouldAllowScreensharing
|
||||
? (
|
||||
<Button
|
||||
disabled={(!isMeteorConnected && !isVideoBroadcasting)}
|
||||
icon={isVideoBroadcasting ? 'desktop' : 'desktop_off'}
|
||||
disabled={(!isMeteorConnected && !isScreenBroadcasting)}
|
||||
icon={amIBroadcasting ? 'desktop' : 'desktop_off'}
|
||||
data-test={dataTest}
|
||||
label={intl.formatMessage(vLabel)}
|
||||
description={intl.formatMessage(vDescr)}
|
||||
color={isVideoBroadcasting ? 'primary' : 'default'}
|
||||
ghost={!isVideoBroadcasting}
|
||||
color={amIBroadcasting ? 'primary' : 'default'}
|
||||
ghost={!amIBroadcasting}
|
||||
hideLabel
|
||||
circle
|
||||
size="lg"
|
||||
onClick={isVideoBroadcasting
|
||||
onClick={amIBroadcasting
|
||||
? screenshareHasEnded
|
||||
: () => {
|
||||
if (isSafari && !ScreenshareBridgeService.HAS_DISPLAY_MEDIA) {
|
||||
@ -191,7 +192,7 @@ const ScreenshareButton = ({
|
||||
shareScreen(amIPresenter, handleFailure);
|
||||
}
|
||||
}}
|
||||
id={isVideoBroadcasting ? 'unshare-screen-button' : 'share-screen-button'}
|
||||
id={amIBroadcasting ? 'unshare-screen-button' : 'share-screen-button'}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { withTracker } from 'meteor/react-meteor-data';
|
||||
import ScreenshareButton from './component';
|
||||
import { isScreenSharingEnabled } from '/imports/ui/services/features';
|
||||
import {
|
||||
isVideoBroadcasting,
|
||||
isScreenBroadcasting,
|
||||
dataSavingSetting,
|
||||
} from '/imports/ui/components/screenshare/service';
|
||||
|
||||
@ -11,14 +11,14 @@ const ScreenshareButtonContainer = (props) => <ScreenshareButton {...props} />;
|
||||
|
||||
/*
|
||||
* All props, including the ones that are inherited from actions-bar
|
||||
* isVideoBroadcasting,
|
||||
* isScreenBroadcasting,
|
||||
* amIPresenter,
|
||||
* screenSharingCheck,
|
||||
* isMeteorConnected,
|
||||
* screenshareDataSavingSetting,
|
||||
*/
|
||||
export default withTracker(() => ({
|
||||
isVideoBroadcasting: isVideoBroadcasting(),
|
||||
isScreenBroadcasting: isScreenBroadcasting(),
|
||||
screenshareDataSavingSetting: dataSavingSetting(),
|
||||
enabled: isScreenSharingEnabled(),
|
||||
}))(ScreenshareButtonContainer);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import Presentations from '/imports/api/presentations';
|
||||
import { isScreenBroadcasting, isCameraAsContentBroadcasting } from '/imports/ui/components/screenshare/service';
|
||||
import Settings from '/imports/ui/services/settings';
|
||||
import getFromUserSettings from '/imports/ui/services/users-settings';
|
||||
import { isExternalVideoEnabled, isScreenSharingEnabled } from '/imports/ui/services/features';
|
||||
@ -31,7 +32,8 @@ function shouldShowWhiteboard() {
|
||||
|
||||
function shouldShowScreenshare() {
|
||||
const { viewScreenshare } = Settings.dataSaving;
|
||||
return isScreenSharingEnabled() && (viewScreenshare || UserService.isUserPresenter()) && isVideoBroadcasting();
|
||||
return isScreenSharingEnabled() && (viewScreenshare || UserService.isUserPresenter())
|
||||
&& (isScreenBroadcasting() || isCameraAsContentBroadcasting());
|
||||
}
|
||||
|
||||
function shouldShowExternalVideo() {
|
||||
@ -53,7 +55,6 @@ const setPresentationIsOpen = (layoutContextDispatch, value) => {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const isThereWebcamOn = (meetingID) => {
|
||||
return VideoStreams.find({
|
||||
meetingId: meetingID
|
||||
@ -63,9 +64,8 @@ const isThereWebcamOn = (meetingID) => {
|
||||
const buildLayoutWhenPresentationAreaIsDisabled = (layoutContextDispatch) => {
|
||||
const isSharingVideo = getVideoUrl();
|
||||
const isSharedNotesPinned = NotesService.isSharedNotesPinned();
|
||||
const hasScreenshare = isVideoBroadcasting();
|
||||
const hasScreenshare = isScreenSharingEnabled();
|
||||
const isThereWebcam = isThereWebcamOn(Auth.meetingID);
|
||||
|
||||
const isGeneralMediaOff = !hasScreenshare && !isSharedNotesPinned && !isSharingVideo
|
||||
const webcamIsOnlyContent = isThereWebcam && isGeneralMediaOff;
|
||||
const isThereNoMedia = !isThereWebcam && isGeneralMediaOff;
|
||||
@ -84,7 +84,8 @@ export default {
|
||||
shouldShowScreenshare,
|
||||
shouldShowExternalVideo,
|
||||
shouldShowOverlay,
|
||||
isVideoBroadcasting,
|
||||
isScreenBroadcasting,
|
||||
isCameraAsContentBroadcasting,
|
||||
setPresentationIsOpen,
|
||||
shouldShowSharedNotes,
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import { debounce } from 'radash';
|
||||
import FullscreenButtonContainer from '/imports/ui/components/common/fullscreen-button/container';
|
||||
@ -32,40 +32,6 @@ import Settings from '/imports/ui/services/settings';
|
||||
import deviceInfo from '/imports/utils/deviceInfo';
|
||||
import { uniqueId } from '/imports/utils/string-utils';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
screenShareLabel: {
|
||||
id: 'app.screenshare.screenShareLabel',
|
||||
description: 'screen share area element label',
|
||||
},
|
||||
presenterLoadingLabel: {
|
||||
id: 'app.screenshare.presenterLoadingLabel',
|
||||
},
|
||||
viewerLoadingLabel: {
|
||||
id: 'app.screenshare.viewerLoadingLabel',
|
||||
},
|
||||
presenterSharingLabel: {
|
||||
id: 'app.screenshare.presenterSharingLabel',
|
||||
},
|
||||
autoplayBlockedDesc: {
|
||||
id: 'app.media.screenshare.autoplayBlockedDesc',
|
||||
},
|
||||
autoplayAllowLabel: {
|
||||
id: 'app.media.screenshare.autoplayAllowLabel',
|
||||
},
|
||||
screenshareStarted: {
|
||||
id: 'app.media.screenshare.start',
|
||||
description: 'toast to show when a screenshare has started',
|
||||
},
|
||||
screenshareEnded: {
|
||||
id: 'app.media.screenshare.end',
|
||||
description: 'toast to show when a screenshare has ended',
|
||||
},
|
||||
screenshareEndedDueToDataSaving: {
|
||||
id: 'app.media.screenshare.endDueToDataSaving',
|
||||
description: 'toast to show when a screenshare has ended by changing data savings option',
|
||||
},
|
||||
});
|
||||
|
||||
const ALLOW_FULLSCREEN = Meteor.settings.public.app.allowFullscreen;
|
||||
const MOBILE_HOVER_TIMEOUT = 5000;
|
||||
const MEDIA_FLOW_PROBE_INTERVAL = 500;
|
||||
@ -80,7 +46,7 @@ class ScreenshareComponent extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
constructor(props) {
|
||||
super();
|
||||
this.state = {
|
||||
loaded: false,
|
||||
@ -100,11 +66,17 @@ class ScreenshareComponent extends React.Component {
|
||||
this.onStreamStateChange = this.onStreamStateChange.bind(this);
|
||||
this.onSwitched = this.onSwitched.bind(this);
|
||||
this.handleOnVolumeChanged = this.handleOnVolumeChanged.bind(this);
|
||||
this.dispatchScreenShareSize = this.dispatchScreenShareSize.bind(this);
|
||||
this.handleOnMuted = this.handleOnMuted.bind(this);
|
||||
this.debouncedDispatchScreenShareSize = debounce(
|
||||
{ delay: SCREEN_SIZE_DISPATCH_INTERVAL },
|
||||
this.dispatchScreenShareSize
|
||||
this.dispatchScreenShareSize,
|
||||
);
|
||||
|
||||
const { locales, icon } = props;
|
||||
this.locales = locales;
|
||||
this.icon = icon;
|
||||
|
||||
this.volume = getVolume();
|
||||
this.mobileHoverSetTimeout = null;
|
||||
this.mediaFlowMonitor = null;
|
||||
@ -116,6 +88,7 @@ class ScreenshareComponent extends React.Component {
|
||||
layoutContextDispatch,
|
||||
intl,
|
||||
isPresenter,
|
||||
startPreviewSizeBig,
|
||||
} = this.props;
|
||||
|
||||
screenshareHasStarted(isPresenter);
|
||||
@ -126,7 +99,9 @@ class ScreenshareComponent extends React.Component {
|
||||
// Attaches the local stream if it exists to serve as the local presenter preview
|
||||
attachLocalPreviewStream(getMediaElement());
|
||||
|
||||
notify(intl.formatMessage(intlMessages.screenshareStarted), 'info', 'desktop');
|
||||
this.setState({ switched: startPreviewSizeBig });
|
||||
|
||||
notify(intl.formatMessage(this.locales.started), 'info', this.icon);
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_HAS_SCREEN_SHARE,
|
||||
@ -160,9 +135,9 @@ class ScreenshareComponent extends React.Component {
|
||||
unsubscribeFromStreamStateChange('screenshare', this.onStreamStateChange);
|
||||
|
||||
if (Settings.dataSaving.viewScreenshare) {
|
||||
notify(intl.formatMessage(intlMessages.screenshareEnded), 'info', 'desktop');
|
||||
notify(intl.formatMessage(this.locales.ended), 'info', this.icon);
|
||||
} else {
|
||||
notify(intl.formatMessage(intlMessages.screenshareEndedDueToDataSaving), 'info', 'desktop');
|
||||
notify(intl.formatMessage(this.locales.endedDueToDataSaving), 'info', this.icon);
|
||||
}
|
||||
|
||||
layoutContextDispatch({
|
||||
@ -274,7 +249,7 @@ class ScreenshareComponent extends React.Component {
|
||||
height,
|
||||
browserWidth: window.innerWidth,
|
||||
browserHeight: window.innerHeight,
|
||||
}
|
||||
};
|
||||
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SCREEN_SHARE_SIZE,
|
||||
@ -282,12 +257,6 @@ class ScreenshareComponent extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
onVideoResize() {
|
||||
// Debounced version of the dispatcher to pace things out - we don't want
|
||||
// to hog the CPU just for resize recalculations...
|
||||
this.debouncedDispatchScreenShareSize();
|
||||
}
|
||||
|
||||
onLoadedMetadata() {
|
||||
const element = getMediaElement();
|
||||
|
||||
@ -322,6 +291,12 @@ class ScreenshareComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onVideoResize() {
|
||||
// Debounced version of the dispatcher to pace things out - we don't want
|
||||
// to hog the CPU just for resize recalculations...
|
||||
this.debouncedDispatchScreenShareSize();
|
||||
}
|
||||
|
||||
onStreamStateChange(event) {
|
||||
const { streamState } = event.detail;
|
||||
const { mediaFlowing } = this.state;
|
||||
@ -343,8 +318,8 @@ class ScreenshareComponent extends React.Component {
|
||||
|
||||
return (
|
||||
<FullscreenButtonContainer
|
||||
key={uniqueId('fullscreenButton-')}
|
||||
elementName={intl.formatMessage(intlMessages.screenShareLabel)}
|
||||
key={_.uniqueId('fullscreenButton-')}
|
||||
elementName={intl.formatMessage(this.locales.label)}
|
||||
fullscreenRef={this.screenshareContainer}
|
||||
elementId={fullscreenElementId}
|
||||
isFullscreen={fullscreenContext}
|
||||
@ -358,17 +333,20 @@ class ScreenshareComponent extends React.Component {
|
||||
|
||||
return (
|
||||
<AutoplayOverlay
|
||||
key={uniqueId('screenshareAutoplayOverlay')}
|
||||
autoplayBlockedDesc={intl.formatMessage(intlMessages.autoplayBlockedDesc)}
|
||||
autoplayAllowLabel={intl.formatMessage(intlMessages.autoplayAllowLabel)}
|
||||
key={_.uniqueId('screenshareAutoplayOverlay')}
|
||||
autoplayBlockedDesc={intl.formatMessage(this.locales.autoplayBlockedDesc)}
|
||||
autoplayAllowLabel={intl.formatMessage(this.locales.autoplayAllowLabel)}
|
||||
handleAllowAutoplay={this.handleAllowAutoplay}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderSwitchButton() {
|
||||
const { showSwitchPreviewSizeButton } = this.props;
|
||||
const { switched } = this.state;
|
||||
|
||||
if (!showSwitchPreviewSizeButton) return null;
|
||||
|
||||
return (
|
||||
<SwitchButtonContainer
|
||||
handleSwitch={this.onSwitched}
|
||||
@ -468,12 +446,12 @@ class ScreenshareComponent extends React.Component {
|
||||
<div data-test="isSharingScreen">
|
||||
{!switched
|
||||
&& ScreenshareComponent.renderScreenshareContainerInside(
|
||||
intl.formatMessage(intlMessages.presenterSharingLabel),
|
||||
intl.formatMessage(this.locales.presenterSharingLabel),
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
: ScreenshareComponent.renderScreenshareContainerInside(
|
||||
intl.formatMessage(intlMessages.presenterLoadingLabel),
|
||||
intl.formatMessage(this.locales.presenterLoadingLabel),
|
||||
)
|
||||
}
|
||||
</Styled.ScreenshareContainer>
|
||||
@ -501,7 +479,7 @@ class ScreenshareComponent extends React.Component {
|
||||
{
|
||||
!loaded
|
||||
? ScreenshareComponent.renderScreenshareContainerInside(
|
||||
intl.formatMessage(intlMessages.viewerLoadingLabel),
|
||||
intl.formatMessage(this.locales.viewerLoadingLabel),
|
||||
)
|
||||
: null
|
||||
}
|
||||
@ -580,5 +558,6 @@ ScreenshareComponent.propTypes = {
|
||||
formatMessage: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
isPresenter: PropTypes.bool.isRequired,
|
||||
layoutContextDispatch: PropTypes.func.isRequired,
|
||||
enableVolumeControl: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
@ -2,8 +2,12 @@ import React, { useContext } from 'react';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import {
|
||||
isVideoBroadcasting,
|
||||
isGloballyBroadcasting,
|
||||
getSharingContentType,
|
||||
getBroadcastContentType,
|
||||
isScreenGloballyBroadcasting,
|
||||
isCameraAsContentGloballyBroadcasting,
|
||||
isScreenBroadcasting,
|
||||
isCameraAsContentBroadcasting,
|
||||
} from './service';
|
||||
import ScreenshareComponent from './component';
|
||||
import { layoutSelect, layoutSelectOutput, layoutDispatch } from '../layout/context';
|
||||
@ -11,6 +15,77 @@ import getFromUserSettings from '/imports/ui/services/users-settings';
|
||||
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
|
||||
import { shouldEnableVolumeControl } from './service';
|
||||
import MediaService from '/imports/ui/components/media/service';
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
const screenshareIntlMessages = defineMessages({
|
||||
// SCREENSHARE
|
||||
label: {
|
||||
id: 'app.screenshare.screenShareLabel',
|
||||
description: 'screen share area element label',
|
||||
},
|
||||
presenterLoadingLabel: {
|
||||
id: 'app.screenshare.presenterLoadingLabel',
|
||||
},
|
||||
viewerLoadingLabel: {
|
||||
id: 'app.screenshare.viewerLoadingLabel',
|
||||
},
|
||||
presenterSharingLabel: {
|
||||
id: 'app.screenshare.presenterSharingLabel',
|
||||
},
|
||||
autoplayBlockedDesc: {
|
||||
id: 'app.media.screenshare.autoplayBlockedDesc',
|
||||
},
|
||||
autoplayAllowLabel: {
|
||||
id: 'app.media.screenshare.autoplayAllowLabel',
|
||||
},
|
||||
started: {
|
||||
id: 'app.media.screenshare.start',
|
||||
description: 'toast to show when a screenshare has started',
|
||||
},
|
||||
ended: {
|
||||
id: 'app.media.screenshare.end',
|
||||
description: 'toast to show when a screenshare has ended',
|
||||
},
|
||||
endedDueToDataSaving: {
|
||||
id: 'app.media.screenshare.endDueToDataSaving',
|
||||
description: 'toast to show when a screenshare has ended by changing data savings option',
|
||||
}
|
||||
});
|
||||
|
||||
const cameraAsContentIntlMessages = defineMessages({
|
||||
// CAMERA AS CONTENT
|
||||
label: {
|
||||
id: 'app.cameraAsContent.cameraAsContentLabel',
|
||||
description: 'screen share area element label',
|
||||
},
|
||||
presenterLoadingLabel: {
|
||||
id: 'app.cameraAsContent.presenterLoadingLabel',
|
||||
},
|
||||
viewerLoadingLabel: {
|
||||
id: 'app.cameraAsContent.viewerLoadingLabel',
|
||||
},
|
||||
presenterSharingLabel: {
|
||||
id: 'app.cameraAsContent.presenterSharingLabel',
|
||||
},
|
||||
autoplayBlockedDesc: {
|
||||
id: 'app.media.cameraAsContent.autoplayBlockedDesc',
|
||||
},
|
||||
autoplayAllowLabel: {
|
||||
id: 'app.media.cameraAsContent.autoplayAllowLabel',
|
||||
},
|
||||
started: {
|
||||
id: 'app.media.cameraAsContent.start',
|
||||
description: 'toast to show when camera as content has started',
|
||||
},
|
||||
ended: {
|
||||
id: 'app.media.cameraAsContent.end',
|
||||
description: 'toast to show when camera as content has ended',
|
||||
},
|
||||
endedDueToDataSaving: {
|
||||
id: 'app.media.cameraAsContent.endDueToDataSaving',
|
||||
description: 'toast to show when camera as content has ended by changing data savings option',
|
||||
},
|
||||
});
|
||||
|
||||
const ScreenshareContainer = (props) => {
|
||||
const screenShare = layoutSelectOutput((i) => i.screenShare);
|
||||
@ -26,7 +101,29 @@ const ScreenshareContainer = (props) => {
|
||||
const currentUser = users[Auth.meetingID][Auth.userID];
|
||||
const isPresenter = currentUser.presenter;
|
||||
|
||||
if (isVideoBroadcasting()) {
|
||||
const info = {
|
||||
screenshare: {
|
||||
icon: "desktop",
|
||||
locales: screenshareIntlMessages,
|
||||
startPreviewSizeBig: false,
|
||||
showSwitchPreviewSizeButton: true,
|
||||
},
|
||||
camera: {
|
||||
icon: "video",
|
||||
locales: cameraAsContentIntlMessages,
|
||||
startPreviewSizeBig: true,
|
||||
showSwitchPreviewSizeButton: false,
|
||||
},
|
||||
};
|
||||
|
||||
const getContentType = () => {
|
||||
return isPresenter ? getSharingContentType() : getBroadcastContentType();
|
||||
}
|
||||
const contentTypeInfo = info[getContentType()];
|
||||
const defaultInfo = info.camera;
|
||||
const selectedInfo = contentTypeInfo ? contentTypeInfo : defaultInfo;
|
||||
|
||||
if (isScreenBroadcasting() || isCameraAsContentBroadcasting()) {
|
||||
return (
|
||||
<ScreenshareComponent
|
||||
{
|
||||
@ -37,6 +134,7 @@ const ScreenshareContainer = (props) => {
|
||||
fullscreenContext,
|
||||
fullscreenElementId,
|
||||
isPresenter,
|
||||
...selectedInfo,
|
||||
}
|
||||
}
|
||||
/>
|
||||
@ -49,7 +147,7 @@ const LAYOUT_CONFIG = Meteor.settings.public.layout;
|
||||
|
||||
export default withTracker(() => {
|
||||
return {
|
||||
isGloballyBroadcasting: isGloballyBroadcasting(),
|
||||
isGloballyBroadcasting: isScreenGloballyBroadcasting() || isCameraAsContentGloballyBroadcasting(),
|
||||
toggleSwapLayout: MediaService.toggleSwapLayout,
|
||||
hidePresentationOnJoin: getFromUserSettings('bbb_hide_presentation_on_join', LAYOUT_CONFIG.hidePresentationOnJoin),
|
||||
enableVolumeControl: shouldEnableVolumeControl(),
|
||||
|
@ -21,21 +21,58 @@ const DEFAULT_SCREENSHARE_STATS_TYPES = [
|
||||
'inbound-rtp',
|
||||
];
|
||||
|
||||
const CONTENT_TYPE_CAMERA = "camera";
|
||||
const CONTENT_TYPE_SCREENSHARE = "screenshare";
|
||||
|
||||
let _isSharingScreen = false;
|
||||
const _sharingScreenDep = {
|
||||
const _isSharingDep = {
|
||||
value: false,
|
||||
tracker: new Tracker.Dependency(),
|
||||
};
|
||||
|
||||
const isSharingScreen = () => {
|
||||
_sharingScreenDep.tracker.depend();
|
||||
return _sharingScreenDep.value;
|
||||
const _sharingContentTypeDep = {
|
||||
value: false,
|
||||
tracker: new Tracker.Dependency(),
|
||||
};
|
||||
|
||||
const setSharingScreen = (isSharingScreen) => {
|
||||
if (_sharingScreenDep.value !== isSharingScreen) {
|
||||
_sharingScreenDep.value = isSharingScreen;
|
||||
_sharingScreenDep.tracker.changed();
|
||||
const _cameraAsContentDeviceIdTypeDep = {
|
||||
value: '',
|
||||
tracker: new Tracker.Dependency(),
|
||||
};
|
||||
|
||||
const isSharing = () => {
|
||||
_isSharingDep.tracker.depend();
|
||||
return _isSharingDep.value;
|
||||
};
|
||||
|
||||
const setIsSharing = (isSharing) => {
|
||||
if (_isSharingDep.value !== isSharing) {
|
||||
_isSharingDep.value = isSharing;
|
||||
_isSharingDep.tracker.changed();
|
||||
}
|
||||
};
|
||||
|
||||
const setSharingContentType = (contentType) => {
|
||||
if (_sharingContentTypeDep.value !== contentType) {
|
||||
_sharingContentTypeDep.value = contentType;
|
||||
_sharingContentTypeDep.tracker.changed();
|
||||
}
|
||||
}
|
||||
|
||||
const getSharingContentType = () => {
|
||||
_sharingContentTypeDep.tracker.depend();
|
||||
return _sharingContentTypeDep.value;
|
||||
};
|
||||
|
||||
const getCameraAsContentDeviceId = () => {
|
||||
_cameraAsContentDeviceIdTypeDep.tracker.depend();
|
||||
return _cameraAsContentDeviceIdTypeDep.value;
|
||||
};
|
||||
|
||||
const setCameraAsContentDeviceId = (deviceId) => {
|
||||
if (_cameraAsContentDeviceIdTypeDep.value !== deviceId) {
|
||||
_cameraAsContentDeviceIdTypeDep.value = deviceId;
|
||||
_cameraAsContentDeviceIdTypeDep.tracker.changed();
|
||||
}
|
||||
};
|
||||
|
||||
@ -65,32 +102,58 @@ const _handleStreamTermination = () => {
|
||||
screenshareHasEnded();
|
||||
};
|
||||
|
||||
// A simplified, trackable version of isVideoBroadcasting that DOES NOT
|
||||
// A simplified, trackable version of isScreenBroadcasting that DOES NOT
|
||||
// account for the presenter's local sharing state.
|
||||
// It reflects the GLOBAL screen sharing state (akka-apps)
|
||||
const isGloballyBroadcasting = () => {
|
||||
const screenshareEntry = Screenshare.findOne({ meetingId: Auth.meetingID },
|
||||
const isScreenGloballyBroadcasting = () => {
|
||||
const screenshareEntry = Screenshare.findOne({ meetingId: Auth.meetingID, "screenshare.contentType": CONTENT_TYPE_SCREENSHARE },
|
||||
{ fields: { 'screenshare.stream': 1 } });
|
||||
|
||||
return (!screenshareEntry ? false : !!screenshareEntry.screenshare.stream);
|
||||
}
|
||||
|
||||
// A simplified, trackable version of isCameraContentBroadcasting that DOES NOT
|
||||
// account for the presenter's local sharing state.
|
||||
// It reflects the GLOBAL camera as content sharing state (akka-apps)
|
||||
const isCameraAsContentGloballyBroadcasting = () => {
|
||||
const cameraAsContentEntry = Screenshare.findOne({ meetingId: Auth.meetingID, "screenshare.contentType": CONTENT_TYPE_CAMERA },
|
||||
{ fields: { 'screenshare.stream': 1 } });
|
||||
|
||||
return (!cameraAsContentEntry ? false : !!cameraAsContentEntry.screenshare.stream);
|
||||
}
|
||||
|
||||
// when the meeting information has been updated check to see if it was
|
||||
// screensharing. If it has changed either trigger a call to receive video
|
||||
// and display it, or end the call and hide the video
|
||||
const isVideoBroadcasting = () => {
|
||||
const sharing = isSharingScreen();
|
||||
const screenshareEntry = Screenshare.findOne({ meetingId: Auth.meetingID },
|
||||
const isScreenBroadcasting = () => {
|
||||
const sharing = isSharing() && getSharingContentType() == CONTENT_TYPE_SCREENSHARE;
|
||||
const screenshareEntry = Screenshare.findOne({ meetingId: Auth.meetingID, "screenshare.contentType": CONTENT_TYPE_SCREENSHARE },
|
||||
{ fields: { 'screenshare.stream': 1 } });
|
||||
const screenIsShared = !screenshareEntry ? false : !!screenshareEntry.screenshare.stream;
|
||||
|
||||
if (screenIsShared && isSharingScreen) {
|
||||
setSharingScreen(false);
|
||||
if (screenIsShared && isSharing) {
|
||||
setIsSharing(false);
|
||||
}
|
||||
|
||||
return sharing || screenIsShared;
|
||||
};
|
||||
|
||||
// when the meeting information has been updated check to see if it was
|
||||
// sharing camera as content. If it has changed either trigger a call to receive video
|
||||
// and display it, or end the call and hide the video
|
||||
const isCameraAsContentBroadcasting = () => {
|
||||
const sharing = isSharing() && getSharingContentType() === CONTENT_TYPE_CAMERA;
|
||||
const screenshareEntry = Screenshare.findOne({ meetingId: Auth.meetingID, "screenshare.contentType": CONTENT_TYPE_CAMERA },
|
||||
{ fields: { 'screenshare.stream': 1 } });
|
||||
const cameraAsContentIsShared = !screenshareEntry ? false : !!screenshareEntry.screenshare.stream;
|
||||
|
||||
if (cameraAsContentIsShared && isSharing) {
|
||||
setIsSharing(false);
|
||||
}
|
||||
|
||||
return sharing || cameraAsContentIsShared;
|
||||
};
|
||||
|
||||
|
||||
const screenshareHasAudio = () => {
|
||||
const screenshareEntry = Screenshare.findOne({ meetingId: Auth.meetingID },
|
||||
@ -103,9 +166,24 @@ const screenshareHasAudio = () => {
|
||||
return !!screenshareEntry.screenshare.hasAudio;
|
||||
}
|
||||
|
||||
const getBroadcastContentType = () => {
|
||||
const screenshareEntry = Screenshare.findOne({meetindId: Auth.meedingID},
|
||||
{ fields: { 'screenshare.contentType': 1} });
|
||||
|
||||
if (!screenshareEntry) {
|
||||
// defaults to contentType: "camera"
|
||||
return CONTENT_TYPE_CAMERA;
|
||||
}
|
||||
|
||||
return screenshareEntry.screenshare.contentType;
|
||||
}
|
||||
|
||||
const screenshareHasEnded = () => {
|
||||
if (isSharingScreen()) {
|
||||
setSharingScreen(false);
|
||||
if (isSharing()) {
|
||||
setIsSharing(false);
|
||||
}
|
||||
if (getSharingContentType() === CONTENT_TYPE_CAMERA) {
|
||||
setCameraAsContentDeviceId('');
|
||||
}
|
||||
|
||||
KurentoBridge.stop();
|
||||
@ -151,7 +229,10 @@ const screenshareHasStarted = (isPresenter) => {
|
||||
}
|
||||
};
|
||||
|
||||
const shareScreen = async (isPresenter, onFail) => {
|
||||
const shareScreen = async (isPresenter, onFail, options = {}) => {
|
||||
if (isCameraAsContentBroadcasting()) {
|
||||
screenshareHasEnded();
|
||||
}
|
||||
// stop external video share if running
|
||||
const meeting = Meetings.findOne({ meetingId: Auth.meetingID });
|
||||
|
||||
@ -160,7 +241,14 @@ const shareScreen = async (isPresenter, onFail) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const stream = await BridgeService.getScreenStream();
|
||||
let stream;
|
||||
let contentType = CONTENT_TYPE_SCREENSHARE;
|
||||
if (options.stream == null) {
|
||||
stream = await BridgeService.getScreenStream();
|
||||
} else {
|
||||
contentType = CONTENT_TYPE_CAMERA;
|
||||
stream = options.stream;
|
||||
}
|
||||
_trackStreamTermination(stream, _handleStreamTermination);
|
||||
|
||||
if (!isPresenter) {
|
||||
@ -168,7 +256,7 @@ const shareScreen = async (isPresenter, onFail) => {
|
||||
return;
|
||||
}
|
||||
|
||||
await KurentoBridge.share(stream, onFail);
|
||||
await KurentoBridge.share(stream, onFail, contentType);
|
||||
|
||||
// Stream might have been disabled in the meantime. I love badly designed
|
||||
// async components like this screen sharing bridge :) - prlanzarin 09 May 22
|
||||
@ -180,7 +268,8 @@ const shareScreen = async (isPresenter, onFail) => {
|
||||
// Close Shared Notes if open.
|
||||
NotesService.pinSharedNotes(false);
|
||||
|
||||
setSharingScreen(true);
|
||||
setSharingContentType(contentType);
|
||||
setIsSharing(true);
|
||||
} catch (error) {
|
||||
onFail(error);
|
||||
}
|
||||
@ -257,21 +346,28 @@ const isMediaFlowing = (previousStats, currentStats) => {
|
||||
export {
|
||||
SCREENSHARE_MEDIA_ELEMENT_NAME,
|
||||
isMediaFlowing,
|
||||
isVideoBroadcasting,
|
||||
isScreenBroadcasting,
|
||||
isCameraAsContentBroadcasting,
|
||||
screenshareHasEnded,
|
||||
screenshareHasStarted,
|
||||
screenshareHasAudio,
|
||||
getBroadcastContentType,
|
||||
shareScreen,
|
||||
screenShareEndAlert,
|
||||
dataSavingSetting,
|
||||
isSharingScreen,
|
||||
setSharingScreen,
|
||||
isSharing,
|
||||
setIsSharing,
|
||||
setSharingContentType,
|
||||
getSharingContentType,
|
||||
getMediaElement,
|
||||
getMediaElementDimensions,
|
||||
attachLocalPreviewStream,
|
||||
isGloballyBroadcasting,
|
||||
isScreenGloballyBroadcasting,
|
||||
isCameraAsContentGloballyBroadcasting,
|
||||
getStats,
|
||||
setVolume,
|
||||
getVolume,
|
||||
shouldEnableVolumeControl,
|
||||
setCameraAsContentDeviceId,
|
||||
getCameraAsContentDeviceId,
|
||||
};
|
||||
|
@ -205,6 +205,10 @@ const intlMessages = defineMessages({
|
||||
id: 'app.videoPreview.wholeImageBrightnessDesc',
|
||||
description: 'Whole image brightness aria description',
|
||||
},
|
||||
cameraAsContentSettingsTitle: {
|
||||
id: 'app.videoPreview.cameraAsContentSettingsTitle',
|
||||
description: 'Title for the video preview modal when sharing camera as content',
|
||||
},
|
||||
sliderDesc: {
|
||||
id: 'app.videoPreview.sliderDesc',
|
||||
description: 'Brightness slider aria description',
|
||||
@ -426,7 +430,7 @@ class VideoPreview extends Component {
|
||||
handleVirtualBgSelected(type, name, customParams) {
|
||||
const { sharedDevices } = this.props;
|
||||
const { webcamDeviceId } = this.state;
|
||||
const shared = sharedDevices.includes(webcamDeviceId);
|
||||
const shared = this.isAlreadyShared(webcamDeviceId);
|
||||
|
||||
if (type !== EFFECT_TYPES.NONE_TYPE || CAMERA_BRIGHTNESS_AVAILABLE) {
|
||||
return this.startVirtualBackground(this.currentVideoStream, type, name, customParams).then((switched) => {
|
||||
@ -479,6 +483,8 @@ class VideoPreview extends Component {
|
||||
const {
|
||||
resolve,
|
||||
startSharing,
|
||||
cameraAsContent,
|
||||
startSharingCameraAsContent,
|
||||
} = this.props;
|
||||
const {
|
||||
webcamDeviceId,
|
||||
@ -503,18 +509,28 @@ class VideoPreview extends Component {
|
||||
|
||||
this.updateVirtualBackgroundInfo();
|
||||
this.cleanupStreamAndVideo();
|
||||
|
||||
PreviewService.changeProfile(selectedProfile);
|
||||
PreviewService.changeWebcam(webcamDeviceId);
|
||||
startSharing(webcamDeviceId);
|
||||
if (cameraAsContent) {
|
||||
startSharingCameraAsContent(webcamDeviceId);
|
||||
} else {
|
||||
startSharing(webcamDeviceId);
|
||||
}
|
||||
if (resolve) resolve();
|
||||
}
|
||||
|
||||
handleStopSharing() {
|
||||
const { resolve, stopSharing } = this.props;
|
||||
const { resolve, stopSharing, stopSharingCameraAsContent } = this.props;
|
||||
const { webcamDeviceId } = this.state;
|
||||
PreviewService.deleteStream(webcamDeviceId);
|
||||
stopSharing(webcamDeviceId);
|
||||
this.cleanupStreamAndVideo();
|
||||
|
||||
if (this.isCameraAsContentDevice(webcamDeviceId)) {
|
||||
stopSharingCameraAsContent();
|
||||
} else {
|
||||
PreviewService.deleteStream(webcamDeviceId);
|
||||
stopSharing(webcamDeviceId);
|
||||
this.cleanupStreamAndVideo();
|
||||
}
|
||||
if (resolve) resolve();
|
||||
}
|
||||
|
||||
@ -631,7 +647,8 @@ class VideoPreview extends Component {
|
||||
}
|
||||
|
||||
getInitialCameraStream(deviceId) {
|
||||
const defaultProfile = PreviewService.getDefaultProfile();
|
||||
const { cameraAsContent } = this.props;
|
||||
const defaultProfile = !cameraAsContent ? PreviewService.getDefaultProfile() : PreviewService.getCameraAsContentProfile();
|
||||
|
||||
return this.getCameraStream(deviceId, defaultProfile).then(() => {
|
||||
this.updateDeviceId(deviceId);
|
||||
@ -706,11 +723,24 @@ class VideoPreview extends Component {
|
||||
return `${intl.formatMessage(intlMessages.cameraLabel)} ${index}`
|
||||
}
|
||||
|
||||
isAlreadyShared (webcamId) {
|
||||
const { sharedDevices, cameraAsContentDeviceId } = this.props;
|
||||
|
||||
return sharedDevices.includes(webcamId) || webcamId === cameraAsContentDeviceId;
|
||||
}
|
||||
|
||||
isCameraAsContentDevice (deviceId) {
|
||||
const { cameraAsContentDeviceId } = this.props;
|
||||
|
||||
return deviceId === cameraAsContentDeviceId;
|
||||
}
|
||||
|
||||
renderDeviceSelectors() {
|
||||
const {
|
||||
intl,
|
||||
sharedDevices,
|
||||
isVisualEffects,
|
||||
cameraAsContent,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@ -720,6 +750,7 @@ class VideoPreview extends Component {
|
||||
} = this.state;
|
||||
|
||||
const shared = sharedDevices.includes(webcamDeviceId);
|
||||
const shouldShowVirtualBackgrounds = isVirtualBackgroundsEnabled() && !cameraAsContent;
|
||||
|
||||
if (isVisualEffects) {
|
||||
return (
|
||||
@ -731,70 +762,103 @@ class VideoPreview extends Component {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Styled.Label htmlFor="setCam">
|
||||
{intl.formatMessage(intlMessages.cameraLabel)}
|
||||
</Styled.Label>
|
||||
{ availableWebcams && availableWebcams.length > 0
|
||||
? (
|
||||
<Styled.Select
|
||||
id="setCam"
|
||||
value={webcamDeviceId || ''}
|
||||
onChange={this.handleSelectWebcam}
|
||||
>
|
||||
{availableWebcams.map((webcam, index) => (
|
||||
<option key={webcam.deviceId} value={webcam.deviceId}>
|
||||
{webcam.label || this.getFallbackLabel(webcam, index)}
|
||||
</option>
|
||||
))}
|
||||
</Styled.Select>
|
||||
)
|
||||
: (
|
||||
<span>
|
||||
{intl.formatMessage(intlMessages.webcamNotFoundLabel)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
{ shared
|
||||
? (
|
||||
<Styled.Label>
|
||||
{intl.formatMessage(intlMessages.sharedCameraLabel)}
|
||||
</Styled.Label>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<Styled.Label htmlFor="setQuality">
|
||||
{intl.formatMessage(intlMessages.qualityLabel)}
|
||||
</Styled.Label>
|
||||
{PreviewService.PREVIEW_CAMERA_PROFILES.length > 0
|
||||
? (
|
||||
<Styled.Select
|
||||
id="setQuality"
|
||||
value={selectedProfile || ''}
|
||||
onChange={this.handleSelectProfile}
|
||||
>
|
||||
{PreviewService.PREVIEW_CAMERA_PROFILES.map((profile) => {
|
||||
const label = intlMessages[`${profile.id}`]
|
||||
? intl.formatMessage(intlMessages[`${profile.id}`])
|
||||
: profile.name;
|
||||
|
||||
return (
|
||||
<option key={profile.id} value={profile.id}>
|
||||
{`${label}`}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
{ cameraAsContent
|
||||
? (
|
||||
<>
|
||||
<Styled.Label htmlFor="setCam">
|
||||
{intl.formatMessage(intlMessages.cameraLabel)}
|
||||
</Styled.Label>
|
||||
{ availableWebcams && availableWebcams.length > 0
|
||||
? (
|
||||
<Styled.Select
|
||||
id="setCam"
|
||||
value={webcamDeviceId || ''}
|
||||
onChange={this.handleSelectWebcam}
|
||||
>
|
||||
{availableWebcams.map((webcam, index) => (
|
||||
<option key={webcam.deviceId} value={webcam.deviceId}>
|
||||
{webcam.label || this.getFallbackLabel(webcam, index)}
|
||||
</option>
|
||||
))}
|
||||
</Styled.Select>
|
||||
)
|
||||
: (
|
||||
<span>
|
||||
{intl.formatMessage(intlMessages.profileNotFoundLabel)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{isVirtualBackgroundsEnabled() && this.renderVirtualBgSelector()}
|
||||
<span>
|
||||
{intl.formatMessage(intlMessages.webcamNotFoundLabel)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
:
|
||||
<>
|
||||
<Styled.Label htmlFor="setCam">
|
||||
{intl.formatMessage(intlMessages.cameraLabel)}
|
||||
</Styled.Label>
|
||||
{ availableWebcams && availableWebcams.length > 0
|
||||
? (
|
||||
<Styled.Select
|
||||
id="setCam"
|
||||
value={webcamDeviceId || ''}
|
||||
onChange={this.handleSelectWebcam}
|
||||
>
|
||||
{availableWebcams.map((webcam, index) => (
|
||||
<option key={webcam.deviceId} value={webcam.deviceId}>
|
||||
{webcam.label || this.getFallbackLabel(webcam, index)}
|
||||
</option>
|
||||
))}
|
||||
</Styled.Select>
|
||||
)
|
||||
: (
|
||||
<span>
|
||||
{intl.formatMessage(intlMessages.webcamNotFoundLabel)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
{ shared
|
||||
? (
|
||||
<Styled.Label>
|
||||
{intl.formatMessage(intlMessages.sharedCameraLabel)}
|
||||
</Styled.Label>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<Styled.Label htmlFor="setQuality">
|
||||
{intl.formatMessage(intlMessages.qualityLabel)}
|
||||
</Styled.Label>
|
||||
{PreviewService.PREVIEW_CAMERA_PROFILES.length > 0
|
||||
? (
|
||||
<Styled.Select
|
||||
id="setQuality"
|
||||
value={selectedProfile || ''}
|
||||
onChange={this.handleSelectProfile}
|
||||
>
|
||||
{PreviewService.PREVIEW_CAMERA_PROFILES.map((profile) => {
|
||||
const label = intlMessages[`${profile.id}`]
|
||||
? intl.formatMessage(intlMessages[`${profile.id}`])
|
||||
: profile.name;
|
||||
|
||||
return (
|
||||
<option key={profile.id} value={profile.id}>
|
||||
{`${label}`}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Styled.Select>
|
||||
)
|
||||
: (
|
||||
<span>
|
||||
{intl.formatMessage(intlMessages.profileNotFoundLabel)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{shouldShowVirtualBackgrounds && this.renderVirtualBgSelector()}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -806,16 +870,25 @@ class VideoPreview extends Component {
|
||||
}
|
||||
|
||||
renderBrightnessInput() {
|
||||
const {
|
||||
cameraAsContent,
|
||||
} = this.props;
|
||||
const {
|
||||
webcamDeviceId,
|
||||
} = this.state;
|
||||
if (!ENABLE_CAMERA_BRIGHTNESS) return null;
|
||||
|
||||
const { intl } = this.props;
|
||||
const { brightness, wholeImageBrightness, isStartSharingDisabled } = this.state;
|
||||
const shared = this.isAlreadyShared(webcamDeviceId);
|
||||
|
||||
const origin = brightness <= 100 ? 'left' : 'right';
|
||||
const offset = origin === 'left'
|
||||
? (brightness * 100) / 200
|
||||
: ((200 - brightness) * 100) / 200;
|
||||
|
||||
if(cameraAsContent){ return null }
|
||||
|
||||
return (
|
||||
<>
|
||||
<Styled.Label htmlFor="brightness">
|
||||
@ -951,10 +1024,15 @@ class VideoPreview extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
getModalTitle() {
|
||||
const { intl, cameraAsContent } = this.props;
|
||||
if (cameraAsContent) return intl.formatMessage(intlMessages.cameraAsContentSettingsTitle);
|
||||
return intl.formatMessage(intlMessages.webcamSettingsTitle);
|
||||
}
|
||||
|
||||
renderModalContent() {
|
||||
const {
|
||||
intl,
|
||||
sharedDevices,
|
||||
hasVideoStream,
|
||||
forceOpen,
|
||||
camCapReached,
|
||||
@ -971,7 +1049,7 @@ class VideoPreview extends Component {
|
||||
&& !forceOpen
|
||||
&& !(deviceError || previewError);
|
||||
|
||||
const shared = sharedDevices.includes(webcamDeviceId);
|
||||
const shared = this.isAlreadyShared(webcamDeviceId);
|
||||
|
||||
const { isIe } = browserInfo;
|
||||
|
||||
|
@ -3,6 +3,9 @@ import { withTracker } from 'meteor/react-meteor-data';
|
||||
import Service from './service';
|
||||
import VideoPreview from './component';
|
||||
import VideoService from '../video-provider/service';
|
||||
import ScreenShareService from '/imports/ui/components/screenshare/service';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import { SCREENSHARING_ERRORS } from '/imports/api/screenshare/client/bridge/errors';
|
||||
|
||||
const VideoPreviewContainer = (props) => <VideoPreview {...props} />;
|
||||
|
||||
@ -12,6 +15,27 @@ export default withTracker(({ setIsOpen, callbackToClose }) => ({
|
||||
setIsOpen(false);
|
||||
VideoService.joinVideo(deviceId);
|
||||
},
|
||||
startSharingCameraAsContent: (deviceId) => {
|
||||
callbackToClose();
|
||||
setIsOpen(false);
|
||||
const handleFailure = (error) => {
|
||||
const {
|
||||
errorCode = SCREENSHARING_ERRORS.UNKNOWN_ERROR.errorCode,
|
||||
errorMessage = error.message,
|
||||
} = error;
|
||||
|
||||
logger.error({
|
||||
logCode: 'camera_as_content_failed',
|
||||
extraInfo: { errorCode, errorMessage },
|
||||
}, `Sharing camera as content failed: ${errorMessage} (code=${errorCode})`);
|
||||
|
||||
ScreenShareService.screenshareHasEnded();
|
||||
};
|
||||
ScreenShareService.shareScreen(
|
||||
true, handleFailure, { stream: Service.getStream(deviceId)._mediaStream }
|
||||
);
|
||||
ScreenShareService.setCameraAsContentDeviceId(deviceId);
|
||||
},
|
||||
stopSharing: (deviceId) => {
|
||||
callbackToClose();
|
||||
setIsOpen(false);
|
||||
@ -22,7 +46,13 @@ export default withTracker(({ setIsOpen, callbackToClose }) => ({
|
||||
VideoService.exitVideo();
|
||||
}
|
||||
},
|
||||
stopSharingCameraAsContent: () => {
|
||||
callbackToClose();
|
||||
setIsOpen(false);
|
||||
ScreenShareService.screenshareHasEnded();
|
||||
},
|
||||
sharedDevices: VideoService.getSharedDevices(),
|
||||
cameraAsContentDeviceId: ScreenShareService.getCameraAsContentDeviceId(),
|
||||
isCamLocked: VideoService.isUserLocked(),
|
||||
camCapReached: VideoService.hasCapReached(),
|
||||
closeModal: () => {
|
||||
|
@ -14,6 +14,7 @@ const GUM_RETRY_DELAY = 200;
|
||||
const CAMERA_PROFILES = Meteor.settings.public.kurento.cameraProfiles || [];
|
||||
// Filtered, without hidden profiles
|
||||
const PREVIEW_CAMERA_PROFILES = CAMERA_PROFILES.filter(p => !p.hidden);
|
||||
const CAMERA_AS_CONTENT_PROFILE_ID = 'fhd';
|
||||
|
||||
const getDefaultProfile = () => {
|
||||
return CAMERA_PROFILES.find(profile => profile.id === BBBStorage.getItem('WebcamProfileId'))
|
||||
@ -22,6 +23,11 @@ const getDefaultProfile = () => {
|
||||
|| CAMERA_PROFILES[0];
|
||||
}
|
||||
|
||||
const getCameraAsContentProfile = () => {
|
||||
return CAMERA_PROFILES.find(profile => profile.id == CAMERA_AS_CONTENT_PROFILE_ID)
|
||||
|| CAMERA_PROFILES.find(profile => profile.default)
|
||||
}
|
||||
|
||||
const getCameraProfile = (id) => {
|
||||
return CAMERA_PROFILES.find(profile => profile.id === id);
|
||||
}
|
||||
@ -236,6 +242,7 @@ export default {
|
||||
deleteStream,
|
||||
digestVideoDevices,
|
||||
getDefaultProfile,
|
||||
getCameraAsContentProfile,
|
||||
getCameraProfile,
|
||||
doGUM,
|
||||
terminateCameraStream,
|
||||
|
@ -23,6 +23,7 @@ class ScreenshareBroker extends BaseBroker {
|
||||
this.ws = null;
|
||||
this.webRtcPeer = null;
|
||||
this.hasAudio = false;
|
||||
this.contentType = "camera";
|
||||
this.offering = true;
|
||||
this.signalCandidates = true;
|
||||
this.ending = false;
|
||||
@ -32,6 +33,7 @@ class ScreenshareBroker extends BaseBroker {
|
||||
// caleeName,
|
||||
// iceServers,
|
||||
// hasAudio,
|
||||
// contentType,
|
||||
// bitrate,
|
||||
// offering,
|
||||
// mediaServer,
|
||||
@ -161,6 +163,7 @@ class ScreenshareBroker extends BaseBroker {
|
||||
callerName: this.userId,
|
||||
sdpOffer: offer,
|
||||
hasAudio: !!this.hasAudio,
|
||||
contentType: this.contentType,
|
||||
bitrate: this.bitrate,
|
||||
mediaServer: this.mediaServer,
|
||||
};
|
||||
|
@ -67,6 +67,7 @@ public:
|
||||
alwaysShowWaitingRoomUI: true
|
||||
enableLimitOfViewersInWebcam: false
|
||||
enableMultipleCameras: true
|
||||
enableCameraAsContent: false
|
||||
# Allow users to open webcam video modal/preview when video is already
|
||||
# active. This also allows to change virtual backgrounds without
|
||||
# restarting webcam.
|
||||
@ -390,6 +391,14 @@ public:
|
||||
width: 1280
|
||||
height: 720
|
||||
frameRate: 30
|
||||
- id: fhd
|
||||
name: Camera as content
|
||||
hidden: true
|
||||
default: false
|
||||
bitrate: 1500
|
||||
constraints:
|
||||
width: 1920
|
||||
height: 1080
|
||||
enableScreensharing: true
|
||||
enableVideo: true
|
||||
enableVideoMenu: true
|
||||
|
@ -177,6 +177,11 @@
|
||||
"app.media.screenshare.notSupported": "Screensharing is not supported in this browser.",
|
||||
"app.media.screenshare.autoplayBlockedDesc": "We need your permission to show you the presenter's screen.",
|
||||
"app.media.screenshare.autoplayAllowLabel": "View shared screen",
|
||||
"app.media.cameraAsContent.start": "Present camera has started",
|
||||
"app.media.cameraAsContent.end": "Present camera has ended",
|
||||
"app.media.cameraAsContent.endDueToDataSaving": "Present camera stopped due to data savings",
|
||||
"app.media.cameraAsContent.autoplayBlockedDesc": "We need your permission to show you the presenter's camera.",
|
||||
"app.media.cameraAsContent.autoplayAllowLabel": "View present camera",
|
||||
"app.screenshare.presenterLoadingLabel": "Your screenshare is loading",
|
||||
"app.screenshare.viewerLoadingLabel": "The presenter's screen is loading",
|
||||
"app.screenshare.presenterSharingLabel": "You are now sharing your screen",
|
||||
@ -185,6 +190,9 @@
|
||||
"app.screenshare.screenshareRetryOtherEnvError": "Code {0}. Could not share the screen. Try again using a different browser or device.",
|
||||
"app.screenshare.screenshareUnsupportedEnv": "Code {0}. Browser is not supported. Try again using a different browser or device.",
|
||||
"app.screenshare.screensharePermissionError": "Code {0}. Permission to capture the screen needs to be granted.",
|
||||
"app.cameraAsContent.presenterLoadingLabel": "Your camera is loading",
|
||||
"app.cameraAsContent.viewerLoadingLabel": "The presenter's camera is loading",
|
||||
"app.cameraAsContent.presenterSharingLabel": "You are now presenting your camera",
|
||||
"app.meeting.ended": "This session has ended",
|
||||
"app.meeting.meetingTimeRemaining": "Meeting time remaining: {0}",
|
||||
"app.meeting.meetingTimeHasEnded": "Time ended. Meeting will close soon",
|
||||
@ -453,7 +461,10 @@
|
||||
"app.actionsBar.actionsDropdown.minimizePresentationLabel": "Minimize presentation",
|
||||
"app.actionsBar.actionsDropdown.minimizePresentationDesc": "Button used to minimize presentation",
|
||||
"app.actionsBar.actionsDropdown.layoutModal": "Layout Settings Modal",
|
||||
"app.actionsBar.actionsDropdown.shareCameraAsContent": "Share camera as content",
|
||||
"app.actionsBar.actionsDropdown.unshareCameraAsContent": "Stop camera as content",
|
||||
"app.screenshare.screenShareLabel" : "Screen share",
|
||||
"app.cameraAsContent.cameraAsContentLabel" : "Present camera",
|
||||
"app.submenu.application.applicationSectionTitle": "Application",
|
||||
"app.submenu.application.animationsLabel": "Animations",
|
||||
"app.submenu.application.audioFilterLabel": "Audio Filters for Microphone",
|
||||
@ -972,6 +983,7 @@
|
||||
"app.videoPreview.webcamPreviewLabel": "Webcam preview",
|
||||
"app.videoPreview.webcamSettingsTitle": "Webcam settings",
|
||||
"app.videoPreview.webcamEffectsTitle": "Webcam visual effects",
|
||||
"app.videoPreview.cameraAsContentSettingsTitle": "Present Camera",
|
||||
"app.videoPreview.webcamVirtualBackgroundLabel": "Virtual background settings",
|
||||
"app.videoPreview.webcamVirtualBackgroundDisabledLabel": "This device does not support virtual backgrounds",
|
||||
"app.videoPreview.webcamNotFoundLabel": "Webcam not found",
|
||||
|
Loading…
Reference in New Issue
Block a user