diff options
author | ByteHamster <ByteHamster@users.noreply.github.com> | 2021-11-28 22:19:14 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-28 22:19:14 +0100 |
commit | f0100e61ac633516082ea112363132c99f7c0b7a (patch) | |
tree | f7598c0cee85780b409ab895a8041d1607eec312 /core/src/main/java/de | |
parent | af2835c59dcb0473aba7a48b38f5abe28dca34d3 (diff) | |
download | AntennaPod-f0100e61ac633516082ea112363132c99f7c0b7a.zip |
Chromecast rework (#5518)
Diffstat (limited to 'core/src/main/java/de')
13 files changed, 125 insertions, 617 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java b/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java new file mode 100644 index 000000000..ac67fb042 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/ClientConfig.java @@ -0,0 +1,50 @@ +package de.danoeh.antennapod.core; + +import android.content.Context; + +import de.danoeh.antennapod.net.ssl.SslProviderInstaller; + +import de.danoeh.antennapod.core.preferences.PlaybackPreferences; +import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; +import de.danoeh.antennapod.core.preferences.UsageStatistics; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.core.storage.PodDBAdapter; +import de.danoeh.antennapod.core.util.NetworkUtils; +import de.danoeh.antennapod.core.util.gui.NotificationUtils; + +import java.io.File; + +/** + * Stores callbacks for core classes like Services, DB classes etc. and other configuration variables. + * Apps using the core module of AntennaPod should register implementations of all interfaces here. + */ +public class ClientConfig { + + /** + * Should be used when setting User-Agent header for HTTP-requests. + */ + public static String USER_AGENT; + + public static ApplicationCallbacks applicationCallbacks; + + public static DownloadServiceCallbacks downloadServiceCallbacks; + + private static boolean initialized = false; + + public static synchronized void initialize(Context context) { + if (initialized) { + return; + } + PodDBAdapter.init(context); + UserPreferences.init(context); + UsageStatistics.init(context); + PlaybackPreferences.init(context); + SslProviderInstaller.install(context); + NetworkUtils.init(context); + AntennapodHttpClient.setCacheDirectory(new File(context.getCacheDir(), "okhttp")); + SleepTimerPreferences.init(context); + NotificationUtils.createChannels(context); + initialized = true; + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java index 8d80ef32b..f0c61403f 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java @@ -8,8 +8,8 @@ import android.util.Log; import de.danoeh.antennapod.event.PlayerStatusEvent; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.model.playback.MediaType; -import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.model.playback.Playable; +import de.danoeh.antennapod.playback.base.PlayerStatus; import org.greenrobot.eventbus.EventBus; import static de.danoeh.antennapod.model.feed.FeedPreferences.SPEED_USE_GLOBAL; diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java index 79363e872..7ce06a9fb 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java @@ -40,6 +40,7 @@ import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.core.service.download.HttpDownloader; import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.core.util.playback.IPlayer; +import de.danoeh.antennapod.playback.base.PlaybackServiceMediaPlayer; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java index 5648024de..34fc7d699 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java @@ -17,8 +17,9 @@ import androidx.media.AudioManagerCompat; import de.danoeh.antennapod.event.PlayerErrorEvent; import de.danoeh.antennapod.event.playback.BufferUpdateEvent; import de.danoeh.antennapod.event.playback.SpeedChangedEvent; -import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.util.playback.MediaPlayerError; +import de.danoeh.antennapod.playback.base.PlaybackServiceMediaPlayer; +import de.danoeh.antennapod.playback.base.PlayerStatus; import org.antennapod.audio.MediaPlayer; import java.io.File; @@ -39,7 +40,7 @@ import de.danoeh.antennapod.model.playback.MediaType; import de.danoeh.antennapod.model.feed.VolumeAdaptionSetting; import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.util.RewindAfterPauseUtils; +import de.danoeh.antennapod.playback.base.RewindAfterPauseUtils; import de.danoeh.antennapod.core.util.playback.AudioPlayer; import de.danoeh.antennapod.core.util.playback.IPlayer; import de.danoeh.antennapod.model.playback.Playable; @@ -148,7 +149,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { } public LocalPSMP(@NonNull Context context, - @NonNull PSMPCallback callback) { + @NonNull PlaybackServiceMediaPlayer.PSMPCallback callback) { super(context, callback); this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); this.playerLock = new PlayerLock(); @@ -265,9 +266,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { LocalPSMP.this.startWhenPrepared.set(startWhenPrepared); setPlayerStatus(PlayerStatus.INITIALIZING, media); try { - if (media instanceof FeedMedia && ((FeedMedia) media).getItem() == null) { - ((FeedMedia) media).setItem(DBReader.getFeedItem(((FeedMedia) media).getItemId())); - } + callback.ensureMediaInfoLoaded(media); callback.onMediaChanged(false); setPlaybackParams(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media), UserPreferences.isSkipSilence()); if (stream) { @@ -1098,7 +1097,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { EventBus.getDefault().post(BufferUpdateEvent.ended()); return true; default: - return callback.onMediaPlayerInfo(what, 0); + return true; } } @@ -1148,4 +1147,9 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { executor.submit(r); } } + + @Override + public boolean isCasting() { + return false; + } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index 60ccb5c9e..949c0ff9d 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java @@ -37,6 +37,7 @@ import android.widget.Toast; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; @@ -47,6 +48,10 @@ import de.danoeh.antennapod.event.playback.BufferUpdateEvent; import de.danoeh.antennapod.event.playback.PlaybackServiceEvent; import de.danoeh.antennapod.event.PlayerErrorEvent; import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent; +import de.danoeh.antennapod.playback.base.PlaybackServiceMediaPlayer; +import de.danoeh.antennapod.playback.base.PlayerStatus; +import de.danoeh.antennapod.playback.cast.CastPsmp; +import de.danoeh.antennapod.playback.cast.CastStateListener; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -103,24 +108,10 @@ public class PlaybackService extends MediaBrowserServiceCompat { */ private static final String TAG = "PlaybackService"; - /** - * Parcelable of type Playable. - */ public static final String EXTRA_PLAYABLE = "PlaybackService.PlayableExtra"; - /** - * True if cast session should disconnect. - */ - public static final String EXTRA_CAST_DISCONNECT = "extra.de.danoeh.antennapod.core.service.castDisconnect"; - /** - * True if media should be streamed. - */ public static final String EXTRA_SHOULD_STREAM = "extra.de.danoeh.antennapod.core.service.shouldStream"; public static final String EXTRA_ALLOW_STREAM_THIS_TIME = "extra.de.danoeh.antennapod.core.service.allowStream"; public static final String EXTRA_ALLOW_STREAM_ALWAYS = "extra.de.danoeh.antennapod.core.service.allowStreamAlways"; - /** - * True if playback should be started immediately after media has been - * prepared. - */ public static final String EXTRA_START_WHEN_PREPARED = "extra.de.danoeh.antennapod.core.service.startWhenPrepared"; public static final String EXTRA_PREPARE_IMMEDIATELY = "extra.de.danoeh.antennapod.core.service.prepareImmediately"; @@ -200,10 +191,10 @@ public class PlaybackService extends MediaBrowserServiceCompat { private PlaybackServiceMediaPlayer mediaPlayer; private PlaybackServiceTaskManager taskManager; - private PlaybackServiceFlavorHelper flavorHelper; private PlaybackServiceStateManager stateManager; private Disposable positionEventTimer; private PlaybackServiceNotificationBuilder notificationBuilder; + private CastStateListener castStateListener; private String autoSkippedFeedMediaId = null; @@ -280,7 +271,6 @@ public class PlaybackService extends MediaBrowserServiceCompat { EventBus.getDefault().register(this); taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback); - flavorHelper = new PlaybackServiceFlavorHelper(PlaybackService.this, flavorHelperCallback); PreferenceManager.getDefaultSharedPreferences(this) .registerOnSharedPreferenceChangeListener(prefListener); @@ -305,12 +295,36 @@ public class PlaybackService extends MediaBrowserServiceCompat { npe.printStackTrace(); } - flavorHelper.initializeMediaPlayer(PlaybackService.this); + recreateMediaPlayer(); mediaSession.setActive(true); - + castStateListener = new CastStateListener(this) { + @Override + public void onSessionStartedOrEnded() { + recreateMediaPlayer(); + } + }; EventBus.getDefault().post(new PlaybackServiceEvent(PlaybackServiceEvent.Action.SERVICE_STARTED)); } + void recreateMediaPlayer() { + Playable media = null; + boolean wasPlaying = false; + if (mediaPlayer != null) { + media = mediaPlayer.getPlayable(); + wasPlaying = mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING; + mediaPlayer.pause(true, false); + mediaPlayer.shutdown(); + } + mediaPlayer = CastPsmp.getInstanceIfConnected(this, mediaPlayerCallback); + if (mediaPlayer == null) { + mediaPlayer = new LocalPSMP(this, mediaPlayerCallback); // Cast not supported or not connected + } + if (media != null) { + mediaPlayer.playMediaObject(media, !media.localFileAvailable(), wasPlaying, true); + } + isCasting = mediaPlayer.isCasting(); + } + @Override public void onDestroy() { super.onDestroy(); @@ -324,6 +338,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { stateManager.stopForeground(!UserPreferences.isPersistNotify()); isRunning = false; currentMediaType = MediaType.UNKNOWN; + castStateListener.destroy(); cancelPositionObserver(); PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(prefListener); @@ -337,8 +352,6 @@ public class PlaybackService extends MediaBrowserServiceCompat { unregisterReceiver(audioBecomingNoisy); unregisterReceiver(skipCurrentEpisodeReceiver); unregisterReceiver(pausePlayCurrentEpisodeReceiver); - flavorHelper.removeCastConsumer(); - flavorHelper.unregisterWifiBroadcastReceiver(); mediaPlayer.shutdown(); taskManager.shutdown(); } @@ -483,9 +496,8 @@ public class PlaybackService extends MediaBrowserServiceCompat { final int keycode = intent.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1); final boolean hardwareButton = intent.getBooleanExtra(MediaButtonReceiver.EXTRA_HARDWAREBUTTON, false); - final boolean castDisconnect = intent.getBooleanExtra(EXTRA_CAST_DISCONNECT, false); Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE); - if (keycode == -1 && playable == null && !castDisconnect) { + if (keycode == -1 && playable == null) { Log.e(TAG, "PlaybackService was started with no arguments"); stateManager.stopService(); return Service.START_NOT_STICKY; @@ -509,7 +521,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { stateManager.stopService(); return Service.START_NOT_STICKY; } - } else if (!flavorHelper.castDisconnect(castDisconnect) && playable != null) { + } else { stateManager.validStartCommandWasReceived(); boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, true); boolean allowStreamThisTime = intent.getBooleanExtra(EXTRA_ALLOW_STREAM_THIS_TIME, false); @@ -553,9 +565,6 @@ public class PlaybackService extends MediaBrowserServiceCompat { stateManager.stopService(); }); return Service.START_NOT_STICKY; - } else { - Log.d(TAG, "Did not handle intent to PlaybackService: " + intent); - Log.d(TAG, "Extras: " + intent.getExtras()); } } @@ -781,8 +790,6 @@ public class PlaybackService extends MediaBrowserServiceCompat { saveCurrentPosition(true, null, PlaybackServiceMediaPlayer.INVALID_TIME); } - - @Override public WidgetUpdater.WidgetState requestWidgetState() { return new WidgetUpdater.WidgetState(getPlayable(), getStatus(), @@ -873,11 +880,6 @@ public class PlaybackService extends MediaBrowserServiceCompat { } @Override - public boolean onMediaPlayerInfo(int code, @StringRes int resourceId) { - return flavorHelper.onMediaPlayerInfo(PlaybackService.this, code, resourceId); - } - - @Override public void onPostPlayback(@NonNull Playable media, boolean ended, boolean skipped, boolean playingNext) { PlaybackService.this.onPostPlayback(media, ended, skipped, playingNext); @@ -916,10 +918,24 @@ public class PlaybackService extends MediaBrowserServiceCompat { return PlaybackService.this.getNextInQueue(currentMedia); } + @Nullable + @Override + public Playable findMedia(@NonNull String url) { + FeedItem item = DBReader.getFeedItemByGuidOrEpisodeUrl(null, url); + return item != null ? item.getMedia() : null; + } + @Override public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) { PlaybackService.this.onPlaybackEnded(mediaType, stopPlaying); } + + @Override + public void ensureMediaInfoLoaded(@NonNull Playable media) { + if (media instanceof FeedMedia && ((FeedMedia) media).getItem() == null) { + ((FeedMedia) media).setItem(DBReader.getFeedItem(((FeedMedia) media).getItemId())); + } + } }; @Subscribe(threadMode = ThreadMode.MAIN) @@ -1248,15 +1264,15 @@ public class PlaybackService extends MediaBrowserServiceCompat { // This would give the PIP of videos a play button capabilities = capabilities | PlaybackStateCompat.ACTION_PLAY; if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_WATCH) { - flavorHelper.sessionStateAddActionForWear(sessionState, + WearMediaSession.sessionStateAddActionForWear(sessionState, CUSTOM_ACTION_REWIND, getString(R.string.rewind_label), android.R.drawable.ic_media_rew); - flavorHelper.sessionStateAddActionForWear(sessionState, + WearMediaSession.sessionStateAddActionForWear(sessionState, CUSTOM_ACTION_FAST_FORWARD, getString(R.string.fast_forward_label), android.R.drawable.ic_media_ff); - flavorHelper.mediaSessionSetExtraForWear(mediaSession); + WearMediaSession.mediaSessionSetExtraForWear(mediaSession); } } @@ -1338,7 +1354,6 @@ public class PlaybackService extends MediaBrowserServiceCompat { notificationBuilder.setPlayable(playable); notificationBuilder.setMediaSessionToken(mediaSession.getSessionToken()); notificationBuilder.setPlayerStatus(playerStatus); - notificationBuilder.setCasting(isCasting); notificationBuilder.updatePosition(getCurrentPosition(), getCurrentPlaybackSpeed()); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); @@ -1901,93 +1916,6 @@ public class PlaybackService extends MediaBrowserServiceCompat { (sharedPreferences, key) -> { if (UserPreferences.PREF_LOCKSCREEN_BACKGROUND.equals(key)) { updateNotificationAndMediaSession(getPlayable()); - } else { - flavorHelper.onSharedPreference(key); } }; - - interface FlavorHelperCallback { - PlaybackServiceMediaPlayer.PSMPCallback getMediaPlayerCallback(); - - void setMediaPlayer(PlaybackServiceMediaPlayer mediaPlayer); - - PlaybackServiceMediaPlayer getMediaPlayer(); - - void setIsCasting(boolean isCasting); - - void sendNotificationBroadcast(int type, int code); - - void saveCurrentPosition(boolean fromMediaPlayer, Playable playable, int position); - - void setupNotification(boolean connected, PlaybackServiceMediaPlayer.PSMPInfo info); - - MediaSessionCompat getMediaSession(); - - Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter); - - void unregisterReceiver(BroadcastReceiver receiver); - } - - private final FlavorHelperCallback flavorHelperCallback = new FlavorHelperCallback() { - @Override - public PlaybackServiceMediaPlayer.PSMPCallback getMediaPlayerCallback() { - return PlaybackService.this.mediaPlayerCallback; - } - - @Override - public void setMediaPlayer(PlaybackServiceMediaPlayer mediaPlayer) { - PlaybackService.this.mediaPlayer = mediaPlayer; - } - - @Override - public PlaybackServiceMediaPlayer getMediaPlayer() { - return PlaybackService.this.mediaPlayer; - } - - @Override - public void setIsCasting(boolean isCasting) { - PlaybackService.isCasting = isCasting; - stateManager.validStartCommandWasReceived(); - } - - @Override - public void sendNotificationBroadcast(int type, int code) { - PlaybackService.this.sendNotificationBroadcast(type, code); - } - - @Override - public void saveCurrentPosition(boolean fromMediaPlayer, Playable playable, int position) { - PlaybackService.this.saveCurrentPosition(fromMediaPlayer, playable, position); - } - - @Override - public void setupNotification(boolean connected, PlaybackServiceMediaPlayer.PSMPInfo info) { - if (connected) { - PlaybackService.this.updateNotificationAndMediaSession(info.playable); - } else { - PlayerStatus status = info.playerStatus; - if (status == PlayerStatus.PLAYING || status == PlayerStatus.SEEKING - || status == PlayerStatus.PREPARING || UserPreferences.isPersistNotify()) { - PlaybackService.this.updateNotificationAndMediaSession(info.playable); - } else if (!UserPreferences.isPersistNotify()) { - stateManager.stopForeground(true); - } - } - } - - @Override - public MediaSessionCompat getMediaSession() { - return PlaybackService.this.mediaSession; - } - - @Override - public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { - return PlaybackService.this.registerReceiver(receiver, filter); - } - - @Override - public void unregisterReceiver(BroadcastReceiver receiver) { - PlaybackService.this.unregisterReceiver(receiver); - } - }; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java deleted file mode 100644 index 623ad58bb..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java +++ /dev/null @@ -1,380 +0,0 @@ -package de.danoeh.antennapod.core.service.playback; - -import android.content.Context; -import android.media.AudioManager; -import android.net.wifi.WifiManager; -import androidx.annotation.NonNull; -import androidx.annotation.StringRes; -import android.util.Log; -import android.util.Pair; -import android.view.SurfaceHolder; - -import java.util.List; -import java.util.concurrent.Future; - -import de.danoeh.antennapod.model.playback.MediaType; -import de.danoeh.antennapod.model.playback.Playable; - - -/* - * An inconvenience of an implementation like this is that some members and methods that once were - * private are now protected, allowing for access from classes of the same package, namely - * PlaybackService. A workaround would be to move this to a dedicated package. - */ -/** - * Abstract class that allows for different implementations of the PlaybackServiceMediaPlayer for local - * and remote (cast devices) playback. - */ -public abstract class PlaybackServiceMediaPlayer { - private static final String TAG = "PlaybackSvcMediaPlayer"; - - /** - * Return value of some PSMP methods if the method call failed. - */ - static final int INVALID_TIME = -1; - - private volatile PlayerStatus oldPlayerStatus; - volatile PlayerStatus playerStatus; - - /** - * A wifi-lock that is acquired if the media file is being streamed. - */ - private WifiManager.WifiLock wifiLock; - - final PSMPCallback callback; - final Context context; - - PlaybackServiceMediaPlayer(@NonNull Context context, - @NonNull PSMPCallback callback){ - this.context = context; - this.callback = callback; - - playerStatus = PlayerStatus.STOPPED; - } - - /** - * Starts or prepares playback of the specified Playable object. If another Playable object is already being played, the currently playing - * episode will be stopped and replaced with the new Playable object. If the Playable object is already being played, the method will - * not do anything. - * Whether playback starts immediately depends on the given parameters. See below for more details. - * <p/> - * States: - * During execution of the method, the object will be in the INITIALIZING state. The end state depends on the given parameters. - * <p/> - * If 'prepareImmediately' is set to true, the method will go into PREPARING state and after that into PREPARED state. If - * 'startWhenPrepared' is set to true, the method will additionally go into PLAYING state. - * <p/> - * If an unexpected error occurs while loading the Playable's metadata or while setting the MediaPlayers data source, the object - * will enter the ERROR state. - * <p/> - * This method is executed on an internal executor service. - * - * @param playable The Playable object that is supposed to be played. This parameter must not be null. - * @param stream The type of playback. If false, the Playable object MUST provide access to a locally available file via - * getLocalMediaUrl. If true, the Playable object MUST provide access to a resource that can be streamed by - * the Android MediaPlayer via getStreamUrl. - * @param startWhenPrepared Sets the 'startWhenPrepared' flag. This flag determines whether playback will start immediately after the - * episode has been prepared for playback. Setting this flag to true does NOT mean that the episode will be prepared - * for playback immediately (see 'prepareImmediately' parameter for more details) - * @param prepareImmediately Set to true if the method should also prepare the episode for playback. - */ - public abstract void playMediaObject(@NonNull Playable playable, boolean stream, boolean startWhenPrepared, boolean prepareImmediately); - - /** - * Resumes playback if the PSMP object is in PREPARED or PAUSED state. If the PSMP object is in an invalid state. - * nothing will happen. - * <p/> - * This method is executed on an internal executor service. - */ - public abstract void resume(); - - /** - * Saves the current position and pauses playback. Note that, if audiofocus - * is abandoned, the lockscreen controls will also disapear. - * <p/> - * This method is executed on an internal executor service. - * - * @param abandonFocus is true if the service should release audio focus - * @param reinit is true if service should reinit after pausing if the media - * file is being streamed - */ - public abstract void pause(boolean abandonFocus, boolean reinit); - - /** - * Prepared media player for playback if the service is in the INITALIZED - * state. - * <p/> - * This method is executed on an internal executor service. - */ - public abstract void prepare(); - - /** - * Resets the media player and moves it into INITIALIZED state. - * <p/> - * This method is executed on an internal executor service. - */ - public abstract void reinit(); - - /** - * Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing. - * Invalid time values (< 0) will be ignored. - * <p/> - * This method is executed on an internal executor service. - */ - public abstract void seekTo(int t); - - /** - * Seek a specific position from the current position - * - * @param d offset from current position (positive or negative) - */ - public abstract void seekDelta(int d); - - /** - * Returns the duration of the current media object or INVALID_TIME if the duration could not be retrieved. - */ - public abstract int getDuration(); - - /** - * Returns the position of the current media object or INVALID_TIME if the position could not be retrieved. - */ - public abstract int getPosition(); - - public abstract boolean isStartWhenPrepared(); - - public abstract void setStartWhenPrepared(boolean startWhenPrepared); - - /** - * Sets the playback parameters. - * - Speed - * - SkipSilence (ExoPlayer only) - * This method is executed on an internal executor service. - */ - public abstract void setPlaybackParams(final float speed, final boolean skipSilence); - - /** - * Returns the current playback speed. If the playback speed could not be retrieved, 1 is returned. - */ - public abstract float getPlaybackSpeed(); - - /** - * Sets the playback volume. - * This method is executed on an internal executor service. - */ - public abstract void setVolume(float volumeLeft, float volumeRight); - - /** - * Returns true if the mediaplayer can mix stereo down to mono - */ - public abstract boolean canDownmix(); - - public abstract void setDownmix(boolean enable); - - public abstract MediaType getCurrentMediaType(); - - public abstract boolean isStreaming(); - - /** - * Releases internally used resources. This method should only be called when the object is not used anymore. - */ - public abstract void shutdown(); - - /** - * Releases internally used resources. This method should only be called when the object is not used anymore. - * This method is executed on an internal executor service. - */ - public abstract void shutdownQuietly(); - - public abstract void setVideoSurface(SurfaceHolder surface); - - public abstract void resetVideoSurface(); - - /** - * Return width and height of the currently playing video as a pair. - * - * @return Width and height as a Pair or null if the video size could not be determined. The method might still - * return an invalid non-null value if the getVideoWidth() and getVideoHeight() methods of the media player return - * invalid values. - */ - public abstract Pair<Integer, Integer> getVideoSize(); - - /** - * Returns a PSMInfo object that contains information about the current state of the PSMP object. - * - * @return The PSMPInfo object. - */ - public final synchronized PSMPInfo getPSMPInfo() { - return new PSMPInfo(oldPlayerStatus, playerStatus, getPlayable()); - } - - /** - * Returns the current status, if you need the media and the player status together, you should - * use getPSMPInfo() to make sure they're properly synchronized. Otherwise a race condition - * could result in nonsensical results (like a status of PLAYING, but a null playable) - * @return the current player status - */ - public synchronized PlayerStatus getPlayerStatus() { - return playerStatus; - } - - /** - * Returns the current media, if you need the media and the player status together, you should - * use getPSMPInfo() to make sure they're properly synchronized. Otherwise a race condition - * could result in nonsensical results (like a status of PLAYING, but a null playable) - * @return the current media. May be null - */ - public abstract Playable getPlayable(); - - protected abstract void setPlayable(Playable playable); - - public abstract List<String> getAudioTracks(); - - public abstract void setAudioTrack(int track); - - public abstract int getSelectedAudioTrack(); - - public void skip() { - endPlayback(false, true, true, true); - } - - /** - * Ends playback of current media (if any) and moves into INDETERMINATE state, unless - * {@param toStoppedState} is set to true, in which case it moves into STOPPED state. - * - * @see #endPlayback(boolean, boolean, boolean, boolean) - */ - public Future<?> stopPlayback(boolean toStoppedState) { - return endPlayback(false, false, false, toStoppedState); - } - - /** - * Internal method that handles end of playback. - * - * Currently, it has 5 use cases: - * <ul> - * <li>Media playback has completed: call with (true, false, true, true)</li> - * <li>User asks to skip to next episode: call with (false, true, true, true)</li> - * <li>Skipping to next episode due to playback error: call with (false, false, true, true)</li> - * <li>Stopping the media player: call with (false, false, false, true)</li> - * <li>We want to change the media player implementation: call with (false, false, false, false)</li> - * </ul> - * - * @param hasEnded If true, we assume the current media's playback has ended, for - * purposes of post playback processing. - * @param wasSkipped Whether the user chose to skip the episode (by pressing the skip - * button). - * @param shouldContinue If true, the media player should try to load, and possibly play, - * the next item, based on the user preferences and whether such item - * exists. - * @param toStoppedState If true, the playback state gets set to STOPPED if the media player - * is not loading/playing after this call, and the UI will reflect that. - * Only relevant if {@param shouldContinue} is set to false, otherwise - * this method's behavior defaults as if this parameter was true. - * - * @return a Future, just for the purpose of tracking its execution. - */ - protected abstract Future<?> endPlayback(boolean hasEnded, boolean wasSkipped, - boolean shouldContinue, boolean toStoppedState); - - /** - * @return {@code true} if the WifiLock feature should be used, {@code false} otherwise. - */ - protected abstract boolean shouldLockWifi(); - - final synchronized void acquireWifiLockIfNecessary() { - if (shouldLockWifi()) { - if (wifiLock == null) { - wifiLock = ((WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE)) - .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); - wifiLock.setReferenceCounted(false); - } - wifiLock.acquire(); - } - } - - final synchronized void releaseWifiLockIfNecessary() { - if (wifiLock != null && wifiLock.isHeld()) { - wifiLock.release(); - } - } - - /** - * Sets the player status of the PSMP object. PlayerStatus and media attributes have to be set at the same time - * so that getPSMPInfo can't return an invalid state (e.g. status is PLAYING, but media is null). - * <p/> - * This method will notify the callback about the change of the player status (even if the new status is the same - * as the old one). - * <p/> - * It will also call {@link PSMPCallback#onPlaybackPause(Playable, int)} or {@link PSMPCallback#onPlaybackStart(Playable, int)} - * depending on the status change. - * - * @param newStatus The new PlayerStatus. This must not be null. - * @param newMedia The new playable object of the PSMP object. This can be null. - * @param position The position to be set to the current Playable object in case playback started or paused. - * Will be ignored if given the value of {@link #INVALID_TIME}. - */ - final synchronized void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia, int position) { - Log.d(TAG, this.getClass().getSimpleName() + ": Setting player status to " + newStatus); - - this.oldPlayerStatus = playerStatus; - this.playerStatus = newStatus; - setPlayable(newMedia); - - if (newMedia != null && newStatus != PlayerStatus.INDETERMINATE) { - if (oldPlayerStatus == PlayerStatus.PLAYING && newStatus != PlayerStatus.PLAYING) { - callback.onPlaybackPause(newMedia, position); - } else if (oldPlayerStatus != PlayerStatus.PLAYING && newStatus == PlayerStatus.PLAYING) { - callback.onPlaybackStart(newMedia, position); - } - } - - callback.statusChanged(new PSMPInfo(oldPlayerStatus, playerStatus, getPlayable())); - } - - public boolean isAudioChannelInUse() { - AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - return (audioManager.getMode() != AudioManager.MODE_NORMAL || audioManager.isMusicActive()); - } - - /** - * @see #setPlayerStatus(PlayerStatus, Playable, int) - */ - final void setPlayerStatus(@NonNull PlayerStatus newStatus, Playable newMedia) { - setPlayerStatus(newStatus, newMedia, INVALID_TIME); - } - - public interface PSMPCallback { - void statusChanged(PSMPInfo newInfo); - - void shouldStop(); - - void onMediaChanged(boolean reloadUI); - - boolean onMediaPlayerInfo(int code, @StringRes int resourceId); - - void onPostPlayback(@NonNull Playable media, boolean ended, boolean skipped, boolean playingNext); - - void onPlaybackStart(@NonNull Playable playable, int position); - - void onPlaybackPause(Playable playable, int position); - - Playable getNextInQueue(Playable currentMedia); - - void onPlaybackEnded(MediaType mediaType, boolean stopPlaying); - } - - /** - * Holds information about a PSMP object. - */ - public static class PSMPInfo { - public final PlayerStatus oldPlayerStatus; - public PlayerStatus playerStatus; - public Playable playable; - - PSMPInfo(PlayerStatus oldPlayerStatus, PlayerStatus playerStatus, Playable playable) { - this.oldPlayerStatus = oldPlayerStatus; - this.playerStatus = playerStatus; - this.playable = playable; - } - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java index 5aee8c24c..c348f5773 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java @@ -31,17 +31,17 @@ import de.danoeh.antennapod.model.playback.Playable; import java.util.ArrayList; import java.util.concurrent.ExecutionException; +import de.danoeh.antennapod.playback.base.PlayerStatus; import org.apache.commons.lang3.ArrayUtils; public class PlaybackServiceNotificationBuilder { private static final String TAG = "PlaybackSrvNotification"; private static Bitmap defaultIcon = null; - private Context context; + private final Context context; private Playable playable; private MediaSessionCompat.Token mediaSessionToken; private PlayerStatus playerStatus; - private boolean isCasting; private Bitmap icon; private String position; @@ -140,7 +140,7 @@ public class PlaybackServiceNotificationBuilder { if (playable != null) { notification.setContentTitle(playable.getFeedTitle()); notification.setContentText(playable.getEpisodeTitle()); - addActions(notification, mediaSessionToken, playerStatus, isCasting); + addActions(notification, mediaSessionToken, playerStatus); if (icon != null) { notification.setLargeIcon(icon); @@ -175,23 +175,10 @@ public class PlaybackServiceNotificationBuilder { } private void addActions(NotificationCompat.Builder notification, MediaSessionCompat.Token mediaSessionToken, - PlayerStatus playerStatus, boolean isCasting) { + PlayerStatus playerStatus) { ArrayList<Integer> compactActionList = new ArrayList<>(); int numActions = 0; // we start and 0 and then increment by 1 for each call to addAction - - if (isCasting) { - Intent stopCastingIntent = new Intent(context, PlaybackService.class); - stopCastingIntent.putExtra(PlaybackService.EXTRA_CAST_DISCONNECT, true); - PendingIntent stopCastingPendingIntent = PendingIntent.getService(context, - numActions, stopCastingIntent, PendingIntent.FLAG_UPDATE_CURRENT - | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); - notification.addAction(R.drawable.ic_notification_cast_off, - context.getString(R.string.cast_disconnect_label), - stopCastingPendingIntent); - numActions++; - } - // always let them rewind PendingIntent rewindButtonPendingIntent = getPendingIntentForMediaAction( KeyEvent.KEYCODE_MEDIA_REWIND, numActions); @@ -270,10 +257,6 @@ public class PlaybackServiceNotificationBuilder { this.playerStatus = playerStatus; } - public void setCasting(boolean casting) { - isCasting = casting; - } - public PlayerStatus getPlayerStatus() { return playerStatus; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdater.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdater.java index edb8bc3a9..43837a473 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdater.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackVolumeUpdater.java @@ -4,6 +4,8 @@ import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.model.feed.FeedPreferences; import de.danoeh.antennapod.model.feed.VolumeAdaptionSetting; import de.danoeh.antennapod.model.playback.Playable; +import de.danoeh.antennapod.playback.base.PlaybackServiceMediaPlayer; +import de.danoeh.antennapod.playback.base.PlayerStatus; class PlaybackVolumeUpdater { diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java deleted file mode 100644 index 4f2ae34f8..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlayerStatus.java +++ /dev/null @@ -1,33 +0,0 @@ -package de.danoeh.antennapod.core.service.playback; - -public enum PlayerStatus { - INDETERMINATE(0), // player is currently changing its state, listeners should wait until the player has left this state. - ERROR(-1), - PREPARING(19), - PAUSED(30), - PLAYING(40), - STOPPED(5), - PREPARED(20), - SEEKING(29), - INITIALIZING(9), // playback service is loading the Playable's metadata - INITIALIZED(10); // playback service was started, data source of media player was set. - - private final int statusValue; - private static final PlayerStatus[] fromOrdinalLookup; - - static { - fromOrdinalLookup = PlayerStatus.values(); - } - - PlayerStatus(int val) { - statusValue = val; - } - - public static PlayerStatus fromOrdinal(int o) { - return fromOrdinalLookup[o]; - } - - public boolean isAtLeast(PlayerStatus other) { - return other == null || this.statusValue>=other.statusValue; - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/RewindAfterPauseUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/RewindAfterPauseUtils.java deleted file mode 100644 index 813c6d0f7..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/util/RewindAfterPauseUtils.java +++ /dev/null @@ -1,47 +0,0 @@ -package de.danoeh.antennapod.core.util; - -import java.util.concurrent.TimeUnit; - -/** - * This class calculates the proper rewind time after the pause and resume. - * <p> - * User might loose context if he/she pauses and resumes the media after longer time. - * Media file should be "rewinded" x seconds after user resumes the playback. - */ -public class RewindAfterPauseUtils { - private RewindAfterPauseUtils(){} - - public static final long ELAPSED_TIME_FOR_SHORT_REWIND = TimeUnit.MINUTES.toMillis(1); - public static final long ELAPSED_TIME_FOR_MEDIUM_REWIND = TimeUnit.HOURS.toMillis(1); - public static final long ELAPSED_TIME_FOR_LONG_REWIND = TimeUnit.DAYS.toMillis(1); - - public static final long SHORT_REWIND = TimeUnit.SECONDS.toMillis(3); - public static final long MEDIUM_REWIND = TimeUnit.SECONDS.toMillis(10); - public static final long LONG_REWIND = TimeUnit.SECONDS.toMillis(20); - - /** - * @param currentPosition current position in a media file in ms - * @param lastPlayedTime timestamp when was media paused - * @return new rewinded position for playback in milliseconds - */ - public static int calculatePositionWithRewind(int currentPosition, long lastPlayedTime) { - if (currentPosition > 0 && lastPlayedTime > 0) { - long elapsedTime = System.currentTimeMillis() - lastPlayedTime; - long rewindTime = 0; - - if (elapsedTime > ELAPSED_TIME_FOR_LONG_REWIND) { - rewindTime = LONG_REWIND; - } else if (elapsedTime > ELAPSED_TIME_FOR_MEDIUM_REWIND) { - rewindTime = MEDIUM_REWIND; - } else if (elapsedTime > ELAPSED_TIME_FOR_SHORT_REWIND) { - rewindTime = SHORT_REWIND; - } - - int newPosition = currentPosition - (int) rewindTime; - - return Math.max(newPosition, 0); - } else { - return currentPosition; - } - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java index b436d80b2..549171c76 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java @@ -22,9 +22,9 @@ import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer; -import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.model.playback.Playable; +import de.danoeh.antennapod.playback.base.PlaybackServiceMediaPlayer; +import de.danoeh.antennapod.playback.base.PlayerStatus; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; diff --git a/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java b/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java index 5275e7080..2762fb9fe 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java +++ b/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java @@ -25,11 +25,11 @@ import de.danoeh.antennapod.model.playback.MediaType; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; import de.danoeh.antennapod.core.receiver.PlayerWidget; -import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.util.TimeSpeedConverter; import de.danoeh.antennapod.model.playback.Playable; +import de.danoeh.antennapod.playback.base.PlayerStatus; import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter; import de.danoeh.antennapod.ui.appstartintent.VideoPlayerActivityStarter; diff --git a/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdaterJobService.java b/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdaterJobService.java index b14fb3b0b..325c508c5 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdaterJobService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdaterJobService.java @@ -6,9 +6,9 @@ import androidx.annotation.NonNull; import androidx.core.app.SafeJobIntentService; import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; -import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.model.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlayableUtils; +import de.danoeh.antennapod.playback.base.PlayerStatus; public class WidgetUpdaterJobService extends SafeJobIntentService { private static final int JOB_ID = -17001; |