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