/*
* 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;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.MessageEvent;
import org.mconf.bbb.BigBlueButtonClient.OnConnectedListener;
import org.mconf.bbb.BigBlueButtonClient.OnDisconnectedListener;
import org.mconf.bbb.api.ApplicationService;
import org.mconf.bbb.api.JoinedMeeting;
import org.red5.server.so.SharedObjectMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.flazr.rtmp.RtmpDecoder;
import com.flazr.rtmp.RtmpEncoder;
import com.flazr.rtmp.RtmpMessage;
import com.flazr.rtmp.client.ClientHandshakeHandler;
import com.flazr.rtmp.client.ClientOptions;
import com.flazr.rtmp.message.Command;
import com.flazr.rtmp.message.CommandAmf0;
import com.flazr.rtmp.message.Control;
/*
* - what happens when a client join a session
* getMyUserId
* participantsSO
* participants.getParticipants
* meetMeUsersSO
* voice.getMeetMeUsers
* voice.isRoomMuted
* presentationSO
* presentation.getPresentationInfo
* presentation.assignPresenter
* breakoutSO
* drawSO
* deskSO
* deskshare.checkIfStreamIsPublishing
* chat.getChatMessages
*
* - web client module division
* breakout
* chat
* deskshare
* example
* listeners
* phone
* present
* videoconf
* viewers
* whiteboard
*/
public class MainRtmpConnection extends RtmpConnection {
private static final Logger log = LoggerFactory.getLogger(MainRtmpConnection.class);
private boolean connected = false;
public MainRtmpConnection(ClientOptions options, BigBlueButtonClient context) {
super(options, context);
}
@Override
protected ChannelPipelineFactory pipelineFactory() {
return new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() throws Exception {
final ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("handshaker", new ClientHandshakeHandler(options));
pipeline.addLast("decoder", new RtmpDecoder());
pipeline.addLast("encoder", new RtmpEncoder());
pipeline.addLast("handler", MainRtmpConnection.this);
return pipeline;
}
};
}
@Override
protected void onConnectedUnsuccessfully() {
for (OnConnectedListener listener : context.getConnectedListeners())
listener.onConnectedUnsuccessfully();
}
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
/*
* https://github.com/bigbluebutton/bigbluebutton/blob/master/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as#L102
* _netConnection.connect(?);
*/
JoinedMeeting meeting = context.getJoinService().getJoinedMeeting();
options.setArgs((Object[]) null);
List<Object> args = new ArrayList<Object>();
args.add(meeting.getFullname());
args.add(meeting.getRole());
args.add(meeting.getConference());
if (context.getJoinService().getApplicationService().getVersion().equals(ApplicationService.VERSION_0_7))
args.add(meeting.getMode());
//args.add(meeting.getRoom());
args.add(meeting.getVoicebridge());
args.add(meeting.doRecord());
args.add(meeting.getExternUserID());
args.add(meeting.getInternalUserID());
if (meeting.isGuestDefined())
args.add(meeting.isGuest());
args.add(false);
args.add(false);
args.add(false);
options.setArgs(args.toArray());
writeCommandExpectingResult(e.getChannel(), Command.connect(options));
}
@Override
public void channelDisconnected(ChannelHandlerContext ctx,
ChannelStateEvent e) throws Exception {
super.channelDisconnected(ctx, e);
log.debug("Rtmp Channel Disconnected");
connected = false;
for (OnDisconnectedListener l : context.getDisconnectedListeners())
l.onDisconnected();
}
@SuppressWarnings("unchecked")
public String connectGetCode(Command command) {
return ((Map<String, Object>) command.getArg(0)).get("code").toString();
}
public void doGetMyUserId(Channel channel) {
Command command = new CommandAmf0("getMyUserId", null);
writeCommandExpectingResult(channel, command);
}
public boolean onGetMyUserId(String resultFor, Command command) {
if (resultFor.equals("getMyUserId")) {
context.setMyUserId((String) command.getArg(0));
connected = true;
for (OnConnectedListener l : context.getConnectedListeners())
l.onConnectedSuccessfully();
return true;
} else
return false;
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent me) {
final Channel channel = me.getChannel();
final RtmpMessage message = (RtmpMessage) me.getMessage();
switch(message.getHeader().getMessageType()) {
case CONTROL:
Control control = (Control) message;
switch(control.getType()) {
case PING_REQUEST:
final int time = control.getTime();
Control pong = Control.pingResponse(time);
channel.write(pong);
break;
}
break;
case COMMAND_AMF0:
case COMMAND_AMF3:
Command command = (Command) message;
String name = command.getName();
log.debug("server command: {}", name);
if(name.equals("_result")) {
String resultFor = transactionToCommandMap.get(command.getTransactionId());
if (resultFor == null) {
log.warn("result for method without tracked transaction");
break;
}
log.info("result for method call: {}", resultFor);
if(resultFor.equals("connect")) {
String code = connectGetCode(command);
if (code.equals("NetConnection.Connect.Success"))
doGetMyUserId(channel);
else {
log.error("method connect result in {}, quitting", code);
log.debug("connect response: {}", command.toString());
channel.close();
}
return;
} else if (onGetMyUserId(resultFor, command)) {
context.createUsersModule(this, channel);
break;
}
context.onCommand(resultFor, command);
break;
}
break;
case SHARED_OBJECT_AMF0:
case SHARED_OBJECT_AMF3:
onSharedObject(channel, (SharedObjectMessage) message);
break;
default:
log.info("ignoring rtmp message: {}", message);
break;
}
}
public boolean isConnected() {
return connected;
}
}