package com.skcraft.playblock.queue;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.TimerTask;
import com.skcraft.playblock.media.Media;
import com.skcraft.playblock.media.PlayingMedia;
/**
* Handles a queue of media by keeping track of queued media and automatically
* "advancing" to the next media file after each media file has elapsed its
* expected length. Media is "advanced" by firing the callbacks on added
* {@link QueueListener}s.
*/
public class MediaQueue {
private final QueueManager queueManager;
private final List<QueueListener> listeners = new ArrayList<QueueListener>();
private final Queue<Media> queue = new LinkedList<Media>();
private PlayingMedia playing;
private MediaAdvancer timerTask;
/**
* Construct a new media queue.
*
* @param queueManager
* the queue manager
*/
MediaQueue(QueueManager queueManager) {
this.queueManager = queueManager;
}
/**
* Add a listener for events.
*
* @param listener
* the listener
*/
public synchronized void addQueueListener(QueueListener listener) {
listeners.add(listener);
}
/**
* Remove a listener. If the listener isn't registered, then nothing will
* happen.
*
* @param listener
* the listener
*/
public synchronized void removeQueueListener(QueueListener listener) {
listeners.remove(listener);
}
/**
* Fire {@link QueueListener#mediaComplete(Media)} events.
*
* @param media
* the media
*/
private synchronized void fireMediaComplete(Media media) {
for (QueueListener listener : listeners) {
listener.mediaComplete(media);
}
}
/**
* Fire {@link QueueListener#mediaAdvance(Media)} events.
*
* @param media
* the media, or null
*/
private synchronized void fireMediaAdvance(Media media) {
for (QueueListener listener : listeners) {
listener.mediaAdvance(media);
}
}
/**
* Advance to the next media and "complete" the currently playing media.
*
* @param media
* the media, null to not play more media
*/
private synchronized void advanceTo(Media media) {
if (playing != null) {
if (timerTask != null) {
timerTask.cancel();
}
fireMediaComplete(playing.getMedia());
playing = null;
}
if (media != null) {
playing = PlayingMedia.fromNow(media);
queueManager.getQueueTimer().schedule(timerTask = new MediaAdvancer(), media.getLength());
}
fireMediaAdvance(media);
}
/**
* Advance to the next media in the queue, or stop playing if there's
* nothing playing.
*/
private synchronized void advanceNext() {
advanceTo(queue.poll());
}
/**
* Add media to the queue, or play immediately if the queue was empty.
*
* <p>
* The passed media must have its length known.
* </p>
*
* @param media
* the media
*/
public synchronized void add(Media media) {
if (media.getLength() == null) {
throw new RuntimeException("Can't add media with unknown length");
}
if (playing == null) {
advanceTo(media);
} else {
queue.add(media);
}
}
/**
* Get the currently playing media.
*
* @return the currently playing media, or null if nothing is playing
*/
public synchronized PlayingMedia getCurrentMedia() {
return playing;
}
/**
* Mark this queue for release, indicating that it is no longer to be used.
* It is important that this method is called in order to release resources.
*
* <p>
* This will not fire any events on added {@link QueueListener}s.
* </p>
*/
public synchronized void release() {
if (timerTask != null) {
timerTask.cancel();
}
playing = null;
}
/**
* Used to advance the media after the duration of the currently "playing"
* media has elapsed.
*/
private class MediaAdvancer extends TimerTask {
@Override
public void run() {
advanceNext();
}
}
}