Merge pull request #12395 from gustavotrott/end-when-no-moderator

Implements endWhenNoModerator
This commit is contained in:
Anton Georgiev 2021-05-31 10:59:55 -04:00 committed by GitHub
commit 8868e84e9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 131 additions and 13 deletions

View File

@ -32,4 +32,5 @@ object MeetingEndReason {
val BREAKOUT_ENDED_EXCEEDING_DURATION = "BREAKOUT_ENDED_EXCEEDING_DURATION"
val BREAKOUT_ENDED_BY_MOD = "BREAKOUT_ENDED_BY_MOD"
val ENDED_DUE_TO_NO_AUTHED_USER = "ENDED_DUE_TO_NO_AUTHED_USER"
val ENDED_DUE_TO_NO_MODERATOR = "ENDED_DUE_TO_NO_MODERATOR"
}

View File

@ -3,14 +3,18 @@ package org.bigbluebutton.core.domain
case class MeetingExpiryTracker(
startedOnInMs: Long,
userHasJoined: Boolean,
moderatorHasJoined: Boolean,
isBreakout: Boolean,
lastUserLeftOnInMs: Option[Long],
lastModeratorLeftOnInMs: Long,
durationInMs: Long,
meetingExpireIfNoUserJoinedInMs: Long,
meetingExpireWhenLastUserLeftInMs: Long,
userInactivityInspectTimerInMs: Long,
userInactivityThresholdInMs: Long,
userActivitySignResponseDelayInMs: Long
userActivitySignResponseDelayInMs: Long,
endWhenNoModerator: Boolean,
endWhenNoModeratorDelayInMs: Long
) {
def setUserHasJoined(): MeetingExpiryTracker = {
if (!userHasJoined) {
@ -24,6 +28,18 @@ case class MeetingExpiryTracker(
copy(lastUserLeftOnInMs = Some(timestampInMs))
}
def setModeratorHasJoined(): MeetingExpiryTracker = {
if (!moderatorHasJoined) {
copy(moderatorHasJoined = true, lastModeratorLeftOnInMs = 0)
} else {
copy(lastModeratorLeftOnInMs = 0)
}
}
def setLastModeratorLeftOn(timestampInMs: Long): MeetingExpiryTracker = {
copy(lastModeratorLeftOnInMs = timestampInMs)
}
def hasMeetingExpiredAfterLastUserLeft(timestampInMs: Long): Boolean = {
val expire = for {
lastUserLeftOn <- lastUserLeftOnInMs

View File

@ -55,6 +55,10 @@ object Users2x {
users.toVector.length
}
def numActiveModerators(users: Users2x): Int = {
users.toVector.filter(u => u.role == Roles.MODERATOR_ROLE && !u.userLeftFlag.left).length
}
def findNotPresenters(users: Users2x): Vector[UserState] = {
users.toVector.filter(u => !u.presenter)
}

View File

@ -135,14 +135,18 @@ class MeetingActor(
val expiryTracker = new MeetingExpiryTracker(
startedOnInMs = TimeUtil.timeNowInMs(),
userHasJoined = false,
moderatorHasJoined = false,
isBreakout = props.meetingProp.isBreakout,
lastUserLeftOnInMs = None,
lastModeratorLeftOnInMs = 0,
durationInMs = TimeUtil.minutesToMillis(props.durationProps.duration),
meetingExpireIfNoUserJoinedInMs = TimeUtil.minutesToMillis(props.durationProps.meetingExpireIfNoUserJoinedInMinutes),
meetingExpireWhenLastUserLeftInMs = TimeUtil.minutesToMillis(props.durationProps.meetingExpireWhenLastUserLeftInMinutes),
userInactivityInspectTimerInMs = TimeUtil.minutesToMillis(props.durationProps.userInactivityInspectTimerInMinutes),
userInactivityThresholdInMs = TimeUtil.minutesToMillis(props.durationProps.userInactivityThresholdInMinutes),
userActivitySignResponseDelayInMs = TimeUtil.minutesToMillis(props.durationProps.userActivitySignResponseDelayInMinutes)
userActivitySignResponseDelayInMs = TimeUtil.minutesToMillis(props.durationProps.userActivitySignResponseDelayInMinutes),
endWhenNoModerator = props.durationProps.endWhenNoModerator,
endWhenNoModeratorDelayInMs = TimeUtil.minutesToMillis(props.durationProps.endWhenNoModeratorDelayInMinutes)
)
val recordingTracker = new MeetingRecordingTracker(startedOnInMs = 0L, previousDurationInMs = 0L, currentDurationInMs = 0L)
@ -298,6 +302,23 @@ class MeetingActor(
}
}
private def updateModeratorsPresence() {
if (Users2x.numActiveModerators(liveMeeting.users2x) > 0) {
if (state.expiryTracker.moderatorHasJoined == false ||
state.expiryTracker.lastModeratorLeftOnInMs != 0) {
log.info("A moderator has joined. Setting setModeratorHasJoined(). meetingId=" + props.meetingProp.intId)
val tracker = state.expiryTracker.setModeratorHasJoined()
state = state.update(tracker)
}
} else {
if (state.expiryTracker.moderatorHasJoined == true) {
log.info("All moderators have left. Setting setLastModeratorLeftOn(). meetingId=" + props.meetingProp.intId)
val tracker = state.expiryTracker.setLastModeratorLeftOn(TimeUtil.timeNowInMs())
state = state.update(tracker)
}
}
}
private def updateUserLastInactivityInspect(userId: String) {
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, userId)
@ -321,9 +342,15 @@ class MeetingActor(
// Users
case m: ValidateAuthTokenReqMsg => state = usersApp.handleValidateAuthTokenReqMsg(m, state)
case m: UserJoinMeetingReqMsg => state = handleUserJoinMeetingReqMsg(m, state)
case m: UserJoinMeetingAfterReconnectReqMsg => state = handleUserJoinMeetingAfterReconnectReqMsg(m, state)
case m: UserLeaveReqMsg => state = handleUserLeaveReqMsg(m, state)
case m: UserJoinMeetingReqMsg =>
state = handleUserJoinMeetingReqMsg(m, state)
updateModeratorsPresence()
case m: UserJoinMeetingAfterReconnectReqMsg =>
state = handleUserJoinMeetingAfterReconnectReqMsg(m, state)
updateModeratorsPresence()
case m: UserLeaveReqMsg =>
state = handleUserLeaveReqMsg(m, state)
updateModeratorsPresence()
case m: UserBroadcastCamStartMsg => handleUserBroadcastCamStartMsg(m)
case m: UserBroadcastCamStopMsg => handleUserBroadcastCamStopMsg(m)
case m: GetCamBroadcastPermissionReqMsg => handleGetCamBroadcastPermissionReqMsg(m)
@ -331,7 +358,7 @@ class MeetingActor(
case m: UserJoinedVoiceConfEvtMsg => handleUserJoinedVoiceConfEvtMsg(m)
case m: LogoutAndEndMeetingCmdMsg => usersApp.handleLogoutAndEndMeetingCmdMsg(m, state)
case m: SetRecordingStatusCmdMsg =>
case m: SetRecordingStatusCmdMsg =>
state = usersApp.handleSetRecordingStatusCmdMsg(m, state)
updateUserLastActivity(m.body.setBy)
case m: RecordAndClearPreviousMarkersCmdMsg =>
@ -354,6 +381,7 @@ class MeetingActor(
case m: ChangeUserRoleCmdMsg =>
usersApp.handleChangeUserRoleCmdMsg(m)
updateUserLastActivity(m.body.changedBy)
updateModeratorsPresence()
// Whiteboard
case m: SendCursorPositionPubMsg => wbApp.handle(m, liveMeeting, msgBus)
@ -596,7 +624,8 @@ class MeetingActor(
processUserInactivityAudit()
flagRegisteredUsersWhoHasNotJoined()
checkIfNeetToEndMeetingWhenNoAuthedUsers(liveMeeting)
checkIfNeedToEndMeetingWhenNoAuthedUsers(liveMeeting)
checkIfNeedToEndMeetingWhenNoModerators(liveMeeting)
}
def checkVoiceConfUsersStatus(): Unit = {
@ -663,7 +692,7 @@ class MeetingActor(
}
private def checkIfNeetToEndMeetingWhenNoAuthedUsers(liveMeeting: LiveMeeting): Unit = {
private def checkIfNeedToEndMeetingWhenNoAuthedUsers(liveMeeting: LiveMeeting): Unit = {
val authUserJoined = MeetingStatus2x.hasAuthedUserJoined(liveMeeting.status)
if (endMeetingWhenNoMoreAuthedUsers &&
@ -684,6 +713,28 @@ class MeetingActor(
}
}
private def checkIfNeedToEndMeetingWhenNoModerators(liveMeeting: LiveMeeting): Unit = {
if (state.expiryTracker.endWhenNoModerator &&
!liveMeeting.props.meetingProp.isBreakout &&
state.expiryTracker.moderatorHasJoined &&
state.expiryTracker.lastModeratorLeftOnInMs != 0 &&
//Check if has moderator with leftFlag
Users2x.findModerator(liveMeeting.users2x).toVector.length == 0) {
val hasModeratorLeftRecently = (TimeUtil.timeNowInMs() - state.expiryTracker.endWhenNoModeratorDelayInMs) < state.expiryTracker.lastModeratorLeftOnInMs
if (!hasModeratorLeftRecently) {
log.info("Meeting will end due option endWhenNoModerator is enabled and all moderators have left the meeting. meetingId=" + props.meetingProp.intId)
sendEndMeetingDueToExpiry(
MeetingEndReason.ENDED_DUE_TO_NO_MODERATOR,
eventBus, outGW, liveMeeting,
"system"
)
} else {
val msToEndMeeting = state.expiryTracker.lastModeratorLeftOnInMs - (TimeUtil.timeNowInMs() - state.expiryTracker.endWhenNoModeratorDelayInMs)
log.info("All moderators have left. Meeting will end in " + TimeUtil.millisToSeconds(msToEndMeeting) + " seconds. meetingId=" + props.meetingProp.intId)
}
}
}
def handleExtendMeetingDuration(msg: ExtendMeetingDuration) {
}

View File

@ -4,7 +4,9 @@ case class ConfigProps(defaultConfigToken: String, config: String)
case class DurationProps(duration: Int, createdTime: Long, createdDate: String,
meetingExpireIfNoUserJoinedInMinutes: Int, meetingExpireWhenLastUserLeftInMinutes: Int,
userInactivityInspectTimerInMinutes: Int, userInactivityThresholdInMinutes: Int, userActivitySignResponseDelayInMinutes: Int)
userInactivityInspectTimerInMinutes: Int, userInactivityThresholdInMinutes: Int,
userActivitySignResponseDelayInMinutes: Int,
endWhenNoModerator: Boolean, endWhenNoModeratorDelayInMinutes: Int)
case class MeetingProp(name: String, extId: String, intId: String, isBreakout: Boolean)

View File

@ -17,6 +17,8 @@ trait TestFixtures {
val userInactivityInspectTimerInMinutes = 60
val userInactivityThresholdInMinutes = 10
val userActivitySignResponseDelayInMinutes = 5
val endWhenNoModerator = false
val endWhenNoModeratorDelayInMinutes = 1
val autoStartRecording = false
val allowStartStopRecording = false

View File

@ -432,7 +432,8 @@ public class MeetingService implements MessageListener {
m.getDialNumber(), m.getMaxUsers(),
m.getMeetingExpireIfNoUserJoinedInMinutes(), m.getmeetingExpireWhenLastUserLeftInMinutes(),
m.getUserInactivityInspectTimerInMinutes(), m.getUserInactivityThresholdInMinutes(),
m.getUserActivitySignResponseDelayInMinutes(), m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getMeetingKeepEvents(),
m.getUserActivitySignResponseDelayInMinutes(), m.getEndWhenNoModerator(), m.getEndWhenNoModeratorDelayInMinutes(),
m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getMeetingKeepEvents(),
m.breakoutRoomsParams,
m.lockSettingsParams, m.getHtml5InstanceId());
}

View File

@ -115,6 +115,7 @@ public class ParamsProcessorUtil {
private Integer userActivitySignResponseDelayInMinutes = 5;
private Boolean defaultAllowDuplicateExtUserid = true;
private Boolean defaultEndWhenNoModerator = false;
private Integer defaultEndWhenNoModeratorDelayInMinutes = 1;
private Integer defaultHtml5InstanceId = 1;
private String formatConfNum(String s) {
@ -523,6 +524,8 @@ public class ParamsProcessorUtil {
meeting.setUserActivitySignResponseDelayInMinutes(userActivitySignResponseDelayInMinutes);
meeting.setUserInactivityThresholdInMinutes(userInactivityThresholdInMinutes);
// meeting.setHtml5InstanceId(html5InstanceId);
meeting.setEndWhenNoModerator(defaultEndWhenNoModerator);
meeting.setEndWhenNoModeratorDelayInMinutes(defaultEndWhenNoModeratorDelayInMinutes);
// Add extra parameters for breakout room
if (isBreakout) {
@ -1156,5 +1159,8 @@ public class ParamsProcessorUtil {
this.defaultEndWhenNoModerator = val;
}
public void setEndWhenNoModeratorDelayInMinutes(Integer value) {
this.defaultEndWhenNoModeratorDelayInMinutes = value;
}
}

View File

@ -91,6 +91,8 @@ public class Meeting {
private Integer userInactivityInspectTimerInMinutes = 120;
private Integer userInactivityThresholdInMinutes = 30;
private Integer userActivitySignResponseDelayInMinutes = 5;
private Boolean endWhenNoModerator = false;
private Integer endWhenNoModeratorDelayInMinutes = 1;
public final BreakoutRoomsParams breakoutRoomsParams;
public final LockSettingsParams lockSettingsParams;
@ -99,8 +101,6 @@ public class Meeting {
private String meetingEndedCallbackURL = "";
public final Boolean endWhenNoModerator;
private Integer html5InstanceId;
public Meeting(Meeting.Builder builder) {
@ -134,6 +134,7 @@ public class Meeting {
lockSettingsParams = builder.lockSettingsParams;
allowDuplicateExtUserid = builder.allowDuplicateExtUserid;
endWhenNoModerator = builder.endWhenNoModerator;
endWhenNoModeratorDelayInMinutes = builder.endWhenNoModeratorDelayInMinutes;
html5InstanceId = builder.html5InstanceId;
/*
@ -660,6 +661,22 @@ public class Meeting {
this.userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes;
}
public Boolean getEndWhenNoModerator() {
return endWhenNoModerator;
}
public void setEndWhenNoModerator(Boolean endWhenNoModerator) {
this.endWhenNoModerator = endWhenNoModerator;
}
public Integer getEndWhenNoModeratorDelayInMinutes() {
return endWhenNoModeratorDelayInMinutes;
}
public void setEndWhenNoModeratorDelayInMinutes(Integer endWhenNoModeratorDelayInMinutes) {
this.endWhenNoModeratorDelayInMinutes = endWhenNoModeratorDelayInMinutes;
}
public String getMeetingEndedCallbackURL() {
return meetingEndedCallbackURL;
}
@ -742,6 +759,7 @@ public class Meeting {
private LockSettingsParams lockSettingsParams;
private Boolean allowDuplicateExtUserid;
private Boolean endWhenNoModerator;
private Integer endWhenNoModeratorDelayInMinutes;
private int html5InstanceId;
public Builder(String externalId, String internalId, long createTime) {
@ -885,6 +903,11 @@ public class Meeting {
return this;
}
public Builder withEndWhenNoModeratorDelayInMinutes(Integer endWhenNoModeratorDelayInMinutes) {
this.endWhenNoModeratorDelayInMinutes = endWhenNoModeratorDelayInMinutes;
return this;
}
public Builder withHTML5InstanceId(int instanceId) {
html5InstanceId = instanceId;
return this;

View File

@ -26,6 +26,8 @@ public interface IBbbWebApiGWApp {
Integer userInactivityInspectTimerInMinutes,
Integer userInactivityThresholdInMinutes,
Integer userActivitySignResponseDelayInMinutes,
Boolean endWhenNoModerator,
Integer endWhenNoModeratorDelayInMinutes,
Boolean muteOnStart,
Boolean allowModsToUnmuteUsers,
Boolean keepEvents,

View File

@ -135,6 +135,8 @@ class BbbWebApiGWApp(
userInactivityInspectTimerInMinutes: java.lang.Integer,
userInactivityThresholdInMinutes: java.lang.Integer,
userActivitySignResponseDelayInMinutes: java.lang.Integer,
endWhenNoModerator: java.lang.Boolean,
endWhenNoModeratorDelayInMinutes: java.lang.Integer,
muteOnStart: java.lang.Boolean,
allowModsToUnmuteUsers: java.lang.Boolean,
keepEvents: java.lang.Boolean,
@ -151,7 +153,9 @@ class BbbWebApiGWApp(
meetingExpireWhenLastUserLeftInMinutes = meetingExpireWhenLastUserLeftInMinutes.intValue(),
userInactivityInspectTimerInMinutes = userInactivityInspectTimerInMinutes.intValue(),
userInactivityThresholdInMinutes = userInactivityThresholdInMinutes.intValue(),
userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes.intValue()
userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes.intValue(),
endWhenNoModerator = endWhenNoModerator.booleanValue(),
endWhenNoModeratorDelayInMinutes.intValue()
)
val password = PasswordProp(moderatorPass = moderatorPass, viewerPass = viewerPass)

View File

@ -47,6 +47,8 @@ export default function addMeeting(meeting) {
userInactivityInspectTimerInMinutes: Number,
userInactivityThresholdInMinutes: Number,
userActivitySignResponseDelayInMinutes: Number,
endWhenNoModerator: Boolean,
endWhenNoModeratorDelayInMinutes: Number,
timeRemaining: Number,
},
welcomeProp: {

View File

@ -387,3 +387,6 @@ defaultTextTrackUrl=${bigbluebutton.web.serverURL}/bigbluebutton
# Needed for classes where teacher gets disconnected and can't get back in. Prevents
# students from running amok.
endWhenNoModerator=false
# Number of minutes to wait for moderator rejoin before end meeting (if `endWhenNoModerator` enabled)
endWhenNoModeratorDelayInMinutes=1

View File

@ -164,6 +164,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<property name="lockSettingsLockOnJoinConfigurable" value="${lockSettingsLockOnJoinConfigurable}"/>
<property name="allowDuplicateExtUserid" value="${allowDuplicateExtUserid}"/>
<property name="endWhenNoModerator" value="${endWhenNoModerator}"/>
<property name="endWhenNoModeratorDelayInMinutes" value="${endWhenNoModeratorDelayInMinutes}"/>
<property name="defaultKeepEvents" value="${defaultKeepEvents}"/>
</bean>