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

Implement sleep timer (#157) #1140

Open
wants to merge 2 commits into
base: master
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Vanilla Music player is a [GPLv3](LICENSE) licensed MP3/OGG/FLAC/PCM player for
* accelerometer/shake control
* cover art support
* [Simple Last.fm Scrobbler](https://github.com/tgwizard/sls) support
* Sleep timer

[<img src="https://f-droid.org/badge/get-it-on.png"
alt="Get it on F-Droid"
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ THE SOFTWARE.
</intent-filter>
</activity>

<activity
android:name="SleepTimerActivity"/>



<meta-data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,11 +396,13 @@ public void onTimelineChanged()
static final int MENU_MORE_GENRE = 24;
static final int MENU_MORE_FOLDER = 25;
static final int MENU_JUMP_TO_TIME = 26;
static final int MENU_SLEEP_TIMER = 27;

@Override
public boolean onCreateOptionsMenu(Menu menu)
{
menu.add(0, MENU_PREFS, 10, R.string.settings).setIcon(R.drawable.ic_menu_preferences);
menu.add(0, MENU_SLEEP_TIMER, 90, R.string.sleep_timer);
return true;
}

Expand All @@ -417,6 +419,10 @@ public boolean onOptionsItemSelected(MenuItem item)
case MENU_EMPTY_QUEUE:
PlaybackService.get(this).emptyQueue();
break;
case MENU_SLEEP_TIMER:
PlaybackService.get(this); // force creation with `this` context, if not created yet
startActivity(new Intent(this, SleepTimerActivity.class));
break;
default:
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,11 @@ public final class PlaybackService extends Service
* The time to wait before considering the player idle.
*/
private int mIdleTimeout;
/**
* Time left on the sleep timer, in milliseconds, or -1 if not running
*/
private int mSleepTimer = -1;
private long mSleepStart = -1;
/**
* The intent for the notification to execute, created by
* {@link PlaybackService#createNotificationAction(SharedPreferences)}.
Expand Down Expand Up @@ -807,9 +812,17 @@ private void triggerGaplessUpdate() {
int fa = finishAction(mState);
Song nextSong = getSong(1);

// check if would sleep before next song
boolean sleep_before_end = false;
if(mSleepStart != -1) {
int time_left = mMediaPlayer.getDuration() - mMediaPlayer.getCurrentPosition();
sleep_before_end = getSleepTimerMil() < time_left;
}

if( nextSong != null
&& fa != SongTimeline.FINISH_REPEAT_CURRENT
&& fa != SongTimeline.FINISH_STOP_CURRENT
&& !sleep_before_end
&& !mTimeline.isEndOfQueue() ) {
doGapless = true;
}
Expand Down Expand Up @@ -1424,6 +1437,47 @@ private void processSong(Song song)

}

public void setSleepTimer(int timeout)
{
if(timeout < 0) {
cancelSleepTimer();
return;
}
// use milliseconds here
mSleepStart = SystemClock.elapsedRealtime();
mSleepTimer = timeout * 1000;

mHandler.sendEmptyMessage(MSG_GAPLESS_UPDATE);
}

public int getSleepTimer()
{
if(mSleepTimer == -1)
return -1;
return getSleepTimerMil()/1000; // in seconds
}

public int getSleepTimerMil()
{
if(mSleepTimer == -1)
return -1;
int time_elapsed = (int)(SystemClock.elapsedRealtime() - mSleepStart);
int time_left = mSleepTimer - time_elapsed;
if(time_left < 0)
time_left = 0;

return time_left;
}

public void cancelSleepTimer()
{
boolean retrigger_gapless = getSleepTimer() != 0;
mSleepTimer = -1;
mSleepStart = -1;
if(retrigger_gapless)
mHandler.sendEmptyMessage(MSG_GAPLESS_UPDATE);
}

@Override
public void onCompletion(MediaPlayer player)
{
Expand All @@ -1436,6 +1490,10 @@ public void onCompletion(MediaPlayer player)
} else if (finishAction(mState) == SongTimeline.FINISH_STOP_CURRENT) {
unsetFlag(FLAG_PLAYING);
setCurrentSong(+1);
} else if (getSleepTimer() == 0) {
unsetFlag(FLAG_PLAYING);
setCurrentSong(+1);
cancelSleepTimer();
} else {
if (mTimeline.isEndOfQueue()) {
unsetFlag(FLAG_PLAYING);
Expand Down Expand Up @@ -1547,6 +1605,7 @@ public void onSharedPreferenceChanged(SharedPreferences settings, String key)
* The current song's playback position changed.
*/
private static final int MSG_BROADCAST_SEEK = 19;
private static final int MSG_SLEEP_RESET = 20;

@Override
public boolean handleMessage(Message message)
Expand Down Expand Up @@ -1639,6 +1698,9 @@ public boolean handleMessage(Message message)
mRemoteControlClient.updateRemote(mCurrentSong, mState, mForceNotificationVisible);
mMediaSessionTracker.updateSession(mCurrentSong, mState);
break;
case MSG_SLEEP_RESET:
cancelSleepTimer();
break;
default:
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package ch.blinkenlights.android.vanilla;

import android.app.ActionBar;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.NumberPicker;
import android.widget.TextView;
import android.widget.TimePicker;

import androidx.annotation.NonNull;

import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;

/**
* Shows the sleep timer
*/
public class SleepTimerActivity extends Activity
implements View.OnClickListener
{

private LinearLayout mPickerLayout;
private NumberPicker mNumberHours;
private NumberPicker mNumberMinutes;
private TextView mCountdown;
private Button mButtonOk;
private boolean toEnable;
private Timer timer;

/**
* Initialize the activity
*/
@Override
protected void onCreate(Bundle savedInstanceState)
{
ThemeHelper.setTheme(this, R.style.BackActionBar);
super.onCreate(savedInstanceState);

setContentView(R.layout.sleep_timer);

mNumberHours = (NumberPicker) findViewById(R.id.sleep_timer_hours);
mNumberHours.setMaxValue(23);
mNumberHours.setMinValue(0);

mNumberMinutes = (NumberPicker) findViewById(R.id.sleep_timer_minutes);
mNumberMinutes.setMaxValue(59);
mNumberMinutes.setMinValue(0);

mCountdown = (TextView) findViewById(R.id.sleep_timer_countdown);
mPickerLayout = (LinearLayout) findViewById(R.id.sleep_timer_timer);

mButtonOk = (Button) findViewById(R.id.sleep_timer_button_ok);
mButtonOk.setOnClickListener(this);

doGUI();
}

@Override
protected void onPause() {
super.onPause();
if(timer != null) {
timer.cancel();
timer.purge();
}
}

@Override
protected void onDestroy() {
super.onDestroy();
if(timer != null) {
timer.cancel();
timer.purge();
}
}

@Override
protected void onResume() {
super.onResume();
doGUI();
}

private void doGUI()
{
int current_timer = PlaybackService.get(this).getSleepTimer();
if(current_timer < 0)
setToEnable();
else
setToDisable(current_timer);
}

private void setToEnable()
{
mButtonOk.setText(R.string.sleep_timer_start);
mPickerLayout.setVisibility(View.VISIBLE);
mCountdown.setVisibility(View.GONE);

mNumberHours.setValue(1);
mNumberMinutes.setValue(0);

timer = null; // force delete

toEnable = true;
}

private void setToDisable(int current)
{
mButtonOk.setText(R.string.sleep_timer_stop);
mPickerLayout.setVisibility(View.GONE);
mCountdown.setVisibility(View.VISIBLE);
mCountdown.setText(timeForTimeout(current));

timer = new Timer();
timer.scheduleAtFixedRate(new CountdownUpdater(current, mCountdown), 0, 1000);

toEnable = false;
}

@Override
public void onClick(View view)
{
// only one clickable view
int timeout = -1;
if(toEnable)
timeout = mNumberHours.getValue() * 3600 + mNumberMinutes.getValue() * 60;

PlaybackService.get(this).setSleepTimer(timeout);
doGUI();
}

@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if(item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}

private String timeForTimeout(int seconds)
{
int hours = seconds / 3600;
int minutes = (seconds%3600) / 60;
seconds = seconds % 60;

return String.format(Locale.US, "%02d:%02d:%02d", hours, minutes, seconds);
}

private class CountdownUpdater extends TimerTask {

private int timeout;
final private TextView textView;

public CountdownUpdater(int timeout, TextView textView)
{
this.timeout = timeout;
this.textView = textView;
}

@Override
public void run()
{
runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText(timeForTimeout(timeout));
}
});
timeout -= 1;
if(timeout == 0) {
timer.cancel();
timer.purge();
}
}
}
}
53 changes: 53 additions & 0 deletions app/src/main/res/layout/sleep_timer.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/sleep_timer_timer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">

<NumberPicker
android:id="@+id/sleep_timer_hours"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sleep_timer_hours" />

<NumberPicker
android:id="@+id/sleep_timer_minutes"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sleep_timer_minutes" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp"
android:layout_margin="24dp"
android:visibility="gone"
android:id="@+id/sleep_timer_countdown"/>
</LinearLayout>
<Button
android:id="@+id/sleep_timer_button_ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>
5 changes: 5 additions & 0 deletions app/src/main/res/values/translatable.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ THE SOFTWARE.
<string name="no_songs">No tracks found on your device.</string>
<string name="empty_queue">No tracks selected. Pick some from the library or enter random mode by tapping this message.</string>
<string name="settings">Settings</string>
<string name="sleep_timer">Sleep Timer</string>
<string name="sleep_timer_start">Start</string>
<string name="sleep_timer_stop">Stop</string>
<string name="sleep_timer_hours">Hours</string>
<string name="sleep_timer_minutes">Minutes</string>
<string name="no_shuffle">No shuffle</string>
<string name="shuffle_songs">Shuffle tracks</string>
<string name="shuffle_songs_continuously">Shuffle tracks continuously</string>
Expand Down