diff options
Diffstat (limited to 'src/com/android/gallery3d/app/MoviePlayer.java')
-rwxr-xr-x[-rw-r--r--] | src/com/android/gallery3d/app/MoviePlayer.java | 1251 |
1 files changed, 1206 insertions, 45 deletions
diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java index f6bd36725..2ef7c2e85 100644..100755 --- a/src/com/android/gallery3d/app/MoviePlayer.java +++ b/src/com/android/gallery3d/app/MoviePlayer.java @@ -23,10 +23,15 @@ import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface.OnDismissListener; +import android.content.DialogInterface.OnShowListener; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.graphics.Color; import android.media.AudioManager; import android.media.MediaPlayer; +import android.media.Metadata; import android.media.audiofx.AudioEffect; import android.media.audiofx.Virtualizer; import android.net.Uri; @@ -35,26 +40,47 @@ import android.os.Bundle; import android.os.Handler; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.widget.VideoView; +import android.widget.Toast; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.common.BlobCache; import com.android.gallery3d.util.CacheManager; import com.android.gallery3d.util.GalleryUtils; +import org.codeaurora.gallery3d.ext.IContrllerOverlayExt; +import org.codeaurora.gallery3d.ext.IMoviePlayer; +import org.codeaurora.gallery3d.ext.IMovieItem; +import org.codeaurora.gallery3d.ext.MovieUtils; +import org.codeaurora.gallery3d.video.BookmarkEnhance; +import org.codeaurora.gallery3d.video.ExtensionHelper; +import org.codeaurora.gallery3d.video.IControllerRewindAndForward; +import org.codeaurora.gallery3d.video.IControllerRewindAndForward.IRewindAndForwardListener; +import org.codeaurora.gallery3d.video.ScreenModeManager; +import org.codeaurora.gallery3d.video.ScreenModeManager.ScreenModeListener; +import org.codeaurora.gallery3d.video.CodeauroraVideoView; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.util.HashMap; +import java.util.Map; public class MoviePlayer implements MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, - ControllerOverlay.Listener { + ControllerOverlay.Listener, + MediaPlayer.OnInfoListener, + MediaPlayer.OnPreparedListener, + MediaPlayer.OnSeekCompleteListener, + MediaPlayer.OnVideoSizeChangedListener, + MediaPlayer.OnBufferingUpdateListener { @SuppressWarnings("unused") private static final String TAG = "MoviePlayer"; + private static final boolean LOG = false; private static final String KEY_VIDEO_POSITION = "video-position"; private static final String KEY_RESUMEABLE_TIME = "resumeable-timeout"; @@ -68,18 +94,32 @@ public class MoviePlayer implements private static final String CMDNAME = "command"; private static final String CMDPAUSE = "pause"; + private static final String KEY_VIDEO_CAN_SEEK = "video_can_seek"; + private static final String KEY_VIDEO_CAN_PAUSE = "video_can_pause"; + private static final String KEY_VIDEO_LAST_DURATION = "video_last_duration"; + private static final String KEY_VIDEO_LAST_DISCONNECT_TIME = "last_disconnect_time"; + private static final String KEY_VIDEO_STREAMING_TYPE = "video_streaming_type"; + private static final String KEY_VIDEO_STATE = "video_state"; + private static final String VIRTUALIZE_EXTRA = "virtualize"; private static final long BLACK_TIMEOUT = 500; + private static final int DELAY_REMOVE_MS = 10000; + public static final int SERVER_TIMEOUT = 8801; // If we resume the acitivty with in RESUMEABLE_TIMEOUT, we will keep playing. // Otherwise, we pause the player. private static final long RESUMEABLE_TIMEOUT = 3 * 60 * 1000; // 3 mins + public static final int STREAMING_LOCAL = 0; + public static final int STREAMING_HTTP = 1; + public static final int STREAMING_RTSP = 2; + public static final int STREAMING_SDP = 3; + private int mStreamingType = STREAMING_LOCAL; + private Context mContext; - private final VideoView mVideoView; + private final CodeauroraVideoView mVideoView; private final View mRootView; private final Bookmarker mBookmarker; - private final Uri mUri; private final Handler mHandler = new Handler(); private final AudioBecomingNoisyReceiver mAudioBecomingNoisyReceiver; private final MovieControllerOverlay mController; @@ -87,6 +127,11 @@ public class MoviePlayer implements private long mResumeableTime = Long.MAX_VALUE; private int mVideoPosition = 0; private boolean mHasPaused = false; + private boolean mVideoHasPaused = false; + private boolean mCanResumed = false; + private boolean mFirstBePlayed = false; + private boolean mKeyguardLocked = false; + private boolean mIsOnlyAudio = false; private int mLastSystemUiVis = 0; // If the time bar is being dragged. @@ -97,6 +142,36 @@ public class MoviePlayer implements private Virtualizer mVirtualizer; + private MovieActivity mActivityContext;//for dialog and toast context + private MoviePlayerExtension mPlayerExt = new MoviePlayerExtension(); + private RetryExtension mRetryExt = new RetryExtension(); + private ServerTimeoutExtension mServerTimeoutExt = new ServerTimeoutExtension(); + private ScreenModeExt mScreenModeExt = new ScreenModeExt(); + private IContrllerOverlayExt mOverlayExt; + private IControllerRewindAndForward mControllerRewindAndForwardExt; + private IRewindAndForwardListener mRewindAndForwardListener = new ControllerRewindAndForwardExt(); + private boolean mCanReplay; + private boolean mVideoCanSeek = false; + private boolean mVideoCanPause = false; + private boolean mWaitMetaData; + private boolean mIsShowResumingDialog; + private TState mTState = TState.PLAYING; + private IMovieItem mMovieItem; + private int mVideoLastDuration;//for duration displayed in init state + + private enum TState { + PLAYING, + PAUSED, + STOPED, + COMPELTED, + RETRY_ERROR + } + + interface Restorable { + void onRestoreInstanceState(Bundle icicle); + void onSaveInstanceState(Bundle outState); + } + private final Runnable mPlayingChecker = new Runnable() { @Override public void run() { @@ -116,22 +191,57 @@ public class MoviePlayer implements } }; + private Runnable mDelayVideoRunnable = new Runnable() { + @Override + public void run() { + if (LOG) { + Log.v(TAG, "mDelayVideoRunnable.run()"); + } + mVideoView.setVisibility(View.VISIBLE); + } + }; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + mKeyguardLocked = true; + } else if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) { + if ((mCanResumed) && (!mVideoHasPaused)) { + playVideo(); + } + mKeyguardLocked = false; + mCanResumed = false; + } else if (Intent.ACTION_SHUTDOWN.equals(intent.getAction())) { + if (LOG) { + Log.v(TAG, "Intent.ACTION_SHUTDOWN received"); + } + mActivityContext.finish(); + } + } + }; + public MoviePlayer(View rootView, final MovieActivity movieActivity, - Uri videoUri, Bundle savedInstance, boolean canReplay) { + IMovieItem info, Bundle savedInstance, boolean canReplay) { mContext = movieActivity.getApplicationContext(); mRootView = rootView; - mVideoView = (VideoView) rootView.findViewById(R.id.surface_view); + mVideoView = (CodeauroraVideoView) rootView.findViewById(R.id.surface_view); mBookmarker = new Bookmarker(movieActivity); - mUri = videoUri; - mController = new MovieControllerOverlay(mContext); + mController = new MovieControllerOverlay(movieActivity); ((ViewGroup)rootView).addView(mController.getView()); mController.setListener(this); mController.setCanReplay(canReplay); + init(movieActivity, info, canReplay); + mVideoView.setOnErrorListener(this); mVideoView.setOnCompletionListener(this); - mVideoView.setVideoURI(mUri); + + if (mVirtualizer != null) { + mVirtualizer.release(); + mVirtualizer = null; + } Intent ai = movieActivity.getIntent(); boolean virtualize = ai.getBooleanExtra(VIRTUALIZE_EXTRA, false); @@ -186,20 +296,39 @@ public class MoviePlayer implements i.putExtra(CMDNAME, CMDPAUSE); movieActivity.sendBroadcast(i); + // Listen for broadcasts related to user-presence + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_USER_PRESENT); + filter.addAction(Intent.ACTION_SHUTDOWN); + mContext.registerReceiver(mReceiver, filter); + if (savedInstance != null) { // this is a resumed activity mVideoPosition = savedInstance.getInt(KEY_VIDEO_POSITION, 0); mResumeableTime = savedInstance.getLong(KEY_RESUMEABLE_TIME, Long.MAX_VALUE); - mVideoView.start(); - mVideoView.suspend(); + onRestoreInstanceState(savedInstance); mHasPaused = true; + doStartVideo(true, mVideoPosition, mVideoLastDuration,false); + mVideoView.start(); + mActivityContext.initEffects(mVideoView.getAudioSessionId()); } else { - final Integer bookmark = mBookmarker.getBookmark(mUri); - if (bookmark != null) { - showResumeDialog(movieActivity, bookmark); + mTState = TState.PLAYING; + mFirstBePlayed = true; + String mUri = mMovieItem.getUri().toString(); + boolean isLive = mUri.startsWith("rtsp://") && (mUri.contains(".sdp") + || mUri.contains(".smil")); + if (!isLive) { + final BookmarkerInfo bookmark = mBookmarker.getBookmark(mMovieItem.getUri()); + if (bookmark != null) { + showResumeDialog(movieActivity, bookmark); + } else { + doStartVideo(false, 0, 0); + } } else { - startVideo(); + doStartVideo(false, 0, 0); } } + mScreenModeExt.setScreenMode(); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @@ -213,11 +342,17 @@ public class MoviePlayer implements new View.OnSystemUiVisibilityChangeListener() { @Override public void onSystemUiVisibilityChange(int visibility) { + boolean finish = (mActivityContext == null ? true : mActivityContext.isFinishing()); int diff = mLastSystemUiVis ^ visibility; mLastSystemUiVis = visibility; if ((diff & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0 && (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) { mController.show(); + mRootView.setBackgroundColor(Color.BLACK); + } + + if (LOG) { + Log.v(TAG, "onSystemUiVisibilityChange(" + visibility + ") finishing()=" + finish); } } }); @@ -242,14 +377,15 @@ public class MoviePlayer implements public void onSaveInstanceState(Bundle outState) { outState.putInt(KEY_VIDEO_POSITION, mVideoPosition); outState.putLong(KEY_RESUMEABLE_TIME, mResumeableTime); + onSaveInstanceStateMore(outState); } - private void showResumeDialog(Context context, final int bookmark) { + private void showResumeDialog(Context context, final BookmarkerInfo bookmark) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.resume_playing_title); builder.setMessage(String.format( context.getString(R.string.resume_playing_message), - GalleryUtils.formatDuration(context, bookmark / 1000))); + GalleryUtils.formatDuration(context, bookmark.mBookmark / 1000))); builder.setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { @@ -260,42 +396,157 @@ public class MoviePlayer implements R.string.resume_playing_resume, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mVideoView.seekTo(bookmark); - startVideo(); + // here try to seek for bookmark + mVideoCanSeek = true; + doStartVideo(true, bookmark.mBookmark, bookmark.mDuration); } }); builder.setNegativeButton( R.string.resume_playing_restart, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - startVideo(); + doStartVideo(true, 0, bookmark.mDuration); + } + }); + AlertDialog dialog = builder.create(); + dialog.setOnShowListener(new OnShowListener() { + @Override + public void onShow(DialogInterface arg0) { + mIsShowResumingDialog = true; } }); - builder.show(); + dialog.setOnDismissListener(new OnDismissListener() { + @Override + public void onDismiss(DialogInterface arg0) { + mIsShowResumingDialog = false; + } + }); + dialog.show(); + } + + public void setDefaultScreenMode() { + addBackground(); + mController.setDefaultScreenMode(); + removeBackground(); + } + + public boolean onPause() { + if (LOG) { + Log.v(TAG, "onPause() isLiveStreaming()=" + isLiveStreaming()); + } + boolean pause = false; + if (isLiveStreaming()) { + pause = false; + } else { + doOnPause(); + pause = true; + } + if (LOG) { + Log.v(TAG, "onPause() , return " + pause); + } + return pause; } - public void onPause() { + // we should stop video anyway after this function called. + public void onStop() { + if (LOG) { + Log.v(TAG, "onStop() mHasPaused=" + mHasPaused); + } + if (!mHasPaused) { + doOnPause(); + } + } + + private void doOnPause() { + long start = System.currentTimeMillis(); + addBackground(); mHasPaused = true; mHandler.removeCallbacksAndMessages(null); - mVideoPosition = mVideoView.getCurrentPosition(); - mBookmarker.setBookmark(mUri, mVideoPosition, mVideoView.getDuration()); + int position = mVideoView.getCurrentPosition(); + mVideoPosition = position >= 0 ? position : mVideoPosition; + Log.v(TAG, "mVideoPosition is " + mVideoPosition); + int duration = mVideoView.getDuration(); + mVideoLastDuration = duration > 0 ? duration : mVideoLastDuration; + mBookmarker.setBookmark(mMovieItem.getUri(), mVideoPosition, mVideoLastDuration); + long end1 = System.currentTimeMillis(); mVideoView.suspend(); mResumeableTime = System.currentTimeMillis() + RESUMEABLE_TIMEOUT; + mVideoView.setResumed(false);// avoid start after surface created + long end2 = System.currentTimeMillis(); + // TODO comments by sunlei + mOverlayExt.clearBuffering(); + mServerTimeoutExt.recordDisconnectTime(); + if (LOG) { + Log.v(TAG, "doOnPause() save video info consume:" + (end1 - start)); + Log.v(TAG, "doOnPause() suspend video consume:" + (end2 - end1)); + Log.v(TAG, "doOnPause() mVideoPosition=" + mVideoPosition + ", mResumeableTime=" + + mResumeableTime + + ", mVideoLastDuration=" + mVideoLastDuration + ", mIsShowResumingDialog=" + + mIsShowResumingDialog); + } } public void onResume() { + mDragging = false;// clear drag info if (mHasPaused) { - mVideoView.seekTo(mVideoPosition); - mVideoView.resume(); + //M: same as launch case to delay transparent. + mVideoView.removeCallbacks(mDelayVideoRunnable); + mVideoView.postDelayed(mDelayVideoRunnable, BLACK_TIMEOUT); - // If we have slept for too long, pause the play - if (System.currentTimeMillis() > mResumeableTime) { - pauseVideo(); + if (mServerTimeoutExt.handleOnResume() || mIsShowResumingDialog) { + mHasPaused = false; + return; + } + switch (mTState) { + case RETRY_ERROR: + mRetryExt.showRetry(); + break; + case STOPED: + mPlayerExt.stopVideo(); + break; + case COMPELTED: + mController.showEnded(); + if (mVideoCanSeek || mVideoView.canSeekForward()) { + mVideoView.seekTo(mVideoPosition); + } + mVideoView.setDuration(mVideoLastDuration); + break; + case PAUSED: + // if video was paused, so it should be started. + doStartVideo(true, mVideoPosition, mVideoLastDuration, false); + pauseVideo(); + break; + default: + mVideoView.seekTo(mVideoPosition); + mVideoView.resume(); + pauseVideoMoreThanThreeMinutes(); + break; } + mHasPaused = false; + } + + if (System.currentTimeMillis() > mResumeableTime) { + mHandler.removeCallbacks(mPlayingChecker); + mHandler.postDelayed(mPlayingChecker, 250); } + mHandler.post(mProgressChecker); } + private void pauseVideoMoreThanThreeMinutes() { + // If we have slept for too long, pause the play + // If is live streaming, do not pause it too + long now = System.currentTimeMillis(); + if (now > mResumeableTime && !isLiveStreaming()) { + if (mVideoCanPause || mVideoView.canPause()) { + pauseVideo(); + } + } + if (LOG) { + Log.v(TAG, "pauseVideoMoreThanThreeMinutes() now=" + now); + } + } + public void onDestroy() { if (mVirtualizer != null) { mVirtualizer.release(); @@ -303,27 +554,36 @@ public class MoviePlayer implements } mVideoView.stopPlayback(); mAudioBecomingNoisyReceiver.unregister(); + mContext.unregisterReceiver(mReceiver); + mServerTimeoutExt.clearTimeoutDialog(); } // This updates the time bar display (if necessary). It is called every // second by mProgressChecker and also from places where the time bar needs // to be updated immediately. private int setProgress() { - if (mDragging || !mShowing) { + if (mDragging || (!mShowing && !mIsOnlyAudio)) { return 0; } int position = mVideoView.getCurrentPosition(); int duration = mVideoView.getDuration(); mController.setTimes(position, duration, 0, 0); + if (mControllerRewindAndForwardExt != null + && mControllerRewindAndForwardExt.getPlayPauseEanbled()) { + updateRewindAndForwardUI(); + } return position; } - private void startVideo() { + private void doStartVideo(final boolean enableFasten, final int position, final int duration, + boolean start) { // For streams that we expect to be slow to start up, show a // progress spinner until playback starts. - String scheme = mUri.getScheme(); - if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme)) { + String scheme = mMovieItem.getUri().getScheme(); + if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme) + || "https".equalsIgnoreCase(scheme)) { mController.showLoading(); + mOverlayExt.setPlayingInfo(isLiveStreaming()); mHandler.removeCallbacks(mPlayingChecker); mHandler.postDelayed(mPlayingChecker, 250); } else { @@ -331,35 +591,94 @@ public class MoviePlayer implements mController.hide(); } - mVideoView.start(); + if (onIsRTSP()) { + Map<String, String> header = new HashMap<String, String>(1); + header.put("CODEAURORA-ASYNC-RTSP-PAUSE-PLAY", "true"); + mVideoView.setVideoURI(mMovieItem.getUri(), header, !mWaitMetaData); + } else { + mVideoView.setVideoURI(mMovieItem.getUri(), null, !mWaitMetaData); + } + if (start) { + mVideoView.start(); + mVideoView.setVisibility(View.VISIBLE); + mActivityContext.initEffects(mVideoView.getAudioSessionId()); + } + //we may start video from stopVideo, + //this case, we should reset canReplay flag according canReplay and loop + boolean loop = mPlayerExt.getLoop(); + boolean canReplay = loop ? loop : mCanReplay; + mController.setCanReplay(canReplay); + if (position > 0 && (mVideoCanSeek || mVideoView.canSeek())) { + mVideoView.seekTo(position); + } + if (enableFasten) { + mVideoView.setDuration(duration); + } setProgress(); } + private void doStartVideo(boolean enableFasten, int position, int duration) { + doStartVideo(enableFasten, position, duration, true); + } + private void playVideo() { + if (LOG) { + Log.v(TAG, "playVideo()"); + } + mTState = TState.PLAYING; mVideoView.start(); mController.showPlaying(); setProgress(); } private void pauseVideo() { + if (LOG) { + Log.v(TAG, "pauseVideo()"); + } + mTState = TState.PAUSED; mVideoView.pause(); + setProgress(); mController.showPaused(); } // Below are notifications from VideoView @Override public boolean onError(MediaPlayer player, int arg1, int arg2) { + mMovieItem.setError(); + if (mServerTimeoutExt.onError(player, arg1, arg2)) { + return true; + } + if (mRetryExt.onError(player, arg1, arg2)) { + return true; + } mHandler.removeCallbacksAndMessages(null); // VideoView will show an error dialog if we return false, so no need // to show more message. + //M:resume controller + mController.setViewEnabled(true); mController.showErrorMessage(""); return false; } @Override public void onCompletion(MediaPlayer mp) { - mController.showEnded(); - onCompletion(); + if (LOG) { + Log.v(TAG, "onCompletion() mCanReplay=" + mCanReplay); + } + if (mMovieItem.getError()) { + Log.w(TAG, "error occured, exit the video player!"); + mActivityContext.finish(); + return; + } + if (mPlayerExt.getLoop()) { + onReplay(); + } else { //original logic + mTState = TState.COMPELTED; + if (mCanReplay) { + mController.showEnded(); + } + onCompletion(); + } } public void onCompletion() { @@ -369,9 +688,23 @@ public class MoviePlayer implements @Override public void onPlayPause() { if (mVideoView.isPlaying()) { - pauseVideo(); + if (mVideoView.canPause()) { + pauseVideo(); + //set view disabled(play/pause asynchronous processing) + mController.setViewEnabled(true); + if (mControllerRewindAndForwardExt != null) { + mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt + .canStop(), false, false); + } + } } else { playVideo(); + //set view disabled(play/pause asynchronous processing) + mController.setViewEnabled(true); + if (mControllerRewindAndForwardExt != null) { + mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt + .canStop(), false, false); + } } } @@ -382,18 +715,27 @@ public class MoviePlayer implements @Override public void onSeekMove(int time) { - mVideoView.seekTo(time); + if (mVideoView.canSeek()) { + mVideoView.seekTo(time); + } } @Override public void onSeekEnd(int time, int start, int end) { mDragging = false; - mVideoView.seekTo(time); + if (mVideoView.canSeek()) { + mVideoView.seekTo(time); + } + } + + @Override + public void onSeekComplete(MediaPlayer mp) { setProgress(); } @Override public void onShown() { + addBackground(); mShowing = true; setProgress(); showSystemUi(true); @@ -403,11 +745,82 @@ public class MoviePlayer implements public void onHidden() { mShowing = false; showSystemUi(false); + removeBackground(); + } + + @Override + public boolean onInfo(MediaPlayer mp, int what, int extra) { + if (LOG) { + Log.v(TAG, "onInfo() what:" + what + " extra:" + extra); + } + if (what == MediaPlayer.MEDIA_INFO_NOT_SEEKABLE && mOverlayExt != null) { + boolean flag = (extra == 1); + mOverlayExt.setCanPause(flag); + mOverlayExt.setCanScrubbing(flag); + } else if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE && mServerTimeoutExt != null) { + Log.e(TAG, "setServerTimeout " + extra); + mServerTimeoutExt.setTimeout(extra * 1000); + } + if (mRetryExt.onInfo(mp, what, extra)) { + return true; + } + return false; + } + + @Override + public void onBufferingUpdate(MediaPlayer mp, int percent) { + boolean fullBuffer = isFullBuffer(); + mOverlayExt.showBuffering(fullBuffer, percent); + } + + @Override + public void onPrepared(MediaPlayer mp) { + if (LOG) { + Log.v(TAG, "onPrepared(" + mp + ")"); + } + if (!isLocalFile()) { + mOverlayExt.setPlayingInfo(isLiveStreaming()); + } + getVideoInfo(mp); + boolean canPause = mVideoView.canPause(); + boolean canSeek = mVideoView.canSeek(); + mOverlayExt.setCanPause(canPause); + mOverlayExt.setCanScrubbing(canSeek); + mController.setPlayPauseReplayResume(); + if (!canPause && !mVideoView.isTargetPlaying()) { + mVideoView.start(); + } + updateRewindAndForwardUI(); + if (LOG) { + Log.v(TAG, "onPrepared() canPause=" + canPause + ", canSeek=" + canSeek); + } + } + + public boolean onIsRTSP() { + if (MovieUtils.isRtspStreaming(mMovieItem.getUri(), mMovieItem + .getMimeType())) { + if (LOG) { + Log.v(TAG, "onIsRTSP() is RTSP"); + } + return true; + } + if (LOG) { + Log.v(TAG, "onIsRTSP() is not RTSP"); + } + return false; } @Override public void onReplay() { - startVideo(); + if (LOG) { + Log.v(TAG, "onReplay()"); + } + mTState = TState.PLAYING; + mFirstBePlayed = true; + if (mRetryExt.handleOnReplay()) { + return; + } + doStartVideo(false, 0, 0); } // Below are key events passed from MovieActivity. @@ -421,14 +834,14 @@ public class MoviePlayer implements switch (keyCode) { case KeyEvent.KEYCODE_HEADSETHOOK: case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - if (mVideoView.isPlaying()) { + if (mVideoView.isPlaying() && mVideoView.canPause()) { pauseVideo(); } else { playVideo(); } return true; case KEYCODE_MEDIA_PAUSE: - if (mVideoView.isPlaying()) { + if (mVideoView.isPlaying() && mVideoView.canPause()) { pauseVideo(); } return true; @@ -450,6 +863,21 @@ public class MoviePlayer implements return isMediaKey(keyCode); } + public void updateRewindAndForwardUI() { + if (LOG) { + Log.v(TAG, "updateRewindAndForwardUI"); + Log.v(TAG, "updateRewindAndForwardUI== getCurrentPosition = " + mVideoView.getCurrentPosition()); + Log.v(TAG, "updateRewindAndForwardUI==getDuration =" + mVideoView.getDuration()); + } + if (mControllerRewindAndForwardExt != null) { + mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt + .canStop(), mVideoView.canSeekBackward() + && mControllerRewindAndForwardExt.getTimeBarEanbled(), mVideoView + .canSeekForward() + && mControllerRewindAndForwardExt.getTimeBarEanbled()); + } + } + private static boolean isMediaKey(int keyCode) { return keyCode == KeyEvent.KEYCODE_HEADSETHOOK || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS @@ -459,6 +887,30 @@ public class MoviePlayer implements || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE; } + private void init(MovieActivity movieActivity, IMovieItem info, boolean canReplay) { + mActivityContext = movieActivity; + mCanReplay = canReplay; + mMovieItem = info; + judgeStreamingType(info.getUri(), info.getMimeType()); + + mVideoView.setOnInfoListener(this); + mVideoView.setOnPreparedListener(this); + mVideoView.setOnBufferingUpdateListener(this); + mVideoView.setOnVideoSizeChangedListener(this); + mRootView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + mController.show(); + return true; + } + }); + mOverlayExt = mController.getOverlayExt(); + mControllerRewindAndForwardExt = mController.getControllerRewindAndForwardExt(); + if (mControllerRewindAndForwardExt != null) { + mControllerRewindAndForwardExt.setIListener(mRewindAndForwardListener); + } + } + // We want to pause when the headset is unplugged. private class AudioBecomingNoisyReceiver extends BroadcastReceiver { @@ -473,7 +925,691 @@ public class MoviePlayer implements @Override public void onReceive(Context context, Intent intent) { - if (mVideoView.isPlaying()) pauseVideo(); + if (mVideoView.isPlaying() && mVideoView.canPause()) pauseVideo(); + } + } + + public int getAudioSessionId() { + return mVideoView.getAudioSessionId(); + } + + public void setOnPreparedListener(MediaPlayer.OnPreparedListener listener) { + mVideoView.setOnPreparedListener(listener); + } + + public boolean isFullBuffer() { + if (mStreamingType == STREAMING_RTSP || mStreamingType == STREAMING_SDP) { + return false; + } + return true; + } + + public boolean isLocalFile() { + if (mStreamingType == STREAMING_LOCAL) { + return true; + } + return false; + } + + private void getVideoInfo(MediaPlayer mp) { + if (!MovieUtils.isLocalFile(mMovieItem.getUri(), mMovieItem.getMimeType())) { + Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL, + MediaPlayer.BYPASS_METADATA_FILTER); + if (data != null) { + // TODO comments by sunlei + mServerTimeoutExt.setVideoInfo(data); + } else { + Log.w(TAG, "Metadata is null!"); + } + } + } + + private void judgeStreamingType(Uri uri, String mimeType) { + if (LOG) { + Log.v(TAG, "judgeStreamingType(" + uri + ")"); + } + if (uri == null) { + return; + } + String scheme = uri.getScheme(); + mWaitMetaData = true; + if (MovieUtils.isSdpStreaming(uri, mimeType)) { + mStreamingType = STREAMING_SDP; + mWaitMetaData = false; + } else if (MovieUtils.isRtspStreaming(uri, mimeType)) { + mStreamingType = STREAMING_RTSP; + mWaitMetaData = false; + } else if (MovieUtils.isHttpStreaming(uri, mimeType)) { + mStreamingType = STREAMING_HTTP; + mWaitMetaData = false; + } else { + mStreamingType = STREAMING_LOCAL; + mWaitMetaData = false; + } + if (LOG) { + Log.v(TAG, "mStreamingType=" + mStreamingType + + " mCanGetMetaData=" + mWaitMetaData); + } + } + + public boolean isLiveStreaming() { + boolean isLive = false; + if (mStreamingType == STREAMING_SDP) { + isLive = true; + } + if (LOG) { + Log.v(TAG, "isLiveStreaming() return " + isLive); + } + return isLive; + } + + public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { + // reget the audio type + if (width != 0 && height != 0) { + mIsOnlyAudio = false; + } else { + mIsOnlyAudio = true; + } + mOverlayExt.setBottomPanel(mIsOnlyAudio, true); + if (LOG) { + Log.v(TAG, "onVideoSizeChanged(" + width + ", " + height + ") mIsOnlyAudio=" + + mIsOnlyAudio); + } + } + + public IMoviePlayer getMoviePlayerExt() { + return mPlayerExt; + } + + public SurfaceView getVideoSurface() { + return mVideoView; + } + + // Wait for any animation, ten seconds should be enough + private final Runnable mRemoveBackground = new Runnable() { + @Override + public void run() { + if (LOG) { + Log.v(TAG, "mRemoveBackground.run()"); + } + mRootView.setBackground(null); + } + }; + + private void removeBackground() { + if (LOG) { + Log.v(TAG, "removeBackground()"); + } + mHandler.removeCallbacks(mRemoveBackground); + mHandler.postDelayed(mRemoveBackground, DELAY_REMOVE_MS); + } + + // add background for removing ghost image. + private void addBackground() { + if (LOG) { + Log.v(TAG, "addBackground()"); + } + mHandler.removeCallbacks(mRemoveBackground); + mRootView.setBackgroundColor(Color.BLACK); + } + + private void clearVideoInfo() { + mVideoPosition = 0; + mVideoLastDuration = 0; + mIsOnlyAudio = false; + + if (mServerTimeoutExt != null) { + mServerTimeoutExt.clearServerInfo(); + } + } + + private void onSaveInstanceStateMore(Bundle outState) { + outState.putInt(KEY_VIDEO_LAST_DURATION, mVideoLastDuration); + outState.putBoolean(KEY_VIDEO_CAN_SEEK, mVideoView.canSeekForward()); + outState.putBoolean(KEY_VIDEO_CAN_PAUSE, mVideoView.canPause()); + outState.putInt(KEY_VIDEO_STREAMING_TYPE, mStreamingType); + outState.putString(KEY_VIDEO_STATE, String.valueOf(mTState)); + mServerTimeoutExt.onSaveInstanceState(outState); + mScreenModeExt.onSaveInstanceState(outState); + mRetryExt.onSaveInstanceState(outState); + mPlayerExt.onSaveInstanceState(outState); + } + + private void onRestoreInstanceState(Bundle icicle) { + mVideoLastDuration = icicle.getInt(KEY_VIDEO_LAST_DURATION); + mVideoCanSeek = icicle.getBoolean(KEY_VIDEO_CAN_SEEK); + mVideoCanPause = icicle.getBoolean(KEY_VIDEO_CAN_PAUSE); + mStreamingType = icicle.getInt(KEY_VIDEO_STREAMING_TYPE); + mTState = TState.valueOf(icicle.getString(KEY_VIDEO_STATE)); + mServerTimeoutExt.onRestoreInstanceState(icicle); + mScreenModeExt.onRestoreInstanceState(icicle); + mRetryExt.onRestoreInstanceState(icicle); + mPlayerExt.onRestoreInstanceState(icicle); + } + + private class MoviePlayerExtension implements IMoviePlayer, Restorable { + + private static final String KEY_VIDEO_IS_LOOP = "video_is_loop"; + + private BookmarkEnhance mBookmark;//for bookmark + private boolean mIsLoop; + private boolean mLastPlaying; + private boolean mLastCanPaused; + + @Override + public boolean getLoop() { + return mIsLoop; + } + + @Override + public void setLoop(boolean loop) { + if (isLocalFile()) { + mIsLoop = loop; + mController.setCanReplay(loop); + } + } + + @Override + public void startNextVideo(IMovieItem item) { + IMovieItem next = item; + if (next != null && next != mMovieItem) { + int position = mVideoView.getCurrentPosition(); + int duration = mVideoView.getDuration(); + mBookmarker.setBookmark(mMovieItem.getUri(), position, duration); + mVideoView.stopPlayback(); + mVideoView.setVisibility(View.INVISIBLE); + clearVideoInfo(); + mActivityContext.releaseEffects(); + mMovieItem = next; + mActivityContext.refreshMovieInfo(mMovieItem); + doStartVideo(false, 0, 0); + mVideoView.setVisibility(View.VISIBLE); + } else { + Log.e(TAG, "Cannot play the next video! " + item); + } + mActivityContext.closeOptionsMenu(); + } + + @Override + public void onRestoreInstanceState(Bundle icicle) { + mIsLoop = icicle.getBoolean(KEY_VIDEO_IS_LOOP, false); + if (mIsLoop) { + mController.setCanReplay(true); + } // else will get can replay from intent. + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putBoolean(KEY_VIDEO_IS_LOOP, mIsLoop); + } + + @Override + public void stopVideo() { + if (LOG) { + Log.v(TAG, "stopVideo()"); + } + mTState = TState.STOPED; + mVideoView.clearSeek(); + mVideoView.clearDuration(); + mVideoView.stopPlayback(); + mVideoView.setResumed(false); + mVideoView.setVisibility(View.INVISIBLE); + clearVideoInfo(); + mActivityContext.releaseEffects(); + mFirstBePlayed = false; + mController.setCanReplay(true); + mController.showEnded(); + mController.setViewEnabled(true); + setProgress(); + } + + @Override + public boolean canStop() { + boolean stopped = false; + if (mController != null) { + stopped = mOverlayExt.isPlayingEnd(); + } + if (LOG) { + Log.v(TAG, "canStop() stopped=" + stopped); + } + return !stopped; + } + + @Override + public void addBookmark() { + if (mBookmark == null) { + mBookmark = new BookmarkEnhance(mActivityContext); + } + String uri = String.valueOf(mMovieItem.getUri()); + if (mBookmark.exists(uri)) { + Toast.makeText(mActivityContext, R.string.bookmark_exist, Toast.LENGTH_SHORT) + .show(); + } else { + mBookmark.insert(mMovieItem.getTitle(), uri, + mMovieItem.getMimeType(), 0); + Toast.makeText(mActivityContext, R.string.bookmark_add_success, Toast.LENGTH_SHORT) + .show(); + } + } + }; + + private class RetryExtension implements Restorable, MediaPlayer.OnErrorListener, + MediaPlayer.OnInfoListener { + private static final String KEY_VIDEO_RETRY_COUNT = "video_retry_count"; + private int mRetryDuration; + private int mRetryPosition; + private int mRetryCount; + + public void retry() { + doStartVideo(true, mRetryPosition, mRetryDuration); + if (LOG) { + Log.v(TAG, "retry() mRetryCount=" + mRetryCount + ", mRetryPosition=" + + mRetryPosition); + } + } + + public void clearRetry() { + if (LOG) { + Log.v(TAG, "clearRetry() mRetryCount=" + mRetryCount); + } + mRetryCount = 0; + } + + public boolean reachRetryCount() { + if (LOG) { + Log.v(TAG, "reachRetryCount() mRetryCount=" + mRetryCount); + } + if (mRetryCount > 3) { + return true; + } + return false; + } + + public int getRetryCount() { + if (LOG) { + Log.v(TAG, "getRetryCount() return " + mRetryCount); + } + return mRetryCount; + } + + public boolean isRetrying() { + boolean retry = false; + if (mRetryCount > 0) { + retry = true; + } + if (LOG) { + Log.v(TAG, "isRetrying() mRetryCount=" + mRetryCount); + } + return retry; + } + + @Override + public void onRestoreInstanceState(Bundle icicle) { + mRetryCount = icicle.getInt(KEY_VIDEO_RETRY_COUNT); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putInt(KEY_VIDEO_RETRY_COUNT, mRetryCount); + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + return false; + } + + @Override + public boolean onInfo(MediaPlayer mp, int what, int extra) { + return false; + } + + public boolean handleOnReplay() { + if (isRetrying()) { // from connecting error + clearRetry(); + int errorPosition = mVideoView.getCurrentPosition(); + int errorDuration = mVideoView.getDuration(); + doStartVideo(errorPosition > 0, errorPosition, errorDuration); + if (LOG) { + Log.v(TAG, "onReplay() errorPosition=" + errorPosition + ", errorDuration=" + + errorDuration); + } + return true; + } + return false; + } + + public void showRetry() { + if (mVideoCanSeek || mVideoView.canSeekForward()) { + mVideoView.seekTo(mVideoPosition); + } + mVideoView.setDuration(mVideoLastDuration); + mRetryPosition = mVideoPosition; + mRetryDuration = mVideoLastDuration; + } + } + private class ServerTimeoutExtension implements Restorable, MediaPlayer.OnErrorListener { + // for cmcc server timeout case + // please remember to clear this value when changed video. + private int mServerTimeout = -1; + private long mLastDisconnectTime; + private boolean mIsShowDialog = false; + private AlertDialog mServerTimeoutDialog; + private int RESUME_DIALOG_TIMEOUT = 3 * 60 * 1000; // 3 mins + + // check whether disconnect from server timeout or not. + // if timeout, return false. otherwise, return true. + private boolean passDisconnectCheck() { + if (!isFullBuffer()) { + // record the time disconnect from server + long now = System.currentTimeMillis(); + if (LOG) { + Log.v(TAG, "passDisconnectCheck() now=" + now + ", mLastDisconnectTime=" + + mLastDisconnectTime + + ", mServerTimeout=" + mServerTimeout); + } + if (mServerTimeout > 0 && (now - mLastDisconnectTime) > mServerTimeout) { + // disconnect time more than server timeout, notify user + notifyServerTimeout(); + return false; + } + } + return true; + } + + private void recordDisconnectTime() { + if (!isFullBuffer()) { + // record the time disconnect from server + mLastDisconnectTime = System.currentTimeMillis(); + } + if (LOG) { + Log.v(TAG, "recordDisconnectTime() mLastDisconnectTime=" + mLastDisconnectTime); + } + } + + private void clearServerInfo() { + mServerTimeout = -1; + } + + private void notifyServerTimeout() { + if (mServerTimeoutDialog == null) { + // for updating last position and duration. + if (mVideoCanSeek || mVideoView.canSeekForward()) { + mVideoView.seekTo(mVideoPosition); + } + mVideoView.setDuration(mVideoLastDuration); + AlertDialog.Builder builder = new AlertDialog.Builder(mActivityContext); + mServerTimeoutDialog = builder.setTitle(R.string.server_timeout_title) + .setMessage(R.string.server_timeout_message) + .setNegativeButton(android.R.string.cancel, new OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + if (LOG) { + Log.v(TAG, "NegativeButton.onClick() mIsShowDialog=" + + mIsShowDialog); + } + mController.showEnded(); + onCompletion(); + } + + }) + .setPositiveButton(R.string.resume_playing_resume, new OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + if (LOG) { + Log.v(TAG, "PositiveButton.onClick() mIsShowDialog=" + + mIsShowDialog); + } + doStartVideo(true, mVideoPosition, mVideoLastDuration); + } + + }) + .setOnCancelListener(new OnCancelListener() { + public void onCancel(DialogInterface dialog) { + mController.showEnded(); + onCompletion(); + } + }) + .create(); + mServerTimeoutDialog.setOnDismissListener(new OnDismissListener() { + + public void onDismiss(DialogInterface dialog) { + if (LOG) { + Log.v(TAG, "mServerTimeoutDialog.onDismiss()"); + } + mVideoView.setDialogShowState(false); + mIsShowDialog = false; + } + + }); + mServerTimeoutDialog.setOnShowListener(new OnShowListener() { + + public void onShow(DialogInterface dialog) { + if (LOG) { + Log.v(TAG, "mServerTimeoutDialog.onShow()"); + } + mVideoView.setDialogShowState(true); + mIsShowDialog = true; + } + + }); + } + mServerTimeoutDialog.show(); + } + + private void clearTimeoutDialog() { + if (mServerTimeoutDialog != null && mServerTimeoutDialog.isShowing()) { + mServerTimeoutDialog.dismiss(); + } + mServerTimeoutDialog = null; + } + + @Override + public void onRestoreInstanceState(Bundle icicle) { + mLastDisconnectTime = icicle.getLong(KEY_VIDEO_LAST_DISCONNECT_TIME); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putLong(KEY_VIDEO_LAST_DISCONNECT_TIME, mLastDisconnectTime); + } + + public boolean handleOnResume() { + if (mIsShowDialog && !isLiveStreaming()) { + // wait for user's operation + return true; + } + if (!passDisconnectCheck()) { + return true; + } + return false; + } + + public void setVideoInfo(Metadata data) { + mServerTimeout = RESUME_DIALOG_TIMEOUT; + if (data.has(SERVER_TIMEOUT)) { + mServerTimeout = data.getInt(SERVER_TIMEOUT); + if (mServerTimeout == 0) { + mServerTimeout = RESUME_DIALOG_TIMEOUT; + } + if (LOG) { + Log.v(TAG, "get server timeout from metadata. mServerTimeout=" + + mServerTimeout); + } + } + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + // if we are showing a dialog, cancel the error dialog + if (mIsShowDialog) { + return true; + } + return false; + } + + public void setTimeout(int timeout) { + mServerTimeout = timeout; + } + } + + private class ScreenModeExt implements Restorable, ScreenModeListener { + private static final String KEY_VIDEO_SCREEN_MODE = "video_screen_mode"; + private int mScreenMode = ScreenModeManager.SCREENMODE_BIGSCREEN; + private ScreenModeManager mScreenModeManager = new ScreenModeManager(); + + public void setScreenMode() { + mVideoView.setScreenModeManager(mScreenModeManager); + mController.setScreenModeManager(mScreenModeManager); + mScreenModeManager.addListener(this); + //notify all listener to change screen mode + mScreenModeManager.setScreenMode(mScreenMode); + if (LOG) { + Log.v(TAG, "setScreenMode() mScreenMode=" + mScreenMode); + } + } + + @Override + public void onScreenModeChanged(int newMode) { + mScreenMode = newMode;// changed from controller + if (LOG) { + Log.v(TAG, "OnScreenModeClicked(" + newMode + ")"); + } + } + + @Override + public void onRestoreInstanceState(Bundle icicle) { + mScreenMode = icicle.getInt(KEY_VIDEO_SCREEN_MODE, + ScreenModeManager.SCREENMODE_BIGSCREEN); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + outState.putInt(KEY_VIDEO_SCREEN_MODE, mScreenMode); + } + } + + private class ControllerRewindAndForwardExt implements IRewindAndForwardListener { + @Override + public void onPlayPause() { + onPlayPause(); + } + + @Override + public void onSeekStart() { + onSeekStart(); + } + + @Override + public void onSeekMove(int time) { + onSeekMove(time); + } + + @Override + public void onSeekEnd(int time, int trimStartTime, int trimEndTime) { + onSeekEnd(time, trimStartTime, trimEndTime); + } + + @Override + public void onShown() { + onShown(); + } + + @Override + public void onHidden() { + onHidden(); + } + + @Override + public void onReplay() { + onReplay(); + } + + @Override + public boolean onIsRTSP() { + return false; + } + + @Override + public void onStopVideo() { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt onStopVideo()"); + } + if (mPlayerExt.canStop()) { + mPlayerExt.stopVideo(); + mControllerRewindAndForwardExt.showControllerButtonsView(false, + false, false); + } + } + + @Override + public void onRewind() { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt onRewind()"); + } + if (mVideoView != null && mVideoView.canSeekBackward()) { + mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt + .canStop(), + false, false); + int stepValue = getStepOptionValue(); + int targetDuration = mVideoView.getCurrentPosition() + - stepValue < 0 ? 0 : mVideoView.getCurrentPosition() + - stepValue; + if (LOG) { + Log.v(TAG, "onRewind targetDuration " + targetDuration); + } + mVideoView.seekTo(targetDuration); + } else { + mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt + .canStop(), + false, false); + } + } + + @Override + public void onForward() { + if (LOG) { + Log.v(TAG, "ControllerRewindAndForwardExt onForward()"); + } + if (mVideoView != null && mVideoView.canSeekForward()) { + mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt + .canStop(), + false, false); + int stepValue = getStepOptionValue(); + int targetDuration = mVideoView.getCurrentPosition() + + stepValue > mVideoView.getDuration() ? mVideoView + .getDuration() : mVideoView.getCurrentPosition() + + stepValue; + if (LOG) { + Log.v(TAG, "onForward targetDuration " + targetDuration); + } + mVideoView.seekTo(targetDuration); + } else { + mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt + .canStop(), + false, false); + } + } + } + + public int getStepOptionValue() { + final String slectedStepOption = "selected_step_option"; + final String videoPlayerData = "video_player_data"; + final int stepBase = 3000; + final int stepOptionThreeSeconds = 0; + SharedPreferences mPrefs = mContext.getSharedPreferences( + videoPlayerData, 0); + return (mPrefs.getInt(slectedStepOption, stepOptionThreeSeconds) + 1) * stepBase; + } + + public void restartHidingController() { + if (mController != null) { + mController.maybeStartHiding(); + } + } + + public void cancelHidingController() { + if (mController != null) { + mController.cancelHiding(); } } } @@ -497,6 +1633,10 @@ class Bookmarker { public void setBookmark(Uri uri, int bookmark, int duration) { try { + // do not record or override bookmark if duration is not valid. + if (duration <= 0) { + return; + } BlobCache cache = CacheManager.getCache(mContext, BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES, BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION); @@ -505,7 +1645,7 @@ class Bookmarker { DataOutputStream dos = new DataOutputStream(bos); dos.writeUTF(uri.toString()); dos.writeInt(bookmark); - dos.writeInt(duration); + dos.writeInt(Math.abs(duration)); dos.flush(); cache.insert(uri.hashCode(), bos.toByteArray()); } catch (Throwable t) { @@ -513,7 +1653,7 @@ class Bookmarker { } } - public Integer getBookmark(Uri uri) { + public BookmarkerInfo getBookmark(Uri uri) { try { BlobCache cache = CacheManager.getCache(mContext, BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES, @@ -537,10 +1677,31 @@ class Bookmarker { || (bookmark > (duration - HALF_MINUTE))) { return null; } - return Integer.valueOf(bookmark); + return new BookmarkerInfo(bookmark, duration); } catch (Throwable t) { Log.w(TAG, "getBookmark failed", t); } return null; } } + +class BookmarkerInfo { + public final int mBookmark; + public final int mDuration; + + public BookmarkerInfo(int bookmark, int duration) { + this.mBookmark = bookmark; + this.mDuration = duration; + } + + @Override + public String toString() { + return new StringBuilder() + .append("BookmarkInfo(bookmark=") + .append(mBookmark) + .append(", duration=") + .append(mDuration) + .append(")") + .toString(); + } +} |