Merge pull request #5157 from jfsiebel/implement-start-stop-recording-html5
Implement start stop recording html5
This commit is contained in:
commit
d17ca71a60
@ -1,7 +1,9 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import mapToAcl from '/imports/startup/mapToAcl';
|
||||
import endMeeting from './methods/endMeeting';
|
||||
import toggleRecording from './methods/toggleRecording';
|
||||
|
||||
Meteor.methods(mapToAcl(['methods.endMeeting'], {
|
||||
Meteor.methods(mapToAcl(['methods.endMeeting', 'methods.toggleRecording'], {
|
||||
endMeeting,
|
||||
toggleRecording,
|
||||
}));
|
||||
|
@ -0,0 +1,44 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
|
||||
export default function toggleRecording(credentials) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(requesterToken, String);
|
||||
|
||||
const EVENT_NAME = 'SetRecordingStatusCmdMsg';
|
||||
|
||||
let meetingRecorded;
|
||||
let allowedToRecord;
|
||||
|
||||
const meetingObject = Meetings.findOne({ meetingId });
|
||||
|
||||
if (meetingObject != null) {
|
||||
const {
|
||||
allowStartStopRecording,
|
||||
recording,
|
||||
record,
|
||||
} = meetingObject.recordProp;
|
||||
|
||||
meetingRecorded = recording;
|
||||
allowedToRecord = record && allowStartStopRecording;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
recording: !meetingRecorded,
|
||||
setBy: requesterUserId,
|
||||
};
|
||||
|
||||
if (allowedToRecord) {
|
||||
Logger.info(`Setting the record parameter to ${!meetingRecorded} for ${meetingId} by ${requesterUserId}`);
|
||||
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
}
|
||||
return null;
|
||||
}
|
@ -46,6 +46,14 @@ const intlMessages = defineMessages({
|
||||
id: 'app.actionsBar.actionsDropdown.stopDesktopShareDesc',
|
||||
description: 'adds context to stop desktop share option',
|
||||
},
|
||||
startRecording: {
|
||||
id: 'app.actionsBar.actionsDropdown.startRecording',
|
||||
description: 'start recording option',
|
||||
},
|
||||
stopRecording: {
|
||||
id: 'app.actionsBar.actionsDropdown.stopRecording',
|
||||
description: 'stop recording option',
|
||||
},
|
||||
});
|
||||
|
||||
class ActionsDropdown extends Component {
|
||||
@ -57,6 +65,7 @@ class ActionsDropdown extends Component {
|
||||
componentWillMount() {
|
||||
this.presentationItemId = _.uniqueId('action-item-');
|
||||
this.videoItemId = _.uniqueId('action-item-');
|
||||
this.recordId = _.uniqueId('action-item-');
|
||||
}
|
||||
|
||||
componentWillUpdate(nextProps) {
|
||||
@ -67,50 +76,69 @@ class ActionsDropdown extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
handlePresentationClick() {
|
||||
this.props.mountModal(<PresentationUploaderContainer />);
|
||||
}
|
||||
|
||||
getAvailableActions() {
|
||||
const {
|
||||
intl,
|
||||
handleShareScreen,
|
||||
handleUnshareScreen,
|
||||
isVideoBroadcasting,
|
||||
isUserPresenter,
|
||||
isUserModerator,
|
||||
allowStartStopRecording,
|
||||
isRecording,
|
||||
record,
|
||||
toggleRecording,
|
||||
} = this.props;
|
||||
|
||||
return _.compact([
|
||||
(<DropdownListItem
|
||||
icon="presentation"
|
||||
label={intl.formatMessage(intlMessages.presentationLabel)}
|
||||
description={intl.formatMessage(intlMessages.presentationDesc)}
|
||||
key={this.presentationItemId}
|
||||
onClick={this.handlePresentationClick}
|
||||
/>),
|
||||
(Meteor.settings.public.kurento.enableScreensharing ?
|
||||
(isUserPresenter ?
|
||||
<DropdownListItem
|
||||
icon="presentation"
|
||||
label={intl.formatMessage(intlMessages.presentationLabel)}
|
||||
description={intl.formatMessage(intlMessages.presentationDesc)}
|
||||
key={this.presentationItemId}
|
||||
onClick={this.handlePresentationClick}
|
||||
/>
|
||||
: null),
|
||||
(Meteor.settings.public.kurento.enableScreensharing && isUserPresenter ?
|
||||
<DropdownListItem
|
||||
icon="desktop"
|
||||
label={intl.formatMessage(isVideoBroadcasting ? intlMessages.stopDesktopShareLabel : intlMessages.desktopShareLabel)}
|
||||
description={intl.formatMessage(isVideoBroadcasting ? intlMessages.stopDesktopShareDesc : intlMessages.desktopShareDesc)}
|
||||
label={intl.formatMessage(isVideoBroadcasting ?
|
||||
intlMessages.stopDesktopShareLabel : intlMessages.desktopShareLabel)}
|
||||
description={intl.formatMessage(isVideoBroadcasting ?
|
||||
intlMessages.stopDesktopShareDesc : intlMessages.desktopShareDesc)}
|
||||
key={this.videoItemId}
|
||||
onClick={isVideoBroadcasting ? handleUnshareScreen : handleShareScreen }
|
||||
onClick={isVideoBroadcasting ? handleUnshareScreen : handleShareScreen}
|
||||
/>
|
||||
: null),
|
||||
: null),
|
||||
(record && isUserModerator && allowStartStopRecording ?
|
||||
<DropdownListItem
|
||||
icon="record"
|
||||
label={intl.formatMessage(isRecording ?
|
||||
intlMessages.stopRecording : intlMessages.startRecording)}
|
||||
description={intl.formatMessage(isRecording ?
|
||||
intlMessages.stopRecording : intlMessages.startRecording)}
|
||||
key={this.recordId}
|
||||
onClick={toggleRecording}
|
||||
/>
|
||||
: null),
|
||||
]);
|
||||
}
|
||||
|
||||
handlePresentationClick() {
|
||||
this.props.mountModal(<PresentationUploaderContainer />);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
isUserPresenter,
|
||||
handleShareScreen,
|
||||
handleUnshareScreen,
|
||||
isVideoBroadcasting,
|
||||
isUserModerator,
|
||||
} = this.props;
|
||||
|
||||
const availableActions = this.getAvailableActions();
|
||||
|
||||
if (!isUserPresenter) return null;
|
||||
if ((!isUserPresenter && !isUserModerator) || availableActions.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Dropdown ref={(ref) => { this._dropdown = ref; }} >
|
||||
|
@ -18,8 +18,17 @@ class ActionsBar extends React.PureComponent {
|
||||
emojiList,
|
||||
emojiSelected,
|
||||
handleEmojiChange,
|
||||
isUserModerator,
|
||||
recordSettingsList,
|
||||
toggleRecording,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
allowStartStopRecording,
|
||||
recording: isRecording,
|
||||
record,
|
||||
} = recordSettingsList;
|
||||
|
||||
const actionBarClasses = {};
|
||||
actionBarClasses[styles.centerWithActions] = isUserPresenter;
|
||||
actionBarClasses[styles.center] = true;
|
||||
@ -32,6 +41,11 @@ class ActionsBar extends React.PureComponent {
|
||||
handleShareScreen,
|
||||
handleUnshareScreen,
|
||||
isVideoBroadcasting,
|
||||
isUserModerator,
|
||||
allowStartStopRecording,
|
||||
isRecording,
|
||||
record,
|
||||
toggleRecording,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -9,6 +9,7 @@ const ActionsBarContainer = props => <ActionsBar {...props} />;
|
||||
|
||||
export default withTracker(() => ({
|
||||
isUserPresenter: Service.isUserPresenter(),
|
||||
isUserModerator: Service.isUserModerator(),
|
||||
emojiList: Service.getEmojiList(),
|
||||
emojiSelected: Service.getEmoji(),
|
||||
handleEmojiChange: Service.setEmoji,
|
||||
@ -17,5 +18,6 @@ export default withTracker(() => ({
|
||||
handleShareScreen: () => shareScreen(),
|
||||
handleUnshareScreen: () => unshareScreen(),
|
||||
isVideoBroadcasting: isVideoBroadcasting(),
|
||||
|
||||
recordSettingsList: Service.recordSettingsList(),
|
||||
toggleRecording: Service.toggleRecording,
|
||||
}))(ActionsBarContainer);
|
||||
|
@ -2,10 +2,14 @@ import Auth from '/imports/ui/services/auth';
|
||||
import Users from '/imports/api/users';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import { EMOJI_STATUSES } from '/imports/utils/statuses';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
|
||||
export default {
|
||||
isUserPresenter: () => Users.findOne({ userId: Auth.userID }).presenter,
|
||||
getEmoji: () => Users.findOne({ userId: Auth.userID }).emoji,
|
||||
setEmoji: status => makeCall('setEmojiStatus', Auth.userID, status),
|
||||
getEmojiList: () => EMOJI_STATUSES,
|
||||
isUserModerator: () => Users.findOne({ userId: Auth.userID }).moderator,
|
||||
recordSettingsList: () => Meetings.findOne({ meetingId: Auth.meetingID }).recordProp,
|
||||
toggleRecording: () => makeCall('toggleRecording'),
|
||||
};
|
||||
|
@ -29,12 +29,24 @@ const intlMessages = defineMessages({
|
||||
id: 'app.navBar.toggleUserList.newMessages',
|
||||
description: 'label for toggleUserList btn when showing red notification',
|
||||
},
|
||||
recordingSession: {
|
||||
id: 'app.navBar.recording',
|
||||
description: 'label for when the session is being recorded',
|
||||
},
|
||||
recordingIndicatorOn: {
|
||||
id: 'app.navBar.recording.on',
|
||||
description: 'label for indicator when the session is being recorded',
|
||||
},
|
||||
recordingIndicatorOff: {
|
||||
id: 'app.navBar.recording.off',
|
||||
description: 'label for indicator when the session is not being recorded',
|
||||
},
|
||||
});
|
||||
|
||||
const propTypes = {
|
||||
presentationTitle: PropTypes.string.isRequired,
|
||||
hasUnreadMessages: PropTypes.bool.isRequired,
|
||||
beingRecorded: PropTypes.bool.isRequired,
|
||||
beingRecorded: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
@ -165,6 +177,8 @@ class NavBar extends Component {
|
||||
render() {
|
||||
const { hasUnreadMessages, beingRecorded, isExpanded, intl } = this.props;
|
||||
|
||||
const recordingMessage = beingRecorded.recording ? 'recordingIndicatorOn' : 'recordingIndicatorOff';
|
||||
|
||||
const toggleBtnClasses = {};
|
||||
toggleBtnClasses[styles.btn] = true;
|
||||
toggleBtnClasses[styles.btnWithNotificationDot] = hasUnreadMessages;
|
||||
@ -192,7 +206,13 @@ class NavBar extends Component {
|
||||
</div>
|
||||
<div className={styles.center}>
|
||||
{this.renderPresentationTitle()}
|
||||
<RecordingIndicator beingRecorded={beingRecorded} />
|
||||
{beingRecorded.record ?
|
||||
<span className={styles.presentationTitleSeparator}>|</span>
|
||||
: null}
|
||||
<RecordingIndicator
|
||||
{...beingRecorded}
|
||||
title={intl.formatMessage(intlMessages[recordingMessage])}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.right}>
|
||||
<SettingsDropdownContainer />
|
||||
|
@ -29,7 +29,7 @@ export default withRouter(withTracker(({ location, router }) => {
|
||||
|
||||
if (meetingObject != null) {
|
||||
meetingTitle = meetingObject.meetingProp.name;
|
||||
meetingRecorded = meetingObject.recordProp.recording;
|
||||
meetingRecorded = meetingObject.recordProp;
|
||||
}
|
||||
|
||||
const checkUnreadMessages = () => {
|
||||
|
@ -1,9 +1,17 @@
|
||||
import React from 'react';
|
||||
import { styles } from './styles';
|
||||
|
||||
const RecordingIndicator = ({ beingRecorded }) => {
|
||||
if (!beingRecorded) return null;
|
||||
return <div className={styles.indicator} />;
|
||||
const RecordingIndicator = ({
|
||||
record, title, recording,
|
||||
}) => {
|
||||
if (!record) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={recording ? styles.recordIndicator : styles.notRecording} />
|
||||
<span className={recording ? styles.recordingLabel : styles.notRecordingLabel}>{title}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RecordingIndicator;
|
||||
|
@ -1,22 +1,12 @@
|
||||
@import "../../../stylesheets/variables/_all";
|
||||
|
||||
.indicator {
|
||||
%baseIndicator {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: $font-size-base;
|
||||
height: $font-size-base;
|
||||
border-radius: 50%;
|
||||
border: 1px solid $color-white;
|
||||
margin-left: $line-height-computed;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: calc( -1px - #{($line-height-computed / 2)});
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background-color: $color-white;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
@ -31,3 +21,37 @@
|
||||
background-color: $color-danger;
|
||||
}
|
||||
}
|
||||
|
||||
%baseIndicatorLabel {
|
||||
font-weight: 200;
|
||||
font-size: $font-size-base;
|
||||
margin: 0 0 0 0.5rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.recordIndicator {
|
||||
@extend %baseIndicator;
|
||||
}
|
||||
|
||||
.notRecording {
|
||||
@extend %baseIndicator;
|
||||
border: 1px solid $color-gray;
|
||||
|
||||
&:after {
|
||||
background-color: $color-gray;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.recordingLabel {
|
||||
@extend %baseIndicatorLabel;
|
||||
color: $color-white;
|
||||
}
|
||||
|
||||
.notRecordingLabel {
|
||||
@extend %baseIndicatorLabel;
|
||||
color: $color-gray;
|
||||
}
|
||||
|
@ -39,6 +39,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.presentationTitleSeparator {
|
||||
color: $color-gray;
|
||||
font-size: $font-size-base;
|
||||
margin: 0 1rem;
|
||||
}
|
||||
|
||||
.btnWithNotificationDot {
|
||||
position: relative;
|
||||
|
||||
|
@ -96,7 +96,8 @@
|
||||
"toggleVoice",
|
||||
"clearPublicChatHistory",
|
||||
"changeRole",
|
||||
"ejectUserFromVoice"
|
||||
"ejectUserFromVoice",
|
||||
"toggleRecording"
|
||||
]
|
||||
},
|
||||
"presenter": {
|
||||
|
@ -96,7 +96,8 @@
|
||||
"toggleVoice",
|
||||
"clearPublicChatHistory",
|
||||
"changeRole",
|
||||
"ejectUserFromVoice"
|
||||
"ejectUserFromVoice",
|
||||
"toggleRecording"
|
||||
]
|
||||
},
|
||||
"presenter": {
|
||||
|
@ -93,6 +93,9 @@
|
||||
"app.navBar.userListToggleBtnLabel": "User List Toggle",
|
||||
"app.navBar.toggleUserList.ariaLabel": "Users / Conversations Toggle",
|
||||
"app.navBar.toggleUserList.newMessages": "with new message notification",
|
||||
"app.navBar.recording": "This session is being recorded",
|
||||
"app.navBar.recording.on": "Recording",
|
||||
"app.navBar.recording.off": "Not recording",
|
||||
"app.leaveConfirmation.title": "Leave Session",
|
||||
"app.leaveConfirmation.message": "Do you want to leave this meeting?",
|
||||
"app.leaveConfirmation.confirmLabel": "Leave",
|
||||
@ -185,6 +188,8 @@
|
||||
"app.actionsBar.actionsDropdown.initPollDesc": "Initiate a poll",
|
||||
"app.actionsBar.actionsDropdown.desktopShareDesc": "Share your screen with others",
|
||||
"app.actionsBar.actionsDropdown.stopDesktopShareDesc": "Stop sharing your screen with",
|
||||
"app.actionsBar.actionsDropdown.startRecording": "Start recording",
|
||||
"app.actionsBar.actionsDropdown.stopRecording": "Stop recording",
|
||||
"app.actionsBar.emojiMenu.statusTriggerLabel": "Status",
|
||||
"app.actionsBar.emojiMenu.awayLabel": "Away",
|
||||
"app.actionsBar.emojiMenu.awayDesc": "Change your status to away",
|
||||
|
Loading…
Reference in New Issue
Block a user