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 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 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 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 135 public void onStop() { 136 if (mProgress != null) { 137 mProgress.dismiss(); 138 mProgress = null; 139 } 140 super.onStop(); 141 } 142 143 @Override 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 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 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. 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 205 private void playVideo() { 206 mVideoView.start(); 207 mController.showPlaying(); 208 setProgress(); 209 } 210 211 private void pauseVideo() { 212 mVideoView.pause(); 213 mController.showPaused(); 214 } 215 216 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 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 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 288 public void onPlayPause() { 289 if (mVideoView.isPlaying()) { 290 pauseVideo(); 291 } else { 292 playVideo(); 293 } 294 } 295 296 @Override 297 public void onSeekStart() { 298 pauseVideo(); 299 } 300 301 @Override 302 public void onSeekMove(int time) { 303 mVideoView.seekTo(time); 304 } 305 306 @Override 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 315 public void onShown() { 316 } 317 318 @Override 319 public void onHidden() { 320 } 321 322 @Override 323 public void onReplay() { 324 mVideoView.seekTo(mTrimStartTime); 325 playVideo(); 326 } 327 328 @Override 329 public void onCompletion(MediaPlayer mp) { 330 mController.showEnded(); 331 } 332 333 @Override 334 public boolean onError(MediaPlayer mp, int what, int extra) { 335 return false; 336 } 337 } 338