diff options
| author | Chih-Chung Chang <chihchung@google.com> | 2011-10-18 06:44:01 -0700 |
|---|---|---|
| committer | Android Git Automerger <android-git-automerger@android.com> | 2011-10-18 06:44:01 -0700 |
| commit | f9c4464d8aa366e9c7dc91493fcd8bfe2084277a (patch) | |
| tree | 98cb6c0af4c6863bff8ef7ee580bf4a5827f908f | |
| parent | 5d393ac4b272131104b1ffe9a04e84022e63c0f0 (diff) | |
| parent | 209a9163d4e8cee0bfe162ae598ef40e6051479c (diff) | |
| download | android_packages_apps_Gallery2-f9c4464d8aa366e9c7dc91493fcd8bfe2084277a.tar.gz android_packages_apps_Gallery2-f9c4464d8aa366e9c7dc91493fcd8bfe2084277a.tar.bz2 android_packages_apps_Gallery2-f9c4464d8aa366e9c7dc91493fcd8bfe2084277a.zip | |
am 209a9163: Fix 5393669: New Player Control UI.
* commit '209a9163d4e8cee0bfe162ae598ef40e6051479c':
Fix 5393669: New Player Control UI.
33 files changed, 889 insertions, 82 deletions
diff --git a/res/anim/player_out.xml b/res/anim/player_out.xml new file mode 100644 index 000000000..b6d90d245 --- /dev/null +++ b/res/anim/player_out.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2011 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. +--> + +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/linear_interpolator" + android:fromAlpha="1.0" + android:toAlpha="0.0" + android:duration="500"/> diff --git a/res/drawable-hdpi/bg_vidcontrol.png b/res/drawable-hdpi/bg_vidcontrol.png Binary files differnew file mode 100644 index 000000000..dfe2da136 --- /dev/null +++ b/res/drawable-hdpi/bg_vidcontrol.png diff --git a/res/drawable-hdpi/ic_vidcontrol_pause.png b/res/drawable-hdpi/ic_vidcontrol_pause.png Binary files differnew file mode 100644 index 000000000..3d1a8bf5c --- /dev/null +++ b/res/drawable-hdpi/ic_vidcontrol_pause.png diff --git a/res/drawable-hdpi/ic_vidcontrol_play.png b/res/drawable-hdpi/ic_vidcontrol_play.png Binary files differnew file mode 100644 index 000000000..cc021667a --- /dev/null +++ b/res/drawable-hdpi/ic_vidcontrol_play.png diff --git a/res/drawable-hdpi/ic_vidcontrol_reload.png b/res/drawable-hdpi/ic_vidcontrol_reload.png Binary files differnew file mode 100644 index 000000000..bf8d5299c --- /dev/null +++ b/res/drawable-hdpi/ic_vidcontrol_reload.png diff --git a/res/drawable-hdpi/scrubber_knob.png b/res/drawable-hdpi/scrubber_knob.png Binary files differnew file mode 100644 index 000000000..426e3daff --- /dev/null +++ b/res/drawable-hdpi/scrubber_knob.png diff --git a/res/drawable-mdpi/bg_vidcontrol.png b/res/drawable-mdpi/bg_vidcontrol.png Binary files differnew file mode 100644 index 000000000..5a5ce5571 --- /dev/null +++ b/res/drawable-mdpi/bg_vidcontrol.png diff --git a/res/drawable-mdpi/ic_vidcontrol_pause.png b/res/drawable-mdpi/ic_vidcontrol_pause.png Binary files differnew file mode 100644 index 000000000..931907929 --- /dev/null +++ b/res/drawable-mdpi/ic_vidcontrol_pause.png diff --git a/res/drawable-mdpi/ic_vidcontrol_play.png b/res/drawable-mdpi/ic_vidcontrol_play.png Binary files differnew file mode 100644 index 000000000..8d7cd837b --- /dev/null +++ b/res/drawable-mdpi/ic_vidcontrol_play.png diff --git a/res/drawable-mdpi/ic_vidcontrol_reload.png b/res/drawable-mdpi/ic_vidcontrol_reload.png Binary files differnew file mode 100644 index 000000000..51b1f4834 --- /dev/null +++ b/res/drawable-mdpi/ic_vidcontrol_reload.png diff --git a/res/drawable-mdpi/scrubber_knob.png b/res/drawable-mdpi/scrubber_knob.png Binary files differnew file mode 100644 index 000000000..9205d9c21 --- /dev/null +++ b/res/drawable-mdpi/scrubber_knob.png diff --git a/res/drawable-sw600dp-mdpi/bg_vidcontrol.png b/res/drawable-sw600dp-mdpi/bg_vidcontrol.png Binary files differnew file mode 100644 index 000000000..dfe2da136 --- /dev/null +++ b/res/drawable-sw600dp-mdpi/bg_vidcontrol.png diff --git a/res/drawable-sw600dp-mdpi/ic_vidcontrol_pause.png b/res/drawable-sw600dp-mdpi/ic_vidcontrol_pause.png Binary files differnew file mode 100644 index 000000000..3d1a8bf5c --- /dev/null +++ b/res/drawable-sw600dp-mdpi/ic_vidcontrol_pause.png diff --git a/res/drawable-sw600dp-mdpi/ic_vidcontrol_play.png b/res/drawable-sw600dp-mdpi/ic_vidcontrol_play.png Binary files differnew file mode 100644 index 000000000..cc021667a --- /dev/null +++ b/res/drawable-sw600dp-mdpi/ic_vidcontrol_play.png diff --git a/res/drawable-sw600dp-mdpi/ic_vidcontrol_reload.png b/res/drawable-sw600dp-mdpi/ic_vidcontrol_reload.png Binary files differnew file mode 100644 index 000000000..bf8d5299c --- /dev/null +++ b/res/drawable-sw600dp-mdpi/ic_vidcontrol_reload.png diff --git a/res/drawable-sw600dp-mdpi/scrubber_knob.png b/res/drawable-sw600dp-mdpi/scrubber_knob.png Binary files differnew file mode 100644 index 000000000..426e3daff --- /dev/null +++ b/res/drawable-sw600dp-mdpi/scrubber_knob.png diff --git a/res/drawable-sw600dp/bg_vidcontrol.png b/res/drawable-sw600dp/bg_vidcontrol.png Binary files differnew file mode 100644 index 000000000..0eb814815 --- /dev/null +++ b/res/drawable-sw600dp/bg_vidcontrol.png diff --git a/res/drawable-sw600dp/ic_vidcontrol_pause.png b/res/drawable-sw600dp/ic_vidcontrol_pause.png Binary files differnew file mode 100644 index 000000000..4d274c058 --- /dev/null +++ b/res/drawable-sw600dp/ic_vidcontrol_pause.png diff --git a/res/drawable-sw600dp/ic_vidcontrol_play.png b/res/drawable-sw600dp/ic_vidcontrol_play.png Binary files differnew file mode 100644 index 000000000..6f97a6407 --- /dev/null +++ b/res/drawable-sw600dp/ic_vidcontrol_play.png diff --git a/res/drawable-sw600dp/ic_vidcontrol_reload.png b/res/drawable-sw600dp/ic_vidcontrol_reload.png Binary files differnew file mode 100644 index 000000000..2aaf4919a --- /dev/null +++ b/res/drawable-sw600dp/ic_vidcontrol_reload.png diff --git a/res/drawable-sw600dp/scrubber_knob.png b/res/drawable-sw600dp/scrubber_knob.png Binary files differnew file mode 100644 index 000000000..4e415a2a7 --- /dev/null +++ b/res/drawable-sw600dp/scrubber_knob.png diff --git a/res/drawable-xhdpi/bg_vidcontrol.png b/res/drawable-xhdpi/bg_vidcontrol.png Binary files differnew file mode 100644 index 000000000..0eb814815 --- /dev/null +++ b/res/drawable-xhdpi/bg_vidcontrol.png diff --git a/res/drawable-xhdpi/ic_vidcontrol_pause.png b/res/drawable-xhdpi/ic_vidcontrol_pause.png Binary files differnew file mode 100644 index 000000000..4d274c058 --- /dev/null +++ b/res/drawable-xhdpi/ic_vidcontrol_pause.png diff --git a/res/drawable-xhdpi/ic_vidcontrol_play.png b/res/drawable-xhdpi/ic_vidcontrol_play.png Binary files differnew file mode 100644 index 000000000..6f97a6407 --- /dev/null +++ b/res/drawable-xhdpi/ic_vidcontrol_play.png diff --git a/res/drawable-xhdpi/ic_vidcontrol_reload.png b/res/drawable-xhdpi/ic_vidcontrol_reload.png Binary files differnew file mode 100644 index 000000000..2aaf4919a --- /dev/null +++ b/res/drawable-xhdpi/ic_vidcontrol_reload.png diff --git a/res/drawable-xhdpi/scrubber_knob.png b/res/drawable-xhdpi/scrubber_knob.png Binary files differnew file mode 100644 index 000000000..4e415a2a7 --- /dev/null +++ b/res/drawable-xhdpi/scrubber_knob.png diff --git a/res/layout/movie_view.xml b/res/layout/movie_view.xml index 6d6b28d1c..bd0415c3a 100644 --- a/res/layout/movie_view.xml +++ b/res/layout/movie_view.xml @@ -24,24 +24,4 @@ android:layout_height="match_parent" android:layout_centerInParent="true" /> - <LinearLayout android:id="@+id/progress_indicator" - android:orientation="vertical" - android:layout_centerInParent="true" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <ProgressBar android:id="@android:id/progress" - style="?android:attr/progressBarStyleLarge" - android:layout_gravity="center" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> - - <TextView android:paddingTop="5dip" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:text="@string/loading_video" android:textSize="14sp" - android:textColor="#ffffffff" /> - </LinearLayout> - </RelativeLayout> diff --git a/res/values/styles.xml b/res/values/styles.xml index df9fea382..b26c72861 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -35,4 +35,5 @@ <style name="DialogPickerTheme" parent="Theme.Gallery"> </style> <bool name="picker_is_dialog">false</bool> + <color name="darker_transparent">#C1000000</color> </resources> diff --git a/src/com/android/gallery3d/app/ControllerOverlay.java b/src/com/android/gallery3d/app/ControllerOverlay.java new file mode 100644 index 000000000..847d9e60c --- /dev/null +++ b/src/com/android/gallery3d/app/ControllerOverlay.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2011 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.view.View; + +public interface ControllerOverlay { + + interface Listener { + void onPlayPause(); + void onSeekStart(); + void onSeekMove(int time); + void onSeekEnd(int time); + void onShown(); + void onHidden(); + void onReplay(); + } + + void setListener(Listener listener); + + void setCanReplay(boolean canReplay); + + /** + * @return The overlay view that should be added to the player. + */ + View getView(); + + void show(); + + void showPlaying(); + + void showPaused(); + + void showEnded(); + + void showLoading(); + + void showErrorMessage(String message); + + void hide(); + + void setTimes(int currentTime, int totalTime); + + void resetTime(); + +} diff --git a/src/com/android/gallery3d/app/MovieActivity.java b/src/com/android/gallery3d/app/MovieActivity.java index 58b1b088c..95e0c74d3 100644 --- a/src/com/android/gallery3d/app/MovieActivity.java +++ b/src/com/android/gallery3d/app/MovieActivity.java @@ -57,7 +57,10 @@ public class MovieActivity extends Activity { View rootView = findViewById(R.id.root); Intent intent = getIntent(); initializeActionBar(intent); - mPlayer = new MoviePlayer(rootView, this, intent.getData(), savedInstanceState) { + mFinishOnCompletion = intent.getBooleanExtra( + MediaStore.EXTRA_FINISH_ON_COMPLETION, true); + mPlayer = new MoviePlayer(rootView, this, intent.getData(), savedInstanceState, + !mFinishOnCompletion) { @Override public void onCompletion() { if (mFinishOnCompletion) { @@ -73,7 +76,6 @@ public class MovieActivity extends Activity { setRequestedOrientation(orientation); } } - mFinishOnCompletion = intent.getBooleanExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, true); Window win = getWindow(); WindowManager.LayoutParams winParams = win.getAttributes(); winParams.buttonBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF; diff --git a/src/com/android/gallery3d/app/MovieControllerOverlay.java b/src/com/android/gallery3d/app/MovieControllerOverlay.java new file mode 100644 index 000000000..752213e4b --- /dev/null +++ b/src/com/android/gallery3d/app/MovieControllerOverlay.java @@ -0,0 +1,390 @@ +/* + * Copyright (C) 2011 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 com.android.gallery3d.common.Utils; +import com.android.gallery3d.R; + +import android.content.Context; +import android.os.Handler; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.LayoutInflater; +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.ImageButton; +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; + +/** + * 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; + + private final View background; + private final TimeBar timeBar; + + private View mainView; + private final LinearLayout loadingView; + private final TextView errorView; + private final ImageView playPauseReplayView; + + 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); + + LayoutInflater inflater = LayoutInflater.from(context); + + 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); + 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 = new TextView(context); + errorView.setGravity(Gravity.CENTER); + errorView.setBackgroundColor(0xCC000000); + errorView.setTextColor(0xFFFFFFFF); + addView(errorView, matchParent); + + handler = new Handler(); + startHidingRunnable = new Runnable() { + public void run() { + startHiding(); + } + }; + + 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(); + } + + public void setListener(Listener listener) { + this.listener = listener; + } + + public void setCanReplay(boolean canReplay) { + this.canReplay = canReplay; + } + + public View getView() { + return this; + } + + public void showPlaying() { + state = State.PLAYING; + showMainView(playPauseReplayView); + } + + public void showPaused() { + state = State.PAUSED; + showMainView(playPauseReplayView); + } + + public void showEnded() { + state = State.ENDED; + showMainView(playPauseReplayView); + } + + public void showLoading() { + state = State.LOADING; + showMainView(loadingView); + } + + public void showErrorMessage(String message) { + state = State.ERROR; + int padding = (int) (getMeasuredWidth() * ERROR_MESSAGE_RELATIVE_PADDING); + errorView.setPadding(padding, 10, padding, 10); + errorView.setText(message); + showMainView(errorView); + } + + public void resetTime() { + timeBar.resetTime(); + } + + 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(); + } + } + + 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(); + } + + public void show() { + boolean wasHidden = hidden; + hidden = false; + updateViews(); + setVisibility(View.VISIBLE); + setFocusable(false); + if (listener != null && wasHidden != hidden) { + listener.onShown(); + } + maybeStartHiding(); + } + + private void maybeStartHiding() { + cancelHiding(); + if (state == State.PLAYING) { + handler.postDelayed(startHidingRunnable, 2500); + } + } + + private void startHiding() { + startHideAnimation(timeBar); + startHideAnimation(playPauseReplayView); + } + + private void startHideAnimation(View view) { + if (view.getVisibility() == View.VISIBLE) { + view.startAnimation(hideAnimation); + } + } + + private void cancelHiding() { + handler.removeCallbacks(startHidingRunnable); + background.setAnimation(null); + timeBar.setAnimation(null); + playPauseReplayView.setAnimation(null); + } + + public void onAnimationStart(Animation animation) { + // Do nothing. + } + + public void onAnimationRepeat(Animation animation) { + // Do nothing. + } + + public void onAnimationEnd(Animation animation) { + hide(); + } + + 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(); + } + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (super.onTouchEvent(event)) { + return true; + } + + if (hidden) { + show(); + return true; + } + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + cancelHiding(); + if (state == State.PLAYING || state == State.PAUSED) { + listener.onPlayPause(); + } + break; + case MotionEvent.ACTION_UP: + maybeStartHiding(); + break; + } + return true; + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int bw; + int bh; + int y; + int h = b - t; + int w = r - l; + boolean error = errorView.getVisibility() == View.VISIBLE; + + bw = timeBar.getBarHeight(); + bh = bw; + y = b - bh; + + background.layout(l, y, r, b); + + timeBar.layout(l, b - timeBar.getPreferredHeight(), r, b); + // Needed, otherwise the framework will not re-layout in case only the padding is changed + timeBar.requestLayout(); + + // play pause / next / previous buttons + int cx = l + w / 2; // center x + int playbackButtonsCenterline = t + h / 2; + bw = playPauseReplayView.getMeasuredWidth(); + bh = playPauseReplayView.getMeasuredHeight(); + playPauseReplayView.layout( + cx - bw / 2, playbackButtonsCenterline - bh / 2, cx + bw / 2, + playbackButtonsCenterline + bh / 2); + + // Space available on each side of the error message for the next and previous buttons + int errorMessagePadding = (int) (w * ERROR_MESSAGE_RELATIVE_PADDING); + + if (mainView != null) { + layoutCenteredView(mainView, l, t, r, b); + } + } + + 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() { + 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(); + } + + // TimeBar listener + + public void onScrubbingStart() { + cancelHiding(); + listener.onSeekStart(); + } + + public void onScrubbingMove(int time) { + cancelHiding(); + listener.onSeekMove(time); + } + + public void onScrubbingEnd(int time) { + maybeStartHiding(); + listener.onSeekEnd(time); + } + +} diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java index f37bc7d27..07fe71a85 100644 --- a/src/com/android/gallery3d/app/MoviePlayer.java +++ b/src/com/android/gallery3d/app/MoviePlayer.java @@ -36,7 +36,9 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.widget.MediaController; import android.widget.VideoView; @@ -46,7 +48,8 @@ import java.io.DataInputStream; import java.io.DataOutputStream; public class MoviePlayer implements - MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener { + MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, + ControllerOverlay.Listener { @SuppressWarnings("unused") private static final String TAG = "MoviePlayer"; @@ -64,84 +67,64 @@ public class MoviePlayer implements private Context mContext; private final VideoView mVideoView; - private final View mProgressView; private final Bookmarker mBookmarker; private final Uri mUri; private final Handler mHandler = new Handler(); private final AudioBecomingNoisyReceiver mAudioBecomingNoisyReceiver; private final ActionBar mActionBar; - private final MediaController mMediaController; + private final ControllerOverlay mController; private long mResumeableTime = Long.MAX_VALUE; private int mVideoPosition = 0; private boolean mHasPaused = false; + // If the time bar is being dragged. + private boolean mDragging; + + // If the time bar is visible. + private boolean mShowing; + private final Runnable mPlayingChecker = new Runnable() { @Override public void run() { if (mVideoView.isPlaying()) { - mProgressView.setVisibility(View.GONE); + mController.showPlaying(); } else { mHandler.postDelayed(mPlayingChecker, 250); } } }; + private final Runnable mProgressChecker = new Runnable() { + @Override + public void run() { + int pos = setProgress(); + mHandler.postDelayed(mProgressChecker, 1000 - (pos % 1000)); + } + }; + public MoviePlayer(View rootView, final MovieActivity movieActivity, Uri videoUri, - Bundle savedInstance) { + Bundle savedInstance, boolean canReplay) { mContext = movieActivity.getApplicationContext(); mVideoView = (VideoView) rootView.findViewById(R.id.surface_view); - mProgressView = rootView.findViewById(R.id.progress_indicator); mBookmarker = new Bookmarker(movieActivity); mActionBar = movieActivity.getActionBar(); mUri = videoUri; - // For streams that we expect to be slow to start up, show a - // progress spinner until playback starts. - String scheme = mUri.getScheme(); - if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme)) { - mHandler.postDelayed(mPlayingChecker, 250); - } else { - mProgressView.setVisibility(View.GONE); - } + mController = new MovieControllerOverlay(mContext); + ((ViewGroup)rootView).addView(mController.getView()); + mController.setListener(this); + mController.setCanReplay(canReplay); mVideoView.setOnErrorListener(this); mVideoView.setOnCompletionListener(this); mVideoView.setVideoURI(mUri); - - mMediaController = new MediaController(movieActivity) { - @Override - public void show() { - showSystemUi(true); - mActionBar.show(); - super.show(); + mVideoView.setOnTouchListener(new View.OnTouchListener() { + public boolean onTouch(View v, MotionEvent event) { + mController.show(); + return true; } - - @Override - public void hide() { - super.hide(); - mActionBar.hide(); - showSystemUi(false); - } - - // We intercept the "back" key events here, so hide() won't be - // called for ACTION_DOWN events of the "back" key (The code is in - // MediaController). Otherwise after system bar is hidden, we - // will not receive the ACTION_UP events of the "back" key. - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - int keyCode = event.getKeyCode(); - if (keyCode == KeyEvent.KEYCODE_BACK) { - if (event.getAction() == KeyEvent.ACTION_UP) { - movieActivity.onBackPressed(); - } - return true; - } - return super.dispatchKeyEvent(event); - } - }; - - mMediaController.show(); + }); // When the user touches the screen or uses some hard key, the framework // will change system ui visibility from invisible to visible. We show @@ -150,19 +133,14 @@ public class MoviePlayer implements new View.OnSystemUiVisibilityChangeListener() { public void onSystemUiVisibilityChange(int visibility) { if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) { - mMediaController.show(); + mController.show(); } } }); - mVideoView.setMediaController(mMediaController); - mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(); mAudioBecomingNoisyReceiver.register(); - // make the video view handle keys for seeking and pausing - mVideoView.requestFocus(); - Intent i = new Intent(SERVICECMD); i.putExtra(CMDNAME, CMDPAUSE); movieActivity.sendBroadcast(i); @@ -178,7 +156,7 @@ public class MoviePlayer implements if (bookmark != null) { showResumeDialog(movieActivity, bookmark); } else { - mVideoView.start(); + startVideo(); } } } @@ -186,7 +164,6 @@ public class MoviePlayer implements private void showSystemUi(boolean visible) { int flag = visible ? 0 : View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; mVideoView.setSystemUiVisibility(flag); - mMediaController.setSystemUiVisibility(flag); } public void onSaveInstanceState(Bundle outState) { @@ -211,14 +188,14 @@ public class MoviePlayer implements @Override public void onClick(DialogInterface dialog, int which) { mVideoView.seekTo(bookmark); - mVideoView.start(); + startVideo(); } }); builder.setNegativeButton( R.string.resume_playing_restart, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mVideoView.start(); + startVideo(); } }); builder.show(); @@ -240,10 +217,10 @@ public class MoviePlayer implements // If we have slept for too long, pause the play if (System.currentTimeMillis() > mResumeableTime) { - mMediaController.show(); - mVideoView.pause(); + pauseVideo(); } } + mHandler.post(mProgressChecker); } public void onDestroy() { @@ -251,19 +228,113 @@ public class MoviePlayer implements mAudioBecomingNoisyReceiver.unregister(); } + // 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) { + return 0; + } + int position = mVideoView.getCurrentPosition(); + int duration = mVideoView.getDuration(); + mController.setTimes(position, duration); + return position; + } + + private void startVideo() { + // For streams that we expect to be slow to start up, show a + // progress spinner until playback starts. + String scheme = mUri.getScheme(); + if ("http".equalsIgnoreCase(scheme) || "rtsp".equalsIgnoreCase(scheme)) { + mController.showLoading(); + mHandler.removeCallbacks(mPlayingChecker); + mHandler.postDelayed(mPlayingChecker, 250); + } else { + mController.showPlaying(); + } + + mVideoView.start(); + setProgress(); + } + + private void playVideo() { + mVideoView.start(); + mController.showPlaying(); + setProgress(); + } + + private void pauseVideo() { + mVideoView.pause(); + mController.showPaused(); + } + + // Below are notifications from VideoView + @Override public boolean onError(MediaPlayer player, int arg1, int arg2) { mHandler.removeCallbacksAndMessages(null); - mProgressView.setVisibility(View.GONE); + // VideoView will show an error dialog if we return false, so no need + // to show more message. + mController.showErrorMessage(""); return false; } + @Override public void onCompletion(MediaPlayer mp) { + mController.showEnded(); onCompletion(); } public void onCompletion() { } + // Below are notifications from ControllerOverlay + @Override + public void onPlayPause() { + if (mVideoView.isPlaying()) { + pauseVideo(); + } else { + playVideo(); + } + } + + @Override + public void onSeekStart() { + mDragging = true; + } + + @Override + public void onSeekMove(int time) { + mVideoView.seekTo(time); + } + + @Override + public void onSeekEnd(int time) { + mDragging = false; + mVideoView.seekTo(time); + setProgress(); + } + + @Override + public void onShown() { + mShowing = true; + mActionBar.show(); + showSystemUi(true); + setProgress(); + } + + @Override + public void onHidden() { + mShowing = false; + mActionBar.hide(); + showSystemUi(false); + } + + @Override + public void onReplay() { + startVideo(); + } + + // We want to pause when the headset is unplugged. private class AudioBecomingNoisyReceiver extends BroadcastReceiver { public void register() { diff --git a/src/com/android/gallery3d/app/TimeBar.java b/src/com/android/gallery3d/app/TimeBar.java new file mode 100644 index 000000000..0ac8efb42 --- /dev/null +++ b/src/com/android/gallery3d/app/TimeBar.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2011 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 com.android.gallery3d.common.Utils; +import com.android.gallery3d.R; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.View; + +/** + * 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); + } + + // Padding around the scrubber to increase its touch target + private static final int SCRUBBER_PADDING_IN_DP = 10; + + // The total padding, top plus bottom + private static final int V_PADDING_IN_DP = 30; + + private static final int TEXT_SIZE_IN_DP = 14; + + private final Listener listener; + + // the bars we use for displaying the progress + private final Rect progressBar; + private final Rect playedBar; + + private final Paint progressPaint; + private final Paint playedPaint; + private final Paint timeTextPaint; + + private final Bitmap scrubber; + private final int scrubberPadding; // adds some touch tolerance around the scrubber + + private int scrubberLeft; + private int scrubberTop; + private int scrubberCorrection; + private boolean scrubbing; + private boolean showTimes; + private boolean showScrubber; + + private int totalTime; + private int currentTime; + + private final Rect timeBounds; + + private int vPaddingInPx; + + public TimeBar(Context context, Listener listener) { + super(context); + this.listener = Utils.checkNotNull(listener); + + showTimes = true; + showScrubber = true; + + progressBar = new Rect(); + playedBar = new Rect(); + + progressPaint = new Paint(); + progressPaint.setColor(0xFF808080); + playedPaint = new Paint(); + playedPaint.setColor(0xFFFFFFFF); + + 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); + + timeBounds = new Rect(); + timeTextPaint.getTextBounds("0:00:00", 0, 7, timeBounds); + + scrubber = BitmapFactory.decodeResource(getResources(), R.drawable.scrubber_knob); + scrubberPadding = (int) (metrics.density * SCRUBBER_PADDING_IN_DP); + + vPaddingInPx = (int) (metrics.density * V_PADDING_IN_DP); + } + + private void update() { + playedBar.set(progressBar); + + if (totalTime > 0) { + playedBar.right = + playedBar.left + (int) ((progressBar.width() * (long) currentTime) / totalTime); + } else { + playedBar.right = progressBar.left; + } + + if (!scrubbing) { + scrubberLeft = playedBar.right - scrubber.getWidth() / 2; + } + 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; + } + 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; + } + 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); + } + update(); + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + + // draw progress bars + canvas.drawRect(progressBar, progressPaint); + canvas.drawRect(playedBar, playedPaint); + + // draw scrubber and timers + if (showScrubber) { + canvas.drawBitmap(scrubber, scrubberLeft, scrubberTop, null); + } + 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 + public boolean onTouchEvent(MotionEvent event) { + + if (showScrubber) { + int x = (int) event.getX(); + int y = (int) event.getY(); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + if (inScrubber(x, y)) { + scrubbing = true; + scrubberCorrection = x - scrubberLeft; + listener.onScrubbingStart(); + return true; + } + break; + case MotionEvent.ACTION_MOVE: + if (scrubbing) { + scrubberLeft = x - scrubberCorrection; + clampScrubber(); + currentTime = getScrubberTime(); + listener.onScrubbingMove(currentTime); + invalidate(); + return true; + } + break; + case MotionEvent.ACTION_UP: + if (scrubbing) { + listener.onScrubbingEnd(getScrubberTime()); + scrubbing = false; + return true; + } + break; + } + } + 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(); + } + } + +} |
