* commit 'bb78ae9eedce1136410be326274b5304e8e046dc': Fix scrubbing behavior on keyguard music transport
This commit is contained in:
@@ -60,12 +60,12 @@ import java.util.TimeZone;
|
|||||||
*/
|
*/
|
||||||
public class KeyguardTransportControlView extends FrameLayout {
|
public class KeyguardTransportControlView extends FrameLayout {
|
||||||
|
|
||||||
private static final int DISPLAY_TIMEOUT_MS = 5000; // 5s
|
|
||||||
private static final int RESET_TO_METADATA_DELAY = 5000;
|
private static final int RESET_TO_METADATA_DELAY = 5000;
|
||||||
protected static final boolean DEBUG = false;
|
protected static final boolean DEBUG = false;
|
||||||
protected static final String TAG = "TransportControlView";
|
protected static final String TAG = "TransportControlView";
|
||||||
|
|
||||||
private static final boolean ANIMATE_TRANSITIONS = true;
|
private static final boolean ANIMATE_TRANSITIONS = true;
|
||||||
|
protected static final long QUIESCENT_PLAYBACK_FACTOR = 1000;
|
||||||
|
|
||||||
private ViewGroup mMetadataContainer;
|
private ViewGroup mMetadataContainer;
|
||||||
private ViewGroup mInfoContainer;
|
private ViewGroup mInfoContainer;
|
||||||
@@ -89,11 +89,9 @@ public class KeyguardTransportControlView extends FrameLayout {
|
|||||||
private ImageView mBadge;
|
private ImageView mBadge;
|
||||||
|
|
||||||
private boolean mSeekEnabled;
|
private boolean mSeekEnabled;
|
||||||
private boolean mUserSeeking;
|
|
||||||
private java.text.DateFormat mFormat;
|
private java.text.DateFormat mFormat;
|
||||||
|
|
||||||
private Date mTimeElapsed;
|
private Date mTempDate = new Date();
|
||||||
private Date mTrackDuration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The metadata which should be populated into the view once we've been attached
|
* The metadata which should be populated into the view once we've been attached
|
||||||
@@ -111,18 +109,25 @@ public class KeyguardTransportControlView extends FrameLayout {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClientPlaybackStateUpdate(int state) {
|
public void onClientPlaybackStateUpdate(int state) {
|
||||||
setSeekBarsEnabled(false);
|
|
||||||
updatePlayPauseState(state);
|
updatePlayPauseState(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
|
public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs,
|
||||||
long currentPosMs, float speed) {
|
long currentPosMs, float speed) {
|
||||||
setSeekBarsEnabled(mMetadata != null && mMetadata.duration > 0);
|
|
||||||
updatePlayPauseState(state);
|
updatePlayPauseState(state);
|
||||||
if (DEBUG) Log.d(TAG, "onClientPlaybackStateUpdate(state=" + state +
|
if (DEBUG) Log.d(TAG, "onClientPlaybackStateUpdate(state=" + state +
|
||||||
", stateChangeTimeMs=" + stateChangeTimeMs + ", currentPosMs=" + currentPosMs +
|
", stateChangeTimeMs=" + stateChangeTimeMs + ", currentPosMs=" + currentPosMs +
|
||||||
", speed=" + speed + ")");
|
", speed=" + speed + ")");
|
||||||
|
|
||||||
|
removeCallbacks(mUpdateSeekBars);
|
||||||
|
// Since the music client may be responding to historical events that cause the
|
||||||
|
// playback state to change dramatically, wait until things become quiescent before
|
||||||
|
// resuming automatic scrub position update.
|
||||||
|
if (mTransientSeek.getVisibility() == View.VISIBLE
|
||||||
|
&& playbackPositionShouldMove(mCurrentPlayState)) {
|
||||||
|
postDelayed(mUpdateSeekBars, QUIESCENT_PLAYBACK_FACTOR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -136,15 +141,21 @@ public class KeyguardTransportControlView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final Runnable mUpdateSeekBars = new Runnable() {
|
private class UpdateSeekBarRunnable implements Runnable {
|
||||||
public void run() {
|
public void run() {
|
||||||
if (updateSeekBars()) {
|
boolean seekAble = updateOnce();
|
||||||
|
if (seekAble) {
|
||||||
removeCallbacks(this);
|
removeCallbacks(this);
|
||||||
postDelayed(this, 1000);
|
postDelayed(this, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public boolean updateOnce() {
|
||||||
|
return updateSeekBars();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private final UpdateSeekBarRunnable mUpdateSeekBars = new UpdateSeekBarRunnable();
|
||||||
|
|
||||||
private final Runnable mResetToMetadata = new Runnable() {
|
private final Runnable mResetToMetadata = new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
resetToMetadata();
|
resetToMetadata();
|
||||||
@@ -163,6 +174,7 @@ public class KeyguardTransportControlView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
if (keyCode != -1) {
|
if (keyCode != -1) {
|
||||||
sendMediaButtonClick(keyCode);
|
sendMediaButtonClick(keyCode);
|
||||||
|
delayResetToMetadata(); // if the scrub bar is showing, keep showing it.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -177,25 +189,67 @@ public class KeyguardTransportControlView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This class is here to throttle scrub position updates to the music client
|
||||||
|
class FutureSeekRunnable implements Runnable {
|
||||||
|
private int mProgress;
|
||||||
|
private boolean mPending;
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
scrubTo(mProgress);
|
||||||
|
mPending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setProgress(int progress) {
|
||||||
|
mProgress = progress;
|
||||||
|
if (!mPending) {
|
||||||
|
mPending = true;
|
||||||
|
postDelayed(this, 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is here because RemoteControlClient's method isn't visible :/
|
||||||
|
private final static boolean playbackPositionShouldMove(int playstate) {
|
||||||
|
switch(playstate) {
|
||||||
|
case RemoteControlClient.PLAYSTATE_STOPPED:
|
||||||
|
case RemoteControlClient.PLAYSTATE_PAUSED:
|
||||||
|
case RemoteControlClient.PLAYSTATE_BUFFERING:
|
||||||
|
case RemoteControlClient.PLAYSTATE_ERROR:
|
||||||
|
case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
|
||||||
|
case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
|
||||||
|
return false;
|
||||||
|
case RemoteControlClient.PLAYSTATE_PLAYING:
|
||||||
|
case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
|
||||||
|
case RemoteControlClient.PLAYSTATE_REWINDING:
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final FutureSeekRunnable mFutureSeekRunnable = new FutureSeekRunnable();
|
||||||
|
|
||||||
private final SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener =
|
private final SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener =
|
||||||
new SeekBar.OnSeekBarChangeListener() {
|
new SeekBar.OnSeekBarChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||||
if (fromUser) {
|
if (fromUser) {
|
||||||
scrubTo(progress);
|
mFutureSeekRunnable.setProgress(progress);
|
||||||
delayResetToMetadata();
|
delayResetToMetadata();
|
||||||
|
mTempDate.setTime(progress);
|
||||||
|
mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate));
|
||||||
|
} else {
|
||||||
|
updateSeekDisplay();
|
||||||
}
|
}
|
||||||
updateSeekDisplay();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
mUserSeeking = true;
|
delayResetToMetadata();
|
||||||
|
removeCallbacks(mUpdateSeekBars); // don't update during user interaction
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
mUserSeeking = false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -247,17 +301,11 @@ public class KeyguardTransportControlView extends FrameLayout {
|
|||||||
if (enabled == mSeekEnabled) return;
|
if (enabled == mSeekEnabled) return;
|
||||||
|
|
||||||
mSeekEnabled = enabled;
|
mSeekEnabled = enabled;
|
||||||
if (mTransientSeek.getVisibility() == VISIBLE) {
|
if (mTransientSeek.getVisibility() == VISIBLE && !enabled) {
|
||||||
mTransientSeek.setVisibility(INVISIBLE);
|
mTransientSeek.setVisibility(INVISIBLE);
|
||||||
mMetadataContainer.setVisibility(VISIBLE);
|
mMetadataContainer.setVisibility(VISIBLE);
|
||||||
mUserSeeking = false;
|
|
||||||
cancelResetToMetadata();
|
cancelResetToMetadata();
|
||||||
}
|
}
|
||||||
if (enabled) {
|
|
||||||
mUpdateSeekBars.run();
|
|
||||||
} else {
|
|
||||||
removeCallbacks(mUpdateSeekBars);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTransportControlCallback(KeyguardHostView.TransportControlCallback
|
public void setTransportControlCallback(KeyguardHostView.TransportControlCallback
|
||||||
@@ -294,6 +342,8 @@ public class KeyguardTransportControlView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
|
final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn();
|
||||||
setEnableMarquee(screenOn);
|
setEnableMarquee(screenOn);
|
||||||
|
// Allow long-press anywhere else in this view to show the seek bar
|
||||||
|
setOnLongClickListener(mTransportShowSeekBarListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -326,7 +376,6 @@ public class KeyguardTransportControlView extends FrameLayout {
|
|||||||
mAudioManager.unregisterRemoteController(mRemoteController);
|
mAudioManager.unregisterRemoteController(mRemoteController);
|
||||||
KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitor);
|
KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitor);
|
||||||
mMetadata.clear();
|
mMetadata.clear();
|
||||||
mUserSeeking = false;
|
|
||||||
removeCallbacks(mUpdateSeekBars);
|
removeCallbacks(mUpdateSeekBars);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,18 +533,12 @@ public class KeyguardTransportControlView extends FrameLayout {
|
|||||||
|
|
||||||
void updateSeekDisplay() {
|
void updateSeekDisplay() {
|
||||||
if (mMetadata != null && mRemoteController != null && mFormat != null) {
|
if (mMetadata != null && mRemoteController != null && mFormat != null) {
|
||||||
if (mTimeElapsed == null) {
|
mTempDate.setTime(mRemoteController.getEstimatedMediaPosition());
|
||||||
mTimeElapsed = new Date();
|
mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate));
|
||||||
}
|
mTempDate.setTime(mMetadata.duration);
|
||||||
if (mTrackDuration == null) {
|
mTransientSeekTimeTotal.setText(mFormat.format(mTempDate));
|
||||||
mTrackDuration = new Date();
|
|
||||||
}
|
|
||||||
mTimeElapsed.setTime(mRemoteController.getEstimatedMediaPosition());
|
|
||||||
mTrackDuration.setTime(mMetadata.duration);
|
|
||||||
mTransientSeekTimeElapsed.setText(mFormat.format(mTimeElapsed));
|
|
||||||
mTransientSeekTimeTotal.setText(mFormat.format(mTrackDuration));
|
|
||||||
|
|
||||||
if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + mTimeElapsed +
|
if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + mTempDate +
|
||||||
" duration=" + mMetadata.duration);
|
" duration=" + mMetadata.duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -508,10 +551,16 @@ public class KeyguardTransportControlView extends FrameLayout {
|
|||||||
mTransientSeek.setVisibility(INVISIBLE);
|
mTransientSeek.setVisibility(INVISIBLE);
|
||||||
mMetadataContainer.setVisibility(VISIBLE);
|
mMetadataContainer.setVisibility(VISIBLE);
|
||||||
cancelResetToMetadata();
|
cancelResetToMetadata();
|
||||||
|
removeCallbacks(mUpdateSeekBars); // don't update if scrubber isn't visible
|
||||||
} else {
|
} else {
|
||||||
mTransientSeek.setVisibility(VISIBLE);
|
mTransientSeek.setVisibility(VISIBLE);
|
||||||
mMetadataContainer.setVisibility(INVISIBLE);
|
mMetadataContainer.setVisibility(INVISIBLE);
|
||||||
delayResetToMetadata();
|
delayResetToMetadata();
|
||||||
|
if (playbackPositionShouldMove(mCurrentPlayState)) {
|
||||||
|
mUpdateSeekBars.run();
|
||||||
|
} else {
|
||||||
|
mUpdateSeekBars.updateOnce();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mTransportControlCallback.userActivity();
|
mTransportControlCallback.userActivity();
|
||||||
return true;
|
return true;
|
||||||
@@ -573,9 +622,6 @@ public class KeyguardTransportControlView extends FrameLayout {
|
|||||||
case RemoteControlClient.PLAYSTATE_PLAYING:
|
case RemoteControlClient.PLAYSTATE_PLAYING:
|
||||||
imageResId = R.drawable.ic_media_pause;
|
imageResId = R.drawable.ic_media_pause;
|
||||||
imageDescId = R.string.keyguard_transport_pause_description;
|
imageDescId = R.string.keyguard_transport_pause_description;
|
||||||
if (mSeekEnabled) {
|
|
||||||
mUpdateSeekBars.run();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RemoteControlClient.PLAYSTATE_BUFFERING:
|
case RemoteControlClient.PLAYSTATE_BUFFERING:
|
||||||
@@ -590,10 +636,9 @@ public class KeyguardTransportControlView extends FrameLayout {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state != RemoteControlClient.PLAYSTATE_PLAYING) {
|
boolean clientSupportsSeek = mMetadata != null && mMetadata.duration > 0;
|
||||||
removeCallbacks(mUpdateSeekBars);
|
setSeekBarsEnabled(clientSupportsSeek);
|
||||||
updateSeekBars();
|
|
||||||
}
|
|
||||||
mBtnPlay.setImageResource(imageResId);
|
mBtnPlay.setImageResource(imageResId);
|
||||||
mBtnPlay.setContentDescription(getResources().getString(imageDescId));
|
mBtnPlay.setContentDescription(getResources().getString(imageDescId));
|
||||||
mCurrentPlayState = state;
|
mCurrentPlayState = state;
|
||||||
@@ -601,11 +646,9 @@ public class KeyguardTransportControlView extends FrameLayout {
|
|||||||
|
|
||||||
boolean updateSeekBars() {
|
boolean updateSeekBars() {
|
||||||
final int position = (int) mRemoteController.getEstimatedMediaPosition();
|
final int position = (int) mRemoteController.getEstimatedMediaPosition();
|
||||||
|
if (DEBUG) Log.v(TAG, "Estimated time:" + position);
|
||||||
if (position >= 0) {
|
if (position >= 0) {
|
||||||
if (DEBUG) Log.v(TAG, "Seek to " + position);
|
mTransientSeekBar.setProgress(position);
|
||||||
if (!mUserSeeking) {
|
|
||||||
mTransientSeekBar.setProgress(position);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Log.w(TAG, "Updating seek bars; received invalid estimated media position (" +
|
Log.w(TAG, "Updating seek bars; received invalid estimated media position (" +
|
||||||
@@ -671,34 +714,4 @@ public class KeyguardTransportControlView extends FrameLayout {
|
|||||||
public boolean providesClock() {
|
public boolean providesClock() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean wasPlayingRecently(int state, long stateChangeTimeMs) {
|
|
||||||
switch (state) {
|
|
||||||
case RemoteControlClient.PLAYSTATE_PLAYING:
|
|
||||||
case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
|
|
||||||
case RemoteControlClient.PLAYSTATE_REWINDING:
|
|
||||||
case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
|
|
||||||
case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
|
|
||||||
case RemoteControlClient.PLAYSTATE_BUFFERING:
|
|
||||||
// actively playing or about to play
|
|
||||||
return true;
|
|
||||||
case RemoteControlClient.PLAYSTATE_NONE:
|
|
||||||
return false;
|
|
||||||
case RemoteControlClient.PLAYSTATE_STOPPED:
|
|
||||||
case RemoteControlClient.PLAYSTATE_PAUSED:
|
|
||||||
case RemoteControlClient.PLAYSTATE_ERROR:
|
|
||||||
// we have stopped playing, check how long ago
|
|
||||||
if (DEBUG) {
|
|
||||||
if ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS) {
|
|
||||||
Log.v(TAG, "wasPlayingRecently: time < TIMEOUT was playing recently");
|
|
||||||
} else {
|
|
||||||
Log.v(TAG, "wasPlayingRecently: time > TIMEOUT");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS);
|
|
||||||
default:
|
|
||||||
Log.e(TAG, "Unknown playback state " + state + " in wasPlayingRecently()");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user