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