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 return mVideoPosition; 201 } 202 203 private void playVideo() { 204 mVideoView.start(); 205 mController.showPlaying(); 206 setProgress(); 207 } 208 209 private void pauseVideo() { 210 mVideoView.pause(); 211 mController.showPaused(); 212 } 213 214 215 private boolean isModified() { 216 int delta = mTrimEndTime - mTrimStartTime; 217 218 // Considering that we only trim at sync frame, we don't want to trim 219 // when the time interval is too short or too close to the origin. 220 if (delta < 100 || Math.abs(mVideoView.getDuration() - delta) < 100) { 221 return false; 222 } else { 223 return true; 224 } 225 } 226 227 private void trimVideo() { 228 229 mDstFileInfo = SaveVideoFileUtils.getDstMp4FileInfo(TIME_STAMP_NAME, 230 getContentResolver(), mUri, getString(R.string.folder_download)); 231 final File mSrcFile = new File(mSrcVideoPath); 232 233 showProgressDialog(); 234 235 new Thread(new Runnable() { 236 @Override 237 public void run() { 238 try { 239 VideoUtils.startTrim(mSrcFile, mDstFileInfo.mFile, 240 mTrimStartTime, mTrimEndTime); 241 // Update the database for adding a new video file. 242 SaveVideoFileUtils.insertContent(mDstFileInfo, 243 getContentResolver(), mUri); 244 } catch (IOException e) { 245 e.printStackTrace(); 246 } 247 // After trimming is done, trigger the UI changed. 248 mHandler.post(new Runnable() { 249 @Override 250 public void run() { 251 Toast.makeText(getApplicationContext(), 252 getString(R.string.save_into, mDstFileInfo.mFolderName), 253 Toast.LENGTH_SHORT) 254 .show(); 255 // TODO: change trimming into a service to avoid 256 // this progressDialog and add notification properly. 257 if (mProgress != null) { 258 mProgress.dismiss(); 259 mProgress = null; 260 // Show the result only when the activity not stopped. 261 Intent intent = new Intent(android.content.Intent.ACTION_VIEW); 262 intent.setDataAndType(Uri.fromFile(mDstFileInfo.mFile), "video/*"); 263 intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false); 264 startActivity(intent); 265 finish(); 266 } 267 } 268 }); 269 } 270 }).start(); 271 } 272 273 private void showProgressDialog() { 274 // create a background thread to trim the video. 275 // and show the progress. 276 mProgress = new ProgressDialog(this); 277 mProgress.setTitle(getString(R.string.trimming)); 278 mProgress.setMessage(getString(R.string.please_wait)); 279 // TODO: make this cancelable. 280 mProgress.setCancelable(false); 281 mProgress.setCanceledOnTouchOutside(false); 282 mProgress.show(); 283 } 284 285 @Override 286 public void onPlayPause() { 287 if (mVideoView.isPlaying()) { 288 pauseVideo(); 289 } else { 290 playVideo(); 291 } 292 } 293 294 @Override 295 public void onSeekStart() { 296 pauseVideo(); 297 } 298 299 @Override 300 public void onSeekMove(int time) { 301 mVideoView.seekTo(time); 302 } 303 304 @Override 305 public void onSeekEnd(int time, int start, int end) { 306 mVideoView.seekTo(time); 307 mTrimStartTime = start; 308 mTrimEndTime = end; 309 setProgress(); 310 // Enable save if there's modifications 311 mSaveVideoTextView.setEnabled(isModified()); 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