
org.jitsi.util.event.VideoNotifierSupport Maven / Gradle / Ivy
/*
* 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 org.jitsi.util.event;
import java.awt.*;
import java.util.*;
import java.util.List;
// disambiguation
/**
* Represents a mechanism to easily add to a specific Object by means
* of composition support for firing VideoEvents to
* VideoListeners.
*
* @author Lyubomir Marinov
*/
public class VideoNotifierSupport
{
private static final long THREAD_TIMEOUT = 5000;
/**
* The list of VideoEvents which are to be delivered to the
* {@link #listeners} registered with this instance when
* {@link #synchronous} is equal to false.
*/
private final List events;
/**
* The list of VideoListeners interested in changes in the
* availability of visual Components depicting video.
*/
private final List listeners
= new ArrayList();
/**
* The Object which is to be reported as the source of the
* VideoEvents fired by this instance.
*/
private final Object source;
/**
* The indicator which determines whether this instance delivers the
* VideoEvents to the {@link #listeners} synchronously.
*/
private final boolean synchronous;
/**
* The Thread in which {@link #events} are delivered to the
* {@link #listeners} when {@link #synchronous} is equal to false.
*/
private Thread thread;
/**
* Initializes a new VideoNotifierSupport instance which is to
* facilitate the management of VideoListeners and firing
* VideoEvents to them for a specific Object.
*
* @param source the Object which is to be reported as the source
* of the VideoEvents fired by the new instance
*/
public VideoNotifierSupport(Object source)
{
this(source, true);
}
/**
* Initializes a new VideoNotifierSupport instance which is to
* facilitate the management of VideoListeners and firing
* VideoEvents to them for a specific Object.
*
* @param source the Object which is to be reported as the source
* of the VideoEvents fired by the new instance
* @param synchronous true if the new instance is to deliver the
* VideoEvents synchronously; otherwise, false
*/
public VideoNotifierSupport(Object source, boolean synchronous)
{
this.source = source;
this.synchronous = synchronous;
events = this.synchronous ? null : new LinkedList();
}
/**
* Adds a specific VideoListener to this
* VideoNotifierSupport in order to receive notifications when
* visual/video Components are being added and removed.
*
* Adding a listener which has already been added does nothing i.e. it is
* not added more than once and thus does not receive one and the same
* VideoEvent multiple times.
*
* @param listener the VideoListener to be notified when
* visual/video Components are being added or removed in this
* VideoNotifierSupport
*/
public void addVideoListener(VideoListener listener)
{
if (listener == null)
throw new NullPointerException("listener");
synchronized (listeners)
{
if (!listeners.contains(listener))
listeners.add(listener);
}
}
protected void doFireVideoEvent(VideoEvent event)
{
VideoListener[] listeners;
synchronized (this.listeners)
{
listeners
= this.listeners.toArray(
new VideoListener[this.listeners.size()]);
}
for (VideoListener listener : listeners)
switch (event.getType())
{
case VideoEvent.VIDEO_ADDED:
listener.videoAdded(event);
break;
case VideoEvent.VIDEO_REMOVED:
listener.videoRemoved(event);
break;
default:
listener.videoUpdate(event);
break;
}
}
/**
* Notifies the VideoListeners registered with this
* VideoMediaStream about a specific type of change in the
* availability of a specific visual Component depicting video.
*
* @param type the type of change as defined by VideoEvent in the
* availability of the specified visual Component depicting video
* @param visualComponent the visual Component depicting video
* which has been added or removed
* @param origin {@link VideoEvent#LOCAL} if the origin of the video is
* local (e.g. it is being locally captured); {@link VideoEvent#REMOTE} if
* the origin of the video is remote (e.g. a remote peer is streaming it)
* @param wait true if the call is to wait till the specified
* VideoEvent has been delivered to the VideoListeners;
* otherwise, false
* @return true if this event and, more specifically, the visual
* Component it describes have been consumed and should be
* considered owned, referenced (which is important because
* Components belong to a single Container at a time);
* otherwise, false
*/
public boolean fireVideoEvent(
int type, Component visualComponent, int origin,
boolean wait)
{
VideoEvent event
= new VideoEvent(source, type, visualComponent, origin);
fireVideoEvent(event, wait);
return event.isConsumed();
}
/**
* Notifies the VideoListeners registered with this instance about
* a specific VideoEvent.
*
* @param event the VideoEvent to be fired to the
* VideoListeners registered with this instance
* @param wait true if the call is to wait till the specified
* VideoEvent has been delivered to the VideoListeners;
* otherwise, false
*/
public void fireVideoEvent(VideoEvent event, boolean wait)
{
if (synchronous)
doFireVideoEvent(event);
else
{
synchronized (events)
{
events.add(event);
if (thread == null)
startThread();
else
events.notify();
if (wait)
{
boolean interrupted = false;
while (events.contains(event) && (thread != null))
{
try
{
events.wait();
}
catch (InterruptedException ie)
{
interrupted = true;
}
}
if (interrupted)
Thread.currentThread().interrupt();
}
}
}
}
/**
* Removes a specific VideoListener from this
* VideoNotifierSupport in order to have to no longer receive
* notifications when visual/video Components are being added and
* removed.
*
* @param listener the VideoListener to no longer be notified when
* visual/video Components are being added or removed
*/
public void removeVideoListener(VideoListener listener)
{
synchronized (listeners)
{
listeners.remove(listener);
}
}
private void runInThread()
{
while (true)
{
VideoEvent event = null;
synchronized (events)
{
long emptyTime = -1;
boolean interrupted = false;
while (events.isEmpty())
{
if (emptyTime == -1)
emptyTime = System.currentTimeMillis();
else
{
long newEmptyTime = System.currentTimeMillis();
if ((newEmptyTime - emptyTime) >= THREAD_TIMEOUT)
{
events.notify();
return;
}
}
try
{
events.wait(THREAD_TIMEOUT);
}
catch (InterruptedException ie)
{
interrupted = true;
}
}
if (interrupted)
Thread.currentThread().interrupt();
event = events.remove(0);
}
if (event != null)
{
try
{
doFireVideoEvent(event);
}
catch (Throwable t)
{
if (t instanceof ThreadDeath)
throw (ThreadDeath) t;
}
synchronized (events)
{
events.notify();
}
}
}
}
private void startThread()
{
thread
= new Thread("VideoNotifierSupportThread")
{
@Override
public void run()
{
try
{
runInThread();
}
finally
{
synchronized (events)
{
if (Thread.currentThread().equals(thread))
{
thread = null;
if (events.isEmpty())
events.notify();
else
startThread();
}
}
}
}
};
thread.setDaemon(true);
thread.start();
}
}