Allow playback to be halted after a set idle timeout
This commit is contained in:
parent
6ea8b257c5
commit
1527a56b31
@ -76,6 +76,15 @@
|
||||
<string name="title_by_artist">%s by %s</string>
|
||||
|
||||
<!-- Preferences -->
|
||||
<plurals name="minutes">
|
||||
<item quantity="one">1 minute</item>
|
||||
<item quantity="other">%d minutes</item>
|
||||
</plurals>
|
||||
<plurals name="hours">
|
||||
<item quantity="one">1 hour</item>
|
||||
<item quantity="other">%d hours</item>
|
||||
</plurals>
|
||||
|
||||
<string name="pref_output">Audio Output</string>
|
||||
<string name="volume_title">Volume</string>
|
||||
<string name="volume_summary">Music volume</string>
|
||||
@ -101,6 +110,10 @@
|
||||
<string name="default_action_summary">What to do when an item is tapped</string>
|
||||
|
||||
<string name="pref_misc">Miscellaneous Features</string>
|
||||
<string name="use_idle_timeout_title">Enable Idle Timeout</string>
|
||||
<string name="use_idle_timeout_summary">When active, playback will be stopped after the given period of inactivity</string>
|
||||
<string name="idle_timeout_title">Idle Timeout</string>
|
||||
<string name="idle_timeout_summary">The amount of time that must pass before becoming idle</string>
|
||||
<string name="scrobble_title">Use ScrobbleDroid API</string>
|
||||
<string name="scrobble_summary">Scrobble to Last.FM through ScrobbleDroid or Simple Last.FM Scrobbler</string>
|
||||
</resources>
|
@ -74,6 +74,16 @@
|
||||
android:defaultValue="0" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/pref_misc">
|
||||
<CheckBoxPreference
|
||||
android:key="use_idle_timeout"
|
||||
android:title="@string/use_idle_timeout_title"
|
||||
android:summary="@string/use_idle_timeout_summary"
|
||||
android:defaultValue="false" />
|
||||
<org.kreed.vanilla.IdlePreference
|
||||
android:key="idle_timeout"
|
||||
android:title="@string/idle_timeout_title"
|
||||
android:summary="@string/idle_timeout_summary"
|
||||
android:dependency="use_idle_timeout" />
|
||||
<CheckBoxPreference
|
||||
android:key="scrobble"
|
||||
android:title="@string/scrobble_title"
|
||||
|
131
src/org/kreed/vanilla/IdlePreference.java
Normal file
131
src/org/kreed/vanilla/IdlePreference.java
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Christopher Eby <kreed@kreed.org>
|
||||
*
|
||||
* This file is part of Vanilla Music Player.
|
||||
*
|
||||
* Vanilla Music Player is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Library General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Vanilla Music Player is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kreed.vanilla;
|
||||
|
||||
import android.app.AlertDialog.Builder;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.preference.DialogPreference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* A preference that provides a dialog with a slider for idle time.
|
||||
*
|
||||
* The slider produces a value in seconds from 60 (1 minute) to 21600
|
||||
* (6 hours). The values range on an approximately exponential scale.
|
||||
*/
|
||||
public class IdlePreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener {
|
||||
private static int MIN = 60;
|
||||
private static int MAX = 21600;
|
||||
|
||||
/**
|
||||
* The current idle timeout displayed on the slider. Will not be persisted
|
||||
* until the dialog is closed.
|
||||
*/
|
||||
private int mValue;
|
||||
/**
|
||||
* The view in which the value is displayed.
|
||||
*/
|
||||
private TextView mValueText;
|
||||
|
||||
public IdlePreference(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(Builder builder)
|
||||
{
|
||||
Context context = getContext();
|
||||
ViewGroup.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
||||
mValue = getPersistedInt(3600);
|
||||
|
||||
LinearLayout layout = new LinearLayout(context);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
layout.setLayoutParams(params);
|
||||
|
||||
mValueText = new TextView(context);
|
||||
mValueText.setGravity(Gravity.RIGHT);
|
||||
mValueText.setPadding(20, 0, 20, 0);
|
||||
layout.addView(mValueText);
|
||||
|
||||
SeekBar seekBar = new SeekBar(context);
|
||||
seekBar.setPadding(20, 0, 20, 20);
|
||||
seekBar.setLayoutParams(params);
|
||||
seekBar.setMax(1000);
|
||||
seekBar.setProgress((int)(Math.pow((float)(mValue - MIN) / (MAX - MIN), 0.25f) * 1000));
|
||||
seekBar.setOnSeekBarChangeListener(this);
|
||||
layout.addView(seekBar);
|
||||
|
||||
updateText();
|
||||
|
||||
builder.setView(layout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the text view with the current value.
|
||||
*/
|
||||
private void updateText()
|
||||
{
|
||||
Resources res = getContext().getResources();
|
||||
int value = mValue;
|
||||
String text;
|
||||
if (value >= 3570) {
|
||||
int hours = (int)Math.round(value / 3600f);
|
||||
text = res.getQuantityString(R.plurals.hours, hours, hours);
|
||||
} else {
|
||||
int minutes = (int)Math.round(value / 60f);
|
||||
text = res.getQuantityString(R.plurals.minutes, minutes, minutes);
|
||||
}
|
||||
mValueText.setText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult)
|
||||
{
|
||||
if (positiveResult && shouldPersist())
|
||||
persistInt(mValue);
|
||||
}
|
||||
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
|
||||
{
|
||||
// Approximate an exponential curve with x^4. Produces a value from 60-10860.
|
||||
if (fromUser) {
|
||||
float value = seekBar.getProgress() / 1000.0f;
|
||||
value *= value;
|
||||
value *= value;
|
||||
mValue = (int)(value * (MAX - MIN)) + MIN;
|
||||
updateText();
|
||||
}
|
||||
}
|
||||
|
||||
public void onStartTrackingTouch(SeekBar seekBar)
|
||||
{
|
||||
}
|
||||
|
||||
public void onStopTrackingTouch(SeekBar seekBar)
|
||||
{
|
||||
}
|
||||
}
|
@ -71,6 +71,14 @@ public class PlaybackActivity extends Activity implements Handler.Callback, View
|
||||
startService(new Intent(this, PlaybackService.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
if (ContextApplication.hasService())
|
||||
ContextApplication.getService().userActionTriggered();
|
||||
}
|
||||
|
||||
public static boolean handleKeyLongPress(int keyCode)
|
||||
{
|
||||
switch (keyCode) {
|
||||
|
@ -104,6 +104,10 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
public static final int FLAG_SHUFFLE = 0x4;
|
||||
public static final int FLAG_REPEAT = 0x8;
|
||||
public static final int ALL_FLAGS = FLAG_NO_MEDIA + FLAG_PLAYING + FLAG_SHUFFLE + FLAG_REPEAT;
|
||||
/**
|
||||
* The flags that are (usually) only toggled by user action.
|
||||
*/
|
||||
public static final int USER_MASK = FLAG_PLAYING + FLAG_SHUFFLE + FLAG_REPEAT;
|
||||
|
||||
public static final int NEVER = 0;
|
||||
public static final int WHEN_PLAYING = 1;
|
||||
@ -114,6 +118,10 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
private boolean mScrobble;
|
||||
private int mNotificationMode;
|
||||
private byte mHeadsetControls = -1;
|
||||
/**
|
||||
* The time to wait before considering the player idle.
|
||||
*/
|
||||
private int mIdleTimeout;
|
||||
|
||||
private Looper mLooper;
|
||||
private Handler mHandler;
|
||||
@ -138,6 +146,15 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
private boolean mIgnoreNextUp;
|
||||
private boolean mLoaded;
|
||||
boolean mInCall;
|
||||
/**
|
||||
* The volume set by the user in the preferences.
|
||||
*/
|
||||
private float mUserVolume = 1.0f;
|
||||
/**
|
||||
* The actual volume of the media player. Will differ from the user volume
|
||||
* when fading the volume.
|
||||
*/
|
||||
private float mCurrentVolume = 1.0f;
|
||||
|
||||
private Method mIsWiredHeadsetOn;
|
||||
private Method mStartForeground;
|
||||
@ -325,8 +342,11 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
mNotificationMode = Integer.parseInt(mSettings.getString("notification_mode", "1"));
|
||||
mScrobble = mSettings.getBoolean("scrobble", false);
|
||||
float volume = mSettings.getFloat("volume", 1.0f);
|
||||
if (volume != 1.0f)
|
||||
if (volume != 1.0f) {
|
||||
mCurrentVolume = mUserVolume = volume;
|
||||
mMediaPlayer.setVolume(volume, volume);
|
||||
}
|
||||
mIdleTimeout = mSettings.getBoolean("use_idle_timeout", false) ? mSettings.getInt("idle_timeout", 3600) : 0;
|
||||
|
||||
PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
|
||||
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "VanillaMusicSongChangeLock");
|
||||
@ -359,6 +379,7 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
mScrobble = mSettings.getBoolean("scrobble", false);
|
||||
} else if ("volume".equals(key)) {
|
||||
float volume = mSettings.getFloat("volume", 1.0f);
|
||||
mCurrentVolume = mUserVolume = volume;
|
||||
if (mMediaPlayer != null) {
|
||||
synchronized (mMediaPlayer) {
|
||||
mMediaPlayer.setVolume(volume, volume);
|
||||
@ -367,6 +388,9 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
} else if ("media_button".equals(key)) {
|
||||
mHeadsetControls = (byte)(mSettings.getBoolean("media_button", true) ? 1 : 0);
|
||||
setupReceiver();
|
||||
} else if ("use_idle_timeout".equals(key) || "idle_timeout".equals(key)) {
|
||||
mIdleTimeout = mSettings.getBoolean("use_idle_timeout", false) ? mSettings.getInt("idle_timeout", 3600) : 0;
|
||||
userActionTriggered();
|
||||
}
|
||||
}
|
||||
|
||||
@ -378,21 +402,21 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
ContextApplication.broadcast(intent);
|
||||
}
|
||||
|
||||
boolean setFlag(int flag)
|
||||
void setFlag(int flag)
|
||||
{
|
||||
synchronized (mStateLock) {
|
||||
return updateState(mState | flag);
|
||||
updateState(mState | flag);
|
||||
}
|
||||
}
|
||||
|
||||
boolean unsetFlag(int flag)
|
||||
void unsetFlag(int flag)
|
||||
{
|
||||
synchronized (mStateLock) {
|
||||
return updateState(mState & ~flag);
|
||||
updateState(mState & ~flag);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean updateState(int state)
|
||||
private void updateState(int state)
|
||||
{
|
||||
state &= ALL_FLAGS;
|
||||
|
||||
@ -401,7 +425,7 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
|
||||
Song song = getSong(0);
|
||||
if (song == null && (state & FLAG_PLAYING) != 0)
|
||||
return false;
|
||||
return;
|
||||
|
||||
int oldState = mState;
|
||||
mState = state;
|
||||
@ -454,11 +478,10 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
mMediaPlayer.pause();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
if ((oldState & USER_MASK) != (state & USER_MASK))
|
||||
userActionTriggered();
|
||||
}
|
||||
|
||||
private void updateNotification(Song song)
|
||||
@ -544,6 +567,9 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
Log.e("VanillaMusic", "IOException", e);
|
||||
}
|
||||
|
||||
if (delta != 0)
|
||||
userActionTriggered();
|
||||
|
||||
mHandler.sendEmptyMessage(PROCESS_SONG);
|
||||
}
|
||||
|
||||
@ -686,8 +712,12 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
case TelephonyManager.CALL_STATE_RINGING:
|
||||
case TelephonyManager.CALL_STATE_OFFHOOK:
|
||||
mInCall = true;
|
||||
if (!mPlayingBeforeCall)
|
||||
mPlayingBeforeCall = unsetFlag(FLAG_PLAYING);
|
||||
if (!mPlayingBeforeCall) {
|
||||
synchronized (mStateLock) {
|
||||
if (mPlayingBeforeCall = (mState & FLAG_PLAYING) != 0)
|
||||
unsetFlag(FLAG_PLAYING);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TelephonyManager.CALL_STATE_IDLE:
|
||||
mInCall = false;
|
||||
@ -741,8 +771,21 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
private static final int POST_CREATE = 1;
|
||||
private static final int MEDIA_BUTTON = 2;
|
||||
private static final int CREATE = 3;
|
||||
/**
|
||||
* This message is sent with a delay specified by a user preference. After
|
||||
* this delay, assuming no new IDLE_TIMEOUT messages cancel it, playback
|
||||
* will be stopped.
|
||||
*/
|
||||
private static final int IDLE_TIMEOUT = 4;
|
||||
private static final int TRACK_CHANGED = 5;
|
||||
private static final int RELEASE_WAKE_LOCK = 6;
|
||||
/**
|
||||
* Decrease the volume gradually over five seconds, pausing when 0 is
|
||||
* reached.
|
||||
*
|
||||
* arg1 should be the progress in the fade as a percentage, 1-100.
|
||||
*/
|
||||
private static final int FADE_OUT = 7;
|
||||
private static final int SAVE_STATE = 12;
|
||||
private static final int PROCESS_SONG = 13;
|
||||
|
||||
@ -793,6 +836,31 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
|
||||
telephonyManager.listen(mCallListener, PhoneStateListener.LISTEN_CALL_STATE);
|
||||
break;
|
||||
case IDLE_TIMEOUT:
|
||||
if ((mState & FLAG_PLAYING) != 0)
|
||||
mHandler.sendMessage(mHandler.obtainMessage(FADE_OUT, 100, 0));
|
||||
break;
|
||||
case FADE_OUT:
|
||||
int progress = message.arg1 - 1;
|
||||
if (progress == 0) {
|
||||
unsetFlag(FLAG_PLAYING);
|
||||
mCurrentVolume = mUserVolume;
|
||||
} else {
|
||||
// Fade out on a x^4 curve. This produces a smoother
|
||||
// transition, since we are using raw sound intensities which
|
||||
// are heard by humans with a logarithmic scale. Don't fall
|
||||
// below .01 though: past this, hearing this music becomes
|
||||
// difficult or impossible.
|
||||
mCurrentVolume = Math.max((float)(Math.pow(progress / 100f, 4) * mUserVolume), .01f);
|
||||
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(FADE_OUT, progress, 0), 50);
|
||||
}
|
||||
if (mMediaPlayer != null) {
|
||||
synchronized (mMediaPlayer) {
|
||||
mMediaPlayer.setVolume(mCurrentVolume, mCurrentVolume);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -887,4 +955,24 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
if (shouldAdvance)
|
||||
setCurrentSong(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the idle timeout countdown. Should be called by a user action
|
||||
* has been trigger (new song chosen or playback toggled).
|
||||
*
|
||||
* If an idle fade out is actually in progress, aborts it and resets the
|
||||
* volume.
|
||||
*/
|
||||
public void userActionTriggered()
|
||||
{
|
||||
mHandler.removeMessages(FADE_OUT);
|
||||
mHandler.removeMessages(IDLE_TIMEOUT);
|
||||
if (mIdleTimeout != 0)
|
||||
mHandler.sendEmptyMessageDelayed(IDLE_TIMEOUT, mIdleTimeout * 1000);
|
||||
|
||||
if (mCurrentVolume != mUserVolume) {
|
||||
mCurrentVolume = mUserVolume;
|
||||
mMediaPlayer.setVolume(mCurrentVolume, mCurrentVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user