/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.java.sip.communicator.impl.gui.main.call.conference;
import java.awt.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import net.java.sip.communicator.impl.gui.main.call.*;
import net.java.sip.communicator.impl.gui.utils.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;
import net.java.sip.communicator.plugin.desktoputil.*;
import net.java.sip.communicator.plugin.desktoputil.TransparentPanel;
import org.jitsi.util.swing.*;
/**
* Extends <tt>BasicConferenceCallPanel</tt> to implement a user interface
* <tt>Component</tt> which depicts a <tt>CallConference</tt> with audio and
* video and is contained in a <tt>CallPanel</tt>.
*
* @author Yana Stamcheva
* @author Lyubomir Marinov
*/
public class VideoConferenceCallPanel
extends BasicConferenceCallPanel
{
/**
* The <tt>Logger</tt> used by the <tt>VideoConferenceCallPanel</tt> class
* and its instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(VideoConferenceCallPanel.class);
/**
* The compile-time flag which indicates whether each video displayed by
* <tt>VideoConferenceCallPanel</tt> is to be depicted with an associated
* tool bar showing information and controls related to the (local or
* remote) peer sending the respective video.
*/
private static final boolean SHOW_TOOLBARS = true;
/**
* The facility which aids this instance with the video-related information.
*/
private final UIVideoHandler2 uiVideoHandler;
/**
* The <tt>Observer</tt> which listens to {@link #uiVideoHandler} about
* changes in the video-related information.
*/
private final Observer uiVideoHandlerObserver
= new Observer()
{
public void update(Observable o, Object arg)
{
updateViewFromModel();
}
};
/**
* The <tt>VideoContainer</tt> which occupies this whole <tt>Component</tt>
* and arranges the visual <tt>Component</tt>s displaying the video
* streaming between the local peer/user and the remote peer(s).
*/
private final VideoContainer videoContainer;
/**
* The set of visual <tt>Component</tt>s displaying video streaming between
* the local peer/user and the remote peers which are depicted by this
* instance.
*/
private final Set<Component> videos = new HashSet<Component>();
/**
* The thumbnail container.
*/
private final ThumbnailConferenceCallPanel thumbnailContainer;
private final JPanel thumbnailPanel;
/**
* Initializes a new <tt>VideoConferenceCallPanel</tt> instance which is to
* be used by a specific <tt>CallPanel</tt> to depict a specific
* <tt>CallConference</tt>. The new instance will depict both the
* audio-related and the video-related information.
*
* @param callPanel the <tt>CallPanel</tt> which will use the new instance
* to depict the specified <tt>CallConference</tt>.
* @param callConference the <tt>CallConference</tt> to be depicted by the
* new instance
* @param uiVideoHandler the utility which is to aid the new instance in
* dealing with the video-related information
*/
public VideoConferenceCallPanel(
CallPanel callPanel,
CallConference callConference,
UIVideoHandler2 uiVideoHandler)
{
super(callPanel, callConference);
this.uiVideoHandler = uiVideoHandler;
thumbnailPanel = new JPanel(new BorderLayout());
thumbnailContainer
= new ThumbnailConferenceCallPanel( callPanel,
callConference,
uiVideoHandler);
videoContainer = createVideoContainer();
/*
* Our user interface hierarchy has been initialized so we are ready to
* begin receiving events warranting updates of this view from its
* model.
*/
uiVideoHandler.addObserver(uiVideoHandlerObserver);
/*
* Notify the super that this instance has completed its initialization
* and the view that it implements is ready to be updated from the
* model.
*/
initializeComplete();
}
private void addConferenceMemberContainers(
ConferenceParticipantContainer cpc)
{
List<ConferenceParticipantContainer> cmcs
= cpc.conferenceMemberContainers;
if ((cmcs != null) && !cmcs.isEmpty())
{
for (ConferenceParticipantContainer cmc : cmcs)
{
if (!cmc.toBeRemoved)
{
videoContainer.add(
cmc.getComponent(),
VideoLayout.CENTER_REMOTE);
}
}
}
}
private Component createDefaultPhotoPanel(Call call)
{
OperationSetServerStoredAccountInfo accountInfo
= call.getProtocolProvider().getOperationSet(
OperationSetServerStoredAccountInfo.class);
ImageIcon photoLabelIcon = null;
if (accountInfo != null)
{
byte[] accountImage = AccountInfoUtils.getImage(accountInfo);
// do not set empty images
if ((accountImage != null) && (accountImage.length > 0))
photoLabelIcon = new ImageIcon(accountImage);
}
if (photoLabelIcon == null)
{
photoLabelIcon
= new ImageIcon(
ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO));
}
return createDefaultPhotoPanel(photoLabelIcon);
}
private Component createDefaultPhotoPanel(CallPeer callPeer)
{
byte[] peerImage = CallManager.getPeerImage(callPeer);
ImageIcon photoLabelIcon
= (peerImage == null)
? new ImageIcon(
ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO))
: new ImageIcon(peerImage);
return createDefaultPhotoPanel(photoLabelIcon);
}
private Component createDefaultPhotoPanel(ConferenceMember conferenceMember)
{
return
createDefaultPhotoPanel(
new ImageIcon(
ImageLoader.getImage(
ImageLoader.DEFAULT_USER_PHOTO)));
}
/**
* Creates a new <tt>Component</tt> which is to display a specific
* <tt>ImageIcon</tt> representing the photo of a participant in a call.
*
* @param photoLabelIcon the <tt>ImageIcon</tt> which represents the photo
* of a participant in a call and which is to be displayed by the new
* <tt>Component</tt>
* @return a new <tt>Component</tt> which displays the specified
* <tt>photoLabelIcon</tt>
*/
private Component createDefaultPhotoPanel(ImageIcon photoLabelIcon)
{
JLabel photoLabel = new JLabel();
photoLabel.setIcon(photoLabelIcon);
@SuppressWarnings("serial")
JPanel photoPanel
= new TransparentPanel(new GridBagLayout())
{
/**
* @{inheritDoc}
*/
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g = g.create();
try
{
AntialiasingManager.activateAntialiasing(g);
g.setColor(Color.GRAY);
g.fillRoundRect(
0, 0, this.getWidth(), this.getHeight(),
6, 6);
}
finally
{
g.dispose();
}
}
};
photoPanel.setPreferredSize(new Dimension(320, 240));
GridBagConstraints photoPanelConstraints = new GridBagConstraints();
photoPanelConstraints.anchor = GridBagConstraints.CENTER;
photoPanelConstraints.fill = GridBagConstraints.NONE;
photoPanel.add(photoLabel, photoPanelConstraints);
return photoPanel;
}
/**
* Initializes a new <tt>VideoContainer</tt> instance which is to contain
* the visual/video <tt>Component</tt>s of the telephony conference depicted
* by this instance.
*/
private VideoContainer createVideoContainer()
{
VideoContainer videoContainer = new VideoContainer(null, true);
thumbnailPanel.setBackground(Color.DARK_GRAY);
thumbnailPanel.add(thumbnailContainer, BorderLayout.NORTH);
add(thumbnailPanel, BorderLayout.EAST);
add(videoContainer, BorderLayout.CENTER);
return videoContainer;
}
/**
* Shows/hides the participants thumbnails list.
*
* @param show <tt>true</tt> to show the participants list, <tt>false</tt>
* to hide it
*/
public void showThumbnailsList(boolean show)
{
thumbnailPanel.setVisible(show);
}
/**
* {@inheritDoc}
*/
@Override
public void dispose()
{
try
{
uiVideoHandler.deleteObserver(uiVideoHandlerObserver);
}
finally
{
super.dispose();
}
}
/**
* Determines whether a specific <tt>ConferenceMember</tt> represents the
* same conference participant as a specific <tt>CallPeer</tt>. If the
* specified <tt>conferenceMember</tt> is <tt>null</tt>, returns
* <tt>true</tt>. Otherwise, determines whether the addresses of the
* specified <tt>conferenceMember</tt> and the specified <tt>callPeer</tt>
* identify one and the same entity.
*
* @param conferenceMember the <tt>ConferenceMember</tt> to be checked
* whether is represents the same conference participant as the specified
* <tt>callPeer</tt>. If it is <tt>null</tt>, <tt>true</tt> is returned.
* @param callPeer the <tt>CallPeer</tt> to be checked whether it represents
* the same conference participant as the specified
* <tt>conferenceMember</tt>
* @return <tt>true</tt> if the specified <tt>conferenceMember</tt> and the
* specified <tt>callPeer</tt> represent the same conference participant or
* the specified <tt>conferenceMember</tt> is <tt>null</tt>; otherwise,
* <tt>false</tt>
*/
private boolean isConferenceMemberCallPeer(
ConferenceMember conferenceMember,
CallPeer callPeer)
{
return
(conferenceMember == null)
? true
: CallManager.addressesAreEqual(
conferenceMember.getAddress(),
callPeer.getAddress());
}
/**
* Determines whether a specific <tt>ConferenceMember</tt> represents the
* local peer/user. Since this instance depicts a whole telephony
* conference, the local peer/user may be participating with multiple
* <tt>Call</tt>s in it. The <tt>Call</tt>s may be through different
* (local) accounts. That's why the implementation determines whether the
* address of the specified <tt>conferenceMember</tt> identifies the address
* of a (local) accounts involved in the telephony conference depicted by
* this instance.
*
* @param conferenceMember the <tt>ConferenceMember</tt> to be checked
* whether it represents the local peer/user
* @return <tt>true</tt> if the specified <tt>conferenceMember</tt>
* represents the local peer/user; otherwise, <tt>false</tt>
*/
private boolean isConferenceMemberLocalUser(
ConferenceMember conferenceMember)
{
String address = conferenceMember.getAddress();
for (Call call : callConference.getCalls())
{
if (CallManager.addressesAreEqual(
address,
call.getProtocolProvider().getAccountID()
.getAccountAddress()))
{
return true;
}
}
return false;
}
private void removeConferenceMemberContainers(
ConferenceParticipantContainer cpc,
boolean all)
{
List<ConferenceParticipantContainer> cmcs
= cpc.conferenceMemberContainers;
if ((cmcs != null) && !cmcs.isEmpty())
{
Iterator<ConferenceParticipantContainer> i = cmcs.iterator();
while (i.hasNext())
{
ConferenceParticipantContainer cmc = i.next();
if (all || cmc.toBeRemoved)
{
i.remove();
videoContainer.remove(cmc.getComponent());
cmc.dispose();
}
}
}
}
/**
* Updates the <tt>ConferenceParticipantContainer</tt>s which depict the
* <tt>ConferenceMember</tt>s of the <tt>CallPeer</tt> depicted by a
* specific <tt>ConferenceParticipantContainer</tt>.
*
* @param cpc the <tt>ConferenceParticipantContainer</tt> which depicts the
* <tt>CallPeer</tt> whose <tt>ConferenceMember</tt>s are to be depicted
* @param videos the visual <tt>Component</tt>s displaying video streaming
* from the remote peer (represented by <tt>cpc</tt>) to the local peer/user
* @param videoTelephony the <tt>OperationSetVideoTelephony</tt> which
* retrieved the specified <tt>videos</tt> from the <tt>CallPeer</tt>
* depicted by <tt>cpc</tt>. While the <tt>CallPeer</tt> could be queried
* for it, such a query would waste more resources at run time given that
* the invoker has it already.
*/
private void updateConferenceMemberContainers(
ConferenceParticipantContainer cpc,
List<Component> videos,
OperationSetVideoTelephony videoTelephony)
{
CallPeer callPeer = (CallPeer) cpc.getParticipant();
List<ConferenceParticipantContainer> cmcs
= cpc.conferenceMemberContainers;
/*
* Invalidate all conferenceMemberContainers. Then validate which of
* them are to remain and which of them are to really be removed
* later on.
*/
if (cmcs != null)
{
for (ConferenceParticipantContainer cmc : cmcs)
cmc.toBeRemoved = true;
}
/*
* Depict the remote videos. They may or may not be associated with
* ConferenceMembers so the ConferenceMembers which have no
* associated videos will be depicted afterwards.
*/
if (videos != null)
{
Component video = cpc.getVideo();
for (Component conferenceMemberVideo : videos)
{
/*
* One of the remote videos is already used to depict the
* callPeer.
*/
if (conferenceMemberVideo == video)
continue;
boolean addNewConferenceParticipantContainer = true;
ConferenceMember conferenceMember
= videoTelephony.getConferenceMember(
callPeer,
conferenceMemberVideo);
if (cmcs == null)
{
cmcs = new LinkedList<ConferenceParticipantContainer>();
cpc.conferenceMemberContainers = cmcs;
}
else
{
for (ConferenceParticipantContainer cmc : cmcs)
{
Object cmcParticipant = cmc.getParticipant();
if (conferenceMember == null)
{
if (cmcParticipant == callPeer)
{
Component cmcVideo = cmc.getVideo();
if (cmcVideo == null)
{
cmc.setVideo(conferenceMemberVideo);
cmc.toBeRemoved = false;
addNewConferenceParticipantContainer
= false;
break;
}
else if (cmcVideo == conferenceMemberVideo)
{
cmc.toBeRemoved = false;
addNewConferenceParticipantContainer
= false;
break;
}
}
}
else if (cmcParticipant == conferenceMember)
{
cmc.setVideo(conferenceMemberVideo);
cmc.toBeRemoved = false;
addNewConferenceParticipantContainer = false;
break;
}
}
}
if (addNewConferenceParticipantContainer)
{
ConferenceParticipantContainer cmc
= (conferenceMember == null)
? new ConferenceParticipantContainer(
callPeer,
conferenceMemberVideo)
: new ConferenceParticipantContainer(
conferenceMember,
conferenceMemberVideo);
cmcs.add(cmc);
}
}
}
/*
* Depict the ConferenceMembers which have not been depicted yet.
* They have no associated videos.
*/
List<ConferenceMember> conferenceMembers
= callPeer.getConferenceMembers();
if (!conferenceMembers.isEmpty())
{
if (cmcs == null)
{
cmcs = new LinkedList<ConferenceParticipantContainer>();
cpc.conferenceMemberContainers = cmcs;
}
for (ConferenceMember conferenceMember : conferenceMembers)
{
/*
* If the callPeer reports itself as a ConferenceMember, then
* we've already depicted it with cpc.
*/
if (isConferenceMemberCallPeer(conferenceMember, callPeer))
continue;
/*
* If the callPeer reports the local peer/user as a
* ConferenceMember, then we've already depicted it.
*/
if (isConferenceMemberLocalUser(conferenceMember))
continue;
boolean addNewConferenceParticipantContainer = true;
for (ConferenceParticipantContainer cmc : cmcs)
{
if (cmc.getParticipant() == conferenceMember)
{
/*
* It is possible to have a ConferenceMember who is
* sending video but we just do not have the SSRC of
* that video to associate the video with the
* ConferenceMember. In such a case, we may be depicting
* the ConferenceMember twice: once with video without a
* ConferenceMember and once with a ConferenceMember
* without video. This will surely be the case at the
* time of this writing with non-focus participants in a
* telephony conference hosted on a Jitsi Videobridge.
* Such a display is undesirable. If the
* conferenceMember is known to send video, we will not
* display it until we associated it with a video. This
* way, if a ConferenceMember is not sending video, we
* will depict it and we can be sure that no video
* without a ConferenceMember association will be
* depicting it a second time.
*/
if (cmc.toBeRemoved
&& !conferenceMember
.getVideoStatus()
.allowsSending())
{
cmc.setVideo(null);
cmc.toBeRemoved = false;
}
addNewConferenceParticipantContainer = false;
break;
}
}
if (addNewConferenceParticipantContainer)
{
ConferenceParticipantContainer cmc
= new ConferenceParticipantContainer(
conferenceMember,
null);
cmcs.add(cmc);
}
}
}
if ((cmcs != null) && !cmcs.isEmpty())
{
removeConferenceMemberContainers(cpc, false);
/*
* If cpc is already added to the user interface hierarchy of this
* instance, then it was there before the update procedure and it
* was determined to be appropriate to continue to depict its model.
* Consequently, its Component will be neither added to (because it
* was already added) nor removed from the user interface hierarchy
* of this instance. That's why we have make sure that the
* Components of its conferenceMemberContainers are also added to
* the user interface.
*/
if (UIVideoHandler2.isAncestor(this, cpc.getComponent()))
addConferenceMemberContainers(cpc);
}
}
/**
* {@inheritDoc}
*/
@Override
protected ConferenceCallPeerRenderer updateViewFromModel(
ConferenceCallPeerRenderer callPeerPanel,
CallPeer callPeer)
{
if (callPeer == null)
{
/*
* The local peer/user will be represented by a Call which has a
* CallPeer who provides local video. However, if the user has
* selected to hide the local video, the local peer/user will not be
* represented at all.
*/
Component video = null;
if (uiVideoHandler.isLocalVideoVisible())
{
for (Call aCall : callConference.getCalls())
{
Iterator<? extends CallPeer> callPeerIter
= aCall.getCallPeers();
OperationSetVideoTelephony videoTelephony
= aCall.getProtocolProvider().getOperationSet(
OperationSetVideoTelephony.class);
while (callPeerIter.hasNext())
{
callPeer = callPeerIter.next();
if (videoTelephony != null)
{
try
{
video
= videoTelephony.getLocalVisualComponent(
callPeer);
}
catch (OperationFailedException ofe)
{
logger.error(
"Failed to retrieve the local video"
+ " for display",
ofe);
}
if (video != null)
break;
}
}
if (video != null)
break;
}
}
if (callPeer == null)
callPeerPanel = null;
else
{
Call call = callPeer.getCall();
if (callPeerPanel instanceof ConferenceParticipantContainer)
{
ConferenceParticipantContainer cpc
= (ConferenceParticipantContainer) callPeerPanel;
if (cpc.getParticipant() == call)
cpc.setVideo(video);
else
callPeerPanel = null;
}
else
callPeerPanel = null;
if (callPeerPanel == null)
{
callPeerPanel
= new ConferenceParticipantContainer(call, video);
}
}
}
else
{
/*
* The specified callPeer will be represented by one of its remote
* videos which is not associated with a ConferenceMember or is
* associated with a ConferenceMember representing the callPeer
* itself.
*/
OperationSetVideoTelephony videoTelephony
= callPeer.getProtocolProvider().getOperationSet(
OperationSetVideoTelephony.class);
List<Component> videos = null;
Component video = null;
if (videoTelephony != null)
{
videos = videoTelephony.getVisualComponents(callPeer);
if ((videos != null) && !videos.isEmpty())
{
for (Component aVideo : videos)
{
ConferenceMember conferenceMember
= videoTelephony.getConferenceMember(
callPeer,
aVideo);
if (isConferenceMemberCallPeer(
conferenceMember,
callPeer))
{
video = aVideo;
break;
}
}
}
}
ConferenceParticipantContainer cpc = null;
if (callPeerPanel instanceof ConferenceParticipantContainer)
{
cpc = (ConferenceParticipantContainer) callPeerPanel;
if (cpc.getParticipant() == callPeer)
cpc.setVideo(video);
else
cpc = null;
}
if (cpc == null)
cpc = new ConferenceParticipantContainer(callPeer, video);
callPeerPanel = cpc;
// Update the conferenceMemberContainers of the cpc.
updateConferenceMemberContainers(cpc, videos, videoTelephony);
}
return callPeerPanel;
}
/**
* {@inheritDoc}
*
* If {@link #SHOW_TOOLBARS} is <tt>false</tt>, disables the use of
* <tt>ConferenceParticipantContainer</tt>. A reason for such a value of
* <tt>SHOW_TOOLBARS</tt> may be that the functionality implemented in the
* model may not fully support mapping of visual <tt>Component</tt>s
* displaying video to telephony conference participants (e.g. in telephony
* conferences utilizing the Jitsi Videobridge server-side technology). In
* such a case displays the videos only, does not map videos to participants
* and does not display participants who do not have videos.
*/
@Override
protected void updateViewFromModelInEventDispatchThread()
{
if (SHOW_TOOLBARS)
{
super.updateViewFromModelInEventDispatchThread();
return;
}
/*
* Determine the set of visual Components displaying video streaming
* between the local peer/user and the remote peers which are to be
* depicted by this instance.
*/
Component localVideo = null;
Set<Component> videos = new HashSet<Component>();
for (Call call : callConference.getCalls())
{
OperationSetVideoTelephony videoTelephony
= call.getProtocolProvider().getOperationSet(
OperationSetVideoTelephony.class);
if (videoTelephony == null)
continue;
Iterator<? extends CallPeer> callPeerIter = call.getCallPeers();
while (callPeerIter.hasNext())
{
CallPeer callPeer = callPeerIter.next();
/*
* TODO VideoConferenceCallPanel respects
* UIVideoHandler2.isLocalVideoVisible() in order to react to
* the associated button at the bottom of the CallPanel.
* However, it does not add a close button on top of the local
* video in contrast to OneToOneCallPeerPanel. Overall, the
* result is questionable.
*/
if (uiVideoHandler.isLocalVideoVisible()
&& (localVideo == null))
{
try
{
localVideo
= videoTelephony.getLocalVisualComponent(callPeer);
}
catch (OperationFailedException ofe)
{
/*
* We'll just try to get the local video through another
* CallPeer then.
*/
}
if (localVideo != null)
videos.add(localVideo);
}
List<Component> callPeerRemoteVideos
= videoTelephony.getVisualComponents(callPeer);
videos.addAll(callPeerRemoteVideos);
}
}
/*
* Remove the Components of this view which are no longer present in the
* model.
*/
Iterator<Component> thisVideoIter = this.videos.iterator();
while (thisVideoIter.hasNext())
{
Component thisVideo = thisVideoIter.next();
if (!videos.contains(thisVideo))
{
thisVideoIter.remove();
videoContainer.remove(thisVideo);
}
/*
* If a video is known to be depicted by this view and is still
* present in the model, then we could remove it from the set of
* videos present in the model in order to prevent going through the
* procedure of adding it to this view. However, we choose to play
* on the safe side.
*/
}
/*
* Add the Components of the model which are not depicted by this view.
*/
for (Component video : videos)
{
if (!UIVideoHandler2.isAncestor(videoContainer, video))
{
this.videos.add(video);
videoContainer.add(
video,
(video == localVideo)
? VideoLayout.LOCAL
: VideoLayout.CENTER_REMOTE);
}
}
}
@Override
protected void viewForModelAdded(
ConferenceCallPeerRenderer callPeerPanel,
CallPeer callPeer)
{
videoContainer.add(
callPeerPanel.getComponent(),
VideoLayout.CENTER_REMOTE);
if ((callPeer != null)
&& (callPeerPanel instanceof ConferenceParticipantContainer))
{
addConferenceMemberContainers(
(ConferenceParticipantContainer) callPeerPanel);
}
}
@Override
protected void viewForModelRemoved(
ConferenceCallPeerRenderer callPeerPanel,
CallPeer callPeer)
{
videoContainer.remove(callPeerPanel.getComponent());
if ((callPeer != null)
&& (callPeerPanel instanceof ConferenceParticipantContainer))
{
removeConferenceMemberContainers(
(ConferenceParticipantContainer) callPeerPanel,
true);
}
}
/**
* Implements an AWT <tt>Component</tt> which contains the user interface
* elements depicting a specific participant in the telephony conference
* depicted by a <tt>VideoConferenceCallPanel</tt>.
*/
private class ConferenceParticipantContainer
extends TransparentPanel
implements ConferenceCallPeerRenderer
{
/**
* The list of <tt>ConferenceParticipantContainer</tt>s which represent
* the <tt>ConferenceMember</tt>s of the participant represented by this
* instance. Since a <tt>CallPeer</tt> may send the local peer/user
* multiple videos without providing a way to associate a
* ConferenceMember with each one of them, the list may contain
* <tt>ConferenceParticipantContainer</tt>s which do not represent a
* specific <tt>ConferenceMember</tt> instance but rather a video sent
* by a <tt>CallPeer</tt> to the local peer/user which looks like (in
* the terms of <tt>VideoConferenceCallPanel) a member of a conference
* organized by the <tt>CallPeer</tt> in question.
* <p>
* Implements a state which is private to
* <tt>VideoConferenceCallPanel</tt> and is of no concern to
* <tt>ConferenceParticipantContainer</tt>.
* </p>
*/
List<ConferenceParticipantContainer> conferenceMemberContainers;
/**
* The indicator which determines whether this instance is to be removed
* because it has become out-of-date, obsolete, unnecessary.
* <p>
* Implements a state which is private to
* <tt>VideoConferenceCallPanel</tt> and is of no concern to
* <tt>ConferenceParticipantContainer</tt>.
* </p>
*/
boolean toBeRemoved;
/**
* The <tt>BasicConferenceParticipantPanel</tt> which is displayed at
* the bottom of this instance, bellow the {@link #video} (i.e.
* {@link #videoContainer}) and is referred to as the tool bar.
*/
private final BasicConferenceParticipantPanel<?> toolBar;
/**
* The visual <tt>Component</tt>, if any, displaying video which is
* depicted by this instance.
*/
private Component video;
/**
* The <tt>VideoContainer</tt> which lays out the video depicted by this
* instance i.e. {@link #video}.
*/
private final VideoContainer videoContainer;
/**
* The <tt>CallPeer</tt> associated with this container, if it has been
* created to represent a <tt>CallPeer</tt>.
*/
private CallPeer callPeer;
/**
* The <tt>conferenceMember</tt> associated with this container, if it
* has been created to represent a <tt>conferenceMember</tt>.
*/
private ConferenceMember conferenceMember;
/**
* Indicates that this container contains information for the local
* user.
*/
private boolean isLocalUser;
/**
* Initializes a new <tt>ConferenceParticipantContainer</tt> instance
* which is to depict the local peer/user.
*
* @param call a <tt>Call</tt> which is to provide information about the
* local peer/user
* @param video the visual <tt>Component</tt>, if any, displaying the
* video streaming from the local peer/user to the remote peer(s)
*/
public ConferenceParticipantContainer(Call call, Component video)
{
this(
createDefaultPhotoPanel(call),
video,
new ConferencePeerPanel(
VideoConferenceCallPanel.this,
call,
true),
null, null, true);
}
public ConferenceParticipantContainer(
CallPeer callPeer,
Component video)
{
this( createDefaultPhotoPanel(callPeer),
video,
new ConferencePeerPanel(
VideoConferenceCallPanel.this,
callPeer,
true),
callPeer, null, false);
}
private ConferenceParticipantContainer(
Component noVideo,
Component video,
BasicConferenceParticipantPanel<?> toolBar,
CallPeer callPeer,
ConferenceMember conferenceMember,
boolean isLocalUser)
{
super(new BorderLayout());
this.callPeer = callPeer;
this.conferenceMember = conferenceMember;
this.isLocalUser = isLocalUser;
videoContainer = new VideoContainer(noVideo, false);
add(videoContainer, BorderLayout.CENTER);
this.toolBar = toolBar;
if (this.toolBar != null)
add(this.toolBar, BorderLayout.SOUTH);
if (video != null)
{
setVideo(video);
}
else
setVisible(false);
}
public ConferenceParticipantContainer(
ConferenceMember conferenceMember,
Component video)
{
this(
createDefaultPhotoPanel(conferenceMember),
video,
new ConferenceMemberPanel(
VideoConferenceCallPanel.this,
conferenceMember,
true),
null, conferenceMember, false);
}
public void dispose()
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
if (delegate != null)
delegate.dispose();
// Dispose of the conferenceMemberContainers if any.
/*
* XXX The field conferenceMemberContainers implements a state
* private to VideoConferenceCallPanel which the latter makes sure
* to access on the AWT event dispatching thread only. Since we are
* going out of our way here to help VideoConferenceCallPanel,
* ensure that the mentioned synchronization rule is not violated.
*/
CallManager.assertIsEventDispatchingThread();
if (conferenceMemberContainers != null)
{
for (ConferenceParticipantContainer cmc
: conferenceMemberContainers)
{
cmc.dispose();
}
}
}
public CallPanel getCallPanel()
{
return getCallRenderer().getCallContainer();
}
public SwingCallRenderer getCallRenderer()
{
return VideoConferenceCallPanel.this;
}
public Component getComponent()
{
return this;
}
private ConferenceCallPeerRenderer
getConferenceCallPeerRendererDelegate()
{
return
(toolBar instanceof ConferenceCallPeerRenderer)
? (ConferenceCallPeerRenderer) toolBar
: null;
}
/**
* Gets the conference participant depicted by this instance.
*
* @return the conference participant depicted by this instance
*/
public Object getParticipant()
{
return (toolBar == null) ? null : toolBar.getParticipant();
}
public Component getVideo()
{
return video;
}
/**
* {@inheritDoc}
*
* Delegates to the <tt>toolBar</tt>, if the latter implements
* <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
* container only. Otherwise, returns <tt>false</tt>.
*/
public boolean isLocalVideoVisible()
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
return (delegate == null) ? false : delegate.isLocalVideoVisible();
}
/**
* {@inheritDoc}
*
* Delegates to the <tt>toolBar</tt>, if the latter implements
* <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
* container only.
*/
public void printDTMFTone(char dtmfChar)
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
if (delegate != null)
delegate.printDTMFTone(dtmfChar);
}
/**
* {@inheritDoc}
*
* Delegates to the <tt>toolBar</tt>, if the latter implements
* <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
* container only.
*/
public void securityNegotiationStarted(
CallPeerSecurityNegotiationStartedEvent ev)
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
if (delegate != null)
delegate.securityNegotiationStarted(ev);
}
/**
* {@inheritDoc}
*
* Delegates to the <tt>toolBar</tt>, if the latter implements
* <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
* container only.
*/
public void securityOff(CallPeerSecurityOffEvent ev)
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
if (delegate != null)
delegate.securityOff(ev);
}
/**
* {@inheritDoc}
*
* Delegates to the <tt>toolBar</tt>, if the latter implements
* <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
* container only.
*/
public void securityOn(CallPeerSecurityOnEvent ev)
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
if (delegate != null)
delegate.securityOn(ev);
}
/**
* {@inheritDoc}
*
* Delegates to the <tt>toolBar</tt>, if the latter implements
* <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
* container only.
*/
public void securityPending()
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
if (delegate != null)
delegate.securityPending();
}
/**
* {@inheritDoc}
*
* Delegates to the <tt>toolBar</tt>, if the latter implements
* <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
* container only.
*/
public void securityTimeout(CallPeerSecurityTimeoutEvent ev)
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
if (delegate != null)
delegate.securityTimeout(ev);
}
/**
* {@inheritDoc}
*
* Delegates to the <tt>toolBar</tt>, if the latter implements
* <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
* container only.
*/
public void setErrorReason(String reason)
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
if (delegate != null)
delegate.setErrorReason(reason);
}
/**
* {@inheritDoc}
*
* Delegates to the <tt>toolBar</tt>, if the latter implements
* <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
* container only.
*/
public void setLocalVideoVisible(boolean visible)
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
if (delegate != null)
delegate.setLocalVideoVisible(visible);
}
/**
* {@inheritDoc}
*
* Delegates to the <tt>toolBar</tt>, if the latter implements
* <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
* container only.
*/
public void setMute(boolean mute)
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
if (delegate != null)
delegate.setMute(mute);
}
/**
* {@inheritDoc}
*
* Delegates to the <tt>toolBar</tt>, if the latter implements
* <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
* container only.
*/
public void setOnHold(boolean onHold)
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
if (delegate != null)
delegate.setOnHold(onHold);
}
/**
* {@inheritDoc}
*
* Delegates to the <tt>toolBar</tt>, if the latter implements
* <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
* container only.
*/
public void setPeerImage(byte[] image)
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
if (delegate != null)
delegate.setPeerImage(image);
}
/**
* {@inheritDoc}
*
* Delegates to the <tt>toolBar</tt>, if the latter implements
* <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
* container only.
*/
public void setPeerName(String name)
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
if (delegate != null)
delegate.setPeerName(name);
}
/**
* {@inheritDoc}
*
* Delegates to the <tt>toolBar</tt>, if the latter implements
* <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
* container only.
*/
public void setPeerState(
CallPeerState oldState,
CallPeerState newState,
String stateString)
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
if (delegate != null)
delegate.setPeerState(oldState, newState, stateString);
}
/**
* {@inheritDoc}
*
* Delegates to the <tt>toolBar</tt>, if the latter implements
* <tt>ConferenceCallPeerRenderer</tt>, because this instance is a
* container only.
*/
public void setSecurityPanelVisible(boolean visible)
{
ConferenceCallPeerRenderer delegate
= getConferenceCallPeerRendererDelegate();
if (delegate != null)
delegate.setSecurityPanelVisible(visible);
}
/**
* Sets the visual <tt>Component</tt> displaying the video associated
* with the participant depicted by this instance.
*
* @param video the visual <tt>Component</tt> displaying video which is
* to be associated with the participant depicted by this instance
*/
void setVideo(Component video)
{
if (this.video != video)
{
if (this.video != null)
videoContainer.remove(this.video);
this.video = video;
if (this.video != null)
{
setVisible(true);
videoContainer.add(this.video, VideoLayout.CENTER_REMOTE);
}
else
setVisible(false);
// Update thumbnails container according to video status.
if (thumbnailContainer != null)
{
if (conferenceMember != null)
thumbnailContainer
.updateThumbnail(conferenceMember, (video != null));
else if (callPeer != null)
thumbnailContainer
.updateThumbnail(callPeer, (video != null));
else if (isLocalUser)
thumbnailContainer
.updateThumbnail((video != null));
}
}
}
}
}