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