TrimVideo.java revision 5e92668f42b5f29a0e55653b0bbf2df342723a6d
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 17package com.android.gallery3d.app; 18 19import android.app.ProgressDialog; 20import android.content.ContentResolver; 21import android.content.ContentValues; 22import android.content.Context; 23import android.content.Intent; 24import android.database.Cursor; 25import android.media.MediaPlayer; 26import android.net.Uri; 27import android.os.Bundle; 28import android.os.Environment; 29import android.os.Handler; 30import android.provider.MediaStore.Video; 31import android.provider.MediaStore.Video.VideoColumns; 32import android.view.View; 33import android.view.ViewGroup; 34import android.widget.Toast; 35import android.widget.VideoView; 36 37import com.actionbarsherlock.app.ActionBar; 38import com.actionbarsherlock.app.SherlockActivity; 39import com.actionbarsherlock.view.Menu; 40import com.actionbarsherlock.view.MenuInflater; 41import com.actionbarsherlock.view.MenuItem; 42import com.actionbarsherlock.view.Window; 43import com.android.gallery3d.R; 44import com.android.gallery3d.util.BucketNames; 45 46import java.io.File; 47import java.io.IOException; 48import java.sql.Date; 49import java.text.SimpleDateFormat; 50 51public class TrimVideo extends SherlockActivity implements 52 MediaPlayer.OnErrorListener, 53 MediaPlayer.OnCompletionListener, 54 ControllerOverlay.Listener { 55 56 private VideoView mVideoView; 57 private TrimControllerOverlay mController; 58 private Context mContext; 59 private Uri mUri; 60 private final Handler mHandler = new Handler(); 61 public static final String TRIM_ACTION = "com.android.camera.action.TRIM"; 62 63 public ProgressDialog mProgress; 64 65 private int mTrimStartTime = 0; 66 private int mTrimEndTime = 0; 67 private int mVideoPosition = 0; 68 public static final String KEY_TRIM_START = "trim_start"; 69 public static final String KEY_TRIM_END = "trim_end"; 70 public static final String KEY_VIDEO_POSITION = "video_pos"; 71 private boolean mHasPaused = false; 72 73 private String mSrcVideoPath = null; 74 private String mSaveFileName = null; 75 private static final String TIME_STAMP_NAME = "'TRIM'_yyyyMMdd_HHmmss"; 76 private File mSrcFile = null; 77 private File mDstFile = null; 78 private File mSaveDirectory = null; 79 // For showing the result. 80 private String saveFolderName = null; 81 82 @Override 83 public void onCreate(Bundle savedInstanceState) { 84 mContext = getApplicationContext(); 85 super.onCreate(savedInstanceState); 86 87 requestWindowFeature(Window.FEATURE_ACTION_BAR); 88 requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); 89 90 ActionBar actionBar = getSupportActionBar(); 91 int displayOptions = ActionBar.DISPLAY_HOME_AS_UP 92 | ActionBar.DISPLAY_SHOW_TITLE; 93 actionBar.setDisplayOptions(displayOptions, displayOptions); 94 95 Intent intent = getIntent(); 96 mUri = intent.getData(); 97 mSrcVideoPath = intent.getStringExtra(PhotoPage.KEY_MEDIA_ITEM_PATH); 98 setContentView(R.layout.trim_view); 99 View rootView = findViewById(R.id.trim_view_root); 100 101 mVideoView = (VideoView) rootView.findViewById(R.id.surface_view); 102 103 mController = new TrimControllerOverlay(mContext); 104 ((ViewGroup) rootView).addView(mController.getView()); 105 mController.setListener(this); 106 mController.setCanReplay(true); 107 108 mVideoView.setOnErrorListener(this); 109 mVideoView.setOnCompletionListener(this); 110 mVideoView.setVideoURI(mUri); 111 112 playVideo(); 113 } 114 115 @Override 116 public void onResume() { 117 super.onResume(); 118 if (mHasPaused) { 119 mVideoView.seekTo(mVideoPosition); 120 mVideoView.resume(); 121 mHasPaused = false; 122 } 123 mHandler.post(mProgressChecker); 124 } 125 126 @Override 127 public void onPause() { 128 mHasPaused = true; 129 mHandler.removeCallbacksAndMessages(null); 130 mVideoPosition = mVideoView.getCurrentPosition(); 131 mVideoView.suspend(); 132 super.onPause(); 133 } 134 135 @Override 136 public void onStop() { 137 if (mProgress != null) { 138 mProgress.dismiss(); 139 mProgress = null; 140 } 141 super.onStop(); 142 } 143 144 @Override 145 public void onDestroy() { 146 mVideoView.stopPlayback(); 147 super.onDestroy(); 148 } 149 150 private final Runnable mProgressChecker = new Runnable() { 151 @Override 152 public void run() { 153 int pos = setProgress(); 154 mHandler.postDelayed(mProgressChecker, 200 - (pos % 200)); 155 } 156 }; 157 158 @Override 159 public void onSaveInstanceState(Bundle savedInstanceState) { 160 savedInstanceState.putInt(KEY_TRIM_START, mTrimStartTime); 161 savedInstanceState.putInt(KEY_TRIM_END, mTrimEndTime); 162 savedInstanceState.putInt(KEY_VIDEO_POSITION, mVideoPosition); 163 super.onSaveInstanceState(savedInstanceState); 164 } 165 166 @Override 167 public void onRestoreInstanceState(Bundle savedInstanceState) { 168 super.onRestoreInstanceState(savedInstanceState); 169 mTrimStartTime = savedInstanceState.getInt(KEY_TRIM_START, 0); 170 mTrimEndTime = savedInstanceState.getInt(KEY_TRIM_END, 0); 171 mVideoPosition = savedInstanceState.getInt(KEY_VIDEO_POSITION, 0); 172 } 173 174 // This updates the time bar display (if necessary). It is called by 175 // mProgressChecker and also from places where the time bar needs 176 // to be updated immediately. 177 private int setProgress() { 178 mVideoPosition = mVideoView.getCurrentPosition(); 179 // If the video position is smaller than the starting point of trimming, 180 // correct it. 181 if (mVideoPosition < mTrimStartTime) { 182 mVideoView.seekTo(mTrimStartTime); 183 mVideoPosition = mTrimStartTime; 184 } 185 // If the position is bigger than the end point of trimming, show the 186 // replay button and pause. 187 if (mVideoPosition >= mTrimEndTime && mTrimEndTime > 0) { 188 if (mVideoPosition > mTrimEndTime) { 189 mVideoView.seekTo(mTrimEndTime); 190 mVideoPosition = mTrimEndTime; 191 } 192 mController.showEnded(); 193 mVideoView.pause(); 194 } 195 196 int duration = mVideoView.getDuration(); 197 if (duration > 0 && mTrimEndTime == 0) { 198 mTrimEndTime = duration; 199 } 200 mController.setTimes(mVideoPosition, duration, mTrimStartTime, mTrimEndTime); 201 return mVideoPosition; 202 } 203 204 private void playVideo() { 205 mVideoView.start(); 206 mController.showPlaying(); 207 setProgress(); 208 } 209 210 private void pauseVideo() { 211 mVideoView.pause(); 212 mController.showPaused(); 213 } 214 215 @Override 216 public boolean onCreateOptionsMenu(Menu menu) { 217 super.onCreateOptionsMenu(menu); 218 MenuInflater inflater = getSupportMenuInflater(); 219 inflater.inflate(R.menu.trim, menu); 220 return true; 221 }; 222 223 // Copy from SaveCopyTask.java in terms of how to handle the destination 224 // path and filename : querySource() and getSaveDirectory(). 225 private interface ContentResolverQueryCallback { 226 void onCursorResult(Cursor cursor); 227 } 228 229 private void querySource(String[] projection, ContentResolverQueryCallback callback) { 230 ContentResolver contentResolver = getContentResolver(); 231 Cursor cursor = null; 232 try { 233 cursor = contentResolver.query(mUri, projection, null, null, null); 234 if ((cursor != null) && cursor.moveToNext()) { 235 callback.onCursorResult(cursor); 236 } 237 } catch (Exception e) { 238 // Ignore error for lacking the data column from the source. 239 } finally { 240 if (cursor != null) { 241 cursor.close(); 242 } 243 } 244 } 245 246 private File getSaveDirectory() { 247 final File[] dir = new File[1]; 248 querySource(new String[] { 249 VideoColumns.DATA }, new ContentResolverQueryCallback() { 250 251 @Override 252 public void onCursorResult(Cursor cursor) { 253 dir[0] = new File(cursor.getString(0)).getParentFile(); 254 } 255 }); 256 return dir[0]; 257 } 258 259 @Override 260 public boolean onOptionsItemSelected(MenuItem item) { 261 int id = item.getItemId(); 262 if (id == android.R.id.home) { 263 finish(); 264 return true; 265 } else if (id == R.id.action_trim_video) { 266 trimVideo(); 267 return true; 268 } 269 return false; 270 } 271 272 private void trimVideo() { 273 // Use the default save directory if the source directory cannot be 274 // saved. 275 mSaveDirectory = getSaveDirectory(); 276 if ((mSaveDirectory == null) || !mSaveDirectory.canWrite()) { 277 mSaveDirectory = new File(Environment.getExternalStorageDirectory(), 278 BucketNames.DOWNLOAD); 279 saveFolderName = getString(R.string.folder_download); 280 } else { 281 saveFolderName = mSaveDirectory.getName(); 282 } 283 mSaveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format( 284 new Date(System.currentTimeMillis())); 285 286 mDstFile = new File(mSaveDirectory, mSaveFileName + ".mp4"); 287 mSrcFile = new File(mSrcVideoPath); 288 289 showProgressDialog(); 290 291 new Thread(new Runnable() { 292 @Override 293 public void run() { 294 try { 295 ShortenExample.main(null, mSrcFile, mDstFile, mTrimStartTime, mTrimEndTime); 296 } catch (IOException e) { 297 e.printStackTrace(); 298 } 299 // After trimming is done, trigger the UI changed. 300 mHandler.post(new Runnable() { 301 @Override 302 public void run() { 303 // TODO: change trimming into a service to avoid 304 // this progressDialog and add notification properly. 305 if (mProgress != null) { 306 mProgress.dismiss(); 307 // Update the database for adding a new video file. 308 insertContent(mDstFile); 309 Toast.makeText(getApplicationContext(), 310 "Saved into " + saveFolderName, Toast.LENGTH_SHORT) 311 .show(); 312 mProgress = null; 313 } 314 } 315 }); 316 } 317 }).start(); 318 } 319 320 private void showProgressDialog() { 321 // create a background thread to trim the video. 322 // and show the progress. 323 mProgress = new ProgressDialog(this); 324 mProgress.setTitle("Trimming"); 325 mProgress.setMessage("please wait"); 326 // TODO: make this cancelable. 327 mProgress.setCancelable(false); 328 mProgress.setCanceledOnTouchOutside(false); 329 mProgress.show(); 330 } 331 332 /** 333 * Insert the content (saved file) with proper video properties. 334 */ 335 private Uri insertContent(File file) { 336 long now = System.currentTimeMillis() / 1000; 337 338 final ContentValues values = new ContentValues(12); 339 values.put(Video.Media.TITLE, mSaveFileName); 340 values.put(Video.Media.DISPLAY_NAME, file.getName()); 341 values.put(Video.Media.MIME_TYPE, "video/mp4"); 342 values.put(Video.Media.DATE_TAKEN, now); 343 values.put(Video.Media.DATE_MODIFIED, now); 344 values.put(Video.Media.DATE_ADDED, now); 345 values.put(Video.Media.DATA, file.getAbsolutePath()); 346 values.put(Video.Media.SIZE, file.length()); 347 // Copy the data taken and location info from src. 348 String[] projection = new String[] { 349 VideoColumns.DATE_TAKEN, 350 VideoColumns.LATITUDE, 351 VideoColumns.LONGITUDE, 352 VideoColumns.RESOLUTION, 353 }; 354 355 // Copy some info from the source file. 356 querySource(projection, new ContentResolverQueryCallback() { 357 358 @Override 359 public void onCursorResult(Cursor cursor) { 360 values.put(Video.Media.DATE_TAKEN, cursor.getLong(0)); 361 double latitude = cursor.getDouble(1); 362 double longitude = cursor.getDouble(2); 363 // TODO: Change || to && after the default location issue is 364 // fixed. 365 if ((latitude != 0f) || (longitude != 0f)) { 366 values.put(Video.Media.LATITUDE, latitude); 367 values.put(Video.Media.LONGITUDE, longitude); 368 } 369 values.put(Video.Media.RESOLUTION, cursor.getString(3)); 370 371 } 372 }); 373 374 return getContentResolver().insert(Video.Media.EXTERNAL_CONTENT_URI, values); 375 } 376 377 @Override 378 public void onPlayPause() { 379 if (mVideoView.isPlaying()) { 380 pauseVideo(); 381 } else { 382 playVideo(); 383 } 384 } 385 386 @Override 387 public void onSeekStart() { 388 pauseVideo(); 389 } 390 391 @Override 392 public void onSeekMove(int time) { 393 mVideoView.seekTo(time); 394 } 395 396 @Override 397 public void onSeekEnd(int time, int start, int end) { 398 mVideoView.seekTo(time); 399 mTrimStartTime = start; 400 mTrimEndTime = end; 401 setProgress(); 402 } 403 404 @Override 405 public void onShown() { 406 } 407 408 409 @Override 410 public void onHidden() { 411 } 412 413 @Override 414 public void onReplay() { 415 mVideoView.seekTo(mTrimStartTime); 416 playVideo(); 417 } 418 419 @Override 420 public void onCompletion(MediaPlayer mp) { 421 mController.showEnded(); 422 } 423 424 @Override 425 public boolean onError(MediaPlayer mp, int what, int extra) { 426 return false; 427 } 428} 429