Change the orientation of Android VideoView using buffered video

I am trying to significantly expand the functionality of the latest YouTube application in the Android market. When watching a video, there are two separate layouts, one in the portrait, which provides additional information, and one in the landscape, which provides full viewing of the video.

YouTube app portrait layout
YouTupe app in portrait mode

YouTube app landscape layout
YouTube app in landscape mode

(Sorry for the randomness of the photos, but these were the first photos that I could find in the actual layout)

This is pretty easy to do fine - just specify an alternative layout in the layout, and everything will be fine. The fact that the YouTube application is really good (and what I'm trying to replicate) is that when the orientation is changed, the video continues to play and does not need to be re-recorded from the very beginning.

I realized that overriding onConfigurationChange () and setting new LayoutParameters will allow me to resize the video without forced recovery, but if I accidentally rotate the screen, it will be scaled several times at different widths / heights. I tried to make all kinds of invalidate () calls in VideoView, tried to call RequestLayout () in the parent RelativeLayout container and just tried as many different things as possible, but I can't get it to work correctly. Any advice would be greatly appreciated!

Here is my code:

@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { questionText.setVisibility(View.GONE); respond.setVisibility(View.GONE); questionVideo.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); } else { questionText.setVisibility(View.VISIBLE); respond.setVisibility(View.VISIBLE); Resources r = getResources(); int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 150.0f, r.getDisplayMetrics()); questionVideo.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT, height)); } } 

EDIT: I found in logcat some interesting output that appears when my video rotates, which seems to be the culprit, although I have no idea how to fix it:

Logcat output when resized correctly (occupies the entire window)

pay attention to h = 726

 12-13 15:37:35.468 1262 1270 I ActivityManager: Config changed: { scale=1.0 imsi=310/4 loc=en_US touch=3 keys=1/1/2 nav=1/1 orien=2 layout=34 uiMode=17 seq=210} 12-13 15:37:35.561 1262 1268 I TIOverlay: Position/X0/Y76/W480/H225 12-13 15:37:35.561 1262 1268 I TIOverlay: Adjusted Position/X1/Y0/W403/H225 12-13 15:37:35.561 1262 1268 I TIOverlay: Rotation/90 12-13 15:37:35.561 1262 1268 I Overlay : v4l2_overlay_set_position:: w=480 h=224 12-13 15:37:35.561 1262 1268 I Overlay : v4l2_overlay_set_position:: w=402 h=726 12-13 15:37:35.561 1262 1268 I Overlay : dumping driver state: 12-13 15:37:35.561 1262 1268 I Overlay : output pixfmt: 12-13 15:37:35.561 1262 1268 I Overlay : w: 432 12-13 15:37:35.561 1262 1268 I Overlay : h: 240 12-13 15:37:35.561 1262 1268 I Overlay : color: 7 12-13 15:37:35.561 1262 1268 I Overlay : UYVY 12-13 15:37:35.561 1262 1268 I Overlay : v4l2_overlay window: 12-13 15:37:35.561 1262 1268 I Overlay : window l: 1 12-13 15:37:35.561 1262 1268 I Overlay : window t: 0 12-13 15:37:35.561 1262 1268 I Overlay : window w: 402 12-13 15:37:35.561 1262 1268 I Overlay : window h: 726 

Logcat output when resizing incorrectly (takes up a small part of the full screen)

pay attention to h = 480

 12-13 15:43:00.085 1262 1270 I ActivityManager: Config changed: { scale=1.0 imsi=310/4 loc=en_US touch=3 keys=1/1/2 nav=1/1 orien=2 layout=34 uiMode=17 seq=216} 12-13 15:43:00.171 1262 1268 I TIOverlay: Position/X0/Y76/W480/H225 12-13 15:43:00.171 1262 1268 I TIOverlay: Adjusted Position/X138/Y0/W266/H225 12-13 15:43:00.171 1262 1268 I TIOverlay: Rotation/90 12-13 15:43:00.179 1262 1268 I Overlay : v4l2_overlay_set_position:: w=480 h=224 12-13 15:43:00.179 1262 1268 I Overlay : v4l2_overlay_set_position:: w=266 h=480 12-13 15:43:00.179 1262 1268 I Overlay : dumping driver state: 12-13 15:43:00.179 1262 1268 I Overlay : output pixfmt: 12-13 15:43:00.179 1262 1268 I Overlay : w: 432 12-13 15:43:00.179 1262 1268 I Overlay : h: 240 12-13 15:43:00.179 1262 1268 I Overlay : color: 7 12-13 15:43:00.179 1262 1268 I Overlay : UYVY 12-13 15:43:00.179 1262 1268 I Overlay : v4l2_overlay window: 12-13 15:43:00.179 1262 1268 I Overlay : window l: 138 12-13 15:43:00.179 1262 1268 I Overlay : window t: 0 12-13 15:43:00.179 1262 1268 I Overlay : window w: 266 12-13 15:43:00.179 1262 1268 I Overlay : window h: 480 

Maybe someone knows what "Overlay" is and why he is not getting the correct height value?

+47
android android-orientation android-videoview
Dec 13 '10 at 22:16
source share
10 answers

I managed to narrow down the problem to the onMeasure function in the VideoView class. By creating a child class and overriding the onMeasure function, I was able to get the desired functionality.

 public class VideoViewCustom extends VideoView { private int mForceHeight = 0; private int mForceWidth = 0; public VideoViewCustom(Context context) { super(context); } public VideoViewCustom(Context context, AttributeSet attrs) { this(context, attrs, 0); } public VideoViewCustom(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void setDimensions(int w, int h) { this.mForceHeight = h; this.mForceWidth = w; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Log.i("@@@@", "onMeasure"); setMeasuredDimension(mForceWidth, mForceHeight); } } 

Then, inside my operation, I did the following:

 @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); questionVideo.setDimensions(displayHeight, displayWidth); questionVideo.getHolder().setFixedSize(displayHeight, displayWidth); } else { getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); questionVideo.setDimensions(displayWidth, smallHeight); questionVideo.getHolder().setFixedSize(displayWidth, smallHeight); } } 

Line:

 questionVideo.getHolder().setFixedSize(displayWidth, smallHeight); 

is key to doing this work. If you make a call to setDimensions without this guy, the video will still not change.

The only thing you need to do is make sure that you call setDimensions () inside the onCreate () method, otherwise your video will not start buffering since the video will not be set to paint on any size surface.

 // onCreate() questionVideo.setDimensions(initialWidth, initialHeight); 

One final key - if you ever wonder why VideoView doesn't resize when rotated, you need to make sure that the dimensions you resize are either exactly equal to the visible area or smaller. I had a really big problem when I set the width / height of the VideoView for the whole screen size, when I still had a notification bar / title bar on the screen, and it didn't change the size of the VideoView at all. The problem is simply removing the notification bar and title bar.

Hope this helps someone in the future!

EDIT: (June 2016)

This answer is very old (I think Android 2.2 / 2.3) and probably not as relevant as the other answers below! Look at them first if you are not developing legacy Android :)

+56
Dec 15 '10 at 16:52
source share

First of all, many thanks for your own extensive answer.

I had the same problem that in most cases the video will be smaller or larger or distorted inside the VideoView after rotation.

I tried your solution, but I also tried random things, and accidentally noticed that if my VideoView is concentrated in its parent, it magically works on its own (no custom VideoView is needed or something else).

To be more specific, with this layout, I reproduce the problem most of the time:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <VideoView android:id="@+id/videoView" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> 

With this one layout, I never had a problem (plus, the video is focused, as it should be anyway;):

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <VideoView android:id="@+id/videoView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerInParent="true" /> </RelativeLayout> 

It also works with wrap_content instead of match_parent (the video still takes up all the space), which makes no sense to me.

Anyway, I have no explanation for this - it seems like a VideoView error for me.

+23
Nov 01 2018-11-11T00:
source share

Here is a very simple way to accomplish what you want with minimal code:

AndroidManifest.xml:

 android:configChanges="orientation|keyboard|keyboardHidden|screenSize|screenLayout|uiMode" 

Note. Edit if necessary for your API, it covers 10+, but for lower APIs you need to remove the "screenSize | screenLayout | uiMode" part of this line

Inside the "OnCreate" method, usually in the "super.onCreate" section, add:

 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 

And then somewhere, usually below, add:

 @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); } 

This will cause the video to resize in full screen mode, when the orientation changes without interrupting playback and only requires overriding the setting method.

+20
Jan 01 '13 at 18:55
source share

VideoView uses what is called an overlay, which is the area in which the video is displayed. This overlay is located outside the window in which the VideoView is located. VideoView punch a hole in its window so that the overlay is visible. Then it synchronizes with the layout (for example, if you move or resize the VideoView, the overlay also needs to be moved and resized).

There, somewhere at the layout stage, an error appears due to which the overlay uses the previous size specified by VideoView.

To fix this, subclass VideoView and override onLayout:

 @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); getHolder().setSizeFromLayout(); } 

and the overlay will have the correct size from the dimensions of the VideoView layout.

+11
Feb 28 '14 at 16:59
source share

YouTube app replication

I managed to create a sample project that does not require android:configChanges="orientation" or a custom VideoView . The experience gained is identical to how the YouTube app handles rotation during video playback. In other words, the video does not need to be paused, re-buffered or reloaded, or to skip or delete any sound frames when changing the orientation of the device.

Optimal method

This method uses a TextureView and its accompanying SurfaceTexture as a receiver for the current MediaPlayer graphics card. Since SurfaceTexture uses the GL texture object (just refers to an integer from the GL context), believe that in order to save the SurfaceTexture reference through configuration changes. TextureView itself is destroyed and recreated during a configuration change (along with backup activity), and the newly created TextureView is simply updated using the SurfaceTexture link before attaching it.

I created a complete working example showing how and when to initialize your MediaPlayer and a possible MediaController, and I will highlight the interesting parts related to this issue below:

 public class VideoFragment { TextureView mDisplay; SurfaceTexture mTexture; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View rootView = inflater.inflate(R.layout.fragment_main, container, false); mDisplay = (TextureView) rootView.findViewById(R.id.texture_view); if (mTexture != null) { mDisplay.setSurfaceTexture(mTexture); } mDisplay.setSurfaceTextureListener(mTextureListener); return rootView; } TextureView.SurfaceTextureListener mTextureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { mTexture = surface; // Initialize your media now or flag that the SurfaceTexture is available.. } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { mTexture = surface; } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { mTexture = surface; return false; // this says you are still using the SurfaceTexture.. } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { mTexture = surface; } }; @Override public void onDestroyView() { mDisplay = null; super.onDestroyView(); } // ... } 

Since the solution uses the saved fragment, and not the activity that manually processes configuration changes, you can fully use the resource inflation system, depending on the configuration, as you naturally. Unfortunately, if your minimum sdk level is below API 16, you will essentially need a backport TextureView (which I haven't done yet).

Finally, if you're interested, check out my original question , detailing: my original approach, the Android stack for Android, why it doesn't work, and an alternative workaround.

+7
Dec 10 '13 at 0:48
source share

use this:

 @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { getActivity().getWindow().addFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN); getActivity().getWindow().clearFlags( WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { getActivity().getWindow().addFlags( WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); getActivity().getWindow().clearFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN); } } 

Also, don't forget to add the line below to your activity in the manifest:

 android:configChanges="orientation|keyboard|keyboardHidden|screenSize" 
+3
Oct 28 '13 at 10:48 on
source share

I gave examples from some of the answers here and tried to do it my own way. Sounds like a simple solution.

 videoLayout = (RelativeLayout) videoView.findViewById(R.id.videoFrame); 

onConfigurationChange inside the fragment. Where the video is displayed in full screen mode in landscape mode. Please note that I am hiding the action bar.

 @Override public void onConfigurationChanged(Configuration newConfig) { int height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200.0f,getResources().getDisplayMetrics()); ActionBar actionBar = weatherActivity.getSupportActionBar(); LayoutParams params = videoLayout.getLayoutParams(); if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { if(actionBar.isShowing()) actionBar.hide(); params.width = ViewGroup.LayoutParams.MATCH_PARENT; params.height = ViewGroup.LayoutParams.MATCH_PARENT; videoLayout.requestLayout(); } else if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { if(!actionBar.isShowing()) actionBar.show(); params.width = ViewGroup.LayoutParams.MATCH_PARENT; params.height = height; videoLayout.requestLayout(); } super.onConfigurationChanged(newConfig); } 

and here is my layout file

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <RelativeLayout android:id="@+id/videoFrame" android:layout_height="200dp" android:layout_width="match_parent"> <VideoView android:id="@+id/video" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_centerHorizontal="true"/> </RelativeLayout> 

I have another relativelayout below this that is not in this layout. But it does not matter.

+2
Apr 08 '15 at 17:53 on
source share

see my sample code, it works for me

 public class CustomVideoView extends android.widget.VideoView { private int width; private int height; private Context context; private VideoSizeChangeListener listener; private boolean isFullscreen; public CustomVideoView(Context context) { super(context); init(context); } public CustomVideoView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } /** * get video screen width and height for calculate size * * @param context Context */ private void init(Context context) { this.context = context; setScreenSize(); } /** * calculate real screen size */ private void setScreenSize() { Display display = ((Activity) context).getWindowManager().getDefaultDisplay(); if (Build.VERSION.SDK_INT >= 17) { //new pleasant way to get real metrics DisplayMetrics realMetrics = new DisplayMetrics(); display.getRealMetrics(realMetrics); width = realMetrics.widthPixels; height = realMetrics.heightPixels; } else if (Build.VERSION.SDK_INT >= 14) { //reflection for this weird in-between time try { Method mGetRawH = Display.class.getMethod("getRawHeight"); Method mGetRawW = Display.class.getMethod("getRawWidth"); width = (Integer) mGetRawW.invoke(display); height = (Integer) mGetRawH.invoke(display); } catch (Exception e) { //this may not be 100% accurate, but it all we've got width = display.getWidth(); height = display.getHeight(); } } else { //This should be close, as lower API devices should not have window navigation bars width = display.getWidth(); height = display.getHeight(); } // when landscape w > h, swap it if (width > height) { int temp = width; width = height; height = temp; } } /** * set video size change listener * */ public void setVideoSizeChangeListener(VideoSizeChangeListener listener) { this.listener = listener; } public interface VideoSizeChangeListener { /** * when landscape */ void onFullScreen(); /** * when portrait */ void onNormalSize(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { // full screen when landscape setSize(height, width); if (listener != null) listener.onFullScreen(); isFullscreen = true; } else { // height = width * 9/16 setSize(width, width * 9 / 16); if (listener != null) listener.onNormalSize(); isFullscreen = false; } } /** * @return true: fullscreen */ public boolean isFullscreen() { return isFullscreen; } /** * set video sie * * @param w Width * @param h Height */ private void setSize(int w, int h) { setMeasuredDimension(w, h); getHolder().setFixedSize(w, h); } } 

and do not recreate the view when changing orientation

 // AndroidManifest.xml android:configChanges="screenSize|orientation|keyboardHidden" 

portrain

scenery landscape

my source code is here

+2
Jun 26 '15 at 19:32
source share

While the Mark37 (very useful) answer works, manually setting dimensions (using setDimensions) is required. This may be good in an application where the required sizes are known in advance, but if you want the views to automatically determine the size based on the video parameters (for example, to make sure the original aspect ratio is preserved), a different approach is needed.

Fortunately, it turns out that the setDimensions part is not really needed. VideoView onMeasure already includes all the necessary logic, so instead of relying on someone to call setDimensions, you can just call super.onMeasure and then use the getMeasuredWidth / getMeasuredHeight view to get a fixed size.

So, the VideoViewCustom class becomes simple:

 public class VideoViewCustom extends VideoView { public VideoViewCustom(Context context) { super(context); } public VideoViewCustom(Context context, AttributeSet attrs) { this(context, attrs, 0); } public VideoViewCustom(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); getHolder().setFixedSize(getMeasuredWidth(), getMeasuredHeight()); } } 

This version does not require additional code in the caller and makes full use of the existing onMeasure implementation in VideoView.

In fact, this is similar to the approach proposed by Zapek, which does basically the same thing, only with onLayout, not onMeasure.

+1
Apr 09 '14 at 11:27
source share

I tried to use the code of other answers, but this did not solve my problem, because if I quickly turned the screen, my video size was full. Therefore, I partially use their code, which I insert into the stream, which updates the window very quickly. So here is my code:

  new Thread(new Runnable() { @Override public void run() { while(true) { try { Thread.sleep(1); } catch(InterruptedException e){} handler.post(new Runnable() { @Override public void run() { getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); } }); } } }).start(); 

This way the video size will always be correct (in my case).

0
Apr 27 '17 at 10:54 on
source share



All Articles