TrimVideo.java revision 15ff1b1ca52bea348b8a490b5b5abe53fa43eaf2
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
17package com.android.gallery3d.app;
18
19import android.app.ActionBar;
20import android.app.Activity;
21import android.app.ProgressDialog;
22import android.content.ContentResolver;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.Intent;
26import android.database.Cursor;
27import android.media.MediaPlayer;
28import android.net.Uri;
29import android.os.Bundle;
30import android.os.Environment;
31import android.os.Handler;
32import android.provider.MediaStore.Video;
33import android.provider.MediaStore.Video.VideoColumns;
34import android.view.Menu;
35import android.view.MenuInflater;
36import android.view.MenuItem;
37import android.view.View;
38import android.view.ViewGroup;
39import android.widget.Toast;
40import android.widget.VideoView;
41
42import com.android.gallery3d.R;
43import com.android.gallery3d.util.BucketNames;
44
45import java.io.File;
46import java.io.IOException;
47import java.sql.Date;
48import java.text.SimpleDateFormat;
49
50public 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
82    public void onCreate(Bundle savedInstanceState) {
83        mContext = getApplicationContext();
84        super.onCreate(savedInstanceState);
85
86        ActionBar actionBar = getActionBar();
87        actionBar.setDisplayHomeAsUpEnabled(true);
88
89        Intent intent = getIntent();
90        mUri = intent.getData();
91        mSrcVideoPath = intent.getStringExtra(PhotoPage.KEY_MEDIA_ITEM_PATH);
92        setContentView(R.layout.trim_view);
93        View rootView = findViewById(R.id.trim_view_root);
94
95        mVideoView = (VideoView) rootView.findViewById(R.id.surface_view);
96
97        mController = new TrimControllerOverlay(mContext);
98        ((ViewGroup) rootView).addView(mController.getView());
99        mController.setListener(this);
100        mController.setCanReplay(true);
101
102        mVideoView.setOnErrorListener(this);
103        mVideoView.setOnCompletionListener(this);
104        mVideoView.setVideoURI(mUri);
105
106        playVideo();
107    }
108
109    @Override
110    public void onResume() {
111        super.onResume();
112        if (mHasPaused) {
113            mVideoView.seekTo(mVideoPosition);
114            mVideoView.resume();
115            mHasPaused = false;
116        }
117        mHandler.post(mProgressChecker);
118    }
119
120    @Override
121    public void onPause() {
122        mHasPaused = true;
123        mHandler.removeCallbacksAndMessages(null);
124        mVideoPosition = mVideoView.getCurrentPosition();
125        mVideoView.suspend();
126        super.onPause();
127    }
128
129    @Override
130    public void onStop() {
131        if (mProgress != null) {
132            mProgress.dismiss();
133            mProgress = null;
134        }
135        super.onStop();
136    }
137
138    @Override
139    public void onDestroy() {
140        mVideoView.stopPlayback();
141        super.onDestroy();
142    }
143
144    private final Runnable mProgressChecker = new Runnable() {
145        @Override
146        public void run() {
147            int pos = setProgress();
148            mHandler.postDelayed(mProgressChecker, 200 - (pos % 200));
149        }
150    };
151
152    @Override
153    public void onSaveInstanceState(Bundle savedInstanceState) {
154        savedInstanceState.putInt(KEY_TRIM_START, mTrimStartTime);
155        savedInstanceState.putInt(KEY_TRIM_END, mTrimEndTime);
156        savedInstanceState.putInt(KEY_VIDEO_POSITION, mVideoPosition);
157        super.onSaveInstanceState(savedInstanceState);
158    }
159
160    @Override
161    public void onRestoreInstanceState(Bundle savedInstanceState) {
162        super.onRestoreInstanceState(savedInstanceState);
163        mTrimStartTime = savedInstanceState.getInt(KEY_TRIM_START, 0);
164        mTrimEndTime = savedInstanceState.getInt(KEY_TRIM_END, 0);
165        mVideoPosition = savedInstanceState.getInt(KEY_VIDEO_POSITION, 0);
166    }
167
168    // This updates the time bar display (if necessary). It is called by
169    // mProgressChecker and also from places where the time bar needs
170    // to be updated immediately.
171    private int setProgress() {
172        mVideoPosition = mVideoView.getCurrentPosition();
173        // If the video position is smaller than the starting point of trimming,
174        // correct it.
175        if (mVideoPosition < mTrimStartTime) {
176            mVideoView.seekTo(mTrimStartTime);
177            mVideoPosition = mTrimStartTime;
178        }
179        // If the position is bigger than the end point of trimming, show the
180        // replay button and pause.
181        if (mVideoPosition >= mTrimEndTime && mTrimEndTime > 0) {
182            if (mVideoPosition > mTrimEndTime) {
183                mVideoView.seekTo(mTrimEndTime);
184                mVideoPosition = mTrimEndTime;
185            }
186            mController.showEnded();
187            mVideoView.pause();
188        }
189
190        int duration = mVideoView.getDuration();
191        if (duration > 0 && mTrimEndTime == 0) {
192            mTrimEndTime = duration;
193        }
194        mController.setTimes(mVideoPosition, duration, mTrimStartTime, mTrimEndTime);
195        return mVideoPosition;
196    }
197
198    private void playVideo() {
199        mVideoView.start();
200        mController.showPlaying();
201        setProgress();
202    }
203
204    private void pauseVideo() {
205        mVideoView.pause();
206        mController.showPaused();
207    }
208
209    @Override
210    public boolean onCreateOptionsMenu(Menu menu) {
211        super.onCreateOptionsMenu(menu);
212        MenuInflater inflater = getMenuInflater();
213        inflater.inflate(R.menu.trim, menu);
214        return true;
215    };
216
217    // Copy from SaveCopyTask.java in terms of how to handle the destination
218    // path and filename : querySource() and getSaveDirectory().
219    private interface ContentResolverQueryCallback {
220        void onCursorResult(Cursor cursor);
221    }
222
223    private void querySource(String[] projection, ContentResolverQueryCallback callback) {
224        ContentResolver contentResolver = getContentResolver();
225        Cursor cursor = null;
226        try {
227            cursor = contentResolver.query(mUri, projection, null, null, null);
228            if ((cursor != null) && cursor.moveToNext()) {
229                callback.onCursorResult(cursor);
230            }
231        } catch (Exception e) {
232            // Ignore error for lacking the data column from the source.
233        } finally {
234            if (cursor != null) {
235                cursor.close();
236            }
237        }
238    }
239
240    private File getSaveDirectory() {
241        final File[] dir = new File[1];
242        querySource(new String[] {
243        VideoColumns.DATA }, new ContentResolverQueryCallback() {
244
245                @Override
246            public void onCursorResult(Cursor cursor) {
247                dir[0] = new File(cursor.getString(0)).getParentFile();
248            }
249        });
250        return dir[0];
251    }
252
253    @Override
254    public boolean onOptionsItemSelected(MenuItem item) {
255        int id = item.getItemId();
256        if (id == android.R.id.home) {
257            finish();
258            return true;
259        } else if (id == R.id.action_trim_video) {
260            trimVideo();
261            return true;
262        }
263        return false;
264    }
265
266    private void trimVideo() {
267        // Use the default save directory if the source directory cannot be
268        // saved.
269        mSaveDirectory = getSaveDirectory();
270        if ((mSaveDirectory == null) || !mSaveDirectory.canWrite()) {
271            mSaveDirectory = new File(Environment.getExternalStorageDirectory(),
272                    BucketNames.DOWNLOAD);
273            saveFolderName = getString(R.string.folder_download);
274        } else {
275            saveFolderName = mSaveDirectory.getName();
276        }
277        mSaveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format(
278                new Date(System.currentTimeMillis()));
279
280        mDstFile = new File(mSaveDirectory, mSaveFileName + ".mp4");
281        mSrcFile = new File(mSrcVideoPath);
282
283        showProgressDialog();
284
285        new Thread(new Runnable() {
286            @Override
287            public void run() {
288                try {
289                    ShortenExample.main(null, mSrcFile, mDstFile, mTrimStartTime, mTrimEndTime);
290                } catch (IOException e) {
291                    e.printStackTrace();
292                }
293                // After trimming is done, trigger the UI changed.
294                mHandler.post(new Runnable() {
295                    @Override
296                    public void run() {
297                        // TODO: change trimming into a service to avoid
298                        // this progressDialog and add notification properly.
299                        if (mProgress != null) {
300                            mProgress.dismiss();
301                            // Update the database for adding a new video file.
302                            insertContent(mDstFile);
303                            Toast.makeText(getApplicationContext(),
304                                    "Saved into " + saveFolderName, Toast.LENGTH_SHORT)
305                                    .show();
306                            mProgress = null;
307                        }
308                    }
309                });
310            }
311        }).start();
312    }
313
314    private void showProgressDialog() {
315        // create a background thread to trim the video.
316        // and show the progress.
317        mProgress = new ProgressDialog(this);
318        mProgress.setTitle("Trimming");
319        mProgress.setMessage("please wait");
320        // TODO: make this cancelable.
321        mProgress.setCancelable(false);
322        mProgress.setCanceledOnTouchOutside(false);
323        mProgress.show();
324    }
325
326    /**
327     * Insert the content (saved file) with proper video properties.
328     */
329    private Uri insertContent(File file) {
330        long now = System.currentTimeMillis() / 1000;
331
332        final ContentValues values = new ContentValues(12);
333        values.put(Video.Media.TITLE, mSaveFileName);
334        values.put(Video.Media.DISPLAY_NAME, file.getName());
335        values.put(Video.Media.MIME_TYPE, "video/mp4");
336        values.put(Video.Media.DATE_TAKEN, now);
337        values.put(Video.Media.DATE_MODIFIED, now);
338        values.put(Video.Media.DATE_ADDED, now);
339        values.put(Video.Media.DATA, file.getAbsolutePath());
340        values.put(Video.Media.SIZE, file.length());
341        // Copy the data taken and location info from src.
342        String[] projection = new String[] {
343                VideoColumns.DATE_TAKEN,
344                VideoColumns.LATITUDE,
345                VideoColumns.LONGITUDE,
346                VideoColumns.RESOLUTION,
347        };
348
349        // Copy some info from the source file.
350        querySource(projection, new ContentResolverQueryCallback() {
351
352            @Override
353            public void onCursorResult(Cursor cursor) {
354                values.put(Video.Media.DATE_TAKEN, cursor.getLong(0));
355                double latitude = cursor.getDouble(1);
356                double longitude = cursor.getDouble(2);
357                // TODO: Change || to && after the default location issue is
358                // fixed.
359                if ((latitude != 0f) || (longitude != 0f)) {
360                    values.put(Video.Media.LATITUDE, latitude);
361                    values.put(Video.Media.LONGITUDE, longitude);
362                }
363                values.put(Video.Media.RESOLUTION, cursor.getString(3));
364
365            }
366        });
367
368        return getContentResolver().insert(Video.Media.EXTERNAL_CONTENT_URI, values);
369    }
370
371    @Override
372    public void onPlayPause() {
373        if (mVideoView.isPlaying()) {
374            pauseVideo();
375        } else {
376            playVideo();
377        }
378    }
379
380    @Override
381    public void onSeekStart() {
382        pauseVideo();
383    }
384
385    @Override
386    public void onSeekMove(int time) {
387        mVideoView.seekTo(time);
388    }
389
390    @Override
391    public void onSeekEnd(int time, int start, int end) {
392        mVideoView.seekTo(time);
393        mTrimStartTime = start;
394        mTrimEndTime = end;
395        setProgress();
396    }
397
398    @Override
399    public void onShown() {
400    }
401
402
403    @Override
404    public void onHidden() {
405    }
406
407    @Override
408    public void onReplay() {
409        mVideoView.seekTo(mTrimStartTime);
410        playVideo();
411    }
412
413    @Override
414    public void onCompletion(MediaPlayer mp) {
415        mController.showEnded();
416    }
417
418    @Override
419    public boolean onError(MediaPlayer mp, int what, int extra) {
420        return false;
421    }
422}
423