diff options
| -rw-r--r-- | res/drawable/ic_video_snapshot.xml | 43 | ||||
| -rw-r--r-- | res/drawable/ic_video_snapshot_padding.xml | 38 | ||||
| -rw-r--r-- | src/com/android/gallery3d/app/MovieControllerOverlayNew.java | 110 | ||||
| -rwxr-xr-x | src/com/android/gallery3d/app/MoviePlayer.java | 4 | ||||
| -rw-r--r-- | src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java | 8 | ||||
| -rw-r--r-- | src/org/codeaurora/gallery3d/video/IVideoSnapshotListener.java | 35 | ||||
| -rw-r--r-- | src/org/codeaurora/gallery3d/video/VideoSnapshotExt.java | 220 |
7 files changed, 456 insertions, 2 deletions
diff --git a/res/drawable/ic_video_snapshot.xml b/res/drawable/ic_video_snapshot.xml new file mode 100644 index 000000000..df5b00f5e --- /dev/null +++ b/res/drawable/ic_video_snapshot.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (c) 2016, The Linux Foundation. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="36dp" + android:width="36dp" + android:viewportHeight="108.0" + android:viewportWidth="108.0"> + <path + android:fillColor="#FF000000" + android:fillAlpha="0.30" + android:pathData="M54,54m-54,0a54,54 0,1 1,108 0a54,54 0,1 1,-108 0"/> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M42,36h6v-6h-6V36zM30,72h6v-6h-6V72zM72,30h-6v6h6V30zM60,30h-6v6h6V30zM72,42h-6v6h6V42zM30,60h6v-6h-6V60zM72,66V54h-6v12H54v6h12v10h6V72h10v-6H72zM42,72h6v-6h-6V72zM30,48h6v-6h-6V48zM30,36h6v-6h-6V36z"/> +</vector> diff --git a/res/drawable/ic_video_snapshot_padding.xml b/res/drawable/ic_video_snapshot_padding.xml new file mode 100644 index 000000000..f495b752f --- /dev/null +++ b/res/drawable/ic_video_snapshot_padding.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (c) 2016, The Linux Foundation. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--> + +<inset + xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/ic_video_snapshot" + android:insetTop="12dp" + android:insetRight="12dp" + android:insetBottom="12dp" + android:insetLeft="12dp"/> + diff --git a/src/com/android/gallery3d/app/MovieControllerOverlayNew.java b/src/com/android/gallery3d/app/MovieControllerOverlayNew.java index 135591c05..e9e44edb7 100644 --- a/src/com/android/gallery3d/app/MovieControllerOverlayNew.java +++ b/src/com/android/gallery3d/app/MovieControllerOverlayNew.java @@ -30,16 +30,21 @@ package com.android.gallery3d.app; import android.content.Context; +import android.graphics.Color; import android.graphics.Rect; import android.view.MotionEvent; import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; import android.widget.ImageView; +import org.codeaurora.gallery3d.video.IVideoSnapshotListener; +import org.codeaurora.gallery3d.video.IControllerRewindAndForward; import com.android.gallery3d.R; -import org.codeaurora.gallery3d.video.IControllerRewindAndForward; - public class MovieControllerOverlayNew extends MovieControllerOverlay { + private VideoSnapshotLayout mVideoSnapshotLayout = new VideoSnapshotLayout(); + private IVideoSnapshotListener mVideoSnapshotListener; private boolean mIsLive = false; private ImageView mLiveImage; @@ -49,6 +54,8 @@ public class MovieControllerOverlayNew extends MovieControllerOverlay { public MovieControllerOverlayNew(Context context) { super(context); + + mVideoSnapshotLayout.init(context); mLiveImage = ((MovieActivity) mContext).getLiveImage(); mStopBtn = mControllerRewindAndForwardExt.getStopBtn(); mLiveMargin = context.getResources().getDimensionPixelSize( @@ -116,6 +123,13 @@ public class MovieControllerOverlayNew extends MovieControllerOverlay { } mTimeBar.layout(mTimeBar.getPreferredHeight(), y - mTimeBar.getPreferredHeight(), width - mScreenModeExt.getAddedRightPadding(), y); + + mVideoSnapshotLayout.layoutButton( + w - pr - mVideoSnapshotLayout.getButtonWidth(), + y - mTimeBar.getPreferredHeight() - mVideoSnapshotLayout.getButtonHeight(), + w - pr, + y - mTimeBar.getPreferredHeight()); + mVideoSnapshotLayout.layoutAnim(left, top, right, bottom); } public void setLive(boolean live) { @@ -134,4 +148,96 @@ public class MovieControllerOverlayNew extends MovieControllerOverlay { return false; } + public void setVideoSnapshotListener(IVideoSnapshotListener listener) { + mVideoSnapshotListener = listener; + } + + public void showVideoSnapshotButton(boolean show) { + if (mVideoSnapshotLayout != null) { + mVideoSnapshotLayout.showVideoSnapshotButton(show); + } + } + + class VideoSnapshotLayout implements View.OnClickListener { + private ImageView mVideoSnapshotButton; + private ImageView mVideoSnapshotAnimView; + private Animation mVideoSnapshotAnimation; + + public void init(Context context) { + LayoutParams wrapContent = + new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + mVideoSnapshotButton = new ImageView(context); + mVideoSnapshotButton.setImageResource(R.drawable.ic_video_snapshot_padding); + mVideoSnapshotButton.setScaleType(ImageView.ScaleType.CENTER); + mVideoSnapshotButton.setFocusable(true); + mVideoSnapshotButton.setClickable(true); + mVideoSnapshotButton.setOnClickListener(this); + addView(mVideoSnapshotButton, wrapContent); + + LayoutParams matchParent = + new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + mVideoSnapshotAnimView = new ImageView(context); + addView(mVideoSnapshotAnimView, matchParent); + + mVideoSnapshotAnimation = AnimationUtils.loadAnimation(context, R.anim.player_out); + mVideoSnapshotAnimation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + mVideoSnapshotAnimView.setBackgroundColor(Color.WHITE); + mVideoSnapshotButton.setEnabled(false); + } + + @Override + public void onAnimationEnd(Animation animation) { + mVideoSnapshotAnimView.setBackgroundColor(Color.TRANSPARENT); + mVideoSnapshotButton.setEnabled(true); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + } + + public int getButtonWidth() { + return mVideoSnapshotButton == null ? 0 : mVideoSnapshotButton.getMeasuredWidth(); + } + + public int getButtonHeight() { + return mVideoSnapshotButton == null ? 0 : mVideoSnapshotButton.getMeasuredHeight(); + } + + public void layoutButton(int l, int t, int r, int b) { + if (mVideoSnapshotButton != null) { + mVideoSnapshotButton.layout(l, t, r, b); + } + } + + public void layoutAnim(int l, int t, int r, int b) { + if (mVideoSnapshotAnimView != null) { + mVideoSnapshotAnimView.layout(l, t, r, b); + } + } + + public void showVideoSnapshotButton(boolean show) { + if (mVideoSnapshotButton != null) { + mVideoSnapshotButton.setVisibility(show ? VISIBLE : GONE); + } + } + + @Override + public void onClick(View v) { + maybeStartHiding(); + if (mVideoSnapshotListener != null && mVideoSnapshotListener.canVideoSnapshot()) { + mVideoSnapshotListener.onVideoSnapshot(); + startAnimation(); + } + } + + private void startAnimation() { + if (mVideoSnapshotAnimView != null) { + mVideoSnapshotAnimView.startAnimation(mVideoSnapshotAnimation); + } + } + } } diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java index 1f6401ee5..8581f80e8 100755 --- a/src/com/android/gallery3d/app/MoviePlayer.java +++ b/src/com/android/gallery3d/app/MoviePlayer.java @@ -62,6 +62,7 @@ import org.codeaurora.gallery3d.video.IControllerRewindAndForward.IRewindAndForw import org.codeaurora.gallery3d.video.ScreenModeManager; import org.codeaurora.gallery3d.video.ScreenModeManager.ScreenModeListener; import org.codeaurora.gallery3d.video.CodeauroraVideoView; +import org.codeaurora.gallery3d.video.VideoSnapshotExt; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -161,6 +162,8 @@ public class MoviePlayer implements private IMovieItem mMovieItem; private int mVideoLastDuration;//for duration displayed in init state + private VideoSnapshotExt mVideoSnapshotExt = new VideoSnapshotExt(); + private enum TState { PLAYING, PAUSED, @@ -364,6 +367,7 @@ public class MoviePlayer implements } } mScreenModeExt.setScreenMode(); + mVideoSnapshotExt.init(mController, mVideoView, isLocalFile()); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) diff --git a/src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java b/src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java index aae691d55..00ca80122 100644 --- a/src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java +++ b/src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java @@ -1102,4 +1102,12 @@ public class CodeauroraVideoView extends SurfaceView implements MediaPlayerContr Log.v(TAG, "setOnBufferingUpdateListener(" + l + ")"); } } + + public MediaPlayer getMediaPlayer() { + return mMediaPlayer; + } + + public Uri getUri() { + return mUri; + } } diff --git a/src/org/codeaurora/gallery3d/video/IVideoSnapshotListener.java b/src/org/codeaurora/gallery3d/video/IVideoSnapshotListener.java new file mode 100644 index 000000000..b8675ce29 --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/IVideoSnapshotListener.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.codeaurora.gallery3d.video; + +public interface IVideoSnapshotListener { + void onVideoSnapshot(); + boolean canVideoSnapshot(); +} diff --git a/src/org/codeaurora/gallery3d/video/VideoSnapshotExt.java b/src/org/codeaurora/gallery3d/video/VideoSnapshotExt.java new file mode 100644 index 000000000..7788e79d5 --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/VideoSnapshotExt.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.codeaurora.gallery3d.video; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.graphics.Bitmap; +import android.media.MediaMetadataRetriever; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Environment; +import android.provider.MediaStore.Images; +import android.provider.MediaStore.Images.ImageColumns; +import android.util.Log; +import com.android.gallery3d.R; +import com.android.gallery3d.app.MovieControllerOverlay; +import com.android.gallery3d.app.MovieControllerOverlayNew; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class VideoSnapshotExt implements IVideoSnapshotListener { + private static String TAG = "VideoSnapshotExt"; + private static final boolean DEBUG = false; + + private static final String FOLDER = Environment.getExternalStorageDirectory() + + File.separator + "Pictures" + File.separator + "VideoSnapshots"; + private static final String FILE_PREFIX = "VideoSnapshot_"; + private static final String FILE_EXT = ".jpg"; + private static final String TIME_STAMP_NAME = "yyyyMMdd_HHmmss"; + private static final int CONTENT_VALUES_SIZE = 9; + + private CodeauroraVideoView mVideoView; + private Context mContext; + private ContentResolver mContentResolver; + + @Override + public void onVideoSnapshot() { + if (canVideoSnap()) { + int currentPosition = mVideoView.getMediaPlayer().getCurrentPosition(); + doVideoSnap(mVideoView.getUri(), currentPosition); + } + } + + @Override + public boolean canVideoSnapshot() { + return canVideoSnap(); + } + + public void init(MovieControllerOverlay controller, CodeauroraVideoView videoView, + boolean isLocalFile) { + mContext = videoView.getContext(); + mContentResolver = mContext.getContentResolver(); + + mVideoView = videoView; + if (controller instanceof MovieControllerOverlayNew) { + ((MovieControllerOverlayNew) controller).showVideoSnapshotButton(isLocalFile); + ((MovieControllerOverlayNew) controller).setVideoSnapshotListener(this); + } + } + + private boolean canVideoSnap() { + if (mVideoView.getMediaPlayer() == null) { + Log.w(TAG, "can't videoSnapshot because mMediaPlayer is null !"); + return false; + } + return true; + } + + private void doVideoSnap(Uri uri, int snapPosition) { + VideoSnapParameters parameters = new VideoSnapParameters(uri, snapPosition); + new VideoSnapTask().execute(parameters); + } + + private class VideoSnapParameters { + private int mSnapPosition; + private Uri mUri; + + /** + * @param snapPosition milliseconds + */ + public VideoSnapParameters(Uri uri, int snapPosition) { + mUri = uri; + mSnapPosition = snapPosition; + } + + public int getSnapPosition() { + return mSnapPosition; + } + + public Uri getUri() { + return mUri; + } + } + + private class VideoSnapTask extends AsyncTask<VideoSnapParameters, Void, Bitmap> { + SimpleDateFormat mDateFormat = new SimpleDateFormat(TIME_STAMP_NAME, Locale.getDefault()); + @Override + protected Bitmap doInBackground(VideoSnapParameters... params) { + VideoSnapParameters parameters = params[0]; + return doVideoSnap(parameters.getUri(), parameters.getSnapPosition()); + } + + @Override + protected void onPostExecute(Bitmap result) { + if (result != null) { + saveVideoSnap(mContentResolver, result); + } + } + + private boolean isPositionValid(int currentPosition, long duration) { + return (currentPosition <= duration) && (currentPosition >= 0); + } + + private Bitmap doVideoSnap(Uri videoUri, int snapPosition) { + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + retriever.setDataSource(mContext, videoUri); + String durationString = + retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); + long duration = + (durationString == null) ? 0 : Long.parseLong(durationString); // milliseconds + if (DEBUG) { + Log.d(TAG, "doVideoSnap at " + snapPosition + "/" + duration); + } + if (isPositionValid(snapPosition, duration)) { + // convert ms to us. + Bitmap bitmap = retriever.getFrameAtTime(snapPosition * 1000, + MediaMetadataRetriever.OPTION_CLOSEST); + if (bitmap == null) { + Log.w(TAG, "frame cannot be retrieved. "); + } else { + retriever.release(); + if (DEBUG) { + Log.d(TAG, "retriever get frame resolution : " + + bitmap.getHeight() + "x" + bitmap.getWidth()); + } + return bitmap; + } + } + retriever.release(); + return null; + } + + private void saveVideoSnap(ContentResolver resolver, Bitmap videoSnap) { + File folder = new File(FOLDER); + if (!folder.exists()) { + folder.mkdirs(); + } + + long dateTaken = System.currentTimeMillis(); + String fileName = mDateFormat.format(new Date(dateTaken)); + String fileTitle = FILE_PREFIX + fileName; + File file = new File(folder, fileTitle + FILE_EXT); + + try { + FileOutputStream fos = new FileOutputStream(file); + videoSnap.compress(Bitmap.CompressFormat.JPEG, 85, fos); + fos.close(); + + addImage(resolver, file.getPath(), fileTitle, dateTaken, file.length(), 0, + videoSnap.getWidth(), videoSnap.getHeight()); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private static void addImage(ContentResolver resolver, String path, String title, long date, + long size, int orientation, int width, int height) { + // Insert into MediaStore. + ContentValues values = new ContentValues(CONTENT_VALUES_SIZE); + values.put(ImageColumns.TITLE, title); + values.put(ImageColumns.DISPLAY_NAME, title + ".jpg"); + values.put(ImageColumns.DATE_TAKEN, date); + values.put(ImageColumns.MIME_TYPE, "image/jpeg"); + values.put(ImageColumns.ORIENTATION, orientation); + values.put(ImageColumns.DATA, path); + values.put(ImageColumns.SIZE, size); + values.put(ImageColumns.WIDTH, width); + values.put(ImageColumns.HEIGHT, height); + + try { + resolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values); + } catch (Throwable th) { + Log.w(TAG, "Failed to write MediaStore" + th); + } + } +} |
