mirror of
https://github.com/AntennaPod/AntennaPod.git
synced 2025-01-31 19:04:52 +01:00
Moved background tasks of PlaybackService into PlaybackServiceTaskManager
This commit is contained in:
parent
eee1f68a0d
commit
513183bc84
@ -0,0 +1,364 @@
|
||||
package de.danoeh.antennapod.service.playback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import de.danoeh.antennapod.AppConfig;
|
||||
import de.danoeh.antennapod.feed.EventDistributor;
|
||||
import de.danoeh.antennapod.feed.FeedItem;
|
||||
import de.danoeh.antennapod.storage.DBReader;
|
||||
import de.danoeh.antennapod.util.playback.Playable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* Manages the background tasks of PlaybackSerivce, i.e.
|
||||
* the sleep timer, the position saver, the widget updater and
|
||||
* the queue loader.
|
||||
* <p/>
|
||||
* The PlaybackServiceTaskManager(PSTM) uses a callback object (PSTMCallback)
|
||||
* to notify the PlaybackService about updates from the running tasks.
|
||||
*/
|
||||
public class PlaybackServiceTaskManager {
|
||||
private static final String TAG = "PlaybackServiceTaskManager";
|
||||
|
||||
/**
|
||||
* Update interval of position saver in milliseconds.
|
||||
*/
|
||||
public static final int POSITION_SAVER_WAITING_INTERVAL = 5000;
|
||||
/**
|
||||
* Notification interval of widget updater in milliseconds.
|
||||
*/
|
||||
public static final int WIDGET_UPDATER_NOTIFICATION_INTERVAL = 1500;
|
||||
|
||||
private static final int SCHED_EX_POOL_SIZE = 2;
|
||||
private final ScheduledThreadPoolExecutor schedExecutor;
|
||||
|
||||
private ScheduledFuture positionSaverFuture;
|
||||
private ScheduledFuture widgetUpdaterFuture;
|
||||
private Future sleepTimerFuture;
|
||||
private volatile Future<List<FeedItem>> queueFuture;
|
||||
private volatile Future chapterLoaderFuture;
|
||||
|
||||
private SleepTimer sleepTimer;
|
||||
|
||||
private final Context context;
|
||||
private final PSTMCallback callback;
|
||||
|
||||
/**
|
||||
* Sets up a new PSTM. This method will also start the queue loader task.
|
||||
*
|
||||
* @param context
|
||||
* @param callback A PSTMCallback object for notifying the user about updates. Must not be null.
|
||||
*/
|
||||
public PlaybackServiceTaskManager(Context context, PSTMCallback callback) {
|
||||
if (context == null)
|
||||
throw new IllegalArgumentException("context must not be null");
|
||||
if (callback == null)
|
||||
throw new IllegalArgumentException("callback must not be null");
|
||||
|
||||
this.context = context;
|
||||
this.callback = callback;
|
||||
schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE, new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r);
|
||||
t.setPriority(Thread.MIN_PRIORITY);
|
||||
return t;
|
||||
}
|
||||
});
|
||||
loadQueue();
|
||||
EventDistributor.getInstance().register(eventDistributorListener);
|
||||
}
|
||||
|
||||
private final EventDistributor.EventListener eventDistributorListener = new EventDistributor.EventListener() {
|
||||
@Override
|
||||
public void update(EventDistributor eventDistributor, Integer arg) {
|
||||
if ((EventDistributor.QUEUE_UPDATE & arg) != 0) {
|
||||
loadQueue();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private synchronized boolean isQueueLoaderActive() {
|
||||
return queueFuture != null && !queueFuture.isDone();
|
||||
}
|
||||
|
||||
private synchronized void cancelQueueLoader() {
|
||||
if (isQueueLoaderActive()) {
|
||||
queueFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void loadQueue() {
|
||||
if (!isQueueLoaderActive()) {
|
||||
queueFuture = schedExecutor.submit(new Callable<List<FeedItem>>() {
|
||||
@Override
|
||||
public List<FeedItem> call() throws Exception {
|
||||
return DBReader.getQueue(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the queue or waits until the PSTM hasloaded the queue from the database.
|
||||
*/
|
||||
public synchronized List<FeedItem> getQueue() throws InterruptedException {
|
||||
try {
|
||||
return queueFuture.get();
|
||||
} catch (ExecutionException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the position saver task. If the position saver is already active, nothing will happen.
|
||||
*/
|
||||
public synchronized void startPositionSaver() {
|
||||
if (!isPositionSaverActive()) {
|
||||
Runnable positionSaver = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.positionSaverTick();
|
||||
}
|
||||
};
|
||||
positionSaverFuture = schedExecutor.scheduleWithFixedDelay(positionSaver, POSITION_SAVER_WAITING_INTERVAL,
|
||||
POSITION_SAVER_WAITING_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Started PositionSaver");
|
||||
} else {
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Call to startPositionSaver was ignored.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the position saver is currently running.
|
||||
*/
|
||||
public synchronized boolean isPositionSaverActive() {
|
||||
return positionSaverFuture != null && !positionSaverFuture.isCancelled() && !positionSaverFuture.isDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the position saver. If the position saver is not running, nothing will happen.
|
||||
*/
|
||||
public synchronized void cancelPositionSaver() {
|
||||
if (isPositionSaverActive()) {
|
||||
positionSaverFuture.cancel(false);
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Cancelled PositionSaver");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the widget updater task. If the widget updater is already active, nothing will happen.
|
||||
*/
|
||||
public synchronized void startWidgetUpdater() {
|
||||
if (!isWidgetUpdaterActive()) {
|
||||
Runnable widgetUpdater = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onWidgetUpdaterTick();
|
||||
}
|
||||
};
|
||||
widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(widgetUpdater, WIDGET_UPDATER_NOTIFICATION_INTERVAL,
|
||||
WIDGET_UPDATER_NOTIFICATION_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Started WidgetUpdater");
|
||||
} else {
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Call to startWidgetUpdater was ignored.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new sleep timer with the given waiting time. If another sleep timer is already active, it will be
|
||||
* cancelled first.
|
||||
* After waitingTime has elapsed, onSleepTimerExpired() will be called.
|
||||
*
|
||||
* @throws java.lang.IllegalArgumentException if waitingTime <= 0
|
||||
*/
|
||||
public synchronized void setSleepTimer(long waitingTime) {
|
||||
if (waitingTime <= 0)
|
||||
throw new IllegalArgumentException("waitingTime <= 0");
|
||||
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime)
|
||||
+ " milliseconds");
|
||||
if (isSleepTimerActive()) {
|
||||
sleepTimerFuture.cancel(true);
|
||||
}
|
||||
sleepTimer = new SleepTimer(waitingTime);
|
||||
sleepTimerFuture = schedExecutor.submit(sleepTimer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the sleep timer is currently active.
|
||||
*/
|
||||
public synchronized boolean isSleepTimerActive() {
|
||||
return sleepTimer != null && sleepTimer.isWaiting() && sleepTimerFuture != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the sleep timer. If the sleep timer is not active, nothing will happen.
|
||||
*/
|
||||
public synchronized void disableSleepTimer() {
|
||||
if (isSleepTimerActive()) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Disabling sleep timer");
|
||||
sleepTimerFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current sleep timer time or 0 if the sleep timer is not active.
|
||||
*/
|
||||
public synchronized long getSleepTimerTimeLeft() {
|
||||
if (isSleepTimerActive()) {
|
||||
return sleepTimer.getWaitingTime();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the widget updater is currently running.
|
||||
*/
|
||||
public synchronized boolean isWidgetUpdaterActive() {
|
||||
return widgetUpdaterFuture != null && !widgetUpdaterFuture.isCancelled() && !widgetUpdaterFuture.isDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the widget updater. If the widget updater is not running, nothing will happen.
|
||||
*/
|
||||
public synchronized void cancelWidgetUpdater() {
|
||||
if (isWidgetUpdaterActive()) {
|
||||
widgetUpdaterFuture.cancel(false);
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Cancelled WidgetUpdater");
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void cancelChapterLoader() {
|
||||
if (isChapterLoaderActive()) {
|
||||
chapterLoaderFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized boolean isChapterLoaderActive() {
|
||||
return chapterLoaderFuture != null && !chapterLoaderFuture.isDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new thread that loads the chapter marks from a playable object. If another chapter loader is already active,
|
||||
* it will be cancelled first.
|
||||
* On completion, the callback's onChapterLoaded method will be called.
|
||||
*/
|
||||
public synchronized void startChapterLoader(final Playable media) {
|
||||
if (media == null)
|
||||
throw new IllegalArgumentException("media = null");
|
||||
|
||||
if (isChapterLoaderActive()) {
|
||||
cancelChapterLoader();
|
||||
}
|
||||
|
||||
Runnable chapterLoader = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Chapter loader started");
|
||||
if (media.getChapters() == null) {
|
||||
media.loadChapterMarks();
|
||||
if (!Thread.currentThread().isInterrupted() && media.getChapters() != null) {
|
||||
callback.onChapterLoaded(media);
|
||||
}
|
||||
}
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Chapter loader stopped");
|
||||
}
|
||||
};
|
||||
chapterLoaderFuture = schedExecutor.submit(chapterLoader);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cancels all tasks. The PSTM will be in the initial state after execution of this method.
|
||||
*/
|
||||
public synchronized void cancelAllTasks() {
|
||||
cancelPositionSaver();
|
||||
cancelWidgetUpdater();
|
||||
disableSleepTimer();
|
||||
cancelQueueLoader();
|
||||
cancelChapterLoader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all tasks and shuts down the internal executor service of the PSTM. The object should not be used after
|
||||
* execution of this method.
|
||||
*/
|
||||
public synchronized void shutdown() {
|
||||
EventDistributor.getInstance().unregister(eventDistributorListener);
|
||||
cancelAllTasks();
|
||||
schedExecutor.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleeps for a given time and then pauses playback.
|
||||
*/
|
||||
private class SleepTimer implements Runnable {
|
||||
private static final String TAG = "SleepTimer";
|
||||
private static final long UPDATE_INTERVALL = 1000L;
|
||||
private volatile long waitingTime;
|
||||
private boolean isWaiting;
|
||||
|
||||
public SleepTimer(long waitingTime) {
|
||||
super();
|
||||
this.waitingTime = waitingTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
isWaiting = true;
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Starting");
|
||||
while (waitingTime > 0) {
|
||||
try {
|
||||
Thread.sleep(UPDATE_INTERVALL);
|
||||
waitingTime -= UPDATE_INTERVALL;
|
||||
|
||||
if (waitingTime <= 0) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Waiting completed");
|
||||
callback.onSleepTimerExpired();
|
||||
postExecute();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Log.d(TAG, "Thread was interrupted while waiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
postExecute();
|
||||
}
|
||||
|
||||
protected void postExecute() {
|
||||
isWaiting = false;
|
||||
}
|
||||
|
||||
public long getWaitingTime() {
|
||||
return waitingTime;
|
||||
}
|
||||
|
||||
public boolean isWaiting() {
|
||||
return isWaiting;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static interface PSTMCallback {
|
||||
void positionSaverTick();
|
||||
|
||||
void onSleepTimerExpired();
|
||||
|
||||
void onWidgetUpdaterTick();
|
||||
|
||||
void onChapterLoaded(Playable media);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user