使用缓冲视频更改Android VideoView方向

Mar*_* G. 50 android android-orientation android-videoview

我正在尝试复制Android市场中最新YouTube应用的功能.观看视频时,有两个独立的布局,一个是纵向提供额外的信息,一个是横向,提供视频的全屏视图.

YouTube应用程序纵向布局
YouTupe应用程序处于纵向模式

YouTube应用横向布局
横向模式的YouTube应用

(抱歉照片的随机性,但它们是我能找到的第一个实际布局照片)

这通常很容易做到 - 只需在layout-land中指定一个替代布局,一切都会很好.YouTube应用程序的功能非常好(以及我想要复制的内容)是在方向更改时,视频会继续播放,而不必从头开始重新缓冲.

我已经发现,覆盖onConfigurationChange()并设置新的LayoutParameters将允许我在不强制拒绝的情况下调整视频大小 - 但是当多次旋转屏幕时,视频会随机缩放到不同的宽度/高度.我已尝试在VideoView上进行各种invalidate()调用,尝试在父RelativeLayout容器上调用RequestLayout()并尝试尽可能多的不同内容,但我似乎无法让它正常工作.任何建议将不胜感激!

这是我的代码:

@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));
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:我在logcat中发现了一些有趣的输出,当我的视频旋转时出现这似乎是罪魁祸首 - 虽然我不知道如何解决它:

正确调整大小时的Logcat输出(占用整个窗口)

注意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
Run Code Online (Sandbox Code Playgroud)

调整大小时Logcat输出错误(占据全屏的一小部分)

注意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
Run Code Online (Sandbox Code Playgroud)

也许有人知道'Overlay'是什么以及为什么它没有得到正确的身高值?

Mar*_* G. 57

编辑:( 2016年6月)

这个答案非常陈旧(我认为android 2.2/2.3)并且可能与下面的其他答案没有相关性!首先看看他们,除非你正在开发传统的Android :)


我能够将问题缩小到VideoView类中的onMeasure函数.通过创建子类并覆盖onMeasure函数,我能够获得所需的功能.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在我的活动中,我刚刚做了以下事情:

@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);

    }
}
Run Code Online (Sandbox Code Playgroud)

这条线:

questionVideo.getHolder().setFixedSize(displayWidth, smallHeight);
Run Code Online (Sandbox Code Playgroud)

是这项工作的关键.如果你在没有这个人的情况下进行setDimensions调用,视频仍然不会调整大小.

您需要做的唯一其他事情是确保您在onCreate()方法中调用setDimensions()或者您的视频不会开始缓冲,因为视频不会设置为在任何大小的表面上绘制.

// onCreate()
questionVideo.setDimensions(initialWidth, initialHeight); 
Run Code Online (Sandbox Code Playgroud)

最后一个关键部分 - 如果您发现自己想知道为什么VideoView没有在旋转时调整大小,您需要确保要调整大小的尺寸要么与可见区域完全相等,要么小于它.我有一个非常大的问题,当我在屏幕上仍然有通知栏/标题栏并且根本没有调整VideoView的大小时,我将VideoView的宽度/高度设置为整个显示大小.只需删除通知栏和标题栏即可解决问题.

希望这有助于未来的人!


BoD*_*BoD 23

首先,非常感谢您自己的广泛答案.

我遇到了同样的问题,旋转后视频大部分时间内视频会变小或变大或变形.

我尝试了你的解决方案,但我也尝试过随机的东西,我偶然发现如果我的VideoView在其父级中心,它会神奇地自行工作(不需要自定义VideoView或任何东西).

更具体地说,通过这种布局,我在大多数时候重现了这个问题:

<?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>
Run Code Online (Sandbox Code Playgroud)

有了这个布局,我从来没有遇到过这个问题(另外,视频是居中的,无论如何都应该如此;):

<?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>
Run Code Online (Sandbox Code Playgroud)

它也适用wrap_content而不是match_parent(视频仍占用所有空间)对我来说没有多大意义.

无论如何,我对此没有任何解释 - 这对我来说看起来像一个VideoView错误.

  • 你救了我!与此挣扎了8个小时! (3认同)

Aba*_*art 23

这是一个用最少的代码完成你想要的东西的简单方法:

AndroidManifest.xml中:

android:configChanges="orientation|keyboard|keyboardHidden|screenSize|screenLayout|uiMode"
Run Code Online (Sandbox Code Playgroud)

注意:根据API的需要进行编辑,这包括10+,但是较低的API需要删除此行的"screenSize | screenLayout | uiMode"部分

在"OnCreate"方法中,通常在"super.onCreate"下,添加:

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN);
Run Code Online (Sandbox Code Playgroud)

然后在某处,通常在底部,添加:

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

只要方向发生变化而不中断播放,这将调整视频大小全屏,只需要覆盖配置方法.


小智 11

VideoView使用所谓的覆盖,即覆盖视频的区域.该叠加层位于持有VideoView的窗口后面.VideoView在其窗口中打出一个洞,以便可以看到叠加层.然后它使其与布局保持同步(例如,如果移动或调整VideoView的大小,则必须移动叠加层并调整其大小).

在布局阶段某处存在一个错误,使得叠加层使用VideoView设置的先前大小.

要修复它,请继承VideoView并覆盖onLayout:

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

并且叠加层将具有VideoView布局尺寸的正确尺寸.


dco*_*cow 7

复制YouTube应用

我设法构建了一个不需要 android:configChanges="orientation"或自定义的示例项目VideoView.由此产生的体验与YouTube应用在视频播放过程中处理轮换的方式相同.换句话说,视频不需要暂停,重新缓冲或重新加载,并且在设备方向改变时不会跳过或丢弃任何音频帧.

优化方法

此方法使用TextureView及其附带SurfaceTexture作为MediaPlayer当前视频帧的接收器.由于SurfaceTexture使用GL纹理对象(简单地通过GL上下文中的整数引用),因此相信通过配置更改保留对SurfaceTexture的引用是可以的.在配置更改期间(以及后备Activity),TextureView本身将被销毁并重新创建,并且在连接之前,只需使用SurfaceTexture引用更新新创建的TextureView.

我已经创建了一个完整的工作示例,显示了如何以及何时初始化MediaPlayer和可能的MediaController,我将在下面突出显示与此问题相关的有趣部分:

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();
    }

    // ...

}
Run Code Online (Sandbox Code Playgroud)

由于解决方案使用保留的Fragment而不是手动处理配置更改的Activity,因此您可以自然地充分利用特定于配置的资源膨胀系统.不幸的是,如果你的最低sdk低于API 16,你基本上需要backport TextureView(我还没有).

最后,如果您有兴趣,请查看我的初步问题详细说明:我的原始方法,Android媒体堆栈,它为什么不起作用,以及替代解决方法.