From e0e1a27e951b0278e438910f157a1695d4dab9c6 Mon Sep 17 00:00:00 2001
From: Lyubomir Marinov <lyubomir.marinov@jitsi.org>
Date: Wed, 24 Oct 2012 07:07:18 +0000
Subject: [PATCH] Fixes exceptions, a memory leak due to incomplete code.

---
 .../media/renderer/video/JAWTRenderer.java    |  13 +-
 .../impl/neomedia/RTPTranslatorImpl.java      |  18 +-
 .../device/VideoMediaDeviceSession.java       |  50 +++-
 src/org/jitsi/util/swing/FitLayout.java       |   9 +-
 src/org/jitsi/util/swing/VideoLayout.java     | 253 +++++++++++++-----
 5 files changed, 256 insertions(+), 87 deletions(-)

diff --git a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/renderer/video/JAWTRenderer.java b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/renderer/video/JAWTRenderer.java
index 07b66f37..f1568666 100644
--- a/src/net/java/sip/communicator/impl/neomedia/jmfext/media/renderer/video/JAWTRenderer.java
+++ b/src/net/java/sip/communicator/impl/neomedia/jmfext/media/renderer/video/JAWTRenderer.java
@@ -18,6 +18,7 @@
 import org.jitsi.service.configuration.*;
 import org.jitsi.service.libjitsi.*;
 import org.jitsi.util.*;
+import org.jitsi.util.swing.*;
 
 /**
  * Implements a <tt>VideoRenderer</tt> which uses JAWT to perform native
@@ -595,13 +596,19 @@ public Format setInputFormat(Format format)
              * Apart from the simplest of cases in which the component has no
              * preferredSize, it is also necessary to reflect the width and
              * height of the input onto the preferredSize when the ratio of the
-             * input is different than the ratio of the preferredSize.
+             * input is different than the ratio of the preferredSize. It may
+             * also be argued that the component needs to know of the width and
+             * height of the input if its preferredSize is with the same ratio
+             * but is smaller.
              */
             if ((preferredSize == null)
                     || (preferredSize.width < 1)
                     || (preferredSize.height < 1)
-                    || (preferredSize.width * inputHeight
-                            != preferredSize.height * inputWidth))
+                    || !VideoLayout.areAspectRatiosEqual(
+                            preferredSize,
+                            inputWidth, inputHeight)
+                    || (preferredSize.width < inputWidth)
+                    || (preferredSize.height < inputHeight))
             {
                 component.setPreferredSize(
                         new Dimension(inputWidth, inputHeight));
diff --git a/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java b/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java
index 2a6134aa..4a039be0 100644
--- a/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java
+++ b/src/org/jitsi/impl/neomedia/RTPTranslatorImpl.java
@@ -319,7 +319,23 @@ public synchronized SendStream createSendStream(
     public synchronized void dispose()
     {
         manager.removeReceiveStreamListener(this);
-        manager.dispose();
+        try
+        {
+            manager.dispose();
+        }
+        catch (Throwable t)
+        {
+            if (t instanceof ThreadDeath)
+                throw (ThreadDeath) t;
+            else
+            {
+                /*
+                 * RTPManager.dispose() often throws at least a
+                 * NullPointerException in relation to some RTP BYE.
+                 */
+                logger.error("Failed to dispose of RTPManager", t);
+            }
+        }
     }
 
     public synchronized void dispose(StreamRTPManager streamRTPManager)
diff --git a/src/org/jitsi/impl/neomedia/device/VideoMediaDeviceSession.java b/src/org/jitsi/impl/neomedia/device/VideoMediaDeviceSession.java
index 2470f221..99271d51 100644
--- a/src/org/jitsi/impl/neomedia/device/VideoMediaDeviceSession.java
+++ b/src/org/jitsi/impl/neomedia/device/VideoMediaDeviceSession.java
@@ -32,6 +32,7 @@
 import org.jitsi.service.resources.*;
 import org.jitsi.util.*;
 import org.jitsi.util.event.*;
+import org.jitsi.util.swing.*;
 
 /**
  * Extends <tt>MediaDeviceSession</tt> to add video-specific functionality.
@@ -1132,13 +1133,42 @@ public void run()
 
         if (visualComponent != null)
         {
-            fireVideoEvent(
-                    new SizeChangeVideoEvent(
-                            this,
-                            visualComponent,
-                            origin,
-                            width, height),
-                    false);
+            /*
+             * The Player will notice the new size and will notify about it
+             * before it reaches the Renderer. The notification/event may as
+             * well arrive before the Renderer reflects the new size onto the
+             * preferredSize of the Component. In order to make sure that the
+             * new size is reflected on the preferredSize of the Component
+             * before the notification/event arrives to its
+             * destination/listener, reflect it as soon as possible i.e. now.
+             */
+            try
+            {
+                Dimension preferredSize = visualComponent.getPreferredSize();
+
+                if ((preferredSize == null)
+                        || (preferredSize.width < 1)
+                        || (preferredSize.height < 1)
+                        || !VideoLayout.areAspectRatiosEqual(
+                                preferredSize,
+                                width, height)
+                        || (preferredSize.width < width)
+                        || (preferredSize.height < height))
+                {
+                    visualComponent.setPreferredSize(
+                            new Dimension(width, height));
+                }
+            }
+            finally
+            {
+                fireVideoEvent(
+                        new SizeChangeVideoEvent(
+                                this,
+                                visualComponent,
+                                origin,
+                                width, height),
+                        false);
+            }
         }
     }
 
@@ -1245,10 +1275,8 @@ private void playerVisualComponentResized(
             int outputHeightDelta
                 = outputSize.height - playerScalerOutputSize.height;
 
-            if ((outputWidthDelta < -1)
-                    || (outputWidthDelta > 1)
-                    || (outputHeightDelta < -1)
-                    || (outputHeightDelta > 1))
+            if ((outputWidthDelta < -1) || (outputWidthDelta > 1)
+                    || (outputHeightDelta < -1) || (outputHeightDelta > 1))
             {
                 playerScaler.setOutputSize(outputSize);
             }
diff --git a/src/org/jitsi/util/swing/FitLayout.java b/src/org/jitsi/util/swing/FitLayout.java
index fb7615d8..6af174e3 100644
--- a/src/org/jitsi/util/swing/FitLayout.java
+++ b/src/org/jitsi/util/swing/FitLayout.java
@@ -25,9 +25,12 @@ public class FitLayout
     implements LayoutManager
 {
 
-    /*
-     * Does nothing because this LayoutManager lays out only the first Component
-     * of the parent Container and thus doesn't need String associations.
+    /**
+     * {@inheritDoc}
+     *
+     * Does nothing because this <tt>LayoutManager</tt> lays out only the first
+     * <tt>Component</tt> of the parent <tt>Container</tt> and thus doesn't need
+     * any <tt>String</tt> associations.
      */
     public void addLayoutComponent(String name, Component comp)
     {
diff --git a/src/org/jitsi/util/swing/VideoLayout.java b/src/org/jitsi/util/swing/VideoLayout.java
index 37c96bcc..d3a94827 100644
--- a/src/org/jitsi/util/swing/VideoLayout.java
+++ b/src/org/jitsi/util/swing/VideoLayout.java
@@ -77,7 +77,7 @@ public class VideoLayout
     /**
      * The map of component constraints.
      */
-    private final HashMap<Component, Object> constraints
+    private final Map<Component, Object> constraints
         = new HashMap<Component, Object>();
 
     /**
@@ -122,17 +122,19 @@ public void addLayoutComponent(String name, Component comp)
 
         synchronized (constraints)
         {
-            this.constraints.put(comp, name);
+            constraints.put(comp, name);
         }
 
         if ((name == null) || name.equals(CENTER_REMOTE))
         {
-            remotes.add(comp);
+            if (!remotes.contains(comp))
+                remotes.add(comp);
             remoteAlignmentX = Component.CENTER_ALIGNMENT;
         }
         else if (name.equals(EAST_REMOTE))
         {
-            remotes.add(comp);
+            if (!remotes.contains(comp))
+                remotes.add(comp);
             remoteAlignmentX = Component.RIGHT_ALIGNMENT;
         }
         else if (name.equals(LOCAL))
@@ -143,6 +145,39 @@ else if (name.equals(CANVAS))
             canvas = comp;
     }
 
+    /**
+     * Determines whether the aspect ratio of a specific <tt>Dimension</tt> is
+     * to be considered equal to the aspect ratio of specific <tt>width</tt> and
+     * <tt>height</tt>.
+     *
+     * @param size the <tt>Dimension</tt> whose aspect ratio is to be compared
+     * to the aspect ratio of <tt>width</tt> and <tt>height</tt>
+     * @param width the width which defines in combination with <tt>height</tt>
+     * the aspect ratio to be compared to the aspect ratio of <tt>size</tt>
+     * @param height the height which defines in combination with <tt>width</tt>
+     * the aspect ratio to be compared to the aspect ratio of <tt>size</tt>
+     * @return <tt>true</tt> if the aspect ratio of <tt>size</tt> is to be
+     * considered equal to the aspect ratio of <tt>width</tt> and
+     * <tt>height</tt>; otherwise, <tt>false</tt>
+     */
+    public static boolean areAspectRatiosEqual(
+            Dimension size,
+            int width, int height)
+    {
+        if (size.height == 0)
+            return (height == 0);
+        else if (height == 0)
+            return false;
+        else
+        {
+            double a = size.width / size.height;
+            double b = width / height;
+            double difference = a - b;
+
+            return (-0.01 < difference) && (difference < 0.01);
+        }
+    }
+
     /**
      * Determines how may columns to use for the grid display of specific remote
      * visual/video <tt>Component</tt>s.
@@ -210,26 +245,35 @@ public Component getLocalCloseButton()
     }
 
     /**
-     * Lays out this given container.
+     * Lays out the specified <tt>Container</tt> (i.e. the <tt>Component</tt>s
+     * it contains) in accord with the logic implemented by this
+     * <tt>LayoutManager</tt>.
      *
-     * @param parent the container to lay out
+     * @param parent the <tt>Container</tt> to lay out
      */
     @Override
     public void layoutContainer(Container parent)
     {
+        /*
+         * XXX The methods layoutContainer and preferredLayoutSize must be kept
+         * in sync.
+         */
+
         List<Component> remotes;
         Component local = getLocal();
 
         /*
          * When there are multiple remote visual/video Components, the local one
          * will be displayed as if it is a remote one i.e. in the same grid, not
-         * on top of a remote one.
+         * on top of a remote one. The same layout will be used when this
+         * instance is dedicated to a telephony conference.
          */
-        if ((this.remotes.size() > 1) && (local != null))
+        if (conference || ((this.remotes.size() > 1) && (local != null)))
         {
             remotes = new ArrayList<Component>();
             remotes.addAll(this.remotes);
-            remotes.add(local);
+            if (local != null)
+                remotes.add(local);
         }
         else
             remotes = this.remotes;
@@ -237,9 +281,17 @@ public void layoutContainer(Container parent)
         int remoteCount = remotes.size();
         Dimension parentSize = parent.getSize();
 
-        if ((remoteCount == 1) && !conference)
+        if (!conference && (remoteCount == 1))
         {
-            super.layoutContainer(parent,
+            /*
+             * If the videos are to be laid out as in a one-to-one call, the
+             * remote video has to fill the parent and the local video will be
+             * placed on top of the remote video. The remote video will be laid
+             * out now and the local video will be laid out later/further
+             * bellow.
+             */
+            super.layoutContainer(
+                    parent,
                     (local == null)
                         ? Component.CENTER_ALIGNMENT
                         : remoteAlignmentX);
@@ -305,7 +357,7 @@ else if (remoteCount > 0)
              * If the local visual/video Component is not displayed as if it is
              * a remote one, it will be placed on top of a remote one.
              */
-            if (!remotes.contains(local) && !conference)
+            if (!remotes.contains(local))
             {
                 Component remote0 = remotes.isEmpty() ? null : remotes.get(0);
                 int localX;
@@ -321,7 +373,7 @@ else if (remoteCount > 0)
                  * that there is no remote video and the remote is the
                  * photoLabel.
                  */
-                if ((remotes.size() == 1) && (remote0 instanceof JLabel))
+                if ((remoteCount == 1) && (remote0 instanceof JLabel))
                 {
                     localX = (parentSize.width - width) / 2;
                     localY = parentSize.height - height;
@@ -340,6 +392,7 @@ else if (remoteCount > 0)
                         Component.BOTTOM_ALIGNMENT);
             }
 
+            /* The closeButton has to be on top of the local video. */
             if (closeButton != null)
             {
                 /*
@@ -363,26 +416,12 @@ else if (remoteCount > 0)
             }
         }
 
+        /*
+         * The video canvas will get the locations of the other components to
+         * paint so it has to cover the parent completely.
+         */
         if (canvas != null)
-        {
-            /*
-             * The video canvas will get the locations of the other components
-             * to paint so it has to cover the parent completely.
-             */
             canvas.setBounds(0, 0, parentSize.width, parentSize.height);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     *
-     * The <tt>VideoLayout</tt> implementation of the method does nothing.
-     */
-    @Override
-    public Dimension minimumLayoutSize(Container parent)
-    {
-        // TODO Auto-generated method stub
-        return super.minimumLayoutSize(parent);
     }
 
     /**
@@ -401,73 +440,144 @@ public Dimension preferredLayoutSize(Container parent)
         /*
          * When there are multiple remote visual/video Components, the local one
          * will be displayed as if it is a remote one i.e. in the same grid, not
-         * on top of a remote one.
+         * on top of a remote one. The same layout will be used when this
+         * instance is dedicated to a telephony conference.
          */
-        if ((this.remotes.size() > 1) && (local != null))
+        if (conference || ((this.remotes.size() > 1) && (local != null)))
         {
             remotes = new ArrayList<Component>();
             remotes.addAll(this.remotes);
-            remotes.add(local);
+            if (local != null)
+                remotes.add(local);
         }
         else
             remotes = this.remotes;
 
         int remoteCount = remotes.size();
+        Dimension preferredLayoutSize;
 
-        if (remoteCount == 0)
+        if (!conference && (remoteCount == 1))
         {
             /*
-             * If there is no remote visual/video Component, the local one will
-             * serve the preferredSize of the Container.
+             * If the videos are to be laid out as in a one-to-one call, the
+             * remote video has to fill the parent and the local video will be
+             * placed on top of the remote video. The remote video will be laid
+             * out now and the local video will be laid out later/further
+             * bellow.
              */
-            if (local != null)
+            preferredLayoutSize = super.preferredLayoutSize(parent);
+        }
+        else if (remoteCount > 0)
+        {
+            int columns = calculateColumnCount(remotes);
+            int columnsMinus1 = columns - 1;
+            int rows = (remoteCount + columnsMinus1) / columns;
+            int i = 0;
+            Dimension[] preferredSizes = new Dimension[columns * rows];
+
+            for (Component remote : remotes)
             {
-                Dimension preferredSize = local.getPreferredSize();
+                int column = columnsMinus1 - (i % columns);
+                int row = i / columns;
 
-                if (preferredSize != null)
-                    return preferredSize;
+                preferredSizes[column + row * columns]
+                    = remote.getPreferredSize();
+
+                i++;
+                if (i >= remoteCount)
+                    break;
             }
-        }
-        else if (remoteCount == 1)
-        {
-            /*
-             * If there is a single remote visual/video Component, the local one
-             * will be on top of it so the remote one will serve the
-             * preferredSize of the Container.
-             */
-            Dimension preferredSize = remotes.get(0).getPreferredSize();
 
-            if (preferredSize != null)
-                return preferredSize;
-        }
-        else if (remoteCount > 1)
-        {
-            int maxWidth = 0;
-            int maxHeight = 0;
+            int preferredLayoutWidth = 0;
 
-            for (Component remote : remotes)
+            for (int column = 0; column < columns; column++)
             {
-                Dimension preferredSize = remote.getPreferredSize();
+                int preferredColumnWidth = 0;
 
-                if (preferredSize != null)
+                for (int row = 0; row < rows; row++)
                 {
-                    if (maxWidth < preferredSize.width)
-                        maxWidth = preferredSize.width;
-                    if (maxHeight < preferredSize.height)
-                        maxHeight = preferredSize.height;
+                    Dimension preferredSize
+                        = preferredSizes[column + row * columns];
+
+                    if (preferredSize != null)
+                        preferredColumnWidth += preferredSize.width;
                 }
+                preferredColumnWidth /= rows;
+
+                preferredLayoutWidth += preferredColumnWidth;
             }
 
-            if ((maxWidth > 0) && (maxHeight > 0))
+            int preferredLayoutHeight = 0;
+
+            for (int row = 0; row < rows; row++)
             {
-                int columns = calculateColumnCount(remotes);
-                int rows = (remoteCount + columns - 1) / columns;
+                int preferredRowHeight = 0;
 
-                return new Dimension(maxWidth * columns, maxHeight * rows);
+                for (int column = 0; column < columns; column++)
+                {
+                    Dimension preferredSize
+                        = preferredSizes[column + row * columns];
+
+                    if (preferredSize != null)
+                        preferredRowHeight = preferredSize.height;
+                }
+                preferredRowHeight /= columns;
+
+                preferredLayoutHeight += preferredRowHeight;
             }
+
+            preferredLayoutSize
+                = new Dimension(
+                        preferredLayoutWidth + columnsMinus1 * HGAP,
+                        preferredLayoutHeight);
         }
+        else
+            preferredLayoutSize = null;
 
-        return super.preferredLayoutSize(parent);
+        if (local != null)
+        {
+            /*
+             * If the local visual/video Component is not displayed as if it is
+             * a remote one, it will be placed on top of a remote one. Then for
+             * the purposes of the preferredLayoutSize method it needs to be
+             * considered only if there is no remote video whatsoever.
+             */
+            if (!remotes.contains(local) && (preferredLayoutSize == null))
+            {
+                Dimension preferredSize = local.getPreferredSize();
+
+                if (preferredSize != null)
+                {
+                    int preferredHeight
+                        = Math.round(
+                                preferredSize.height * LOCAL_TO_REMOTE_RATIO);
+                    int preferredWidth
+                        = Math.round(
+                                preferredSize.width * LOCAL_TO_REMOTE_RATIO);
+
+                    preferredLayoutSize
+                        = new Dimension(preferredWidth, preferredHeight);
+                }
+            }
+
+            /*
+             * The closeButton has to be on top of the local video.
+             * Consequently, the preferredLayoutSize method does not have to
+             * consider it. Well, maybe if does if the local video is smaller
+             * than the closeButton... but that's just not cool anyway.
+             */
+        }
+
+        /*
+         * The video canvas will get the locations of the other components to
+         * paint so it has to cover the parent completely. In other words, the
+         * preferredLayoutSize method does not have to consider it.
+         */
+
+        return
+            (preferredLayoutSize == null)
+                ? super.preferredLayoutSize(parent)
+                : preferredLayoutSize;
     }
 
     /**
@@ -480,6 +590,11 @@ public void removeLayoutComponent(Component comp)
     {
         super.removeLayoutComponent(comp);
 
+        synchronized (constraints)
+        {
+            constraints.remove(comp);
+        }
+
         if (local == comp)
             local = null;
         else if (closeButton == comp)
-- 
GitLab