feat: only record media while meeting is being actively recorded

Only record media (microphone, webcams and screens) while meeting is
being actively recorded (ie an user has enabled recording in the
conference). If the conference's recording is paused, media capture will
stop as well (with appropriate recording events).

A bigbluebutton.properties/API#create parameter called
`recordFullDurationMedia` is added to control this behavior. The default
is false (only capture while recording is active). Setting it to `true`
enables the current (legacy) behavior: always capture media if the
meeting's `recorded` prop is true.
This commit is contained in:
prlanzarin 2023-05-30 14:56:50 -03:00
parent c6f40a862e
commit 4d1aa87a88
24 changed files with 167 additions and 62 deletions

View File

@ -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)
}
}

View File

@ -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 =>
@ -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,
@ -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)

View File

@ -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 {
def genRecordPath(
recordDir: String,
meetingId: String,
@ -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 =>
@ -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,

View File

@ -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 =>
@ -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)
}

View File

@ -779,6 +779,7 @@ class MeetingActor(
val elapsedInMin = TimeUtil.millisToMinutes(elapsedInMs)
if (props.recordProp.record &&
(MeetingStatus2x.isRecording(liveMeeting.status) || props.recordProp.recordFullDurationMedia) &&
recordingChapterBreakLengthInMinutes > 0 &&
elapsedInMin > recordingChapterBreakLengthInMinutes) {
lastRecBreakSentOn = now
@ -786,15 +787,7 @@ class MeetingActor(
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)
}
}
@ -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)
// 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, recordFile)
VoiceApp.startRecordingVoiceConference(liveMeeting, outGW)
}
}
}

View File

@ -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)

View File

@ -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.

View File

@ -22,6 +22,7 @@ trait TestFixtures {
val autoStartRecording = false
val allowStartStopRecording = false
val recordFullDurationMedia = false
val webcamsOnlyForModerator = false
val meetingCameraCap = 0
val userCameraCap = 0
@ -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)

View File

@ -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.");
}

View File

@ -400,6 +400,7 @@ public class MeetingService implements MessageListener {
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(),

View File

@ -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;
@ -503,6 +504,18 @@ public class ParamsProcessorUtil {
}
}
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))) {
@ -735,6 +748,7 @@ public class ParamsProcessorUtil {
.withDefaultAvatarURL(avatarURL)
.withAutoStartRecording(autoStartRec)
.withAllowStartStopRecording(allowStartStoptRec)
.withRecordFullDurationMedia(_recordFullDurationMedia)
.withWebcamsOnlyForModerator(webcamsOnlyForMod)
.withMeetingCameraCap(meetingCameraCap)
.withUserCameraCap(userCameraCap)
@ -1226,6 +1240,10 @@ public class ParamsProcessorUtil {
this.allowStartStopRecording = allowStartStopRecording;
}
public void setRecordFullDurationMedia(boolean recordFullDurationMedia) {
this.recordFullDurationMedia = recordFullDurationMedia;
}
public void setLearningDashboardEnabled(boolean learningDashboardEnabled) {
this.learningDashboardEnabled = learningDashboardEnabled;
}

View File

@ -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;
@ -148,6 +149,7 @@ public class Meeting {
record = builder.record;
autoStartRecording = builder.autoStartRecording;
allowStartStopRecording = builder.allowStartStopRecording;
recordFullDurationMedia = builder.recordFullDurationMedia;
webcamsOnlyForModerator = builder.webcamsOnlyForModerator;
meetingCameraCap = builder.meetingCameraCap;
userCameraCap = builder.userCameraCap;
@ -582,6 +584,10 @@ public class Meeting {
return allowStartStopRecording;
}
public boolean getRecordFullDurationMedia() {
return recordFullDurationMedia;
}
public boolean getWebcamsOnlyForModerator() {
return webcamsOnlyForModerator;
}
@ -862,6 +868,7 @@ public class Meeting {
private int maxUsers;
private boolean record;
private boolean autoStartRecording;
private boolean recordFullDurationMedia;
private boolean allowStartStopRecording;
private boolean webcamsOnlyForModerator;
private Integer meetingCameraCap;
@ -938,6 +945,11 @@ public class Meeting {
return this;
}
public Builder withRecordFullDurationMedia(boolean recordFullDurationMedia) {
this.recordFullDurationMedia = recordFullDurationMedia;
return this;
}
public Builder withWebcamsOnlyForModerator(boolean only) {
this.webcamsOnlyForModerator = only;
return this;

View File

@ -16,6 +16,7 @@ public class CreateMeetingMessage {
public final Long duration;
public boolean autoStartRecording;
public boolean allowStartStopRecording;
public boolean recordFullDurationMedia;
public boolean webcamsOnlyForModerator;
public final Integer meetingCameraCap;
public final Integer userCameraCap;
@ -34,6 +35,7 @@ public class CreateMeetingMessage {
public CreateMeetingMessage(String id, String externalId, String name, Boolean record,
String voiceBridge, Long duration,
Boolean autoStartRecording, Boolean allowStartStopRecording,
Boolean recordFullDurationMedia,
Boolean webcamsOnlyForModerator, Integer meetingCameraCap, Integer userCameraCap, Integer maxPinnedCameras, String moderatorPass,
String viewerPass, String learningDashboardAccessToken,
ArrayList<String> disabledFeatures,
@ -49,6 +51,7 @@ public class CreateMeetingMessage {
this.duration = duration;
this.autoStartRecording = autoStartRecording;
this.allowStartStopRecording = allowStartStopRecording;
this.recordFullDurationMedia = recordFullDurationMedia;
this.webcamsOnlyForModerator = webcamsOnlyForModerator;
this.meetingCameraCap = meetingCameraCap;
this.userCameraCap = userCameraCap;

View File

@ -11,11 +11,14 @@ public interface IPublisherService {
void createMeeting(String meetingID, String externalMeetingID,
String parentMeetingID, String meetingName, Boolean recorded,
String voiceBridge, Integer duration, Boolean autoStartRecording,
Boolean allowStartStopRecording, Boolean webcamsOnlyForModerator,
Boolean allowStartStopRecording,
Boolean recordFullDurationMedia,
Boolean webcamsOnlyForModerator,
Integer userCameraCap,
String moderatorPass, String viewerPass, Long createTime,
String createDate, Boolean isBreakout, Integer sequence,
Boolean freeJoin, Map<String, String> metadata, String guestPolicy);
Boolean freeJoin, Map<String, String> metadata, String guestPolicy
);
void endMeeting(String meetingId);
void send(String channel, String message);
void registerUser(String meetingID, String internalUserId, String fullname, String role, String externUserID,

View File

@ -18,7 +18,9 @@ public interface IBbbWebApiGWApp {
void createMeeting(String meetingID, String externalMeetingID,
String parentMeetingID, String meetingName, Boolean recorded,
String voiceBridge, Integer duration, Boolean autoStartRecording,
Boolean allowStartStopRecording, Boolean webcamsOnlyForModerator,
Boolean allowStartStopRecording,
Boolean recordFullDurationMedia,
Boolean webcamsOnlyForModerator,
Integer meetingCameraCap,
Integer userCameraCap,
Integer maxPinnedCameras,

View File

@ -5,12 +5,16 @@ public class RecordProp2 {
public final boolean record;
public final boolean autoStartRecording;
public final boolean allowStartStopRecording;
public final boolean recordFullDurationMedia;
public RecordProp2(boolean record,
boolean autoStartRecording,
boolean allowStartStopRecording) {
boolean allowStartStopRecording,
boolean recordFullDurationMedia
) {
this.record = record;
this.autoStartRecording = autoStartRecording;
this.allowStartStopRecording = allowStartStopRecording;
this.recordFullDurationMedia = recordFullDurationMedia;
}
}

View File

@ -120,7 +120,9 @@ class BbbWebApiGWApp(
def createMeeting(meetingId: String, extMeetingId: String, parentMeetingId: String, meetingName: String,
recorded: java.lang.Boolean, voiceBridge: String, duration: java.lang.Integer,
autoStartRecording: java.lang.Boolean,
allowStartStopRecording: java.lang.Boolean, webcamsOnlyForModerator: java.lang.Boolean,
allowStartStopRecording: java.lang.Boolean,
recordFullDurationMedia: java.lang.Boolean,
webcamsOnlyForModerator: java.lang.Boolean,
meetingCameraCap: java.lang.Integer,
userCameraCap: java.lang.Integer,
maxPinnedCameras: java.lang.Integer,
@ -182,7 +184,9 @@ class BbbWebApiGWApp(
val password = PasswordProp(moderatorPass = moderatorPass, viewerPass = viewerPass, learningDashboardAccessToken = learningDashboardAccessToken)
val recordProp = RecordProp(record = recorded.booleanValue(), autoStartRecording = autoStartRecording.booleanValue(),
allowStartStopRecording = allowStartStopRecording.booleanValue(), keepEvents = keepEvents.booleanValue())
allowStartStopRecording = allowStartStopRecording.booleanValue(),
recordFullDurationMedia = recordFullDurationMedia.booleanValue(),
keepEvents = keepEvents.booleanValue())
val breakoutProps = BreakoutProps(
parentId = parentMeetingId,

View File

@ -1 +1 @@
git clone --branch v2.9.12 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu
git clone --branch v2.9.13 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu

View File

@ -231,6 +231,11 @@ autoStartRecording=false
# Allow the user to start/stop recording.
allowStartStopRecording=true
# Whether media (audio, cameras and screen sharing) should be captured on their
# full duration if the meeting is recorded (recorded=true). Effectively ignores
# the meeting's current recording state (paused/running).
recordFullDurationMedia=false
# Number of minutes that Learning Dashboard will be available after the end of the meeting
# if 0, the Learning Dashboard will keep available permanently
# this is the default value, can be customized using the create API

View File

@ -158,6 +158,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<property name="disableRecordingDefault" value="${disableRecordingDefault}"/>
<property name="autoStartRecording" value="${autoStartRecording}"/>
<property name="allowStartStopRecording" value="${allowStartStopRecording}"/>
<property name="recordFullDurationMedia" value="${recordFullDurationMedia}"/>
<property name="learningDashboardEnabled" value="${learningDashboardEnabled}"/>
<property name="learningDashboardCleanupDelayInMinutes" value="${learningDashboardCleanupDelayInMinutes}"/>
<property name="webcamsOnlyForModerator" value="${webcamsOnlyForModerator}"/>

View File

@ -915,6 +915,7 @@ class ApiController {
isBreakout meeting.isBreakout()
logoutTimer meeting.getLogoutTimer()
allowStartStopRecording meeting.getAllowStartStopRecording()
recordFullDurationMedia meeting.getRecordFullDurationMedia()
welcome us.welcome
if (!StringUtils.isEmpty(meeting.moderatorOnlyMessage) && us.role.equals(ROLE_MODERATOR)) {
modOnlyMessage meeting.moderatorOnlyMessage

View File

@ -1324,6 +1324,7 @@ These configs can be set in `/etc/bigbluebutton/bbb-web.properties`
| `allowRequestsWithoutSession` | Allow requests without JSESSIONID to be handled | true/false | false |
| `supportedChecksumAlgorithms` | List of supported hash algorithms for validating checksums | sha1, sha256, sha384, sha512 | sha1, sha256, sha384, sha512 |
| `allowRevealOfBBBVersion` | Allow endpoint with current BigBlueButton version | true/false | false |
| `recordFullDurationMedia` | Controls whether media should be captured on their full duration if the meeting's recorded property is true | true/false | false |
- _`overwritable`_: Config will be overwritten if the param is present in the API `/create` request

View File

@ -377,6 +377,13 @@ const createEndpointTableData = [
"required": false,
"type": "String",
"description": (<>Message to be displayed in presentation uploader modal describing how to use an external application to upload presentation files. Only works if <code className="language-plaintext highlighter-rouge">presentationUploadExternalUrl</code> is also set. (added 2.6)</>)
},
{
"name": "recordFullDurationMedia",
"required": false,
"type": "Boolean",
"default": "false",
"description": (<>Controls whether media (audio, cameras and screen sharing) should be captured on their full duration if the meeting's recorded property is true (<code className="language-plaintext highlighter-rouge">recorded=true</code>). Default is false: only captures media while recording is running in the meeting. (added 2.6.9)</>)
}
]

View File

@ -96,7 +96,7 @@ Updated in 2.5:
Updated in 2.6:
- **create** - **Added:** `notifyRecordingIsOn`, `presentationUploadExternalUrl`, `presentationUploadExternalDescription`; Added `liveTranscription` and `presentation` as options for `disabledFeatures=`.
- **create** - **Added:** `notifyRecordingIsOn`, `presentationUploadExternalUrl`, `presentationUploadExternalDescription`, `recordFullDurationMedia` (v2.6.9); Added `liveTranscription` and `presentation` as options for `disabledFeatures=`.
- **getRecordings** - **Added:** Added support for pagination using `offset`, `limit`