Video Cropping
In this part of Android SurfaceView story we are going to create application which will do the following:
- display video from assets folder using TextureView.
- when user click on screen, TextureView should be resized and video should be cropped to match new view size.
Final Results:
Step 1 - Preparing
Create Android project and target Android version 4.0. Make sure you have following lines in your AndroidManifest.xml file.
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="14"/>
Step 2 - XML
Copy video file big_buck_bunny.mp4 to your assets folder.
In your values folder create dimen.xml file and add following lines.
<dimen name="texture_view_width">320dp</dimen> <dimen name="texture_view_height">176dp</dimen> <dimen name="text_size_big">22sp</dimen>
In your values folder create string.xml file and add following line.
<string name="Original_Video_Size">Original Video Size</string>
In your layout folder create texture_video_crop.xml file and add following lines:
<?xml version="1.0" encoding="utf-8"?> <!--suppress AndroidLintContentDescription --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/rootView"> <TextView android:layout_width="@dimen/texture_view_width" android:layout_height="@dimen/texture_view_height" android:gravity="center" android:text="@string/Original_Video_Size" android:textSize="@dimen/text_size_big" android:background="@android:color/darker_gray"/> <TextureView android:id="@+id/textureView" android:layout_width="@dimen/texture_view_width" android:layout_height="@dimen/texture_view_height"/> </FrameLayout>
Note: TextView is only indicator of our video original size.
Step 3 - Basic code
Create a new activity class and call it VideoCropActivity. Don’t forget to declare it inside AndroidManifest.xml file.
Imports
import android.app.Activity; import android.content.res.AssetFileDescriptor; import android.graphics.Matrix; import android.graphics.SurfaceTexture; import android.media.MediaMetadataRetriever; import android.media.MediaPlayer; import android.os.Bundle; import android.util.Log; import android.view.MotionEvent; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.FrameLayout; import java.io.IOException;
Our start point will be code from Playing video from assets tutorial, which will just load and play video from assets folder.
public class VideoCropActivity extends Activity implements TextureView.SurfaceTextureListener { // Log tag private static final String TAG = VideoCropActivity.class.getName(); // Asset video file name private static final String FILE_NAME = "big_buck_bunny.mp4"; // MediaPlayer instance to control playback of video file. private MediaPlayer mMediaPlayer; private TextureView mTextureView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.texture_video_crop); initView(); } private void initView() { mTextureView = (TextureView) findViewById(R.id.textureView); // SurfaceTexture is available only after the TextureView // is attached to a window and onAttachedToWindow() has been invoked. // We need to use SurfaceTextureListener to be notified when the SurfaceTexture // becomes available. mTextureView.setSurfaceTextureListener(this); } @Override protected void onDestroy() { super.onDestroy(); if (mMediaPlayer != null) { // Make sure we stop video and release resources when activity is destroyed. mMediaPlayer.stop(); mMediaPlayer.release(); mMediaPlayer = null; } } @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i2) { Surface surface = new Surface(surfaceTexture); try { AssetFileDescriptor afd = getAssets().openFd(FILE_NAME); mMediaPlayer = new MediaPlayer(); mMediaPlayer .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); mMediaPlayer.setSurface(surface); mMediaPlayer.setLooping(true); // don't forget to call MediaPlayer.prepareAsync() method when you use constructor for // creating MediaPlayer mMediaPlayer.prepareAsync(); // Play video when the media source is ready for playback. mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mediaPlayer) { mediaPlayer.start(); } }); } catch (IllegalArgumentException e) { Log.d(TAG, e.getMessage()); } catch (SecurityException e) { Log.d(TAG, e.getMessage()); } catch (IllegalStateException e) { Log.d(TAG, e.getMessage()); } catch (IOException e) { Log.d(TAG, e.getMessage()); } } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i2) { } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { } }
To calculate video scale factor we need to know the original video size. We can do that by using MediaMetadataRetriever class.
Declare following class variables:
// Original video size, in our case 640px / 360px private float mVideoWidth; private float mVideoHeight;
Create method which will initialize them:
private void calculateVideoSize() { try { AssetFileDescriptor afd = getAssets().openFd(FILE_NAME); MediaMetadataRetriever metaRetriever = new MediaMetadataRetriever(); metaRetriever.setDataSource( afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); String height = metaRetriever .extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); String width = metaRetriever .extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); mVideoHeight = Float.parseFloat(height); mVideoWidth = Float.parseFloat(width); } catch (IOException e) { Log.d(TAG, e.getMessage()); } catch (NumberFormatException e) { Log.d(TAG, e.getMessage()); } }
Now call this method inside your activity onCreate method:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.texture_video_crop); calculateVideoSize(); initView(); }
Step 4 - View resizing
The next part ois view resizing. When user touches the screen, view width and height should be updated to appropriate x and y values of touch event.
Inside initView method we need to set touch listener to our root view, and when action up occurs - call method which will update texture view size.
private void initView() { mTextureView = (TextureView) findViewById(R.id.textureView); // SurfaceTexture is available only after the TextureView // is attached to a window and onAttachedToWindow() has been invoked. // We need to use SurfaceTextureListener to be notified when the SurfaceTexture // becomes available. mTextureView.setSurfaceTextureListener(this); FrameLayout rootView = (FrameLayout) findViewById(R.id.rootView); rootView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { switch (motionEvent.getAction()) { case MotionEvent.ACTION_UP: updateTextureViewSize((int) motionEvent.getX(), (int) motionEvent.getY()); break; } return true; } }); } private void updateTextureViewSize(int viewWidth, int viewHeight) { mTextureView.setLayoutParams(new FrameLayout.LayoutParams(viewWidth, viewHeight)); }
Now you can launch application and check the results. As you can see, view size is changed, however video itself is stretched. Next task is to fix this issue.
Step 5 - Video cropping
Now change updateTextureViewSize method. First we need to calculate scaleX and scaleY factor and set it to Matrix object using method setScale(..). Next pass this matrix to TextureView by setTransform(..) method and you are done.
private void updateTextureViewSize(int viewWidth, int viewHeight) { float scaleX = 1.0f; float scaleY = 1.0f; if (mVideoWidth > viewWidth && mVideoHeight > viewHeight) { scaleX = mVideoWidth / viewWidth; scaleY = mVideoHeight / viewHeight; } else if (mVideoWidth < viewWidth && mVideoHeight < viewHeight) { scaleY = viewWidth / mVideoWidth; scaleX = viewHeight / mVideoHeight; } else if (viewWidth > mVideoWidth) { scaleY = (viewWidth / mVideoWidth) / (viewHeight / mVideoHeight); } else if (viewHeight > mVideoHeight) { scaleX = (viewHeight / mVideoHeight) / (viewWidth / mVideoWidth); } // Calculate pivot points, in our case crop from center int pivotPointX = viewWidth / 2; int pivotPointY = viewHeight / 2; Matrix matrix = new Matrix(); matrix.setScale(scaleX, scaleY, pivotPointX, pivotPointY); mTextureView.setTransform(matrix); mTextureView.setLayoutParams(new FrameLayout.LayoutParams(viewWidth, viewHeight)); }
Step 6 - Launch
When you launch application, you should notice that video is cropped correctly and displayed properly now. Of course, when width to height ratio is too big, video looses it quality as is scaled too much - as inImageView.setScaleType(ImageVIew.ScaleType.CENTER_CROP);