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