| /* |
| * 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.Metadata; |
| import android.media.audiofx.AudioEffect; |
| 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.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, |
| 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"; |
| 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 CodeauroraVideoView mVideoView; |
| private final View mRootView; |
| private final Bookmarker mBookmarker; |
| private final Handler mHandler = new Handler(); |
| private final AudioBecomingNoisyReceiver mAudioBecomingNoisyReceiver; |
| private final MovieControllerOverlay mController; |
| |
| 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; |
| |
| 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() { |
| 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)); |
| } |
| }; |
| |
| 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())) { |
| 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); |
| mBookmarker = new Bookmarker(movieActivity); |
| |
| 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); |
| |
| 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(); |
| } |
| }); |
| |
| // The SurfaceView is transparent before drawing the first frame. |
| // This makes the UI flashing when open a video. (black -> old screen |
| // -> video) However, we have no way to know the timing of the first |
| // frame. So, we hide the VideoView for a while to make sure the |
| // video has been drawn on it. |
| mVideoView.postDelayed(new Runnable() { |
| @Override |
| public void run() { |
| mVideoView.setVisibility(View.VISIBLE); |
| } |
| }, BLACK_TIMEOUT); |
| |
| 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(); |
| } |
| |
| @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; |
| } |
| 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(); |
| 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; |
| } |
| |
| // 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 (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) { |
| //M: same as launch case to delay transparent. |
| mVideoView.removeCallbacks(mDelayVideoRunnable); |
| mVideoView.postDelayed(mDelayVideoRunnable, BLACK_TIMEOUT); |
| |
| 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(); |
| 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() { |
| 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 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 = 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 { |
| 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(); |
| 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) { |
| 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() { |
| } |
| |
| // Below are notifications from ControllerOverlay |
| @Override |
| public void onPlayPause() { |
| if (mVideoView.isPlaying()) { |
| 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); |
| } |
| } |
| } |
| |
| @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); |
| 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())) { |
| 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) { |
| 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() { |
| 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(); |
| } |
| } |
| } |
| |
| 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(); |
| } |
| } |