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.Context;
23 import android.content.Intent;
24 import android.media.MediaPlayer;
25 import android.net.Uri;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.provider.MediaStore;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.view.Window;
32 import android.widget.TextView;
33 import android.widget.Toast;
34 import android.widget.VideoView;
35 
36 import com.android.gallery3d.R;
37 import com.android.gallery3d.util.SaveVideoFileInfo;
38 import com.android.gallery3d.util.SaveVideoFileUtils;
39 
40 import java.io.File;
41 import java.io.IOException;
42 
43 public class TrimVideo extends Activity implements
44         MediaPlayer.OnErrorListener,
45         MediaPlayer.OnCompletionListener,
46         ControllerOverlay.Listener {
47 
48     private VideoView mVideoView;
49     private TextView mSaveVideoTextView;
50     private TrimControllerOverlay mController;
51     private Context mContext;
52     private Uri mUri;
53     private final Handler mHandler = new Handler();
54     public static final String TRIM_ACTION = "com.android.camera.action.TRIM";
55 
56     public ProgressDialog mProgress;
57 
58     private int mTrimStartTime = 0;
59     private int mTrimEndTime = 0;
60     private int mVideoPosition = 0;
61     public static final String KEY_TRIM_START = "trim_start";
62     public static final String KEY_TRIM_END = "trim_end";
63     public static final String KEY_VIDEO_POSITION = "video_pos";
64     private boolean mHasPaused = false;
65 
66     private String mSrcVideoPath = null;
67     private static final String TIME_STAMP_NAME = "'TRIM'_yyyyMMdd_HHmmss";
68     private SaveVideoFileInfo mDstFileInfo = null;
69 
70     @Override
onCreate(Bundle savedInstanceState)71     public void onCreate(Bundle savedInstanceState) {
72         mContext = getApplicationContext();
73         super.onCreate(savedInstanceState);
74 
75         requestWindowFeature(Window.FEATURE_ACTION_BAR);
76         requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
77 
78         ActionBar actionBar = getActionBar();
79         int displayOptions = ActionBar.DISPLAY_SHOW_HOME;
80         actionBar.setDisplayOptions(0, displayOptions);
81         displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM;
82         actionBar.setDisplayOptions(displayOptions, displayOptions);
83         actionBar.setCustomView(R.layout.trim_menu);
84 
85         mSaveVideoTextView = (TextView) findViewById(R.id.start_trim);
86         mSaveVideoTextView.setOnClickListener(new View.OnClickListener() {
87             @Override
88             public void onClick(View arg0) {
89                 trimVideo();
90             }
91         });
92         mSaveVideoTextView.setEnabled(false);
93 
94         Intent intent = getIntent();
95         mUri = intent.getData();
96         mSrcVideoPath = intent.getStringExtra(PhotoPage.KEY_MEDIA_ITEM_PATH);
97         setContentView(R.layout.trim_view);
98         View rootView = findViewById(R.id.trim_view_root);
99 
100         mVideoView = (VideoView) rootView.findViewById(R.id.surface_view);
101 
102         mController = new TrimControllerOverlay(mContext);
103         ((ViewGroup) rootView).addView(mController.getView());
104         mController.setListener(this);
105         mController.setCanReplay(true);
106 
107         mVideoView.setOnErrorListener(this);
108         mVideoView.setOnCompletionListener(this);
109         mVideoView.setVideoURI(mUri);
110 
111         playVideo();
112     }
113 
114     @Override
onResume()115     public void onResume() {
116         super.onResume();
117         if (mHasPaused) {
118             mVideoView.seekTo(mVideoPosition);
119             mVideoView.resume();
120             mHasPaused = false;
121         }
122         mHandler.post(mProgressChecker);
123     }
124 
125     @Override
onPause()126     public void onPause() {
127         mHasPaused = true;
128         mHandler.removeCallbacksAndMessages(null);
129         mVideoPosition = mVideoView.getCurrentPosition();
130         mVideoView.suspend();
131         super.onPause();
132     }
133 
134     @Override
onStop()135     public void onStop() {
136         if (mProgress != null) {
137             mProgress.dismiss();
138             mProgress = null;
139         }
140         super.onStop();
141     }
142 
143     @Override
onDestroy()144     public void onDestroy() {
145         mVideoView.stopPlayback();
146         super.onDestroy();
147     }
148 
149     private final Runnable mProgressChecker = new Runnable() {
150         @Override
151         public void run() {
152             int pos = setProgress();
153             mHandler.postDelayed(mProgressChecker, 200 - (pos % 200));
154         }
155     };
156 
157     @Override
onSaveInstanceState(Bundle savedInstanceState)158     public void onSaveInstanceState(Bundle savedInstanceState) {
159         savedInstanceState.putInt(KEY_TRIM_START, mTrimStartTime);
160         savedInstanceState.putInt(KEY_TRIM_END, mTrimEndTime);
161         savedInstanceState.putInt(KEY_VIDEO_POSITION, mVideoPosition);
162         super.onSaveInstanceState(savedInstanceState);
163     }
164 
165     @Override
onRestoreInstanceState(Bundle savedInstanceState)166     public void onRestoreInstanceState(Bundle savedInstanceState) {
167         super.onRestoreInstanceState(savedInstanceState);
168         mTrimStartTime = savedInstanceState.getInt(KEY_TRIM_START, 0);
169         mTrimEndTime = savedInstanceState.getInt(KEY_TRIM_END, 0);
170         mVideoPosition = savedInstanceState.getInt(KEY_VIDEO_POSITION, 0);
171     }
172 
173     // This updates the time bar display (if necessary). It is called by
174     // mProgressChecker and also from places where the time bar needs
175     // to be updated immediately.
setProgress()176     private int setProgress() {
177         mVideoPosition = mVideoView.getCurrentPosition();
178         // If the video position is smaller than the starting point of trimming,
179         // correct it.
180         if (mVideoPosition < mTrimStartTime) {
181             mVideoView.seekTo(mTrimStartTime);
182             mVideoPosition = mTrimStartTime;
183         }
184         // If the position is bigger than the end point of trimming, show the
185         // replay button and pause.
186         if (mVideoPosition >= mTrimEndTime && mTrimEndTime > 0) {
187             if (mVideoPosition > mTrimEndTime) {
188                 mVideoView.seekTo(mTrimEndTime);
189                 mVideoPosition = mTrimEndTime;
190             }
191             mController.showEnded();
192             mVideoView.pause();
193         }
194 
195         int duration = mVideoView.getDuration();
196         if (duration > 0 && mTrimEndTime == 0) {
197             mTrimEndTime = duration;
198         }
199         mController.setTimes(mVideoPosition, duration, mTrimStartTime, mTrimEndTime);
200         // Enable save if there's modifications
201         mSaveVideoTextView.setEnabled(isModified());
202         return mVideoPosition;
203     }
204 
playVideo()205     private void playVideo() {
206         mVideoView.start();
207         mController.showPlaying();
208         setProgress();
209     }
210 
pauseVideo()211     private void pauseVideo() {
212         mVideoView.pause();
213         mController.showPaused();
214     }
215 
216 
isModified()217     private boolean isModified() {
218         int delta = mTrimEndTime - mTrimStartTime;
219 
220         // Considering that we only trim at sync frame, we don't want to trim
221         // when the time interval is too short or too close to the origin.
222         if (delta < 100 || Math.abs(mVideoView.getDuration() - delta) < 100) {
223             return false;
224         } else {
225             return true;
226         }
227     }
228 
trimVideo()229     private void trimVideo() {
230 
231         mDstFileInfo = SaveVideoFileUtils.getDstMp4FileInfo(TIME_STAMP_NAME,
232                 getContentResolver(), mUri, getString(R.string.folder_download));
233         final File mSrcFile = new File(mSrcVideoPath);
234 
235         showProgressDialog();
236 
237         new Thread(new Runnable() {
238             @Override
239             public void run() {
240                 try {
241                     VideoUtils.startTrim(mSrcFile, mDstFileInfo.mFile,
242                             mTrimStartTime, mTrimEndTime);
243                     // Update the database for adding a new video file.
244                     SaveVideoFileUtils.insertContent(mDstFileInfo,
245                             getContentResolver(), mUri);
246                 } catch (IOException e) {
247                     e.printStackTrace();
248                 }
249                 // After trimming is done, trigger the UI changed.
250                 mHandler.post(new Runnable() {
251                     @Override
252                     public void run() {
253                         Toast.makeText(getApplicationContext(),
254                             getString(R.string.save_into, mDstFileInfo.mFolderName),
255                             Toast.LENGTH_SHORT)
256                             .show();
257                         // TODO: change trimming into a service to avoid
258                         // this progressDialog and add notification properly.
259                         if (mProgress != null) {
260                             mProgress.dismiss();
261                             mProgress = null;
262                             // Show the result only when the activity not stopped.
263                             Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
264                             intent.setDataAndType(Uri.fromFile(mDstFileInfo.mFile), "video/*");
265                             intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false);
266                             startActivity(intent);
267                             finish();
268                         }
269                     }
270                 });
271             }
272         }).start();
273     }
274 
showProgressDialog()275     private void showProgressDialog() {
276         // create a background thread to trim the video.
277         // and show the progress.
278         mProgress = new ProgressDialog(this);
279         mProgress.setTitle(getString(R.string.trimming));
280         mProgress.setMessage(getString(R.string.please_wait));
281         // TODO: make this cancelable.
282         mProgress.setCancelable(false);
283         mProgress.setCanceledOnTouchOutside(false);
284         mProgress.show();
285     }
286 
287     @Override
onPlayPause()288     public void onPlayPause() {
289         if (mVideoView.isPlaying()) {
290             pauseVideo();
291         } else {
292             playVideo();
293         }
294     }
295 
296     @Override
onSeekStart()297     public void onSeekStart() {
298         pauseVideo();
299     }
300 
301     @Override
onSeekMove(int time)302     public void onSeekMove(int time) {
303         mVideoView.seekTo(time);
304     }
305 
306     @Override
onSeekEnd(int time, int start, int end)307     public void onSeekEnd(int time, int start, int end) {
308         mVideoView.seekTo(time);
309         mTrimStartTime = start;
310         mTrimEndTime = end;
311         setProgress();
312     }
313 
314     @Override
onShown()315     public void onShown() {
316     }
317 
318     @Override
onHidden()319     public void onHidden() {
320     }
321 
322     @Override
onReplay()323     public void onReplay() {
324         mVideoView.seekTo(mTrimStartTime);
325         playVideo();
326     }
327 
328     @Override
onCompletion(MediaPlayer mp)329     public void onCompletion(MediaPlayer mp) {
330         mController.showEnded();
331     }
332 
333     @Override
onError(MediaPlayer mp, int what, int extra)334     public boolean onError(MediaPlayer mp, int what, int extra) {
335         return false;
336     }
337 }
338