Android SurfaceView和TextureView 利用Mediaplayer播放视频

时间:2024-04-03 11:04:54

1 TextureView 概述

SurfaceView和View的区别简单概述。 已经讲解了SurfaceView和View的区别,SurfaceView会有自己独立的Surface,虽然它被添加到了View 结构树中,但是它其实不在view 树结构中,不能像普通的View一样可以平移,缩放,旋转等操作。同时SurfaceView不能放在类似RecyclerView或ScrollView中,一些View中的特性也无法使用。
其实SurfaceView是可以应用平移,缩放,旋转等动画的,但是由于其内部在独立的Surface上进行绘制,所以对SurfaceView进行这些动画并不会达到我们要的效果。

注意:上面说了SurfaceView不支持平移,缩放,旋转等动画,但是当我们利用SurfaceView测试这些不支持的动画时,如果使用的是7.0 甚至更高版本的Android系统,会发现SurfaceView也支持平移,缩放的动画操作。

代码示例:

public class Main24Activity extends AppCompatActivity {
    private SurfaceView mSurfaceView;
    private MediaPlayer mMediaPlayer;
    private SurfaceHolder mSurfaceHolder;
    private ProgressBar mProgressBar;
    private Button btn1;
    private Button btn2;
    private Button btn3;
    private Button btn4;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main24);
        mSurfaceView = findViewById(R.id.surfaceView);
        mProgressBar = findViewById(R.id.progressBar);
        btn1 = findViewById(R.id.btn1);
        btn2 = findViewById(R.id.btn2);
        btn3 = findViewById(R.id.btn3);
        btn4 = findViewById(R.id.btn4);

        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //属性动画
                mSurfaceView.animate().scaleX(0.5f).scaleY(0.5f);
            }
        });

        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mSurfaceView.animate().translationXBy(-10).translationYBy(-10);
            }
        });

        btn3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mSurfaceView.animate().rotationBy(10);
            }
        });

        btn4.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mSurfaceView.animate().alphaBy(0.1f);
            }
        });

        /**
         *   http://movie.ks.js.cn/flv/other/1_0.flv
       */
        String uri = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4";

        mMediaPlayer = new MediaPlayer();
        try {

           // mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mMediaPlayer.setDataSource(uri);
           // mMediaPlayer.setDataSource(this, Uri.parse(uri));
            mSurfaceHolder = mSurfaceView.getHolder();
            mSurfaceHolder.addCallback(new SHCallBack());
            mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
                @Override
                public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
                    System.out.println("===========video=================" + width + "   " + height + "    " + mp.getVideoWidth() + "   " + mp.getVideoHeight());
                    //尺寸动态变化
                    if (mp != null && width > 0 && height > 0 && mp.getVideoHeight() > 0 && mp.getVideoWidth() > 0) {
                        int videoWidth = mp.getVideoWidth();
                        int videoHeight = mp.getVideoHeight();
                        WindowManager wm = (WindowManager) Main24Activity.this.getSystemService(Context.WINDOW_SERVICE);
                        int screenwidth = wm.getDefaultDisplay().getWidth();
                        float size = (videoHeight * 1.0f) / (videoWidth * 1.0f);
                        int surfaceHeight = (int) (screenwidth *size);
                        FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mSurfaceView.getLayoutParams();
                       // layoutParams.width = width;
                        layoutParams.height = surfaceHeight;
                        mSurfaceView.setLayoutParams(layoutParams);
                    }
                }
            });
            mMediaPlayer.prepareAsync();
            mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    mProgressBar.setVisibility(View.INVISIBLE);
                    mMediaPlayer.start();
                    mMediaPlayer.setLooping(true);
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private class SHCallBack implements SurfaceHolder.Callback {
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            mMediaPlayer.setDisplay(holder);
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {

        }
    }
}

<?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">
    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="100dp" />
    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />
    <LinearLayout
        android:id="@+id/btncontainer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_gravity="bottom|left" >
        <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="btn1"/>

        <Button
            android:id="@+id/btn2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="btn2"/>

        <Button
            android:id="@+id/btn3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="btn3"/>

        <Button
            android:id="@+id/btn4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="btn4"/>
          </LinearLayout>
</FrameLayout>

android 5.0上的效果,播放的flv是链接
Android SurfaceView和TextureView 利用Mediaplayer播放视频
在android 7.0上的表现:播放的MP4视频,支持平移缩放
Android SurfaceView和TextureView 利用Mediaplayer播放视频

当放入ScrollView,RecycleView等ViewGroup中时无法显示

<ScrollView
    android:layout_width="match_parent"
    android:layout_height="100dp">
    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="100dp" />
</ScrollView>

Android SurfaceView和TextureView 利用Mediaplayer播放视频

2 TextureView

Android SurfaceView和TextureView 利用Mediaplayer播放视频
TextureView在api14提供,已经有了VideoView和SurfaceView为什么还需要TextureView呢,由于VideoView是继承SurfaceView而来,所以SurfaceView有的那些缺点VideoView也有,开发中SurfaceView最主要的限制就是不能像普通view一样平移,旋转,缩放等动画操作,要想缩放大小只能改变SurfaceView的大小,视频会一直铺满SurfaceView。TextureView没有自己独立的Surface所以可以克服SUrfaceView的缺点。
TextureView能展示一个内容流,这个内容流可以是一个video或者一个OpenGL场景。TextureView必须在硬件加速的窗口中使用,如果在软件渲染模式下则什么都不显示。

不像SurfaceView,TextureVIew更像一个普通view,它没有自己的独立Surface,所以上面也说了TextureView可以进行平移,旋转,缩放等动画操作。

TextureView内部利用SurfaceTexture渲染内容,TextureView创建完成不代表可以使用,需要内部的SurfaceTexture准备就绪也就是SurfaceTexture要等TextureView attached to a window后才能开始工作。可以通过给TextureView设置SurfaceTextuListener监听器来监听准备就绪的回调。

注意只有一个生产者能够使用TextureView,例如当你利用TextureView展示Camera预览图像时,你不能使用lockCanvas()在TextureView上绘制内容。

SurfaceTexture作为数据通道,把从数据源(MediaPlayer)中获取到的图像帧数据转为GL外部纹理,交给TextureVeiw作为View heirachy中的一个硬件加速层来显示,从而实现视频播放功能。

TextureView的优缺点:
优点:支持移动、旋转、缩放等动画,支持截图
缺点:必须在硬件加速的窗口中使用,占用内存比SurfaceView高,在5.0以前在主线程渲染,5.0以后有单独的渲染线程。

SurfaceTextureListener 接口

TextureView.SurfaceTextureListener:
Android SurfaceView和TextureView 利用Mediaplayer播放视频

  • onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)
    TextureView的 SurfaceTexture准备完成
  • onSurfaceTextureDestroyed(SurfaceTexture surface)
    SurfaceTexture将被销毁时调用
  • onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)
    SurfaceTexture 缓存的尺寸改变时调用
  • onSurfaceTextureUpdated(SurfaceTexture surface)
    SurfaceTexture利用SurfaceTexture.updateTexImage()更新时调用

代码示例:

public class Main25Activity extends AppCompatActivity {
    private TextureView mTextureView;
    private MediaPlayer mMediaPlayer;
    private Surface mSurface;
    private ProgressBar mProgressBar;
    private Button btn1;
    private Button btn2;
    private Button btn3;
    private Button btn4;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main25);
        mTextureView = findViewById(R.id.surfaceView);
        mProgressBar = findViewById(R.id.progressBar);
        btn1 = findViewById(R.id.btn1);
        btn2 = findViewById(R.id.btn2);
        btn3 = findViewById(R.id.btn3);
        btn4 = findViewById(R.id.btn4);

        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //属性动画
                mTextureView.animate().scaleX(0.5f).scaleY(0.5f);
            }
        });

        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mTextureView.animate().translationXBy(10).translationYBy(10);
            }
        });

        btn3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mTextureView.animate().rotationBy(10);
            }
        });

        btn4.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mTextureView.animate().alphaBy(0.1f);
            }
        });

        mMediaPlayer = new MediaPlayer();
        try {

            mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
                @Override
                public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                    mSurface = new Surface(surface);
                    //TextureView,5.0以前在主线程渲染,在5.0以后有独立的渲染线程,所以5.0的系统需要自己开启线程。
                    //必须在onSurfaceTextureAvailable回调后才能开启线程。
                    new Thread(new MediaThread()).start();
                }

                @Override
                public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

                }

                @Override
                public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                    return false;
                }

                @Override
                public void onSurfaceTextureUpdated(SurfaceTexture surface) {

                }
            });


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private class MediaThread implements Runnable{

        @Override
        public void run() {
            try {
                /**
                 * rtmp://live.hkstv.hk.lxdns.com/live/hks
                 rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov
                 mms://space.hngd.gov.cn/live1
                 http://movie.ks.js.cn/flv/other/1_0.flv
                 http://live.hkstv.hk.lxdns.com/live/hks/playlist.m3u8
                 */
                String uri = "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4";
                mMediaPlayer.setSurface(mSurface);
                mMediaPlayer.setDataSource(uri);
                // mMediaPlayer.setDataSource(this, Uri.parse(uri));
                mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
                    @Override
                    public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
                        System.out.println("===========video=================" + width + "   " + height + "    " + mp.getVideoWidth() + "   " + mp.getVideoHeight());
                        //尺寸动态变化
                        if (mp != null && width > 0 && height > 0 && mp.getVideoHeight() > 0 && mp.getVideoWidth() > 0) {
                            int videoWidth = mp.getVideoWidth();
                            int videoHeight = mp.getVideoHeight();
                            WindowManager wm = (WindowManager) Main25Activity.this.getSystemService(Context.WINDOW_SERVICE);
                            int screenwidth = wm.getDefaultDisplay().getWidth();
                            float size = (videoHeight * 1.0f) / (videoWidth * 1.0f);
                            int surfaceHeight = (int) (screenwidth *size);
                            FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mTextureView.getLayoutParams();
                            // layoutParams.width = width;
                            layoutParams.height = surfaceHeight;
                            mTextureView.setLayoutParams(layoutParams);
                        }
                    }
                });
                mMediaPlayer.prepareAsync();
                mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                    @Override
                    public void onPrepared(MediaPlayer mp) {
                        mProgressBar.setVisibility(View.INVISIBLE);
                        mMediaPlayer.start();
                        mMediaPlayer.setLooping(true);
                    }
                });
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

布局文件:

<?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">
        <TextureView
            android:id="@+id/surfaceView"
            android:layout_width="match_parent"
            android:layout_height="100dp" />

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />
    <LinearLayout
        android:id="@+id/btncontainer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_gravity="bottom|left" >
        <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="btn1"/>

        <Button
            android:id="@+id/btn2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="btn2"/>

        <Button
            android:id="@+id/btn3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="btn3"/>

        <Button
            android:id="@+id/btn4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="btn4"/>
    </LinearLayout>
</FrameLayout>

硬件加速

<activity
    android:name=".Main25Activity"
    android:screenOrientation="portrait"
    android:hardwareAccelerated="true">
</activity>

Android SurfaceView和TextureView 利用Mediaplayer播放视频
其他说明:(参考其他文章)

  • android 7.0上的surfaceview的性能比TextureView更有优势,支持平移、缩放操作。

  • TextureView增加了额外1~3帧的延迟显示画面更新

  • TextureView总是使用GL合成,而SurfaceView可以使用硬件overlay后端,占用更少的内存。

  • TextureView的内部缓冲队列导致比SurfaceView使用更多的内存