TextureView: How to create a full-screen video background in Android applications
February 18, 2021
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.
- display a full screen background video without distorting the video size
This tutorial is derived from the creation of our latest app, TrackBack - available on the play store soon. Some XML names and items have been changed slightly to suite this tutorial. We used royalty free video and a basic video editor to adjust the video size and combine clips.
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 a video file to your assets folder at res/raw. If raw doesn't exist, make it.
In your values folder create dimen.xml file and add following lines.
<!-- common settings -->
<dimen name="padding_left_right">23dp</dimen>
<dimen name="margin_left_right">23dp</dimen>
<dimen name="margin_left_right_large">50dp</dimen>
<dimen name="margin_top_bottom">30dp</dimen>
<dimen name="margin_top_bottom_lg">90dp</dimen>
<dimen name="margin_btn_lg">50dp</dimen>
<dimen name="text_btn_lg">21dp</dimen>
<dimen name="margin_top_bottom_alt">45dp</dimen>
<dimen name="heading_lg">35sp</dimen>
<dimen name="heading_md">21sp</dimen>
In your values folder create string.xml file and add following lines - adjusting appropriately.
<!-- app required strings -->
<string name="app_name">TrackBack</string>
<!-- Strings related to login -->
<string name="welcome_login">Do you have an account? Sign in</string>
<string name="welcome_msg">Keep track of everyone and everything you love... in real time!</string>
In your layout folder create activity_video_crop.xml
file or any appropriate name for this in your app.. and add following lines:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/welcome_constraint"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Activity_Welcome">
<FrameLayout
android:id="@+id/welcome_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextureView
android:id="@+id/videoview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
>
</TextureView>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimarySeeThrough"
>
<ImageView
android:layout_width="55dp"
android:layout_height="55dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="@dimen/margin_top_bottom_lg"
android:id="@+id/welcome_logo"
android:src="@drawable/logo_white"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_left_right"
android:id="@+id/welcome_text"
app:layout_constraintTop_toBottomOf="@+id/welcome_logo"
android:text="@string/app_name"
android:textSize="@dimen/heading_lg"
android:textColor="@color/white"
android:textStyle="bold"
android:textAlignment="center"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="90dp"
android:layout_marginRight="@dimen/margin_left_right"
android:layout_marginLeft="@dimen/margin_left_right"
android:id="@+id/welcome_note_2"
app:layout_constraintBottom_toBottomOf="@+id/btn_start"
android:text="@string/welcome_msg"
android:textSize="@dimen/heading_md"
android:textColor="@color/white"
android:backgroundTint="@color/white"
android:textAlignment="center"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/rounded_top_bottom_blue"
android:text=" Continue "
android:textSize="@dimen/text_btn_lg"
android:textAlignment="center"
android:gravity="center"
android:foregroundGravity="center"
android:textColor="@color/slight_white"
app:layout_constraintBottom_toBottomOf="@id/welcome_sign_in"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginBottom="@dimen/margin_top_bottom_alt"
android:layout_marginLeft="@dimen/margin_btn_lg"
android:layout_marginRight="@dimen/margin_btn_lg"
android:id="@+id/btn_start"
/>
<TextView
android:id="@+id/welcome_sign_in"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:textSize="20dp"
android:textColor="@color/slight_white"
android:text="@string/welcome_login"
android:layout_marginBottom="@dimen/margin_top_bottom_lg"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Note: All that's required here is the FrameLayout and the TextureView. I prefer using ConstraintLayout as the root to easily position items.
Step 3 - Basic code
Create a new activity class and call it ActivityCrop or something appropriate. Don’t forget to declare it inside AndroidManifest.xml file.
Imports:
package com.rahaprogramming.trackback;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.SurfaceTexture;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
import android.widget.FrameLayout;
import androidx.appcompat.app.AppCompatActivity;
public class Activity_Welcome extends AppCompatActivity implements
TextureView.SurfaceTextureListener, MediaPlayer.OnCompletionListener {
//declare class variables
Context context = this;
Uri video_uri;
// MediaPlayer instance
private MediaPlayer mMediaPlayer;
//views
private TextureView mTextureView;
FrameLayout frameLayout;
Surface surface;
// Original video size - we created this video and knew the size.
// for unknown size - use meta data
private float mVideoWidth = 1080;
private float mVideoHeight = 1440;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_welcome);
//Toolbar toolbar = findViewById(R.id.toolbar);
//setSupportActionBar(toolbar);
video_uri = Uri.parse("android.resource://"+getPackageName()+"/"+R.raw.trackback);
//set views
mTextureView = findViewById(R.id.videoview);
mTextureView.setClickable(false);
frameLayout = findViewById(R.id.welcome_frame);
//init MediaPlayer
mMediaPlayer = new MediaPlayer();
//set implemented listeners
mMediaPlayer.setOnCompletionListener(this);
mTextureView.setSurfaceTextureListener(this);
}
//plays the video
private void playVideo() {
//its a big file - use separate thread
new Thread(new Runnable() {
public void run() {
try {
mMediaPlayer.setDataSource(context,video_uri);
mMediaPlayer.setLooping(true);
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 (Exception e) { // I can split the exceptions to get which error i need.
Utils.log("Error: "+e.toString());
e.printStackTrace();
}
}
}).start();
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
//set surface
surface = new Surface(surfaceTexture);
mMediaPlayer.setSurface(surface);
//update viewable area
updateTextureViewSize(width, height);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
//set surface
surface = new Surface(surfaceTexture);
mMediaPlayer.setSurface(surface);
//update viewable area
updateTextureViewSize(width, height);
}
//uses the view width to determine best crop to fit the screen
//@param int viewWidth width of viewport
//@param int viewHeight height of viewport
private void updateTextureViewSize(int viewWidth, int viewHeight) {
float scaleX = 1.0f;
float scaleY = 1.0f;
Utils.log(viewWidth+" "+viewHeight+" "+mVideoHeight+" "+mVideoWidth);
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);
//transform the video viewing size
mTextureView.setTransform(matrix);
//set the width and height of playing view
mTextureView.setLayoutParams(new FrameLayout.LayoutParams(viewWidth, viewHeight));
//finally, play the video
playVideo();
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
// callback when the video is over
public void onCompletion(MediaPlayer mp) {
//if this happens.. never will.. just restart the video
mp.stop();
mp.release();
}
@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;
}
}
}
Step 5 - Video cropping
The resizing is done by the 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.
//uses the view width to determine best crop to fit the screen
//@param int viewWidth width of viewport
//@param int viewHeight height of viewport
private void updateTextureViewSize(int viewWidth, int viewHeight) {
float scaleX = 1.0f;
float scaleY = 1.0f;
Utils.log(viewWidth+" "+viewHeight+" "+mVideoHeight+" "+mVideoWidth);
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);
//transform the video viewing size
mTextureView.setTransform(matrix);
//set the width and height of playing view
mTextureView.setLayoutParams(new FrameLayout.LayoutParams(viewWidth, viewHeight));
//finally, play the video
playVideo();
}
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 in:
ImageView.setScaleType(ImageVIew.ScaleType.CENTER_CROP)
;
Great blog you have got here..
It’s difficult to find quality writing
like yours nowadays. I really appreciate people like you!
Take care!!
I have read a few of the articles on your blog site now, and I truly like your updating style.
What a fantastic written post. It’ s so useful …
excellent as well as fantastic blog. I actually wish to thank you, for giving us far better details.
Having read this I believed it was very enlightening.
I appreciate you finding the time and energy to put this
short article together.
I once again find myself spending a lot of time
both reading and commenting.
But so what, it was still worthwhile!
fantastic and outstanding blog. I actually intend to thanks, for offering us
far better details.
I like what you guys tend to be up too. Such clever work and coverage!
Keep up the good works guys I’ve added you
guys to my personal blogroll.
It’s really a nice and useful piece of information. I am happy that you shared this useful
info with us. Please keep us informed like this.
Thank you for sharing.