diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/FeedPreferenceSkipDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/FeedPreferenceSkipDialog.java new file mode 100644 index 000000000..0e5a064eb --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/dialog/FeedPreferenceSkipDialog.java @@ -0,0 +1,48 @@ +package de.danoeh.antennapod.dialog; + +import android.content.Context; +import android.view.View; +import android.widget.EditText; +import androidx.appcompat.app.AlertDialog; +import de.danoeh.antennapod.R; + +/** + * Displays a dialog with a username and password text field and an optional checkbox to save username and preferences. + */ +public abstract class FeedPreferenceSkipDialog extends AlertDialog.Builder { + + public FeedPreferenceSkipDialog(Context context, int skipIntroInitialValue, + int skipEndInitialValue) { + super(context); + setTitle(R.string.pref_feed_skip); + View rootView = View.inflate(context, R.layout.feed_pref_skip_dialog, null); + setView(rootView); + + final EditText etxtSkipIntro = rootView.findViewById(R.id.etxtSkipIntro); + final EditText etxtSkipEnd = rootView.findViewById(R.id.etxtSkipEnd); + + etxtSkipIntro.setText(String.valueOf(skipIntroInitialValue)); + etxtSkipEnd.setText(String.valueOf(skipEndInitialValue)); + + setNegativeButton(R.string.cancel_label, null); + setPositiveButton(R.string.confirm_label, (dialog, which) + -> { + int skipIntro; + int skipEnding; + try { + skipIntro = Integer.parseInt(etxtSkipIntro.getText().toString()); + } catch (NumberFormatException e) { + skipIntro = 0; + } + + try { + skipEnding = Integer.parseInt(etxtSkipEnd.getText().toString()); + } catch (NumberFormatException e) { + skipEnding = 0; + } + onConfirmed(skipIntro, skipEnding); + }); + } + + protected abstract void onConfirmed(int skipIntro, int skipEndig); +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java index 23b8b7f19..8251e8716 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java @@ -17,6 +17,7 @@ import androidx.preference.PreferenceFragmentCompat; import androidx.preference.SwitchPreference; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; +import de.danoeh.antennapod.core.event.settings.SkipIntroEndingChangedEvent; import de.danoeh.antennapod.core.event.settings.SpeedPresetChangedEvent; import de.danoeh.antennapod.core.event.settings.VolumeAdaptionChangedEvent; import de.danoeh.antennapod.core.feed.Feed; @@ -28,6 +29,7 @@ import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.dialog.AuthenticationDialog; import de.danoeh.antennapod.dialog.EpisodeFilterDialog; +import de.danoeh.antennapod.dialog.FeedPreferenceSkipDialog; import io.reactivex.Maybe; import io.reactivex.MaybeOnSubscribe; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -99,6 +101,7 @@ public class FeedSettingsFragment extends Fragment { private static final CharSequence PREF_EPISODE_FILTER = "episodeFilter"; private static final CharSequence PREF_SCREEN = "feedSettingsScreen"; private static final String PREF_FEED_PLAYBACK_SPEED = "feedPlaybackSpeed"; + private static final String PREF_AUTO_SKIP = "feedAutoSkip"; private static final DecimalFormat SPEED_FORMAT = new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.US)); @@ -142,6 +145,7 @@ public class FeedSettingsFragment extends Fragment { setupAuthentificationPreference(); setupEpisodeFilterPreference(); setupPlaybackSpeedPreference(); + setupFeedAutoSkipPreference(); updateAutoDeleteSummary(); updateVolumeReductionValue(); @@ -159,6 +163,26 @@ public class FeedSettingsFragment extends Fragment { } } + private void setupFeedAutoSkipPreference() { + findPreference(PREF_AUTO_SKIP).setOnPreferenceClickListener(preference -> { + new FeedPreferenceSkipDialog(getContext(), + feedPreferences.getFeedSkipIntro(), + feedPreferences.getFeedSkipEnding()) { + @Override + protected void onConfirmed(int skipIntro, int skipEnding) { + feedPreferences.setFeedSkipIntro(skipIntro); + feedPreferences.setFeedSkipEnding(skipEnding); + feed.savePreferences(); + EventBus.getDefault().post( + new SkipIntroEndingChangedEvent(feedPreferences.getFeedSkipIntro(), + feedPreferences.getFeedSkipEnding(), + feed.getId())); + } + }.show(); + return false; + }); + } + private void setupPlaybackSpeedPreference() { ListPreference feedPlaybackSpeedPreference = findPreference(PREF_FEED_PLAYBACK_SPEED); diff --git a/app/src/main/res/layout/feed_pref_skip_dialog.xml b/app/src/main/res/layout/feed_pref_skip_dialog.xml new file mode 100644 index 000000000..db76a3426 --- /dev/null +++ b/app/src/main/res/layout/feed_pref_skip_dialog.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/xml/feed_settings.xml b/app/src/main/res/xml/feed_settings.xml index a9effdeeb..9a3a4f438 100644 --- a/app/src/main/res/xml/feed_settings.xml +++ b/app/src/main/res/xml/feed_settings.xml @@ -1,7 +1,7 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + android:key="feedSettingsScreen"> + + 0; @@ -75,7 +85,20 @@ public class FeedPreferences { String includeFilter = cursor.getString(indexIncludeFilter); String excludeFilter = cursor.getString(indexExcludeFilter); float feedPlaybackSpeed = cursor.getFloat(indexFeedPlaybackSpeed); - return new FeedPreferences(feedId, autoDownload, autoRefresh, autoDeleteAction, volumeAdaptionSetting, username, password, new FeedFilter(includeFilter, excludeFilter), feedPlaybackSpeed); + int feedAutoSkipIntro = cursor.getInt(indexAutoSkipIntro); + int feedAutoSkipEnding = cursor.getInt(indexAutoSkipEnding); + return new FeedPreferences(feedId, + autoDownload, + autoRefresh, + autoDeleteAction, + volumeAdaptionSetting, + username, + password, + new FeedFilter(includeFilter, excludeFilter), + feedPlaybackSpeed, + feedAutoSkipIntro, + feedAutoSkipEnding + ); } /** @@ -204,4 +227,20 @@ public class FeedPreferences { public void setFeedPlaybackSpeed(float playbackSpeed) { feedPlaybackSpeed = playbackSpeed; } + + public void setFeedSkipIntro(int skipIntro) { + feedSkipIntro = skipIntro; + } + + public int getFeedSkipIntro() { + return feedSkipIntro; + } + + public void setFeedSkipEnding(int skipEnding) { + feedSkipEnding = skipEnding; + } + + public int getFeedSkipEnding() { + return feedSkipEnding; + } } 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 2679a93d4..a83e50506 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 @@ -51,12 +51,14 @@ import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.event.MessageEvent; import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.event.ServiceEvent; +import de.danoeh.antennapod.core.event.settings.SkipIntroEndingChangedEvent; import de.danoeh.antennapod.core.event.settings.SpeedPresetChangedEvent; import de.danoeh.antennapod.core.event.settings.VolumeAdaptionChangedEvent; import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.feed.FeedPreferences; import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; @@ -211,6 +213,8 @@ public class PlaybackService extends MediaBrowserServiceCompat { private Disposable positionEventTimer; private PlaybackServiceNotificationBuilder notificationBuilder; + private long autoSkippedFeedMediaId = -1; + /** * Used for Lollipop notifications, Android Wear, and Android Auto. */ @@ -507,6 +511,30 @@ public class PlaybackService extends MediaBrowserServiceCompat { return Service.START_NOT_STICKY; } + private void skipIntro(Playable playable) { + if (! (playable instanceof FeedMedia)) { + return; + } + + FeedMedia feedMedia = (FeedMedia) playable; + FeedPreferences preferences = feedMedia.getItem().getFeed().getPreferences(); + int skipIntro = preferences.getFeedSkipIntro(); + + Context context = getApplicationContext(); + if (skipIntro > 0 && playable.getPosition() < skipIntro * 1000) { + int duration = getDuration(); + if (skipIntro * 1000 < duration) { + Log.d(TAG, "skipIntro " + playable.getEpisodeTitle()); + mediaPlayer.seekTo(skipIntro * 1000); + String skipIntroMesg = context.getString(R.string.pref_feed_skip_intro_toast, + skipIntro); + Toast toast = Toast.makeText(context, skipIntroMesg, + Toast.LENGTH_LONG); + toast.show(); + } + } + } + private void displayStreamingNotAllowedNotification(Intent originalIntent) { Intent intentAllowThisTime = new Intent(originalIntent); intentAllowThisTime.setAction(EXTRA_ALLOW_STREAM_THIS_TIME); @@ -758,7 +786,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { case PLAYING: PlaybackPreferences.writePlayerStatus(mediaPlayer.getPlayerStatus()); setupNotification(newInfo); - setupPositionUpdater(); + setupPositionObserver(); stateManager.validStartCommandWasReceived(); // set sleep timer if auto-enabled if (newInfo.oldPlayerStatus != null && newInfo.oldPlayerStatus != PlayerStatus.SEEKING @@ -849,6 +877,8 @@ public class PlaybackService extends MediaBrowserServiceCompat { taskManager.startWidgetUpdater(); if (position != PlaybackServiceMediaPlayer.INVALID_TIME) { playable.setPosition(position); + } else { + skipIntro(playable); } playable.onPlaybackStart(); taskManager.startPositionSaver(); @@ -1000,6 +1030,10 @@ public class PlaybackService extends MediaBrowserServiceCompat { media.onPlaybackPause(getApplicationContext()); } + if (autoSkippedFeedMediaId >= 0 && autoSkippedFeedMediaId == media.getId()) { + ended = true; + } + if (item != null) { if (ended || smartMarkAsPlayed || (skipped && !UserPreferences.shouldSkipKeepEpisode())) { @@ -1040,6 +1074,33 @@ public class PlaybackService extends MediaBrowserServiceCompat { sendBroadcast(intent); } + private void skipEndingIfNecessary() { + Playable playable = mediaPlayer.getPlayable(); + if (! (playable instanceof FeedMedia)) { + return; + } + + int duration = getDuration(); + int remainingTime = duration - getCurrentPosition(); + + FeedMedia feedMedia = (FeedMedia) playable; + FeedPreferences preferences = feedMedia.getItem().getFeed().getPreferences(); + int skipEnd = preferences.getFeedSkipEnding(); + if (skipEnd > 0 + && skipEnd < playable.getDuration() + && (remainingTime - (skipEnd * 1000) > 0) + && ((remainingTime - skipEnd * 1000) < (getCurrentPlaybackSpeed() * 1000))) { + Log.d(TAG, "skipEndingIfNecessary: Skipping the remaining " + remainingTime + " " + skipEnd * 1000 + " speed " + getCurrentPlaybackSpeed()); + Context context = getApplicationContext(); + String skipMesg = context.getString(R.string.pref_feed_skip_ending_toast, skipEnd); + Toast toast = Toast.makeText(context, skipMesg, Toast.LENGTH_LONG); + toast.show(); + + this.autoSkippedFeedMediaId = feedMedia.getItem().getId(); + mediaPlayer.skip(); + } + } + /** * Updates the Media Session for the corresponding status. * @@ -1451,12 +1512,14 @@ public class PlaybackService extends MediaBrowserServiceCompat { }; @Subscribe(threadMode = ThreadMode.MAIN) + @SuppressWarnings("unused") public void volumeAdaptionChanged(VolumeAdaptionChangedEvent event) { PlaybackVolumeUpdater playbackVolumeUpdater = new PlaybackVolumeUpdater(); playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer, event.getFeedId(), event.getVolumeAdaptionSetting()); } @Subscribe(threadMode = ThreadMode.MAIN) + @SuppressWarnings("unused") public void speedPresetChanged(SpeedPresetChangedEvent event) { if (getPlayable() instanceof FeedMedia) { if (((FeedMedia) getPlayable()).getItem().getFeed().getId() == event.getFeedId()) { @@ -1469,6 +1532,22 @@ public class PlaybackService extends MediaBrowserServiceCompat { } } + @Subscribe(threadMode = ThreadMode.MAIN) + @SuppressWarnings("unused") + public void skipIntroEndingPresetChanged(SkipIntroEndingChangedEvent event) { + if (getPlayable() instanceof FeedMedia) { + if (((FeedMedia) getPlayable()).getItem().getFeed().getId() == event.getFeedId()) { + if (event.getSkipEnding() != 0) { + FeedPreferences feedPreferences + = ((FeedMedia) getPlayable()).getItem().getFeed().getPreferences(); + feedPreferences.setFeedSkipIntro(event.getSkipIntro()); + feedPreferences.setFeedSkipEnding(event.getSkipEnding()); + + } + } + } + } + public static MediaType getCurrentMediaType() { return currentMediaType; } @@ -1613,7 +1692,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { return mediaPlayer.getVideoSize(); } - private void setupPositionUpdater() { + private void setupPositionObserver() { if (positionEventTimer != null) { positionEventTimer.dispose(); } @@ -1629,6 +1708,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { getSystemService(NOTIFICATION_SERVICE); notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); } + skipEndingIfNecessary(); }); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java index ee31c8cc4..8574ff33b 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java @@ -304,6 +304,12 @@ class DBUpgrader { db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS + " ADD COLUMN " + PodDBAdapter.KEY_IMAGE_URL + " TEXT DEFAULT NULL"); } + if (oldVersion < 1090001) { + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + + " ADD COLUMN " + PodDBAdapter.KEY_FEED_SKIP_INTRO + " INTEGER DEFAULT 0;"); + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + + " ADD COLUMN " + PodDBAdapter.KEY_FEED_SKIP_ENDING + " INTEGER DEFAULT 0;"); + } } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java index 6ca3419cf..e6d47b32a 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java @@ -47,7 +47,7 @@ public class PodDBAdapter { private static final String TAG = "PodDBAdapter"; public static final String DATABASE_NAME = "Antennapod.db"; - public static final int VERSION = 1090000; + public static final int VERSION = 1090001; /** * Maximum number of arguments for IN-operator. @@ -109,6 +109,8 @@ public class PodDBAdapter { public static final String KEY_INCLUDE_FILTER = "include_filter"; public static final String KEY_EXCLUDE_FILTER = "exclude_filter"; public static final String KEY_FEED_PLAYBACK_SPEED = "feed_playback_speed"; + public static final String KEY_FEED_SKIP_INTRO = "feed_skip_intro"; + public static final String KEY_FEED_SKIP_ENDING = "feed_skip_ending"; // Table names static final String TABLE_NAME_FEEDS = "Feeds"; @@ -144,7 +146,9 @@ public class PodDBAdapter { + KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0," + KEY_AUTO_DELETE_ACTION + " INTEGER DEFAULT 0," + KEY_FEED_PLAYBACK_SPEED + " REAL DEFAULT " + SPEED_USE_GLOBAL + "," - + KEY_FEED_VOLUME_ADAPTION + " INTEGER DEFAULT 0)"; + + KEY_FEED_VOLUME_ADAPTION + " INTEGER DEFAULT 0," + + KEY_FEED_SKIP_INTRO + " INTEGER DEFAULT 0," + + KEY_FEED_SKIP_ENDING + " INTEGER DEFAULT 0)"; private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE " + TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE @@ -244,7 +248,9 @@ public class PodDBAdapter { TABLE_NAME_FEEDS + "." + KEY_FEED_VOLUME_ADAPTION, TABLE_NAME_FEEDS + "." + KEY_INCLUDE_FILTER, TABLE_NAME_FEEDS + "." + KEY_EXCLUDE_FILTER, - TABLE_NAME_FEEDS + "." + KEY_FEED_PLAYBACK_SPEED + TABLE_NAME_FEEDS + "." + KEY_FEED_PLAYBACK_SPEED, + TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_INTRO, + TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_ENDING }; /** @@ -421,6 +427,8 @@ public class PodDBAdapter { values.put(KEY_INCLUDE_FILTER, prefs.getFilter().getIncludeFilter()); values.put(KEY_EXCLUDE_FILTER, prefs.getFilter().getExcludeFilter()); values.put(KEY_FEED_PLAYBACK_SPEED, prefs.getFeedPlaybackSpeed()); + values.put(KEY_FEED_SKIP_INTRO, prefs.getFeedSkipIntro()); + values.put(KEY_FEED_SKIP_ENDING, prefs.getFeedSkipEnding()); db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(prefs.getFeedID())}); } diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index efd7d698a..40ef88440 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -444,6 +444,12 @@ Playback Speeds Customize the speeds available for variable speed audio playback The speed to use when starting audio playback for episodes in this podcast + Auto Skip + Skip introductions and ending credits. + Skip last + Skip first + Skipped last %d seconds + Skipped first %d seconds Adjust media info to playback speed Displayed position and duration are adapted to playback speed Fast Forward Skip Time