/* * 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) { Toast.makeText(getApplicationContext(), getString(R.string.trim_too_long), Toast.LENGTH_SHORT).show(); 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 now = System.currentTimeMillis() / 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, now); values.put(Video.Media.DATE_MODIFIED, now); values.put(Video.Media.DATE_ADDED, now); 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) { values.put(Video.Media.DATE_TAKEN, cursor.getLong(0)); 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; } }