package org.bigbluebutton.freeswitch.voice.freeswitch; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.bigbluebutton.freeswitch.voice.events.ConferenceEventListener; import org.bigbluebutton.freeswitch.voice.events.DeskShareEndedEvent; import org.bigbluebutton.freeswitch.voice.events.DeskShareStartedEvent; import org.bigbluebutton.freeswitch.voice.events.DeskShareRTMPBroadcastEvent; import org.bigbluebutton.freeswitch.voice.events.VoiceConferenceEvent; import org.bigbluebutton.freeswitch.voice.events.VoiceStartRecordingEvent; import org.bigbluebutton.freeswitch.voice.events.VoiceUserJoinedEvent; import org.bigbluebutton.freeswitch.voice.events.VoiceUserLeftEvent; import org.bigbluebutton.freeswitch.voice.events.VoiceUserMutedEvent; import org.bigbluebutton.freeswitch.voice.events.VoiceUserTalkingEvent; import org.freeswitch.esl.client.IEslEventListener; import org.freeswitch.esl.client.transport.event.EslEvent; import org.jboss.netty.channel.ExceptionEvent; public class ESLEventListener implements IEslEventListener { private static final String START_TALKING_EVENT = "start-talking"; private static final String STOP_TALKING_EVENT = "stop-talking"; private static final String START_RECORDING_EVENT = "start-recording"; private static final String STOP_RECORDING_EVENT = "stop-recording"; private static final String DESKSHARE_CONFERENCE_NAME_SUFFIX = "-DESKSHARE"; private final ConferenceEventListener conferenceEventListener; public ESLEventListener(ConferenceEventListener conferenceEventListener) { this.conferenceEventListener = conferenceEventListener; } @Override public void conferenceEventPlayFile(String uniqueId, String confName, int confSize, EslEvent event) { //Ignored, Noop } @Override public void backgroundJobResultReceived(EslEvent event) { System.out.println( "Background job result received [" + event + "]"); } @Override public void exceptionCaught(ExceptionEvent e) { // setChanged(); // notifyObservers(e); } private static final Pattern GLOBAL_AUDION_PATTERN = Pattern.compile("(GLOBAL_AUDIO)_(.*)$"); private static final Pattern CALLERNAME_PATTERN = Pattern.compile("(.*)-bbbID-(.*)$"); @Override public void conferenceEventJoin(String uniqueId, String confName, int confSize, EslEvent event) { Integer memberId = this.getMemberIdFromEvent(event); Map<String, String> headers = event.getEventHeaders(); String callerId = this.getCallerIdFromEvent(event); String callerIdName = this.getCallerIdNameFromEvent(event); boolean muted = headers.get("Speak").equals("true") ? false : true; //Was inverted which was causing a State issue boolean speaking = headers.get("Talking").equals("true") ? true : false; String voiceUserId = callerIdName; Matcher gapMatcher = GLOBAL_AUDION_PATTERN.matcher(callerIdName); if (gapMatcher.matches()) { System.out.println("Ignoring GLOBAL AUDIO USER [" + callerIdName + "]"); return; } // (WebRTC) Deskstop sharing conferences' name is of the form ddddd-DESKSHARE // Voice conferences' name is of the form ddddd if (confName.endsWith(DESKSHARE_CONFERENCE_NAME_SUFFIX)) { System.out.println("User joined deskshare conference, user=[" + callerIdName + "], " + "conf=[" + confName + "] callerId=[" + callerId + "]"); DeskShareStartedEvent dsStart = new DeskShareStartedEvent(confName, callerId, callerIdName); conferenceEventListener.handleConferenceEvent(dsStart); } else { Matcher matcher = CALLERNAME_PATTERN.matcher(callerIdName); if (matcher.matches()) { voiceUserId = matcher.group(1).trim(); callerIdName = matcher.group(2).trim(); } System.out.println("User joined voice conference, user=[" + callerIdName + "], conf=[" + confName + "] callerId=[" + callerId + "]"); VoiceUserJoinedEvent pj = new VoiceUserJoinedEvent(voiceUserId, memberId.toString(), confName, callerId, callerIdName, muted, speaking, "none"); conferenceEventListener.handleConferenceEvent(pj); } } @Override public void conferenceEventLeave(String uniqueId, String confName, int confSize, EslEvent event) { Integer memberId = this.getMemberIdFromEvent(event); String callerId = this.getCallerIdFromEvent(event); String callerIdName = this.getCallerIdNameFromEvent(event); // (WebRTC) Deskstop sharing conferences' name is of the form ddddd-DESKSHARE // Voice conferences' name is of the form ddddd if (confName.endsWith(DESKSHARE_CONFERENCE_NAME_SUFFIX)) { System.out.println("User left deskshare conference, user=[" + memberId.toString() + "], " + "conf=[" + confName + "]"); DeskShareEndedEvent dsEnd = new DeskShareEndedEvent(confName, callerId, callerIdName); conferenceEventListener.handleConferenceEvent(dsEnd); } else { System.out.println("User left voice conference, user=[" + memberId.toString() + "], " + "conf=[" + confName + "]"); VoiceUserLeftEvent pl = new VoiceUserLeftEvent(memberId.toString(), confName); conferenceEventListener.handleConferenceEvent(pl); } } @Override public void conferenceEventMute(String uniqueId, String confName, int confSize, EslEvent event) { Integer memberId = this.getMemberIdFromEvent(event); System.out.println("******************** Received Conference Muted Event from FreeSWITCH user[" + memberId.toString() + "]"); System.out.println("User muted voice conference, user=[" + memberId.toString() + "], conf=[" + confName + "]"); VoiceUserMutedEvent pm = new VoiceUserMutedEvent(memberId.toString(), confName, true); conferenceEventListener.handleConferenceEvent(pm); } @Override public void conferenceEventUnMute(String uniqueId, String confName, int confSize, EslEvent event) { Integer memberId = this.getMemberIdFromEvent(event); System.out.println("******************** Received ConferenceUnmuted Event from FreeSWITCH user[" + memberId.toString() + "]"); System.out.println("User unmuted voice conference, user=[" + memberId.toString() + "], conf=[" + confName + "]"); VoiceUserMutedEvent pm = new VoiceUserMutedEvent(memberId.toString(), confName, false); conferenceEventListener.handleConferenceEvent(pm); } @Override public void conferenceEventAction(String uniqueId, String confName, int confSize, String action, EslEvent event) { Integer memberId = this.getMemberIdFromEvent(event); VoiceUserTalkingEvent pt; System.out.println("******************** Receive conference Action [" + action + "]"); if (action == null) { return; } if (action.equals(START_TALKING_EVENT)) { pt = new VoiceUserTalkingEvent(memberId.toString(), confName, true); conferenceEventListener.handleConferenceEvent(pt); } else if (action.equals(STOP_TALKING_EVENT)) { pt = new VoiceUserTalkingEvent(memberId.toString(), confName, false); conferenceEventListener.handleConferenceEvent(pt); } else { System.out.println("Unknown conference Action [" + action + "]"); } } @Override public void conferenceEventTransfer(String uniqueId, String confName, int confSize, EslEvent event) { //Ignored, Noop } @Override public void conferenceEventThreadRun(String uniqueId, String confName, int confSize, EslEvent event) { } //@Override public void conferenceEventRecord(String uniqueId, String confName, int confSize, EslEvent event) { String action = event.getEventHeaders().get("Action"); if(action == null) { return; } if (action.equals(START_RECORDING_EVENT)) { if (confName.endsWith(DESKSHARE_CONFERENCE_NAME_SUFFIX)){ if (isRTMPStream(event)) { DeskShareRTMPBroadcastEvent rtmp = new DeskShareRTMPBroadcastEvent(confName, true); rtmp.setBroadcastingStreamUrl(getStreamUrl(event)); rtmp.setVideoHeight(Integer.parseInt(getBroadcastParameter(event, "vh"))); rtmp.setVideoWidth(Integer.parseInt(getBroadcastParameter(event, "vw"))); rtmp.setTimestamp(genTimestamp().toString()); System.out.println("DeskShare conference broadcast started. url=[" + getStreamUrl(event) + "], conf=[" + confName + "]"); conferenceEventListener.handleConferenceEvent(rtmp); } } else { VoiceStartRecordingEvent sre = new VoiceStartRecordingEvent(confName, true); sre.setRecordingFilename(getRecordFilenameFromEvent(event)); sre.setTimestamp(genTimestamp().toString()); System.out.println("Voice conference recording started. file=[" + getRecordFilenameFromEvent(event) + "], conf=[" + confName + "]"); conferenceEventListener.handleConferenceEvent(sre); } } else if (action.equals(STOP_RECORDING_EVENT)) { if (confName.endsWith(DESKSHARE_CONFERENCE_NAME_SUFFIX)){ if (isRTMPStream(event)) { DeskShareRTMPBroadcastEvent rtmp = new DeskShareRTMPBroadcastEvent(confName, false); rtmp.setBroadcastingStreamUrl(getStreamUrl(event)); rtmp.setVideoHeight(Integer.parseInt(getBroadcastParameter(event, "vh"))); rtmp.setVideoWidth(Integer.parseInt(getBroadcastParameter(event, "vw"))); rtmp.setTimestamp(genTimestamp().toString()); System.out.println("DeskShare conference broadcast stopped. url=[" + getStreamUrl(event) + "], conf=[" + confName + "]"); conferenceEventListener.handleConferenceEvent(rtmp); } } else { VoiceStartRecordingEvent sre = new VoiceStartRecordingEvent(confName, false); sre.setRecordingFilename(getRecordFilenameFromEvent(event)); sre.setTimestamp(genTimestamp().toString()); System.out.println("Voice conference recording stopped. file=[" + getRecordFilenameFromEvent(event) + "], conf=[" + confName + "]"); conferenceEventListener.handleConferenceEvent(sre); } } else { System.out.println("Processing UNKNOWN conference Action " + action + "]"); } } private Long genTimestamp() { return TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); } @Override public void eventReceived(EslEvent event) { System.out.println("ESL Event Listener received event=[" + event.getEventName() + "]" + event.getEventHeaders().toString()); // if (event.getEventName().equals(FreeswitchHeartbeatMonitor.EVENT_HEARTBEAT)) { //// setChanged(); // notifyObservers(event); // return; // } } private Integer getMemberIdFromEvent(EslEvent e) { return new Integer(e.getEventHeaders().get("Member-ID")); } private String getCallerIdFromEvent(EslEvent e) { return e.getEventHeaders().get("Caller-Caller-ID-Number"); } private String getCallerIdNameFromEvent(EslEvent e) { return e.getEventHeaders().get("Caller-Caller-ID-Name"); } private String getRecordFilenameFromEvent(EslEvent e) { return e.getEventHeaders().get("Path"); } // Distinguish between recording to a file: // /path/to/a/file.mp4 // and broadcasting a stream: // {channels=2,samplerate=48000,vw=1920,vh=1080,fps=15.00}rtmp://192.168.0.109/live/abc/dev-test private Boolean isRTMPStream(EslEvent e) { String path = e.getEventHeaders().get("Path"); if (path.contains("rtmp") && path.contains("channels") && path.contains("samplerate") && path.contains("vw") && path.contains("vh") && path.contains("fps")) { return true; } else { return false; } } // returns a String so that we can parse to an int or double depending on the param private String getBroadcastParameter(EslEvent e, String param) { String path = e.getEventHeaders().get("Path"); if (isRTMPStream(e)) { String temp = path.substring(path.indexOf("{") + 1, path.indexOf("}")); String[] arr = temp.split(","); for (int i = 0; i < 5; i++) { if (arr[i].startsWith(param)) { return arr[i].substring(arr[i].indexOf('=') + 1); } } return "0"; } else { return "0"; } } // Obtain the rtmp url from the event (if any): private String getStreamUrl(EslEvent e) { String path = e.getEventHeaders().get("Path"); if (isRTMPStream(e)){ return path.substring(path.lastIndexOf("}") + 1); } else { return ""; } } }