• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.gallery3d.app;
18 
19 import android.app.ActionBar;
20 import android.app.Activity;
21 import android.app.ProgressDialog;
22 import android.content.ContentResolver;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.database.Cursor;
27 import android.media.MediaPlayer;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.os.Environment;
31 import android.os.Handler;
32 import android.provider.MediaStore;
33 import android.provider.MediaStore.Video;
34 import android.provider.MediaStore.Video.VideoColumns;
35 import android.view.View;
36 import android.view.ViewGroup;
37 import android.view.Window;
38 import android.widget.TextView;
39 import android.widget.Toast;
40 import android.widget.VideoView;
41 
42 import com.android.gallery3d.R;
43 import com.android.gallery3d.util.BucketNames;
44 
45 import java.io.File;
46 import java.io.IOException;
47 import java.sql.Date;
48 import java.text.SimpleDateFormat;
49 
50 public class TrimVideo extends Activity implements
51         MediaPlayer.OnErrorListener,
52         MediaPlayer.OnCompletionListener,
53         ControllerOverlay.Listener {
54 
55     private VideoView mVideoView;
56     private TrimControllerOverlay mController;
57     private Context mContext;
58     private Uri mUri;
59     private final Handler mHandler = new Handler();
60     public static final String TRIM_ACTION = "com.android.camera.action.TRIM";
61 
62     public ProgressDialog mProgress;
63 
64     private int mTrimStartTime = 0;
65     private int mTrimEndTime = 0;
66     private int mVideoPosition = 0;
67     public static final String KEY_TRIM_START = "trim_start";
68     public static final String KEY_TRIM_END = "trim_end";
69     public static final String KEY_VIDEO_POSITION = "video_pos";
70     private boolean mHasPaused = false;
71 
72     private String mSrcVideoPath = null;
73     private String mSaveFileName = null;
74     private static final String TIME_STAMP_NAME = "'TRIM'_yyyyMMdd_HHmmss";
75     private File mSrcFile = null;
76     private File mDstFile = null;
77     private File mSaveDirectory = null;
78     // For showing the result.
79     private String saveFolderName = null;
80 
81     @Override
onCreate(Bundle savedInstanceState)82     public void onCreate(Bundle savedInstanceState) {
83         mContext = getApplicationContext();
84         super.onCreate(savedInstanceState);
85 
86         requestWindowFeature(Window.FEATURE_ACTION_BAR);
87         requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
88 
89         ActionBar actionBar = getActionBar();
90         int displayOptions = ActionBar.DISPLAY_SHOW_HOME;
91         actionBar.setDisplayOptions(0, displayOptions);
92         displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM;
93         actionBar.setDisplayOptions(displayOptions, displayOptions);
94         actionBar.setCustomView(R.layout.trim_menu);
95 
96         TextView mSaveVideoTextView = (TextView) findViewById(R.id.start_trim);
97         mSaveVideoTextView.setOnClickListener(new View.OnClickListener() {
98             @Override
99             public void onClick(View arg0) {
100                 trimVideo();
101             }
102         });
103 
104         Intent intent = getIntent();
105         mUri = intent.getData();
106         mSrcVideoPath = intent.getStringExtra(PhotoPage.KEY_MEDIA_ITEM_PATH);
107         setContentView(R.layout.trim_view);
108         View rootView = findViewById(R.id.trim_view_root);
109 
110         mVideoView = (VideoView) rootView.findViewById(R.id.surface_view);
111 
112         mController = new TrimControllerOverlay(mContext);
113         ((ViewGroup) rootView).addView(mController.getView());
114         mController.setListener(this);
115         mController.setCanReplay(true);
116 
117         mVideoView.setOnErrorListener(this);
118         mVideoView.setOnCompletionListener(this);
119         mVideoView.setVideoURI(mUri);
120 
121         playVideo();
122     }
123 
124     @Override
onResume()125     public void onResume() {
126         super.onResume();
127         if (mHasPaused) {
128             mVideoView.seekTo(mVideoPosition);
129             mVideoView.resume();
130             mHasPaused = false;
131         }
132         mHandler.post(mProgressChecker);
133     }
134 
135     @Override
onPause()136     public void onPause() {
137         mHasPaused = true;
138         mHandler.removeCallbacksAndMessages(null);
139         mVideoPosition = mVideoView.getCurrentPosition();
140         mVideoView.suspend();
141         super.onPause();
142     }
143 
144     @Override
onStop()145     public void onStop() {
146         if (mProgress != null) {
147             mProgress.dismiss();
148             mProgress = null;
149         }
150         super.onStop();
151     }
152 
153     @Override
onDestroy()154     public void onDestroy() {
155         mVideoView.stopPlayback();
156         super.onDestroy();
157     }
158 
159     private final Runnable mProgressChecker = new Runnable() {
160         @Override
161         public void run() {
162             int pos = setProgress();
163             mHandler.postDelayed(mProgressChecker, 200 - (pos % 200));
164         }
165     };
166 
167     @Override
onSaveInstanceState(Bundle savedInstanceState)168     public void onSaveInstanceState(Bundle savedInstanceState) {
169         savedInstanceState.putInt(KEY_TRIM_START, mTrimStartTime);
170         savedInstanceState.putInt(KEY_TRIM_END, mTrimEndTime);
171         savedInstanceState.putInt(KEY_VIDEO_POSITION, mVideoPosition);
172         super.onSaveInstanceState(savedInstanceState);
173     }
174 
175     @Override
onRestoreInstanceState(Bundle savedInstanceState)176     public void onRestoreInstanceState(Bundle savedInstanceState) {
177         super.onRestoreInstanceState(savedInstanceState);
178         mTrimStartTime = savedInstanceState.getInt(KEY_TRIM_START, 0);
179         mTrimEndTime = savedInstanceState.getInt(KEY_TRIM_END, 0);
180         mVideoPosition = savedInstanceState.getInt(KEY_VIDEO_POSITION, 0);
181     }
182 
183     // This updates the time bar display (if necessary). It is called by
184     // mProgressChecker and also from places where the time bar needs
185     // to be updated immediately.
setProgress()186     private int setProgress() {
187         mVideoPosition = mVideoView.getCurrentPosition();
188         // If the video position is smaller than the starting point of trimming,
189         // correct it.
190         if (mVideoPosition < mTrimStartTime) {
191             mVideoView.seekTo(mTrimStartTime);
192             mVideoPosition = mTrimStartTime;
193         }
194         // If the position is bigger than the end point of trimming, show the
195         // replay button and pause.
196         if (mVideoPosition >= mTrimEndTime && mTrimEndTime > 0) {
197             if (mVideoPosition > mTrimEndTime) {
198                 mVideoView.seekTo(mTrimEndTime);
199                 mVideoPosition = mTrimEndTime;
200             }
201             mController.showEnded();
202             mVideoView.pause();
203         }
204 
205         int duration = mVideoView.getDuration();
206         if (duration > 0 && mTrimEndTime == 0) {
207             mTrimEndTime = duration;
208         }
209         mController.setTimes(mVideoPosition, duration, mTrimStartTime, mTrimEndTime);
210         return mVideoPosition;
211     }
212 
playVideo()213     private void playVideo() {
214         mVideoView.start();
215         mController.showPlaying();
216         setProgress();
217     }
218 
pauseVideo()219     private void pauseVideo() {
220         mVideoView.pause();
221         mController.showPaused();
222     }
223 
224     // Copy from SaveCopyTask.java in terms of how to handle the destination
225     // path and filename : querySource() and getSaveDirectory().
226     private interface ContentResolverQueryCallback {
onCursorResult(Cursor cursor)227         void onCursorResult(Cursor cursor);
228     }
229 
querySource(String[] projection, ContentResolverQueryCallback callback)230     private void querySource(String[] projection, ContentResolverQueryCallback callback) {
231         ContentResolver contentResolver = getContentResolver();
232         Cursor cursor = null;
233         try {
234             cursor = contentResolver.query(mUri, projection, null, null, null);
235             if ((cursor != null) && cursor.moveToNext()) {
236                 callback.onCursorResult(cursor);
237             }
238         } catch (Exception e) {
239             // Ignore error for lacking the data column from the source.
240         } finally {
241             if (cursor != null) {
242                 cursor.close();
243             }
244         }
245     }
246 
getSaveDirectory()247     private File getSaveDirectory() {
248         final File[] dir = new File[1];
249         querySource(new String[] {
250                 VideoColumns.DATA }, new ContentResolverQueryCallback() {
251 
252             @Override
253             public void onCursorResult(Cursor cursor) {
254                 dir[0] = new File(cursor.getString(0)).getParentFile();
255             }
256         });
257         return dir[0];
258     }
259 
trimVideo()260     private void trimVideo() {
261         int delta = mTrimEndTime - mTrimStartTime;
262         // Considering that we only trim at sync frame, we don't want to trim
263         // when the time interval is too short or too close to the origin.
264         if (delta < 100 ) {
265             Toast.makeText(getApplicationContext(),
266                 getString(R.string.trim_too_short),
267                 Toast.LENGTH_SHORT).show();
268             return;
269         }
270         if (Math.abs(mVideoView.getDuration() - delta) < 100) {
271             // If no change has been made, go back
272             onBackPressed();
273             return;
274         }
275         // Use the default save directory if the source directory cannot be
276         // saved.
277         mSaveDirectory = getSaveDirectory();
278         if ((mSaveDirectory == null) || !mSaveDirectory.canWrite()) {
279             mSaveDirectory = new File(Environment.getExternalStorageDirectory(),
280                     BucketNames.DOWNLOAD);
281             saveFolderName = getString(R.string.folder_download);
282         } else {
283             saveFolderName = mSaveDirectory.getName();
284         }
285         mSaveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format(
286                 new Date(System.currentTimeMillis()));
287 
288         mDstFile = new File(mSaveDirectory, mSaveFileName + ".mp4");
289         mSrcFile = new File(mSrcVideoPath);
290 
291         showProgressDialog();
292 
293         new Thread(new Runnable() {
294             @Override
295             public void run() {
296                 try {
297                     TrimVideoUtils.startTrim(mSrcFile, mDstFile, mTrimStartTime, mTrimEndTime);
298                     // Update the database for adding a new video file.
299                     insertContent(mDstFile);
300                 } catch (IOException e) {
301                     e.printStackTrace();
302                 }
303                 // After trimming is done, trigger the UI changed.
304                 mHandler.post(new Runnable() {
305                     @Override
306                     public void run() {
307                         Toast.makeText(getApplicationContext(),
308                             getString(R.string.save_into) + " " + saveFolderName,
309                             Toast.LENGTH_SHORT)
310                             .show();
311                         // TODO: change trimming into a service to avoid
312                         // this progressDialog and add notification properly.
313                         if (mProgress != null) {
314                             mProgress.dismiss();
315                             mProgress = null;
316                             // Show the result only when the activity not stopped.
317                             Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
318                             intent.setDataAndTypeAndNormalize(Uri.fromFile(mDstFile), "video/*");
319                             intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false);
320                             startActivity(intent);
321                             finish();
322                         }
323                     }
324                 });
325             }
326         }).start();
327     }
328 
showProgressDialog()329     private void showProgressDialog() {
330         // create a background thread to trim the video.
331         // and show the progress.
332         mProgress = new ProgressDialog(this);
333         mProgress.setTitle(getString(R.string.trimming));
334         mProgress.setMessage(getString(R.string.please_wait));
335         // TODO: make this cancelable.
336         mProgress.setCancelable(false);
337         mProgress.setCanceledOnTouchOutside(false);
338         mProgress.show();
339     }
340 
341     /**
342      * Insert the content (saved file) with proper video properties.
343      */
insertContent(File file)344     private Uri insertContent(File file) {
345         long nowInMs = System.currentTimeMillis();
346         long nowInSec = nowInMs / 1000;
347         final ContentValues values = new ContentValues(12);
348         values.put(Video.Media.TITLE, mSaveFileName);
349         values.put(Video.Media.DISPLAY_NAME, file.getName());
350         values.put(Video.Media.MIME_TYPE, "video/mp4");
351         values.put(Video.Media.DATE_TAKEN, nowInMs);
352         values.put(Video.Media.DATE_MODIFIED, nowInSec);
353         values.put(Video.Media.DATE_ADDED, nowInSec);
354         values.put(Video.Media.DATA, file.getAbsolutePath());
355         values.put(Video.Media.SIZE, file.length());
356         // Copy the data taken and location info from src.
357         String[] projection = new String[] {
358                 VideoColumns.DATE_TAKEN,
359                 VideoColumns.LATITUDE,
360                 VideoColumns.LONGITUDE,
361                 VideoColumns.RESOLUTION,
362         };
363 
364         // Copy some info from the source file.
365         querySource(projection, new ContentResolverQueryCallback() {
366             @Override
367             public void onCursorResult(Cursor cursor) {
368                 long timeTaken = cursor.getLong(0);
369                 if (timeTaken > 0) {
370                     values.put(Video.Media.DATE_TAKEN, timeTaken);
371                 }
372                 double latitude = cursor.getDouble(1);
373                 double longitude = cursor.getDouble(2);
374                 // TODO: Change || to && after the default location issue is
375                 // fixed.
376                 if ((latitude != 0f) || (longitude != 0f)) {
377                     values.put(Video.Media.LATITUDE, latitude);
378                     values.put(Video.Media.LONGITUDE, longitude);
379                 }
380                 values.put(Video.Media.RESOLUTION, cursor.getString(3));
381 
382             }
383         });
384 
385         return getContentResolver().insert(Video.Media.EXTERNAL_CONTENT_URI, values);
386     }
387 
388     @Override
onPlayPause()389     public void onPlayPause() {
390         if (mVideoView.isPlaying()) {
391             pauseVideo();
392         } else {
393             playVideo();
394         }
395     }
396 
397     @Override
onSeekStart()398     public void onSeekStart() {
399         pauseVideo();
400     }
401 
402     @Override
onSeekMove(int time)403     public void onSeekMove(int time) {
404         mVideoView.seekTo(time);
405     }
406 
407     @Override
onSeekEnd(int time, int start, int end)408     public void onSeekEnd(int time, int start, int end) {
409         mVideoView.seekTo(time);
410         mTrimStartTime = start;
411         mTrimEndTime = end;
412         setProgress();
413     }
414 
415     @Override
onShown()416     public void onShown() {
417     }
418 
419     @Override
onHidden()420     public void onHidden() {
421     }
422 
423     @Override
onReplay()424     public void onReplay() {
425         mVideoView.seekTo(mTrimStartTime);
426         playVideo();
427     }
428 
429     @Override
onCompletion(MediaPlayer mp)430     public void onCompletion(MediaPlayer mp) {
431         mController.showEnded();
432     }
433 
434     @Override
onError(MediaPlayer mp, int what, int extra)435     public boolean onError(MediaPlayer mp, int what, int extra) {
436         return false;
437     }
438 }
439