binpress

Video Cropping with Texture View

In this tutorial we are going to create an Android application which will do following:

  • Display a video from assets folder using TextureView
  • When a user touches the screen, TextureView must resize itself and video should be cropped to match new view size

You can also use my TextureVideoView project – a custom view based on Android’s TextureViewwhich gives you ability to easy play and crop video. This very similar to ImageView#setScaleType

Final Results

video-crop-1
video-crop-2
video-crop-3
video-crop-4

Step 1 – Preparation

Create android project and target Android version 4.0. Make sure you have following lines in your AndroidManifest.xml file.

  1. <uses-sdk
  2.     android:minSdkVersion="14"
  3.     android:targetSdkVersion="14"/>

Step 2 – XML

Copy the video file bigbuckbunny.mp4 to your assets folder.

<string name="Original_Video_Size">Original Video Size</string>

In your layout folder create a texture_video_crop.xml file and add the following lines:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.              android:layout_width="match_parent"
  4.              android:layout_height="match_parent"
  5.              android:id="@+id/rootView">
  6.  
  7.     <TextView
  8.             android:layout_width="@dimen/texture_view_width"
  9.             android:layout_height="@dimen/texture_view_height"
  10.             android:gravity="center"
  11.             android:text="@string/Original_Video_Size"
  12.             android:textSize="@dimen/text_size_big"
  13.             android:background="@android:color/darker_gray"/>
  14.  
  15.     <TextureView
  16.             android:id="@+id/textureView"
  17.             android:layout_width="@dimen/texture_view_width"
  18.             android:layout_height="@dimen/texture_view_height"/>
  19. </FrameLayout>

Note: TextView is only an 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

  1. import android.app.Activity;
  2. import android.content.res.AssetFileDescriptor;
  3. import android.graphics.Matrix;
  4. import android.graphics.SurfaceTexture;
  5. import android.media.MediaMetadataRetriever;
  6. import android.media.MediaPlayer;
  7. import android.os.Bundle;
  8. import android.util.Log;
  9. import android.view.MotionEvent;
  10. import android.view.Surface;
  11. import android.view.TextureView;
  12. import android.view.View;
  13. import android.widget.FrameLayout;
  14.  
  15. import java.io.IOException;

We start by loading and playing the video from the assets folder.

  1. public class VideoCropActivity extends Activity implements TextureView.SurfaceTextureListener {
  2.  
  3.     // Log tag
  4.     private static final String TAG = VideoCropActivity.class.getName();
  5.  
  6.     // Asset video file name
  7.     private static final String FILE_NAME = "big_buck_bunny.mp4";
  8.  
  9.     // MediaPlayer instance to control playback of video file.
  10.     private MediaPlayer mMediaPlayer;
  11.     private TextureView mTextureView;
  12.  
  13.     @Override
  14.     public void onCreate(Bundle savedInstanceState) {
  15.         super.onCreate(savedInstanceState);
  16.         setContentView(R.layout.texture_video_crop);
  17.  
  18.         initView();
  19.     }
  20.  
  21.     private void initView() {
  22.         mTextureView = (TextureView) findViewById(R.id.textureView);
  23.         // SurfaceTexture is available only after the TextureView
  24.         // is attached to a window and onAttachedToWindow() has been invoked.
  25.         // We need to use SurfaceTextureListener to be notified when the SurfaceTexture
  26.         // becomes available.
  27.         mTextureView.setSurfaceTextureListener(this);
  28.     }
  29.  
  30.     @Override
  31.     protected void onDestroy() {
  32.         super.onDestroy();
  33.         if (mMediaPlayer != null) {
  34.             // Make sure we stop video and release resources when activity is destroyed.
  35.             mMediaPlayer.stop();
  36.             mMediaPlayer.release();
  37.             mMediaPlayer = null;
  38.         }
  39.     }
  40.  
  41.     @Override
  42.     public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i2) {
  43.         Surface surface = new Surface(surfaceTexture);
  44.  
  45.         try {
  46.             AssetFileDescriptor afd = getAssets().openFd(FILE_NAME);
  47.             mMediaPlayer = new MediaPlayer();
  48.             mMediaPlayer
  49.                     .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
  50.             mMediaPlayer.setSurface(surface);
  51.             mMediaPlayer.setLooping(true);
  52.  
  53.             // don't forget to call MediaPlayer.prepareAsync() method when you use constructor for
  54.             // creating MediaPlayer
  55.             mMediaPlayer.prepareAsync();
  56.  
  57.             // Play video when the media source is ready for playback.
  58.             mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
  59.                 @Override
  60.                 public void onPrepared(MediaPlayer mediaPlayer) {
  61.                     mediaPlayer.start();
  62.                 }
  63.             });
  64.  
  65.         } catch (IllegalArgumentException e) {
  66.             Log.d(TAG, e.getMessage());
  67.         } catch (SecurityException e) {
  68.             Log.d(TAG, e.getMessage());
  69.         } catch (IllegalStateException e) {
  70.             Log.d(TAG, e.getMessage());
  71.         } catch (IOException e) {
  72.             Log.d(TAG, e.getMessage());
  73.         }
  74.     }
  75.  
  76.     @Override
  77.     public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i2) {
  78.     }
  79.  
  80.     @Override
  81.     public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
  82.         return true;
  83.     }
  84.  
  85.     @Override
  86.     public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
  87.     }
  88. }

To calculate video scale factor we need to know the original video size. We can do that by using the MediaMetadataRetriever class.

Declare the following class variables:

  1. // Original video size, in our case 640px / 360px
  2. private float mVideoWidth;
  3. private float mVideoHeight;

Create a method which will initialize them:

  1. private void calculateVideoSize() {
  2.     try {
  3.         AssetFileDescriptor afd = getAssets().openFd(FILE_NAME);
  4.         MediaMetadataRetriever metaRetriever = new MediaMetadataRetriever();
  5.         metaRetriever.setDataSource(
  6.                 afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
  7.         String height = metaRetriever
  8.                 .extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
  9.         String width = metaRetriever
  10.                 .extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
  11.         mVideoHeight = Float.parseFloat(height);
  12.         mVideoWidth = Float.parseFloat(width);
  13.  
  14.     } catch (IOException e) {
  15.         Log.d(TAG, e.getMessage());
  16.     } catch (NumberFormatException e) {
  17.         Log.d(TAG, e.getMessage());
  18.     }
  19. }

And now call this method inside your activity onCreate method:

  1. @Override
  2. public void onCreate(Bundle savedInstanceState) {
  3.     super.onCreate(savedInstanceState);
  4.     setContentView(R.layout.texture_video_crop);
  5.  
  6.     calculateVideoSize();
  7.     initView();
  8. }

Step 4 – View resizing

With the next part of the application logic we are going to add view resizing. When a user touches the screen, view width and height should be updated to the appropriate x and y values of the touch event.

Inside initView method we need to set touch listener to our root view, and when action up occurs – call the method which will update the texture view size.

  1. private void initView() {
  2.     mTextureView = (TextureView) findViewById(R.id.textureView);
  3.     // SurfaceTexture is available only after the TextureView
  4.     // is attached to a window and onAttachedToWindow() has been invoked.
  5.     // We need to use SurfaceTextureListener to be notified when the SurfaceTexture
  6.     // becomes available.
  7.     mTextureView.setSurfaceTextureListener(this);
  8.  
  9.     FrameLayout rootView = (FrameLayout) findViewById(R.id.rootView);
  10.     rootView.setOnTouchListener(new View.OnTouchListener() {
  11.         @Override
  12.         public boolean onTouch(View view, MotionEvent motionEvent) {
  13.             switch (motionEvent.getAction()) {
  14.                 case MotionEvent.ACTION_UP:
  15.                     updateTextureViewSize((int) motionEvent.getX(), (int) motionEvent.getY());
  16.                     break;
  17.             }
  18.             return true;
  19.         }
  20.     });
  21. }
  22.  
  23. private void updateTextureViewSize(int viewWidth, int viewHeight) {
  24.     mTextureView.setLayoutParams(new FrameLayout.LayoutParams(viewWidth, viewHeight));
  25. }

Now you can launch the application and check the results. As you can see, view size is changed, however video itself is stretched. Our next task is to fix this.

video-crop-5

Step 5 – Video cropping

Edit the updateTextureViewSize method – First we need to calculate scaleX and scaleYfactor and set it to a Matrix object using method setScale(..). Next pass this matrix to TextureView by setTransform(..) method and you are done.

  1. private void updateTextureViewSize(int viewWidth, int viewHeight) {
  2.     float scaleX = 1.0f;
  3.     float scaleY = 1.0f;
  4.  
  5.     if (mVideoWidth > viewWidth && mVideoHeight > viewHeight) {
  6.         scaleX = mVideoWidth / viewWidth;
  7.         scaleY = mVideoHeight / viewHeight;
  8.     } else if (mVideoWidth < viewWidth && mVideoHeight < viewHeight) {
  9.         scaleY = viewWidth / mVideoWidth;
  10.         scaleX = viewHeight / mVideoHeight;
  11.     } else if (viewWidth > mVideoWidth) {
  12.         scaleY = (viewWidth / mVideoWidth) / (viewHeight / mVideoHeight);
  13.     } else if (viewHeight > mVideoHeight) {
  14.         scaleX = (viewHeight / mVideoHeight) / (viewWidth / mVideoWidth);
  15.     }
  16.  
  17.     // Calculate pivot points, in our case crop from center
  18.     int pivotPointX = viewWidth / 2;
  19.     int pivotPointY = viewHeight / 2;
  20.  
  21.     Matrix matrix = new Matrix();
  22.     matrix.setScale(scaleX, scaleY, pivotPointX, pivotPointY);
  23.  
  24.     mTextureView.setTransform(matrix);
  25.     mTextureView.setLayoutParams(new FrameLayout.LayoutParams(viewWidth, viewHeight));
  26. }

Step 6 – Launch

When you launch the application you should notice that now the video is cropped and correctly displayed. Of course, when width to height ratio is too big, the video loses quality as it is scaled too much, similar to the behavior in ImageView.setScaleType(ImageVIew.ScaleType.CENTER_CROP);

Author: Dmytro Danylyk

Scroll to Top