diff options
-rw-r--r-- | gallerycommon/src/com/android/gallery3d/common/ApiHelper.java | 3 | ||||
-rw-r--r-- | src/com/android/gallery3d/app/CommonControllerOverlay.java | 333 | ||||
-rw-r--r-- | src/com/android/gallery3d/app/ControllerOverlay.java | 5 | ||||
-rw-r--r-- | src/com/android/gallery3d/app/MovieControllerOverlay.java | 294 | ||||
-rw-r--r-- | src/com/android/gallery3d/app/MoviePlayer.java | 4 | ||||
-rw-r--r-- | src/com/android/gallery3d/app/PhotoPage.java | 5 | ||||
-rw-r--r-- | src/com/android/gallery3d/app/TimeBar.java | 393 | ||||
-rw-r--r-- | src/com/android/gallery3d/app/TrimControllerOverlay.java | 104 | ||||
-rw-r--r-- | src/com/android/gallery3d/app/TrimTimeBar.java | 290 | ||||
-rw-r--r-- | src/com/android/gallery3d/app/TrimVideo.java | 254 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/MenuExecutor.java | 4 |
11 files changed, 1210 insertions, 479 deletions
diff --git a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java index 68aa50dda..20d7e1dc6 100644 --- a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java +++ b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java @@ -160,6 +160,9 @@ public class ApiHelper { public static final boolean CAN_USE_FLAG_SECURE = Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; + public static final boolean HAS_MEDIA_MUXER = + Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1; + public static int getIntFieldIfExists(Class<?> klass, String fieldName, Class<?> obj, int defaultVal) { try { diff --git a/src/com/android/gallery3d/app/CommonControllerOverlay.java b/src/com/android/gallery3d/app/CommonControllerOverlay.java new file mode 100644 index 000000000..ab43dada5 --- /dev/null +++ b/src/com/android/gallery3d/app/CommonControllerOverlay.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2012 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.content.Context; +import android.graphics.Rect; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.android.gallery3d.R; + +/** + * The common playback controller for the Movie Player or Video Trimming. + */ +public abstract class CommonControllerOverlay extends FrameLayout implements + ControllerOverlay, + OnClickListener, + TimeBar.Listener { + + protected enum State { + PLAYING, + PAUSED, + ENDED, + ERROR, + LOADING + } + + private static final float ERROR_MESSAGE_RELATIVE_PADDING = 1.0f / 6; + + protected Listener mListener; + + protected final View mBackground; + protected TimeBar mTimeBar; + + protected View mMainView; + protected final LinearLayout mLoadingView; + protected final TextView mErrorView; + protected final ImageView mPlayPauseReplayView; + + protected State mState; + + protected boolean mCanReplay = true; + + public CommonControllerOverlay(Context context) { + super(context); + + mState = State.LOADING; + // TODO: Move the following layout code into xml file. + LayoutParams wrapContent = + new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + LayoutParams matchParent = + new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + + mBackground = new View(context); + mBackground.setBackgroundColor(context.getResources().getColor(R.color.darker_transparent)); + addView(mBackground, matchParent); + + // Depending on the usage, the timeBar can show a single scrubber, or + // multiple ones for trimming. + createTimeBar(context); + addView(mTimeBar, wrapContent); + + mLoadingView = new LinearLayout(context); + mLoadingView.setOrientation(LinearLayout.VERTICAL); + mLoadingView.setGravity(Gravity.CENTER_HORIZONTAL); + ProgressBar spinner = new ProgressBar(context); + spinner.setIndeterminate(true); + mLoadingView.addView(spinner, wrapContent); + TextView loadingText = createOverlayTextView(context); + loadingText.setText(R.string.loading_video); + mLoadingView.addView(loadingText, wrapContent); + addView(mLoadingView, wrapContent); + + mPlayPauseReplayView = new ImageView(context); + mPlayPauseReplayView.setImageResource(R.drawable.ic_vidcontrol_play); + mPlayPauseReplayView.setBackgroundResource(R.drawable.bg_vidcontrol); + mPlayPauseReplayView.setScaleType(ScaleType.CENTER); + mPlayPauseReplayView.setFocusable(true); + mPlayPauseReplayView.setClickable(true); + mPlayPauseReplayView.setOnClickListener(this); + addView(mPlayPauseReplayView, wrapContent); + + mErrorView = createOverlayTextView(context); + addView(mErrorView, matchParent); + + RelativeLayout.LayoutParams params = + new RelativeLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + setLayoutParams(params); + hide(); + } + + abstract protected void createTimeBar(Context context); + + private TextView createOverlayTextView(Context context) { + TextView view = new TextView(context); + view.setGravity(Gravity.CENTER); + view.setTextColor(0xFFFFFFFF); + view.setPadding(0, 15, 0, 15); + return view; + } + + @Override + public void setListener(Listener listener) { + this.mListener = listener; + } + + @Override + public void setCanReplay(boolean canReplay) { + this.mCanReplay = canReplay; + } + + @Override + public View getView() { + return this; + } + + @Override + public void showPlaying() { + mState = State.PLAYING; + showMainView(mPlayPauseReplayView); + } + + @Override + public void showPaused() { + mState = State.PAUSED; + showMainView(mPlayPauseReplayView); + } + + @Override + public void showEnded() { + mState = State.ENDED; + showMainView(mPlayPauseReplayView); + } + + @Override + public void showLoading() { + mState = State.LOADING; + showMainView(mLoadingView); + } + + @Override + public void showErrorMessage(String message) { + mState = State.ERROR; + int padding = (int) (getMeasuredWidth() * ERROR_MESSAGE_RELATIVE_PADDING); + mErrorView.setPadding( + padding, mErrorView.getPaddingTop(), padding, mErrorView.getPaddingBottom()); + mErrorView.setText(message); + showMainView(mErrorView); + } + + @Override + public void setTimes(int currentTime, int totalTime, + int trimStartTime, int trimEndTime) { + mTimeBar.setTime(currentTime, totalTime, trimStartTime, trimEndTime); + } + + public void hide() { + mPlayPauseReplayView.setVisibility(View.INVISIBLE); + mLoadingView.setVisibility(View.INVISIBLE); + mBackground.setVisibility(View.INVISIBLE); + mTimeBar.setVisibility(View.INVISIBLE); + setVisibility(View.INVISIBLE); + setFocusable(true); + requestFocus(); + } + + private void showMainView(View view) { + mMainView = view; + mErrorView.setVisibility(mMainView == mErrorView ? View.VISIBLE : View.INVISIBLE); + mLoadingView.setVisibility(mMainView == mLoadingView ? View.VISIBLE : View.INVISIBLE); + mPlayPauseReplayView.setVisibility( + mMainView == mPlayPauseReplayView ? View.VISIBLE : View.INVISIBLE); + show(); + } + + @Override + public void show() { + updateViews(); + setVisibility(View.VISIBLE); + setFocusable(false); + } + + @Override + public void onClick(View view) { + if (mListener != null) { + if (view == mPlayPauseReplayView) { + if (mState == State.ENDED) { + if (mCanReplay) { + mListener.onReplay(); + } + } else if (mState == State.PAUSED || mState == State.PLAYING) { + mListener.onPlayPause(); + } + } + } + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (super.onTouchEvent(event)) { + return true; + } + return false; + } + + // The paddings of 4 sides which covered by system components. E.g. + // +-----------------+\ + // | Action Bar | insets.top + // +-----------------+/ + // | | + // | Content Area | insets.right = insets.left = 0 + // | | + // +-----------------+\ + // | Navigation Bar | insets.bottom + // +-----------------+/ + // Please see View.fitSystemWindows() for more details. + private final Rect mWindowInsets = new Rect(); + + @Override + protected boolean fitSystemWindows(Rect insets) { + // We don't set the paddings of this View, otherwise, + // the content will get cropped outside window + mWindowInsets.set(insets); + return true; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + Rect insets = mWindowInsets; + int pl = insets.left; // the left paddings + int pr = insets.right; + int pt = insets.top; + int pb = insets.bottom; + + int h = bottom - top; + int w = right - left; + boolean error = mErrorView.getVisibility() == View.VISIBLE; + + int y = h - pb; + // Put both TimeBar and Background just above the bottom system + // component. + // But extend the background to the width of the screen, since we don't + // care if it will be covered by a system component and it looks better. + mBackground.layout(0, y - mTimeBar.getBarHeight(), w, y); + mTimeBar.layout(pl, y - mTimeBar.getPreferredHeight(), w - pr, y); + + // Needed, otherwise the framework will not re-layout in case only the + // padding is changed + mTimeBar.requestLayout(); + + // Put the play/pause/next/ previous button in the center of the screen + layoutCenteredView(mPlayPauseReplayView, 0, 0, w, h); + + if (mMainView != null) { + layoutCenteredView(mMainView, 0, 0, w, h); + } + } + + private void layoutCenteredView(View view, int l, int t, int r, int b) { + int cw = view.getMeasuredWidth(); + int ch = view.getMeasuredHeight(); + int cl = (r - l - cw) / 2; + int ct = (b - t - ch) / 2; + view.layout(cl, ct, cl + cw, ct + ch); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + measureChildren(widthMeasureSpec, heightMeasureSpec); + } + + protected void updateViews() { + mBackground.setVisibility(View.VISIBLE); + mTimeBar.setVisibility(View.VISIBLE); + mPlayPauseReplayView.setImageResource( + mState == State.PAUSED ? R.drawable.ic_vidcontrol_play : + mState == State.PLAYING ? R.drawable.ic_vidcontrol_pause : + R.drawable.ic_vidcontrol_reload); + mPlayPauseReplayView.setVisibility( + (mState != State.LOADING && mState != State.ERROR && + !(mState == State.ENDED && !mCanReplay)) + ? View.VISIBLE : View.GONE); + requestLayout(); + } + + // TimeBar listener + + @Override + public void onScrubbingStart() { + mListener.onSeekStart(); + } + + @Override + public void onScrubbingMove(int time) { + mListener.onSeekMove(time); + } + + @Override + public void onScrubbingEnd(int time, int trimStartTime, int trimEndTime) { + mListener.onSeekEnd(time, trimStartTime, trimEndTime); + } +} diff --git a/src/com/android/gallery3d/app/ControllerOverlay.java b/src/com/android/gallery3d/app/ControllerOverlay.java index 03472134d..078f59e28 100644 --- a/src/com/android/gallery3d/app/ControllerOverlay.java +++ b/src/com/android/gallery3d/app/ControllerOverlay.java @@ -24,7 +24,7 @@ public interface ControllerOverlay { void onPlayPause(); void onSeekStart(); void onSeekMove(int time); - void onSeekEnd(int time); + void onSeekEnd(int time, int trimStartTime, int trimEndTime); void onShown(); void onHidden(); void onReplay(); @@ -51,5 +51,6 @@ public interface ControllerOverlay { void showErrorMessage(String message); - void setTimes(int currentTime, int totalTime); + void setTimes(int currentTime, int totalTime, + int trimStartTime, int trimEndTime); } diff --git a/src/com/android/gallery3d/app/MovieControllerOverlay.java b/src/com/android/gallery3d/app/MovieControllerOverlay.java index 4781a1b8a..f01e619c6 100644 --- a/src/com/android/gallery3d/app/MovieControllerOverlay.java +++ b/src/com/android/gallery3d/app/MovieControllerOverlay.java @@ -17,105 +17,30 @@ package com.android.gallery3d.app; import android.content.Context; -import android.graphics.Rect; import android.os.Handler; -import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; -import android.view.View.OnClickListener; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.view.animation.AnimationUtils; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.ImageView.ScaleType; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; -import android.widget.TextView; - import com.android.gallery3d.R; /** * The playback controller for the Movie Player. */ -public class MovieControllerOverlay extends FrameLayout implements - ControllerOverlay, - OnClickListener, - AnimationListener, - TimeBar.Listener { - - private enum State { - PLAYING, - PAUSED, - ENDED, - ERROR, - LOADING - } - - private static final float ERROR_MESSAGE_RELATIVE_PADDING = 1.0f / 6; - - private Listener listener; +public class MovieControllerOverlay extends CommonControllerOverlay implements + AnimationListener { - private final View background; - private final TimeBar timeBar; - - private View mainView; - private final LinearLayout loadingView; - private final TextView errorView; - private final ImageView playPauseReplayView; + private boolean hidden; private final Handler handler; private final Runnable startHidingRunnable; private final Animation hideAnimation; - private State state; - - private boolean hidden; - - private boolean canReplay = true; - public MovieControllerOverlay(Context context) { super(context); - state = State.LOADING; - - LayoutParams wrapContent = - new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - LayoutParams matchParent = - new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - - background = new View(context); - background.setBackgroundColor(context.getResources().getColor(R.color.darker_transparent)); - addView(background, matchParent); - - timeBar = new TimeBar(context, this); - addView(timeBar, wrapContent); - - loadingView = new LinearLayout(context); - loadingView.setOrientation(LinearLayout.VERTICAL); - loadingView.setGravity(Gravity.CENTER_HORIZONTAL); - ProgressBar spinner = new ProgressBar(context); - spinner.setIndeterminate(true); - loadingView.addView(spinner, wrapContent); - TextView loadingText = createOverlayTextView(context); - loadingText.setText(R.string.loading_video); - loadingView.addView(loadingText, wrapContent); - addView(loadingView, wrapContent); - - playPauseReplayView = new ImageView(context); - playPauseReplayView.setImageResource(R.drawable.ic_vidcontrol_play); - playPauseReplayView.setBackgroundResource(R.drawable.bg_vidcontrol); - playPauseReplayView.setScaleType(ScaleType.CENTER); - playPauseReplayView.setFocusable(true); - playPauseReplayView.setClickable(true); - playPauseReplayView.setOnClickListener(this); - addView(playPauseReplayView, wrapContent); - - errorView = createOverlayTextView(context); - addView(errorView, matchParent); - handler = new Handler(); startHidingRunnable = new Runnable() { @Override @@ -127,123 +52,47 @@ public class MovieControllerOverlay extends FrameLayout implements hideAnimation = AnimationUtils.loadAnimation(context, R.anim.player_out); hideAnimation.setAnimationListener(this); - RelativeLayout.LayoutParams params = - new RelativeLayout.LayoutParams( - LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - setLayoutParams(params); hide(); } - private TextView createOverlayTextView(Context context) { - TextView view = new TextView(context); - view.setGravity(Gravity.CENTER); - view.setTextColor(0xFFFFFFFF); - view.setPadding(0, 15, 0, 15); - return view; - } - - @Override - public void setListener(Listener listener) { - this.listener = listener; - } - - @Override - public void setCanReplay(boolean canReplay) { - this.canReplay = canReplay; - } - - @Override - public View getView() { - return this; - } - - @Override - public void showPlaying() { - state = State.PLAYING; - showMainView(playPauseReplayView); - } - - @Override - public void showPaused() { - state = State.PAUSED; - showMainView(playPauseReplayView); - } - @Override - public void showEnded() { - state = State.ENDED; - showMainView(playPauseReplayView); + protected void createTimeBar(Context context) { + mTimeBar = new TimeBar(context, this); } @Override - public void showLoading() { - state = State.LOADING; - showMainView(loadingView); - } - - @Override - public void showErrorMessage(String message) { - state = State.ERROR; - int padding = (int) (getMeasuredWidth() * ERROR_MESSAGE_RELATIVE_PADDING); - errorView.setPadding( - padding, errorView.getPaddingTop(), padding, errorView.getPaddingBottom()); - errorView.setText(message); - showMainView(errorView); - } - - @Override - public void setTimes(int currentTime, int totalTime) { - timeBar.setTime(currentTime, totalTime); - } - public void hide() { boolean wasHidden = hidden; hidden = true; - playPauseReplayView.setVisibility(View.INVISIBLE); - loadingView.setVisibility(View.INVISIBLE); - background.setVisibility(View.INVISIBLE); - timeBar.setVisibility(View.INVISIBLE); - setVisibility(View.INVISIBLE); - setFocusable(true); - requestFocus(); - if (listener != null && wasHidden != hidden) { - listener.onHidden(); + super.hide(); + if (mListener != null && wasHidden != hidden) { + mListener.onHidden(); } } - private void showMainView(View view) { - mainView = view; - errorView.setVisibility(mainView == errorView ? View.VISIBLE : View.INVISIBLE); - loadingView.setVisibility(mainView == loadingView ? View.VISIBLE : View.INVISIBLE); - playPauseReplayView.setVisibility( - mainView == playPauseReplayView ? View.VISIBLE : View.INVISIBLE); - show(); - } @Override public void show() { boolean wasHidden = hidden; hidden = false; - updateViews(); - setVisibility(View.VISIBLE); - setFocusable(false); - if (listener != null && wasHidden != hidden) { - listener.onShown(); + super.show(); + if (mListener != null && wasHidden != hidden) { + mListener.onShown(); } maybeStartHiding(); } private void maybeStartHiding() { cancelHiding(); - if (state == State.PLAYING) { + if (mState == State.PLAYING) { handler.postDelayed(startHidingRunnable, 2500); } } private void startHiding() { - startHideAnimation(background); - startHideAnimation(timeBar); - startHideAnimation(playPauseReplayView); + startHideAnimation(mBackground); + startHideAnimation(mTimeBar); + startHideAnimation(mPlayPauseReplayView); } private void startHideAnimation(View view) { @@ -254,9 +103,9 @@ public class MovieControllerOverlay extends FrameLayout implements private void cancelHiding() { handler.removeCallbacks(startHidingRunnable); - background.setAnimation(null); - timeBar.setAnimation(null); - playPauseReplayView.setAnimation(null); + mBackground.setAnimation(null); + mTimeBar.setAnimation(null); + mPlayPauseReplayView.setAnimation(null); } @Override @@ -275,21 +124,6 @@ public class MovieControllerOverlay extends FrameLayout implements } @Override - public void onClick(View view) { - if (listener != null) { - if (view == playPauseReplayView) { - if (state == State.ENDED) { - if (canReplay) { - listener.onReplay(); - } - } else if (state == State.PAUSED || state == State.PLAYING) { - listener.onPlayPause(); - } - } - } - } - - @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (hidden) { show(); @@ -310,8 +144,8 @@ public class MovieControllerOverlay extends FrameLayout implements switch (event.getAction()) { case MotionEvent.ACTION_DOWN: cancelHiding(); - if (state == State.PLAYING || state == State.PAUSED) { - listener.onPlayPause(); + if (mState == State.PLAYING || mState == State.PAUSED) { + mListener.onPlayPause(); } break; case MotionEvent.ACTION_UP: @@ -321,88 +155,12 @@ public class MovieControllerOverlay extends FrameLayout implements return true; } - // The paddings of 4 sides which covered by system components. E.g. - // +-----------------+\ - // | Action Bar | insets.top - // +-----------------+/ - // | | - // | Content Area | insets.right = insets.left = 0 - // | | - // +-----------------+\ - // | Navigation Bar | insets.bottom - // +-----------------+/ - // Please see View.fitSystemWindows() for more details. - private final Rect mWindowInsets = new Rect(); - - @Override - protected boolean fitSystemWindows(Rect insets) { - // We don't set the paddings of this View, otherwise, - // the content will get cropped outside window - mWindowInsets.set(insets); - return true; - } - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - Rect insets = mWindowInsets; - int pl = insets.left; // the left paddings - int pr = insets.right; - int pt = insets.top; - int pb = insets.bottom; - - int h = bottom - top; - int w = right - left; - boolean error = errorView.getVisibility() == View.VISIBLE; - - int y = h - pb; - // Put both TimeBar and Background just above the bottom system - // component. - // But extend the background to the width of the screen, since we don't - // care if it will be covered by a system component and it looks better. - background.layout(0, y - timeBar.getBarHeight(), w, y); - timeBar.layout(pl, y - timeBar.getPreferredHeight(), w - pr, y); - - // Needed, otherwise the framework will not re-layout in case only the - // padding is changed - timeBar.requestLayout(); - - // Put the play/pause/next/ previous button in the center of the screen - layoutCenteredView(playPauseReplayView, 0, 0, w, h); - - if (mainView != null) { - layoutCenteredView(mainView, 0, 0, w, h); - } - } - - private void layoutCenteredView(View view, int l, int t, int r, int b) { - int cw = view.getMeasuredWidth(); - int ch = view.getMeasuredHeight(); - int cl = (r - l - cw) / 2; - int ct = (b - t - ch) / 2; - view.layout(cl, ct, cl + cw, ct + ch); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - measureChildren(widthMeasureSpec, heightMeasureSpec); - } - - private void updateViews() { + protected void updateViews() { if (hidden) { return; } - background.setVisibility(View.VISIBLE); - timeBar.setVisibility(View.VISIBLE); - playPauseReplayView.setImageResource( - state == State.PAUSED ? R.drawable.ic_vidcontrol_play : - state == State.PLAYING ? R.drawable.ic_vidcontrol_pause : - R.drawable.ic_vidcontrol_reload); - playPauseReplayView.setVisibility( - (state != State.LOADING && state != State.ERROR && - !(state == State.ENDED && !canReplay)) - ? View.VISIBLE : View.GONE); - requestLayout(); + super.updateViews(); } // TimeBar listener @@ -410,18 +168,18 @@ public class MovieControllerOverlay extends FrameLayout implements @Override public void onScrubbingStart() { cancelHiding(); - listener.onSeekStart(); + super.onScrubbingStart(); } @Override public void onScrubbingMove(int time) { cancelHiding(); - listener.onSeekMove(time); + super.onScrubbingMove(time); } @Override - public void onScrubbingEnd(int time) { + public void onScrubbingEnd(int time, int trimStartTime, int trimEndTime) { maybeStartHiding(); - listener.onSeekEnd(time); + super.onScrubbingEnd(time, trimStartTime, trimEndTime); } } diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java index 0cf361c71..9d229f088 100644 --- a/src/com/android/gallery3d/app/MoviePlayer.java +++ b/src/com/android/gallery3d/app/MoviePlayer.java @@ -304,7 +304,7 @@ public class MoviePlayer implements } int position = mVideoView.getCurrentPosition(); int duration = mVideoView.getDuration(); - mController.setTimes(position, duration); + mController.setTimes(position, duration, 0, 0); return position; } @@ -376,7 +376,7 @@ public class MoviePlayer implements } @Override - public void onSeekEnd(int time) { + public void onSeekEnd(int time, int start, int end) { mDragging = false; mVideoView.seekTo(time); setProgress(); diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java index 45709955e..6b7459c8d 100644 --- a/src/com/android/gallery3d/app/PhotoPage.java +++ b/src/com/android/gallery3d/app/PhotoPage.java @@ -94,6 +94,7 @@ public class PhotoPage extends ActivityState implements private static final int REQUEST_CROP_PICASA = 3; private static final int REQUEST_EDIT = 4; private static final int REQUEST_PLAY_VIDEO = 5; + private static final int REQUEST_TRIM = 6; public static final String KEY_MEDIA_SET_PATH = "media-set-path"; public static final String KEY_MEDIA_ITEM_PATH = "media-item-path"; @@ -711,7 +712,9 @@ public class PhotoPage extends ActivityState implements return true; } case R.id.action_trim: { - // TODO: Add trimming activity here. + Intent intent = new Intent(mActivity, TrimVideo.class); + intent.setData(manager.getContentUri(path)); + mActivity.startActivityForResult(intent, REQUEST_TRIM); return true; } case R.id.action_edit: { diff --git a/src/com/android/gallery3d/app/TimeBar.java b/src/com/android/gallery3d/app/TimeBar.java index f52879732..b7ffbb0de 100644 --- a/src/com/android/gallery3d/app/TimeBar.java +++ b/src/com/android/gallery3d/app/TimeBar.java @@ -30,250 +30,233 @@ import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; /** - * The time bar view, which includes the current and total time, the progress bar, - * and the scrubber. + * The time bar view, which includes the current and total time, the progress + * bar, and the scrubber. */ public class TimeBar extends View { - public interface Listener { - void onScrubbingStart(); - void onScrubbingMove(int time); - void onScrubbingEnd(int time); - } + public interface Listener { + void onScrubbingStart(); - // Padding around the scrubber to increase its touch target - private static final int SCRUBBER_PADDING_IN_DP = 10; + void onScrubbingMove(int time); - // The total padding, top plus bottom - private static final int V_PADDING_IN_DP = 30; + void onScrubbingEnd(int time, int start, int end); + } - private static final int TEXT_SIZE_IN_DP = 14; + // Padding around the scrubber to increase its touch target + private static final int SCRUBBER_PADDING_IN_DP = 10; - private final Listener listener; + // The total padding, top plus bottom + private static final int V_PADDING_IN_DP = 30; - // the bars we use for displaying the progress - private final Rect progressBar; - private final Rect playedBar; + private static final int TEXT_SIZE_IN_DP = 14; - private final Paint progressPaint; - private final Paint playedPaint; - private final Paint timeTextPaint; + protected final Listener mListener; - private final Bitmap scrubber; - private final int scrubberPadding; // adds some touch tolerance around the scrubber + // the bars we use for displaying the progress + protected final Rect mProgressBar; + protected final Rect mPlayedBar; - private int scrubberLeft; - private int scrubberTop; - private int scrubberCorrection; - private boolean scrubbing; - private boolean showTimes; - private boolean showScrubber; + private final Paint mProgressPaint; + private final Paint mPlayedPaint; + private final Paint mTimeTextPaint; - private int totalTime; - private int currentTime; + protected final Bitmap mScrubber; + protected int mScrubberPadding; // adds some touch tolerance around the + // scrubber - private final Rect timeBounds; + protected int mScrubberLeft; + protected int mScrubberTop; + protected int mScrubberCorrection; + protected boolean mScrubbing; + protected boolean mShowTimes; + protected boolean mShowScrubber; - private int vPaddingInPx; + protected int mTotalTime; + protected int mCurrentTime; - public TimeBar(Context context, Listener listener) { - super(context); - this.listener = Utils.checkNotNull(listener); + protected final Rect mTimeBounds; - showTimes = true; - showScrubber = true; + protected int mVPaddingInPx; - progressBar = new Rect(); - playedBar = new Rect(); + public TimeBar(Context context, Listener listener) { + super(context); + mListener = Utils.checkNotNull(listener); - progressPaint = new Paint(); - progressPaint.setColor(0xFF808080); - playedPaint = new Paint(); - playedPaint.setColor(0xFFFFFFFF); + mShowTimes = true; + mShowScrubber = true; - DisplayMetrics metrics = context.getResources().getDisplayMetrics(); - float textSizeInPx = metrics.density * TEXT_SIZE_IN_DP; - timeTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - timeTextPaint.setColor(0xFFCECECE); - timeTextPaint.setTextSize(textSizeInPx); - timeTextPaint.setTextAlign(Paint.Align.CENTER); + mProgressBar = new Rect(); + mPlayedBar = new Rect(); - timeBounds = new Rect(); - timeTextPaint.getTextBounds("0:00:00", 0, 7, timeBounds); + mProgressPaint = new Paint(); + mProgressPaint.setColor(0xFF808080); + mPlayedPaint = new Paint(); + mPlayedPaint.setColor(0xFFFFFFFF); - scrubber = BitmapFactory.decodeResource(getResources(), R.drawable.scrubber_knob); - scrubberPadding = (int) (metrics.density * SCRUBBER_PADDING_IN_DP); + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + float textSizeInPx = metrics.density * TEXT_SIZE_IN_DP; + mTimeTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mTimeTextPaint.setColor(0xFFCECECE); + mTimeTextPaint.setTextSize(textSizeInPx); + mTimeTextPaint.setTextAlign(Paint.Align.CENTER); - vPaddingInPx = (int) (metrics.density * V_PADDING_IN_DP); - } + mTimeBounds = new Rect(); + mTimeTextPaint.getTextBounds("0:00:00", 0, 7, mTimeBounds); - private void update() { - playedBar.set(progressBar); + mScrubber = BitmapFactory.decodeResource(getResources(), R.drawable.scrubber_knob); + mScrubberPadding = (int) (metrics.density * SCRUBBER_PADDING_IN_DP); - if (totalTime > 0) { - playedBar.right = - playedBar.left + (int) ((progressBar.width() * (long) currentTime) / totalTime); - } else { - playedBar.right = progressBar.left; + mVPaddingInPx = (int) (metrics.density * V_PADDING_IN_DP); } - if (!scrubbing) { - scrubberLeft = playedBar.right - scrubber.getWidth() / 2; + private void update() { + mPlayedBar.set(mProgressBar); + + if (mTotalTime > 0) { + mPlayedBar.right = + mPlayedBar.left + (int) ((mProgressBar.width() * (long) mCurrentTime) / mTotalTime); + } else { + mPlayedBar.right = mProgressBar.left; + } + + if (!mScrubbing) { + mScrubberLeft = mPlayedBar.right - mScrubber.getWidth() / 2; + } + invalidate(); } - invalidate(); - } - - /** - * @return the preferred height of this view, including invisible padding - */ - public int getPreferredHeight() { - return timeBounds.height() + vPaddingInPx + scrubberPadding; - } - - /** - * @return the height of the time bar, excluding invisible padding - */ - public int getBarHeight() { - return timeBounds.height() + vPaddingInPx; - } - - public void setTime(int currentTime, int totalTime) { - if (this.currentTime == currentTime && this.totalTime == totalTime) { - return; + + /** + * @return the preferred height of this view, including invisible padding + */ + public int getPreferredHeight() { + return mTimeBounds.height() + mVPaddingInPx + mScrubberPadding; } - this.currentTime = currentTime; - this.totalTime = totalTime; - update(); - } - - public void setShowTimes(boolean showTimes) { - this.showTimes = showTimes; - requestLayout(); - } - - public void resetTime() { - setTime(0, 0); - } - - public void setShowScrubber(boolean showScrubber) { - this.showScrubber = showScrubber; - if (!showScrubber && scrubbing) { - listener.onScrubbingEnd(getScrubberTime()); - scrubbing = false; + + /** + * @return the height of the time bar, excluding invisible padding + */ + public int getBarHeight() { + return mTimeBounds.height() + mVPaddingInPx; } - requestLayout(); - } - - private boolean inScrubber(float x, float y) { - int scrubberRight = scrubberLeft + scrubber.getWidth(); - int scrubberBottom = scrubberTop + scrubber.getHeight(); - return scrubberLeft - scrubberPadding < x && x < scrubberRight + scrubberPadding - && scrubberTop - scrubberPadding < y && y < scrubberBottom + scrubberPadding; - } - - private void clampScrubber() { - int half = scrubber.getWidth() / 2; - int max = progressBar.right - half; - int min = progressBar.left - half; - scrubberLeft = Math.min(max, Math.max(min, scrubberLeft)); - } - - private int getScrubberTime() { - return (int) ((long) (scrubberLeft + scrubber.getWidth() / 2 - progressBar.left) - * totalTime / progressBar.width()); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - int w = r - l; - int h = b - t; - if (!showTimes && !showScrubber) { - progressBar.set(0, 0, w, h); - } else { - int margin = scrubber.getWidth() / 3; - if (showTimes) { - margin += timeBounds.width(); - } - int progressY = (h + scrubberPadding) / 2; - scrubberTop = progressY - scrubber.getHeight() / 2 + 1; - progressBar.set( - getPaddingLeft() + margin, progressY, - w - getPaddingRight() - margin, progressY + 4); + + public void setTime(int currentTime, int totalTime, + int trimStartTime, int trimEndTime) { + if (mCurrentTime == currentTime && mTotalTime == totalTime) { + return; + } + mCurrentTime = currentTime; + mTotalTime = totalTime; + update(); } - update(); - } - @Override - public void draw(Canvas canvas) { - super.draw(canvas); + private boolean inScrubber(float x, float y) { + int scrubberRight = mScrubberLeft + mScrubber.getWidth(); + int scrubberBottom = mScrubberTop + mScrubber.getHeight(); + return mScrubberLeft - mScrubberPadding < x && x < scrubberRight + mScrubberPadding + && mScrubberTop - mScrubberPadding < y && y < scrubberBottom + mScrubberPadding; + } - // draw progress bars - canvas.drawRect(progressBar, progressPaint); - canvas.drawRect(playedBar, playedPaint); + protected void clampScrubber() { + int half = mScrubber.getWidth() / 2; + int max = mProgressBar.right - half; + int min = mProgressBar.left - half; + mScrubberLeft = Math.min(max, Math.max(min, mScrubberLeft)); + } - // draw scrubber and timers - if (showScrubber) { - canvas.drawBitmap(scrubber, scrubberLeft, scrubberTop, null); + protected int getScrubberTime() { + return (int) ((long) (mScrubberLeft + mScrubber.getWidth() / 2 - mProgressBar.left) + * mTotalTime / mProgressBar.width()); } - if (showTimes) { - canvas.drawText( - stringForTime(currentTime), - timeBounds.width() / 2 + getPaddingLeft(), - timeBounds.height() + vPaddingInPx / 2 + scrubberPadding + 1, - timeTextPaint); - canvas.drawText( - stringForTime(totalTime), - getWidth() - getPaddingRight() - timeBounds.width() / 2, - timeBounds.height() + vPaddingInPx / 2 + scrubberPadding + 1, - timeTextPaint); + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int w = r - l; + int h = b - t; + if (!mShowTimes && !mShowScrubber) { + mProgressBar.set(0, 0, w, h); + } else { + int margin = mScrubber.getWidth() / 3; + if (mShowTimes) { + margin += mTimeBounds.width(); + } + int progressY = (h + mScrubberPadding) / 2; + mScrubberTop = progressY - mScrubber.getHeight() / 2 + 1; + mProgressBar.set( + getPaddingLeft() + margin, progressY, + w - getPaddingRight() - margin, progressY + 4); + } + update(); } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - - if (showScrubber) { - int x = (int) event.getX(); - int y = (int) event.getY(); - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: { - scrubberCorrection = inScrubber(x, y) - ? x - scrubberLeft - : scrubber.getWidth() / 2; - scrubbing = true; - listener.onScrubbingStart(); + + @Override + protected void onDraw(Canvas canvas) { + // draw progress bars + canvas.drawRect(mProgressBar, mProgressPaint); + canvas.drawRect(mPlayedBar, mPlayedPaint); + + // draw scrubber and timers + if (mShowScrubber) { + canvas.drawBitmap(mScrubber, mScrubberLeft, mScrubberTop, null); } - // fall-through - case MotionEvent.ACTION_MOVE: { - scrubberLeft = x - scrubberCorrection; - clampScrubber(); - currentTime = getScrubberTime(); - listener.onScrubbingMove(currentTime); - invalidate(); - return true; + if (mShowTimes) { + canvas.drawText( + stringForTime(mCurrentTime), + mTimeBounds.width() / 2 + getPaddingLeft(), + mTimeBounds.height() + mVPaddingInPx / 2 + mScrubberPadding + 1, + mTimeTextPaint); + canvas.drawText( + stringForTime(mTotalTime), + getWidth() - getPaddingRight() - mTimeBounds.width() / 2, + mTimeBounds.height() + mVPaddingInPx / 2 + mScrubberPadding + 1, + mTimeTextPaint); } - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: { - listener.onScrubbingEnd(getScrubberTime()); - scrubbing = false; - return true; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mShowScrubber) { + int x = (int) event.getX(); + int y = (int) event.getY(); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: { + mScrubberCorrection = inScrubber(x, y) + ? x - mScrubberLeft + : mScrubber.getWidth() / 2; + mScrubbing = true; + mListener.onScrubbingStart(); + } + // fall-through + case MotionEvent.ACTION_MOVE: { + mScrubberLeft = x - mScrubberCorrection; + clampScrubber(); + mCurrentTime = getScrubberTime(); + mListener.onScrubbingMove(mCurrentTime); + invalidate(); + return true; + } + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: { + mListener.onScrubbingEnd(getScrubberTime(), 0, 0); + mScrubbing = false; + return true; + } + } } - } + return false; } - return false; - } - - private String stringForTime(long millis) { - int totalSeconds = (int) millis / 1000; - int seconds = totalSeconds % 60; - int minutes = (totalSeconds / 60) % 60; - int hours = totalSeconds / 3600; - if (hours > 0) { - return String.format("%d:%02d:%02d", hours, minutes, seconds).toString(); - } else { - return String.format("%02d:%02d", minutes, seconds).toString(); + + protected String stringForTime(long millis) { + int totalSeconds = (int) millis / 1000; + int seconds = totalSeconds % 60; + int minutes = (totalSeconds / 60) % 60; + int hours = totalSeconds / 3600; + if (hours > 0) { + return String.format("%d:%02d:%02d", hours, minutes, seconds).toString(); + } else { + return String.format("%02d:%02d", minutes, seconds).toString(); + } } - } } diff --git a/src/com/android/gallery3d/app/TrimControllerOverlay.java b/src/com/android/gallery3d/app/TrimControllerOverlay.java new file mode 100644 index 000000000..9127ad159 --- /dev/null +++ b/src/com/android/gallery3d/app/TrimControllerOverlay.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012 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.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.view.MotionEvent; +import android.view.View; + +/** + * The controller for the Trimming Video. + */ +public class TrimControllerOverlay extends CommonControllerOverlay { + + public TrimControllerOverlay(Context context) { + super(context); + } + + @Override + protected void createTimeBar(Context context) { + mTimeBar = new TrimTimeBar(context, this); + } + + private void hidePlayButtonIfPlaying() { + if (mState == State.PLAYING) { + mPlayPauseReplayView.setVisibility(View.INVISIBLE); + } + mPlayPauseReplayView.setAlpha(1f); + } + + @Override + public void showPlaying() { + super.showPlaying(); + + // Add animation to hide the play button while playing. + ObjectAnimator anim = ObjectAnimator.ofFloat(mPlayPauseReplayView, "alpha", 1f, 0f); + anim.setDuration(200); + anim.start(); + anim.addListener(new AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + hidePlayButtonIfPlaying(); + } + + @Override + public void onAnimationCancel(Animator animation) { + hidePlayButtonIfPlaying(); + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + } + + @Override + public void setTimes(int currentTime, int totalTime, int trimStartTime, int trimEndTime) { + mTimeBar.setTime(currentTime, totalTime, trimStartTime, trimEndTime); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (super.onTouchEvent(event)) { + return true; + } + + // The special thing here is that the State.ENDED include both cases of + // the video completed and current == trimEnd. Both request a replay. + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + if (mState == State.PLAYING || mState == State.PAUSED) { + mListener.onPlayPause(); + } else if (mState == State.ENDED) { + if (mCanReplay) { + mListener.onReplay(); + } + } + break; + case MotionEvent.ACTION_UP: + break; + } + return true; + } +} diff --git a/src/com/android/gallery3d/app/TrimTimeBar.java b/src/com/android/gallery3d/app/TrimTimeBar.java new file mode 100644 index 000000000..f1878bb8a --- /dev/null +++ b/src/com/android/gallery3d/app/TrimTimeBar.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2012 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.content.Context; +import android.graphics.Canvas; +import android.view.MotionEvent; + +/** + * The trim time bar view, which includes the current and total time, the progress + * bar, and the scrubbers for current time, start and end time for trimming. + */ +public class TrimTimeBar extends TimeBar { + + public static final int SCRUBBER_NONE = 0; + public static final int SCRUBBER_START = 1; + public static final int SCRUBBER_CURRENT = 2; + public static final int SCRUBBER_END = 3; + + private int mPressedThumb = SCRUBBER_NONE; + + // On touch event, the setting order is Scrubber Position -> Time -> + // PlayedBar. At the setTimes(), activity can update the Time directly, then + // PlayedBar will be updated too. + private int mTrimStartScrubberLeft; + private int mTrimEndScrubberLeft; + + private int mTrimStartScrubberTop; + private int mTrimEndScrubberTop; + + private int mTrimStartTime; + private int mTrimEndTime; + + public TrimTimeBar(Context context, Listener listener) { + super(context, listener); + + mTrimStartTime = 0; + mTrimEndTime = 0; + mTrimStartScrubberLeft = 0; + mTrimEndScrubberLeft = 0; + mTrimStartScrubberTop = 0; + mTrimEndScrubberTop = 0; + + // Increase the size of this trimTimeBar. + mScrubberPadding = mScrubberPadding * 2; + mVPaddingInPx = mVPaddingInPx * 2; + } + + private int calculatePlayedBarBoundary(int time) { + return mProgressBar.left + (int) ((mProgressBar.width() * (long) time) / mTotalTime); + } + + // Based on all the time info (current, total, trimStart, trimEnd), we + // decide the playedBar size. + private void updatePlayedBarAndScrubberFromTime() { + // According to the Time, update the Played Bar + mPlayedBar.set(mProgressBar); + if (mTotalTime > 0) { + // set playedBar according to the trim time. + mPlayedBar.left = calculatePlayedBarBoundary(mTrimStartTime); + mPlayedBar.right = calculatePlayedBarBoundary(mCurrentTime); + if (!mScrubbing) { + mScrubberLeft = mPlayedBar.right - mScrubber.getWidth() / 2; + mTrimStartScrubberLeft = mPlayedBar.left - mScrubber.getWidth() / 2; + mTrimEndScrubberLeft = calculatePlayedBarBoundary(mTrimEndTime) + - mScrubber.getWidth() / 2; + } + } else { + // If the video is not prepared, just show the scrubber at the end + // of progressBar + mPlayedBar.right = mProgressBar.left; + mScrubberLeft = mProgressBar.left - mScrubber.getWidth() / 2; + mTrimStartScrubberLeft = mProgressBar.left - mScrubber.getWidth() / 2; + mTrimEndScrubberLeft = mProgressBar.right - mScrubber.getWidth() / 2; + } + } + + private void initTrimTimeIfNeeded() { + if (mTotalTime > 0 && mTrimEndTime == 0) { + mTrimEndTime = mTotalTime; + } + } + + private void update() { + initTrimTimeIfNeeded(); + updatePlayedBarAndScrubberFromTime(); + invalidate(); + } + + @Override + public void setTime(int currentTime, int totalTime, + int trimStartTime, int trimEndTime) { + if (mCurrentTime == currentTime && mTotalTime == totalTime + && mTrimStartTime == trimStartTime && mTrimEndTime == trimEndTime) { + return; + } + mCurrentTime = currentTime; + mTotalTime = totalTime; + mTrimStartTime = trimStartTime; + mTrimEndTime = trimEndTime; + update(); + } + + private int whichScrubber(float x, float y) { + if (inScrubber(mTrimStartScrubberLeft, mTrimStartScrubberTop, x, y)) { + return SCRUBBER_START; + } else if (inScrubber(mTrimEndScrubberLeft, mTrimEndScrubberTop, x, y)) { + return SCRUBBER_END; + } else if (inScrubber(mScrubberLeft, mScrubberTop, x, y)) { + return SCRUBBER_CURRENT; + } + return SCRUBBER_NONE; + } + + private boolean inScrubber(int startX, int startY, float x, float y) { + int scrubberRight = startX + mScrubber.getWidth(); + int scrubberBottom = startY + mScrubber.getHeight(); + return startX - mScrubberPadding < x && x < scrubberRight + mScrubberPadding + && startY - mScrubberPadding < y && y < scrubberBottom + mScrubberPadding; + } + + private int clampScrubber(int scrubberX) { + int half = mScrubber.getWidth() / 2; + int max = mProgressBar.right - half; + int min = mProgressBar.left - half; + return Math.min(max, Math.max(min, scrubberX)); + } + + private int getScrubberTime(int scrubberX) { + return (int) ((long) (scrubberX + mScrubber.getWidth() / 2 - mProgressBar.left) + * mTotalTime / mProgressBar.width()); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int w = r - l; + int h = b - t; + if (!mShowTimes && !mShowScrubber) { + mProgressBar.set(0, 0, w, h); + } else { + int margin = mScrubber.getWidth() / 3; + if (mShowTimes) { + margin += mTimeBounds.width(); + } + int progressY = (h + mScrubberPadding) / 2; + int scrubberY = progressY - mScrubber.getHeight() / 2 + 1; + mScrubberTop = scrubberY; + mTrimStartScrubberTop = scrubberY - mScrubber.getHeight() / 2; + mTrimEndScrubberTop = scrubberY + mScrubber.getHeight() / 2; + mProgressBar.set( + getPaddingLeft() + margin, progressY, + w - getPaddingRight() - margin, progressY + 4); + } + update(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + // draw extra scrubbers + if (mShowScrubber) { + canvas.drawBitmap(mScrubber, mTrimStartScrubberLeft, mTrimStartScrubberTop, null); + canvas.drawBitmap(mScrubber, mTrimEndScrubberLeft, mTrimEndScrubberTop, null); + } + } + + private void updateTimeFromPos() { + mCurrentTime = getScrubberTime(mScrubberLeft); + mTrimStartTime = getScrubberTime(mTrimStartScrubberLeft); + mTrimEndTime = getScrubberTime(mTrimEndScrubberLeft); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mShowScrubber) { + int x = (int) event.getX(); + int y = (int) event.getY(); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mPressedThumb = whichScrubber(x, y); + switch (mPressedThumb) { + case SCRUBBER_NONE: + break; + case SCRUBBER_CURRENT: + mScrubbing = true; + mScrubberCorrection = x - mScrubberLeft; + break; + case SCRUBBER_START: + mScrubbing = true; + mScrubberCorrection = x - mTrimStartScrubberLeft; + break; + case SCRUBBER_END: + mScrubbing = true; + mScrubberCorrection = x - mTrimEndScrubberLeft; + break; + } + if (mScrubbing == true) { + mListener.onScrubbingStart(); + return true; + } + break; + case MotionEvent.ACTION_MOVE: + if (mScrubbing) { + int seekToPos = -1; + switch (mPressedThumb) { + case SCRUBBER_CURRENT: + mScrubberLeft = x - mScrubberCorrection; + // Limit current within (start, end) + if (mScrubberLeft <= mTrimStartScrubberLeft) { + mScrubberLeft = mTrimStartScrubberLeft; + } else if (mScrubberLeft >= mTrimEndScrubberLeft) { + mScrubberLeft = mTrimEndScrubberLeft; + } + mScrubberLeft = clampScrubber(mScrubberLeft); + seekToPos = mScrubberLeft; + break; + case SCRUBBER_START: + mTrimStartScrubberLeft = x - mScrubberCorrection; + // Limit start <= end + if (mTrimStartScrubberLeft > mTrimEndScrubberLeft) { + mTrimStartScrubberLeft = mTrimEndScrubberLeft; + } + seekToPos = mTrimStartScrubberLeft; + mTrimStartScrubberLeft = clampScrubber(mTrimStartScrubberLeft); + break; + case SCRUBBER_END: + mTrimEndScrubberLeft = x - mScrubberCorrection; + // Limit end >= start + if (mTrimEndScrubberLeft < mTrimStartScrubberLeft) { + mTrimEndScrubberLeft = mTrimStartScrubberLeft; + } + seekToPos = mTrimEndScrubberLeft; + mTrimEndScrubberLeft = clampScrubber(mTrimEndScrubberLeft); + break; + } + updateTimeFromPos(); + updatePlayedBarAndScrubberFromTime(); + if (seekToPos != -1) { + mListener.onScrubbingMove(getScrubberTime(seekToPos)); + } + invalidate(); + return true; + } + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (mScrubbing) { + int seekToPos = 0; + switch (mPressedThumb) { + case SCRUBBER_CURRENT: + seekToPos = mScrubberLeft; + break; + case SCRUBBER_START: + seekToPos = mTrimStartScrubberLeft; + mScrubberLeft = mTrimStartScrubberLeft; + break; + case SCRUBBER_END: + seekToPos = mTrimEndScrubberLeft; + mScrubberLeft = mTrimEndScrubberLeft; + break; + } + updateTimeFromPos(); + mListener.onScrubbingEnd(getScrubberTime(seekToPos), + getScrubberTime(mTrimStartScrubberLeft), + getScrubberTime(mTrimEndScrubberLeft)); + mScrubbing = false; + mPressedThumb = SCRUBBER_NONE; + return true; + } + break; + } + } + return false; + } +} diff --git a/src/com/android/gallery3d/app/TrimVideo.java b/src/com/android/gallery3d/app/TrimVideo.java new file mode 100644 index 000000000..4fb255729 --- /dev/null +++ b/src/com/android/gallery3d/app/TrimVideo.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2012 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.app.ActionBar; +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; +import android.widget.VideoView; + +import com.android.gallery3d.R; + + +public class TrimVideo extends Activity implements + MediaPlayer.OnErrorListener, + MediaPlayer.OnCompletionListener, + ControllerOverlay.Listener { + + private VideoView mVideoView; + private TrimControllerOverlay mController; + private Context mContext; + private Uri mUri; + private final Handler mHandler = new Handler(); + public static final String TRIM_ACTION = "com.android.camera.action.TRIM"; + + public ProgressDialog mProgress; + + private int mTrimStartTime = 0; + private int mTrimEndTime = 0; + private int mVideoPosition = 0; + public static final String KEY_TRIM_START = "trim_start"; + public static final String KEY_TRIM_END = "trim_end"; + public static final String KEY_VIDEO_POSITION = "video_pos"; + private boolean mHasPaused = false; + + @Override + public void onCreate(Bundle savedInstanceState) { + mContext = getApplicationContext(); + super.onCreate(savedInstanceState); + + ActionBar actionBar = getActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + + Intent intent = getIntent(); + mUri = intent.getData(); + + setContentView(R.layout.trim_view); + View rootView = findViewById(R.id.trim_view_root); + + mVideoView = (VideoView) rootView.findViewById(R.id.surface_view); + + mController = new TrimControllerOverlay(mContext); + ((ViewGroup)rootView).addView(mController.getView()); + mController.setListener(this); + mController.setCanReplay(true); + + mVideoView.setOnErrorListener(this); + mVideoView.setOnCompletionListener(this); + mVideoView.setVideoURI(mUri); + + playVideo(); + } + + @Override + public void onResume() { + super.onResume(); + if (mHasPaused) { + mVideoView.seekTo(mVideoPosition); + mVideoView.resume(); + mHasPaused = false; + } + mHandler.post(mProgressChecker); + } + + @Override + public void onPause() { + mHasPaused = true; + mHandler.removeCallbacksAndMessages(null); + mVideoPosition = mVideoView.getCurrentPosition(); + mVideoView.suspend(); + super.onPause(); + } + + @Override + public void onDestroy() { + mVideoView.stopPlayback(); + super.onDestroy(); + } + + private final Runnable mProgressChecker = new Runnable() { + @Override + public void run() { + int pos = setProgress(); + mHandler.postDelayed(mProgressChecker, 200 - (pos % 200)); + } + }; + + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + savedInstanceState.putInt(KEY_TRIM_START, mTrimStartTime); + savedInstanceState.putInt(KEY_TRIM_END, mTrimEndTime); + savedInstanceState.putInt(KEY_VIDEO_POSITION, mVideoPosition); + super.onSaveInstanceState(savedInstanceState); + } + + @Override + public void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + mTrimStartTime = savedInstanceState.getInt(KEY_TRIM_START, 0); + mTrimEndTime = savedInstanceState.getInt(KEY_TRIM_END, 0); + mVideoPosition = savedInstanceState.getInt(KEY_VIDEO_POSITION, 0); + } + + // This updates the time bar display (if necessary). It is called by + // mProgressChecker and also from places where the time bar needs + // to be updated immediately. + private int setProgress() { + mVideoPosition = mVideoView.getCurrentPosition(); + // If the video position is smaller than the starting point of trimming, + // correct it. + if (mVideoPosition < mTrimStartTime) { + mVideoView.seekTo(mTrimStartTime); + mVideoPosition = mTrimStartTime; + } + // If the position is bigger than the end point of trimming, show the + // replay button and pause. + if (mVideoPosition >= mTrimEndTime && mTrimEndTime > 0) { + if (mVideoPosition > mTrimEndTime) { + mVideoView.seekTo(mTrimEndTime); + mVideoPosition = mTrimEndTime; + } + mController.showEnded(); + mVideoView.pause(); + } + + int duration = mVideoView.getDuration(); + if (duration > 0 && mTrimEndTime == 0) { + mTrimEndTime = duration; + } + mController.setTimes(mVideoPosition, duration, mTrimStartTime, mTrimEndTime); + return mVideoPosition; + } + + private void playVideo() { + mVideoView.start(); + mController.showPlaying(); + setProgress(); + } + + private void pauseVideo() { + mVideoView.pause(); + mController.showPaused(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.trim, menu); + return true; + }; + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == android.R.id.home) { + finish(); + return true; + } else if (id == R.id.action_trim_video) { + // TODO: Add the new MediaMuxer API to support the trimming. + Toast.makeText(getApplicationContext(), + "Trimming will be implemented soon!", Toast.LENGTH_SHORT).show(); + return true; + } + return false; + } + + @Override + public void onPlayPause() { + if (mVideoView.isPlaying()) { + pauseVideo(); + } else { + playVideo(); + } + } + + @Override + public void onSeekStart() { + pauseVideo(); + } + + @Override + public void onSeekMove(int time) { + mVideoView.seekTo(time); + } + + @Override + public void onSeekEnd(int time, int start, int end) { + mVideoView.seekTo(time); + mTrimStartTime = start; + mTrimEndTime = end; + setProgress(); + } + + @Override + public void onShown() { + } + + + @Override + public void onHidden() { + } + + @Override + public void onReplay() { + mVideoView.seekTo(mTrimStartTime); + playVideo(); + } + + @Override + public void onCompletion(MediaPlayer mp) { + mController.showEnded(); + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + return false; + } +} diff --git a/src/com/android/gallery3d/ui/MenuExecutor.java b/src/com/android/gallery3d/ui/MenuExecutor.java index 5e76fe6ad..38d2cc4c8 100644 --- a/src/com/android/gallery3d/ui/MenuExecutor.java +++ b/src/com/android/gallery3d/ui/MenuExecutor.java @@ -32,6 +32,7 @@ import com.actionbarsherlock.view.MenuItem; import com.android.gallery3d.R; import com.android.gallery3d.app.AbstractGalleryActivity; import com.android.gallery3d.app.CropImage; +import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.DataManager; import com.android.gallery3d.data.MediaItem; @@ -159,7 +160,8 @@ public class MenuExecutor { boolean supportDelete = (supported & MediaObject.SUPPORT_DELETE) != 0; boolean supportRotate = (supported & MediaObject.SUPPORT_ROTATE) != 0; boolean supportCrop = (supported & MediaObject.SUPPORT_CROP) != 0; - boolean supportTrim = (supported & MediaObject.SUPPORT_TRIM) != 0; + boolean supportTrim = (supported & MediaObject.SUPPORT_TRIM) != 0 + && ApiHelper.HAS_MEDIA_MUXER; boolean supportShare = (supported & MediaObject.SUPPORT_SHARE) != 0; boolean supportSetAs = (supported & MediaObject.SUPPORT_SETAS) != 0; boolean supportShowOnMap = (supported & MediaObject.SUPPORT_SHOW_ON_MAP) != 0; |