/*
* Copyright 2007 Sun Microsystems, Inc.
*
* This file is part of jVoiceBridge.
*
* jVoiceBridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation and distributed hereunder
* to you.
*
* jVoiceBridge 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the License file that accompanied this
* code.
*/
package com.sun.voip.server;
import com.sun.voip.CallParticipant;
import com.sun.voip.CallEvent;
import com.sun.voip.Logger;
import com.sun.voip.MediaInfo;
import com.sun.voip.MixDataSource;
import com.sun.voip.RtcpReceiver;
import com.sun.voip.RtpPacket;
import com.sun.voip.RtpSocket;
import com.sun.voip.SdpManager;
import com.sun.voip.TreatmentDoneListener;
import com.sun.voip.TreatmentManager;
import com.sun.voip.Util;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.channels.DatagramChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
import java.text.ParseException;
/**
* Receive RTP data for this ConferenceMember, add it to the mix
* and keep statistics.
*/
public class ConferenceMember implements TreatmentDoneListener,
MixDataSource, JoinConfirmationListener {
private ConferenceManager conferenceManager;
private CallParticipant cp;
private WGManager wgManager;
private CallHandler callHandler;
private MemberSender memberSender;
private MemberReceiver memberReceiver;
private MediaInfo myMediaInfo;
private MixManager mixManager;
private ArrayList whisperGroups;
private boolean done = false;
private String timeStarted;
private static Integer statisticsLock = new Integer(0);
private boolean initializationDone = false;
private boolean migrating;
private WhisperGroup initialWhisperGroup;
private WhisperGroup conferenceWhisperGroup;
private WhisperGroup whisperGroup;
private boolean traceCall;
private boolean joinedDistributedConference;
private static int firstRtpPort;
private static int lastRtpPort;
private DatagramChannel datagramChannel;
private RtcpReceiver rtcpReceiver;
private static RtcpReceiver loneRtcpReceiver;
InetSocketAddress rtcpAddress;
private static long startTime;
private static int applyCount;
private static int pmCount;
private static int replaced;
private static long applyTime;
static class CallbackListener implements SenderCallbackListener {
public void senderCallback() {
long start = System.nanoTime();
if (startTime == 0) {
startTime = start;
}
int pmCount = ConferenceMember.pmCount;
applyPrivateMixes();
if (pmCount == ConferenceMember.pmCount) {
return;
}
long now = System.nanoTime();
applyTime += (now - start);
double secondsToApply = applyTime / 1000000000.;
if (++applyCount == 500 && Logger.logLevel >= Logger.LOG_INFO) {
double elapsed = (now - startTime) / 1000000000.;
Logger.println("elapsed " + elapsed + " seconds, applied "
+ ConferenceMember.pmCount
+ " pm's in " + secondsToApply + " seconds, avg per pm "
+ (secondsToApply / pmCount)
+ ", avg time to apply pm's "
+ (secondsToApply / applyCount)
+ " seconds, replaced " + replaced);
applyCount = 0;
ConferenceMember.pmCount = 0;
applyTime = 0;
replaced = 0;
}
}
}
static {
ConferenceSender.addSenderCallbackListener(new CallbackListener());
String s = System.getProperty("com.sun.voip.server.FIRST_RTP_PORT");
if (s != null && s.length() > 0) {
try {
firstRtpPort = Integer.parseInt(s);
if ((firstRtpPort & 1) != 0) {
firstRtpPort++;
}
if (firstRtpPort != 0) {
Logger.println("First RTP Port is " + firstRtpPort);
}
} catch (NumberFormatException e) {
Logger.println(
"Invalid first RTP port, using next available: " + s);
}
}
s = System.getProperty("com.sun.voip.server.LAST_RTP_PORT");
if (firstRtpPort > 0 && s != null && s.length() > 0) {
try {
lastRtpPort = Integer.parseInt(s);
if (lastRtpPort <= firstRtpPort + 1) {
Logger.println("Last RTP port is less than first,"
+ " no limit set.");
lastRtpPort = 0;
}
} catch (NumberFormatException e) {
Logger.println(
"Invalid last RTP port, no limit set: " + s);
}
}
}
public ConferenceMember(ConferenceManager conferenceManager,
CallParticipant cp) throws IOException {
this.conferenceManager = conferenceManager;
this.cp = cp;
initializeChannel();
memberSender = new MemberSender(cp, datagramChannel);
memberReceiver = new MemberReceiver(this, cp, datagramChannel);
memberReceiver.addJoinConfirmationListener(this);
wgManager = conferenceManager.getWGManager();
whisperGroups = wgManager.getWhisperGroups();
mixManager = new MixManager(this,
conferenceManager.getMediaInfo().getSamplesPerPacket(),
conferenceManager.getMediaInfo().getChannels());
timeStarted = Logger.getDate();
addMemberDoneListener(this);
}
private void initializeChannel() throws IOException {
datagramChannel = conferenceManager.getConferenceReceiver().getChannel(cp);
if (datagramChannel != null) {
synchronized (datagramChannel) {
if (loneRtcpReceiver == null) {
int rtcpPort = datagramChannel.socket().getLocalPort() + 1;
Logger.println("Starting lone RtcpReceiver on port "
+ rtcpPort);
loneRtcpReceiver = new RtcpReceiver(
new DatagramSocket(rtcpPort), true);
}
rtcpReceiver = loneRtcpReceiver;
}
return;
}
/*
* We are trying to find a pair of sockets with consecutive port nums.
* The first socket must have an even port.
*
* If we find a socket that we don't like, we have to keep it open
* otherwise when we try to find another socket, we may get the same
* one as before.
*
* So we make a list of the bad sockets and close them all
* after we're done.
*/
ArrayList badChannels = new ArrayList();
int nextRtpPort = firstRtpPort;
try {
while (true) {
datagramChannel = DatagramChannel.open();
if (Logger.logLevel >= Logger.LOG_DETAIL) {
Logger.println("Call " + cp
+ " Opened datagram channel " + datagramChannel);
}
datagramChannel.configureBlocking(false);
DatagramSocket socket = datagramChannel.socket();
socket.setReceiveBufferSize(RtpSocket.MAX_RECEIVE_BUFFER);
socket.setSoTimeout(0);
InetSocketAddress bridgeAddress = Bridge.getLocalBridgeAddress();
InetSocketAddress isa =
new InetSocketAddress(bridgeAddress.getAddress(), nextRtpPort);
if (nextRtpPort > 0) {
nextRtpPort += 2;
if (lastRtpPort != 0 && (nextRtpPort + 1) > lastRtpPort) {
Logger.println("No more RTP ports available, last is "
+ lastRtpPort);
closeBadChannels(badChannels);
throw new IOException(
"No more RTP ports available, last is " + lastRtpPort);
}
}
try {
socket.bind(isa);
int localPort = socket.getLocalPort();
if ((localPort & 1) != 0) {
/*
* Port is odd, can't use this datagramSocket
*/
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Call " + cp
+ " skipping DatagramSocket with odd port "
+ localPort);
}
badChannels.add(datagramChannel);
continue;
}
Logger.writeFile("Call " + cp + " RTCP Port "
+ (localPort + 1));
rtcpReceiver = new RtcpReceiver(
new DatagramSocket(localPort + 1), false);
break;
} catch (SocketException e) {
/*
* Couldn't bind, can't use this DatagramSocket.
*/
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Call " + cp
+ " skipping DatagramSocket " + e.getMessage());
}
badChannels.add(datagramChannel);
continue;
}
}
} catch (Exception e) {
closeBadChannels(badChannels);
throw new IOException("Call " + cp
+ " MemberReceiver exception! " + e.getMessage());
}
closeBadChannels(badChannels);
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Call " + cp + " port "
+ datagramChannel.socket().getLocalPort());
}
}
private void closeBadChannels(ArrayList badChannels) {
while (badChannels.size() > 0) {
/*
* Now close all the channels we couldn't use
*/
DatagramChannel dc = (DatagramChannel) badChannels.remove(0);
try {
dc.close();
if (Logger.logLevel >= Logger.LOG_DETAIL) {
Logger.println("Closed datagram channel " + dc);
}
} catch (IOException e) {
Logger.println("Unable to close channel! " + e.getMessage());
}
}
}
public static void setFirstRtpPort(int firstRtpPort) {
ConferenceMember.firstRtpPort = firstRtpPort;
}
public static int getFirstRtpPort() {
return firstRtpPort;
}
public static void setLastRtpPort(int firstRtpPort) {
ConferenceMember.lastRtpPort = lastRtpPort;
}
public static int getLastRtpPort() {
return lastRtpPort;
}
public String getTimeStarted() {
return timeStarted;
}
/*
* For debugging.
*/
public void traceCall(boolean traceCall) {
this.traceCall = traceCall;
memberSender.traceCall(traceCall);
memberReceiver.traceCall(traceCall);
}
public boolean traceCall() {
return traceCall;
}
public String getMemberState() {
if (initializationDone == false) {
return "\tNot Initialized\n";
}
String s = "";
s += memberReceiver.getMemberState();
s += memberSender.getMemberState();
s += "\tMediaInfo " + memberReceiver.getMediaInfo() + "\n";
s += wgManager.getWhisperGroups(this);
synchronized (conferenceManager) {
synchronized (privateMixesForMe) {
if (privateMixesForMe.size() > 0) {
s += "\tOthers with Private mixes\n";
for (int i = 0; i < privateMixesForMe.size(); i++) {
ConferenceMember member = (ConferenceMember)
privateMixesForMe.get(i);
s += "\t " + member + "\n";
}
}
}
synchronized (mixManager) {
ArrayList mixDescriptors = mixManager.getMixDescriptors();
s += "\tMixDescriptors " + mixDescriptors.size() + "\n";
for (int i = 0; i < mixDescriptors.size(); i++) {
MixDescriptor md = (MixDescriptor) mixDescriptors.get(i);
s += "\t " + md.toAbbreviatedString();
if (whisperGroup == md.getMixDataSource()) {
s += " + ";
}
s += "\n";
}
}
}
return s;
}
/**
* Initialize this member. The call has been established and
* we now know the port at which the member (CallParticipant)
* listens for data.
*/
public void initialize(CallHandler callHandler, InetSocketAddress memberAddress, byte mediaPayload,
byte receivePayload, byte telephoneEventPayload, InetSocketAddress rtcpAddress)
{
this.callHandler = callHandler;
if (cp.getProtocol() != null && "WebRtc".equals(cp.getProtocol()) == false && "Rtmfp".equals(cp.getProtocol()) == false && "Speaker".equals(cp.getProtocol()) == false)
{
if (rtcpAddress != null) {
this.rtcpAddress = rtcpAddress;
} else {
rtcpAddress = new InetSocketAddress(memberAddress.getAddress(),
memberAddress.getPort());
}
Logger.writeFile("Call " + cp + " Initializing sender with member address " + memberAddress);
memberSender.initialize(conferenceManager, callHandler, memberAddress, mediaPayload, telephoneEventPayload);
memberReceiver.initialize(conferenceManager, callHandler, receivePayload, telephoneEventPayload, rtcpReceiver);
if (mediaPayload != receivePayload) {
Logger.println("Call " + cp
+ " send payload " + mediaPayload
+ " receive payload " + receivePayload);
}
try {
myMediaInfo = SdpManager.findMediaInfo(receivePayload);
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Call " + cp + " media info " + myMediaInfo
+ " telephoneEventPayload " + telephoneEventPayload);
}
} catch (ParseException e) {
Logger.println("Call " + cp + " Invalid receivePayload "
+ receivePayload);
callHandler.cancelRequest("Invalid receive payload "
+ receivePayload);
return;
}
} else {
Logger.writeFile("Call " + cp + " Initializing " + cp.getPhoneNumber());
memberSender.initialize(conferenceManager, callHandler, memberAddress, mediaPayload, telephoneEventPayload);
memberReceiver.initialize(conferenceManager, callHandler, receivePayload, telephoneEventPayload, rtcpReceiver);
}
MixManager oldMixManager = mixManager;
mixManager = new MixManager(this,
conferenceManager.getMediaInfo().getSamplesPerPacket(),
conferenceManager.getMediaInfo().getChannels());
synchronized (mixManager) {
mixManager.addMix(memberReceiver, -1.0D); // subtract self (mix-minus)
/*
* Restore private mixes which were set before we were initialized
*/
synchronized (oldMixManager) {
ArrayList mixDescriptors = oldMixManager.getMixDescriptors();
for (int i = 0; i < mixDescriptors.size(); i++) {
MixDescriptor md = (MixDescriptor) mixDescriptors.get(i);
if (md.isPrivateMix()) {
Logger.println("Call " + cp
+ " restoring private mix: " + md);
mixManager.addMix(md);
}
}
}
}
conferenceWhisperGroup = wgManager.getConferenceWhisperGroup();
if (initializationDone) {
addCall(conferenceWhisperGroup);
setWhispering(conferenceWhisperGroup);
Logger.println("Call " + cp
+ " Whispering in conference whisper group");
return;
}
synchronized (conferenceManager) {
String whisperGroupId = null;
if ((whisperGroupId = cp.getWhisperGroupId()) != null) {
try {
addCall(whisperGroupId);
setWhispering(whisperGroupId);
} catch (ParseException e) {
callHandler.cancelRequest("Invalid whisper group "
+ whisperGroupId + " " + e.getMessage());
return;
}
}
}
if (cp.getJoinConfirmationTimeout() == 0) {
addCall(conferenceWhisperGroup);
if (whisperGroup == null) {
setWhispering(conferenceWhisperGroup);
}
}
if (whisperGroup == null) {
try {
addCall("initial-" + cp);
setWhispering("initial-" + cp);
initialWhisperGroup =
wgManager.findWhisperGroup("initial-" + cp);
} catch (ParseException e) {
callHandler.cancelRequest("Call " + cp
+ " Can't add call to initial whisper group "
+ e.getMessage());
return;
}
}
initializationDone = true;
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Call " + cp
+ " ConferenceMember initialization done...");
}
conferenceManager.joinDistributedConference(this);
joinedDistributedConference = false;
}
public void reinitialize(ConferenceManager conferenceManager) {
reinitialize(conferenceManager, true);
}
public void reinitialize(ConferenceManager conferenceManager, boolean initialize) {
synchronized (conferenceManager) {
Logger.println("Call " + this + " Reinitializing");
synchronized (this.conferenceManager) {
/*
* Remove member from whatever whisper groups it's in.
*/
synchronized (whisperGroups) {
for (int i = 0; i < whisperGroups.size(); i++) {
WhisperGroup whisperGroup = (WhisperGroup)
whisperGroups.get(i);
removeCall(whisperGroup.getId());
}
}
this.conferenceManager = conferenceManager;
wgManager = conferenceManager.getWGManager();
whisperGroups = wgManager.getWhisperGroups();
/*
* We have to reinitialize here if the conference parameters
* changed. For example, an incoming call usually comes in
* over the phone lines at PCMU/8000/1. The conference used
* for the incoming call handler is also PCMU/8000/1.
*
* When the call is transferred to the actual conference,
* the conference parameters may be different.
*/
if (initialize)
{
initialize(callHandler, memberSender.getSendAddress(), memberSender.getMediaInfo().getPayload(), memberReceiver.getMediaInfo().getPayload(), (byte) memberReceiver.getTelephoneEventPayload(), rtcpAddress);
}
}
}
conferenceManager.joinDistributedConference(this);
joinedDistributedConference = true;
}
public boolean joinedDistributedConference() {
return joinedDistributedConference;
}
public void cancelRequest(String s) {
if (callHandler != null) {
callHandler.cancelRequest(s);
}
}
public void joinConfirmation() {
addCall(conferenceWhisperGroup);
setWhispering(conferenceWhisperGroup);
}
public MemberSender getMemberSender() {
return memberSender;
}
public MemberReceiver getMemberReceiver() {
return memberReceiver;
}
public InetSocketAddress getRtcpAddress() {
return rtcpAddress;
}
/*
* Maintain list of MixDescriptors.
*
* Notes:
*
* - A member m is always whispering in one and only one whisper group.
* - A descriptor is attenatuated based on where m is whispering
* - m has full volume for members in the whisper group in
* which m is whispering.
* - if m is whispering in wg and wg has attenuation 0, then
* all other wg's are attenuated to 0 for m.
* - otherwise m has wg attenution for the wg's to which m belongs
* but is not whispering in.
* - if m has a private mix pm for m1
* - if m doesn't belong to wg in which m1 is whispering,
* the effective volume ev is 0.
* - if m & m1 are talking in the same wg, ev is pm volume minus 1
* It's minus 1 because m1 already is mixed in the wg with
* volume 1.
* - otherwise, ev is pm volume * wg attenuation
* - pm descriptors for member m and all other members with pm's for m
* must be adjusted when a member m starts or stops talking or
* when AWAY/RETURN changes.
*
* Events:
*
* Member m has been added to whisper group wg.
* A MixDescriptor for wg is added for the member.
* If whisper group member is whispering in has 0 attenuation,
* md attenuation is 0. Otherwise md attenuation is wg attenuation.
*
* No other descriptors need to be adjusted.
* Private mixes don't have to be adjusted because m has not
* started talking in a different whisper group.
*
* Member m has been removed from wg.
* The MixDescriptor for wg is removed for the member.
*
* No other descriptors need to be adjusted since m had to
* stop talking before being removed.
*
* Member m started talking in wg.
* A MixDescriptor for wg with 1.0 attenuation is added.
*
* If wg has 0 attenuation, all other md's have 0 attenuation.
*
* Otherwise, other md's are attenuated to their wg's attenuation.
*
* If m has private mixes for other members,
* those pm's must be attenuated properly based on
* where m and the other members are talking.
*
* If other members have pm' for m, then
* those pm's needot be adjusted based on where m
* and the other members are talking.
*
* Member m stopped talking in wg.
* MixDescriptor for wg is attenuated to wg attenuation.
*
* Nothing more to do until member starts talking in new wg.
*
* Member m is AWAY. All md's for m are muted.
* All pm's which other members have for m are muted.
* XXX May not need to do this since m is muted and
* will not be generating any voice data.
*
* Member is back from AWAY. All md's for m are unmuted.
* All pm's which other members have for m are unmuted.
* XXX May not need to do this since m is muted and
* will not be generating any voice data.
*
* Member m has a private mix for m1.
* A MixDescriptor for m is added describing the desired
* volume for m1. If m is not a member of the whisper group
* in which m1 is whispering, ev is 0.
* If m is not talking in wg, ev must be adjusted by
* wg attentuation.
*/
private void adjustPrivateMixDescriptors() {
synchronized (conferenceManager) {
/*
* Adjust private mixes this member has for other members
*/
synchronized (mixManager) {
ArrayList mixDescriptors = mixManager.getMixDescriptors();
for (int i = 0; i < mixDescriptors.size(); i++) {
MixDescriptor md = (MixDescriptor) mixDescriptors.get(i);
if (md.isPrivateMix() == false) {
continue; // nothing to adjust
}
MemberReceiver mr = (MemberReceiver) md.getMixDataSource();
adjustPrivateMixDescriptor(this, mr.getMember(), md);
}
/*
* Adjust private mixes other members have for this member
*/
synchronized (privateMixesForMe) {
for (int i = 0; i < privateMixesForMe.size(); i++) {
ConferenceMember member = (ConferenceMember)
privateMixesForMe.get(i);
/*
* Adjust private mixes member has for this call.
*/
adjustPrivateMixDescriptor(member, this);
}
}
}
}
}
private void adjustPrivateMixDescriptor(ConferenceMember m1,
ConferenceMember m2) {
MixManager mixManager = m1.getMixManager();
synchronized (mixManager) {
ArrayList mixDescriptors = mixManager.getMixDescriptors();
MixDescriptor md =
mixManager.findMixDescriptor(m2.getMemberReceiver());
if (md == null) {
/*
* m1 doesn't have a private mix for m2.
*/
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println("Call " + m1 + " no pm for " + m2);
}
return;
}
adjustPrivateMixDescriptor(m1, m2, md);
}
}
/*
* m1 has a private mix for m2. Adjust the mix descriptor attenuation
* and muting.
*/
private void adjustPrivateMixDescriptor(ConferenceMember m1,
ConferenceMember m2, MixDescriptor md) {
if (m1.getWhisperGroup() == null) {
/*
* m1 hasn't finished initializing
*/
return;
}
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println("Call " + m1 + " adjustPrivateMixDescriptor: "
+ " md " + md + " pm for " + m2);
}
if (m2.getWhisperGroup() == null ||
m2.getWhisperGroup().isMember(m1) == false) {
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println("Call " + m1 + " not member of "
+ m2.getWhisperGroup() + ", ev is 0");
}
mixManager.setAttenuation(md, 0);
return;
}
double attenuation;
if (m1.getWhisperGroup() == m2.getWhisperGroup()) {
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println("Call " + m1 + " full volume");
}
/*
* Both members are talking in the same whisper group.
*/
attenuation = 1.0;
} else {
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println("Call " + m1 + " attenuating to "
+ m1.getWhisperGroup().getAttenuation());
}
/*
* Members are talking in different whisper groups
*/
attenuation = m1.getWhisperGroup().getAttenuation();
}
mixManager.setAttenuation(md, attenuation);
/*
* Since both members belong to the same whisper group,
* the attenuated value needs to be subtracted from the private mix
* because the member's data is already in the mix by that amount.
*/
//mixManager.adjustVolume(md, -attenuation);
if (m1.isConferenceMuted()) {
mixManager.setMuted(md, true);
} else {
mixManager.setMuted(md, false);
if (m1.isConferenceSilenced()) {
/*
* Conference is silenced for m1
*
* Mute if the member is talking in the conferenceWhisperGroup
*/
MemberReceiver memberReceiver = (MemberReceiver)
md.getMixDataSource();
if (memberReceiver.getWhisperGroup() ==
conferenceWhisperGroup) {
Logger.println("Call " + cp
+ " mute pm for member in main conf");
mixManager.setMuted(md, true);
}
}
}
}
public MixManager getMixManager() {
return mixManager;
}
public void adjustVolume(int[] data, double volume) {
mixManager.adjustVolume(data, volume);
}
private ArrayList privateMixesForMe = new ArrayList();
public ArrayList getPrivateMixesForMe() {
return privateMixesForMe;
}
public void setPrivateMixForMe(ConferenceMember member) {
synchronized (conferenceManager) {
synchronized (privateMixesForMe) {
if (privateMixesForMe.contains(member) == false) {
privateMixesForMe.add(member);
}
}
}
}
public void removePrivateMixForMe(ConferenceMember member) {
synchronized (conferenceManager) {
synchronized (privateMixesForMe) {
privateMixesForMe.remove(member);
}
}
}
private double round(double d) {
return Math.round(d * 1000) / 1000.;
}
/*
* Map of members with private mixes
*/
private static HashMap<ConferenceMember, HashMap> mixMap =
new HashMap<ConferenceMember, HashMap>();
/*
* Set a private mix that this call has for member.
*/
public void setPrivateMix(ConferenceMember member, double[] spatialValues) {
if (cp.getInputTreatment() != null && cp.isRecorder() == false) {
return; // an input treatment doesn't need private mixes. ignore.
}
synchronized (mixMap) {
HashMap<ConferenceMember, double[]> mixesToApply =
mixMap.get(this);
if (mixesToApply == null) {
mixesToApply = new HashMap<ConferenceMember, double[]>();
mixMap.put(this, mixesToApply);
}
if (Logger.logLevel >= Logger.LOG_INFO) {
if (mixesToApply.get(member) != null) {
Logger.println(this + " Replacing mix for " + member);
}
}
if (mixesToApply.put(member, spatialValues) != null) {
replaced++;
}
}
}
static class PrivateMix {
public ConferenceMember memberWithMix;
public ConferenceMember member;
public double[] spatialValues;
public PrivateMix(ConferenceMember memberWithMix,
ConferenceMember member, double[] spatialValues) {
this.memberWithMix = memberWithMix;
this.member = member;
this.spatialValues = spatialValues;
}
}
public static void applyPrivateMixes() {
ArrayList<PrivateMix> mixes = new ArrayList();
synchronized (mixMap) {
Set<ConferenceMember> keySet = mixMap.keySet();
for (ConferenceMember memberWithMix : keySet) {
HashMap<ConferenceMember, double[]> mixesToApply =
mixMap.get(memberWithMix);
Set<ConferenceMember> memberSet = mixesToApply.keySet();
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Applying " + memberSet.size()
+ " private mixes for " + memberWithMix);
}
for (ConferenceMember member : memberSet) {
double[] spatialValues = (double[]) mixesToApply.get(member);
mixes.add(new PrivateMix(memberWithMix, member, spatialValues));
pmCount++;
}
}
mixMap.clear();
}
for (PrivateMix mix : mixes) {
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Applying pm for " + mix.memberWithMix
+ " from " + mix.member + " " + mix.spatialValues[0] + ":"
+ mix.spatialValues[1] + ":" + mix.spatialValues[2]
+ ":" + mix.spatialValues[3]);
}
synchronized (mix.member.getConferenceManager()) {
mix.memberWithMix.applyPrivateMix(mix.member, mix.spatialValues);
}
}
}
private void applyPrivateMix(ConferenceMember member, double[] spatialValues) {
MixDescriptor md = null;
synchronized (conferenceManager) {
synchronized (mixManager) {
boolean remove = false;
if (whisperGroup == null || whisperGroup.hasCommonMix()) {
if (MixDescriptor.isNop(spatialValues, 1)) {
/*
* There's a common mix and the member is already
* mixed in at full volume
*/
remove = true;
}
} else {
if (MixDescriptor.isZeroVolume(spatialValues[3])) {
/*
* There's no common mix, just remove mixDescriptor
* for member to get zero volume.
*/
remove = true;
}
}
if (remove) {
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println("Call " + this
+ " removing private mix for " + member);
}
mixManager.removeMix(member.getMemberReceiver());
member.removePrivateMixForMe(this);
return;
}
if (getCallHandler() == null ||
getCallHandler().isCallEstablished() == false) {
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println("skipping " + this);
}
return;
}
if (member.getCallHandler() == null ||
member.getCallHandler().isCallEstablished() == false) {
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println("skipping " + member);
}
return;
}
md = mixManager.setPrivateMix(member.getMemberReceiver(),
spatialValues);
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Call " + this + " private mix for "
+ member + " " + md);
}
if (md == null) {
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println(this + " pm already set for "
+ member + " vol " + spatialValues[3]);
}
return;
}
/*
* Attenuate this private mix.
* No other mix descriptors need to be attenuated.
*/
if (member.getWhisperGroup() != null) {
/*
* If member isn't done initializing,
* we can't adjust this descriptor because
* the member is not yet whispering.
*/
adjustPrivateMixDescriptor(this, member, md);
}
member.setPrivateMixForMe(this);
}
}
}
private void removePrivateMix(ConferenceMember member) {
synchronized (mixMap) {
synchronized (mixManager) {
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println(cp + " removing private mix for " + member.getMemberReceiver());
}
mixManager.removeMix(member.getMemberReceiver());
}
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println(member + " removing private mix for " + getMemberReceiver());
}
member.removePrivateMixForMe(this);
HashMap<ConferenceMember, double[]> mixesToApply =
mixMap.get(this);
if (mixesToApply == null) {
return;
}
mixesToApply.remove(member);
}
}
static HashMap<String, ArrayList> memberDoneListeners =
new HashMap<String, ArrayList>();
public void addMemberDoneListener(ConferenceMember member) {
synchronized (memberDoneListeners) {
String conferenceId = member.getConferenceManager().getId();
ArrayList<ConferenceMember> memberList =
memberDoneListeners.get(conferenceId);
if (memberList == null) {
memberList = new ArrayList<ConferenceMember>();
memberDoneListeners.put(conferenceId, memberList);
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Created member done list for " + conferenceId);
}
}
if (memberList.contains(member)) {
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Member already in done list for " + conferenceId);
}
return;
}
memberList.add(member);
}
}
private void notifyMemberDoneListeners() {
String conferenceId = conferenceManager.getId();
ArrayList<ConferenceMember> membersToNotify =
new ArrayList<ConferenceMember>();
synchronized (memberDoneListeners) {
ArrayList<ConferenceMember> memberList =
memberDoneListeners.get(conferenceId);
if (memberList == null) {
return;
}
for (ConferenceMember member : memberList) {
if (this != member) {
membersToNotify.add(member);
}
}
memberList.remove(this);
if (memberList.size() == 0) {
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Removing member done list for " + conferenceId);
}
memberDoneListeners.remove(conferenceId);
}
}
for (ConferenceMember member : membersToNotify) {
member.memberDoneNotification(this);
}
}
public void memberDoneNotification(ConferenceMember member) {
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Call " + cp + " got memberDoneNotification for "
+ member);
}
/*
* Remove private mix for member if we have one.
*/
removePrivateMix(member);
memberReceiver.removeForwardMember(member.getMemberSender());
}
public String getAbbreviatedMixDescriptors() {
synchronized (mixManager) {
return mixManager.toAbbreviatedString();
}
}
public MixDescriptor findMixDescriptor(ConferenceMember member) {
synchronized (mixManager) {
MixDescriptor mixDescriptor = (MixDescriptor)
mixManager.findMixDescriptor((MixDataSource)
member.getMemberReceiver());
return mixDescriptor;
}
}
public String getMixDescriptors() {
synchronized (mixManager) {
return mixManager.toString();
}
}
/**
* Mute or unmute the conference from a member
*/
public void setConferenceMuted(boolean isConferenceMuted) {
if (traceCall || Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Call " + cp + " muteConference is now "
+ isConferenceMuted);
}
cp.setConferenceMuted(isConferenceMuted);
attenuateWhisperGroups();
adjustPrivateMixDescriptors();
}
public boolean isConferenceMuted() {
return cp.isConferenceMuted();
}
/**
* Silence or unSilence the main conference from a member
*/
public void setConferenceSilenced(boolean isConferenceSilenced) {
if (traceCall || Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Call " + cp + " conferenceSilenced is now "
+ isConferenceSilenced);
}
cp.setConferenceSilenced(isConferenceSilenced);
attenuateWhisperGroups();
adjustPrivateMixDescriptors();
}
public boolean isConferenceSilenced() {
return cp.isConferenceSilenced();
}
private void muteConferenceWhisperGroup(boolean isMuted) {
synchronized (mixManager) {
ArrayList mixDescriptors = mixManager.getMixDescriptors();
for (int i = 0; i < mixDescriptors.size(); i++) {
MixDescriptor md = (MixDescriptor) mixDescriptors.get(i);
mixManager.setMuted(md, false);
if (md.getMixDataSource() == conferenceWhisperGroup) {
mixManager.setMuted(md, isMuted);
break;
}
}
}
}
/**
* Member is leaving a conference. Print statistics for the member.
*/
public void end() {
if (done) {
return;
}
done = true;
removeCallFromAllWhisperGroups();
memberSender.end();
memberReceiver.end();
if (rtcpReceiver != null && rtcpReceiver != loneRtcpReceiver) {
rtcpReceiver.end();
}
/*
* Don't leave whisper groups or change private mixes if
* the call is migrating.
*/
if (migrating == false) {
notifyMemberDoneListeners();
synchronized (conferenceManager) {
removeMyPrivateMixes();
synchronized (privateMixesForMe) {
removePrivateMixesForMe();
}
}
}
printStatistics();
}
private void removeMyPrivateMixes() {
ArrayList pmToRemove = new ArrayList();
/*
* Make a list of private mixes this call has for others
* Then remove the private mixes.
* We can't remove as we go through the list because
* we will be changing the list.
*/
synchronized (mixManager) {
ArrayList mixDescriptors = mixManager.getMixDescriptors();
for (int i = 0; i < mixDescriptors.size(); i++) {
MixDescriptor md = (MixDescriptor) mixDescriptors.get(i);
if (md.isPrivateMix() == false) {
continue; // not a private mix
}
MemberReceiver mr = (MemberReceiver) md.getMixDataSource();
pmToRemove.add(mr.getMember());
}
for (int i = 0; i < pmToRemove.size(); i++) {
ConferenceMember member = (ConferenceMember) pmToRemove.get(i);
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println("Call " + cp
+ " removing private mix for " + member);
}
removePrivateMix(member);
}
}
}
private void removePrivateMixesForMe() {
/*
* Make a list of private mixes this call has for others
* Then remove the private mixes.
* We can't remove as we go through the list because
* we will be changing the list.
*/
ArrayList pmToRemove = new ArrayList();
/*
* Remove private mixes other members have for this call.
*/
synchronized (privateMixesForMe) {
for (int i = 0; i < privateMixesForMe.size(); i++) {
ConferenceMember member = (ConferenceMember)
privateMixesForMe.get(i);
pmToRemove.add(member);
}
}
for (int i = 0; i < pmToRemove.size(); i++) {
ConferenceMember member = (ConferenceMember)
pmToRemove.get(i);
member.removePrivateMix(this);
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println("Call " + member
+ " removing private mix for " + this);
}
}
}
public void migrating() {
migrating = true;
}
public void printStatistics() {
synchronized (statisticsLock) {
memberSender.printStatistics();
memberReceiver.printStatistics();
}
}
public ConferenceManager getConferenceManager() {
return conferenceManager;
}
/**
* Get CallParticipant for this member
*/
public CallParticipant getCallParticipant() {
return cp;
}
/**
* Get CallHandler for this member
*/
public CallHandler getCallHandler() {
return callHandler;
}
public WhisperGroup getWhisperGroup() {
return memberReceiver.getWhisperGroup();
}
public void setNoCommonMix(String whisperGroupId) throws ParseException {
WhisperGroup whisperGroup = wgManager.findWhisperGroup(whisperGroupId);
if (whisperGroup == null) {
throw new ParseException("No such whisper group: "
+ whisperGroupId, 0);
}
if (this.whisperGroup != whisperGroup) {
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Not in same wg, this " + this.whisperGroup
+ " other " + whisperGroup);
}
return;
}
if (cp.getInputTreatment() != null && cp.isRecorder() == false) {
return; // input treatments don't have descriptors
}
synchronized (mixManager) {
if (whisperGroup.hasCommonMix()) {
mixManager.addMix(whisperGroup, 1);
mixManager.addMix(memberReceiver, -1); // mix-minus
} else {
mixManager.removeMix(whisperGroup);
mixManager.removeMix(memberReceiver); // no need for mix-minus
}
}
double[] spatialValues = new double[4];
/*
* If there's a common mix, add descriptors of zero volume for
* all other non-inputTreatment members.
*
* If there is no common mix, remove all descriptors for all calls,
* effectively making the volume zero for each other call.
*/
synchronized (conferenceManager) {
ArrayList<ConferenceMember> memberList = conferenceManager.getMemberList();
for (ConferenceMember member : memberList) {
if (member == this) {
continue;
}
setPrivateMix(member, spatialValues);
}
}
}
public void addCall(String whisperGroupId) throws ParseException {
synchronized (conferenceManager) {
synchronized (whisperGroups) {
WhisperGroup whisperGroup =
wgManager.findWhisperGroup(whisperGroupId);
if (whisperGroup == null) {
Logger.println("Call " + cp + " Whisper group "
+ whisperGroupId + " doesn't exist. "
+ "Automatically creating it with attenuation 0 "
+ "and locked");
try {
whisperGroup = conferenceManager.createWhisperGroup(
whisperGroupId, 0.0D);
whisperGroup.setTransient(true);
whisperGroup.setLocked(true);
} catch (ParseException e) {
Logger.println("Can't create whisper group "
+ whisperGroupId + " " + e.getMessage());
throw new ParseException("Can't create whisper group "
+ whisperGroupId + " " + e.getMessage(), 0);
}
}
addCall(whisperGroup);
}
}
}
private void addCall(WhisperGroup whisperGroup) {
synchronized (conferenceManager) {
synchronized (whisperGroups) {
whisperGroup.addCall(this);
double attenuation = whisperGroup.getAttenuation();
if (this.whisperGroup != null &&
this.whisperGroup.getAttenuation() == 0) {
/*
* Call is whispering in a 0 attenuation group.
* Descriptor must have 0 attenuation.
*/
attenuation = 0;
}
synchronized(mixManager) {
if (whisperGroup.hasCommonMix() == true &&
(cp.getInputTreatment() == null ||
cp.isRecorder() == true)) {
mixManager.addMix(whisperGroup, attenuation);
} else {
mixManager.removeMix(memberReceiver); // no mix minus
}
}
if (whisperGroup.getAttenuation() == 0) {
/*
* If the call is in a whisper group with full attenuation
* the call is immediately set to whispering so
* it can't hear anything else.
*/
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Call " + cp + " entered 0 attenuation "
+ "start whispering now!");
}
setWhispering(whisperGroup);
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
synchronized (mixManager) {
mixManager.showDescriptors();
}
}
return;
}
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
synchronized (mixManager) {
mixManager.showDescriptors();
}
}
}
}
}
private void removeCallFromAllWhisperGroups() {
synchronized (conferenceManager) {
synchronized (whisperGroups) {
for (int i = 0; i < whisperGroups.size(); i++) {
WhisperGroup whisperGroup =
(WhisperGroup)whisperGroups.get(i);
removeCall(whisperGroup.getId());
}
}
}
}
public void removeCall(String whisperGroupId) {
/*
* We must grab the lock so we don't deadlock with the sender thread.
*/
synchronized (conferenceManager) {
synchronized (whisperGroups) {
WhisperGroup whisperGroup =
wgManager.findWhisperGroup(whisperGroupId);
if (whisperGroup == null) {
Logger.println("Whisper group doesn't exist for "
+ whisperGroupId + "!");
return;
}
if (this.whisperGroup == whisperGroup) {
/*
* Start talking in the main conference.
*/
setWhispering(conferenceWhisperGroup);
}
wgManager.removeCall(whisperGroup, this);
synchronized (mixManager) {
mixManager.removeMix(whisperGroup);
}
}
}
}
/*
* For call migration, preserve settings old call had.
*/
public void migrate(ConferenceMember oldMember) {
synchronized (conferenceManager) {
/*
* If old member was muted, make new member be muted.
*/
getMemberReceiver().setMuted(oldMember.getCallParticipant().isMuted());
/*
* copy private mixes old call has to current call
*/
copyOldPrivateMixes(oldMember);
/*
* Also update private mixes other calls have for old call
* to be for new call
*/
updateOtherPrivateMixes(oldMember);
/*
* remove old member from all whisper groups
* and add new member to the whisper groups the
* old member was in.
*/
wgManager.migrate(oldMember, this);
/*
* If the old member was whispering in a whisper group,
* set the new member to be whispering.
*/
WhisperGroup whisperGroup = oldMember.getWhisperGroup();
setWhispering(whisperGroup);
}
}
private void copyOldPrivateMixes(ConferenceMember oldMember) {
/*
* copy private mixes the oldMember has
*/
MixManager mixManager = oldMember.getMixManager();
synchronized (mixManager) {
ArrayList mixDescriptors = mixManager.getMixDescriptors();
for (int i = 0; i < mixDescriptors.size(); i++) {
MixDescriptor md = (MixDescriptor) mixDescriptors.get(i);
if (md.isPrivateMix() == false) {
continue; // not a private mix
}
MemberReceiver mr = (MemberReceiver) md.getMixDataSource();
if (mr.getMember() == oldMember) {
continue; // it's a descriptor for oldMember (mix minus)
}
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("pre-migrate member " + oldMember
+ " has pm for " + mr);
Logger.println("pre-migrate member " + oldMember
+ " mix descriptors " + oldMember.getMixDescriptors());
}
synchronized (mixMap) {
applyPrivateMix(mr.getMember(), md.getSpatialValues());
}
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Call " + cp + " Set private mix for "
+ mr + " to " + md);
}
}
/*
* Now go through the md's for the old member and remove
* all the private mixes it has
*/
for (int i = 0; i < mixDescriptors.size(); i++) {
MixDescriptor md = (MixDescriptor) mixDescriptors.get(i);
if (md.isPrivateMix() == false) {
continue; // not a private mix
}
MemberReceiver mr = (MemberReceiver) md.getMixDataSource();
if (mr.getMember() == oldMember) {
continue; // it's a descriptor for oldMember (mix minus)
}
/*
* Remove private mix oldMember has
*/
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println(oldMember + " removing pm for " + mr.getMember());
}
oldMember.removePrivateMix(mr.getMember());
}
}
}
/*
* Update private mixes of all other members
* who have a private mix for the oldMember.
*/
private void updateOtherPrivateMixes(ConferenceMember oldMember) {
ArrayList privateMixesForMe = oldMember.getPrivateMixesForMe();
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println(oldMember + " private mixes for me " +
oldMember.getPrivateMixesForMe().size());
}
ConferenceMember[] pmArrayForMe = (ConferenceMember[])
privateMixesForMe.toArray(new ConferenceMember[0]);
for (int i = 0; i < pmArrayForMe.length; i++) {
ConferenceMember m = pmArrayForMe[i];
MixManager mixManager = m.getMixManager();
synchronized (mixManager) {
MixDescriptor[] mixDescriptors = (MixDescriptor[])
mixManager.getMixDescriptors().toArray(new MixDescriptor[0]);
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("member with pm " + m
+ " descriptors before...");
mixManager.showDescriptors();
}
for (int j = 0; j < mixDescriptors.length; j++) {
MixDescriptor md = mixDescriptors[j];
if (md.isPrivateMix() == false) {
Logger.println(this + " Skipping md for " + md);
continue; // not a private mix
}
MemberReceiver mr = (MemberReceiver) md.getMixDataSource();
if (mr.getMember() != oldMember) {
Logger.println(this + " md " + md + " not an md for old member " + mr);
continue; // not a pm for old Member.
}
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("setting pm for " + m
+ " to new member " + this.getMemberReceiver());
}
/*
* Now remove private mix other call has for oldMember
*/
m.removePrivateMix(oldMember);
/*
* Set private mix for new member
*/
synchronized (mixMap) {
m.applyPrivateMix(this, md.getSpatialValues());
}
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("member with pm " + m
+ " descriptors after...");
mixManager.showDescriptors();
}
break;
}
}
}
}
public void setWhispering(String whisperGroupId) throws ParseException {
synchronized (conferenceManager) {
synchronized (whisperGroups) {
WhisperGroup whisperGroup =
wgManager.findWhisperGroup(whisperGroupId);
if (whisperGroup == null) {
Logger.println("Call " + cp
+ " invalid whisper group " + whisperGroupId);
throw new ParseException("Call " + cp
+ " invalid whisper group " + whisperGroupId, 0);
}
if (whisperGroup.isMember(this) == false) {
Logger.println("Call " + cp
+ " is not a member of whisper group "
+ whisperGroupId);
throw new ParseException("Call " + cp
+ " is not a member of whisper group "
+ whisperGroupId, 0);
}
if (this.whisperGroup == whisperGroup) {
Logger.println("Call " + cp + " already whispering in "
+ whisperGroup);
return;
}
if (this.whisperGroup.isLocked()) {
Logger.println("Calls in a locked whisper group "
+ "cannot stop whispering until the call "
+ "is removed from the wg");
throw new ParseException("Calls in a locked whisper group "
+ "cannot stop whispering until the call "
+ "is removed from the wg", 0);
}
setWhispering(whisperGroup);
}
}
}
public void setWhispering(WhisperGroup newWhisperGroup) {
synchronized (conferenceManager) {
synchronized (whisperGroups) {
if (whisperGroup == null) {
/*
* This is the first time. We're still initializing.
*/
whisperGroup = newWhisperGroup;
whisperGroup.setWhispering(true, this);
memberReceiver.setWhisperGroup(whisperGroup);
synchronized (mixManager) {
if (whisperGroup.hasCommonMix() == true &&
(cp.getInputTreatment() == null ||
cp.isRecorder() == true)) {
mixManager.addMix(whisperGroup, 1.0D);
}
adjustPrivateMixDescriptors();
}
return;
}
if (initialWhisperGroup != null) {
String id = initialWhisperGroup.getId();
initialWhisperGroup = null;
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println("Removing initial wg " + id);
}
removeCall(id);
}
if (newWhisperGroup == whisperGroup) {
Logger.writeFile("Call " + this
+ " is already whispering to " + whisperGroup);
return;
}
synchronized (whisperGroup) {
/*
* Stop whispering in old whisper group
*/
whisperGroup.setWhispering(false, this);
/*
* flush any left over contributions
*/
memberReceiver.flushContributions();
/*
* Start whispering in new whisper group
*/
whisperGroup = newWhisperGroup;
memberReceiver.setWhisperGroup(whisperGroup);
Logger.println("Call " + cp
+ " Now whispering in " + whisperGroup);
whisperGroup.setWhispering(true, this);
synchronized (mixManager) {
if (whisperGroup.hasCommonMix() == true &&
(cp.getInputTreatment() == null ||
cp.isRecorder() == true)) {
mixManager.addMix(whisperGroup, 1.0D);
}
attenuateWhisperGroups();
adjustPrivateMixDescriptors();
}
}
}
}
}
/*
* Attenuate whisperGroups which we are not speaking in.
*/
private void attenuateWhisperGroups() {
if (whisperGroup == null) {
return; // not done initializing
}
synchronized (conferenceManager) {
synchronized(mixManager) {
ArrayList mixDescriptors = mixManager.getMixDescriptors();
/*
* Attenuate whisperGroups
*/
for (int i = 0; i < mixDescriptors.size(); i++) {
MixDescriptor md = (MixDescriptor) mixDescriptors.get(i);
if (md.getMixDataSource() instanceof WhisperGroup ==
false) {
continue;
}
WhisperGroup wg = (WhisperGroup) md.getMixDataSource();
muteDescriptor(wg, md);
if (wg == whisperGroup) {
mixManager.setAttenuation(md, 1);
continue; // we're whispering in this one
}
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println("Call " + cp
+ " talking in " + whisperGroup
+ ", attenuating " + wg
+ " to " + wg.getAttenuation());
}
if (whisperGroup.getAttenuation() == 0) {
/*
* I am whispering in a 0 attenuation whisper group.
* I should not hear any other whisper group data.
*/
mixManager.setAttenuation(md, 0);
continue;
}
mixManager.setAttenuation(md, wg.getAttenuation());
}
}
}
}
/*
* Decide whether or not to mute this whisper group descriptor
*/
private void muteDescriptor(WhisperGroup wg, MixDescriptor md) {
if (cp.isConferenceMuted()) {
synchronized (mixManager) {
mixManager.setMuted(md, true);
}
return;
}
if (cp.isConferenceSilenced() == false) {
synchronized (mixManager) {
mixManager.setMuted(md, false);
}
return;
}
/*
* The conference is silenced. Mute whisper groups
* we're not talking in. Also mute the conference whisper group.
*/
if (wg != whisperGroup || wg == conferenceWhisperGroup) {
synchronized (mixManager) {
mixManager.setMuted(md, true);
}
return;
}
/*
* We're whispering in some wg.
*/
synchronized (mixManager) {
mixManager.setMuted(md, false);
}
}
public void setInputVolume(double volume) {
memberReceiver.setInputVolume(volume);
}
public double getInputVolume() {
return memberReceiver.getInputVolume();
}
public void setOutputVolume(double volume) {
memberSender.setOutputVolume(volume);
}
public double getOutputVolume() {
return memberSender.getOutputVolume();
}
public void saveCurrentContribution() {
memberReceiver.saveCurrentContribution();
synchronized (memberTreatments) {
if (currentTreatment == null) {
return;
}
currentTreatment.saveCurrentContribution();
}
}
public String getSourceId() {
return cp.getCallId();
}
public boolean contributionIsInCommonMix() {
return memberReceiver.contributionIsInCommonMix();
}
public int[] getPreviousContribution() {
return memberReceiver.getPreviousContribution();
}
public int[] getCurrentContribution() {
return memberReceiver.getCurrentContribution();
}
public void invalidateCurrentContribution() {
memberReceiver.invalidateCurrentContribution();
}
public boolean sendData() {
if (cp.getInputTreatment() != null && cp.getToRecordingFile() == null) {
/*
* We don't send data to a call playing an input treatment
* unless that call is recording.
*/
return true;
}
/*
* Since we know we get called here every 20ms, use this opportunity
* to check if we've received any data or not and
* handle appropriate timeouts.
*/
if (memberReceiver.checkPacketsReceived() == false) {
return false;
}
int timeout = cp.getCallTimeout();
if (timeout > 0) {
timeout -= RtpPacket.PACKET_PERIOD;
if (timeout <= 0) {
cp.setCallTimeout(0);
callHandler.cancelRequest("Call timeout");
return false;
}
cp.setCallTimeout(timeout);
}
synchronized (mixManager) {
return memberSender.sendData(mixManager.mix());
}
}
/*
* Add a treatment to be played to this member
* We only play one treatment at a time.
*/
ArrayList memberTreatments = new ArrayList();
TreatmentManager currentTreatment;
public void addTreatment(TreatmentManager treatmentManager) {
synchronized (conferenceManager) {
synchronized (memberTreatments) {
memberTreatments.add(treatmentManager);
if (currentTreatment == null) {
startNextTreatment();
}
}
}
}
private void startNextTreatment() {
if (memberTreatments.size() == 0) {
currentTreatment = null;
return;
}
currentTreatment = (TreatmentManager) memberTreatments.get(0);
currentTreatment.addTreatmentDoneListener(this);
synchronized (mixManager) {
mixManager.addMix(currentTreatment, 1.0D);
}
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println("Call " + cp
+ " Starting next treatment " + currentTreatment.getId());
}
}
public void treatmentDoneNotification(TreatmentManager treatmentManager) {
synchronized (conferenceManager) {
synchronized (memberTreatments) {
memberTreatments.remove(treatmentManager);
synchronized (mixManager) {
mixManager.removeMix(treatmentManager);
}
if (Logger.logLevel >= Logger.LOG_MOREINFO) {
Logger.println(
"Treatment done " + treatmentManager.getId());
Logger.println(
"treatments left " + memberTreatments.size());
}
CallEvent callEvent = new CallEvent(CallEvent.TREATMENT_DONE);
callEvent.setTreatmentId(treatmentManager.getId());
callHandler.sendCallEventNotification(callEvent);
startNextTreatment();
}
}
}
public void pauseTreatment(String treatmentId, boolean isPaused) {
synchronized (conferenceManager) {
synchronized (memberTreatments) {
for (int i = 0; i < memberTreatments.size(); i++) {
TreatmentManager treatmentManager = (TreatmentManager)
memberTreatments.get(i);
if (treatmentId == null) {
treatmentManager.pause(isPaused);
} else {
if (treatmentManager.getId().equals(treatmentId)) {
treatmentManager.pause(isPaused);
return;
}
}
}
}
}
}
public void stopTreatment(String treatmentId) {
synchronized (conferenceManager) {
synchronized (memberTreatments) {
if (treatmentId == null) {
/*
* Before stopping the current treatment (if any)
* we have to remove any following treatments.
* Otherwise when this thread calls stopTreatment(),
* the next treatment will be started.
*/
memberTreatments.clear();
if (currentTreatment != null) {
currentTreatment.stopTreatment();
}
return;
}
for (int i = 0; i < memberTreatments.size(); i++) {
TreatmentManager treatmentManager = (TreatmentManager)
memberTreatments.get(i);
if (treatmentManager.getId().equals(treatmentId)) {
if (treatmentManager == currentTreatment) {
treatmentManager.stopTreatment();
} else {
memberTreatments.remove(i);
}
return;
}
}
}
}
}
public boolean hasTreatments() {
return memberTreatments.size() > 0;
}
public String toString() {
return cp.toString();
}
public String toAbbreviatedString() {
String callId = cp.getCallId();
if (callId.length() < 14) {
return callId;
}
return cp.getCallId().substring(0, 13);
}
}