/* * GT-Mconf: Multiconference system for interoperable web and mobile * http://www.inf.ufrgs.br/prav/gtmconf * PRAV Labs - UFRGS * * This file is part of Mconf-Mobile. * * Mconf-Mobile 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 3 of the License, or * (at your option) any later version. * * Mconf-Mobile 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 Mconf-Mobile. If not, see <http://www.gnu.org/licenses/>. */ package org.mconf.bbb.users; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.jboss.netty.channel.Channel; import org.mconf.bbb.BigBlueButtonClient.OnKickUserListener; import org.mconf.bbb.BigBlueButtonClient.OnParticipantJoinedListener; import org.mconf.bbb.BigBlueButtonClient.OnParticipantLeftListener; import org.mconf.bbb.BigBlueButtonClient.OnParticipantStatusChangeListener; import org.mconf.bbb.MainRtmpConnection; import org.mconf.bbb.Module; import org.mconf.bbb.api.ApplicationService; import org.red5.server.api.IAttributeStore; import org.red5.server.api.so.IClientSharedObject; import org.red5.server.api.so.ISharedObjectBase; import org.red5.server.api.so.ISharedObjectListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.flazr.rtmp.message.Command; import com.flazr.rtmp.message.CommandAmf0; public class UsersModule extends Module implements ISharedObjectListener { private static final Logger log = LoggerFactory.getLogger(UsersModule.class); private final IClientSharedObject participantsSO; private Map<String, Participant> participants = new ConcurrentHashMap<String, Participant>(); private int moderatorCount = 0, participantCount = 0; private String joinServiceVersion; public UsersModule(MainRtmpConnection handler, Channel channel) { super(handler, channel); joinServiceVersion = handler.getContext().getJoinService().getApplicationService().getVersion(); participantsSO = handler.getSharedObject("participantsSO", false); participantsSO.addSharedObjectListener(this); participantsSO.connect(channel); } @Override public void onSharedObjectClear(ISharedObjectBase so) { log.debug("onSharedObjectClear"); doQueryParticipants(); } @Override public void onSharedObjectConnect(ISharedObjectBase so) { log.debug("onSharedObjectConnect"); } @Override public void onSharedObjectDelete(ISharedObjectBase so, String key) { log.debug("onSharedObjectDelete"); } @Override public void onSharedObjectDisconnect(ISharedObjectBase so) { log.debug("onSharedObjectDisconnect"); } @SuppressWarnings("unchecked") @Override public void onSharedObjectSend(ISharedObjectBase so, String method, List<?> params) { log.debug("onSharedObjectSend"); if (so.equals(participantsSO)) { if (method.equals("kickUserCallback")) { IParticipant p = getParticipant(params.get(0)); if (handler.getContext().isMyself(p.getUserId())) { for (OnKickUserListener l : handler.getContext().getKickUserListeners()) l.onKickMyself(); channel.close(); } else for (OnKickUserListener l : handler.getContext().getKickUserListeners()) l.onKickUser(p); return; } if (method.equals("participantLeft")) { IParticipant p = getParticipant(params.get(0)); synchronized (handler.getContext().getParticipantLeftListeners()) { for (OnParticipantLeftListener l : handler.getContext().getParticipantLeftListeners()) l.onParticipantLeft(p); } if(p.getRole().equals("MODERATOR")) moderatorCount--; else participantCount--; log.debug("participantLeft: {}", p); participants.remove(p.getUserId()); return; } if (method.equals("participantJoined")) { Participant p = new Participant((Map<String, Object>) params.get(0), joinServiceVersion); onParticipantJoined(p); return; } if (method.equals("participantStatusChange")) { Participant p = getParticipant(params.get(0)); if (p != null) onParticipantStatusChange(p, (String) params.get(1), params.get(2)); return; } } } static public String getUserIdFromObject(Object param) { if (param.getClass() == Double.class) return Integer.toString(((Double) param).intValue()); else if (param.getClass() == String.class) return (String) param; else return null; } private Participant getParticipant(Object param) { String userId = getUserIdFromObject(param); if (userId != null) return participants.get(userId); else return null; } @Override public void onSharedObjectUpdate(ISharedObjectBase so, String key, Object value) { log.debug("onSharedObjectUpdate 1"); } @Override public void onSharedObjectUpdate(ISharedObjectBase so, IAttributeStore values) { log.debug("onSharedObjectUpdate 2"); } @Override public void onSharedObjectUpdate(ISharedObjectBase so, Map<String, Object> values) { log.debug("onSharedObjectUpdate 3"); } /** * {@link} https://github.com/bigbluebutton/bigbluebutton/blob/master/bigbluebutton-client/src/org/bigbluebutton/modules/chat/services/PrivateChatSharedObjectService.as#L142 */ public void doQueryParticipants() { Command cmd = new CommandAmf0("participants.getParticipants", null); handler.writeCommandExpectingResult(channel, cmd); } /** * example: * [MAP {count=2.0, participants={112={status={raiseHand=false, hasStream=false, presenter=false}, name=Eclipse, userid=112.0, role=VIEWER}, 97={status={raiseHand=false, hasStream=false, presenter=true}, name=Felipe, userid=97.0, role=MODERATOR}}}] * [1 COMMAND_AMF0 c3 #0 t0 (0) s299] name: _result, transactionId: 4, object: null, args: [{count=2.0, participants={112={status={raiseHand=false, hasStream=false, presenter=false}, name=Eclipse, userid=112.0, role=VIEWER}, 97={status={raiseHand=false, hasStream=false, presenter=true}, name=Felipe, userid=97.0, role=MODERATOR}}}] */ @SuppressWarnings("unchecked") public boolean onQueryParticipants(String resultFor, Command command) { if (resultFor.equals("participants.getParticipants")) { Map<String, Object> args = (Map<String, Object>) command.getArg(0); participants.clear(); @SuppressWarnings("unused") int count = ((Double) args.get("count")).intValue(); Map<String, Object> participantsMap = (Map<String, Object>) args.get("participants"); for (Map.Entry<String, Object> entry : participantsMap.entrySet()) { Participant p = new Participant((Map<String, Object>) entry.getValue(), joinServiceVersion); onParticipantJoined(p); } return true; } return false; } public Map<String, Participant> getParticipants() { return participants; } public void onParticipantJoined(Participant p) { log.info("new participant: {}", p.toString()); participants.put(p.getUserId(), p); if (p.isModerator()) moderatorCount++; else participantCount++; for (OnParticipantJoinedListener l : handler.getContext().getParticipantJoinedListeners()) l.onParticipantJoined(p); } private void onParticipantStatusChange(Participant p, String key, Object value) { log.debug("participantStatusChange: " + p.getName() + " status: " + key + " value: " + value.toString()); if (key.equals("presenter")) { p.getStatus().setPresenter((Boolean) value); for (OnParticipantStatusChangeListener l : handler.getContext().getParticipantStatusChangeListeners()) l.onChangePresenter(p); } else if (key.equals("hasStream")) { p.getStatus().setHasStream(value); for (OnParticipantStatusChangeListener l : handler.getContext().getParticipantStatusChangeListeners()) l.onChangeHasStream(p); } else if (key.equals("streamName")) { p.getStatus().setStreamName((String) value); } else if (key.equals("raiseHand")) { p.getStatus().setRaiseHand((Boolean) value); for (OnParticipantStatusChangeListener l : handler.getContext().getParticipantStatusChangeListeners()) l.onChangeRaiseHand(p); } } public void raiseHand(String userId, boolean value) { Command cmd = new CommandAmf0("participants.setParticipantStatus", null, userId, "raiseHand", value); handler.writeCommandExpectingResult(channel, cmd); } public void assignPresenter(String userId) { // as it's implemented on bigbluebutton-client/src/org/bigbluebutton/modules/present/business/PresentSOService.as:353 Participant p = participants.get(userId); if (p == null) { log.warn("Inconsistent state here"); return; } if (joinServiceVersion.equals(ApplicationService.VERSION_0_7)) { Command cmd = new CommandAmf0("presentation.assignPresenter", null, userId, p.getName(), 1); handler.writeCommandExpectingResult(channel, cmd); } else { //if (joinServiceVersion == JoinService0Dot8.class) Command cmd = new CommandAmf0("participants.assignPresenter", null, userId, p.getName(), 1); handler.writeCommandExpectingResult(channel, cmd); } } public void addStream(String streamName) { if (joinServiceVersion.equals(ApplicationService.VERSION_0_7)) { Command cmd = new CommandAmf0("participants.setParticipantStatus", null, handler.getContext().getMyUserId(), "streamName", streamName); handler.writeCommandExpectingResult(channel, cmd); cmd = new CommandAmf0("participants.setParticipantStatus", null, handler.getContext().getMyUserId(), "hasStream", true); handler.writeCommandExpectingResult(channel, cmd); } else { //if (joinServiceVersion == JoinService0Dot8.class) Command cmd = new CommandAmf0("participants.setParticipantStatus", null, handler.getContext().getMyUserId(), "hasStream", "true,stream=" + streamName); handler.writeCommandExpectingResult(channel, cmd); } } public void removeStream(String streamName) { if (joinServiceVersion.equals(ApplicationService.VERSION_0_7)) { Command cmd = new CommandAmf0("participants.setParticipantStatus", null, handler.getContext().getMyUserId(), ""); handler.writeCommandExpectingResult(channel, cmd); cmd = new CommandAmf0("participants.setParticipantStatus", null, handler.getContext().getMyUserId(), "hasStream", false); handler.writeCommandExpectingResult(channel, cmd); } else { //if (joinServiceVersion == JoinService0Dot8.class) { Command cmd = new CommandAmf0("participants.setParticipantStatus", null, handler.getContext().getMyUserId(), "hasStream", "false,stream=" + streamName); handler.writeCommandExpectingResult(channel, cmd); } } public void kickUser(String userId) { if (handler.getContext().getMyself().isModerator()) { List<Object> list = new ArrayList<Object>(); list.add(userId); participantsSO.sendMessage("kickUserCallback", list); } } @Override public boolean onCommand(String resultFor, Command command) { if (onQueryParticipants(resultFor, command)) { handler.getContext().createChatModule(handler, channel); handler.getContext().createListenersModule(handler, channel); handler.getContext().createPresentModule(handler, channel); return true; } else return false; } public int getModeratorCount() { return moderatorCount; } public int getParticipantCount() { return participantCount; } }