Merge pull request #3097 from ByteHamster/exoplayer-main-thread

Executing all ExoPlayer methods on main thread
This commit is contained in:
H. Lehmann 2019-04-11 20:50:56 +02:00 committed by GitHub
commit 97d08f3b00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 27 deletions

View File

@ -776,15 +776,6 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi
loadData();
}
public void onEventMainThread(ServiceEvent event) {
Log.d(TAG, "onEvent(" + event + ")");
switch(event.action) {
case SERVICE_STARTED:
externalPlayerFragment.connectToPlaybackService();
break;
}
}
public void onEventMainThread(ProgressEvent event) {
Log.d(TAG, "onEvent(" + event + ")");
switch(event.action) {

View File

@ -18,11 +18,13 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.event.ServiceEvent;
import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.greenrobot.event.EventBus;
import io.reactivex.Maybe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
@ -90,8 +92,11 @@ public class ExternalPlayerFragment extends Fragment {
loadMediaInfo();
}
public void connectToPlaybackService() {
controller.init();
public void onEventMainThread(ServiceEvent event) {
Log.d(TAG, "onEvent(" + event + ")");
if (event.action == ServiceEvent.Action.SERVICE_STARTED) {
controller.init();
}
}
private PlaybackController setupPlaybackController() {
@ -109,12 +114,12 @@ public class ExternalPlayerFragment extends Fragment {
@Override
public boolean loadMediaInfo() {
ExternalPlayerFragment fragment = ExternalPlayerFragment.this;
if (fragment != null) {
return fragment.loadMediaInfo();
} else {
return false;
}
return ExternalPlayerFragment.this.loadMediaInfo();
}
@Override
public void setupGUI() {
ExternalPlayerFragment.this.loadMediaInfo();
}
@Override
@ -133,17 +138,28 @@ public class ExternalPlayerFragment extends Fragment {
public void onResume() {
super.onResume();
onPositionObserverUpdate();
}
@Override
public void onStart() {
super.onStart();
controller.init();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
super.onStop();
if (controller != null) {
controller.release();
}
EventBus.getDefault().unregister(this);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "Fragment is about to be destroyed");
if (controller != null) {
controller.release();
}
if (disposable != null) {
disposable.dispose();
}

View File

@ -165,6 +165,7 @@ public class ExoPlayerWrapper implements IPlayer {
@Override
public void seekTo(int i) throws IllegalStateException {
mExoPlayer.seekTo(i);
audioSeekCompleteListener.onSeekComplete(null);
}
@Override

View File

@ -18,6 +18,7 @@ import java.io.File;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@ -57,16 +58,42 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
private final ReentrantLock playerLock;
private CountDownLatch seekLatch;
private final ThreadPoolExecutor executor;
private final PlayerExecutor executor;
/**
* All ExoPlayer methods must be executed on the same thread.
* We use the main application thread. This class allows to
* "fake" an executor that just calls the methods on the
* calling thread instead of submitting to an executor.
* Other players are still executed in a background thread.
*/
private class PlayerExecutor {
private boolean useCallerThread = true;
private ThreadPoolExecutor threadPool;
public Future<?> submit(Runnable r) {
if (useCallerThread) {
r.run();
return new FutureTask<Void>(() -> {}, null);
} else {
return threadPool.submit(r);
}
}
public void shutdown() {
threadPool.shutdown();
}
}
public LocalPSMP(@NonNull Context context,
@NonNull PSMPCallback callback) {
super(context, callback);
this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
this.playerLock = new ReentrantLock();
this.startWhenPrepared = new AtomicBoolean(false);
executor = new ThreadPoolExecutor(1, 1, 5, TimeUnit.MINUTES, new LinkedBlockingDeque<>(),
executor = new PlayerExecutor();
executor.threadPool = new ThreadPoolExecutor(1, 1, 5, TimeUnit.MINUTES, new LinkedBlockingDeque<>(),
(r, executor) -> Log.d(TAG, "Rejected execution of runnable"));
mediaPlayer = null;
@ -105,6 +132,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
@Override
public void playMediaObject(@NonNull final Playable playable, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) {
Log.d(TAG, "playMediaObject(...)");
executor.useCallerThread = UserPreferences.useExoplayer();
executor.submit(() -> {
playerLock.lock();
try {
@ -372,6 +400,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
*/
@Override
public void reinit() {
executor.useCallerThread = UserPreferences.useExoplayer();
executor.submit(() -> {
playerLock.lock();
Log.d(TAG, "reinit()");
@ -821,6 +850,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
@Override
protected Future<?> endPlayback(final boolean hasEnded, final boolean wasSkipped,
final boolean shouldContinue, final boolean toStoppedState) {
executor.useCallerThread = UserPreferences.useExoplayer();
return executor.submit(() -> {
playerLock.lock();
releaseWifiLockIfNecessary();
@ -1012,7 +1042,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
mp -> genericSeekCompleteListener();
private void genericSeekCompleteListener() {
Thread t = new Thread(() -> {
Runnable r = () -> {
Log.d(TAG, "genericSeekCompleteListener");
if(seekLatch != null) {
seekLatch.countDown();
@ -1025,7 +1055,12 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
setPlayerStatus(statusBeforeSeeking, media, getPosition());
}
playerLock.unlock();
});
t.start();
};
if (executor.useCallerThread) {
r.run();
} else {
new Thread(r).start();
}
}
}

View File

@ -1,6 +1,8 @@
package de.danoeh.antennapod.core.service.playback;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Vibrator;
import android.support.annotation.NonNull;
import android.util.Log;
@ -126,6 +128,7 @@ public class PlaybackServiceTaskManager {
public synchronized void startPositionSaver() {
if (!isPositionSaverActive()) {
Runnable positionSaver = callback::positionSaverTick;
positionSaver = useMainThreadIfNecessary(positionSaver);
positionSaverFuture = schedExecutor.scheduleWithFixedDelay(positionSaver, POSITION_SAVER_WAITING_INTERVAL,
POSITION_SAVER_WAITING_INTERVAL, TimeUnit.MILLISECONDS);
@ -158,6 +161,7 @@ public class PlaybackServiceTaskManager {
public synchronized void startWidgetUpdater() {
if (!isWidgetUpdaterActive()) {
Runnable widgetUpdater = callback::onWidgetUpdaterTick;
widgetUpdater = useMainThreadIfNecessary(widgetUpdater);
widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(widgetUpdater, WIDGET_UPDATER_NOTIFICATION_INTERVAL,
WIDGET_UPDATER_NOTIFICATION_INTERVAL, TimeUnit.MILLISECONDS);
@ -184,7 +188,8 @@ public class PlaybackServiceTaskManager {
sleepTimerFuture.cancel(true);
}
sleepTimer = new SleepTimer(waitingTime, shakeToReset, vibrate);
sleepTimerFuture = schedExecutor.schedule(sleepTimer, 0, TimeUnit.MILLISECONDS);
Runnable runnable = useMainThreadIfNecessary(sleepTimer);
sleepTimerFuture = schedExecutor.schedule(runnable, 0, TimeUnit.MILLISECONDS);
}
/**
@ -267,6 +272,7 @@ public class PlaybackServiceTaskManager {
}
Log.d(TAG, "Chapter loader stopped");
};
chapterLoader = useMainThreadIfNecessary(chapterLoader);
chapterLoaderFuture = schedExecutor.submit(chapterLoader);
}
@ -292,6 +298,17 @@ public class PlaybackServiceTaskManager {
schedExecutor.shutdown();
}
private Runnable useMainThreadIfNecessary(Runnable runnable) {
if (Looper.myLooper() == Looper.getMainLooper()) {
// Called in main thread => ExoPlayer is used
// Run on ui thread even if called from schedExecutor
Handler handler = new Handler();
return () -> handler.post(runnable);
} else {
return runnable;
}
}
/**
* Sleeps for a given time and then pauses playback.
*/