package org.mconf.bbb.bot; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.mconf.bbb.BigBlueButtonClient; import org.mconf.bbb.BigBlueButtonClient.OnConnectedListener; import org.mconf.bbb.BigBlueButtonClient.OnDisconnectedListener; import org.mconf.bbb.BigBlueButtonClient.OnParticipantJoinedListener; import org.mconf.bbb.BigBlueButtonClient.OnParticipantLeftListener; import org.mconf.bbb.BigBlueButtonClient.OnParticipantStatusChangeListener; import org.mconf.bbb.BigBlueButtonClient.OnPublicChatMessageListener; import org.mconf.bbb.api.JoinService0Dot8; import org.mconf.bbb.api.JoinServiceBase; import org.mconf.bbb.chat.ChatMessage; import org.mconf.bbb.deskshare.BbbDeskShareReceiver; import org.mconf.bbb.phone.BbbVoiceConnection; import org.mconf.bbb.users.IParticipant; import org.mconf.bbb.users.Participant; import org.mconf.bbb.video.BbbVideoPublisher; import org.mconf.bbb.video.BbbVideoReceiver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.flazr.io.flv.FlvReader; import com.flazr.rtmp.RtmpReader; import com.flazr.rtmp.RtmpWriter; import com.flazr.rtmp.message.Video; public class Bot extends BigBlueButtonClient implements OnParticipantJoinedListener, OnParticipantLeftListener, OnParticipantStatusChangeListener, OnConnectedListener, OnDisconnectedListener, OnPublicChatMessageListener { private static final Logger log = LoggerFactory.getLogger(Bot.class); public BbbVoiceConnection voiceConnection; private Map<String, BbbVideoReceiver> remoteVideos = new HashMap<String, BbbVideoReceiver>(); private BbbDeskShareReceiver deskShareReceiver; private String server; private String securityKey; private String meetingId; private String name; private boolean moderator; private String videoFilename; private String audioFilename; private boolean sendVideo; private boolean recvVideo; private boolean sendAudio; private boolean recvAudio; private boolean create; private FlvPreLoader videoLoader; private BbbVideoPublisher videoPublisher; private boolean disconnected_myself = false; private boolean chat_sent = false; private boolean recordAudio; private int audioSampleSize; private int numberOfAudioSamples; private void sendVideo() { RtmpReader reader = new GlobalFlvReader(videoLoader); String streamName = reader.getWidth() + "x" + reader.getHeight() + getMyUserId(); if (getJoinService().getClass() == JoinService0Dot8.class) streamName += "-" + new Date().getTime(); videoPublisher = new BbbVideoPublisher(this, reader, streamName); videoPublisher.setLoop(true); videoPublisher.start(); } private void connectDeskShare() { deskShareReceiver = new BbbDeskShareReceiver(this) { @Override protected void onVideo(Video video) { // it will log a message when it receives a video packet super.onVideo(video); } }; deskShareReceiver.start(); } private void connectVoice() { RtmpReader reader = null; RtmpWriter writer = null; if (audioFilename != null && audioFilename.length() > 0) { if (sendAudio) { try { reader = new FlvReader(audioFilename); } catch (Exception e) { e.printStackTrace(); log.error("Can't create a FlvReader instance for " + audioFilename); } } else if (recordAudio) { try { writer = new LimitedSizeFlvWriter(audioFilename, audioSampleSize); } catch (Exception e) { e.printStackTrace(); log.error("Can't create a FlvWriter instance for " + audioFilename); } } } voiceConnection = new BbbVoiceConnection(this, reader, writer) { private void reconnect() { try { Thread.sleep(3000); } catch (InterruptedException e) { // do nothing, just return return; } voiceConnection.start(); } @Override public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { super.channelDisconnected(ctx, e); if (!disconnected_myself) { log.error("{} has dropped from the voice conference, reconnecting to {}", name, getJoinService().getApplicationService().getServerUrl()); reconnect(); } } @Override protected void onConnectedUnsuccessfully() { if (!disconnected_myself) { log.error("The voice connection of {} to {} was unsucceeded, trying one more time", name, getJoinService().getApplicationService().getServerUrl()); reconnect(); } } }; voiceConnection.setLoop(true); voiceConnection.start(); } @Override public void onParticipantJoined(IParticipant p) { if (isMyself(p.getUserId())) { if (videoFilename != null && videoFilename.length() > 0 && sendVideo) sendVideo(); } else { if (p.getStatus().doesHaveStream() && recvVideo) startReceivingVideo(p.getUserId()); } } @Override public void onParticipantLeft(IParticipant p) { if (p.getStatus().doesHaveStream() && recvVideo) stopReceivingVideo(p.getUserId()); } @Override public void onChangePresenter(IParticipant p) { // TODO Auto-generated method stub } @Override public void onChangeHasStream(IParticipant p) { if (p.getUserId() == getMyUserId()) return; if (recvVideo) { if (p.getStatus().doesHaveStream()) { startReceivingVideo(p.getUserId()); } else { stopReceivingVideo(p.getUserId()); } } } private void startReceivingVideo(String userId) { BbbVideoReceiver videoReceiver = new BbbVideoReceiver(userId, this) { @Override protected void onVideo(Video video) { // it will log a message when it receives a video packet super.onVideo(video); } }; remoteVideos.put(userId, videoReceiver); videoReceiver.start(); } private void stopReceivingVideo(String userId) { BbbVideoReceiver videoReceiver = remoteVideos.get(userId); if (videoReceiver != null) { videoReceiver.stop(); remoteVideos.remove(userId); } } @Override public void onChangeRaiseHand(IParticipant p) { // TODO Auto-generated method stub } @Override public void onConnectedSuccessfully() { if (recvAudio || sendAudio) { connectVoice(); } connectDeskShare(); } @Override public void onConnectedUnsuccessfully() { log.error("The connection of {} to {} was unsucceeded", name, getJoinService().getApplicationService().getServerUrl()); } @Override public void onPublicChatMessage(ChatMessage message, IParticipant source) { // TODO Auto-generated method stub } @Override public void onPublicChatMessage(List<ChatMessage> publicChatMessages, Map<String, Participant> participants) { if (!chat_sent) { chat_sent = true; sendPublicChatMessage("Logged in on " + new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").format(Calendar.getInstance().getTime()).toString()); } } public void setServer(String server) { this.server = server; } public void setSecurityKey(String securityKey) { this.securityKey = securityKey; } public void setMeetingId(String meetingId) { this.meetingId = meetingId; } public void setName(String name) { this.name = name; } public void setRole(String role) { this.moderator = role.equals("moderator"); } public void setVideoFilename(String videoFilename) { this.videoFilename = videoFilename; } public void setAudioFilename(String audioFilename) { this.audioFilename = audioFilename; } public void setSendVideo(boolean sendVideo) { this.sendVideo = sendVideo; } public void setReceiveVideo(boolean recvVideo) { this.recvVideo = recvVideo; } public void setSendAudio(boolean sendAudio) { this.sendAudio = sendAudio; } public void setReceiveAudio(boolean recvAudio) { this.recvAudio = recvAudio; } public void start() { disconnected_myself = false; createJoinService(server, securityKey); JoinServiceBase joinService = getJoinService(); if (joinService == null) { log.error("Can't connect to the server, please check the server address"); return; } if (create && joinService.createMeeting(meetingId) != JoinServiceBase.E_OK) { log.error("Can't create the room {}, but I will continue on my task", meetingId); } if (joinService.load() != JoinServiceBase.E_OK) { log.error("Can't load the join service"); return; } if (joinService.join(meetingId, name, moderator) != JoinServiceBase.E_OK) { log.error("Can't join the room {}", meetingId); return; } if (joinService.getJoinedMeeting() != null) { addParticipantJoinedListener(this); addParticipantStatusChangeListener(this); addConnectedListener(this); addDisconnectedListener(this); addPublicChatMessageListener(this); if (!connectBigBlueButton()) { log.error("Failed to connect to BigBlueButton"); return; } } else { log.error(name + " failed to join the meeting"); return; } } private void stop() { removeParticipantJoinedListener(this); removeParticipantStatusChangeListener(this); removeConnectedListener(this); removeDisconnectedListener(this); removePublicChatMessageListener(this); for (BbbVideoReceiver receiver : remoteVideos.values()) receiver.stop(); remoteVideos.clear(); if (deskShareReceiver != null) deskShareReceiver.stop(); if (videoPublisher != null) videoPublisher.stop(); if (voiceConnection != null) voiceConnection.stop(); } public void setCreateMeeting(boolean create) { this.create = create; } public void setVideoLoader(FlvPreLoader loader) { this.videoLoader = loader; } @Override public void disconnect() { disconnected_myself = true; stop(); super.disconnect(); } @Override public void onDisconnected() { if (!disconnected_myself) { log.error("{} has been disconnected, reconnecting", name); stop(); start(); } } public void setRecordAudio(boolean record_audio) { this.recordAudio = record_audio; } public void setAudioSampleSize(int audio_sample_size) { this.audioSampleSize = audio_sample_size; } public void setNumberOfAudioSamples(int number_of_audio_samples) { this.numberOfAudioSamples = number_of_audio_samples; } }