blob: c79c44f93e4f251b64445aa5ef1bfc2ed68d6532 [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.gallery3d.app;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
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.audiofx.Virtualizer;
import android.net.Uri;
import android.os.Build;
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.Toast;
import org.codeaurora.gallery.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.IMovieItem;
import org.codeaurora.gallery3d.ext.IMoviePlayer;
import org.codeaurora.gallery3d.ext.MovieUtils;
import org.codeaurora.gallery3d.video.BookmarkEnhance;
import org.codeaurora.gallery3d.video.CodeauroraVideoView;
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.VideoSnapshotExt;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
public class MoviePlayer implements
MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener,
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 = true;
private static final String KEY_VIDEO_POSITION = "video-position";
private static final String KEY_RESUMEABLE_TIME = "resumeable-timeout";
// These are constants in KeyEvent, appearing on API level 11.
private static final int KEYCODE_MEDIA_PLAY = 126;
private static final int KEYCODE_MEDIA_PAUSE = 127;
// Copied from MediaPlaybackService in the Music Player app.
private static final String SERVICECMD = "com.android.music.musicservicecommand";
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";
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 CodeauroraVideoView mVideoView;
private final View mCoverView;
private final View mRootView;
private final Bookmarker mBookmarker;
private final Handler mHandler = new Handler();
private final AudioBecomingNoisyReceiver mAudioBecomingNoisyReceiver;
private final MovieControllerOverlayNew mController;
private final GestureControllerOverlay mGestureController;
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.
private boolean mDragging;
// If the time bar is visible.
private boolean mShowing;
// used to track what type of audio focus loss caused the video to pause
private boolean mPausedByTransientLossOfFocus = false;
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 VideoSnapshotExt mVideoSnapshotExt = new VideoSnapshotExt();
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() {
if (mVideoView.isPlaying()) {
mController.showPlaying();
} else {
mHandler.postDelayed(mPlayingChecker, 250);
}
}
};
private final Runnable mProgressChecker = new Runnable() {
@Override
public void run() {
int pos = setProgress();
mHandler.postDelayed(mProgressChecker, 1000 - (pos % 1000));
}
};
public interface TimerProgress {
void startTimer();
}
private TimerProgress mTimerController = new TimerProgress() {
@Override
public void startTimer() {
mHandler.removeCallbacks(mProgressChecker);
mHandler.post(mProgressChecker);
}
};
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())) {
Log.v(TAG, "Intent.ACTION_SHUTDOWN received");
mActivityContext.finish();
}
}
};
public MoviePlayer(View rootView, final MovieActivity movieActivity,
IMovieItem info, Bundle savedInstance, boolean canReplay) {
mContext = movieActivity.getApplicationContext();
mRootView = rootView;
mVideoView = (CodeauroraVideoView) rootView.findViewById(R.id.surface_view);
mCoverView = rootView.findViewById(R.id.surface_view_cover);
mBookmarker = new Bookmarker(movieActivity);
mController = new MovieControllerOverlayNew(movieActivity);
mGestureController = new GestureControllerOverlay(movieActivity, mController);
((ViewGroup) rootView).addView(mGestureController.getView());
mGestureController.setListener(this);
((ViewGroup) rootView).addView(mController.getView());
mController.setListener(this);
mController.setCanReplay(canReplay);
init(movieActivity, info, canReplay);
mVideoView.setOnErrorListener(this);
mVideoView.setOnCompletionListener(this);
mVideoView.setTimerProgress(mTimerController);
if (mVirtualizer != null) {
mVirtualizer.release();
mVirtualizer = null;
}
Intent ai = movieActivity.getIntent();
boolean virtualize = ai.getBooleanExtra(VIRTUALIZE_EXTRA, false);
if (virtualize) {
int session = mVideoView.getAudioSessionId();
if (session != 0) {
mVirtualizer = new Virtualizer(0, session);
mVirtualizer.setEnabled(true);
} else {
Log.w(TAG, "no audio session to virtualize");
}
}
mVideoView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mController.show();
return true;
}
});
mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer player) {
if (!mVideoView.canSeekForward() || !mVideoView.canSeekBackward()) {
mController.setSeekable(false);
} else {
mController.setSeekable(true);
}
setProgress();
}
});
setOnSystemUiVisibilityChangeListener();
// Hide system UI by default
showSystemUi(false);
mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver();
mAudioBecomingNoisyReceiver.register();
Intent i = new Intent(SERVICECMD);
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);
onRestoreInstanceState(savedInstance);
mHasPaused = true;
doStartVideo(true, mVideoPosition, mVideoLastDuration,false);
mVideoView.start();
mActivityContext.initEffects(mVideoView.getAudioSessionId());
} else {
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 {
doStartVideo(false, 0, 0);
}
}
mScreenModeExt.setScreenMode();
mVideoSnapshotExt.init(mController, mVideoView, isLocalFile());
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void setOnSystemUiVisibilityChangeListener() {
if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_HIDE_NAVIGATION) return;
// When the user touches the screen or uses some hard key, the framework
// will change system ui visibility from invisible to visible. We show
// the media control and enable system UI (e.g. ActionBar) to be visible at this point
mVideoView.setOnSystemUiVisibilityChangeListener(
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);
}
}
});
}
@SuppressWarnings("deprecation")
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void showSystemUi(boolean visible) {
if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) return;
int flag = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
if (!visible) {
// We used the deprecated "STATUS_BAR_HIDDEN" for unbundling
flag |= View.STATUS_BAR_HIDDEN | View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE;
}
mVideoView.setSystemUiVisibility(flag);
}
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 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.mBookmark / 1000)));
builder.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
onCompletion();
}
});
builder.setPositiveButton(
R.string.resume_playing_resume, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 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) {
doStartVideo(true, 0, bookmark.mDuration);
}
});
AlertDialog dialog = builder.create();
dialog.setOnShowListener(new OnShowListener() {
@Override
public void onShow(DialogInterface arg0) {
mIsShowResumingDialog = true;
}
});
dialog.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface arg0) {
mIsShowResumingDialog = false;
}
});
dialog.show();
}
public void setDefaultScreenMode() {
addBackground();
mController.setDefaultScreenMode();
}
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;
}
// 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);
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 (mGestureController != null) {
mGestureController.doOnPause();
}
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) {
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);
}
mTimerController.startTimer();
}
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();
mVirtualizer = null;
}
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() {
int position = mVideoView.getCurrentPosition();
int duration = mVideoView.getDuration();
mController.setTimes(position, duration, 0, 0);
if (mDragging || (!mShowing && !mIsOnlyAudio)) {
return 0;
}
if (mControllerRewindAndForwardExt != null
&& mControllerRewindAndForwardExt.getPlayPauseEanbled()) {
updateRewindAndForwardUI();
}
return position;
}
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.
requestAudioFocus();
String scheme = mMovieItem.getUri().getScheme();
if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme)
|| "https".equalsIgnoreCase(scheme)) {
mController.showLoading();
mController.setLive(isLiveStreaming());
mOverlayExt.setPlayingInfo(isLiveStreaming());
mHandler.removeCallbacks(mPlayingChecker);
mHandler.postDelayed(mPlayingChecker, 250);
} else {
mController.showPlaying();
mController.hide();
}
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();
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 boolean playVideo() {
if (LOG) {
Log.v(TAG, "playVideo()");
}
if (GalleryUtils.isTelephonyCallInProgress(mContext)) {
Log.w(TAG, "CS/CSVT Call is in progress, can't play video");
return false;
}
requestAudioFocus();
mTState = TState.PLAYING;
mVideoView.start();
mController.showPlaying();
setProgress();
return true;
}
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) {
if (LOG) {
Log.v(TAG, "onCompletion() mCanReplay=" + mCanReplay);
}
if (mMovieItem.getError()) {
Log.w(TAG, "error occured, exit the video player!");
mHandler.removeCallbacksAndMessages(null);
mActivityContext.finish();
return;
}
if (mPlayerExt.getLoop()) {
onReplay();
} else { //original logic
mHandler.removeCallbacksAndMessages(null);
mTState = TState.COMPELTED;
if (mCanReplay) {
mController.showEnded();
}
onCompletion();
}
}
public void onCompletion() {
}
// Below are notifications from ControllerOverlay
@Override
public void onPlayPause() {
if (mVideoView.isPlaying()) {
onPauseVideo();
} else {
onPlayVideo();
}
}
private void onPlayVideo() {
boolean result = playVideo();
if (result) {
//set view disabled(play/pause asynchronous processing)
mController.setViewEnabled(true);
if (mControllerRewindAndForwardExt != null) {
mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt
.canStop(), false, false);
}
}
}
private void onPauseVideo() {
if (mVideoView != null && mVideoView.canPause()) {
pauseVideo();
//set view disabled(play/pause asynchronous processing)
mController.setViewEnabled(true);
if (mControllerRewindAndForwardExt != null) {
mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt
.canStop(), false, false);
}
}
}
@Override
public void onSeekStart() {
mDragging = true;
}
@Override
public void onSeekMove(int time) {
if (mVideoView.canSeek()) {
mVideoView.seekTo(time);
}
}
@Override
public void onSeekEnd(int time, int start, int end) {
mDragging = false;
if (mVideoView.canSeek()) {
mVideoView.seekTo(time);
}
}
@Override
public void onSeekComplete(MediaPlayer mp) {
setProgress();
}
@Override
public void onShown() {
addBackground();
mShowing = true;
setProgress();
showSystemUi(true);
}
@Override
public void onHidden() {
mShowing = false;
showSystemUi(false);
}
@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);
} else if ((what == MediaPlayer.MEDIA_INFO_BUFFERING_START
|| what == MediaPlayer.MEDIA_INFO_BUFFERING_END) && mOverlayExt != null) {
mOverlayExt.showBuffering(what);
}
if (mRetryExt.onInfo(mp, what, extra)) {
return true;
}
return false;
}
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
}
@Override
public void onPrepared(MediaPlayer mp) {
if (LOG) {
Log.v(TAG, "onPrepared(" + mp + ")");
}
mCoverView.setVisibility(View.GONE);
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()) {
requestAudioFocus();
mVideoView.start();
}
updateRewindAndForwardUI();
if (LOG) {
Log.v(TAG, "onPrepared() canPause=" + canPause + ", canSeek=" + canSeek);
}
}
public boolean onIsRTSP() {
if (MovieUtils.isRtspStreaming(mMovieItem.getUri(), mMovieItem
.getMimeType())) {
Log.v(TAG, "onIsRTSP() is RTSP");
return true;
}
Log.v(TAG, "onIsRTSP() is not RTSP");
return false;
}
@Override
public void onReplay() {
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.
public boolean onKeyDown(int keyCode, KeyEvent event) {
// Some headsets will fire off 7-10 events on a single click
if (event.getRepeatCount() > 0) {
return isMediaKey(keyCode);
}
switch (keyCode) {
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
if (mVideoView.isPlaying() && mVideoView.canPause()) {
pauseVideo();
} else {
playVideo();
}
return true;
case KEYCODE_MEDIA_PAUSE:
if (mVideoView.isPlaying() && mVideoView.canPause()) {
pauseVideo();
}
return true;
case KEYCODE_MEDIA_PLAY:
if (!mVideoView.isPlaying()) {
playVideo();
}
return true;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_NEXT:
// TODO: Handle next / previous accordingly, for now we're
// just consuming the events.
return true;
}
return false;
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
return isMediaKey(keyCode);
}
public void updateRewindAndForwardUI() {
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
|| keyCode == KeyEvent.KEYCODE_MEDIA_NEXT
|| keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
|| keyCode == KeyEvent.KEYCODE_MEDIA_PLAY
|| 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 {
public void register() {
mContext.registerReceiver(this,
new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
}
public void unregister() {
mContext.unregisterReceiver(this);
}
@Override
public void onReceive(Context context, Intent intent) {
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
|| mStreamingType == STREAMING_HTTP) {
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())) {
ApiHelper.Metadata data = ApiHelper.MediaPlayer.getMetadata(mp,
ApiHelper.MediaPlayer.METADATA_ALL,
ApiHelper.MediaPlayer.BYPASS_METADATA_FILTER);
if (data != null) {
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;
}
// add background for removing ghost image.
private void addBackground() {
if (LOG) {
Log.v(TAG, "addBackground()");
}
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);
}
mVideoView.setDialogShowState(false);
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(ApiHelper.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() {
Log.v(TAG, "ControllerRewindAndForwardExt onStopVideo()");
if (mPlayerExt.canStop()) {
mPlayerExt.stopVideo();
mControllerRewindAndForwardExt.showControllerButtonsView(false,
false, false);
}
}
@Override
public void onRewind() {
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;
Log.v(TAG, "onRewind targetDuration " + targetDuration);
mVideoView.seekTo(targetDuration);
} else {
mControllerRewindAndForwardExt.showControllerButtonsView(mPlayerExt
.canStop(),
false, false);
}
}
@Override
public void onForward() {
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;
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();
}
}
private AudioManager.OnAudioFocusChangeListener mAudioFocusChangeListener =
new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
boolean isTargetPlaying = (mVideoView != null &&
mVideoView.isTargetPlaying());
Log.d(TAG, "AUDIOFOCUS_GAIN isTargetPlaying : " + isTargetPlaying);
if (mPausedByTransientLossOfFocus || isTargetPlaying) {
onPlayVideo();
mPausedByTransientLossOfFocus = false;
}
break;
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
boolean isPlaying = (mVideoView != null && mVideoView.isPlaying());
Log.d(TAG, "AUDIOFOCUS_LOSS isPlaying : " + isPlaying);
if (isPlaying) {
onPauseVideo();
mPausedByTransientLossOfFocus = true;
}
break;
default:
}
}
};
public int requestAudioFocus() {
AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return am.requestAudioFocus(mAudioFocusChangeListener, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
}
public int abandonAudioFocus() {
AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
return am.abandonAudioFocus(mAudioFocusChangeListener);
}
}
class Bookmarker {
private static final String TAG = "Bookmarker";
private static final String BOOKMARK_CACHE_FILE = "bookmark";
private static final int BOOKMARK_CACHE_MAX_ENTRIES = 100;
private static final int BOOKMARK_CACHE_MAX_BYTES = 10 * 1024;
private static final int BOOKMARK_CACHE_VERSION = 1;
private static final int HALF_MINUTE = 30 * 1000;
private static final int TWO_MINUTES = 4 * HALF_MINUTE;
private final Context mContext;
public Bookmarker(Context context) {
mContext = context;
}
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);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeUTF(uri.toString());
dos.writeInt(bookmark);
dos.writeInt(Math.abs(duration));
dos.flush();
cache.insert(uri.hashCode(), bos.toByteArray());
} catch (Throwable t) {
Log.w(TAG, "setBookmark failed", t);
}
}
public BookmarkerInfo getBookmark(Uri uri) {
try {
BlobCache cache = CacheManager.getCache(mContext,
BOOKMARK_CACHE_FILE, BOOKMARK_CACHE_MAX_ENTRIES,
BOOKMARK_CACHE_MAX_BYTES, BOOKMARK_CACHE_VERSION);
byte[] data = cache.lookup(uri.hashCode());
if (data == null) return null;
DataInputStream dis = new DataInputStream(
new ByteArrayInputStream(data));
String uriString = DataInputStream.readUTF(dis);
int bookmark = dis.readInt();
int duration = dis.readInt();
if (!uriString.equals(uri.toString())) {
return null;
}
if ((bookmark < HALF_MINUTE) || (duration < TWO_MINUTES)
|| (bookmark > (duration - HALF_MINUTE))) {
return null;
}
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();
}
}