Skip intro and ending per feed (#3975)

This commit is contained in:
Tony Tam 2020-04-21 14:20:22 -07:00 committed by GitHub
parent 3a86745e81
commit 92eb1d669b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 327 additions and 8 deletions

View File

@ -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);
}

View File

@ -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);

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/labelSkipIntro"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:text="@string/pref_feed_skip_intro" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<EditText
android:id="@+id/etxtSkipIntro"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:autofillHints="@string/pref_feed_skip_intro"
android:cursorVisible="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:inputType="number"
android:maxLength="5"
android:minWidth="150dp"
android:text="30" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="24dp"
android:text="@string/time_seconds" />
</LinearLayout>
<TextView
android:id="@+id/labelSkipEnd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:text="@string/pref_feed_skip_ending" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<EditText
android:id="@+id/etxtSkipEnd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:cursorVisible="true"
android:focusable="true"
android:autofillHints="@string/pref_feed_skip_ending"
android:focusableInTouchMode="true"
android:inputType="number"
android:maxLength="5"
android:minWidth="150dp"
android:text="30" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="24dp"
android:text="@string/time_seconds" />
</LinearLayout>
</LinearLayout>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="feedSettingsScreen">
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="feedSettingsScreen">
<SwitchPreference
android:key="keepUpdated"
@ -21,6 +21,11 @@
android:title="@string/playback_speed"
android:summary="@string/pref_feed_playback_speed_sum"/>
<Preference
android:key="skipping"
android:summary="@string/pref_feed_skip_sum"
android:title="@string/pref_feed_skip" />
<ListPreference
android:entries="@array/spnAutoDeleteItems"
android:entryValues="@array/spnAutoDeleteValues"

View File

@ -0,0 +1,25 @@
package de.danoeh.antennapod.core.event.settings;
public class SkipIntroEndingChangedEvent {
private final int skipIntro;
private final int skipEnding;
private final long feedId;
public SkipIntroEndingChangedEvent(int skipIntro, int skipEnding, long feedId) {
this.skipIntro= skipIntro;
this.skipEnding = skipEnding;
this.feedId = feedId;
}
public int getSkipIntro() {
return skipIntro;
}
public int getSkipEnding() {
return skipEnding;
}
public long getFeedId() {
return feedId;
}
}

View File

@ -34,12 +34,18 @@ public class FeedPreferences {
private String username;
private String password;
private float feedPlaybackSpeed;
private int feedSkipIntro;
private int feedSkipEnding;
public FeedPreferences(long feedID, boolean autoDownload, AutoDeleteAction auto_delete_action, VolumeAdaptionSetting volumeAdaptionSetting, String username, String password) {
this(feedID, autoDownload, true, auto_delete_action, volumeAdaptionSetting, username, password, new FeedFilter(), SPEED_USE_GLOBAL);
}
private FeedPreferences(long feedID, boolean autoDownload, boolean keepUpdated, AutoDeleteAction auto_delete_action, VolumeAdaptionSetting volumeAdaptionSetting, String username, String password, @NonNull FeedFilter filter, float feedPlaybackSpeed) {
this(feedID, autoDownload, true, auto_delete_action, volumeAdaptionSetting, username, password, new FeedFilter(), feedPlaybackSpeed, 0, 0);
}
private FeedPreferences(long feedID, boolean autoDownload, boolean keepUpdated, AutoDeleteAction auto_delete_action, VolumeAdaptionSetting volumeAdaptionSetting, String username, String password, @NonNull FeedFilter filter, float feedPlaybackSpeed, int feedSkipIntro, int feedSkipEnding) {
this.feedID = feedID;
this.autoDownload = autoDownload;
this.keepUpdated = keepUpdated;
@ -49,6 +55,8 @@ public class FeedPreferences {
this.password = password;
this.filter = filter;
this.feedPlaybackSpeed = feedPlaybackSpeed;
this.feedSkipIntro = feedSkipIntro;
this.feedSkipEnding = feedSkipEnding;
}
public static FeedPreferences fromCursor(Cursor cursor) {
@ -62,6 +70,8 @@ public class FeedPreferences {
int indexIncludeFilter = cursor.getColumnIndex(PodDBAdapter.KEY_INCLUDE_FILTER);
int indexExcludeFilter = cursor.getColumnIndex(PodDBAdapter.KEY_EXCLUDE_FILTER);
int indexFeedPlaybackSpeed = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_PLAYBACK_SPEED);
int indexAutoSkipIntro = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_SKIP_INTRO);
int indexAutoSkipEnding = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_SKIP_ENDING);
long feedId = cursor.getLong(indexId);
boolean autoDownload = cursor.getInt(indexAutoDownload) > 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;
}
}

View File

@ -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();
});
}

View File

@ -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;");
}
}
}

View File

@ -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())});
}

View File

@ -444,6 +444,12 @@
<string name="pref_playback_speed_title">Playback Speeds</string>
<string name="pref_playback_speed_sum">Customize the speeds available for variable speed audio playback</string>
<string name="pref_feed_playback_speed_sum">The speed to use when starting audio playback for episodes in this podcast</string>
<string name="pref_feed_skip">Auto Skip</string>
<string name="pref_feed_skip_sum">Skip introductions and ending credits.</string>
<string name="pref_feed_skip_ending">Skip last</string>
<string name="pref_feed_skip_intro">Skip first</string>
<string name="pref_feed_skip_ending_toast">Skipped last %d seconds</string>
<string name="pref_feed_skip_intro_toast">Skipped first %d seconds</string>
<string name="pref_playback_time_respects_speed_title">Adjust media info to playback speed</string>
<string name="pref_playback_time_respects_speed_sum">Displayed position and duration are adapted to playback speed</string>
<string name="pref_fast_forward">Fast Forward Skip Time</string>