From adce5ec6b78a0bbdfbb771032d9648ed03c80194 Mon Sep 17 00:00:00 2001
From: Lyubomir Marinov <lyubomir.marinov@jitsi.org>
Date: Thu, 3 Apr 2014 11:01:47 +0300
Subject: [PATCH] Removes duplication.

---
 .../impl/neomedia/device/AudioSystem2.java    | 468 ++++++++++++++++++
 .../UpdateAvailableDeviceListListener.java    |  42 ++
 2 files changed, 510 insertions(+)
 create mode 100644 src/org/jitsi/impl/neomedia/device/AudioSystem2.java
 create mode 100644 src/org/jitsi/impl/neomedia/device/UpdateAvailableDeviceListListener.java

diff --git a/src/org/jitsi/impl/neomedia/device/AudioSystem2.java b/src/org/jitsi/impl/neomedia/device/AudioSystem2.java
new file mode 100644
index 00000000..16879940
--- /dev/null
+++ b/src/org/jitsi/impl/neomedia/device/AudioSystem2.java
@@ -0,0 +1,468 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.impl.neomedia.device;
+
+import java.lang.ref.*;
+import java.util.*;
+import java.util.regex.*;
+
+import org.jitsi.util.*;
+
+/**
+ *
+ * @author Lyubomir Marinov
+ */
+public abstract class AudioSystem2
+    extends AudioSystem
+{
+    /**
+     * The <tt>Logger</tt> used by the <tt>AudioSystem2</tt> class and its
+     * instances for logging output.
+     */
+    private static final Logger logger = Logger.getLogger(AudioSystem2.class);
+
+    /**
+     * The number of times that {@link #willOpenStream()} has been invoked
+     * without an intervening {@link #didOpenStream()} i.e. the number of API
+     * clients who are currently executing a <tt>Pa_OpenStream</tt>-like
+     * function and which are thus inhibiting
+     * <tt>updateAvailableDeviceList()</tt>.
+     */
+    private int openStream = 0;
+
+    /**
+     * The <tt>Object</tt> which synchronizes that access to {@link #openStream}
+     * and {@link #updateAvailableDeviceList}.
+     */
+    private final Object openStreamSyncRoot = new Object();
+
+    /**
+     * The number of times that {@link #willUpdateAvailableDeviceList()} has
+     * been invoked without an intervening
+     * {@link #didUpdateAvailableDeviceList()} i.e. the number of API clients
+     * who are currently executing <tt>updateAvailableDeviceList()</tt> and who
+     * are thus inhibiting <tt>openStream</tt>.
+     */
+    private int updateAvailableDeviceList = 0;
+
+    /**
+     * The list of <tt>UpdateAvailableDeviceListListener</tt>s which are to be
+     * notified before and after this <tt>AudioSystem</tt>'s method
+     * <tt>updateAvailableDeviceList()</tt> is invoked.
+     */
+    private final List<WeakReference<UpdateAvailableDeviceListListener>>
+        updateAvailableDeviceListListeners
+            = new LinkedList<WeakReference<UpdateAvailableDeviceListListener>>();
+
+    /**
+     * The <tt>Object</tt> which ensures that this <tt>AudioSystem</tt>'s
+     * function to update the list of available devices will not be invoked
+     * concurrently. The condition should hold true on the native side but,
+     * anyway, it should not hurt (much) to enforce it on the Java side as well.
+     */
+    private final Object updateAvailableDeviceListSyncRoot = new Object();
+
+    protected AudioSystem2(String locatorProtocol, int features)
+        throws Exception
+    {
+        super(locatorProtocol, features);
+    }
+
+    /**
+     * Adds a listener which is to be notified before and after this
+     * <tt>AudioSystem</tt>'s method <tt>updateAvailableDeviceList()</tt> is
+     * invoked.
+     * <p>
+     * <b>Note</b>: The <tt>AudioSystem2</tt> class keeps a
+     * <tt>WeakReference</tt> to the specified <tt>listener</tt> in order to
+     * avoid memory leaks.
+     * </p>
+     *
+     * @param listener the <tt>UpdateAvailableDeviceListListener</tt> to be
+     * notified before and after this <tt>AudioSystem</tt>'s method
+     * <tt>updateAvailableDeviceList()</tt> is invoked
+     */
+    public void addUpdateAvailableDeviceListListener(
+            UpdateAvailableDeviceListListener listener)
+    {
+        if (listener == null)
+            throw new NullPointerException("listener");
+
+        synchronized (updateAvailableDeviceListListeners)
+        {
+            Iterator<WeakReference<UpdateAvailableDeviceListListener>> i
+                = updateAvailableDeviceListListeners.iterator();
+            boolean add = true;
+
+            while (i.hasNext())
+            {
+                UpdateAvailableDeviceListListener l = i.next().get();
+
+                if (l == null)
+                    i.remove();
+                else if (l.equals(listener))
+                    add = false;
+            }
+            if (add)
+            {
+                updateAvailableDeviceListListeners.add(
+                        new WeakReference<UpdateAvailableDeviceListListener>(
+                                listener));
+            }
+        }
+    }
+
+    /**
+     * Sorts a specific list of <tt>CaptureDeviceInfo2</tt>s so that the
+     * ones representing USB devices appear at the beginning/top of the
+     * specified list.
+     *
+     * @param devices the list of <tt>CaptureDeviceInfo2</tt>s to be
+     * sorted so that the ones representing USB devices appear at the
+     * beginning/top of the list
+     */
+    protected static void bubbleUpUsbDevices(List<CaptureDeviceInfo2> devices)
+    {
+        if (!devices.isEmpty())
+        {
+            List<CaptureDeviceInfo2> nonUsbDevices
+                = new ArrayList<CaptureDeviceInfo2>(devices.size());
+
+            for (Iterator<CaptureDeviceInfo2> i = devices.iterator();
+                    i.hasNext();)
+            {
+                CaptureDeviceInfo2 d = i.next();
+
+                if (!d.isSameTransportType("USB"))
+                {
+                    nonUsbDevices.add(d);
+                    i.remove();
+                }
+            }
+            if (!nonUsbDevices.isEmpty())
+            {
+                for (CaptureDeviceInfo2 d : nonUsbDevices)
+                    devices.add(d);
+            }
+        }
+    }
+
+    /**
+     * Notifies this <tt>AudioSystem</tt> that an API client finished executing
+     * a <tt>Pa_OpenStream</tt>-like function.
+     */
+    public void didOpenStream()
+    {
+        synchronized (openStreamSyncRoot)
+        {
+            openStream--;
+            if (openStream < 0)
+                openStream = 0;
+
+            openStreamSyncRoot.notifyAll();
+        }
+    }
+
+    /**
+     * Notifies this <tt>AudioSystem</tt> that a it has finished executing
+     * <tt>updateAvailableDeviceList()</tt>.
+     */
+    private void didUpdateAvailableDeviceList()
+    {
+        synchronized (openStreamSyncRoot)
+        {
+            updateAvailableDeviceList--;
+            if (updateAvailableDeviceList < 0)
+                updateAvailableDeviceList = 0;
+
+            openStreamSyncRoot.notifyAll();
+        }
+
+        fireUpdateAvailableDeviceListEvent(false);
+    }
+
+    /**
+     * Notifies the registered <tt>UpdateAvailableDeviceListListener</tt>s that
+     * this <tt>AudioSystem</tt>'s method <tt>updateAvailableDeviceList()</tt>
+     * will be or was invoked.
+     *
+     * @param will <tt>true</tt> if this <tt>AudioSystem</tt>'s method
+     * <tt>updateAvailableDeviceList()</tt> will be invoked or <tt>false</tt>
+     * if it was invoked
+     */
+    private void fireUpdateAvailableDeviceListEvent(boolean will)
+    {
+        try
+        {
+            List<WeakReference<UpdateAvailableDeviceListListener>> ls;
+
+            synchronized (updateAvailableDeviceListListeners)
+            {
+                ls
+                    = new ArrayList<WeakReference<UpdateAvailableDeviceListListener>>(
+                            updateAvailableDeviceListListeners);
+            }
+
+            for (WeakReference<UpdateAvailableDeviceListListener> wr : ls)
+            {
+                UpdateAvailableDeviceListListener l = wr.get();
+
+                if (l != null)
+                {
+                    try
+                    {
+                        if (will)
+                            l.willUpdateAvailableDeviceList();
+                        else
+                            l.didUpdateAvailableDeviceList();
+                    }
+                    catch (Throwable t)
+                    {
+                        if (t instanceof ThreadDeath)
+                        {
+                            throw (ThreadDeath) t;
+                        }
+                        else
+                        {
+                            logger.error(
+                                    "UpdateAvailableDeviceListListener."
+                                        + (will ? "will" : "did")
+                                        + "UpdateAvailableDeviceList failed.",
+                                    t);
+                        }
+                    }
+                }
+            }
+        }
+        catch (Throwable t)
+        {
+            if (t instanceof InterruptedException)
+                Thread.currentThread().interrupt();
+            else if (t instanceof ThreadDeath)
+                throw (ThreadDeath) t;
+        }
+    }
+
+    /**
+     * Attempts to reorder specific lists of capture and playback/notify
+     * <tt>CaptureDeviceInfo2</tt>s so that devices from the same
+     * hardware appear at the same indices in the respective lists. The judgment
+     * with respect to the belonging to the same hardware is based on the names
+     * of the specified <tt>CaptureDeviceInfo2</tt>s. The implementation
+     * is provided as a fallback to stand in for scenarios in which more
+     * accurate relevant information is not available.
+     *
+     * @param captureDevices
+     * @param playbackDevices
+     */
+    protected static void matchDevicesByName(
+            List<CaptureDeviceInfo2> captureDevices,
+            List<CaptureDeviceInfo2> playbackDevices)
+    {
+        Iterator<CaptureDeviceInfo2> captureIter
+            = captureDevices.iterator();
+        Pattern pattern
+            = Pattern.compile(
+                    "array|headphones|microphone|speakers|\\p{Space}|\\(|\\)",
+                    Pattern.CASE_INSENSITIVE);
+        LinkedList<CaptureDeviceInfo2> captureDevicesWithPlayback
+            = new LinkedList<CaptureDeviceInfo2>();
+        LinkedList<CaptureDeviceInfo2> playbackDevicesWithCapture
+            = new LinkedList<CaptureDeviceInfo2>();
+        int count = 0;
+
+        while (captureIter.hasNext())
+        {
+            CaptureDeviceInfo2 captureDevice = captureIter.next();
+            String captureName = captureDevice.getName();
+
+            if (captureName != null)
+            {
+                captureName = pattern.matcher(captureName).replaceAll("");
+                if (captureName.length() != 0)
+                {
+                    Iterator<CaptureDeviceInfo2> playbackIter
+                        = playbackDevices.iterator();
+                    CaptureDeviceInfo2 matchingPlaybackDevice = null;
+
+                    while (playbackIter.hasNext())
+                    {
+                        CaptureDeviceInfo2 playbackDevice
+                            = playbackIter.next();
+                        String playbackName = playbackDevice.getName();
+
+                        if (playbackName != null)
+                        {
+                            playbackName
+                                = pattern
+                                    .matcher(playbackName)
+                                        .replaceAll("");
+                            if (captureName.equals(playbackName))
+                            {
+                                playbackIter.remove();
+                                matchingPlaybackDevice = playbackDevice;
+                                break;
+                            }
+                        }
+                    }
+                    if (matchingPlaybackDevice != null)
+                    {
+                        captureIter.remove();
+                        captureDevicesWithPlayback.add(captureDevice);
+                        playbackDevicesWithCapture.add(
+                                matchingPlaybackDevice);
+                        count++;
+                    }
+                }
+            }
+        }
+
+        for (int i = count - 1; i >= 0; i--)
+        {
+            captureDevices.add(0, captureDevicesWithPlayback.get(i));
+            playbackDevices.add(0, playbackDevicesWithCapture.get(i));
+        }
+    }
+
+    /**
+     * Reinitializes this <tt>AudioSystem</tt> in order to bring it up to date
+     * with possible changes in the list of available devices. Invokes
+     * <tt>updateAvailableDeviceList()</tt> to update the devices on the
+     * native side and then {@link #initialize()} to reflect any changes on the
+     * Java side. Invoked by the native side of this <tt>AudioSystem</tt> when
+     * it detects that the list of available devices has changed.
+     *
+     * @throws Exception if there was an error during the invocation of
+     * <tt>updateAvailableDeviceList()</tt> and
+     * <tt>DeviceSystem.initialize()</tt>
+     */
+    protected void reinitialize()
+        throws Exception
+    {
+        synchronized (updateAvailableDeviceListSyncRoot)
+        {
+            willUpdateAvailableDeviceList();
+            try
+            {
+                updateAvailableDeviceList();
+            }
+            finally
+            {
+                didUpdateAvailableDeviceList();
+            }
+        }
+
+        /*
+         * XXX We will likely minimize the risk of crashes on the native side
+         * even further by invoking initialize() with
+         * updateAvailableDeviceList() locked. Unfortunately, that will likely
+         * increase the risks of deadlocks on the Java side.
+         */
+        invokeDeviceSystemInitialize(this);
+    }
+
+    public void removeUpdateAvailableDeviceListListener(
+            UpdateAvailableDeviceListListener listener)
+    {
+        if (listener == null)
+            return;
+
+        synchronized (updateAvailableDeviceListListeners)
+        {
+            Iterator<WeakReference<UpdateAvailableDeviceListListener>> i
+                = updateAvailableDeviceListListeners.iterator();
+
+            while (i.hasNext())
+            {
+                UpdateAvailableDeviceListListener l = i.next().get();
+
+                if ((l == null) || l.equals(listener))
+                    i.remove();
+            }
+        }
+    }
+
+    protected abstract void updateAvailableDeviceList();
+
+    /**
+     * Waits for all API clients to finish executing a
+     * <tt>Pa_OpenStream</tt>-like function.
+     */
+    private void waitForOpenStream()
+    {
+        boolean interrupted = false;
+
+        while (openStream > 0)
+        {
+            try
+            {
+                openStreamSyncRoot.wait();
+            }
+            catch (InterruptedException ie)
+            {
+                interrupted = true;
+            }
+        }
+        if (interrupted)
+            Thread.currentThread().interrupt();
+    }
+
+    /**
+     * Waits for all API clients to finish executing
+     * <tt>updateAvailableDeviceList()</tt>.
+     */
+    private void waitForUpdateAvailableDeviceList()
+    {
+        boolean interrupted = false;
+
+        while (updateAvailableDeviceList > 0)
+        {
+            try
+            {
+                openStreamSyncRoot.wait();
+            }
+            catch (InterruptedException ie)
+            {
+                interrupted = true;
+            }
+        }
+        if (interrupted)
+            Thread.currentThread().interrupt();
+    }
+
+    /**
+     * Notifies this <tt>AudioSystem</tt> that an API client will start
+     * executing a <tt>Pa_OpenStream</tt>-like function.
+     */
+    public void willOpenStream()
+    {
+        synchronized (openStreamSyncRoot)
+        {
+            waitForUpdateAvailableDeviceList();
+
+            openStream++;
+            openStreamSyncRoot.notifyAll();
+        }
+    }
+
+    /**
+     * Notifies this <tt>AudioSystem</tt> that it will start executing
+     * <tt>updateAvailableDeviceList()</tt>.
+     */
+    private void willUpdateAvailableDeviceList()
+    {
+        synchronized (openStreamSyncRoot)
+        {
+            waitForOpenStream();
+
+            updateAvailableDeviceList++;
+            openStreamSyncRoot.notifyAll();
+        }
+
+        fireUpdateAvailableDeviceListEvent(true);
+    }
+}
diff --git a/src/org/jitsi/impl/neomedia/device/UpdateAvailableDeviceListListener.java b/src/org/jitsi/impl/neomedia/device/UpdateAvailableDeviceListListener.java
new file mode 100644
index 00000000..89805b88
--- /dev/null
+++ b/src/org/jitsi/impl/neomedia/device/UpdateAvailableDeviceListListener.java
@@ -0,0 +1,42 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.impl.neomedia.device;
+
+import java.util.*;
+
+/**
+ * Represents a listener which is to be notified before and after an associated
+ * <tt>DeviceSystem</tt>'s function to update the list of available devices is
+ * invoked.
+ *
+ * @author Lyubomir Marinov
+ */
+public interface UpdateAvailableDeviceListListener
+    extends EventListener
+{
+    /**
+     * Notifies this listener that the associated <tt>DeviceSystem</tt>'s
+     * function to update the list of available devices was invoked.
+     *
+     * @throws Exception if this implementation encounters an error. Any
+     * <tt>Throwable</tt> apart from <tt>ThreadDeath</tt> will be ignored
+     * after it is logged for debugging purposes.
+     */
+    void didUpdateAvailableDeviceList()
+        throws Exception;
+
+    /**
+     * Notifies this listener that the associated <tt>DeviceSystem</tt>'s
+     * function to update the list of available devices will be invoked.
+     *
+     * @throws Exception if this implementation encounters an error. Any
+     * <tt>Throwable</tt> apart from <tt>ThreadDeath</tt> will be ignored
+     * after it is logged for debugging purposes.
+     */
+    void willUpdateAvailableDeviceList()
+        throws Exception;
+}
-- 
GitLab