Adds support for fullscreen display and local video in calls.

cusax-fix
Lyubomir Marinov 17 years ago
parent 510925a175
commit 1cd6a92981

@ -63,6 +63,8 @@ MUTE_BUTTON=resources/images/impl/gui/buttons/muteButton.png
TRANSFER_CALL_BUTTON=resources/images/impl/gui/buttons/transferCallButton.png
SECURE_BUTTON_ON=resources/images/impl/gui/buttons/secureOn.png
SECURE_BUTTON_OFF=resources/images/impl/gui/buttons/secureOff.png
ENTER_FULL_SCREEN_BUTTON=resources/images/impl/gui/buttons/enterFullScreen.png
EXIT_FULL_SCREEN_BUTTON=resources/images/impl/gui/buttons/exitFullScreen.png
INVITE_DIALOG_ICON=resources/images/impl/gui/common/inviteDialogIcon.png
SEND_SMS_ICON=resources/images/impl/gui/common/gsm.png
DIAL_BUTTON_BG=resources/images/impl/gui/buttons/dialButtonBg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

@ -18,6 +18,7 @@
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.*;
/**
* The <tt>CallParticipantPanel</tt> is the panel containing data for a call
@ -30,6 +31,9 @@
public class CallParticipantPanel
extends TransparentPanel
{
private static final Logger logger =
Logger.getLogger(CallParticipantPanel.class);
private final JLabel stateLabel = new JLabel("Unknown", JLabel.CENTER);
private final JLabel timeLabel = new JLabel("00:00:00", JLabel.CENTER);
@ -50,6 +54,13 @@ public class CallParticipantPanel
private final CallParticipant callParticipant;
private final java.util.List<Container> videoContainers =
new ArrayList<Container>();
private OperationSetVideoTelephony videoTelephony;
private Component localVideo;
/**
* Creates a <tt>CallParticipantPanel</tt> for the given call participant.
*
@ -109,6 +120,8 @@ public CallParticipantPanel(CallParticipant callParticipant)
this.timer = new Timer(1000, new CallTimerListener());
this.timer.setRepeats(true);
addVideoListener();
}
/**
@ -122,18 +135,30 @@ private Component createButtonBar()
{
Component[] buttons =
new Component[]
{ new HoldButton(this.callParticipant),
new MuteButton(this.callParticipant),
createTransferCallButton(), createSecureCallButton() };
{ new HoldButton(callParticipant), new MuteButton(callParticipant),
createTransferCallButton(), createSecureCallButton(),
createEnterFullScreenButton() };
Dimension preferredButtonSize = new Dimension(24, 24);
return createButtonBar(buttons, preferredButtonSize);
}
private Component createButtonBar(Component[] buttons,
Dimension preferredButtonSize)
{
Container buttonBar = new TransparentPanel(new GridLayout(1, 0));
for (int buttonIndex = 0; buttonIndex < buttons.length; buttonIndex++)
{
Component button = buttons[buttonIndex];
if (button != null)
{
button.setPreferredSize(new Dimension(24, 24));
if ((button instanceof JButton)
|| (button instanceof JToggleButton))
{
button.setPreferredSize(preferredButtonSize);
}
buttonBar.add(button);
}
}
@ -151,17 +176,145 @@ private Component createButtonBar()
*/
private Component createCenter()
{
JLabel photoLabel =
final JLabel photoLabel =
new JLabel(new ImageIcon(ImageLoader
.getImage(ImageLoader.DEFAULT_USER_PHOTO)));
photoLabel.setPreferredSize(new Dimension(90, 90));
JPanel center = new TransparentPanel(new FitLayout());
center.add(photoLabel);
final Container videoContainer = createVideoContainer(photoLabel);
videoContainer.addHierarchyListener(new HierarchyListener()
{
public void hierarchyChanged(HierarchyEvent event)
{
int changeFlags = HierarchyEvent.DISPLAYABILITY_CHANGED;
if ((event.getChangeFlags() & changeFlags) == changeFlags)
{
synchronized (videoContainers)
{
boolean changed = false;
if (videoContainer.isDisplayable())
{
if (!videoContainers.contains(videoContainer))
changed = videoContainers.add(videoContainer);
}
else
changed = videoContainers.remove(videoContainer);
if (changed)
handleVideoEvent(null);
}
}
}
});
return videoContainer;
}
/**
* Creates a new AWT <code>Container</code> which can display a single
* <code>Component</code> at a time (supposedly, one which represents video)
* and, in the absence of such a <code>Component</code>, displays a
* predefined default <code>Component</code> (in accord with the previous
* supposition, one which is the default when there is no video). The
* returned <code>Container</code> will track the <code>Components</code>s
* added to and removed from it in order to make sure that
* <code>noVideoContainer</code> is displayed as described.
*
* @param noVideoComponent the predefined default <code>Component</code> to
* be displayed in the returned <code>Container</code> when there
* is no other <code>Component</code> in it
* @return a new <code>Container</code> which can display a single
* <code>Component</code> at a time and, in the absence of such a
* <code>Component</code>, displays <code>noVideoComponent</code>
*/
private Container createVideoContainer(final Component noVideoComponent)
{
final ContainerListener containerListener = new ContainerListener()
{
/*
* Since the videoContainer displays either noVideoComponent or a
* single visual Component which represents video, ensures the last
* Component added to the Container is the only Component it
* contains i.e. noVideoComponent goes away when the video is
* displayed and the video goes away when noVideoComponent is
* displayed.
*/
public void componentAdded(ContainerEvent event)
{
Container container = event.getContainer();
Component local =
((VideoLayout) container.getLayout()).getLocal();
Component added = event.getChild();
if ((local != null) && (added == local))
return;
Component[] components = container.getComponents();
boolean validate = false;
for (int i = 0; i < components.length; i++)
{
Component component = components[i];
if ((component != added) && (component != local))
{
container.remove(component);
validate = true;
}
}
if (validate)
container.validate();
}
/*
* Displays noVideoComponent when there is no visual Component which
* represents video to be displayed.
*/
public void componentRemoved(ContainerEvent event)
{
Container container = event.getContainer();
if ((container.getComponentCount() <= 0)
|| (((VideoLayout) container.getLayout()).getRemote() == null))
{
container.add(noVideoComponent, VideoLayout.REMOTE);
container.validate();
}
}
};
Container videoContainer = new TransparentPanel(new VideoLayout())
{
addVideoListener(center, photoLabel);
/*
* Ensures noVideoComponent is displayed even when the clients of
* the videoContainer invoke its #removeAll() to remove their
* previous visual Components representing video. Just adding
* noVideoComponent upon ContainerEvent#COMPONENT_REMOVED when there
* is no other Component left in the Container will cause an
* infinite loop because Container#removeAll() will detect that a
* new Component has been added while dispatching the event and will
* then try to remove the new Component.
*/
public void removeAll()
{
removeContainerListener(containerListener);
try
{
super.removeAll();
}
finally
{
addContainerListener(containerListener);
containerListener.componentRemoved(new ContainerEvent(this,
ContainerEvent.COMPONENT_REMOVED, null));
}
}
};
return center;
videoContainer.addContainerListener(containerListener);
videoContainer.add(noVideoComponent, VideoLayout.REMOTE);
return videoContainer;
}
/**
@ -286,41 +439,33 @@ private Component createSecureCallLabel()
/**
* Sets up listening to notifications about adding or removing video for the
* <code>CallParticipant</code> this panel depicts and displays the video in
* question in a specific visual <code>Container</code> (currently, the
* central UI area) as soon as it arrives. If the video is removed at a
* later point, the method reverts to showing a specific default visual
* <code>Component</code> (currently, the photo of the
* <code>CallParticipant</code>).
*
* @param videoContainer the visual <code>Container</code> the display area
* of which will display video when it's available
* @param noVideoComponent the default visual <code>Component</code> to be
* displayed in <code>videoContainer</code> when previously
* displayed video is no longer available
* question in the last-known of {@link #videoContainers} (because the video
* is represented by a <code>Component</code> and it cannot be displayed in
* multiple <code>Container</code>s at one and the same time) as soon as it
* arrives.
*/
private void addVideoListener(final Container videoContainer,
final Component noVideoComponent)
private OperationSetVideoTelephony addVideoListener()
{
final Call call = callParticipant.getCall();
if (call == null)
return;
return null;
final OperationSetVideoTelephony telephony =
(OperationSetVideoTelephony) call.getProtocolProvider()
.getOperationSet(OperationSetVideoTelephony.class);
if (telephony == null)
return;
return null;
final VideoListener videoListener = new VideoListener()
{
public void videoAdded(VideoEvent event)
{
handleVideoEvent(telephony, videoContainer, noVideoComponent);
handleVideoEvent(event);
}
public void videoRemoved(VideoEvent event)
{
handleVideoEvent(telephony, videoContainer, noVideoComponent);
handleVideoEvent(event);
}
};
@ -336,9 +481,23 @@ public void videoRemoved(VideoEvent event)
private void addVideoListener()
{
telephony.addVideoListener(callParticipant, videoListener);
try
{
telephony.createLocalVisualComponent(callParticipant,
videoListener);
}
catch (OperationFailedException ex)
{
logger.error(
"Failed to create local video/visual Component.", ex);
}
videoListenerIsAdded = true;
handleVideoEvent(telephony, videoContainer, noVideoComponent);
synchronized (videoContainers)
{
videoTelephony = telephony;
handleVideoEvent(null);
}
}
/*
@ -406,39 +565,104 @@ else if (CallState.CALL_IN_PROGRESS.equals(newCallState))
private void removeVideoListener()
{
telephony.removeVideoListener(callParticipant, videoListener);
if (localVideo != null)
telephony.disposeLocalVisualComponent(callParticipant,
localVideo);
videoListenerIsAdded = false;
synchronized (videoTelephony)
{
if (telephony.equals(videoTelephony))
videoTelephony = null;
}
}
};
call.addCallChangeListener(callListener);
callListener.callStateChanged(new CallChangeEvent(call,
CallChangeEvent.CALL_STATE_CHANGE, null, call.getCallState()));
return telephony;
}
/**
* When a video is added or removed for the <code>callParticipant</code>,
* makes sure to display it or hide it respectively.
* makes sure to display or hide it respectively.
*
* @param telephony the <code>OperationSetVideoTelephony</code> of
* <code>callParticipant</code> which gives access to the video
* @param videoContainer the visual <code>Container</code> in which the
* video is to be displayed (currently, the central UI area
* displaying the photo when there is no video)
* @param noVideoComponent the default visual <code>Component</code> to be
* displayed in <code>videoContainer</code> when the previously
* added video is no longer received from the
* <code>callParticipant</code> (currently, the photo)
* @param event a <code>VideoEvent</code> describing the added visual
* <code>Component</code> representing video and the provider it
* was added into or <code>null</code> if such information is not
* available
*/
private synchronized void handleVideoEvent(
OperationSetVideoTelephony telephony, Container videoContainer,
Component noVideoComponent)
private void handleVideoEvent(final VideoEvent event)
{
Component[] videos = telephony.getVisualComponents(callParticipant);
Component video =
((videos == null) || (videos.length < 1)) ? null : videos[0];
synchronized (videoContainers)
{
if ((event != null) && !event.isConsumed()
&& (event.getOrigin() == VideoEvent.LOCAL))
{
Component localVideo = event.getVisualComponent();
videoContainer.removeAll();
videoContainer.add((video == null) ? noVideoComponent : video);
videoContainer.validate();
switch (event.getType())
{
case VideoEvent.VIDEO_ADDED:
this.localVideo = localVideo;
/*
* Let the creator of the local visual Component know it
* shouldn't be disposed of because we're going to use it.
*/
event.consume();
break;
case VideoEvent.VIDEO_REMOVED:
if (this.localVideo == localVideo)
this.localVideo = null;
break;
}
}
}
if (!SwingUtilities.isEventDispatchThread())
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
handleVideoEvent(event);
}
});
return;
}
synchronized (videoContainers)
{
int videoContainerCount;
if ((videoTelephony != null)
&& ((videoContainerCount = videoContainers.size()) > 0))
{
Container videoContainer =
videoContainers.get(videoContainerCount - 1);
int zOrder = 0;
videoContainer.removeAll();
// LOCAL
if (localVideo != null)
videoContainer.add(localVideo, VideoLayout.LOCAL, zOrder++);
// REMOTE
Component[] videos =
videoTelephony.getVisualComponents(callParticipant);
Component video =
((videos == null) || (videos.length < 1)) ? null
: videos[0];
if (video != null)
videoContainer.add(video, VideoLayout.REMOTE, zOrder++);
videoContainer.validate();
}
}
}
/**
@ -545,4 +769,172 @@ public String getParticipantName()
{
return participantName;
}
private Component createEnterFullScreenButton()
{
JButton button =
new JButton(new ImageIcon(ImageLoader
.getImage(ImageLoader.ENTER_FULL_SCREEN_BUTTON)));
button.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
enterFullScreen();
}
});
return button;
}
private Component createExitFullScreenButton()
{
JButton button =
new JButton(new ImageIcon(ImageLoader
.getImage(ImageLoader.EXIT_FULL_SCREEN_BUTTON)));
button.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
Object source = event.getSource();
Frame fullScreenFrame =
(source instanceof Component) ? TransferCallButton
.getFrame((Component) source) : null;
exitFullScreen(fullScreenFrame);
}
});
return button;
}
private Component createFullScreenButtonBar()
{
Component[] buttons =
new Component[]
{ new HoldButton(callParticipant), new MuteButton(callParticipant),
createExitFullScreenButton() };
Dimension preferredButtonSize = new Dimension(36, 36);
return createButtonBar(buttons, preferredButtonSize);
}
private void enterFullScreen()
{
// Create the main Components of the UI.
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setTitle(getParticipantName());
frame.setUndecorated(true);
Component center = createCenter();
final Component buttonBar = createFullScreenButtonBar();
// Lay out the main Components of the UI.
final Container contentPane = frame.getContentPane();
contentPane.setLayout(new FullScreenLayout(false));
if (center != null)
contentPane.add(center, FullScreenLayout.CENTER);
if (buttonBar != null)
{
contentPane.add(buttonBar, FullScreenLayout.SOUTH);
buttonBar.setVisible(false);
addMouseMotionListener(contentPane, new MouseMotionAdapter()
{
public void mouseMoved(MouseEvent event)
{
Component component = event.getComponent();
if ((component != null) && !component.equals(buttonBar))
{
Point pointInContentPane =
SwingUtilities.convertPoint(component, event
.getPoint(), contentPane);
buttonBar.setVisible(buttonBar.getBounds().contains(
pointInContentPane));
}
}
});
}
// Full-screen windows usually have black backgrounds.
Color background = Color.black;
contentPane.setBackground(background);
setBackground(center, background);
addKeyListener(frame, new KeyAdapter()
{
public void keyPressed(KeyEvent event)
{
if (!event.isConsumed()
&& (event.getKeyCode() == KeyEvent.VK_ESCAPE))
{
event.consume();
exitFullScreen(frame);
}
}
});
frame.addWindowStateListener(new WindowStateListener()
{
public void windowStateChanged(WindowEvent event)
{
switch (event.getID())
{
case WindowEvent.WINDOW_CLOSED:
case WindowEvent.WINDOW_DEACTIVATED:
case WindowEvent.WINDOW_ICONIFIED:
case WindowEvent.WINDOW_LOST_FOCUS:
exitFullScreen(frame);
break;
}
}
});
getGraphicsConfiguration().getDevice().setFullScreenWindow(frame);
}
private void exitFullScreen(Window fullScreenWindow)
{
getGraphicsConfiguration().getDevice().setFullScreenWindow(null);
if (fullScreenWindow != null)
{
if (fullScreenWindow.isVisible())
fullScreenWindow.setVisible(false);
fullScreenWindow.dispose();
}
}
private void setBackground(Component component, Color background)
{
component.setBackground(background);
if (component instanceof Container)
{
Component[] components = ((Container) component).getComponents();
for (int i = 0; i < components.length; i++)
setBackground(components[i], background);
}
}
private void addKeyListener(Component component, KeyListener l)
{
component.addKeyListener(l);
if (component instanceof Container)
{
Component[] components = ((Container) component).getComponents();
for (int i = 0; i < components.length; i++)
addKeyListener(components[i], l);
}
}
private void addMouseMotionListener(Component component,
MouseMotionListener l)
{
component.addMouseMotionListener(l);
if (component instanceof Container)
{
Component[] components = ((Container) component).getComponents();
for (int i = 0; i < components.length; i++)
addMouseMotionListener(components[i], l);
}
}
}

@ -49,6 +49,38 @@ protected Component getComponent(Container parent)
return (components.length > 0) ? components[0] : null;
}
protected void layoutComponent(Component component, Rectangle bounds,
float alignmentX, float alignmentY)
{
Dimension componentSize = component.getPreferredSize();
boolean scale = false;
double ratio = 1;
if ((componentSize.width != bounds.width) && (componentSize.width > 0))
{
scale = true;
ratio = bounds.width / (double) componentSize.width;
}
if ((componentSize.height != bounds.height)
&& (componentSize.height > 0))
{
scale = true;
ratio =
Math.min(bounds.height / (double) componentSize.height, ratio);
}
if (scale)
{
componentSize.width = (int) (componentSize.width * ratio);
componentSize.height = (int) (componentSize.height * ratio);
}
component.setBounds(bounds.x
+ Math.round((bounds.width - componentSize.width) * alignmentX),
bounds.y
+ Math.round((bounds.height - componentSize.height)
* alignmentY), componentSize.width, componentSize.height);
}
/*
* Scales the first Component if its preferred size is larger than the size
* of its parent Container in order to display the Component in its entirety
@ -59,36 +91,8 @@ public void layoutContainer(Container parent)
Component component = getComponent(parent);
if (component != null)
{
Dimension componentSize = component.getPreferredSize();
Dimension parentSize = parent.getSize();
boolean scale = false;
double ratio = 1;
if ((componentSize.width > parentSize.width)
&& (componentSize.width > 0))
{
scale = true;
ratio = parentSize.width / (double) componentSize.width;
}
if ((componentSize.height > parentSize.height)
&& (componentSize.height > 0))
{
scale = true;
ratio =
Math.min(parentSize.height / (double) componentSize.height,
ratio);
}
if (scale)
{
componentSize.width = (int) (componentSize.width * ratio);
componentSize.height = (int) (componentSize.height * ratio);
}
component.setBounds((parentSize.width - componentSize.width) / 2,
(parentSize.height - componentSize.height) / 2,
componentSize.width, componentSize.height);
}
layoutComponent(component, new Rectangle(parent.getSize()),
Component.CENTER_ALIGNMENT, Component.CENTER_ALIGNMENT);
}
/*

@ -0,0 +1,145 @@
/*
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.gui.main.call;
import java.awt.*;
public class FullScreenLayout implements LayoutManager
{
public static final String CENTER = "CENTER";
public static final String SOUTH = "SOUTH";
private Component center;
private final boolean overlay;
private Component south;
public FullScreenLayout(boolean overlay)
{
this.overlay = overlay;
}
public void addLayoutComponent(String name, Component comp)
{
if (CENTER.equals(name))
{
center = comp;
}
else if (SOUTH.equals(name))
{
south = comp;
}
}
private Component[] getLayoutComponents()
{
if (center == null)
{
if (south == null)
{
return new Component[0];
}
else
{
return new Component[]
{ south };
}
}
else if (south == null)
{
return new Component[]
{ center };
}
else
{
return new Component[]
{ center, south };
}
}
public void layoutContainer(Container parent)
{
Dimension parentSize = parent.getSize();
int southWidth;
int southHeight;
if (south == null)
{
southWidth = southHeight = 0;
}
else
{
Dimension southSize = south.getPreferredSize();
southWidth = southSize.width;
southHeight = southSize.height;
}
if (center != null)
{
int yOffset = overlay ? 0 : southHeight;
center.setBounds(0, yOffset, parentSize.width, parentSize.height
- 2 * yOffset);
}
if (south != null)
{
south.setBounds((parentSize.width - southWidth) / 2,
parentSize.height - southHeight, southWidth, southHeight);
}
}
public Dimension minimumLayoutSize(Container parent)
{
Component[] components = getLayoutComponents();
Dimension size = new Dimension(0, 0);
for (int i = 0; i < components.length; i++)
{
Dimension componentSize = components[i].getMinimumSize();
size.width = Math.max(size.width, componentSize.width);
if (overlay)
size.height = Math.max(size.height, componentSize.height);
else
size.height += componentSize.height;
}
return null;
}
public Dimension preferredLayoutSize(Container parent)
{
Component[] components = getLayoutComponents();
Dimension size = new Dimension(0, 0);
for (int i = 0; i < components.length; i++)
{
Dimension componentSize = components[i].getPreferredSize();
size.width = Math.max(size.width, componentSize.width);
if (overlay)
size.height = Math.max(size.height, componentSize.height);
else
size.height += componentSize.height;
}
return null;
}
public void removeLayoutComponent(Component comp)
{
if (comp.equals(center))
{
center = null;
}
else if (comp.equals(south))
{
south = null;
}
}
}

@ -92,7 +92,7 @@ private void actionPerformed(ActionListener listener, ActionEvent evt)
if (telephony != null)
{
final TransferCallDialog dialog =
new TransferCallDialog(getFrame());
new TransferCallDialog(getFrame(this));
/*
* Transferring a call works only when the call is in progress
@ -165,7 +165,7 @@ public void callStateChanged(CallChangeEvent evt)
* <code>OperationSetBasicTelephony</code> to have a specific address.
*
* @param telephony the <code>OperationSetBasicTelephony</code> to have its
* <code>CallParticipant</code>s examed in search for one which
* <code>CallParticipant</code>s examined in search for one which
* has a specific address
* @param address the address to locate the associated
* <code>CallParticipant</code> of
@ -245,20 +245,21 @@ private CallParticipant findCallParticipant(String address)
/**
* Gets the first <code>Frame</code> in the ancestor <code>Component</code>
* hierarchy of this button.
* hierarchy of a specific <code>Component</code>.
* <p>
* The located <code>Frame</code> (if any) is used as the owner of
* <code>Dialog</code>s opened by this button in order to provide natural
* <code>Frame</code> ownership.
* The located <code>Frame</code> (if any) is often used as the owner of
* <code>Dialog</code>s opened by the specified <code>Component</code> in
* order to provide natural <code>Frame</code> ownership.
* </p>
*
* @return the first <code>Frame</code> in the ancestor
* <code>Component</code> hierarchy of this button; <tt>null</tt>,
* if no such <code>Frame</code> was located
* <code>Component</code> hierarchy of the specified
* <code>Component</code>; <tt>null</tt>, if no such
* <code>Frame</code> was located
*/
private Frame getFrame()
public static Frame getFrame(Component component)
{
for (Component component = this; component != null;)
while (component != null)
{
Container container = component.getParent();

@ -0,0 +1,88 @@
/*
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.gui.main.call;
import java.awt.*;
/**
* @author Lubomir Marinov
*/
public class VideoLayout extends FitLayout
{
public static final String LOCAL = "LOCAL";
private static final float LOCAL_TO_REMOTE_RATIO = 0.25f;
public static final String REMOTE = "REMOTE";
private Component local;
private Component remote;
public void addLayoutComponent(String name, Component comp)
{
super.addLayoutComponent(name, comp);
if ((name == null) || name.equals(REMOTE))
remote = comp;
else if (name.equals(LOCAL))
local = comp;
}
protected Component getComponent(Container parent)
{
return getRemote();
}
public Component getLocal()
{
return local;
}
public Component getRemote()
{
return remote;
}
public void layoutContainer(Container parent)
{
super.layoutContainer(parent);
Component local = getLocal();
if (local != null)
{
Dimension parentSize = parent.getSize();
int height = Math.round(parentSize.height * LOCAL_TO_REMOTE_RATIO);
super.layoutComponent(local, new Rectangle(0, parentSize.height
- height, Math.round(parentSize.width * LOCAL_TO_REMOTE_RATIO),
height), Component.LEFT_ALIGNMENT, Component.BOTTOM_ALIGNMENT);
}
}
public Dimension minimumLayoutSize(Container parent)
{
// TODO Auto-generated method stub
return super.minimumLayoutSize(parent);
}
public Dimension preferredLayoutSize(Container parent)
{
// TODO Auto-generated method stub
return super.preferredLayoutSize(parent);
}
public void removeLayoutComponent(Component comp)
{
super.removeLayoutComponent(comp);
if (remote == comp)
remote = null;
else if (local == comp)
local = null;
}
}

@ -486,6 +486,20 @@ public class ImageLoader {
public static final ImageID SECURE_BUTTON_OFF =
new ImageID("SECURE_BUTTON_OFF");
/**
* The button icon of the Enter Full Screen command. The icon shown in the
* CallParticipant panel.
*/
public static final ImageID ENTER_FULL_SCREEN_BUTTON =
new ImageID("ENTER_FULL_SCREEN_BUTTON");
/**
* The button icon of the Exit Full Screen command. The icon shown in the
* CallParticipant panel.
*/
public static final ImageID EXIT_FULL_SCREEN_BUTTON =
new ImageID("EXIT_FULL_SCREEN_BUTTON");
/**
* The image used, when a contact has no photo specified.
*/

@ -207,6 +207,9 @@ public class CallSessionImpl
*/
private byte onHold;
private final Map<Component, LocalVisualComponentData> localVisualComponents =
new HashMap<Component, LocalVisualComponentData>();
/**
* List of RTP format strings which are supported by SIP Communicator in addition
* to the JMF standard formats.
@ -2321,7 +2324,8 @@ else if (newValue == CallState.CALL_ENDED)
Component visualComponent = getVisualComponent(player);
if (visualComponent != null)
{
fireVideoEvent(VideoEvent.VIDEO_REMOVED, visualComponent);
fireVideoEvent(VideoEvent.VIDEO_REMOVED, visualComponent,
VideoEvent.REMOTE);
}
player.deallocate();
@ -2627,7 +2631,8 @@ else if (ce instanceof RealizeCompleteEvent)
Component visualComponent = player.getVisualComponent();
if (visualComponent != null)
{
fireVideoEvent(VideoEvent.VIDEO_ADDED, visualComponent);
fireVideoEvent(VideoEvent.VIDEO_ADDED, visualComponent,
VideoEvent.REMOTE);
}
}
else if (ce instanceof StartEvent)
@ -2964,6 +2969,100 @@ public void addVideoListener(VideoListener listener)
}
}
public Component createLocalVisualComponent(final VideoListener listener)
throws MediaException
{
DataSource dataSource =
mediaServCallback.getMediaControl(getCall())
.createLocalVideoDataSource();
if (dataSource != null)
{
Player player;
try
{
player = Manager.createPlayer(dataSource);
}
catch (IOException ex)
{
throw new MediaException(
"Failed to create Player for local video DataSource.",
MediaException.IO_ERROR, ex);
}
catch (NoPlayerException ex)
{
throw new MediaException(
"Failed to create Player for local video DataSource.",
MediaException.INTERNAL_ERROR, ex);
}
player.addControllerListener(new ControllerListener()
{
public void controllerUpdate(ControllerEvent event)
{
controllerUpdateForCreateLocalVisualComponent(event,
listener);
}
});
player.start();
}
return null;
}
private void controllerUpdateForCreateLocalVisualComponent(
ControllerEvent controllerEvent, VideoListener listener)
{
if (controllerEvent instanceof RealizeCompleteEvent)
{
Player player = (Player) controllerEvent.getSourceController();
Component visualComponent = player.getVisualComponent();
if (visualComponent != null)
{
VideoEvent videoEvent =
new VideoEvent(this, VideoEvent.VIDEO_ADDED,
visualComponent, VideoEvent.LOCAL);
listener.videoAdded(videoEvent);
if (videoEvent.isConsumed())
{
localVisualComponents.put(visualComponent,
new LocalVisualComponentData(player, listener));
}
}
}
}
public void disposeLocalVisualComponent(Component component)
{
if (component == null)
throw new IllegalArgumentException("component");
LocalVisualComponentData data = localVisualComponents.get(component);
if (data != null)
{
Player player = data.player;
player.stop();
player.deallocate();
player.close();
localVisualComponents.remove(component);
VideoListener listener = data.listener;
if (listener != null)
{
VideoEvent videoEvent =
new VideoEvent(this, VideoEvent.VIDEO_REMOVED, component,
VideoEvent.LOCAL);
listener.videoRemoved(videoEvent);
}
}
}
/*
* Gets the visual Components of the #players of this CallSession by calling
* Player#getVisualComponent(). Ignores the failures to access the visual
@ -3033,8 +3132,10 @@ public void removeVideoListener(VideoListener listener)
* @param visualComponent the visual <code>Component</code> depicting video
* which has been added or removed in this
* <code>CallSession</code>
* @param origin
*/
protected void fireVideoEvent(int type, Component visualComponent)
protected void fireVideoEvent(int type, Component visualComponent,
int origin)
{
VideoListener[] listeners;
@ -3047,7 +3148,8 @@ protected void fireVideoEvent(int type, Component visualComponent)
if (listeners.length > 0)
{
VideoEvent event = new VideoEvent(this, type, visualComponent);
VideoEvent event =
new VideoEvent(this, type, visualComponent, origin);
for (int listenerIndex = 0; listenerIndex < listeners.length; listenerIndex++)
{
@ -3065,4 +3167,17 @@ protected void fireVideoEvent(int type, Component visualComponent)
}
}
}
private static class LocalVisualComponentData
{
public final VideoListener listener;
public final Player player;
public LocalVisualComponentData(Player player, VideoListener listener)
{
this.player = player;
this.listener = listener;
}
}
}

@ -52,6 +52,8 @@ public class MediaControl
*/
private MutePushBufferDataSource muteAudioDataSource;
private SourceCloneable cloneableVideoDataSource;
/**
* SDP Codes of all video formats that JMF supports.
*/
@ -402,6 +404,19 @@ private void initCaptureDevices()
// we will check video sizes and will set the most appropriate one
selectVideoSize(videoDataSource);
DataSource cloneableVideoDataSource =
Manager.createCloneableDataSource(videoDataSource);
if (cloneableVideoDataSource == null)
{
this.cloneableVideoDataSource = null;
}
else
{
videoDataSource = cloneableVideoDataSource;
this.cloneableVideoDataSource =
(SourceCloneable) cloneableVideoDataSource;
}
}
// Create the av data source
@ -1337,4 +1352,10 @@ private void selectVideoSize(DataSource videoDS)
if(targetFormat != null)
fControl.setFormat(targetFormat);
}
public DataSource createLocalVideoDataSource()
{
return (cloneableVideoDataSource == null) ? null
: cloneableVideoDataSource.createClone();
}
}

@ -50,6 +50,40 @@ public void addVideoListener(CallParticipant participant,
new InternalVideoListener(this, participant, listener));
}
public Component createLocalVisualComponent(CallParticipant participant,
VideoListener listener) throws OperationFailedException
{
CallSession callSession =
((CallSipImpl) participant.getCall()).getMediaCallSession();
if (callSession != null)
{
try
{
return callSession.createLocalVisualComponent(listener);
}
catch (MediaException ex)
{
throw new OperationFailedException(
"Failed to create visual Component for local video (capture).",
OperationFailedException.INTERNAL_ERROR, ex);
}
}
return null;
}
public void disposeLocalVisualComponent(CallParticipant participant,
Component component)
{
CallSession callSession =
((CallSipImpl) participant.getCall()).getMediaCallSession();
if (callSession != null)
{
callSession.disposeLocalVisualComponent(component);
}
}
/*
* Delegates to the CallSession of the Call of the specified CallParticipant
* because the video is provided by the CallSession in the SIP protocol
@ -176,8 +210,8 @@ public int hashCode()
*/
public void videoAdded(VideoEvent event)
{
delegate.videoAdded(new VideoEvent(this, VideoEvent.VIDEO_ADDED,
event.getVisualComponent()));
delegate.videoAdded(new VideoEvent(this, event.getType(), event
.getVisualComponent(), event.getOrigin()));
}
/*
@ -189,8 +223,8 @@ public void videoAdded(VideoEvent event)
*/
public void videoRemoved(VideoEvent event)
{
delegate.videoAdded(new VideoEvent(this, VideoEvent.VIDEO_REMOVED,
event.getVisualComponent()));
delegate.videoAdded(new VideoEvent(this, event.getType(), event
.getVisualComponent(), event.getOrigin()));
}
}
}

@ -236,6 +236,11 @@ public void setSecureCommunicationStatus(boolean activator,
*/
void addVideoListener(VideoListener listener);
Component createLocalVisualComponent(VideoListener listener)
throws MediaException;
void disposeLocalVisualComponent(Component component);
/**
* Gets the visual/video <code>Component</code>s available in this
* <code>CallSession</code>.

@ -34,6 +34,12 @@ public interface OperationSetVideoTelephony
*/
void addVideoListener(CallParticipant participant, VideoListener listener);
Component createLocalVisualComponent(CallParticipant participant,
VideoListener listener) throws OperationFailedException;
void disposeLocalVisualComponent(CallParticipant participant,
Component component);
/**
* Gets the visual/video <code>Component</code>s available in this telephony
* for a specific <code>CallParticipant</code>.

@ -19,6 +19,9 @@
public class VideoEvent
extends EventObject
{
public static final int LOCAL = 1;
public static final int REMOTE = 2;
/**
* The type of a <code>VideoEvent</code> which notifies about a specific
@ -34,6 +37,17 @@ public class VideoEvent
*/
public static final int VIDEO_REMOVED = 2;
/**
* The indicator which determines whether this event and, more specifically,
* the visual <code>Component</code> it describes have been consumed and
* should be considered owned, referenced (which is important because
* <code>Component</code>s belong to a single <code>Container</code> at a
* time).
*/
private boolean consumed;
private final int origin;
/**
* The type of availability change this <code>VideoEvent</code> notifies
* about which is one of {@link #VIDEO_ADDED} and {@link #VIDEO_REMOVED}.
@ -60,13 +74,33 @@ public class VideoEvent
* @param visualComponent the visual <code>Component</code> depicting video
* which had its availability in the <code>source</code> provider
* changed
* @param origin
*/
public VideoEvent(Object source, int type, Component visualComponent)
public VideoEvent(Object source, int type, Component visualComponent,
int origin)
{
super(source);
this.type = type;
this.visualComponent = visualComponent;
this.origin = origin;
}
/**
* Consumes this event and, more specifically, marks the
* <code>Component</code> it describes as owned, referenced in order to let
* other potential consumers know about its current ownership status (which
* is important because <code>Component</code>s belong to a single
* <code>Container</code> at a time).
*/
public void consume()
{
consumed = true;
}
public int getOrigin()
{
return origin;
}
/**
@ -96,4 +130,22 @@ public Component getVisualComponent()
{
return visualComponent;
}
/**
* Determines whether this event and, more specifically, the visual
* <code>Component</code> it describes have been consumed and should be
* considered owned, referenced (which is important because
* <code>Component</code>s belong to a single <code>Container</code> at a
* time).
*
* @return <tt>true</tt> if this event and, more specifically, the visual
* <code>Component</code> it describes have been consumed and should
* be considered owned, referenced (which is important because
* <code>Component</code>s belong to a single <code>Container</code>
* at a time); otherwise, <tt>false</tt>
*/
public boolean isConsumed()
{
return consumed;
}
}

@ -23,7 +23,7 @@ public interface VideoListener
* Notifies that a visual <code>Component</code> representing video has been
* added to the provider this listener has been added to.
*
* @param event a <code>VisualEvent</code> describing the added visual
* @param event a <code>VideoEvent</code> describing the added visual
* <code>Component</code> representing video and the provider it
* was added into
*/
@ -33,7 +33,7 @@ public interface VideoListener
* Notifies that a visual <code>Component</code> representing video has been
* removed from the provider this listener has been added to.
*
* @param event a <code>VisualEvent</code> describing the removed visual
* @param event a <code>VideoEvent</code> describing the removed visual
* <code>Component</code> representing video and the provider it
* was removed from
*/

Loading…
Cancel
Save