From 521e68e76cfdcf297d0de056032dc142d4939fa0 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Tue, 16 Apr 2013 15:28:46 -0700 Subject: [PATCH] Anti-drift in RCC playback position Periodically verify that the reported playback position hasn't drifted from the estimated playback position. If a drift is noticed, re-synchronize registered IRemoteControlDisplay implementations. bug 8120740 Note that this implementation updates the playback position of all IRemoteControlDisplay implementations, and always causes the OnGetPlaybackPositionListener to be called. This might be undesirable in some circumstances and will be addressed in a subsequent CL. Change-Id: Ib9f40e1b000e912f6c35fa03e41adf81efadc894 --- .../android/media/RemoteControlClient.java | 119 +++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-) diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index 61a0134e01ed8..f77ddc4297f66 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -65,6 +65,7 @@ import java.util.Iterator; public class RemoteControlClient { private final static String TAG = "RemoteControlClient"; + private final static boolean DEBUG = false; /** * Playback state of a RemoteControlClient which is stopped. @@ -219,7 +220,7 @@ public class RemoteControlClient public final static int PLAYBACKINFO_USES_STREAM = 5; //========================================== - // Public flags for the supported transport control capabililities + // Public flags for the supported transport control capabilities /** * Flag indicating a RemoteControlClient makes use of the "previous" media key. * @@ -642,6 +643,57 @@ public class RemoteControlClient sendPlaybackState_syncCacheLock(); // update AudioService sendAudioServiceNewPlaybackState_syncCacheLock(); + + // handle automatic playback position refreshes + if (mEventHandler == null) { + return; + } + mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK); + if (timeInMs == PLAYBACK_POSITION_INVALID) { + // this playback state refresh has no known playback position, it's no use + // trying to see if there is any drift at this point + // (this also bypasses this mechanism for older apps that use the old + // setPlaybackState(int) API) + return; + } + if (playbackPositionShouldMove(mPlaybackState)) { + // playback position moving, schedule next position drift check + mEventHandler.sendMessageDelayed( + mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK), + getCheckPeriodFromSpeed(playbackSpeed)); + } + } + } + } + + private void onPositionDriftCheck() { + if (DEBUG) { Log.d(TAG, "onPositionDriftCheck()"); } + synchronized(mCacheLock) { + if ((mEventHandler == null) || (mPositionProvider == null)) { + return; + } + if ((mPlaybackPositionMs == PLAYBACK_POSITION_INVALID) || (mPlaybackSpeed == 0.0f)) { + if (DEBUG) { Log.d(TAG, " no position or 0 speed, no check needed"); } + return; + } + long estPos = mPlaybackPositionMs + (long) + ((SystemClock.elapsedRealtime() - mPlaybackStateChangeTimeMs) / mPlaybackSpeed); + long actPos = mPositionProvider.onGetPlaybackPosition(); + if (actPos >= 0) { + if (Math.abs(estPos - actPos) > POSITION_DRIFT_MAX_MS) { + // drift happened, report the new position + if (DEBUG) { Log.w(TAG, " drift detected: actual=" +actPos +" est=" +estPos); } + setPlaybackState(mPlaybackState, actPos, mPlaybackSpeed); + } else { + if (DEBUG) { Log.d(TAG, " no drift: actual=" + actPos +" est=" + estPos); } + // no drift, schedule the next drift check + mEventHandler.sendMessageDelayed( + mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK), + getCheckPeriodFromSpeed(mPlaybackSpeed)); + } + } else { + // invalid position (negative value), can't check for drift + mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK); } } } @@ -746,6 +798,14 @@ public class RemoteControlClient // tell RCDs that this RCC's playback position capabilities have changed sendTransportControlInfo_syncCacheLock(); } + if ((mPositionProvider != null) && (mEventHandler != null) + && playbackPositionShouldMove(mPlaybackState)) { + // playback position is already moving, but now we have a position provider, + // so schedule a drift check right now + mEventHandler.sendMessageDelayed( + mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK), + 0 /*check now*/); + } } } @@ -1099,6 +1159,7 @@ public class RemoteControlClient private final static int MSG_UNPLUG_DISPLAY = 8; private final static int MSG_UPDATE_DISPLAY_ARTWORK_SIZE = 9; private final static int MSG_SEEK_TO = 10; + private final static int MSG_POSITION_DRIFT_CHECK = 11; private class EventHandler extends Handler { public EventHandler(RemoteControlClient rcc, Looper looper) { @@ -1146,6 +1207,9 @@ public class RemoteControlClient case MSG_SEEK_TO: onSeekTo(msg.arg1, ((Long)msg.obj).longValue()); break; + case MSG_POSITION_DRIFT_CHECK: + onPositionDriftCheck(); + break; default: Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler"); } @@ -1440,4 +1504,57 @@ public class RemoteControlClient return false; } } + + /** + * Returns whether, for the given playback state, the playback position is expected to + * be changing. + * @param playstate the playback state to evaluate + * @return true during any form of playback, false if it's not playing anything while in this + * playback state + */ + private static boolean playbackPositionShouldMove(int playstate) { + switch(playstate) { + case PLAYSTATE_STOPPED: + case PLAYSTATE_PAUSED: + case PLAYSTATE_BUFFERING: + case PLAYSTATE_ERROR: + case PLAYSTATE_SKIPPING_FORWARDS: + case PLAYSTATE_SKIPPING_BACKWARDS: + return false; + case PLAYSTATE_PLAYING: + case PLAYSTATE_FAST_FORWARDING: + case PLAYSTATE_REWINDING: + default: + return true; + } + } + + /** + * Period for playback position drift checks, 15s when playing at 1x or slower. + */ + private final static long POSITION_REFRESH_PERIOD_PLAYING_MS = 15000; + /** + * Minimum period for playback position drift checks, never more often when every 2s, when + * fast forwarding or rewinding. + */ + private final static long POSITION_REFRESH_PERIOD_MIN_MS = 2000; + /** + * The value above which the difference between client-reported playback position and + * estimated position is considered a drift. + */ + private final static long POSITION_DRIFT_MAX_MS = 500; + /** + * Compute the period at which the estimated playback position should be compared against the + * actual playback position. Is a funciton of playback speed. + * @param speed 1.0f is normal playback speed + * @return the period in ms + */ + private static long getCheckPeriodFromSpeed(float speed) { + if (Math.abs(speed) <= 1.0f) { + return POSITION_REFRESH_PERIOD_PLAYING_MS; + } else { + return Math.max((long)(POSITION_REFRESH_PERIOD_PLAYING_MS / Math.abs(speed)), + POSITION_REFRESH_PERIOD_MIN_MS); + } + } }