Skip to content
Snippets Groups Projects
ActiveSpeakerDetectorImpl.java 7.66 KiB
/*
 * 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;

import java.util.*;

import org.jitsi.service.configuration.*;
import org.jitsi.service.libjitsi.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.service.neomedia.event.*;

/**
 * Implements an {@link ActiveSpeakerDetector} (factory) which uses/delegates to
 * an actual algorithm implementation for the detections/identification of the
 * active/dominant speaker in a multipoint conference.
 *
 * @author Lyubomir Marinov
 */
public class ActiveSpeakerDetectorImpl
    implements ActiveSpeakerDetector
{
    /**
     * The name of the <tt>ConfigurationService</tt> property which specifies
     * the class name of the algorithm implementation for the
     * detection/identification of the active/dominant speaker in a multipoint
     * conference to be used by <tt>ActiveSpeakerDetectorImpl</tt>. The default
     * value is <tt>null</tt>. If the specified value is <tt>null</tt> or the
     * initialization of an instance of the specified class fails,
     * <tt>ActiveSpeakerDetectorImpl</tt> falls back to a list of well-known
     * algorithm implementations.
     */
    private static final String IMPL_CLASS_NAME_PNAME
        = ActiveSpeakerDetectorImpl.class.getName() + ".implClassName";

    /**
     * The names of the classes known by <tt>ActiveSpeakerDetectorImpl</tt> to
     * implement actual algorithms for the detection/identification of the
     * active/dominant speaker in a multipoint conference. 
     */
    private static final String[] IMPL_CLASS_NAMES
        = {
            ".DominantSpeakerIdentification",
            ".BasicActiveSpeakerDetector"
        };

    /**
     * The actual algorithm implementation to be used by this instance for the
     * detection/identification of the active/dominant speaker in a multipoint
     * conference.
     */
    private final ActiveSpeakerDetector impl;

    /**
     * Initializes a new <tt>ActiveSpeakerDetectorImpl</tt> which is to use a
     * default algorithm implementation for the detection/identification of the
     * active/dominant speaker in a multipoint conference.
     */
    public ActiveSpeakerDetectorImpl()
    {
        this(getImplClassNames());
    }

    /**
     * Initializes a new <tt>ActiveSpeakerDetectorImpl</tt> which is to use the
     * first available algorithm from a specific list of algorithms (identified
     * by the names of their implementing classes) for the
     * detection/identification of the active/dominant speaker in a multipoint
     * conference.
     * 
     * @param implClassNames the class names of the algorithm implementations to
     * search through and in which the first available is to be found and used
     * for the detection/identification of the active/dominant speaker in a
     * multipoint conference
     * @throws RuntimeException if none of the algorithm implementations
     * specified by <tt>implClassNames</tt> is available
     */
    public ActiveSpeakerDetectorImpl(String... implClassNames)
    {
        ActiveSpeakerDetector impl = null;
        Throwable cause = null;

        for (String implClassName : implClassNames)
        {
            try
            {
                Class<?> implClass
                    = Class.forName(normalizeClassName(implClassName));

                if ((implClass != null)
                        && ActiveSpeakerDetector.class.isAssignableFrom(
                                implClass))
                {
                    impl = (ActiveSpeakerDetector) implClass.newInstance();
                }
            }
            catch (Throwable t)
            {
                if (t instanceof InterruptedException)
                    Thread.currentThread().interrupt();
                else if (t instanceof ThreadDeath)
                    throw (ThreadDeath) t;
                else
                    cause = t;
            }
            if (impl != null)
                break;
        }
        if (impl == null)
        {
            throw new RuntimeException(
                    "Failed to initialize an actual ActiveSpeakerDetector"
                        + " implementation, tried classes: "
                        + Arrays.toString(implClassNames),
                    cause);
        }
        else
        {
            this.impl = impl;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addActiveSpeakerChangedListener(
            ActiveSpeakerChangedListener listener)
    {
        impl.addActiveSpeakerChangedListener(listener);
    }

    /**
     * Gets the names of the classes known by <tt>ActiveSpeakerDetectorImpl</tt>
     * to implement actual algorithms for the detection/identification of the
     * active/dominant speaker in a multipoint conference. If the
     * <tt>ConfigurationService</tt> property {@link #IMPL_CLASS_NAME_PNAME}
     * specifies a class name, it is prepended to the returned array.
     *
     * @return the names of the classes known by
     * <tt>ActiveSpeakerDetectorImpl</tt> to implement actual algorithms for the
     * detection/identification of the active/dominant speaker in a multipoint
     * conference
     */
    private static String[] getImplClassNames()
    {
        /*
         * The user is allowed to specify the class name of the (default)
         * algorithm implementation through the ConfigurationService.
         */
        ConfigurationService cfg = LibJitsi.getConfigurationService();
        String implClassName = null;

        if (cfg != null)
        {
            implClassName = cfg.getString(IMPL_CLASS_NAME_PNAME);
            if ((implClassName != null) && (implClassName.length() == 0))
                implClassName = null;
        }

        /*
         * Should the user's choice with respect to the algorithm implementation
         * fail, ActiveSpeakerDetectorImpl falls back to well-known algorithm
         * implementations.   
         */
        String[] implClassNames;

        if (implClassName == null)
        {
            implClassNames = IMPL_CLASS_NAMES;
        }
        else
        {
            List<String> implClassNameList
                = new ArrayList<String>(1 + IMPL_CLASS_NAMES.length);

            implClassNameList.add(normalizeClassName(implClassName));
            for (String anImplClassName : IMPL_CLASS_NAMES)
            {
                anImplClassName = normalizeClassName(anImplClassName);
                if (!implClassNameList.contains(anImplClassName))
                    implClassNameList.add(anImplClassName);
            }

            implClassNames
                = implClassNameList.toArray(
                        new String[implClassNameList.size()]);
        }
        return implClassNames;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void levelChanged(long ssrc, int level)
    {
        impl.levelChanged(ssrc, level);
    }

    /**
     * Makes sure that a specific class name starts with a package name.
     *
     * @param className the class name to prefix with a package name if
     * necessary
     * @return a class name with a package name and a simple name
     */
    private static String normalizeClassName(String className)
    {
        if (className.startsWith("."))
        {
            Package pkg = ActiveSpeakerDetectorImpl.class.getPackage();

            if (pkg != null)
                className = pkg.getName() + className;
        }
        return className;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeActiveSpeakerChangedListener(
            ActiveSpeakerChangedListener listener)
    {
        impl.removeActiveSpeakerChangedListener(listener);
    }
}