Merge branch 'v2.0.x-release' of github.com:bigbluebutton/bigbluebutton into merge-v20-master-nov14

This commit is contained in:
Richard Alam 2018-11-14 08:47:21 -08:00
commit d1dad75898
10 changed files with 219 additions and 56 deletions

View File

@ -252,11 +252,10 @@ case class UserState(
locked: Boolean,
presenter: Boolean,
avatar: String,
roleChangedOn: Long = System.currentTimeMillis(),
lastActivityTime: Long = TimeUtil.timeNowInMs(),
roleChangedOn: Long = System.currentTimeMillis(),
lastActivityTime: Long = TimeUtil.timeNowInMs(),
clientType: String,
userLeftFlag: UserLeftFlag)
userLeftFlag: UserLeftFlag)
case class UserIdAndName(id: String, name: String)

View File

@ -53,34 +53,34 @@ class MeetingActor(
val eventBus: InternalEventBus,
val outGW: OutMsgRouter,
val liveMeeting: LiveMeeting)
extends BaseMeetingActor
with SystemConfiguration
with GuestsApp
with LayoutApp2x
with VoiceApp2x
with BreakoutApp2x
with UsersApp2x
extends BaseMeetingActor
with SystemConfiguration
with GuestsApp
with LayoutApp2x
with VoiceApp2x
with BreakoutApp2x
with UsersApp2x
with UserBroadcastCamStartMsgHdlr
with UserJoinMeetingReqMsgHdlr
with UserJoinMeetingAfterReconnectReqMsgHdlr
with UserBroadcastCamStopMsgHdlr
with UserConnectedToGlobalAudioMsgHdlr
with UserDisconnectedFromGlobalAudioMsgHdlr
with MuteAllExceptPresentersCmdMsgHdlr
with MuteMeetingCmdMsgHdlr
with IsMeetingMutedReqMsgHdlr
with UserBroadcastCamStartMsgHdlr
with UserJoinMeetingReqMsgHdlr
with UserJoinMeetingAfterReconnectReqMsgHdlr
with UserBroadcastCamStopMsgHdlr
with UserConnectedToGlobalAudioMsgHdlr
with UserDisconnectedFromGlobalAudioMsgHdlr
with MuteAllExceptPresentersCmdMsgHdlr
with MuteMeetingCmdMsgHdlr
with IsMeetingMutedReqMsgHdlr
with EjectUserFromVoiceCmdMsgHdlr
with EndMeetingSysCmdMsgHdlr
with DestroyMeetingSysCmdMsgHdlr
with SendTimeRemainingUpdateHdlr
with SendBreakoutTimeRemainingMsgHdlr
with ChangeLockSettingsInMeetingCmdMsgHdlr
with SyncGetMeetingInfoRespMsgHdlr
with ClientToServerLatencyTracerMsgHdlr
with ValidateConnAuthTokenSysMsgHdlr
with UserActivitySignCmdMsgHdlr {
with EjectUserFromVoiceCmdMsgHdlr
with EndMeetingSysCmdMsgHdlr
with DestroyMeetingSysCmdMsgHdlr
with SendTimeRemainingUpdateHdlr
with SendBreakoutTimeRemainingMsgHdlr
with ChangeLockSettingsInMeetingCmdMsgHdlr
with SyncGetMeetingInfoRespMsgHdlr
with ClientToServerLatencyTracerMsgHdlr
with ValidateConnAuthTokenSysMsgHdlr
with UserActivitySignCmdMsgHdlr {
override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
case e: Exception => {
@ -179,6 +179,7 @@ class MeetingActor(
def receive = {
//=============================
// 2x messages
case msg: BbbCommonEnvCoreMsg => handleBbbCommonEnvCoreMsg(msg)
@ -240,6 +241,19 @@ class MeetingActor(
}
private def handleBbbCommonEnvCoreMsg(msg: BbbCommonEnvCoreMsg): Unit = {
msg.core match {
case m: ClientToServerLatencyTracerMsg => handleMessageThatDoesNotAffectsInactivity(msg)
case _ => handleMessageThatAffectsInactivity(msg)
}
}
private def handleMessageThatDoesNotAffectsInactivity(msg: BbbCommonEnvCoreMsg): Unit = {
msg.core match {
case m: ClientToServerLatencyTracerMsg => handleClientToServerLatencyTracerMsg(m)
}
}
private def handleMessageThatAffectsInactivity(msg: BbbCommonEnvCoreMsg): Unit = {
msg.core match {
case m: EndMeetingSysCmdMsg => handleEndMeeting(m, state)
@ -287,8 +301,6 @@ class MeetingActor(
case m: SendWhiteboardAnnotationPubMsg => wbApp.handle(m, liveMeeting, msgBus)
case m: GetWhiteboardAnnotationsReqMsg => wbApp.handle(m, liveMeeting, msgBus)
case m: ClientToServerLatencyTracerMsg => handleClientToServerLatencyTracerMsg(m)
// Poll
case m: StartPollReqMsg =>
pollApp.handle(m, state, liveMeeting, msgBus) // passing state but not modifying it
@ -592,7 +604,10 @@ class MeetingActor(
updateParentMeetingWithUsers()
}
if (Users2x.numUsers(liveMeeting.users2x) == 0) {
if (state.expiryTracker.userHasJoined &&
Users2x.numUsers(liveMeeting.users2x) == 0
&& !state.expiryTracker.lastUserLeftOnInMs.isDefined) {
log.info("Setting meeting no more users. meetingId=" + props.meetingProp.intId)
val tracker = state.expiryTracker.setLastUserLeftOn(TimeUtil.timeNowInMs())
state.update(tracker)
} else {

View File

@ -69,8 +69,7 @@ trait FakeTestData {
UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role,
guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, clientType = "unknown",
userLeftFlag = UserLeftFlag(false, 0)
)
userLeftFlag = UserLeftFlag(false, 0))
}
}

View File

@ -0,0 +1 @@
node_modules/

View File

@ -21,7 +21,10 @@ if (!config.hooks.channels) {
config.hooks.channels = {
mainChannel: 'from-akka-apps-redis-channel',
rapChannel: 'bigbluebutton:from-rap',
chatChannel: 'from-akka-apps-chat-redis-channel'
chatChannel: 'from-akka-apps-chat-redis-channel',
compMeetingChannel: 'bigbluebutton:from-bbb-apps:meeting',
compUserChannel: 'bigbluebutton:from-bbb-apps:users',
compChatChannel: 'bigbluebutton:from-bbb-apps:chat'
}
}
// IP where permanent hook will post data (more than 1 URL means more than 1 permanent hook)

View File

@ -11,6 +11,9 @@ module.exports = class MessageMapping {
this.userEvents = ["UserJoinedMeetingEvtMsg","UserLeftMeetingEvtMsg","UserJoinedVoiceConfToClientEvtMsg","UserLeftVoiceConfToClientEvtMsg","PresenterAssignedEvtMsg", "PresenterUnassignedEvtMsg", "UserBroadcastCamStartedEvtMsg", "UserBroadcastCamStoppedEvtMsg", "UserEmojiChangedEvtMsg"];
this.chatEvents = ["SendPublicMessageEvtMsg","SendPrivateMessageEvtMsg"];
this.rapEvents = ["archive_started","archive_ended","sanity_started","sanity_ended","post_archive_started","post_archive_ended","process_started","process_ended","post_process_started","post_process_ended","publish_started","publish_ended","post_publish_started","post_publish_ended"];
this.compMeetingEvents = ["meeting_created_message","meeting_destroyed_event"];
this.compUserEvents = ["user_joined_message","user_left_message","user_listening_only","user_joined_voice_message","user_left_voice_message","user_shared_webcam_message","user_unshared_webcam_message","user_status_changed_message"];
}
// Map internal message based on it's type
@ -23,6 +26,10 @@ module.exports = class MessageMapping {
this.chatTemplate(messageObj);
} else if (this.mappedEvent(messageObj,this.rapEvents)) {
this.rapTemplate(messageObj);
} else if (this.mappedEvent(messageObj,this.compMeetingEvents)) {
this.compMeetingTemplate(messageObj);
} else if (this.mappedEvent(messageObj,this.compUserEvents)) {
this.compUserTemplate(messageObj);
}
}
@ -79,6 +86,46 @@ module.exports = class MessageMapping {
Logger.info("[MessageMapping] Mapped message:", this.mappedMessage);
}
compMeetingTemplate(messageObj) {
const props = messageObj.payload;
const meetingId = props.meeting_id;
this.mappedObject.data = {
"type": "event",
"id": this.mapInternalMessage(messageObj),
"attributes":{
"meeting":{
"internal-meeting-id": meetingId,
"external-meeting-id": IDMapping.getExternalMeetingID(meetingId)
}
},
"event":{
"ts": Date.now()
}
};
if (messageObj.header.name === "meeting_created_message") {
this.mappedObject.data.attributes = {
"meeting":{
"internal-meeting-id": meetingId,
"external-meeting-id": props.external_meeting_id,
"name": props.name,
"is-breakout": props.is_breakout,
"duration": props.duration,
"create-time": props.create_time,
"create-date": props.create_date,
"moderator-pass": props.moderator_pass,
"viewer-pass": props.viewer_pass,
"record": props.recorded,
"voice-conf": props.voice_conf,
"dial-number": props.dial_number,
"max-users": props.max_users,
"metadata": props.metadata
}
};
}
this.mappedMessage = JSON.stringify(this.mappedObject);
Logger.info("[MessageMapping] Mapped message:", this.mappedMessage);
}
// Map internal to external message for user information
userTemplate(messageObj) {
const msgBody = messageObj.core.body;
@ -111,6 +158,68 @@ module.exports = class MessageMapping {
Logger.info("[MessageMapping] Mapped message:", this.mappedMessage);
}
// Map internal to external message for user information
compUserTemplate(messageObj) {
const msgBody = messageObj.payload;
const msgHeader = messageObj.header;
let user;
if (msgHeader.name === "user_joined_message") {
user = {
"internal-user-id": msgBody.user.userid,
"external-user-id": msgBody.user.extern_userid,
"sharing-mic": msgBody.user.voiceUser.joined,
"name": msgBody.user.name,
"role": msgBody.user.role,
"presenter": msgBody.user.presenter,
"stream": msgBody.user.webcam_stream,
"listening-only": msgBody.user.listenOnly
}
}
else {
user = UserMapping.getUser(msgBody.userid) || { "internal-user-id": msgBody.userid || msgBody.user.userid };
if (msgHeader.name === "user_status_changed_message") {
if (msgBody.status === "presenter") {
user["presenter"] = msgBody.value;
}
}
else if (msgHeader.name === "user_listening_only") {
user["listening-only"] = msgBody.listen_only;
}
else if (msgHeader.name === "user_joined_voice_message" || msgHeader.name === "user_left_voice_message") {
user["sharing-mic"] = msgBody.user.voiceUser.joined;
}
else if (msgHeader.name === "user_shared_webcam_message") {
user["stream"].push(msgBody.stream);
}
else if (msgHeader.name === "user_unshared_webcam_message") {
let streams = user["stream"];
let index = streams.indexOf(msgBody.stream);
if (index != -1) {
streams.splice(index,1);
}
user["stream"] = streams;
}
}
this.mappedObject.data = {
"type": "event",
"id": this.mapInternalMessage(messageObj),
"attributes":{
"meeting":{
"internal-meeting-id": msgBody.meeting_id,
"external-meeting-id": IDMapping.getExternalMeetingID(msgBody.meeting_id)
},
"user": user
},
"event":{
"ts": Date.now()
}
};
this.mappedMessage = JSON.stringify(this.mappedObject);
Logger.info("[MessageMapping] Mapped message:", this.mappedMessage);
}
// Map internal to external message for chat information
chatTemplate(messageObj) {
const message = messageObj.core.body.message;
@ -150,12 +259,13 @@ module.exports = class MessageMapping {
const data = messageObj.payload;
this.mappedObject.data = {
"type": "event",
"id": this.mapInternalMessage(messageObj.header.name),
"id": this.mapInternalMessage(messageObj),
"attributes": {
"meeting": {
"internal-meeting-id": data.meeting_id,
"external-meeting-id": data.external_meeting_id
"external-meeting-id": data.external_meeting_id || IDMapping.getExternalMeetingID(data.meeting_id)
},
"record-id": data.record_id,
"success": data.success,
"step-time": data.step_time
},
@ -164,14 +274,18 @@ module.exports = class MessageMapping {
}
};
if (this.mappedObject.data["id"] == "rap-publish-ended") {
this.mappedObject.data["attributes"]["recording"] = {
if (data.workflow) {
this.mappedObject.data.attributes.workflow = data.workflow;
}
if (this.mappedObject.data.id === "rap-publish-ended") {
this.mappedObject.data.attributes.recording = {
"name": data.metadata.meetingName,
"isBreakout": data.metadata.isBreakout,
"startTime": data.startTime,
"endTime": data.endTime,
"is-breakout": data.metadata.isBreakout,
"start-time": data.startTime,
"end-time": data.endTime,
"size": data.playback.size,
"rawSize": data.rawSize,
"raw-size": data.rawSize,
"metadata": data.metadata,
"playback": data.playback,
"download": data.download
@ -183,13 +297,14 @@ module.exports = class MessageMapping {
mapInternalMessage(message) {
let name;
if (message.envelope) {
message = message.envelope.name
name = message.envelope.name
}
else if (message.header) {
message = message.header.name
name = message.header.name
}
const mappedMsg = (() => { switch (message) {
const mappedMsg = (() => { switch (name) {
case "MeetingCreatedEvtMsg": return "meeting-created";
case "MeetingDestroyedEvtMsg": return "meeting-ended";
case "RecordingStatusChangedEvtMsg": return "meeting-recording-changed";
@ -221,6 +336,19 @@ module.exports = class MessageMapping {
case "publish_ended": return "rap-publish-ended";
case "post_publish_started": return "rap-post-publish-started";
case "post_publish_ended": return "rap-post-publish-ended";
case "meeting_created_message": return "meeting-created";
case "meeting_destroyed_event": return "meeting-ended";
case "user_joined_message": return "user-joined";
case "user_left_message": return "user-left";
case "user_listening_only": return (message.payload.listen_only ? "user-audio-listen-only-enabled" : "user-audio-listen-only-disabled");
case "user_joined_voice_message": return "user-audio-voice-enabled";
case "user_left_voice_message": return "user-audio-voice-disabled";
case "user_shared_webcam_message": return "user-cam-broadcast-start";
case "video_stream_unpublished": return "user-cam-broadcast-end";
case "user_status_changed_message":
if (message.payload.status === "presenter") {
return (message.payload.value === "true" ? "user-presenter-assigned" : "user-presenter-unassigned" );
}
} })();
return mappedMsg;
}

View File

@ -32,16 +32,18 @@ module.exports = class UserMapping {
this.externalUserID = null;
this.internalUserID = null;
this.meetingId = null;
this.user = null;
this.redisClient = config.redis.client;
}
save(callback) {
db[this.internalUserID] = this;
this.redisClient.hmset(config.redis.keys.userMap(this.id), this.toRedis(), (error, reply) => {
if (error != null) { Logger.error("[UserMapping] error saving mapping to redis:", error, reply); }
this.redisClient.sadd(config.redis.keys.userMaps, this.id, (error, reply) => {
if (error != null) { Logger.error("[UserMapping] error saving mapping ID to the list of mappings:", error, reply); }
db[this.internalUserID] = this;
(typeof callback === 'function' ? callback(error, db[this.internalUserID]) : undefined);
});
});
@ -68,7 +70,8 @@ module.exports = class UserMapping {
"id": this.id,
"internalUserID": this.internalUserID,
"externalUserID": this.externalUserID,
"meetingId": this.meetingId
"meetingId": this.meetingId,
"user": this.user
};
return r;
}
@ -78,18 +81,20 @@ module.exports = class UserMapping {
this.externalUserID = redisData.externalUserID;
this.internalUserID = redisData.internalUserID;
this.meetingId = redisData.meetingId;
this.user = redisData.user;
}
print() {
return JSON.stringify(this.toRedis());
}
static addMapping(internalUserID, externalUserID, meetingId, callback) {
static addOrUpdateMapping(internalUserID, externalUserID, meetingId, user, callback) {
let mapping = new UserMapping();
mapping.id = nextID++;
mapping.internalUserID = internalUserID;
mapping.externalUserID = externalUserID;
mapping.meetingId = meetingId;
mapping.user = user;
mapping.save(function(error, result) {
Logger.info(`[UserMapping] added user mapping to the list ${internalUserID}:`, mapping.print());
(typeof callback === 'function' ? callback(error, result) : undefined);
@ -131,6 +136,12 @@ module.exports = class UserMapping {
})();
}
static getUser(internalUserID) {
if (db[internalUserID]){
return db[internalUserID].user;
}
}
static getExternalUserID(internalUserID) {
if (db[internalUserID]){
return db[internalUserID].externalUserID;

View File

@ -54,7 +54,7 @@ module.exports = class WebHooks {
});
break;
case "user-joined":
UserMapping.addMapping(message.data.attributes.user["internal-user-id"],message.data.attributes.user["external-user-id"], intId, () => {
UserMapping.addOrUpdateMapping(message.data.attributes.user["internal-user-id"],message.data.attributes.user["external-user-id"], intId, message.data.attributes.user, () => {
processMessage();
});
break;

View File

@ -12,6 +12,7 @@ const CALL_TRANSFER_TIMEOUT = MEDIA.callTransferTimeout;
const CALL_HANGUP_TIMEOUT = MEDIA.callHangupTimeout;
const CALL_HANGUP_MAX_RETRIES = MEDIA.callHangupMaximumRetries;
const CONNECTION_TERMINATED_EVENTS = ['iceConnectionFailed', 'iceConnectionClosed'];
const CALL_CONNECT_NOTIFICATION_TIMEOUT = 500;
export default class SIPBridge extends BaseAudioBridge {
constructor(userData) {
@ -275,9 +276,15 @@ export default class SIPBridge extends BaseAudioBridge {
const connectionCompletedEvents = ['iceConnectionCompleted', 'iceConnectionConnected'];
const handleConnectionCompleted = () => {
connectionCompletedEvents.forEach(e => mediaHandler.off(e, handleConnectionCompleted));
this.callback({ status: this.baseCallStates.started });
this.connectionCompleted = true;
resolve();
// We have to delay notifying that the call is connected because it is sometimes not
// actually ready and if the user says "Yes they can hear themselves" too quickly the
// B-leg transfer will fail
const that = this;
setTimeout(() => {
that.callback({ status: that.baseCallStates.started });
that.connectionCompleted = true;
resolve();
}, CALL_CONNECT_NOTIFICATION_TIMEOUT);
};
connectionCompletedEvents.forEach(e => mediaHandler.on(e, handleConnectionCompleted));

View File

@ -70,7 +70,7 @@
position: relative;
height: 100%;
width: 100%;
object-fit: cover;
object-fit: contain;
border-radius: 5px;
}