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