I would like to confirm that what I am doing is indeed the correct way as some elements behave unexpected.
First, I have a landscape and portrait layout, as I understand, doing this will automatically detect if the phone is in portrait/landscape mode:
- layout
- activity_video_player.xml
- layout-land
- activity_video_player.xml
Then when the user selects a video from the gallery, I check if the video was taking in landscape or portrait, by doing this (inside OnCreate):
int w;
int h;
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(this, videoURI);
String height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
String width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
w = Integer.parseInt(width);
h = Integer.parseInt(height);
if (w > h) {
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} else {
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
I have tested this and it works fine, but I noticed some of my xml elements (play button) is placed incorrectly.
So my app flow is:
MainActivity --> SelectvidButton --> Gallery Intent --> VideoPlayActivity
My Question
Is this the correct way of doing this and if it is, is there any reason why some of the xml elements get placed incorrectly?
EDIT 1:
I noticed that this only happens when the activity is launched for the first time, if I press the back button and select the same video again, the layout is perfectly like I want it to be.
EDIT 2:
I have also noticed that this only happens if the previous activity (MainActivity) was in the same orientation than what the selected video is.
Here is what I ended up doing.
Instead of using MediaMetadataRetriever to get the width and height, I first retrieve a Bitmap from the video file and then setting the orientation according to the width and hight of the Bitmap, as shown below:
private void rotateScreen() {
try {
//Create a new instance of MediaMetadataRetriever
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
//Declare the Bitmap
Bitmap bmp;
//Set the video Uri as data source for MediaMetadataRetriever
retriever.setDataSource(this, mVideoUri);
//Get one "frame"/bitmap - * NOTE - no time was set, so the first available frame will be used
bmp = retriever.getFrameAtTime();
//Get the bitmap width and height
videoWidth = bmp.getWidth();
videoHeight = bmp.getHeight();
//If the width is bigger then the height then it means that the video was taken in landscape mode and we should set the orientation to landscape
if (videoWidth > videoHeight) {
//Set orientation to landscape
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
//If the width is smaller then the height then it means that the video was taken in portrait mode and we should set the orientation to portrait
if (videoWidth < videoHeight) {
//Set orientation to portrait
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
} catch (RuntimeException ex) {
//error occurred
Log.e("MediaMetadataRetriever", "- Failed to rotate the video");
}
}
Related
I'm currently detecting the width and height of a selected video by doing the following:
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(this, mVideoUri);
String height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
String width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
The activity in manifest:
<activity android:name=".TrimVideoActivity"
android:theme="#style/Theme.AppCompat.Light.NoActionBar.FullScreen"
/>
If I toast the the width and height it always returns w : 1920 h : 1080 no matter what the dimensions of the video are. I think it is returning the width and height of the device instead.
Is there something that I'm missing or doing wrong?
EDIT
By following the link that #VladMatvienko suggested I was able to get the correct width and height of the video file, this is how I implemented it:
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
Bitmap bmp = null;
retriever.setDataSource(this, mVideoUri);
bmp = retriever.getFrameAtTime();
String videoWidth = String.valueOf(bmp.getWidth());
String videoHeight = String.valueOf(bmp.getHeight());
Now I want to rotate the screen depending on the result (width/height), I tried it by doing the following:
int w = Integer.parseInt(videoWidth);
int h = Integer.parseInt(videoHeight);
if (w > h) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
} if(w < h) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
But, the screen always gets rotated to landscape, instead of being set to portrait when the width is smaller than the height?
I decided to add a answer explaining how I fixed this issue.
The issue with my initial approach:
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(this, mVideoUri);
String height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
String width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
The problem with this is that the video might not have the metadata that I'm looking for, which will result a nullpointerexception.
To avoid this issue I can get one frame from the video (as a bitmap) and get the width and height from that bitmap by doing the following:
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
Bitmap bmp;
retriever.setDataSource(this, mVideoUri);
bmp = retriever.getFrameAtTime();
But, this brings up another issue, that you might not have. In my case I would like to get the first/nearest frame to the start, because if the screen was rotated during the capturing of the video, then the width/height of the frame will change, so I just added 1 to getFrameAtTime(1).
Now I can rotate the screen depending of the width and height of my video file by doing:
try {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
Bitmap bmp;
retriever.setDataSource(this, mVideoUri);
bmp = retriever.getFrameAtTime(1);
int videoWidth = bmp.getWidth();
int videoHeight = bmp.getHeight();
if (videoWidth > videoHeight) {
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
if (videoWidth < videoHeight) {
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}catch (RuntimeException ex){
Log.e("MediaMetadataRetriever", "- Failed to rotate the video");
}
Of course the above will be called from within onCreate.
Hope this helps someone out there.
Following are the screenshots when using texture view in camera2 apis.In full screen the preview stretches,but it works when using lower resolution(second image).
How to use this preview in full screen without stretching it.
Below answer assumes you are in portrait mode only.
Your question is
How to use the preview in full-screen without stretching it
Let's break it down to 2 things:
You want the preview to fill the screen
The preview cannot be distorted
First you need to know that this is logically impossible without crop, if your device's viewport has a different aspect ratio with any available resolution the camera provides.
So I would assume you accept cropping the preview.
Step 1: Get a list of available resolutions
StreamConfigurationMap map = mCameraCharacteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
throw new IllegalStateException("Failed to get configuration map: " + mCameraId);
}
Size[] sizes = map.getOutputSizes(SurfaceTexture.class);
Now you get a list of available resolutions (Sizes) of your device's camera.
Step 2: Find the best aspect ratio
The idea is to loop the sizes and see which one best fits. You probably need to write your own implementation of "best fits".
I am not going to provide any code here since what I have is quite different from your use case. But ideally, it should be something like this:
Size findBestSize (Size[] sizes) {
//Logic goes here
}
Step 3: Tell the Camera API that you want to use this size
//...
textureView.setBufferSize(bestSize.getWidth(), bestSize.getHeight());
Surface surface = textureView.getSurface();
try {
mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(surface);
mCamera.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
mSessionCallback, null);
} catch (final Exception e) {
//...
}
Step 4: Make your preview extends beyond your viewport
This is then nothing related to the Camera2 API. We "crop" the preview by letting the SurfaceView / TextureView extends beyond device's viewport.
First place your SurfaceView or TextureView in a RelativeLayout.
Use the below to extend it beyond the screen, after you get the aspect ratio from step 2.
Note that in this case you probably need to know this aspect ratio before you even start the camera.
//Suppose this value is obtained from Step 2.
//I simply test here by hardcoding a 3:4 aspect ratio, where my phone has a thinner aspect ratio.
float cameraAspectRatio = (float) 0.75;
//Preparation
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int screenWidth = metrics.widthPixels;
int screenHeight = metrics.heightPixels;
int finalWidth = screenWidth;
int finalHeight = screenHeight;
int widthDifference = 0;
int heightDifference = 0;
float screenAspectRatio = (float) screenWidth / screenHeight;
//Determines whether we crop width or crop height
if (screenAspectRatio > cameraAspectRatio) { //Keep width crop height
finalHeight = (int) (screenWidth / cameraAspectRatio);
heightDifference = finalHeight - screenHeight;
} else { //Keep height crop width
finalWidth = (int) (screenHeight * cameraAspectRatio);
widthDifference = finalWidth - screenWidth;
}
//Apply the result to the Preview
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) cameraView.getLayoutParams();
lp.width = finalWidth;
lp.height = finalHeight;
//Below 2 lines are to center the preview, since cropping default occurs at the right and bottom
lp.leftMargin = - (widthDifference / 2);
lp.topMargin = - (heightDifference / 2);
cameraView.setLayoutParams(lp);
If you don't care about the result of Step 2, you can actually ignore Step 1 to Step 3 and simply use a library out there, as long as you can configure its aspect ratio. (It looks like this one is the best, but I haven't tried yet)
I have tested using my forked library. Without modifying any code of my library, I managed to make the preview fullscreen just by using Step 4:
Before using Step 4:
After using Step 4:
And the preview just after taking a photo will not distort as well, because the preview is also extending beyond your screen.
But the output image will include area that you cannot see in the preview, which makes perfect sense.
The code of Step 1 to Step 3 are generally referenced from Google's CameraView.
That's a common problem on some devices. I've noticed it mostly on samsung. You may use a trick with setting transformation on your TextureView to make it centerCrop like ImageView behaviour
I also faced similar situation, but this one line solved my problem
view_finder.preferredImplementationMode = PreviewView.ImplementationMode.TEXTURE_VIEW
in your xml:
<androidx.camera.view.PreviewView
android:id="#+id/view_finder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
For camera implementation using cameraX you can refer
https://github.com/android/camera-samples/tree/master/CameraXBasic
I figured out what was your poroblem. You were probably trying something like this:
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
#Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int j) {
cam.startPreview(surfaceTexture, i, j);
cam.takePicture();
}
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) { }
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { return false; }
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { }
});
I have an Android app which is capturing an image as a CameraPreview image.
Prior to capturing the image is appears on the Phone screen in Portrait mode (as needed).
But when I do a Save, the image goes to the JPG file rotated to Landscape mode.
I have confirmed this by going to MyFiles, finding the image and viewing it with Gallery - it pops up on-screen in Landscape mode.
Additionally when I upload the image files onto my computer they show up in Landscape mode there as well.
The image is correct, but the orientation is wrong.
The method that I am using to Save is as follows:
private boolean savePhoto(Bitmap bm) {
FileOutputStream image = null;
try {
image = new FileOutputStream(mLocation);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
bm.compress(CompressFormat.JPEG, 100, image);
if (bm != null) {
int h = bm.getHeight();
int w = bm.getWidth();
} else {
return false;
}
return true;
}
Can I insert code here or call a routine (if so what code is needed) that can rotate the image into the appropriate orientation prior to the actual Save?
Or is there some other manner to change the resultant JPG image orientation?
POST EDIT - I just now added the following code to examine the Saved JPG file.
ExifInterface exif = new ExifInterface(f.getPath());
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
However it returned an Orientation = 0 which is not telling me that it 'thinks' that the JPG was Saved in Landscape orientation so that I might be able to use other posting's code.
Thanks
There are several stack overflow questions that deal with keeping the activity in a constant orientation, suggesting either to set android:screenOrientation (or this) or to deal with the configuration changes manually.
However, these answers do not solve the problem if one wants to keep the benefits of automatic layout for most of the views, but to keep one view in a constant orientation. For example I have one main view for the content (a bitmap) and optionally several "toolbars". When the device is rotated from portrait to landscape, I want the toolbars to re-layout automatically. On the other hand, the main content view has a bitmap, and this should rotate with the device. Here is a simple code I use in my onDraw:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Bitmap bm = ... // code to get a bitmap
if (bm != null) {
canvas.drawBitmap(bm,0,0,mPaint);
}
}
This obviously does not work as intended because when the device orientation changes, the view is relayout and its coordinate system rotates in this orientation change by 90 degrees with respect to the physical device. One way to compensate for that would be to use a Matrix-object to draw the bitmap with 90 degrees rotation if the device orientation has changed. However, then also the input coordinates would have to be changed in order to keep the view input points and the point on the bitmap in correspondence.
Therefore my question after this long explanation is: can I keep the view so that its coordinate system with respect to the device is not rotated when the orientation changes.
Here is a picture that expresses what I want and what I currently get (xy coordinate system for the view also illustrated).
Inside your activity in manifest xml use the screen orientation attribut with portrait value
Like
<activity
android:name=".YourActivityName"
android:screenOrientation="portrait" >
</activity>
I am not sure but you should try following
put one xml in layout folder which indicate portrait layout
create one folder named it layout-land
put xml which indicate landscape layout with same name as in layout folder
Android automatically take that layout from land-layout when you will rotate your screen to landscape.
Another way
Activity will be restarted when orientation is changed so make to layout for that activity and you can set layout to your activity as per orientation.
if (getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE)
{
setContentView(R.layout.landscape_layout);
}
else
{
setContentView(R.layout.portrait_layout);
}
Hope it will help you.
You can check the orientation and redraw your View after orientation will be changed
if (getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE)
{
//...
}
else
{
//...
}
Use this code in your Manifest
<uses-permission android:name="android.permission.SET_ORIENTATION"/>
<activity
android:name="com.package.MyActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="portrait" >
</activity>
Well, first of all, you will not need two layouts.
Create only one xml with an image view and a layout which has property align parent bottom set to true.
Now as per the images you have provided, you want to rotate the image anticlockwise when your device is in landscape. For that , you can use my image rotation code :-
You can always check the rotation of the image using Matrix and rotate it accordingly.
This code goes in onCreate-->
The code below is to rotate the image when taken from camera or gallery. You can modify according to your needs. I have put comments below to simplify understanding.
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = false;
bmOptions.inPurgeable = true;
Bitmap cameraBitmap = BitmapFactory.decodeFile(filePath);//You convert your image view image here in a bitmap
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cameraBitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
ExifInterface exif = new ExifInterface(filePath);
float rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
System.out.println(rotation);
float rotationInDegrees = exifToDegrees(rotation);
System.out.println(rotationInDegrees);
//These three lines below set the rotation.
//Put an if condition here to check device rotation and set the rotation of image view as per the device rotation.
//The line below will help you get device rotation.
//getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE
Matrix matrix = new Matrix();
matrix.postRotate(rotationInDegrees);//Here you set the rotation value in which you want your image view to be rotated
//If ends here
Bitmap scaledBitmap = Bitmap.createBitmap(cameraBitmap);
Bitmap rotatedBitmap = Bitmap.createBitmap(cameraBitmap , 0, 0, scaledBitmap .getWidth(), scaledBitmap .getHeight(), matrix, true);
FileOutputStream fos=new FileOutputStream(filePath);
rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
//this rotatedbitmap you can set in your image view
//onCreate Code Ends here.
//This function below is used to get rotation:-
private static float exifToDegrees(float exifOrientation) {
if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) { return 90; }
else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) { return 180; }
else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) { return 270; }
return 0;
}
I have a code in which I call the gallery to select an image that i show in a imageview . This works fine and maintains the aspect ratio.
The problem is when the image size is larger than 2048 x 2048
I am using this code when it is the case:
uriIsNowAbitmap =
MediaStore.Images.Media.getBitmap(this.getContentResolver(),
selectedImageUri);
//...
int height = uriIsNowAbitmap.getHeight();
int width = uriIsNowAbitmap.getWidth();
if ((width>=2048)||(height>=2048)) {
int newheight = height/10; // height in pixels
int newwidth = width/10; // width in pixels
Bitmap avatarScaled = Bitmap.createScaledBitmap(uriIsNowAbitmap, newwidth, newheight, true);
previewNewAvatar.setImageBitmap(avatarScaled);
}
also works correctly, but with a problem. The images are not of the type shown rotated landscape
to try to explain, I put this example. This is the picture of the gallery:
when I select the picture and assign it to the imageview is shown rotated:
I do not understand why. I've tried a thousand ways and have read a lot of information about resize, i have read threads and sample code for this website (Android: high quality image resizing / scaling) and and many more... but nothing helps.
The intention is to show in this way ...make it with photoshop :)
I appreciate any help, took many hours trying to fix it
Regards
Firstly, you will have to detect the orientation change, and when you detect a change to landscape orientation you just rotate your bitmap accordingly. You will have to declare your Bitmap avatarScaled as a global variable in order to access it in the onConfigurationChanged() method below:
Use the onConfigurationChanged method of Activity to detect change in orientation. See the following code:
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Matrix matrix = new Matrix();
matrix.postRotate(90);
Bitmap rotated = Bitmap.createBitmap(avatarScaled, 0, 0, avatarScaled.getWidth(),
avatarScaled.getHeight(), matrix, true);
previewNewAvatar.setImageBitmap(rotated);
}
}
You also have to edit the appropriate element in your manifest file to include the android:configChanges Just see the code below:
<activity android:name=".MyActivity"
android:configChanges="orientation|keyboardHidden"
android:label="#string/app_name">