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 TextureView
which gives you ability to easy play and crop video. This very similar to ImageView#setScaleType
Final Results
![video-crop-1](https://www.binpress.com/wp-content/uploads/2018/09/video-crop-1.png)
![video-crop-2](https://www.binpress.com/wp-content/uploads/2018/09/video-crop-2.png)
![video-crop-3](https://www.binpress.com/wp-content/uploads/2018/09/video-crop-3.png)
![video-crop-4](https://www.binpress.com/wp-content/uploads/2018/09/video-crop-4.png)
Step 1 – Preparation
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 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:
<?xml version="1.0" encoding="utf-8"?>
<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 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
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;
We start by loading and playing the video from the 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 the MediaMetadataRetriever class.
Declare the following class variables:
// Original video size, in our case 640px / 360px
private float mVideoWidth;
private float mVideoHeight;
Create a 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());
}
}
And 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
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.
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 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](https://www.binpress.com/wp-content/uploads/2018/09/video-crop-5.png)
Step 5 – Video cropping
Edit the updateTextureViewSize
method – First we need to calculate scaleX
and scaleY
factor and set it to a 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 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