| /* |
| * 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.ContentResolver; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.database.Cursor; |
| import android.media.MediaPlayer; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Environment; |
| import android.os.Handler; |
| import android.provider.MediaStore; |
| import android.provider.MediaStore.Video; |
| import android.provider.MediaStore.Video.VideoColumns; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.Window; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| import android.widget.VideoView; |
| |
| import com.android.gallery3d.R; |
| import com.android.gallery3d.util.BucketNames; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.sql.Date; |
| import java.text.SimpleDateFormat; |
| |
| 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; |
| |
| private String mSrcVideoPath = null; |
| private String mSaveFileName = null; |
| private static final String TIME_STAMP_NAME = "'TRIM'_yyyyMMdd_HHmmss"; |
| private File mSrcFile = null; |
| private File mDstFile = null; |
| private File mSaveDirectory = null; |
| // For showing the result. |
| private String saveFolderName = null; |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| mContext = getApplicationContext(); |
| super.onCreate(savedInstanceState); |
| |
| requestWindowFeature(Window.FEATURE_ACTION_BAR); |
| requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); |
| |
| ActionBar actionBar = getActionBar(); |
| int displayOptions = ActionBar.DISPLAY_SHOW_HOME; |
| actionBar.setDisplayOptions(0, displayOptions); |
| displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM; |
| actionBar.setDisplayOptions(displayOptions, displayOptions); |
| actionBar.setCustomView(R.layout.trim_menu); |
| |
| TextView mSaveVideoTextView = (TextView) findViewById(R.id.start_trim); |
| mSaveVideoTextView.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View arg0) { |
| trimVideo(); |
| } |
| }); |
| |
| Intent intent = getIntent(); |
| mUri = intent.getData(); |
| mSrcVideoPath = intent.getStringExtra(PhotoPage.KEY_MEDIA_ITEM_PATH); |
| 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 onStop() { |
| if (mProgress != null) { |
| mProgress.dismiss(); |
| mProgress = null; |
| } |
| super.onStop(); |
| } |
| |
| @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(); |
| } |
| |
| // Copy from SaveCopyTask.java in terms of how to handle the destination |
| // path and filename : querySource() and getSaveDirectory(). |
| private interface ContentResolverQueryCallback { |
| void onCursorResult(Cursor cursor); |
| } |
| |
| private void querySource(String[] projection, ContentResolverQueryCallback callback) { |
| ContentResolver contentResolver = getContentResolver(); |
| Cursor cursor = null; |
| try { |
| cursor = contentResolver.query(mUri, projection, null, null, null); |
| if ((cursor != null) && cursor.moveToNext()) { |
| callback.onCursorResult(cursor); |
| } |
| } catch (Exception e) { |
| // Ignore error for lacking the data column from the source. |
| } finally { |
| if (cursor != null) { |
| cursor.close(); |
| } |
| } |
| } |
| |
| private File getSaveDirectory() { |
| final File[] dir = new File[1]; |
| querySource(new String[] { |
| VideoColumns.DATA }, new ContentResolverQueryCallback() { |
| |
| @Override |
| public void onCursorResult(Cursor cursor) { |
| dir[0] = new File(cursor.getString(0)).getParentFile(); |
| } |
| }); |
| return dir[0]; |
| } |
| |
| private void trimVideo() { |
| int delta = mTrimEndTime - mTrimStartTime; |
| // Considering that we only trim at sync frame, we don't want to trim |
| // when the time interval is too short or too close to the origin. |
| if (delta < 100 ) { |
| Toast.makeText(getApplicationContext(), |
| getString(R.string.trim_too_short), |
| Toast.LENGTH_SHORT).show(); |
| return; |
| } |
| if (Math.abs(mVideoView.getDuration() - delta) < 100) { |
| // If no change has been made, go back |
| onBackPressed(); |
| return; |
| } |
| // Use the default save directory if the source directory cannot be |
| // saved. |
| mSaveDirectory = getSaveDirectory(); |
| if ((mSaveDirectory == null) || !mSaveDirectory.canWrite()) { |
| mSaveDirectory = new File(Environment.getExternalStorageDirectory(), |
| BucketNames.DOWNLOAD); |
| saveFolderName = getString(R.string.folder_download); |
| } else { |
| saveFolderName = mSaveDirectory.getName(); |
| } |
| mSaveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format( |
| new Date(System.currentTimeMillis())); |
| |
| mDstFile = new File(mSaveDirectory, mSaveFileName + ".mp4"); |
| mSrcFile = new File(mSrcVideoPath); |
| |
| showProgressDialog(); |
| |
| new Thread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| TrimVideoUtils.startTrim(mSrcFile, mDstFile, mTrimStartTime, mTrimEndTime); |
| // Update the database for adding a new video file. |
| insertContent(mDstFile); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| // After trimming is done, trigger the UI changed. |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| Toast.makeText(getApplicationContext(), |
| getString(R.string.save_into) + " " + saveFolderName, |
| Toast.LENGTH_SHORT) |
| .show(); |
| // TODO: change trimming into a service to avoid |
| // this progressDialog and add notification properly. |
| if (mProgress != null) { |
| mProgress.dismiss(); |
| mProgress = null; |
| // Show the result only when the activity not stopped. |
| Intent intent = new Intent(android.content.Intent.ACTION_VIEW); |
| intent.setDataAndTypeAndNormalize(Uri.fromFile(mDstFile), "video/*"); |
| intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false); |
| startActivity(intent); |
| finish(); |
| } |
| } |
| }); |
| } |
| }).start(); |
| } |
| |
| private void showProgressDialog() { |
| // create a background thread to trim the video. |
| // and show the progress. |
| mProgress = new ProgressDialog(this); |
| mProgress.setTitle(getString(R.string.trimming)); |
| mProgress.setMessage(getString(R.string.please_wait)); |
| // TODO: make this cancelable. |
| mProgress.setCancelable(false); |
| mProgress.setCanceledOnTouchOutside(false); |
| mProgress.show(); |
| } |
| |
| /** |
| * Insert the content (saved file) with proper video properties. |
| */ |
| private Uri insertContent(File file) { |
| long nowInMs = System.currentTimeMillis(); |
| long nowInSec = nowInMs / 1000; |
| final ContentValues values = new ContentValues(12); |
| values.put(Video.Media.TITLE, mSaveFileName); |
| values.put(Video.Media.DISPLAY_NAME, file.getName()); |
| values.put(Video.Media.MIME_TYPE, "video/mp4"); |
| values.put(Video.Media.DATE_TAKEN, nowInMs); |
| values.put(Video.Media.DATE_MODIFIED, nowInSec); |
| values.put(Video.Media.DATE_ADDED, nowInSec); |
| values.put(Video.Media.DATA, file.getAbsolutePath()); |
| values.put(Video.Media.SIZE, file.length()); |
| // Copy the data taken and location info from src. |
| String[] projection = new String[] { |
| VideoColumns.DATE_TAKEN, |
| VideoColumns.LATITUDE, |
| VideoColumns.LONGITUDE, |
| VideoColumns.RESOLUTION, |
| }; |
| |
| // Copy some info from the source file. |
| querySource(projection, new ContentResolverQueryCallback() { |
| @Override |
| public void onCursorResult(Cursor cursor) { |
| long timeTaken = cursor.getLong(0); |
| if (timeTaken > 0) { |
| values.put(Video.Media.DATE_TAKEN, timeTaken); |
| } |
| double latitude = cursor.getDouble(1); |
| double longitude = cursor.getDouble(2); |
| // TODO: Change || to && after the default location issue is |
| // fixed. |
| if ((latitude != 0f) || (longitude != 0f)) { |
| values.put(Video.Media.LATITUDE, latitude); |
| values.put(Video.Media.LONGITUDE, longitude); |
| } |
| values.put(Video.Media.RESOLUTION, cursor.getString(3)); |
| |
| } |
| }); |
| |
| return getContentResolver().insert(Video.Media.EXTERNAL_CONTENT_URI, values); |
| } |
| |
| @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; |
| } |
| } |