| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.gallery3d.app; |
| |
| import android.app.ActionBar; |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.app.ProgressDialog; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.media.MediaPlayer; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.provider.MediaStore; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.Window; |
| import android.widget.ImageView; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| import android.widget.VideoView; |
| import androidx.core.content.FileProvider; |
| |
| import com.android.gallery3d.R; |
| import com.android.gallery3d.util.SaveVideoFileInfo; |
| import com.android.gallery3d.util.SaveVideoFileUtils; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.URI; |
| |
| public class TrimVideo extends Activity implements |
| MediaPlayer.OnErrorListener, |
| MediaPlayer.OnCompletionListener, |
| ControllerOverlay.Listener { |
| |
| private VideoView mVideoView; |
| private TextView mSaveVideoTextView; |
| private ImageView mExitImageView; |
| private TrimControllerOverlay mController; |
| private AlertDialog mExitDialog; |
| private Context mContext; |
| private Uri mUri; |
| private final Handler mHandler = new Handler(); |
| public static final String TRIM_ACTION = "com.android.camera.action.TRIM"; |
| |
| public ProgressDialog mProgress; |
| |
| private int mTrimStartTime = 0; |
| private int mTrimEndTime = 0; |
| private boolean mCheckTrimStartTime; |
| private int mVideoPosition = 0; |
| public static final String KEY_TRIM_START = "trim_start"; |
| public static final String KEY_TRIM_END = "trim_end"; |
| public static final String KEY_VIDEO_POSITION = "video_pos"; |
| private boolean mHasPaused = false; |
| |
| private String mSrcVideoPath = null; |
| private static final String TIME_STAMP_NAME = "'TRIM'_yyyyMMdd_HHmmss"; |
| private SaveVideoFileInfo mDstFileInfo = null; |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| mContext = getApplicationContext(); |
| super.onCreate(savedInstanceState); |
| |
| requestWindowFeature(Window.FEATURE_ACTION_BAR); |
| requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); |
| |
| ActionBar actionBar = getActionBar(); |
| int displayOptions = ActionBar.DISPLAY_SHOW_HOME; |
| actionBar.setDisplayOptions(0, displayOptions); |
| displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM; |
| actionBar.setDisplayOptions(displayOptions, displayOptions); |
| actionBar.setCustomView(R.layout.trim_menu); |
| |
| mExitImageView = (ImageView) findViewById(R.id.exit_trim); |
| mExitImageView.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| mExitDialog = new AlertDialog.Builder(TrimVideo.this, |
| AlertDialog.THEME_DEVICE_DEFAULT_LIGHT). |
| setTitle(R.string.trim_video_exit_title). |
| setMessage(R.string.trim_video_exit_msg). |
| setPositiveButton(R.string.trim_video_exit_discard, |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| finish(); |
| } |
| }). |
| setNegativeButton(R.string.review_cancel, |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| dialog.dismiss(); |
| } |
| }). |
| create(); |
| mExitDialog.show(); |
| } |
| }); |
| |
| mSaveVideoTextView = (TextView) findViewById(R.id.start_trim); |
| mSaveVideoTextView.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View arg0) { |
| trimVideo(); |
| } |
| }); |
| mSaveVideoTextView.setVisibility(View.GONE); |
| |
| Intent intent = getIntent(); |
| mUri = intent.getData(); |
| mSrcVideoPath = intent.getStringExtra(PhotoPage.KEY_MEDIA_ITEM_PATH); |
| setContentView(R.layout.trim_view); |
| View rootView = findViewById(R.id.trim_view_root); |
| |
| mVideoView = (VideoView) rootView.findViewById(R.id.surface_view); |
| |
| mController = new TrimControllerOverlay(mContext); |
| ((ViewGroup) rootView).addView(mController.getView()); |
| mController.setListener(this); |
| mController.setCanReplay(true); |
| |
| mVideoView.setOnErrorListener(this); |
| mVideoView.setOnCompletionListener(this); |
| mVideoView.setVideoURI(mUri); |
| |
| playVideo(); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| if (mHasPaused) { |
| mVideoView.seekTo(mVideoPosition); |
| mVideoView.resume(); |
| mHasPaused = false; |
| } |
| mHandler.post(mProgressChecker); |
| } |
| |
| @Override |
| public void onPause() { |
| mHasPaused = true; |
| mHandler.removeCallbacksAndMessages(null); |
| mVideoPosition = mVideoView.getCurrentPosition(); |
| mVideoView.suspend(); |
| super.onPause(); |
| } |
| |
| @Override |
| public void onStop() { |
| if (mProgress != null) { |
| mProgress.dismiss(); |
| mProgress = null; |
| } |
| super.onStop(); |
| } |
| |
| @Override |
| public void onDestroy() { |
| mVideoView.stopPlayback(); |
| super.onDestroy(); |
| } |
| |
| private final Runnable mProgressChecker = new Runnable() { |
| @Override |
| public void run() { |
| int pos = setProgress(); |
| mHandler.postDelayed(mProgressChecker, 200 - (pos % 200)); |
| } |
| }; |
| |
| @Override |
| public void onSaveInstanceState(Bundle savedInstanceState) { |
| savedInstanceState.putInt(KEY_TRIM_START, mTrimStartTime); |
| savedInstanceState.putInt(KEY_TRIM_END, mTrimEndTime); |
| savedInstanceState.putInt(KEY_VIDEO_POSITION, mVideoPosition); |
| super.onSaveInstanceState(savedInstanceState); |
| } |
| |
| @Override |
| public void onRestoreInstanceState(Bundle savedInstanceState) { |
| super.onRestoreInstanceState(savedInstanceState); |
| mTrimStartTime = savedInstanceState.getInt(KEY_TRIM_START, 0); |
| mTrimEndTime = savedInstanceState.getInt(KEY_TRIM_END, 0); |
| mVideoPosition = savedInstanceState.getInt(KEY_VIDEO_POSITION, 0); |
| } |
| |
| // This updates the time bar display (if necessary). It is called by |
| // mProgressChecker and also from places where the time bar needs |
| // to be updated immediately. |
| private int setProgress() { |
| mVideoPosition = mVideoView.getCurrentPosition(); |
| // If the video position is smaller than the starting point of trimming, |
| // correct it. |
| if (mCheckTrimStartTime && (mVideoPosition < mTrimStartTime)) { |
| mVideoView.seekTo(mTrimStartTime); |
| mVideoPosition = mTrimStartTime; |
| } |
| mCheckTrimStartTime = false; |
| // If the position is bigger than the end point of trimming, show the |
| // replay button and pause. |
| if (mVideoPosition >= mTrimEndTime && mTrimEndTime > 0) { |
| if (mVideoPosition > mTrimEndTime) { |
| mVideoView.seekTo(mTrimEndTime); |
| mVideoPosition = mTrimEndTime; |
| } |
| mController.showEnded(); |
| mVideoView.pause(); |
| } |
| |
| int duration = mVideoView.getDuration(); |
| if (duration > 0 && mTrimEndTime == 0) { |
| mTrimEndTime = duration; |
| } |
| mController.setTimes(mVideoPosition, duration, mTrimStartTime, mTrimEndTime); |
| // Enable save if there's modifications |
| mSaveVideoTextView.setVisibility(isModified() ? View.VISIBLE : View.GONE); |
| return mVideoPosition; |
| } |
| |
| private void playVideo() { |
| mVideoView.start(); |
| mCheckTrimStartTime = true; |
| mController.showPlaying(); |
| setProgress(); |
| } |
| |
| private void pauseVideo() { |
| mVideoView.pause(); |
| mController.showPaused(); |
| } |
| |
| |
| private boolean isModified() { |
| int delta = mTrimEndTime - mTrimStartTime; |
| |
| // Considering that we only trim at sync frame, we don't want to trim |
| // when the time interval is too short or too close to the origin. |
| if (delta < 100 || Math.abs(mVideoView.getDuration() - delta) < 100) { |
| return false; |
| } else { |
| return true; |
| } |
| } |
| |
| private void trimVideo() { |
| |
| mDstFileInfo = SaveVideoFileUtils.getDstMp4FileInfo(TIME_STAMP_NAME, |
| getContentResolver(), mUri, getString(R.string.folder_download)); |
| final File mSrcFile = new File(mSrcVideoPath); |
| |
| showProgressDialog(); |
| |
| new Thread(new Runnable() { |
| @Override |
| public void run() { |
| boolean hasError = false; |
| try { |
| VideoUtils.startTrim(mSrcFile, mDstFileInfo.mFile, |
| mTrimStartTime, mTrimEndTime); |
| // Update the database for adding a new video file. |
| SaveVideoFileUtils.insertContent(mDstFileInfo, |
| getContentResolver(), mUri); |
| } catch (IOException e) { |
| hasError = true; |
| e.printStackTrace(); |
| } catch (IllegalStateException e) { |
| hasError = true; |
| e.printStackTrace(); |
| } |
| //If the exception happens,just notify the UI and avoid the crash. |
| if (hasError){ |
| mHandler.post(new Runnable(){ |
| @Override |
| public void run(){ |
| Toast.makeText(getApplicationContext(), |
| getString(R.string.fail_trim), |
| Toast.LENGTH_SHORT) |
| .show(); |
| if (mProgress != null) { |
| mProgress.dismiss(); |
| mProgress = null; |
| } |
| } |
| }); |
| return; |
| } |
| // After trimming is done, trigger the UI changed. |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| Toast.makeText(getApplicationContext(), |
| getString(R.string.save_into, mDstFileInfo.mFolderName), |
| Toast.LENGTH_SHORT) |
| .show(); |
| // TODO: change trimming into a service to avoid |
| // this progressDialog and add notification properly. |
| if (mProgress != null) { |
| mProgress.dismiss(); |
| mProgress = null; |
| // Show the result only when the activity not stopped. |
| Intent intent = new Intent(android.content.Intent.ACTION_VIEW); |
| intent.setDataAndType( |
| FileProvider.getUriForFile(mContext, |
| "com.android.gallery3d.fileprovider", |
| mDstFileInfo.mFile), "video/*"); |
| intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false); |
| intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); |
| startActivity(intent); |
| finish(); |
| } |
| } |
| }); |
| } |
| }).start(); |
| } |
| |
| private void showProgressDialog() { |
| // create a background thread to trim the video. |
| // and show the progress. |
| mProgress = new ProgressDialog(this); |
| mProgress.setTitle(getString(R.string.trimming)); |
| mProgress.setMessage(getString(R.string.please_wait)); |
| // TODO: make this cancelable. |
| mProgress.setCancelable(false); |
| mProgress.setCanceledOnTouchOutside(false); |
| mProgress.show(); |
| } |
| |
| @Override |
| public void onPlayPause() { |
| if (mVideoView.isPlaying()) { |
| pauseVideo(); |
| } else { |
| playVideo(); |
| } |
| } |
| |
| @Override |
| public void onSeekStart() { |
| pauseVideo(); |
| } |
| |
| @Override |
| public void onSeekMove(int time) { |
| mVideoView.seekTo(time); |
| } |
| |
| @Override |
| public void onSeekEnd(int time, int start, int end) { |
| mVideoView.seekTo(time); |
| mTrimStartTime = start; |
| mTrimEndTime = end; |
| setProgress(); |
| } |
| |
| @Override |
| public void onShown() { |
| } |
| |
| @Override |
| public void onHidden() { |
| } |
| |
| @Override |
| public boolean onIsRTSP() { |
| // TODO Auto-generated method stub |
| return false; |
| } |
| |
| @Override |
| public void onReplay() { |
| mVideoView.seekTo(mTrimStartTime); |
| playVideo(); |
| } |
| |
| @Override |
| public void onCompletion(MediaPlayer mp) { |
| mController.showEnded(); |
| } |
| |
| @Override |
| public boolean onError(MediaPlayer mp, int what, int extra) { |
| return false; |
| } |
| } |