Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add episodes to sleep timer #4754

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -21,6 +21,7 @@
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
import io.reactivex.Observable;
Expand Down Expand Up @@ -95,10 +96,11 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
imm.showSoftInput(etxtTime, InputMethodManager.SHOW_IMPLICIT);
}, 100);

String[] spinnerContent = new String[] {
String[] spinnerContent = new String[]{
getString(R.string.time_seconds),
getString(R.string.time_minutes),
getString(R.string.time_hours) };
getString(R.string.time_hours),
getString(R.string.time_episodes)};
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(getContext(),
android.R.layout.simple_spinner_item, spinnerContent);
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Expand Down Expand Up @@ -133,10 +135,23 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
return;
}
try {
if (spTimeUnit.getSelectedItemPosition() >= 3) {
int timeValue = Integer.parseInt(etxtTime.getText().toString());
if (timeValue > DBReader.getQueueIDList().size()) {
Snackbar.make(content,
R.string.time_dialog_invalid_episodes_input, Snackbar.LENGTH_LONG).show();
return;
}
}

SleepTimerPreferences.setLastTimer(etxtTime.getText().toString(), spTimeUnit.getSelectedItemPosition());
long time = SleepTimerPreferences.timerMillis();
if (controller != null) {
controller.setSleepTimer(time);
if (SleepTimerPreferences.isEpisodesEnabled()) {
controller.setSleepTimerEpisodes((int) time);
} else {
controller.setSleepTimer(time);
}
}
closeKeyboard(content);
} catch (NumberFormatException e) {
Expand All @@ -153,7 +168,11 @@ private void updateTime() {
}
timeSetup.setVisibility(controller.sleepTimerActive() ? View.GONE : View.VISIBLE);
timeDisplay.setVisibility(controller.sleepTimerActive() ? View.VISIBLE : View.GONE);
time.setText(Converter.getDurationStringLong((int) controller.getSleepTimerTimeLeft()));
if (SleepTimerPreferences.isEpisodesEnabled()) {
time.setText(controller.getSleepTimerTimeLeft() + " " + getString(R.string.time_episodes));
} else {
time.setText(Converter.getDurationStringLong((int) controller.getSleepTimerTimeLeft()));
}
}

private void closeKeyboard(View content) {
Expand Down
Expand Up @@ -18,7 +18,7 @@ public class SleepTimerPreferences {
private static final String PREF_SHAKE_TO_RESET = "ShakeToReset";
private static final String PREF_AUTO_ENABLE = "AutoEnable";

private static final TimeUnit[] UNITS = { TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS };
private static final TimeUnit[] UNITS = {TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.MILLISECONDS};

private static final String DEFAULT_VALUE = "15";
private static final int DEFAULT_TIME_UNIT = 1;
Expand Down Expand Up @@ -52,6 +52,10 @@ public static long timerMillis() {
return UNITS[lastTimerTimeUnit()].toMillis(value);
}

public static boolean isEpisodesEnabled() {
return lastTimerTimeUnit() == 3;
}

public static void setVibrate(boolean vibrate) {
prefs.edit().putBoolean(PREF_VIBRATE, vibrate).apply();
}
Expand Down
Expand Up @@ -860,7 +860,11 @@ public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) {
// set sleep timer if auto-enabled
if (newInfo.oldPlayerStatus != null && newInfo.oldPlayerStatus != PlayerStatus.SEEKING
&& SleepTimerPreferences.autoEnable() && !sleepTimerActive()) {
setSleepTimer(SleepTimerPreferences.timerMillis());
if (SleepTimerPreferences.isEpisodesEnabled()) {
setSleepTimerEpisodes((int) SleepTimerPreferences.timerMillis());
} else {
setSleepTimer(SleepTimerPreferences.timerMillis());
}
EventBus.getDefault().post(new MessageEvent(getString(R.string.sleep_timer_enabled_label),
PlaybackService.this::disableSleepTimer));
}
Expand Down Expand Up @@ -982,6 +986,7 @@ public Playable getNextInQueue(Playable currentMedia) {

@Override
public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
sleepTimerPlaybackEnded();
PlaybackService.this.onPlaybackEnded(mediaType, stopPlaying);
}
};
Expand Down Expand Up @@ -1139,6 +1144,12 @@ public void setSleepTimer(long waitingTime) {
sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0);
}

public void setSleepTimerEpisodes(int waitingEpisodes) {
Log.d(TAG, "Setting sleep timer to " + waitingEpisodes + " episodes");
taskManager.setSleepTimerEpisodes(waitingEpisodes);
sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0);
}

public void disableSleepTimer() {
taskManager.disableSleepTimer();
sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0);
Expand Down Expand Up @@ -1441,6 +1452,10 @@ public long getSleepTimerTimeLeft() {
return taskManager.getSleepTimerTimeLeft();
}

public void sleepTimerPlaybackEnded() {
taskManager.playbackEnded();
}

private void bluetoothNotifyChange(PlaybackServiceMediaPlayer.PSMPInfo info, String whatChanged) {
boolean isPlaying = false;

Expand Down
Expand Up @@ -230,6 +230,19 @@ public synchronized void setSleepTimer(long waitingTime) {
sleepTimerFuture = schedExecutor.schedule(sleepTimer, 0, TimeUnit.MILLISECONDS);
}

public synchronized void setSleepTimerEpisodes(int waitingEpisodes) {
if (waitingEpisodes <= 0) {
throw new IllegalArgumentException("Waiting time <= 0");
}

Log.d(TAG, "Setting sleep timer to " + waitingEpisodes + " episodes");
if (isSleepTimerActive()) {
sleepTimerFuture.cancel(true);
}
sleepTimer = new SleepTimer(waitingEpisodes);
sleepTimerFuture = schedExecutor.schedule(sleepTimer, 0, TimeUnit.MILLISECONDS);
}

/**
* Returns true if the sleep timer is currently active.
*/
Expand Down Expand Up @@ -272,6 +285,12 @@ public synchronized long getSleepTimerTimeLeft() {
}
}

public synchronized void playbackEnded() {
if (isSleepTimerActive()) {
sleepTimer.playbackEnded();
}
}


/**
* Returns true if the widget updater is currently running.
Expand Down Expand Up @@ -358,15 +377,36 @@ class SleepTimer implements Runnable {
private static final long UPDATE_INTERVAL = 1000L;
public static final long NOTIFICATION_THRESHOLD = 10000;
private boolean hasVibrated = false;
private final long waitingTime;
private long timeLeft;
private ShakeListener shakeListener;
private final Handler handler;

private final boolean episodes;
private final long waitingTime;
private long timeLeft;
private final int waitingEpisodes;
private int episodesLeft;

public SleepTimer(long waitingTime) {
super();
this.waitingTime = waitingTime;
this.timeLeft = waitingTime;
this.waitingEpisodes = 0;
this.episodes = false;

if (UserPreferences.useExoplayer() && Looper.myLooper() == Looper.getMainLooper()) {
// Run callbacks in main thread so they can call ExoPlayer methods themselves
this.handler = new Handler(Looper.getMainLooper());
} else {
this.handler = null;
}
}

public SleepTimer(int waitingEpisodes) {
super();
this.waitingTime = 0;
this.waitingEpisodes = waitingEpisodes;
this.episodesLeft = waitingEpisodes;
this.episodes = true;

if (UserPreferences.useExoplayer() && Looper.myLooper() == Looper.getMainLooper()) {
// Run callbacks in main thread so they can call ExoPlayer methods themselves
Expand All @@ -386,6 +426,59 @@ private void postCallback(Runnable r) {

@Override
public void run() {
if (episodes) {
checkEpisodes();
} else {
checkTime();
}
}

public void playbackEnded() {
episodesLeft--;
if (episodesLeft <= 0) {
Log.d(TAG, "Sleep timer expired");
if (shakeListener != null) {
shakeListener.pause();
shakeListener = null;
}
hasVibrated = false;
if (!Thread.currentThread().isInterrupted()) {
postCallback(callback::onSleepTimerExpired);
} else {
Log.d(TAG, "Sleep timer interrupted");
}
}
}

private void checkEpisodes() {
Log.d(TAG, "Starting");
while (episodesLeft > 0) {
try {
Thread.sleep(UPDATE_INTERVAL);
} catch (InterruptedException e) {
Log.d(TAG, "Thread was interrupted while waiting");
e.printStackTrace();
break;
}

if (episodesLeft <= 1) {
Log.d(TAG, "Sleep timer is about to expire");
//TODO: Implement vibrate shortly before end
if (SleepTimerPreferences.vibrate() && !hasVibrated) {
Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
if (v != null) {
v.vibrate(500);
hasVibrated = true;
}
}
if (shakeListener == null && SleepTimerPreferences.shakeToReset()) {
shakeListener = new ShakeListener(context, this);
}
}
}
}

private void checkTime() {
Log.d(TAG, "Starting");
long lastTick = System.currentTimeMillis();
while (timeLeft > 0) {
Expand Down Expand Up @@ -432,12 +525,16 @@ public void run() {
}

public long getWaitingTime() {
return timeLeft;
return episodes ? episodesLeft : timeLeft;
}

public void restart() {
postCallback(() -> {
setSleepTimer(waitingTime);
if (episodes) {
setSleepTimerEpisodes(waitingEpisodes);
} else {
setSleepTimer(waitingTime);
}
callback.onSleepTimerReset();
});
if (shakeListener != null) {
Expand Down
Expand Up @@ -570,6 +570,12 @@ public void setSleepTimer(long time) {
}
}

public void setSleepTimerEpisodes(int time) {
if (playbackService != null) {
playbackService.setSleepTimerEpisodes(time);
}
}

public void seekToChapter(Chapter chapter) {
if (playbackService != null) {
playbackService.seekToChapter(chapter);
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/res/values/strings.xml
Expand Up @@ -604,11 +604,13 @@
<string name="disable_sleeptimer_label">Disable sleep timer</string>
<string name="sleep_timer_label">Sleep timer</string>
<string name="time_dialog_invalid_input">Invalid input, time has to be an integer</string>
<string name="time_dialog_invalid_episodes_input">Invalid input, not enough episodes in queue</string>
<string name="shake_to_reset_label">Shake to reset</string>
<string name="timer_vibration_label">Vibrate shortly before end</string>
<string name="time_seconds">seconds</string>
<string name="time_minutes">minutes</string>
<string name="time_hours">hours</string>
<string name="time_episodes">episodes</string>
<plurals name="time_seconds_quantified">
<item quantity="one">1 second</item>
<item quantity="other">%d seconds</item>
Expand Down