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