Added "isListener" to the response of "getMeetingInfo". Had to change bbb-apps, bbb-web and bbb-client to generate and detect events when the user starts/stops its audio. The method used in bbb-client to send the externalUserId can still be improved.

This commit is contained in:
Leonardo Crauss Daronco 2012-02-13 17:04:58 -02:00
parent 8da1535c28
commit 1d848b1aaa
18 changed files with 329 additions and 40 deletions

View File

@ -16,4 +16,6 @@ public class MessagingConstants {
public static final String USER_JOINED_EVENT = "UserJoinedEvent";
public static final String USER_LEFT_EVENT = "UserLeftEvent";
public static final String USER_STATUS_CHANGE_EVENT = "UserStatusChangeEvent";
public static final String USER_JOINED_VOICE_EVENT = "UserJoinedVoiceEvent";
public static final String USER_LEFT_VOICE_EVENT = "UserLeftVoiceEvent";
}

View File

@ -25,6 +25,7 @@ public interface Participant {
public boolean isMuted();
public boolean isTalking();
public int getId();
public String getExternalUserId();
public String getName();
public boolean isMuteLocked();
}

View File

@ -35,4 +35,6 @@ public interface Room {
public boolean record();
public void recording(boolean rec);
public boolean isRecording();
public String getMeetingId();
public void setMeetingId(String meetingid);
}

View File

@ -25,15 +25,17 @@ public class ParticipantJoinedEvent extends ConferenceEvent {
private final String callerIdNum;
private final String callerIdName;
private final String callerIdExternalUserId;
private final Boolean muted;
private final Boolean speaking;
private final Boolean locked = false;
public ParticipantJoinedEvent(Integer participantId, String room,
String callerIdNum, String callerIdName,
Boolean muted, Boolean speaking) {
String callerIdNum, String callerIdName, String callerIdExternalUserId,
Boolean muted, Boolean speaking) {
super(participantId, room);
this.callerIdName = callerIdName;
this.callerIdExternalUserId = callerIdExternalUserId;
this.callerIdNum = callerIdNum;
this.muted = muted;
this.speaking = speaking;
@ -47,6 +49,10 @@ public class ParticipantJoinedEvent extends ConferenceEvent {
return callerIdName;
}
public String getCallerIdExternalUserId() {
return callerIdExternalUserId;
}
public Boolean getMuted() {
return muted;
}

View File

@ -193,10 +193,11 @@ public class FreeswitchApplication extends Observable implements ConferenceServi
Map<String, String> headers = event.getEventHeaders();
String callerId = this.getCallerIdFromEvent(event);
String callerIdName = this.getCallerIdNameFromEvent(event);
String callerIdExternalUserId = this.getCallerIdExternalUserIdFromEvent(event);
boolean muted = headers.get("Speak").equals("true") ? false : true; //Was inverted which was causing a State issue
boolean speeking = headers.get("Talking").equals("true") ? true : false;
ParticipantJoinedEvent pj = new ParticipantJoinedEvent(memberId, confName, callerId, callerIdName, muted, speeking);
ParticipantJoinedEvent pj = new ParticipantJoinedEvent(memberId, confName, callerId, callerIdName, callerIdExternalUserId, muted, speeking);
conferenceEventListener.handleConferenceEvent(pj);
}
@ -365,7 +366,13 @@ public class FreeswitchApplication extends Observable implements ConferenceServi
private String getCallerIdNameFromEvent(EslEvent e)
{
return e.getEventHeaders().get("Caller-Caller-ID-Name");
return e.getEventHeaders().get("Caller-Caller-ID-Name").split(";")[0];
}
private String getCallerIdExternalUserIdFromEvent(EslEvent e)
{
// TODO: this information should get here in another header section
return e.getEventHeaders().get("Caller-Caller-ID-Name").split(";")[1];
}
private String getRecordFilenameFromEvent(EslEvent e) {

View File

@ -0,0 +1,28 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2010 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.webconference.voice.internal;
import org.bigbluebutton.webconference.voice.Participant;
public interface IRoomListener {
public String getName();
public void participantJoined(Participant participant);
public void participantLeft(Participant participant);
}

View File

@ -28,14 +28,16 @@ import net.jcip.annotations.ThreadSafe;
@ThreadSafe
class ParticipantImp implements Participant {
private final int id;
private final String externalUserId;
private final String name;
private boolean muted = false;
private boolean talking = false;
private boolean locked = false;
ParticipantImp(int id, String name) {
ParticipantImp(int id, String name, String externalUserId) {
this.id = id;
this.name = name;
this.externalUserId = externalUserId;
}
synchronized void setTalking(boolean talking) {
@ -69,4 +71,8 @@ class ParticipantImp implements Participant {
public String getName() {
return name;
}
public String getExternalUserId() {
return externalUserId;
}
}

View File

@ -0,0 +1,75 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2010 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.webconference.voice.internal;
import java.util.HashMap;
import org.bigbluebutton.conference.service.messaging.MessagingConstants;
import org.bigbluebutton.conference.service.messaging.MessagingService;
import org.bigbluebutton.webconference.voice.Room;
import org.bigbluebutton.webconference.voice.Participant;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import com.google.gson.Gson;
public class ParticipantUpdatingRoomListener implements IRoomListener {
private static Logger log = Red5LoggerFactory.getLogger(ParticipantUpdatingRoomListener.class, "bigbluebutton");
MessagingService messagingService;
private Room room;
public ParticipantUpdatingRoomListener(Room room, MessagingService messagingService) {
this.room = room;
this.messagingService = messagingService;
}
public String getName() {
return "VOICE:PARTICIPANT:UPDATE:ROOM";
}
public void participantJoined(Participant p) {
if (messagingService != null) {
HashMap<String,String> map= new HashMap<String, String>();
map.put("meetingId", this.room.getMeetingId());
map.put("messageId", MessagingConstants.USER_JOINED_VOICE_EVENT);
map.put("externalUserId", p.getExternalUserId());
Gson gson= new Gson();
messagingService.send(MessagingConstants.PARTICIPANTS_CHANNEL, gson.toJson(map));
log.debug("Publishing message participant joined voice in " + this.room.getMeetingId());
}
}
public void participantLeft(Participant p) {
if (messagingService != null) {
HashMap<String,String> map= new HashMap<String, String>();
map.put("meetingId", this.room.getMeetingId());
map.put("messageId", MessagingConstants.USER_LEFT_VOICE_EVENT);
map.put("externalUserId", p.getExternalUserId());
Gson gson= new Gson();
messagingService.send(MessagingConstants.PARTICIPANTS_CHANNEL, gson.toJson(map));
log.debug("Publishing message participant left voice in " + this.room.getMeetingId());
}
}
}

View File

@ -23,10 +23,13 @@ package org.bigbluebutton.webconference.voice.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.slf4j.Logger;
import org.red5.logging.Red5LoggerFactory;
import org.bigbluebutton.webconference.voice.Participant;
import org.bigbluebutton.webconference.voice.Room;
@ -34,6 +37,7 @@ import net.jcip.annotations.ThreadSafe;
@ThreadSafe
public class RoomImp implements Room {
private static Logger log = Red5LoggerFactory.getLogger( RoomImp.class, "bigbluebutton" );
private final String name;
private final ConcurrentMap<Integer, Participant> participants;
@ -42,18 +46,33 @@ public class RoomImp implements Room {
private boolean record = false;
private String meetingid;
private boolean recording = false;
private transient final Map<String, IRoomListener> listeners;
public RoomImp(String name,boolean record, String meetingid) {
this.name = name;
this.record = record;
this.meetingid = meetingid;
participants = new ConcurrentHashMap<Integer, Participant>();
listeners = new ConcurrentHashMap<String, IRoomListener>();
}
public String getName() {
return name;
}
public void addRoomListener(IRoomListener listener) {
if (! listeners.containsKey(listener.getName())) {
log.debug("adding room listener");
listeners.put(listener.getName(), listener);
}
}
public void removeRoomListener(IRoomListener listener) {
log.debug("removing room listener");
listeners.remove(listener);
}
public int numParticipants() {
return participants.size();
}
@ -63,8 +82,22 @@ public class RoomImp implements Room {
}
public Participant add(Participant p) {
return participants.putIfAbsent(p.getId(), p);
}
int key = p.getId();
if (!participants.containsKey(key)) {
participants.put(key, p);
log.debug("Informing roomlisteners " + listeners.size());
for (Iterator it = listeners.values().iterator(); it.hasNext();) {
IRoomListener listener = (IRoomListener) it.next();
log.debug("calling participantJoined on listener " + listener.getName());
listener.participantJoined(p);
}
return p;
} else {
return participants.get(key);
}
}
public boolean hasParticipant(Integer id) {
return participants.containsKey(id);
@ -72,7 +105,14 @@ public class RoomImp implements Room {
public void remove(Integer id) {
Participant p = participants.remove(id);
if (p != null) p = null;
if (p != null) {
for (Iterator it = listeners.values().iterator(); it.hasNext();) {
IRoomListener listener = (IRoomListener) it.next();
log.debug("calling participantLeft on listener " + listener.getName());
listener.participantLeft(p);
}
p = null;
}
}
public void mute(boolean mute) {
@ -99,11 +139,11 @@ public class RoomImp implements Room {
return recording;
}
public String getMeeting() {
public String getMeetingId() {
return meetingid;
}
public void setMeeting(String meetingid) {
public void setMeetingId(String meetingid) {
this.meetingid = meetingid;
}

View File

@ -21,6 +21,8 @@ package org.bigbluebutton.webconference.voice.internal;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import org.bigbluebutton.conference.Constants;
import org.bigbluebutton.conference.service.messaging.MessagingService;
import org.bigbluebutton.webconference.voice.ConferenceService;
import org.bigbluebutton.webconference.voice.Participant;
import org.bigbluebutton.webconference.voice.VoiceEventRecorder;
@ -34,6 +36,7 @@ import org.bigbluebutton.webconference.voice.events.StartRecordingEvent;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import net.jcip.annotations.ThreadSafe;
//import org.red5.server.api.IConnection;
@ThreadSafe
public class RoomManager {
@ -42,14 +45,21 @@ public class RoomManager {
private final ConcurrentHashMap<String, RoomImp> rooms;
private ConferenceService confService;
private VoiceEventRecorder recorder;
private MessagingService messagingService;
public RoomManager() {
rooms = new ConcurrentHashMap<String, RoomImp>();
}
public void setMessagingService(MessagingService messagingService) {
this.messagingService = messagingService;
this.messagingService.start();
}
public void createRoom(String name,boolean record, String meetingid) {
log.debug("Creating room: " + name);
RoomImp r = new RoomImp(name,record,meetingid);
r.addRoomListener(new ParticipantUpdatingRoomListener(r, messagingService));
rooms.putIfAbsent(name, r);
}
@ -140,17 +150,18 @@ public class RoomManager {
/**
* Record the event if the meeting is being recorded.
*/
recorder.recordConferenceEvent(event, rm.getMeeting());
recorder.recordConferenceEvent(event, rm.getMeetingId());
}
private void handleParticipantJoinedEvent(ConferenceEvent event, RoomImp rm) {
log.debug("Processing ParticipantJoinedEvent for room: " + event.getRoom());
ParticipantJoinedEvent pje = (ParticipantJoinedEvent) event;
ParticipantImp p = new ParticipantImp(pje.getParticipantId(), pje.getCallerIdName());
ParticipantImp p = new ParticipantImp(pje.getParticipantId(), pje.getCallerIdName(), pje.getCallerIdExternalUserId());
p.setMuted(pje.getMuted());
p.setTalking(pje.getSpeaking());
log.debug("Joined [" + p.getId() + "," + p.getName() + "," + p.isMuted() + "," + p.isTalking() + "] to room " + rm.getName());
log.debug("Joined [" + p.getId() + "," + p.getName() + "," + p.getExternalUserId() + "," + p.isMuted() + "," + p.isTalking() + "] to room " + rm.getName());
rm.add(p);
if ((rm.numParticipants() == 1) && rm.record() && !rm.isRecording()) {
@ -162,7 +173,7 @@ public class RoomManager {
rm.recording(true);
log.debug("Starting recording of voice conference");
log.warn(" ** WARNING: Prototyping only. Works only with FreeSWITCH for now. We need to come up with a generic way to trigger recording for both Asterisk and FreeSWITCH.");
confService.recordSession(event.getRoom(), rm.getMeeting());
confService.recordSession(event.getRoom(), rm.getMeetingId());
}
if (rm.isMuted() && !p.isMuted()) {

View File

@ -1,33 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.0.xsd">
<beans:bean id="voiceHandler" class="org.bigbluebutton.conference.service.voice.VoiceHandler">
<beans:property name="conferenceService" ref="conferenceService"/>
<beans:property name="clientNotifier" ref="clientNotifier"/>
</beans:bean>
<bean id="voiceHandler" class="org.bigbluebutton.conference.service.voice.VoiceHandler">
<property name="conferenceService" ref="conferenceService"/>
<property name="clientNotifier" ref="clientNotifier"/>
</bean>
<beans:bean id="clientNotifier" class="org.bigbluebutton.webconference.red5.voice.ClientManager"/>
<bean id="clientNotifier" class="org.bigbluebutton.webconference.red5.voice.ClientManager"/>
<beans:bean id="conferenceService" class="org.bigbluebutton.webconference.voice.ConferenceService">
<beans:property name="conferenceServiceProvider" ref="conferenceServiceProvider"/>
<beans:property name="roomManager" ref="roomManager"/>
<beans:property name="clientManager" ref="clientNotifier"/>
</beans:bean>
<bean id="conferenceService" class="org.bigbluebutton.webconference.voice.ConferenceService">
<property name="conferenceServiceProvider" ref="conferenceServiceProvider"/>
<property name="roomManager" ref="roomManager"/>
<property name="clientManager" ref="clientNotifier"/>
</bean>
<beans:bean id="roomManager" class="org.bigbluebutton.webconference.voice.internal.RoomManager">
<beans:property name="voiceEventRecorder" ref="voiceEventRecorder"/>
</beans:bean>
<bean id="roomManager" class="org.bigbluebutton.webconference.voice.internal.RoomManager">
<property name="messagingService" ref="messagingService"></property>
<property name="voiceEventRecorder" ref="voiceEventRecorder"/>
</bean>
<beans:bean id="voiceEventRecorder" class="org.bigbluebutton.webconference.voice.VoiceEventRecorder">
<beans:property name="recorderApplication" ref="recorderApplication"/>
</beans:bean>
<bean id="voiceEventRecorder" class="org.bigbluebutton.webconference.voice.VoiceEventRecorder">
<property name="recorderApplication" ref="recorderApplication"/>
</bean>
<beans:bean id="voice.service" class="org.bigbluebutton.conference.service.voice.VoiceService">
<beans:property name="conferenceService" ref="conferenceService"/>
</beans:bean>
<bean id="voice.service" class="org.bigbluebutton.conference.service.voice.VoiceService">
<property name="conferenceService" ref="conferenceService"/>
</bean>
</beans:beans>
<bean id="messagingService" class="org.bigbluebutton.conference.service.messaging.RedisMessagingService">
<property name="redisPool" ref="redisPool"/>
</bean>
<bean id="config" class="org.bigbluebutton.conference.service.recorder.GenericObjectPoolConfigWrapper">
<!-- Action to take when trying to acquire a connection and all connections are taken -->
<property name="whenExhaustedAction">
<!-- Fail-fast behaviour, we don't like to keep the kids waiting -->
<util:constant static-field="org.apache.commons.pool.impl.GenericObjectPool.WHEN_EXHAUSTED_FAIL" />
<!-- Default behaviour, block the caller until a resource becomes available -->
<!--<util:constant static-field="org.apache.commons.pool.impl.GenericObjectPool.WHEN_EXHAUSTED_BLOCK" />-->
</property>
<!-- Maximum active connections to Redis instance -->
<property name="maxActive" value="12" />
<!-- Number of connections to Redis that just sit there and do nothing -->
<property name="maxIdle" value="6" />
<!-- Minimum number of idle connections to Redis - these can be seen as always open and ready to serve -->
<property name="minIdle" value="1" />
<!-- Tests whether connection is dead when connection retrieval method is called -->
<property name="testOnBorrow" value="true" />
<!-- Tests whether connection is dead when returning a connection to the pool -->
<property name="testOnReturn" value="true" />
<!-- Tests whether connections are dead during idle periods -->
<property name="testWhileIdle" value="true" />
<!-- Maximum number of connections to test in each idle check -->
<property name="numTestsPerEvictionRun" value="12" />
<!-- Idle connection checking period -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- Maximum time, in milliseconds, to wait for a resource when exausted action is set to WHEN_EXAUSTED_BLOCK -->
<property name="maxWait" value="5000" />
</bean>
<bean id="redisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg index="0">
<bean factory-bean="config" factory-method="getConfig" />
</constructor-arg>
<constructor-arg index="1" value="${redis.host}"/>
<constructor-arg index="2" value="${redis.port}"/>
</bean>
</beans>

View File

@ -1176,6 +1176,7 @@ class ApiController {
isPresenter("${att.isPresenter()}")
hasVideoStream("${att.hasStream()}")
videoStreamName("${att.getStreamName()}")
isListener("${att.isListener()}")
}
}
}

View File

@ -323,6 +323,38 @@ public class MeetingService {
}
log.warn("The meeting " + meetingId + " doesn't exist");
}
@Override
public void userJoinedVoice(String meetingId, String externalUserId) {
Meeting m = getMeeting(meetingId);
if (m != null) {
User user = m.getUserByExternalId(externalUserId);
if(user != null){
user.setIsListener(true);
log.debug("The user " + user.getFullname() + " joined the voice meeting in the meeting " + meetingId);
return;
}
log.warn("The participant " + externalUserId + " doesn't exist in the meeting " + meetingId);
return;
}
log.warn("The meeting " + meetingId + " doesn't exist");
}
@Override
public void userLeftVoice(String meetingId, String externalUserId) {
Meeting m = getMeeting(meetingId);
if (m != null) {
User user = m.getUserByExternalId(externalUserId);
if(user != null){
user.setIsListener(false);
log.debug("The user " + user.getFullname() + " left the voice meeting in the meeting " + meetingId);
return;
}
log.warn("The participant " + externalUserId + " doesn't exist in the meeting " + meetingId);
return;
}
log.warn("The meeting " + meetingId + " doesn't exist");
}
}
}
}

View File

@ -175,6 +175,15 @@ public class Meeting {
public User getUserById(String id){
return this.users.get(id);
}
public User getUserByExternalId(String externalUserId){
for (String key : users.keySet()) {
User u = (User) users.get(key);
if (u.getExternalUserId().equals(externalUserId))
return u;
}
return null;
}
public int getNumUsers(){
return this.users.size();

View File

@ -9,6 +9,7 @@ public class User {
private String fullname;
private String role;
private Map<String,String> status;
private boolean isListener;
public User(String internalUserId, String externalUserId, String fullname, String role) {
this.internalUserId = internalUserId;
@ -16,6 +17,7 @@ public class User {
this.fullname = fullname;
this.role = role;
this.status = new ConcurrentHashMap<String, String>();
this.isListener = false;
}
public String getInternalUserId() {
@ -60,6 +62,14 @@ public class User {
return this.status;
}
public boolean isListener() {
return isListener;
}
public void setIsListener(boolean isListener) {
this.isListener = isListener;
}
public boolean isPresenter() {
String isPresenter = this.status.get("presenter");
if (isPresenter != null) {

View File

@ -6,4 +6,6 @@ public interface MessageListener {
void userJoined(String meetingId, String internalUserId, String externalUserId, String name, String role);
void userLeft(String meetingId, String internalUserId);
void updatedStatus(String meetingId, String internalUserId, String status, String value);
void userJoinedVoice(String meetingId, String externalUserId);
void userLeftVoice(String meetingId, String externalUserId);
}

View File

@ -16,4 +16,6 @@ public class MessagingConstants {
public static final String USER_JOINED_EVENT = "UserJoinedEvent";
public static final String USER_LEFT_EVENT = "UserLeftEvent";
public static final String USER_STATUS_CHANGE_EVENT = "UserStatusChangeEvent";
public static final String USER_JOINED_VOICE_EVENT = "UserJoinedVoiceEvent";
public static final String USER_LEFT_VOICE_EVENT = "UserLeftVoiceEvent";
}

View File

@ -160,6 +160,18 @@ public class RedisMessagingService implements MessagingService {
for (MessageListener listener : listeners) {
listener.userLeft(meetingId, internalUserId);
}
} else if(MessagingConstants.USER_JOINED_VOICE_EVENT.equalsIgnoreCase(messageId)){
String externalUserId = map.get("externalUserId");
for (MessageListener listener : listeners) {
listener.userJoinedVoice(meetingId, externalUserId);
}
} else if(MessagingConstants.USER_LEFT_VOICE_EVENT.equalsIgnoreCase(messageId)){
String externalUserId = map.get("externalUserId");
for (MessageListener listener : listeners) {
listener.userLeftVoice(meetingId, externalUserId);
}
}
}
}