Merge pull request #20703 from danielpetri1/webcam-background-url
feat: Accept custom webcamBackgroundURL
This commit is contained in:
commit
5387fef101
@ -57,7 +57,9 @@ trait RegisterUserReqMsgHdlr {
|
||||
|
||||
val regUser = RegisteredUsers.create(msg.body.intUserId, msg.body.extUserId,
|
||||
msg.body.name, msg.body.role, msg.body.authToken,
|
||||
msg.body.avatarURL, ColorPicker.nextColor(liveMeeting.props.meetingProp.intId), msg.body.guest, msg.body.authed, guestStatus, msg.body.excludeFromDashboard, false)
|
||||
msg.body.avatarURL,
|
||||
msg.body.webcamBackgroundURL,
|
||||
ColorPicker.nextColor(liveMeeting.props.meetingProp.intId), msg.body.guest, msg.body.authed, guestStatus, msg.body.excludeFromDashboard, false)
|
||||
|
||||
checkUserConcurrentAccesses(regUser)
|
||||
|
||||
@ -98,7 +100,7 @@ trait RegisterUserReqMsgHdlr {
|
||||
val g = GuestApprovedVO(regUser.id, GuestStatus.ALLOW)
|
||||
UsersApp.approveOrRejectGuest(liveMeeting, outGW, g, SystemUser.ID)
|
||||
case GuestStatus.WAIT =>
|
||||
val guest = GuestWaiting(regUser.id, regUser.name, regUser.role, regUser.guest, regUser.avatarURL, regUser.color, regUser.authed, regUser.registeredOn)
|
||||
val guest = GuestWaiting(regUser.id, regUser.name, regUser.role, regUser.guest, regUser.avatarURL, regUser.webcamBackgroundURL, regUser.color, regUser.authed, regUser.registeredOn)
|
||||
addGuestToWaitingForApproval(guest, liveMeeting.guestsWaiting)
|
||||
notifyModeratorsOfGuestWaiting(Vector(guest), liveMeeting.users2x, liveMeeting.props.meetingProp.intId)
|
||||
val notifyEvent = MsgBuilder.buildNotifyRoleInMeetingEvtMsg(
|
||||
|
@ -34,7 +34,7 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
|
||||
|
||||
def registerUserInRegisteredUsers() = {
|
||||
val regUser = RegisteredUsers.create(msg.body.intId, msg.body.voiceUserId,
|
||||
msg.body.callerIdName, Roles.VIEWER_ROLE, msg.body.intId, "",
|
||||
msg.body.callerIdName, Roles.VIEWER_ROLE, msg.body.intId, "", "",
|
||||
userColor, true, true, GuestStatus.WAIT, true, false)
|
||||
RegisteredUsers.add(liveMeeting.registeredUsers, regUser)
|
||||
}
|
||||
@ -57,6 +57,7 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
|
||||
presenter = false,
|
||||
locked = MeetingStatus2x.getPermissions(liveMeeting.status).lockOnJoin,
|
||||
avatar = "",
|
||||
webcamBackground = "",
|
||||
color = userColor,
|
||||
clientType = "",
|
||||
pickExempted = false,
|
||||
@ -67,7 +68,7 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
|
||||
|
||||
def registerUserAsGuest() = {
|
||||
if (GuestsWaiting.findWithIntId(liveMeeting.guestsWaiting, msg.body.intId) == None) {
|
||||
val guest = GuestWaiting(msg.body.intId, msg.body.callerIdName, Roles.VIEWER_ROLE, true, "", userColor, true, System.currentTimeMillis())
|
||||
val guest = GuestWaiting(msg.body.intId, msg.body.callerIdName, Roles.VIEWER_ROLE, true, "", "", userColor, true, System.currentTimeMillis())
|
||||
GuestsWaiting.add(liveMeeting.guestsWaiting, guest)
|
||||
notifyModeratorsOfGuestWaiting(guest, liveMeeting.users2x, liveMeeting.props.meetingProp.intId)
|
||||
|
||||
|
@ -6,47 +6,4 @@ trait UserJoinedVoiceConfMessageHdlr {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
/*
|
||||
def startRecordingVoiceConference() {
|
||||
if (Users.numUsersInVoiceConference(liveMeeting.users) == 1 &&
|
||||
props.recordProp.record &&
|
||||
!MeetingStatus2x.isVoiceRecording(liveMeeting.status)) {
|
||||
MeetingStatus2x.startRecordingVoice(liveMeeting.status)
|
||||
log.info("Send START RECORDING voice conf. meetingId=" + props.meetingProp.intId + " voice conf=" + props.voiceProp.voiceConf)
|
||||
outGW.send(new StartRecordingVoiceConf(props.meetingProp.intId, props.recordProp.record, props.voiceProp.voiceConf))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
def handleUserJoinedVoiceConfMessage(msg: UserJoinedVoiceConfMessage) = {
|
||||
log.info("Received user joined voice. meetingId=" + props.meetingProp.intId + " callername=" + msg.callerIdName
|
||||
+ " userId=" + msg.userId + " extUserId=" + msg.externUserId)
|
||||
|
||||
Users.findWithId(msg.userId, liveMeeting.users) match {
|
||||
case Some(user) => {
|
||||
// this is used to restore the mute state on reconnect
|
||||
val previouslyMuted = user.voiceUser.muted
|
||||
|
||||
val nu = Users.restoreMuteState(user, liveMeeting.users, msg.voiceUserId, msg.userId, msg.callerIdName,
|
||||
msg.callerIdNum, msg.muted, msg.talking, msg.avatarURL, msg.listenOnly)
|
||||
|
||||
log.info("User joined voice. meetingId=" + props.meetingProp.intId + " userId=" + user.id + " user=" + nu)
|
||||
outGW.send(new UserJoinedVoice(props.meetingProp.intId, props.recordProp.record, props.voiceProp.voiceConf, nu))
|
||||
|
||||
if (MeetingStatus2x.isMeetingMuted(liveMeeting.status) || previouslyMuted) {
|
||||
outGW.send(new MuteVoiceUser(props.meetingProp.intId, props.recordProp.record,
|
||||
nu.id, nu.id, props.voiceProp.voiceConf,
|
||||
nu.voiceUser.userId, true))
|
||||
}
|
||||
|
||||
startRecordingVoiceConference()
|
||||
}
|
||||
case None => {
|
||||
startRecordingVoiceConference()
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ class GuestsWaiting {
|
||||
}
|
||||
}
|
||||
|
||||
case class GuestWaiting(intId: String, name: String, role: String, guest: Boolean, avatar: String, color: String, authenticated: Boolean, registeredOn: Long)
|
||||
case class GuestWaiting(intId: String, name: String, role: String, guest: Boolean, avatar: String, webcamBackground: String, color: String, authenticated: Boolean, registeredOn: Long)
|
||||
case class GuestPolicy(policy: String, setBy: String)
|
||||
|
||||
object GuestPolicyType {
|
||||
|
@ -5,7 +5,7 @@ import org.bigbluebutton.core.domain.BreakoutRoom2x
|
||||
|
||||
object RegisteredUsers {
|
||||
def create(userId: String, extId: String, name: String, roles: String,
|
||||
token: String, avatar: String, color: String, guest: Boolean, authenticated: Boolean,
|
||||
token: String, avatar: String, webcamBackground: String, color: String, guest: Boolean, authenticated: Boolean,
|
||||
guestStatus: String, excludeFromDashboard: Boolean, loggedOut: Boolean): RegisteredUser = {
|
||||
new RegisteredUser(
|
||||
userId,
|
||||
@ -14,6 +14,7 @@ object RegisteredUsers {
|
||||
roles,
|
||||
token,
|
||||
avatar,
|
||||
webcamBackground,
|
||||
color,
|
||||
guest,
|
||||
authenticated,
|
||||
@ -198,6 +199,7 @@ case class RegisteredUser(
|
||||
role: String,
|
||||
authToken: String,
|
||||
avatarURL: String,
|
||||
webcamBackgroundURL: String,
|
||||
color: String,
|
||||
guest: Boolean,
|
||||
authed: Boolean,
|
||||
|
@ -413,6 +413,7 @@ case class UserState(
|
||||
locked: Boolean,
|
||||
presenter: Boolean,
|
||||
avatar: String,
|
||||
webcamBackground: String,
|
||||
color: String,
|
||||
roleChangedOn: Long = System.currentTimeMillis(),
|
||||
lastActivityTime: Long = System.currentTimeMillis(),
|
||||
|
@ -70,6 +70,7 @@ trait HandlerHelpers extends SystemConfiguration {
|
||||
presenter = false,
|
||||
locked = MeetingStatus2x.getPermissions(liveMeeting.status).lockOnJoin,
|
||||
avatar = regUser.avatarURL,
|
||||
webcamBackground = regUser.webcamBackgroundURL,
|
||||
color = regUser.color,
|
||||
clientType = clientType,
|
||||
pickExempted = false,
|
||||
|
@ -16,7 +16,7 @@ object UserJoinedMeetingEvtMsgBuilder {
|
||||
raiseHand = userState.raiseHand,
|
||||
away = userState.away,
|
||||
pin = userState.pin,
|
||||
presenter = userState.presenter, locked = userState.locked, avatar = userState.avatar, color = userState.color,
|
||||
presenter = userState.presenter, locked = userState.locked, avatar = userState.avatar, webcamBackground = userState.webcamBackground, color = userState.color,
|
||||
clientType = userState.clientType, userCustomData = userState.userCustomData)
|
||||
|
||||
val event = UserJoinedMeetingEvtMsg(meetingId, userState.intId, body)
|
||||
|
@ -20,13 +20,13 @@ trait FakeTestData {
|
||||
val guest1 = createUserVoiceAndCam(liveMeeting, Roles.VIEWER_ROLE, guest = true, authed = true, CallingWith.WEBRTC, muted = false,
|
||||
talking = false, listenOnly = false)
|
||||
Users2x.add(liveMeeting.users2x, guest1)
|
||||
val guestWait1 = GuestWaiting(guest1.intId, guest1.name, guest1.role, guest1.guest, "", "#ff6242", guest1.authed, System.currentTimeMillis())
|
||||
val guestWait1 = GuestWaiting(guest1.intId, guest1.name, guest1.role, guest1.guest, "", "", "#ff6242", guest1.authed, System.currentTimeMillis())
|
||||
GuestsWaiting.add(liveMeeting.guestsWaiting, guestWait1)
|
||||
|
||||
val guest2 = createUserVoiceAndCam(liveMeeting, Roles.VIEWER_ROLE, guest = true, authed = true, CallingWith.FLASH, muted = false,
|
||||
talking = false, listenOnly = false)
|
||||
Users2x.add(liveMeeting.users2x, guest2)
|
||||
val guestWait2 = GuestWaiting(guest2.intId, guest2.name, guest2.role, guest2.guest, "", "#ff6242", guest2.authed, System.currentTimeMillis())
|
||||
val guestWait2 = GuestWaiting(guest2.intId, guest2.name, guest2.role, guest2.guest, "", "", "#ff6242", guest2.authed, System.currentTimeMillis())
|
||||
GuestsWaiting.add(liveMeeting.guestsWaiting, guestWait2)
|
||||
|
||||
val vu1 = FakeUserGenerator.createFakeVoiceOnlyUser(CallingWith.PHONE, muted = false, talking = false, listenOnly = false)
|
||||
@ -69,7 +69,8 @@ trait FakeTestData {
|
||||
UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role, pin = false,
|
||||
mobile = false, guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
|
||||
emoji = "none", reactionEmoji = "none", raiseHand = false, away = false, locked = false, presenter = false,
|
||||
avatar = regUser.avatarURL, color = "#ff6242", clientType = "unknown",
|
||||
avatar = regUser.avatarURL, webcamBackground = regUser.webcamBackgroundURL,
|
||||
color = "#ff6242", clientType = "unknown",
|
||||
pickExempted = false, userLeftFlag = UserLeftFlag(false, 0))
|
||||
}
|
||||
|
||||
|
@ -52,10 +52,12 @@ object FakeUserGenerator {
|
||||
val authToken = RandomStringGenerator.randomAlphanumericString(16)
|
||||
val avatarURL = "https://www." + RandomStringGenerator.randomAlphanumericString(32) + ".com/" +
|
||||
RandomStringGenerator.randomAlphanumericString(10) + ".png"
|
||||
val webcamBackgroundURL = "https://www." + RandomStringGenerator.randomAlphanumericString(32) + ".com/" +
|
||||
RandomStringGenerator.randomAlphanumericString(10) + ".jpg"
|
||||
val color = "#ff6242"
|
||||
|
||||
val ru = RegisteredUsers.create(userId = id, extId, name, role,
|
||||
authToken, avatarURL, color, guest, authed, guestStatus = GuestStatus.ALLOW, false, false)
|
||||
authToken, avatarURL, webcamBackgroundURL, color, guest, authed, guestStatus = GuestStatus.ALLOW, false, false)
|
||||
RegisteredUsers.add(users, ru)
|
||||
ru
|
||||
}
|
||||
|
@ -12,9 +12,11 @@ object TestDataGen {
|
||||
val authToken = RandomStringGenerator.randomAlphanumericString(16)
|
||||
val avatarURL = "https://www." + RandomStringGenerator.randomAlphanumericString(32) + ".com/" +
|
||||
RandomStringGenerator.randomAlphanumericString(10) + ".png"
|
||||
val webcamBackgroundURL = "https://www." + RandomStringGenerator.randomAlphanumericString(32) + ".com/" +
|
||||
RandomStringGenerator.randomAlphanumericString(10) + ".jpg"
|
||||
|
||||
val ru = RegisteredUsers.create(userId = id, extId, name, role,
|
||||
authToken, avatarURL, guest, authed, GuestStatus.ALLOW, false)
|
||||
authToken, avatarURL, webcamBackgroundURL, guest, authed, GuestStatus.ALLOW, false)
|
||||
|
||||
RegisteredUsers.add(users, ru)
|
||||
ru
|
||||
@ -48,7 +50,7 @@ object TestDataGen {
|
||||
val u = UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role,
|
||||
guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
|
||||
emoji = "none", reactionEmoji = "none", raiseHand = false, away = false, pin = false, mobile = false,
|
||||
locked = false, presenter = false, avatar = regUser.avatarURL, color = "#ff6242",
|
||||
locked = false, presenter = false, avatar = regUser.avatarURL, regUser.webcamBackgroundURL, color = "#ff6242",
|
||||
clientType = "unknown", pickExempted = false, userLeftFlag = UserLeftFlag(false, 0))
|
||||
Users2x.add(liveMeeting.users2x, u)
|
||||
u
|
||||
|
@ -115,8 +115,8 @@ case class UserVO(id: String, externalId: String, name: String, role: String,
|
||||
guest: Boolean, authed: Boolean, guestStatus: String, emojiStatus: String,
|
||||
presenter: Boolean, hasStream: Boolean, locked: Boolean, webcamStreams: Set[String],
|
||||
phoneUser: Boolean, voiceUser: VoiceUserVO, listenOnly: Boolean, avatarURL: String,
|
||||
joinedWeb: Boolean)
|
||||
webcamBackgroundURL: String, joinedWeb: Boolean)
|
||||
|
||||
case class VoiceUserVO(userId: String, webUserId: String, callerName: String,
|
||||
callerNum: String, joined: Boolean, locked: Boolean, muted: Boolean,
|
||||
talking: Boolean, avatarURL: String, listenOnly: Boolean)
|
||||
talking: Boolean, avatarURL: String, webcamBackgroundURL: String, listenOnly: Boolean)
|
||||
|
@ -6,7 +6,7 @@ case class RegisterUserReqMsg(
|
||||
body: RegisterUserReqMsgBody
|
||||
) extends BbbCoreMsg
|
||||
case class RegisterUserReqMsgBody(meetingId: String, intUserId: String, name: String, role: String,
|
||||
extUserId: String, authToken: String, avatarURL: String,
|
||||
extUserId: String, authToken: String, avatarURL: String, webcamBackgroundURL: String,
|
||||
guest: Boolean, authed: Boolean, guestStatus: String, excludeFromDashboard: Boolean,
|
||||
userCustomData: Map[String, AnyRef])
|
||||
|
||||
@ -90,24 +90,25 @@ case class UserJoinedMeetingEvtMsg(
|
||||
body: UserJoinedMeetingEvtMsgBody
|
||||
) extends BbbCoreMsg
|
||||
case class UserJoinedMeetingEvtMsgBody(
|
||||
intId: String,
|
||||
extId: String,
|
||||
name: String,
|
||||
role: String,
|
||||
guest: Boolean,
|
||||
authed: Boolean,
|
||||
guestStatus: String,
|
||||
emoji: String,
|
||||
reactionEmoji: String,
|
||||
raiseHand: Boolean,
|
||||
away: Boolean,
|
||||
pin: Boolean,
|
||||
presenter: Boolean,
|
||||
locked: Boolean,
|
||||
avatar: String,
|
||||
color: String,
|
||||
clientType: String,
|
||||
userCustomData: Map[String, String]
|
||||
intId: String,
|
||||
extId: String,
|
||||
name: String,
|
||||
role: String,
|
||||
guest: Boolean,
|
||||
authed: Boolean,
|
||||
guestStatus: String,
|
||||
emoji: String,
|
||||
reactionEmoji: String,
|
||||
raiseHand: Boolean,
|
||||
away: Boolean,
|
||||
pin: Boolean,
|
||||
presenter: Boolean,
|
||||
locked: Boolean,
|
||||
avatar: String,
|
||||
webcamBackground: String,
|
||||
color: String,
|
||||
clientType: String,
|
||||
userCustomData: Map[String, String]
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -126,10 +126,10 @@ public class MeetingService implements MessageListener {
|
||||
|
||||
public void registerUser(String meetingID, String internalUserId,
|
||||
String fullname, String role, String externUserID,
|
||||
String authToken, String avatarURL, Boolean guest,
|
||||
String authToken, String avatarURL, String webcamBackgroundURL, Boolean guest,
|
||||
Boolean authed, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby) {
|
||||
handle(new RegisterUser(meetingID, internalUserId, fullname, role,
|
||||
externUserID, authToken, avatarURL, guest, authed, guestStatus, excludeFromDashboard, leftGuestLobby));
|
||||
externUserID, authToken, avatarURL, webcamBackgroundURL, guest, authed, guestStatus, excludeFromDashboard, leftGuestLobby));
|
||||
|
||||
Meeting m = getMeeting(meetingID);
|
||||
if (m != null) {
|
||||
@ -437,7 +437,7 @@ public class MeetingService implements MessageListener {
|
||||
|
||||
gw.registerUser(message.meetingID,
|
||||
message.internalUserId, message.fullname, message.role,
|
||||
message.externUserID, message.authToken, message.avatarURL, message.guest,
|
||||
message.externUserID, message.authToken, message.avatarURL, message.webcamBackgroundURL, message.guest,
|
||||
message.authed, message.guestStatus, message.excludeFromDashboard, userCustomData);
|
||||
}
|
||||
|
||||
@ -933,7 +933,7 @@ public class MeetingService implements MessageListener {
|
||||
}
|
||||
|
||||
User user = new User(message.userId, message.externalUserId,
|
||||
message.name, message.role, message.avatarURL, message.guest, message.guestStatus,
|
||||
message.name, message.role, message.avatarURL, message.webcamBackgroundURL, message.guest, message.guestStatus,
|
||||
message.clientType);
|
||||
|
||||
if(m.getMaxUsers() > 0 && m.countUniqueExtIds() >= m.getMaxUsers()) {
|
||||
@ -1053,8 +1053,8 @@ public class MeetingService implements MessageListener {
|
||||
} else {
|
||||
if (message.userId.startsWith("v_")) {
|
||||
// A dial-in user joined the meeting. Dial-in users by convention has userId that starts with "v_".
|
||||
User vuser = new User(message.userId, message.userId, message.name, "DIAL-IN-USER", "",
|
||||
true, GuestPolicy.ALLOW, "DIAL-IN");
|
||||
User vuser = new User(message.userId, message.userId, message.name, "DIAL-IN-USER", "", "",
|
||||
true, GuestPolicy.ALLOW, "DIAL-IN");
|
||||
vuser.setVoiceJoined(true);
|
||||
m.userJoined(vuser);
|
||||
}
|
||||
|
@ -84,6 +84,8 @@ public class ParamsProcessorUtil {
|
||||
private Integer defaultHttpSessionTimeout = 14400;
|
||||
private Boolean useDefaultAvatar = false;
|
||||
private String defaultAvatarURL;
|
||||
private Boolean useDefaultWebcamBackground = false;
|
||||
private String defaultWebcamBackgroundURL;
|
||||
private String defaultGuestPolicy;
|
||||
private Boolean authenticatedGuest;
|
||||
private Boolean defaultAllowPromoteGuestToModerator;
|
||||
@ -741,6 +743,7 @@ public class ParamsProcessorUtil {
|
||||
}
|
||||
|
||||
String avatarURL = useDefaultAvatar ? defaultAvatarURL : "";
|
||||
String webcamBackgroundURL = useDefaultWebcamBackground ? defaultWebcamBackgroundURL : "";
|
||||
|
||||
int html5InstanceId = processHtml5InstanceId(params.get(ApiParams.HTML5_INSTANCE_ID));
|
||||
|
||||
@ -760,6 +763,7 @@ public class ParamsProcessorUtil {
|
||||
.withTelVoice(telVoice).withWebVoice(webVoice)
|
||||
.withDialNumber(dialNumber)
|
||||
.withDefaultAvatarURL(avatarURL)
|
||||
.withDefaultWebcamBackgroundURL(webcamBackgroundURL)
|
||||
.withAutoStartRecording(autoStartRec)
|
||||
.withAllowStartStopRecording(allowStartStoptRec)
|
||||
.withRecordFullDurationMedia(_recordFullDurationMedia)
|
||||
@ -1296,6 +1300,14 @@ public class ParamsProcessorUtil {
|
||||
this.defaultAvatarURL = url;
|
||||
}
|
||||
|
||||
public void setUseDefaultWebcamBackground(Boolean value) {
|
||||
this.useDefaultWebcamBackground = value;
|
||||
}
|
||||
|
||||
public void setDefaultWebcamBackgroundURL(String uri) {
|
||||
this.defaultWebcamBackgroundURL = uri;
|
||||
}
|
||||
|
||||
public void setDefaultGuestPolicy(String guestPolicy) {
|
||||
this.defaultGuestPolicy = guestPolicy;
|
||||
}
|
||||
|
@ -79,6 +79,7 @@ public class Meeting {
|
||||
private Integer maxPinnedCameras = 0;
|
||||
private String dialNumber;
|
||||
private String defaultAvatarURL;
|
||||
private String defaultWebcamBackgroundURL;
|
||||
private String guestPolicy = GuestPolicy.ASK_MODERATOR;
|
||||
private String guestLobbyMessage = "";
|
||||
private Map<String,String> usersWithGuestLobbyMessages;
|
||||
@ -148,6 +149,7 @@ public class Meeting {
|
||||
logoutUrl = builder.logoutUrl;
|
||||
logoutTimer = builder.logoutTimer;
|
||||
defaultAvatarURL = builder.defaultAvatarURL;
|
||||
defaultWebcamBackgroundURL = builder.defaultWebcamBackgroundURL;
|
||||
record = builder.record;
|
||||
autoStartRecording = builder.autoStartRecording;
|
||||
allowStartStopRecording = builder.allowStartStopRecording;
|
||||
@ -462,6 +464,10 @@ public class Meeting {
|
||||
return defaultAvatarURL;
|
||||
}
|
||||
|
||||
public String getDefaultWebcamBackgroundURL() {
|
||||
return defaultWebcamBackgroundURL;
|
||||
}
|
||||
|
||||
public void setWaitingPositionsInWaitingQueue(HashMap<String, String> guestUsersWithPositionInWaitingLine) {
|
||||
this.guestUsersWithPositionInWaitingLine = guestUsersWithPositionInWaitingLine;
|
||||
}
|
||||
@ -909,6 +915,7 @@ public class Meeting {
|
||||
private Map<String, String> metadata;
|
||||
private String dialNumber;
|
||||
private String defaultAvatarURL;
|
||||
private String defaultWebcamBackgroundURL;
|
||||
private long createdTime;
|
||||
private boolean isBreakout;
|
||||
private String guestPolicy;
|
||||
@ -1056,6 +1063,11 @@ public class Meeting {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withDefaultWebcamBackgroundURL(String w) {
|
||||
defaultWebcamBackgroundURL = w;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder isBreakout(Boolean b) {
|
||||
isBreakout = b;
|
||||
return this;
|
||||
|
@ -31,6 +31,7 @@ public class User {
|
||||
private String fullname;
|
||||
private String role;
|
||||
private String avatarURL;
|
||||
private String webcamBackgroundURL;
|
||||
private Map<String,String> status;
|
||||
private Boolean guest;
|
||||
private String guestStatus;
|
||||
@ -45,6 +46,7 @@ public class User {
|
||||
String fullname,
|
||||
String role,
|
||||
String avatarURL,
|
||||
String webcamBackgroundURL,
|
||||
Boolean guest,
|
||||
String guestStatus,
|
||||
String clientType) {
|
||||
@ -53,6 +55,7 @@ public class User {
|
||||
this.fullname = fullname;
|
||||
this.role = role;
|
||||
this.avatarURL = avatarURL;
|
||||
this.webcamBackgroundURL = webcamBackgroundURL;
|
||||
this.guest = guest;
|
||||
this.guestStatus = guestStatus;
|
||||
this.status = new ConcurrentHashMap<>();
|
||||
@ -128,6 +131,14 @@ public class User {
|
||||
this.avatarURL = avatarURL;
|
||||
}
|
||||
|
||||
public String getWebcamBackgroundUrl() {
|
||||
return webcamBackgroundURL;
|
||||
}
|
||||
|
||||
public void setWebcamBackgroundUrl(String webcamBackgroundURL) {
|
||||
this.webcamBackgroundURL = webcamBackgroundURL;
|
||||
}
|
||||
|
||||
public boolean isModerator() {
|
||||
return "MODERATOR".equalsIgnoreCase(this.role);
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ public class UserSession {
|
||||
public String logoutUrl = null;
|
||||
public String defaultLayout = "NOLAYOUT";
|
||||
public String avatarURL;
|
||||
public String webcamBackgroundURL;
|
||||
public String guestStatus = GuestPolicy.ALLOW;
|
||||
public String clientUrl = null;
|
||||
public Boolean excludeFromDashboard = false;
|
||||
@ -134,6 +135,10 @@ public class UserSession {
|
||||
return avatarURL;
|
||||
}
|
||||
|
||||
public String getWebcamBackgroundURL() {
|
||||
return webcamBackgroundURL;
|
||||
}
|
||||
|
||||
public String getGuestStatus() {
|
||||
return guestStatus;
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ public class RegisterUser implements IMessage {
|
||||
public final String externUserID;
|
||||
public final String authToken;
|
||||
public final String avatarURL;
|
||||
public final String webcamBackgroundURL;
|
||||
public final Boolean guest;
|
||||
public final Boolean authed;
|
||||
public final String guestStatus;
|
||||
@ -17,7 +18,7 @@ public class RegisterUser implements IMessage {
|
||||
public final Boolean leftGuestLobby;
|
||||
|
||||
public RegisterUser(String meetingID, String internalUserId, String fullname, String role, String externUserID,
|
||||
String authToken, String avatarURL, Boolean guest,
|
||||
String authToken, String avatarURL, String webcamBackgroundURL, Boolean guest,
|
||||
Boolean authed, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby) {
|
||||
this.meetingID = meetingID;
|
||||
this.internalUserId = internalUserId;
|
||||
@ -25,7 +26,8 @@ public class RegisterUser implements IMessage {
|
||||
this.role = role;
|
||||
this.externUserID = externUserID;
|
||||
this.authToken = authToken;
|
||||
this.avatarURL = avatarURL;
|
||||
this.avatarURL = avatarURL;
|
||||
this.webcamBackgroundURL = webcamBackgroundURL;
|
||||
this.guest = guest;
|
||||
this.authed = authed;
|
||||
this.guestStatus = guestStatus;
|
||||
|
@ -7,6 +7,7 @@ public class UserJoined implements IMessage {
|
||||
public final String name;
|
||||
public final String role;
|
||||
public final String avatarURL;
|
||||
public final String webcamBackgroundURL;
|
||||
public final Boolean guest;
|
||||
public final String guestStatus;
|
||||
public final String clientType;
|
||||
@ -18,6 +19,7 @@ public class UserJoined implements IMessage {
|
||||
String name,
|
||||
String role,
|
||||
String avatarURL,
|
||||
String webcamBackgroundURL,
|
||||
Boolean guest,
|
||||
String guestStatus,
|
||||
String clientType) {
|
||||
@ -27,6 +29,7 @@ public class UserJoined implements IMessage {
|
||||
this.name = name;
|
||||
this.role = role;
|
||||
this.avatarURL = avatarURL;
|
||||
this.webcamBackgroundURL = webcamBackgroundURL;
|
||||
this.guest = guest;
|
||||
this.guestStatus = guestStatus;
|
||||
this.clientType = clientType;
|
||||
|
@ -22,7 +22,7 @@ public interface IPublisherService {
|
||||
void endMeeting(String meetingId);
|
||||
void send(String channel, String message);
|
||||
void registerUser(String meetingID, String internalUserId, String fullname, String role, String externUserID,
|
||||
String authToken, String avatarURL, Boolean guest, Boolean excludeFromDashboard, Boolean authed);
|
||||
String authToken, String avatarURL, String webcamBackgroundURL, Boolean guest, Boolean excludeFromDashboard, Boolean authed);
|
||||
void sendKeepAlive(String system, Long bbbWebTimestamp, Long akkaAppsTimestamp);
|
||||
void sendStunTurnInfo(String meetingId, String internalUserId, Set<StunServer> stuns, Set<TurnEntry> turns);
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ public interface IBbbWebApiGWApp {
|
||||
String presentationUploadExternalUrl);
|
||||
|
||||
void registerUser(String meetingID, String internalUserId, String fullname, String role,
|
||||
String externUserID, String authToken, String avatarURL,
|
||||
String externUserID, String authToken, String avatarURL, String webcamBackgroundURL,
|
||||
Boolean guest, Boolean authed, String guestStatus, Boolean excludeFromDashboard, Map<String, Object> userCustomData);
|
||||
void guestWaitingLeft(String meetingID, String internalUserId);
|
||||
|
||||
|
@ -15,7 +15,7 @@ public interface IMeetingService {
|
||||
void addUserSession(String token, UserSession user);
|
||||
void registerUser(String meetingID, String internalUserId,
|
||||
String fullname, String role, String externUserID,
|
||||
String authToken, String avatarURL, Boolean guest, Boolean authed);
|
||||
String authToken, String avatarURL, String webcamBackgroundURL, Boolean guest, Boolean authed);
|
||||
UserSession getUserSession(String token);
|
||||
UserSession removeUserSession(String token);
|
||||
void purgeRegisteredUsers();
|
||||
|
@ -17,6 +17,7 @@ public class Meeting2 {
|
||||
public final boolean forciblyEnded;
|
||||
public final String logoutUrl;
|
||||
public final String defaultAvatarURL;
|
||||
public final String defaultWebcamBackgroundURL;
|
||||
public final Map<String, String> metadata;
|
||||
public final List<String> breakoutRooms;
|
||||
|
||||
@ -30,6 +31,7 @@ public class Meeting2 {
|
||||
boolean forciblyEnded,
|
||||
String logoutUrl,
|
||||
String defaultAvatarURL,
|
||||
String defaultWebcamBackgroundURL,
|
||||
Map<String, String> metadata,
|
||||
List<String> breakoutRooms) {
|
||||
this.props = props;
|
||||
@ -42,6 +44,7 @@ public class Meeting2 {
|
||||
this.forciblyEnded = forciblyEnded;
|
||||
this.logoutUrl = logoutUrl;
|
||||
this.defaultAvatarURL = defaultAvatarURL;
|
||||
this.defaultWebcamBackgroundURL = defaultWebcamBackgroundURL;
|
||||
this.metadata = metadata;
|
||||
this.breakoutRooms = breakoutRooms;
|
||||
}
|
||||
|
@ -253,27 +253,23 @@ class BbbWebApiGWApp(
|
||||
groupsAsVector
|
||||
)
|
||||
|
||||
//meetingManagerActorRef ! new CreateMeetingMsg(defaultProps)
|
||||
|
||||
val event = MsgBuilder.buildCreateMeetingRequestToAkkaApps(defaultProps)
|
||||
msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event))
|
||||
|
||||
}
|
||||
|
||||
def registerUser(meetingId: String, intUserId: String, name: String,
|
||||
role: String, extUserId: String, authToken: String, avatarURL: String,
|
||||
role: String, extUserId: String, authToken: String, avatarURL: String,
|
||||
webcamBackgroundURL: String,
|
||||
guest: java.lang.Boolean, authed: java.lang.Boolean,
|
||||
guestStatus: String, excludeFromDashboard: java.lang.Boolean, userCustomData: java.util.Map[String, AnyRef]): Unit = {
|
||||
|
||||
// meetingManagerActorRef ! new RegisterUser(meetingId = meetingId, intUserId = intUserId, name = name,
|
||||
// role = role, extUserId = extUserId, authToken = authToken, avatarURL = avatarURL,
|
||||
// guest = guest, authed = authed)
|
||||
|
||||
val ucd = userCustomData.asScala.toMap
|
||||
|
||||
val regUser = new RegisterUser(meetingId = meetingId, intUserId = intUserId, name = name,
|
||||
role = role, extUserId = extUserId, authToken = authToken, avatarURL = avatarURL,
|
||||
guest = guest.booleanValue(), authed = authed.booleanValue(), guestStatus = guestStatus,
|
||||
webcamBackgroundURL = webcamBackgroundURL, guest = guest.booleanValue(),
|
||||
authed = authed.booleanValue(), guestStatus = guestStatus,
|
||||
excludeFromDashboard = excludeFromDashboard, ucd)
|
||||
|
||||
val event = MsgBuilder.buildRegisterUserRequestToAkkaApps(regUser)
|
||||
|
@ -40,7 +40,8 @@ object MsgBuilder {
|
||||
val header = BbbCoreHeaderWithMeetingId(RegisterUserReqMsg.NAME, msg.meetingId)
|
||||
val body = RegisterUserReqMsgBody(meetingId = msg.meetingId, intUserId = msg.intUserId,
|
||||
name = msg.name, role = msg.role, extUserId = msg.extUserId, authToken = msg.authToken,
|
||||
avatarURL = msg.avatarURL, guest = msg.guest, authed = msg.authed, guestStatus = msg.guestStatus,
|
||||
avatarURL = msg.avatarURL, webcamBackgroundURL = msg.webcamBackgroundURL,
|
||||
guest = msg.guest, authed = msg.authed, guestStatus = msg.guestStatus,
|
||||
excludeFromDashboard = msg.excludeFromDashboard, msg.userCustomData)
|
||||
val req = RegisterUserReqMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, req)
|
||||
|
@ -4,7 +4,8 @@ case class CallerId(name: String, number: String)
|
||||
case class VoiceUser(id: String, callerId: CallerId, status: String, vid: String, wid: String, callingWith: String)
|
||||
|
||||
case class User2(intId: String, extId: String, name: String, role: String, avatarURL: String,
|
||||
guest: Boolean, waitingForAcceptance: Boolean, status: Vector[String],
|
||||
webcamBackgroundURL: String,
|
||||
guest: Boolean, waitingForAcceptance: Boolean, status: Vector[String],
|
||||
streams: Set[String], customData: UserCustomData, voiceUser: VoiceUser, webcamStreams: Vector[String])
|
||||
|
||||
object Users {
|
||||
@ -40,7 +41,8 @@ class Users {
|
||||
|
||||
case class RegisteredUser2(meetingId: String, intId: String, name: String, role: String,
|
||||
extId: String, authToken: String, avatarURL: String,
|
||||
guest: Boolean, authed: Boolean)
|
||||
webcamBackgroundURL: String,
|
||||
guest: Boolean, authed: Boolean)
|
||||
|
||||
object RegisteredUsers {
|
||||
def findWithId(users: RegisteredUsers, id: String): Option[RegisteredUser2] = {
|
||||
|
@ -16,7 +16,7 @@ case class CreateBreakoutRoomMsg(meetingId: String, parentMeetingId: String,
|
||||
|
||||
case class AddUserSession(token: String, session: UserSession)
|
||||
case class RegisterUser(meetingId: String, intUserId: String, name: String, role: String,
|
||||
extUserId: String, authToken: String, avatarURL: String,
|
||||
extUserId: String, authToken: String, avatarURL: String, webcamBackgroundURL: String,
|
||||
guest: Boolean, authed: Boolean, guestStatus: String, excludeFromDashboard: Boolean, userCustomData: Map[String, AnyRef])
|
||||
|
||||
case class CreateMeetingMsg(defaultProps: DefaultProps)
|
||||
|
@ -122,10 +122,9 @@ class OldMeetingMsgHdlrActor(val olgMsgGW: OldMessageReceivedGW)
|
||||
|
||||
def handleUserJoinedMeetingEvtMsg(msg: UserJoinedMeetingEvtMsg): Unit = {
|
||||
olgMsgGW.handle(new UserJoined(msg.header.meetingId, msg.body.intId,
|
||||
msg.body.extId, msg.body.name, msg.body.role, msg.body.avatar, msg.body.guest,
|
||||
msg.body.extId, msg.body.name, msg.body.role, msg.body.avatar, msg.body.webcamBackground, msg.body.guest,
|
||||
msg.body.guestStatus,
|
||||
msg.body.clientType))
|
||||
|
||||
}
|
||||
|
||||
def handlePresenterUnassignedEvtMsg(msg: PresenterUnassignedEvtMsg): Unit = {
|
||||
|
@ -28,7 +28,8 @@ trait ToAkkaAppsSendersTrait extends SystemConfiguration {
|
||||
val header = BbbCoreHeaderWithMeetingId(RegisterUserReqMsg.NAME, msg.meetingId)
|
||||
val body = RegisterUserReqMsgBody(meetingId = msg.meetingId, intUserId = msg.intUserId,
|
||||
name = msg.name, role = msg.role, extUserId = msg.extUserId, authToken = msg.authToken,
|
||||
avatarURL = msg.avatarURL, guest = msg.guest, authed = msg.authed, guestStatus = msg.guestStatus,
|
||||
avatarURL = msg.avatarURL, webcamBackgroundURL = msg.webcamBackgroundURL, guest = msg.guest,
|
||||
authed = msg.authed, guestStatus = msg.guestStatus,
|
||||
excludeFromDashboard = msg.excludeFromDashboard, msg.userCustomData)
|
||||
val req = RegisterUserReqMsg(header, body)
|
||||
val message = BbbCommonEnvCoreMsg(envelope, req)
|
||||
|
@ -5,7 +5,7 @@ case class UserSession2(authToken: String, internalUserId: String, conferencenam
|
||||
role: String, conference: String, room: String, guest: Boolean = false,
|
||||
authed: Boolean = false, voicebridge: String, webvoiceconf: String,
|
||||
mode: String, record: String, welcome: String, logoutUrl: String,
|
||||
defaultLayout: String = "NOLAYOUT", avatarURL: String)
|
||||
defaultLayout: String = "NOLAYOUT", avatarURL: String, webcamBackgroundURL: String)
|
||||
|
||||
class UserSessions {
|
||||
|
||||
|
@ -29,6 +29,7 @@ export default async function addUserPersistentData(user) {
|
||||
presenter: Boolean,
|
||||
locked: Boolean,
|
||||
avatar: String,
|
||||
webcamBackground: String,
|
||||
clientType: String,
|
||||
left: Boolean,
|
||||
effectiveConnectionType: null,
|
||||
@ -42,6 +43,7 @@ export default async function addUserPersistentData(user) {
|
||||
role,
|
||||
token,
|
||||
avatar,
|
||||
webcamBackground,
|
||||
guest,
|
||||
color,
|
||||
pin,
|
||||
@ -55,6 +57,7 @@ export default async function addUserPersistentData(user) {
|
||||
role,
|
||||
token,
|
||||
avatar,
|
||||
webcamBackground,
|
||||
guest,
|
||||
color,
|
||||
pin,
|
||||
|
@ -30,6 +30,7 @@ export default async function addUser(meetingId, userData) {
|
||||
presenter: Boolean,
|
||||
locked: Boolean,
|
||||
avatar: String,
|
||||
webcamBackground: String,
|
||||
color: String,
|
||||
pin: Boolean,
|
||||
clientType: String,
|
||||
|
@ -1,3 +1,5 @@
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import Users from '/imports/api/users';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
@ -325,6 +327,21 @@ class VideoPreview extends Component {
|
||||
viewState: VIEW_STATES.found,
|
||||
});
|
||||
this.displayPreview();
|
||||
|
||||
// Set the custom or default virtual background
|
||||
const webcamBackground = Users.findOne({
|
||||
meetingId: Auth.meetingID,
|
||||
userId: Auth.userID,
|
||||
}, {
|
||||
fields: {
|
||||
webcamBackground: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const webcamBackgroundURL = webcamBackground?.webcamBackground;
|
||||
if (webcamBackgroundURL !== '') {
|
||||
this.handleVirtualBgSelected(EFFECT_TYPES.IMAGE_TYPE, '', { url: webcamBackgroundURL });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// There were no webcams coming from enumerateDevices. Throw an error.
|
||||
@ -428,7 +445,6 @@ class VideoPreview extends Component {
|
||||
|
||||
// Resolves into true if the background switch is successful, false otherwise
|
||||
handleVirtualBgSelected(type, name, customParams) {
|
||||
const { sharedDevices } = this.props;
|
||||
const { webcamDeviceId } = this.state;
|
||||
const shared = this.isAlreadyShared(webcamDeviceId);
|
||||
|
||||
|
@ -15,7 +15,6 @@ import {
|
||||
getVirtualBgImagePath,
|
||||
} from '/imports/ui/services/virtual-background/service'
|
||||
import logger from '/imports/startup/client/logger';
|
||||
|
||||
import { simd } from 'wasm-feature-detect/dist/cjs/index';
|
||||
|
||||
const blurValue = '25px';
|
||||
@ -387,10 +386,44 @@ export async function createVirtualBackgroundService(parameters = null) {
|
||||
parameters.backgroundType = 'blur';
|
||||
parameters.isVirtualBackground = false;
|
||||
} else {
|
||||
parameters.virtualSource = virtualBackgroundImagePath + parameters.backgroundFilename;
|
||||
|
||||
if (parameters.customParams) {
|
||||
parameters.virtualSource = parameters.customParams.file;
|
||||
} else {
|
||||
parameters.virtualSource = virtualBackgroundImagePath + parameters.backgroundFilename;
|
||||
if (parameters.customParams.file) {
|
||||
parameters.virtualSource = parameters.customParams.file;
|
||||
} else {
|
||||
const imageUrl = parameters.customParams.url;
|
||||
|
||||
// Function to convert image URL to a File object
|
||||
async function getFileFromUrl(url) {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
credentials: 'omit',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Accept': 'image/*',
|
||||
}
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const blob = await response.blob();
|
||||
const file = new File([blob], 'fetchedWebcamBackground', { type: blob.type });
|
||||
return file;
|
||||
} catch (error) {
|
||||
logger.error('Fetch error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let fetchedWebcamBackground = await getFileFromUrl(imageUrl);
|
||||
|
||||
if (fetchedWebcamBackground) {
|
||||
parameters.virtualSource = URL.createObjectURL(fetchedWebcamBackground);
|
||||
} else {
|
||||
logger.error('Failed to fetch custom webcam background image. Using fallback image.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -321,6 +321,10 @@ defaultGuestWaitURL=${bigbluebutton.web.serverURL}/html5client/guestWait
|
||||
useDefaultAvatar=false
|
||||
defaultAvatarURL=${bigbluebutton.web.serverURL}/html5client/resources/images/avatar.png
|
||||
|
||||
# The default webcam background image to display.
|
||||
useDefaultWebcamBackground=false
|
||||
defaultWebcamBackgroundURL=${bigbluebutton.web.serverURL}/html5client/resources/images/virtual-backgrounds/board.jpg
|
||||
|
||||
apiVersion=2.0
|
||||
|
||||
# Salt which is used by 3rd-party apps to authenticate api calls
|
||||
|
@ -163,6 +163,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<property name="defaultMaxPinnedCameras" value="${maxPinnedCameras}"/>
|
||||
<property name="useDefaultAvatar" value="${useDefaultAvatar}"/>
|
||||
<property name="defaultAvatarURL" value="${defaultAvatarURL}"/>
|
||||
<property name="useDefaultWebcamBackground" value="${useDefaultWebcamBackground}"/>
|
||||
<property name="defaultWebcamBackgroundURL" value="${defaultWebcamBackgroundURL}"/>
|
||||
<property name="defaultGuestPolicy" value="${defaultGuestPolicy}"/>
|
||||
<property name="authenticatedGuest" value="${authenticatedGuest}"/>
|
||||
<property name="defaultAllowPromoteGuestToModerator" value="${defaultAllowPromoteGuestToModerator}"/>
|
||||
|
@ -405,6 +405,12 @@ class ApiController {
|
||||
us.avatarURL = meeting.defaultAvatarURL
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(params.webcamBackgroundURL)) {
|
||||
us.webcamBackgroundURL = params.webcamBackgroundURL;
|
||||
} else {
|
||||
us.webcamBackgroundURL = meeting.defaultWebcamBackgroundURL
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(params.excludeFromDashboard)) {
|
||||
try {
|
||||
us.excludeFromDashboard = Boolean.parseBoolean(params.excludeFromDashboard)
|
||||
@ -435,6 +441,7 @@ class ApiController {
|
||||
us.externUserID,
|
||||
us.authToken,
|
||||
us.avatarURL,
|
||||
us.webcamBackgroundURL,
|
||||
us.guest,
|
||||
us.authed,
|
||||
guestStatusVal,
|
||||
@ -941,6 +948,7 @@ class ApiController {
|
||||
logoutUrl us.logoutUrl
|
||||
defaultLayout us.defaultLayout
|
||||
avatarURL us.avatarURL
|
||||
webcamBackgroundURL us.webcamBackgroundURL
|
||||
if (meeting.breakoutRoomsParams != null) {
|
||||
breakoutRooms {
|
||||
record meeting.breakoutRoomsParams.record
|
||||
|
@ -55,6 +55,12 @@ const joinEndpointTableData = [
|
||||
"type": "String",
|
||||
"description": (<>The link for the user’s avatar to be displayed (default can be enabled/disabled and set with “useDefaultAvatar“ and “defaultAvatarURL“ in bbb-web.properties).</>)
|
||||
},
|
||||
{
|
||||
"name": "webcamBackgroundURL",
|
||||
"required": false,
|
||||
"type": "String",
|
||||
"description": (<>The link for the user's webcam background to be displayed (default can be enabled/disabled and set with “useDefaultWebcamBackground“ and “defaultWebcamBackgroundURL“ in bigbluebutton.properties). Added in BigBlueButton 2.7.10.</>)
|
||||
},
|
||||
{
|
||||
"name": "redirect",
|
||||
"required": false,
|
||||
|
Loading…
Reference in New Issue
Block a user