Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: only record media while meeting is being actively recorded #18044

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,30 @@ trait GetRecordingStatusReqMsgHdlr {

def handleGetRecordingStatusReqMsg(msg: GetRecordingStatusReqMsg) {

def buildGetRecordingStatusRespMsg(meetingId: String, userId: String, recorded: Boolean, recording: Boolean): BbbCommonEnvCoreMsg = {
def buildGetRecordingStatusRespMsg(
meetingId: String,
userId: String,
recorded: Boolean,
recording: Boolean,
recordFullDurationMedia: Boolean
): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId)
val envelope = BbbCoreEnvelope(GetRecordingStatusRespMsg.NAME, routing)
val body = GetRecordingStatusRespMsgBody(recorded, recording, userId)
val body = GetRecordingStatusRespMsgBody(recorded, recording, recordFullDurationMedia, userId)
val header = BbbClientMsgHeader(GetRecordingStatusRespMsg.NAME, meetingId, userId)
val event = GetRecordingStatusRespMsg(header, body)

BbbCommonEnvCoreMsg(envelope, event)
}

val event = buildGetRecordingStatusRespMsg(liveMeeting.props.meetingProp.intId, msg.body.requestedBy,
liveMeeting.props.recordProp.record, MeetingStatus2x.isRecording(liveMeeting.status))
val event = buildGetRecordingStatusRespMsg(
liveMeeting.props.meetingProp.intId,
msg.body.requestedBy,
liveMeeting.props.recordProp.record,
MeetingStatus2x.isRecording(liveMeeting.status),
liveMeeting.props.recordProp.recordFullDurationMedia
)

outGW.send(event)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.bigbluebutton.core.bus.BigBlueButtonEvent
import org.bigbluebutton.core.api.SendRecordingTimerInternalMsg
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
import org.bigbluebutton.core.apps.voice.VoiceApp

trait SetRecordingStatusCmdMsgHdlr extends RightsManagementTrait {
this: UsersApp =>
Expand Down Expand Up @@ -49,6 +50,19 @@ trait SetRecordingStatusCmdMsgHdlr extends RightsManagementTrait {
outGW.send(notifyEvent)

MeetingStatus2x.recordingStarted(liveMeeting.status)

// If meeting is not set to record full duration media, then we need to
// start recording media here. Audio/FS recording is triggered here;
// SFU intercepts this event and toggles rec for video and screen sharing.
if (!liveMeeting.props.recordProp.recordFullDurationMedia) {
log.info("Send START RECORDING voice conf. meetingId=" +
liveMeeting.props.meetingProp.intId +
" voice conf=" + liveMeeting.props.voiceProp.voiceConf)
VoiceApp.startRecordingVoiceConference(
liveMeeting,
outGW
)
}
} else {
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
liveMeeting.props.meetingProp.intId,
Expand All @@ -61,6 +75,14 @@ trait SetRecordingStatusCmdMsgHdlr extends RightsManagementTrait {
outGW.send(notifyEvent)

MeetingStatus2x.recordingStopped(liveMeeting.status)

// If meeting is not set to record full duration media, then we need to stop recording
if (!liveMeeting.props.recordProp.recordFullDurationMedia) {
VoiceApp.stopRecordingVoiceConference(
liveMeeting,
outGW
)
}
}

val event = buildRecordingStatusChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.setBy, msg.body.recording)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import org.bigbluebutton.core.running.{LiveMeeting, MeetingActor, OutMsgRouter}
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.apps.users.UsersApp
import org.bigbluebutton.core.util.ColorPicker
import org.bigbluebutton.core.util.TimeUtil

object VoiceApp extends SystemConfiguration {

object VoiceApp extends SystemConfiguration {
def genRecordPath(
recordDir: String,
meetingId: String,
Expand All @@ -35,18 +36,7 @@ object VoiceApp extends SystemConfiguration {
}
}

def startRecordingVoiceConference(liveMeeting: LiveMeeting, outGW: OutMsgRouter, stream: String): Unit = {
MeetingStatus2x.voiceRecordingStart(liveMeeting.status, stream)
val event = MsgBuilder.buildStartRecordingVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
stream
)
outGW.send(event)
}

def stopRecordingVoiceConference(liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {

val recStreams = MeetingStatus2x.getVoiceRecordingStreams(liveMeeting.status)

recStreams foreach { rs =>
Expand All @@ -58,6 +48,27 @@ object VoiceApp extends SystemConfiguration {
}
}

def startRecordingVoiceConference(
liveMeeting: LiveMeeting,
outGW: OutMsgRouter
): Unit = {
val meetingId = liveMeeting.props.meetingProp.intId
val now = TimeUtil.timeNowInMs()
val recordFile = genRecordPath(
voiceConfRecordPath,
meetingId,
now,
voiceConfRecordCodec
)
MeetingStatus2x.voiceRecordingStart(liveMeeting.status, recordFile)
val event = MsgBuilder.buildStartRecordingVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
recordFile
)
outGW.send(event)
}

def broadcastUserMutedVoiceEvtMsg(
meetingId: String,
vu: VoiceUserState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package org.bigbluebutton.core.apps.voice
import org.bigbluebutton.SystemConfiguration
import org.bigbluebutton.common2.msgs.VoiceConfRunningEvtMsg
import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.util.TimeUtil
import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.core.apps.voice.VoiceApp

trait VoiceConfRunningEvtMsgHdlr extends SystemConfiguration {
this: BaseMeetingActor =>
Expand All @@ -15,17 +16,12 @@ trait VoiceConfRunningEvtMsgHdlr extends SystemConfiguration {
log.info("Received VoiceConfRunningEvtMsg " + msg.body.running)

if (liveMeeting.props.recordProp.record) {
if (msg.body.running) {
if (msg.body.running &&
(MeetingStatus2x.isRecording(liveMeeting.status) || liveMeeting.props.recordProp.recordFullDurationMedia)) {
val meetingId = liveMeeting.props.meetingProp.intId
val recordFile = VoiceApp.genRecordPath(
voiceConfRecordPath,
meetingId,
TimeUtil.timeNowInMs(),
voiceConfRecordCodec
)
log.info("Send START RECORDING voice conf. meetingId=" + meetingId + " voice conf=" + liveMeeting.props.voiceProp.voiceConf)

VoiceApp.startRecordingVoiceConference(liveMeeting, outGW, recordFile)
VoiceApp.startRecordingVoiceConference(liveMeeting, outGW)
} else {
VoiceApp.stopRecordingVoiceConference(liveMeeting, outGW)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -779,22 +779,15 @@ class MeetingActor(
val elapsedInMin = TimeUtil.millisToMinutes(elapsedInMs)

if (props.recordProp.record &&
(MeetingStatus2x.isRecording(liveMeeting.status) || props.recordProp.recordFullDurationMedia) &&
recordingChapterBreakLengthInMinutes > 0 &&
elapsedInMin > recordingChapterBreakLengthInMinutes) {
lastRecBreakSentOn = now
val event = MsgBuilder.buildRecordingChapterBreakSysMsg(props.meetingProp.intId, TimeUtil.timeNowInMs())
outGW.send(event)

VoiceApp.stopRecordingVoiceConference(liveMeeting, outGW)

val meetingId = liveMeeting.props.meetingProp.intId
val recordFile = VoiceApp.genRecordPath(
voiceConfRecordPath,
meetingId,
now,
voiceConfRecordCodec
)
VoiceApp.startRecordingVoiceConference(liveMeeting, outGW, recordFile)
VoiceApp.startRecordingVoiceConference(liveMeeting, outGW)
}
}

Expand Down Expand Up @@ -993,17 +986,15 @@ class MeetingActor(
// Remove recording streams that have stopped so we should only have
// one active recording stream.

// Let us start recording.
val meetingId = liveMeeting.props.meetingProp.intId
val recordFile = VoiceApp.genRecordPath(
voiceConfRecordPath,
meetingId,
TimeUtil.timeNowInMs(),
voiceConfRecordCodec
)
log.info("Forcing START RECORDING voice conf. meetingId=" + meetingId + " voice conf=" + liveMeeting.props.voiceProp.voiceConf)

VoiceApp.startRecordingVoiceConference(liveMeeting, outGW, recordFile)
// If the meeting is being actively recorded or recordFullDurationMedia is true
// then we should start recording.
if (MeetingStatus2x.isRecording(liveMeeting.status) ||
liveMeeting.props.recordProp.recordFullDurationMedia) {
val meetingId = liveMeeting.props.meetingProp.intId
log.info("Forcing START RECORDING voice conf. meetingId=" + meetingId + " voice conf=" + liveMeeting.props.voiceProp.voiceConf)

VoiceApp.startRecordingVoiceConference(liveMeeting, outGW)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ case class BreakoutProps(

case class PasswordProp(moderatorPass: String, viewerPass: String, learningDashboardAccessToken: String)

case class RecordProp(record: Boolean, autoStartRecording: Boolean, allowStartStopRecording: Boolean, keepEvents: Boolean)
case class RecordProp(record: Boolean, autoStartRecording: Boolean, allowStartStopRecording: Boolean, recordFullDurationMedia: Boolean, keepEvents: Boolean)

case class WelcomeProp(welcomeMsgTemplate: String, welcomeMsg: String, modOnlyMessage: String)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,12 @@ case class GetRecordingStatusReqMsgBody(requestedBy: String)
*/
object GetRecordingStatusRespMsg { val NAME = "GetRecordingStatusRespMsg" }
case class GetRecordingStatusRespMsg(header: BbbClientMsgHeader, body: GetRecordingStatusRespMsgBody) extends BbbCoreMsg
case class GetRecordingStatusRespMsgBody(recorded: Boolean, recording: Boolean, requestedBy: String)
case class GetRecordingStatusRespMsgBody(
recorded: Boolean,
recording: Boolean,
recordFullDurationMedia: Boolean,
requestedBy: String
)

/**
* Sent by user to start recording mark.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ trait TestFixtures {

val autoStartRecording = false
val allowStartStopRecording = false
val recordFullDurationMedia = false
val webcamsOnlyForModerator = false
val meetingCameraCap = 0
val userCameraCap = 0
Expand Down Expand Up @@ -61,7 +62,10 @@ trait TestFixtures {
userInactivityInspectTimerInMinutes = userInactivityInspectTimerInMinutes, userInactivityThresholdInMinutes = userInactivityInspectTimerInMinutes, userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes)
val password = PasswordProp(moderatorPass = moderatorPassword, viewerPass = viewerPassword, learningDashboardAccessToken = learningDashboardAccessToken)
val recordProp = RecordProp(record = record, autoStartRecording = autoStartRecording,
allowStartStopRecording = allowStartStopRecording, keepEvents = keepEvents)
allowStartStopRecording = allowStartStopRecording,
recordFullDurationMedia = recordFullDurationMedia,
keepEvents = keepEvents
)
val welcomeProp = WelcomeProp(welcomeMsgTemplate = welcomeMsgTemplate, welcomeMsg = welcomeMsg,
modOnlyMessage = modOnlyMessage)
val voiceProp = VoiceProp(telVoice = voiceConfId, voiceConf = voiceConfId, dialNumber = dialNumber, muteOnStart = muteOnStart)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ public class ApiParams {
public static final String END_WHEN_NO_MODERATOR = "endWhenNoModerator";
public static final String END_WHEN_NO_MODERATOR_DELAY_IN_MINUTES = "endWhenNoModeratorDelayInMinutes";

public static final String RECORD_FULL_DURATION_MEDIA = "recordFullDurationMedia";

private ApiParams() {
throw new IllegalStateException("ApiParams is a utility class. Instanciation is forbidden.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ private void handleCreateMeeting(Meeting m) {

gw.createMeeting(m.getInternalId(), m.getExternalId(), m.getParentMeetingId(), m.getName(), m.isRecord(),
m.getTelVoice(), m.getDuration(), m.getAutoStartRecording(), m.getAllowStartStopRecording(),
m.getRecordFullDurationMedia(),
m.getWebcamsOnlyForModerator(), m.getMeetingCameraCap(), m.getUserCameraCap(), m.getMaxPinnedCameras(), m.getModeratorPassword(), m.getViewerPassword(),
m.getLearningDashboardAccessToken(), m.getCreateTime(),
formatPrettyDate(m.getCreateTime()), m.isBreakout(), m.getSequence(), m.isFreeJoin(), m.getMetadata(),
Expand Down Expand Up @@ -1167,7 +1168,7 @@ public void run() {
} else if (message instanceof GuestLobbyMessageChanged) {
processGuestLobbyMessageChanged((GuestLobbyMessageChanged) message);
} else if (message instanceof PrivateGuestLobbyMessageChanged) {
processPrivateGuestLobbyMessageChanged((PrivateGuestLobbyMessageChanged) message);
processPrivateGuestLobbyMessageChanged((PrivateGuestLobbyMessageChanged) message);
} else if (message instanceof RecordChapterBreak) {
processRecordingChapterBreak((RecordChapterBreak) message);
} else if (message instanceof MakePresentationDownloadableMsg) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public class ParamsProcessorUtil {
private boolean disableRecordingDefault;
private boolean autoStartRecording;
private boolean allowStartStopRecording;
private boolean recordFullDurationMedia;
private boolean learningDashboardEnabled = true;
private int learningDashboardCleanupDelayInMinutes;
private boolean webcamsOnlyForModerator;
Expand Down Expand Up @@ -503,6 +504,18 @@ boolean record = processRecordMeeting(params.get(ApiParams.RECORD));
}
}

boolean _recordFullDurationMedia = recordFullDurationMedia;
if (!StringUtils.isEmpty(params.get(ApiParams.ALLOW_START_STOP_RECORDING))) {
try {
_recordFullDurationMedia = Boolean.parseBoolean(params
.get(ApiParams.RECORD_FULL_DURATION_MEDIA));
} catch (Exception ex) {
log.warn(
"Invalid param [recordFullDurationMedia] for meeting=[{}]",
internalMeetingId);
}
}

// Check Disabled Features
ArrayList<String> listOfDisabledFeatures=new ArrayList(Arrays.asList(defaultDisabledFeatures.split(",")));
if (!StringUtils.isEmpty(params.get(ApiParams.DISABLED_FEATURES))) {
Expand Down Expand Up @@ -555,7 +568,7 @@ boolean record = processRecordMeeting(params.get(ApiParams.RECORD));
int learningDashboardCleanupMins = 0;
if(listOfDisabledFeatures.contains("learningDashboard") == false) {
learningDashboardAccessToken = RandomStringUtils.randomAlphanumeric(12).toLowerCase();

learningDashboardCleanupMins = learningDashboardCleanupDelayInMinutes;
if (!StringUtils.isEmpty(params.get(ApiParams.LEARNING_DASHBOARD_CLEANUP_DELAY_IN_MINUTES))) {
try {
Expand Down Expand Up @@ -735,6 +748,7 @@ boolean record = processRecordMeeting(params.get(ApiParams.RECORD));
.withDefaultAvatarURL(avatarURL)
.withAutoStartRecording(autoStartRec)
.withAllowStartStopRecording(allowStartStoptRec)
.withRecordFullDurationMedia(_recordFullDurationMedia)
.withWebcamsOnlyForModerator(webcamsOnlyForMod)
.withMeetingCameraCap(meetingCameraCap)
.withUserCameraCap(userCameraCap)
Expand Down Expand Up @@ -1226,6 +1240,10 @@ public void setAllowStartStopRecording(boolean allowStartStopRecording) {
this.allowStartStopRecording = allowStartStopRecording;
}

public void setRecordFullDurationMedia(boolean recordFullDurationMedia) {
this.recordFullDurationMedia = recordFullDurationMedia;
}

public void setLearningDashboardEnabled(boolean learningDashboardEnabled) {
this.learningDashboardEnabled = learningDashboardEnabled;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public class Meeting {
private boolean record;
private boolean autoStartRecording = false;
private boolean allowStartStopRecording = false;
private boolean recordFullDurationMedia = false;
private boolean haveRecordingMarks = false;
private boolean webcamsOnlyForModerator = false;
private Integer meetingCameraCap = 0;
Expand Down Expand Up @@ -148,6 +149,7 @@ public Meeting(Meeting.Builder builder) {
record = builder.record;
autoStartRecording = builder.autoStartRecording;
allowStartStopRecording = builder.allowStartStopRecording;
recordFullDurationMedia = builder.recordFullDurationMedia;
webcamsOnlyForModerator = builder.webcamsOnlyForModerator;
meetingCameraCap = builder.meetingCameraCap;
userCameraCap = builder.userCameraCap;
Expand Down Expand Up @@ -321,7 +323,7 @@ public Boolean isCaptureSlides() {
public void setCaptureSlides(Boolean capture) {
this.captureSlides = captureSlides;
}

public Boolean isCaptureNotes() {
return captureNotes;
}
Expand Down Expand Up @@ -582,6 +584,10 @@ public boolean getAllowStartStopRecording() {
return allowStartStopRecording;
}

public boolean getRecordFullDurationMedia() {
return recordFullDurationMedia;
}

public boolean getWebcamsOnlyForModerator() {
return webcamsOnlyForModerator;
}
Expand Down Expand Up @@ -862,6 +868,7 @@ public static class Builder {
private int maxUsers;
private boolean record;
private boolean autoStartRecording;
private boolean recordFullDurationMedia;
private boolean allowStartStopRecording;
private boolean webcamsOnlyForModerator;
private Integer meetingCameraCap;
Expand Down Expand Up @@ -938,6 +945,11 @@ public Builder withAllowStartStopRecording(boolean allow) {
return this;
}

public Builder withRecordFullDurationMedia(boolean recordFullDurationMedia) {
this.recordFullDurationMedia = recordFullDurationMedia;
return this;
}

public Builder withWebcamsOnlyForModerator(boolean only) {
this.webcamsOnlyForModerator = only;
return this;
Expand Down