//BigBlueButtonBot, GT-MCONF @PRAV-UFRGS, developed by Arthur C. Rauter, august 2011.
//Adding video to the bots through Xuggler library, September 2011.
package org.mconf.bbb.bot;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.mconf.bbb.BigBlueButtonClient;
import org.mconf.bbb.api.JoinServiceBase;
import org.mconf.bbb.api.Meeting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
public class BotLauncher {
private static final Logger log = LoggerFactory.getLogger(BotLauncher.class);
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("numbots: ");
builder.append(numbots);
builder.append("\nserver: ");
builder.append(server);
builder.append("\nsecurityKey: ");
builder.append(securityKey);
builder.append("\nmeeting: ");
builder.append(meeting);
builder.append("\nvideoFilename: ");
builder.append(videoFilename);
builder.append("\nvoiceFilename: ");
builder.append(voiceFilename);
builder.append("\nget_meetings: ");
builder.append(get_meetings);
builder.append("\ncommand_create: ");
builder.append(command_create);
builder.append("\nname: ");
builder.append(name);
builder.append("\nrole: ");
builder.append(role);
builder.append("\nprobabilities: ");
builder.append(probabilities);
builder.append("\ninterval: ");
builder.append(interval);
builder.append("\nsingle_meeting: ");
builder.append(single_meeting);
builder.append("\neveryone_sends_video: ");
builder.append(everyone_sends_video);
builder.append("\nonly_one_sends_video: ");
builder.append(only_one_sends_video);
builder.append("\neveryone_receives_video: ");
builder.append(everyone_receives_video);
builder.append("\neveryone_sends_audio: ");
builder.append(everyone_sends_audio);
builder.append("\nonly_one_sends_audio: ");
builder.append(only_one_sends_audio);
builder.append("\neveryone_receives_audio: ");
builder.append(everyone_receives_audio);
builder.append("\nrecord_audio: ");
builder.append(record_audio);
builder.append("\naudio_sample_size: ");
builder.append(audio_sample_size);
builder.append("\nnumber_of_audio_samples: ");
builder.append(number_of_audio_samples);
builder.append("\nfill_last_room: ");
builder.append(fill_last_room);
builder.append("\nprint_rooms_info: ");
builder.append(print_rooms_info);
builder.append("\nfinish_spawn_bots_thread: ");
builder.append(finish_spawn_bots_thread);
builder.append("\nfinished_spawn_bots_thread: ");
builder.append(finished_spawn_bots_thread);
builder.append("\nbotArmy: ");
builder.append(botArmy);
builder.append("\nprob_acc: ");
builder.append(prob_acc);
builder.append("\nmeetings: ");
builder.append(meetings);
builder.append("\nprintNumberOfParticipants: ");
builder.append(printNumberOfParticipants);
return builder.toString();
}
@Parameter(names = "--numbots", description = "Number of bots")
private int numbots = 0;
@Parameter(names = "--server", description = "Server address", required = true)
private String server;
@Parameter(names = "--key", description = "Server security key", required = true)
private String securityKey;
@Parameter(names = "--meeting", description = "Meeting ID to spawn the bots")
private String meeting = "Test meeting";
@Parameter(names = "--video", description = "Video filename to be sent")
private String videoFilename = null;
@Parameter(names = "--audio", description = "Audio filename to be sent")
private String voiceFilename = null;
@Parameter(names = "--get_meetings", description = "Displays the open rooms")
private boolean get_meetings = false;
@Parameter(names = "--create", arity = 1, description = "If the meeting specified by --meeting doesn't exists, the bot will create it before join", validateWith = BooleanValidator.class)
private boolean command_create = true;
@Parameter(names = "--name", description = "Prefix of the bots followed by a number")
private String name = "Bot";
@Parameter(names = "--role", description = "Role of the bots in the conference (moderator|viewer)", validateWith = RoleValidator.class)
private String role = "moderator";
@Parameter(names = "--probabilities", description = "Specifies the probabilities for number of users per meeting", validateWith = ProbabilitiesValidator.class, converter = ProbabilitiesConverter.class)
private Map<Integer, Double> probabilities;
@Parameter(names = "--interval", description = "Interval between the launch of each bot (in milliseconds)")
private int interval = 5000;
@Parameter(names = "--single_meeting", arity = 1, description = "If set, all the bots will join the same room specified by --meeting", validateWith = BooleanValidator.class)
private boolean single_meeting = false;
@Parameter(names = "--everyone_sends_video", arity = 1, description = "If [true], all the bots will send the video file specified by --video", validateWith = BooleanValidator.class)
private boolean everyone_sends_video = false;
@Parameter(names = "--only_one_sends_video", arity = 1, description = "If [true], only one bot will send the video file specified by --video", validateWith = BooleanValidator.class)
private boolean only_one_sends_video = true;
@Parameter(names = "--everyone_receives_video", arity = 1, description = "If [true], all the bots will receive the video stream from the others", validateWith = BooleanValidator.class)
private boolean everyone_receives_video = true;
@Parameter(names = "--everyone_sends_audio", arity = 1, description = "If [true], all the bots will send the audio file specified by --audio", validateWith = BooleanValidator.class)
private boolean everyone_sends_audio = false;
@Parameter(names = "--only_one_sends_audio", arity = 1, description = "If [true], only one bot will send the audio file specified by --audio", validateWith = BooleanValidator.class)
private boolean only_one_sends_audio = true;
@Parameter(names = "--everyone_receives_audio", arity = 1, description = "If [true], all the bots will receive the audio stream from the others", validateWith = BooleanValidator.class)
private boolean everyone_receives_audio = true;
@Parameter(names = "--record_audio", arity = 1, description = "If [true], only one bot will be launched to record the audio of a meeting", validateWith = BooleanValidator.class)
private boolean record_audio = false;
@Parameter(names = "--audio_sample_size", description = "Size of the audio sample to record (in milliseconds)")
private int audio_sample_size = 10000;
@Parameter(names = "--number_of_audio_samples", description = "Number of samples to record by the bot")
private int number_of_audio_samples = 1;
@Parameter(names = "--fill_last_room", arity = 1, description = "If [true], the last room will always be filled independently of the number of bots", validateWith = BooleanValidator.class)
private boolean fill_last_room = false;
@Parameter(names = "--print_rooms_info", arity = 1, description = "If [true], the information about number of participants per room will be printed to stdout", validateWith = BooleanValidator.class)
private boolean print_rooms_info = false;
private boolean finish_spawn_bots_thread = false;
private boolean finished_spawn_bots_thread = false;
private List<Bot> botArmy = new ArrayList<Bot>();
private HashMap<Integer, Double> prob_acc;
private List<Meeting> meetings;
private Thread printNumberOfParticipants;
private boolean parse(String[] args) throws IOException {
JCommander parser = new JCommander(this);
try {
parser.parse(args);
} catch (Exception e) {
System.err.println(e.getMessage());
parser.usage();
return false;
}
// set the default probabilities
if (probabilities == null) {
probabilities = new HashMap<Integer, Double>();
probabilities.put(2, 58.68);
probabilities.put(3, 20.82);
probabilities.put(4, 10.14);
probabilities.put(5, 4.56);
probabilities.put(6, 2.74);
probabilities.put(7, 1.21);
probabilities.put(8, 0.8);
probabilities.put(9, 1.05);
}
double acc = 0.0;
prob_acc = new HashMap<Integer, Double>();
for (Entry<Integer, Double> entry : probabilities.entrySet()) {
acc += entry.getValue();
prob_acc.put(entry.getKey(), acc);
}
log.debug("Accumulated probabilities: {}", prob_acc.toString());
role = role.toLowerCase();
if (record_audio) {
numbots = 1;
command_create = false;
single_meeting = true;
only_one_sends_video = false;
only_one_sends_audio = false;
everyone_sends_video = false;
everyone_sends_audio = false;
everyone_receives_video = false;
everyone_receives_audio = true;
}
BigBlueButtonClient client = new BigBlueButtonClient();
client.createJoinService(server, securityKey);
JoinServiceBase joinService = client.getJoinService();
if (joinService == null) {
log.error("Can't connect to the server, please check the server address");
return false;
}
if (joinService.load() != JoinServiceBase.E_OK) {
log.error("Can't load the join service");
return false;
}
meetings = client.getJoinService().getMeetings();
if (get_meetings) {
if (meetings.isEmpty())
log.info("No open meetings");
else {
log.info("Open meetings:");
for (Meeting meeting : meetings) {
log.info(meeting.toString());
}
}
return false;
}
return true;
}
public void spawnBots() throws InterruptedException, IOException {
if (numbots <= 0) {
log.info("Number of bots <= 0, quitting");
return;
}
Thread printNumberOfParticipants = new Thread(new Runnable() {
@Override
public void run() {
BigBlueButtonClient client = new BigBlueButtonClient();
client.createJoinService(server, securityKey);
JoinServiceBase joinService = client.getJoinService();
if (joinService == null) {
log.error("Can't connect to the server, please check the server address");
return;
}
while (true) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
break;
}
if (joinService.load() != JoinServiceBase.E_OK) {
log.error("Can't load the join service");
continue;
}
int count = 0;
for (Meeting m : joinService.getMeetings()) {
int numParticipants = m.getAttendees().size();
log.info("Participants on meeting \"{}\": {}", m.getMeetingID(), numParticipants);
count += numParticipants;
}
log.info("Total number of participants: {}", count);
}
}
});
if (print_rooms_info)
printNumberOfParticipants.start();
final Thread spawn_bots_thread = new Thread(new Runnable() {
@Override
public void run() {
finished_spawn_bots_thread = false;
FlvPreLoader loader = null;
if (videoFilename != null)
loader = new FlvPreLoader(videoFilename);
log.info("Running with the following configuration:\n{}", BotLauncher.this.toString());
Random seed = new Random();
DecimalFormat name_format = new DecimalFormat(new String(new char[Integer.toString(numbots).length()]).replace("\0", "0"));
DecimalFormat meeting_format = new DecimalFormat(new String(new char[3]).replace("\0", "0"));
int meeting_index = 0;
// it will search for the meeting index considering the opened meetings
Pattern pattern = Pattern.compile(meeting + " [0]*(\\d+)");
for (Meeting meeting : meetings) {
if (meeting.getParticipantCount() <= 0)
continue;
Matcher m = pattern.matcher(meeting.getMeetingID());
if (m.matches()) {
int candidate = Integer.parseInt(m.group(1));
if (candidate > meeting_index)
meeting_index = candidate;
}
}
int remaining_bots = 0;
boolean first_in_the_room = true;
int bot_index = 1;
while ((bot_index <= numbots || (fill_last_room && remaining_bots != 0)) && !finish_spawn_bots_thread) {
String instance_meeting;
if (single_meeting) {
instance_meeting = meeting;
} else {
if (remaining_bots == 0) {
meeting_index += 1;
while (remaining_bots == 0) {
double p = seed.nextDouble() * 100;
for (Entry<Integer, Double> entry : prob_acc.entrySet()) {
if (p < entry.getValue()) {
remaining_bots = entry.getKey();
log.debug("p = {}", p);
break;
}
}
}
log.info("The next room will have {} participants", remaining_bots);
first_in_the_room = true;
}
instance_meeting = meeting + " " + meeting_format.format(meeting_index);
remaining_bots -= 1;
}
Bot bot = new Bot();
botArmy.add(bot);
String instance_name = name + " " + name_format.format(bot_index);
log.info("Connecting a new bot called {} to the room {}", instance_name, instance_meeting);
bot.setServer(server);
bot.setSecurityKey(securityKey);
bot.setMeetingId(instance_meeting);
bot.setName(instance_name);
bot.setRole(role);
bot.setVideoFilename(videoFilename);
bot.setAudioFilename(voiceFilename);
bot.setSendVideo(everyone_sends_video || (only_one_sends_video && first_in_the_room));
bot.setReceiveVideo(everyone_receives_video);
bot.setSendAudio(everyone_sends_audio || (only_one_sends_audio && first_in_the_room));
bot.setReceiveAudio(everyone_receives_audio);
bot.setRecordAudio(record_audio);
bot.setAudioSampleSize(audio_sample_size);
bot.setNumberOfAudioSamples(number_of_audio_samples);
bot.setCreateMeeting(command_create && first_in_the_room);
bot.setVideoLoader(loader);
bot.start();
if (interval > 0)
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
first_in_the_room = false;
bot_index++;
}
finished_spawn_bots_thread = true;
}
});
// Runtime.getRuntime().addShutdownHook(new Thread() {
// @Override
// public void run() {
// log.info("Finalizing bots...");
// finalize_spawn_bots_thread = true;
// try {
// spawn_bots_thread.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// for (Bot bot : botArmy) {
// bot.disconnect();
// }
// log.info("Disconnected");
// botArmy.clear();
// }
// });
spawn_bots_thread.start();
System.in.read();
if (!finished_spawn_bots_thread) {
log.info("Stopping to spawn bots...");
finish_spawn_bots_thread = true;
spawn_bots_thread.join();
log.info("New bots won't join anymore...");
} else {
log.info("All bots already joined meetings...");
}
log.info("Press ENTER again to disconnect everybody...");
System.in.read();
for (Bot bot : botArmy) {
bot.disconnect();
}
log.info("Disconnected!");
botArmy.clear();
printNumberOfParticipants.interrupt();
try {
printNumberOfParticipants.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main (String[] args) throws IOException, InterruptedException {
BotLauncher master = new BotLauncher();
if (master.parse(args)) {
master.spawnBots();
}
}
}