原标题:用MediaPlayer+TextureView封装一个完美实现全屏、小窗口的视频播放器

本文作者

本文由xiaoyanger授权发布。

xiaoyanger的博客地址:

http://www.jianshu.com/u/25c3b13f87ce

贴个简易的效果图:

1

为什么使用TextureView

在Android总播放视频可以直接使用VideoView,VideoView是通过继承自SurfaceView来实现的。

SurfaceView的大概原理就是在现有View的位置上创建一个新的Window,内容的显示和渲染都在新的Window中。这使得SurfaceView的绘制和刷新可以在单独的线程中进行,从而大大提高效率。

但是呢,由于SurfaceView的内容没有显示在View中而是显示在新建的Window中, 使得SurfaceView的显示不受View的属性控制,不能进行平移,缩放等变换,也不能放在其它RecyclerView或ScrollView中,一些View中的特性也无法使用。

TextureView是在4.0(API level 14)引入的,与SurfaceView相比,它不会创建新的窗口来显示内容。它是将内容流直接投放到View中,并且可以和其它普通View一样进行移动,旋转,缩放,动画等变化。TextureView必须在硬件加速的窗口中使用。

TextureView被创建后不能直接使用,必须要在它被它添加到ViewGroup后,待SurfaceTexture准备就绪才能起作用(看TextureView的源码,TextureView是在绘制的时候创建的内部SurfaceTexture)。

通常需要给TextureView设置监听器SurfaceTextuListener:

SurfaceTexture的准备就绪、大小变化、销毁、更新等状态变化时都会回调相对应的方法。当TextureView内部创建好SurfaceTexture后,在监听器的onSurfaceTextureAvailable方法中,用SurfaceTexture来关联MediaPlayer,作为播放视频的图像数据来源。

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

2

MediaPlayer介绍

MediaPlayer是Android原生的多媒体播放器,可以用它来实现本地或者在线音视频的播放,同时它支持https和rtsp ( https://developer.android.google.cn/guide/topics/media/media-formats.html)。

MediaPlayer定义了各种状态,可以理解为是它的生命周期。

这个状态图描述了MediaPlayer的各种状态,以及主要方法调用后的状态变化。

MediaPlayer的相关方法及监听接口:

方法

介绍

状态

setDataSource

设置数据源

Initialized

prepare

准备播放,同步

Preparing —> Prepared

prepareAsync

准备播放,异步

Preparing —> Prepared

start

开始或恢复播放

Started

pause

暂停

Paused

stop

停止

Stopped

seekTo

到指定时间点位置

PrePared/Started

reset

重置播放器

Idle

setAudioStreamType

设置音频流类型

--

setDisplay

设置播放视频的Surface

--

setVolume

设置声音

--

getBufferPercentage

获取缓冲半分比

--

getCurrentPosition

获取当前播放位置

--

getDuration

获取播放文件总时间

--

内部回调接口

介绍

状态

OnPreparedListener

准备监听

Preparing ——>Prepared

OnVideoSizeChangedListener

视频尺寸变化监听

--

OnInfoListener

指示信息和警告信息监听

--

OnCompletionListener

播放完成监听

PlaybackCompleted

Listener

播放错误监听

Error

OnBufferingUpdateListener

缓冲更新监听

--

MediaPlayer在直接new出来之后就进入了Idle状态,此时可以调用多个重载的setDataSource()方法从idle状态进入Initialized状态(如果调用setDataSource()方法的时候,MediaPlayer对象不是出于Idle状态,会抛异常,可以调用reset()方法回到Idle状态)。

调用prepared()方法和preparedAsync()方法进入Prepared状态,prepared()方法直接进入Parpared状态,preparedAsync()方法会先进入PreParing状态,播放引擎准备完毕后会通过OnPreparedListener.onPrepared()回调方法通知Prepared状态。

在Prepared状态下就可以调用start()方法进行播放了,此时进入started()状态,如果播放的是网络资源,Started状态下也会自动调用客户端注册的OnBufferingUpdateListener.OnBufferingUpdate()回调方法,对流播放缓冲的状态进行追踪。

pause()方法和start()方法是对应的,调用pause()方法会进入Paused状态,调用start()方法重新进入Started状态,继续播放。

stop()方法会使MdiaPlayer从Started、Paused、Prepared、PlaybackCompleted等状态进入到Stoped状态,播放停止。

当资源播放完毕时,如果调用了setLooping(boolean)方法,会自动进入Started状态重新播放,如果没有调用则会自动调用客户端播放器注册的OnCompletionListener.OnCompletion()方法,此时MediaPlayer进入PlaybackCompleted状态,在此状态里可以调用start()方法重新进入Started状态。

封装考虑

MediaPlayer的方法和接口比较多,不同的状态调用各个方法后状态变化情况也比较复杂。

播放相关的逻辑只与MediaPlayer的播放状态和调用方法相关,而界面展示和UI操作很多时候都需要根据自己项目来定制。

参考原生的VideoView,为了解耦和方便定制,把MediaPlayer的播放逻辑和UI界面展示及操作相关的逻辑分离。我是把MediaPlayer直接封装到NiceVideoPlayer中,各种UI状态和操作反馈都封装到NiceVideoPlayerController里面。如果需要根据不同的项目需求

来修改播放器的功能,就只重写NiceVideoPlayerController就可以了。

3

NiceVideoPlayer

首先,需要一个FrameLayout容器mContainer,里面有两层内容,第一层就是展示播放视频内容的TextureView,第二层就是播放器控制器mController。

那么自定义一个NiceVideoPlayer继承自FrameLayout,将mContainer添加到当前控件:

添加setUp方法来配置播放的视频资源路径(本地/网络资源):

public void setUp(String url, Map headers) { mUrl = url; mHeaders = headers;}

用户要在mController中操作才能播放,因此需要在播放之前设置好mController:

用户在自定义好自己的控制器后通过setController这个方法设置给播放器进行关联。

触发播放时,NiceVideoPlayer将展示视频图像内容的mTextureView添加到mContainer中(在mController的下层),同时初始化mMediaPlayer,待mTextureView的数据通道SurfaceTexture准备就绪后就可以打开播放器:

打开播放器调用prepareAsync()方法后,mMediaPlayer进入准备状态,准备就绪后就可以开始:

NiceVideoPlayer的这些逻辑已经实现视频播放了,操作相关以及UI展示的逻辑需要在控制器NiceVideoPlayerController中来实现。

但是呢,UI的展示和反馈都需要依据播放器当前的播放状态,所以需要给播放器定义一些常量来表示它的播放状态:

播放视频时,mMediaPlayer准备就绪(Prepared)后没有马上进入播放状态,中间有一个时间延迟时间段,然后开始渲染图像。所以将Prepared——>“开始渲染”中间这个时间段定义为STATE_PREPARED。

如果是播放网络视频,在播放过程中,缓冲区数据不足时mMediaPlayer内部会停留在某一帧画面以进行缓冲。

正在缓冲时,mMediaPlayer可能是在正在播放也可能是暂停状态,因为在缓冲时如果用户主动点击了暂停,就是处于STATE_BUFFERING_PAUSED,所以缓冲有STATE_BUFFERING_PLAYING和STATE_BUFFERING_PAUSED两种状态,缓冲结束后,恢复播放或暂停。

mController.setControllerState(mPlayerState, mCurrentState),mCurrentState表示当前播放状态,mPlayerState表示播放器的全屏、小窗口,正常三种状态。

定义好播放状态后,开始暂停等操作逻辑也需要根据播放状态调整:

reStart()方法是暂停时继续播放调用。

4

全屏、小窗口播放的实现

可能最能想到实现全屏的方式就是把当前播放器的宽高给放大到屏幕大小,同时隐藏除播放器以外的其他所有UI,并设置成横屏模式。

但是这种方式有很多问题,比如在列表(ListView或RecyclerView)中,除了放大隐藏外,还需要去计算滑动多少距离才刚好让播放器与屏幕边缘重合,退出全屏的时候还需要滑动到之前的位置,这样实现逻辑不但繁琐,而且和外部UI偶合严重,后面改动维护起来非常困难(我曾经就用这种方式被坑了无数道)。

分析能不能有其他更好的实现方式呢?

整个播放器由mMediaPalyer+mTexutureView+mController组成,要实现全屏或小窗口播放,我们只需要挪动播放器的展示界面mTexutureView和控制界面mController即可。

并且呢我们在上面定义播放器时,已经把mTexutureView和mController一起添加到mContainer中了,所以只需要将mContainer从当前视图中移除,并添加到全屏和小窗口的目标视图中即可。

那么怎么确定全屏和小窗口的目标视图呢?

我们知道每个Activity里面都有一个android.R.content,它是一个FrameLayout,里面包含了我们setContentView的所有控件。

既然它是一个FrameLayout,我们就可以将它作为全屏和小窗口的目标视图。

我们把从当前视图移除的mContainer重新添加到android.R.content中,并且设置成横屏。这个时候还需要注意android.R.content是不包括ActionBar和状态栏的,所以要将Activity设置成全屏模式,同时隐藏ActionBar。

退出全屏也就很简单了,将mContainer从android.R.content中移除,重新添加到当前视图,并恢复ActionBar、清除全屏模式就行了。

进入小窗口播放和退出小窗口的实现原理就和全屏功能一样了,只需要修改它的宽高参数:

这里有个特别需要注意的一点:

当mContainer移除重新添加后,mContainer及其内部的mTextureView和mController都会重绘,mTextureView重绘后,会重新new一个SurfaceTexture,并重新回调onSurfaceTextureAvailable方法,这样mTextureView的数据通道SurfaceTexture发生了变化,但是mMediaPlayer还是持有原先的mSurfaceTexut,所以在切换全屏之前要保存之前的mSufaceTexture,当切换到全屏后重新调用onSurfaceTextureAvailable时,将之前的mSufaceTexture重新设置给mTexutureView。这样就保证了切换时视频播放的无缝衔接。

NiceVideoPlayerControl

为了解除NiceVideoPlayer和NiceVideoPlayerController的耦合,把NiceVideoPlayer的一些功能性和判断性方法抽象到NiceVideoPlayerControl接口中。

NiceVideoPlayer实现这个接口即可。

NiceVideoPlayerManager

同一界面上有多个视频,或者视频放在ReclerView或者ListView的容器中,要保证同一时刻只有一个视频在播放,其他的都是初始状态,所以需要一个NiceVideoPlayerManager来管理播放器,主要功能是保存当前已经开始了的播放器。

采用单例,同时,onBackPressed供Activity中用户按返回键时调用。

NiceVideoPlayer的start方法以及onCompleted需要修改一下,保证开始播放一个视频时要先释放掉之前的播放器;同时自己播放完毕,要将NiceVideoPlayerManager中的mNiceVideoPlayer实例置空,避免内存泄露。

NiceVideoPlayerController

播放控制界面上,播放、暂停、播放进度、缓冲动画、全屏/小屏等触发都是直接调用播放器对应的操作的。需要注意的就是调用之前要判断当前的播放状态,因为有些状态下调用播放器的操作可能引起错误(比如播放器还没准备就绪,就去获取当前的播放位置)。

播放器在触发相应功能的时候都会调用NiceVideoPlayerController的setControllerState(int playerState, int playState)这个方法来让用户修改UI。

不同项目都可能定制不同的控制器(播放操作界面),这里我就不详细分析实现逻辑了,大致功能就类似腾讯视频的热点列表中的播放器。

其中全屏模式下横向滑动改变播放进度、左侧上下滑动改变亮度,右侧上下滑动改变亮度等功能代码中并未实现,有需要的可以直接参考节操播放器(https://github.com/lipangit/JieCaoVideoPlayer),只需要在Controller的onInterceptTouchEvent中处理就行了(后续会添加上去)。

代码有点长,就不贴了,需要的直接下载源码。

https://github.com/xiaoyanger0825/NiceVieoPlayer

5

使用

在RecyclerView或者ListView中使用时,需要监听itemView的detached:

在ItemViewdetach窗口时,需要释放掉itemView内部的播放器。

效果图

6

最后

整个功能有参考节操播放器,但是自己这样封装和节操播放器还是有很大差异:一是分离了播放功能和控制界面,定制只需修改控制器即可。二是全屏/小窗口没有新建一个播放器,只是挪动了播放界面和控制器,不用每个视频都需要新建两个播放器,也不用同步状态。

MediaPlayer有很多格式不支持,后面会考虑用IjkPlayer或者ExoPlayer封装。

如果有错误和更好的建议都请提出,源码已上传GitHub,欢迎Star,谢谢!。

源码:

https://github.com/xiaoyanger0825/NiceVieoPlayer

如果你有想学习的文章直接留言,我会整理征稿。如果你有好的文章想和大家分享欢迎投稿,直接向我投递文章链接即可。返回搜狐,查看更多

责任编辑:

textureview 缩放_用MediaPlayer+TextureView封装一个完美实现全屏、小窗口的视频播放器...相关推荐

  1. android 播放器封装,Android视频播放最全总结:MediaPlayer+TextureView封装一个完美实现全屏、小窗视频播放器,附项目源码...

    原标题:Android视频播放最全总结:MediaPlayer+TextureView封装一个完美实现全屏.小窗视频播放器,附项目源码 作者:xiaoyanger 来源:http://www.jian ...

  2. 如何制作一个完美的全屏视频H5

    写在前面的话: 最近一波H5广告火爆整个互联网圈,身为圈内人,我们怎能     不! 知!道! :( 嘘!真不知道的也继续看下去,有收获 ↓ ) So,搞懂这个并不难. 这篇文章将带你从头到尾了解H5 ...

  3. MediaPlayer+TextureView,完美切换全屏、小窗口的 Android 视频播放器

    NiceVieoPlayer 项目地址:xiaoyanger0825/NiceVieoPlayer  简介:MediaPlayer+TextureView,完美切换全屏.小窗口的 Android 视频 ...

  4. uni-app VLC多媒体播放器、支持rtsp、rtmp、mms、ftp、udp/rtp等等大多数格式、截图、录制、速率、快进、倒退、音量、视频缩放、视频纵横比、音轨、亮度、全屏

    uni-app VLC多媒体播放器.支持rtsp.rtmp.mms.ftp.udp/rtp等等大多数格式.截图.录制.速率.快进.倒退.音量.视频缩放.视频纵横比.音轨.亮度.全屏: https:// ...

  5. 函数模板案例_利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序 排序规则从大到小,排序算法为选择排序 分别利用char数组和int数组进行测试

    案例描述: 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序 排序规则从大到小,排序算法为选择排序 分别利用char数组和int数组进行测试 #include <iostream& ...

  6. 设置全屏_教你在直播中如何显示全屏弹幕,增加与粉丝的互动

    今天来看看bilibili直播平台直播时如何在全屏游戏的时候观看直播间的弹幕. 1弹幕姬 弹幕姬是一个第三方的插件,官网是https://www.danmuji.org/.可以设置它来读取弹幕或观看全 ...

  7. 一个兼容IE7\IE8,H5的多功能视频播放器,H5视频播放器兼容Flash视频播放器

    这里记录一个视频播放器,免费可适当修改:名称:ckplayer视频播放器(免费) 官网地址:http://www.ckplayer.com/ 下载地址:http://www.ckplayer.com/ ...

  8. h5点击图片自动放大_关于微信上网页图片点击全屏放大效果

    实现微信上网页的图片点击后全屏还可以可以缩放,这个功能是别人做的,可是捏点击后屏幕直接黑屏了,图片没有显示出来.这个代码在网上搜一下,挺多类似的. 先上代码. function arrayToJson ...

  9. element中根据条件判断按钮是否禁用_从零动手封装一个通用的vue按钮组件

    我们在使用目前最主流的前端框架vue在开发过程中,组件是一个非常重要的组成部分,可以这么说,所有的vue应用,都是由一个一个的小组件拼装而成的. 正是由于vue组件如此重要,所以vue的生态中,也非常 ...

最新文章

  1. 一个支付案例,学会策略模式!
  2. linux 编程之库的使用(学习笔记)
  3. HDU 1215 七夕节 数论
  4. 谷歌语音转录背后的神经网络
  5. NSIS安装制作程序
  6. html网页中显示乱码的问题解决
  7. 易语言 设置屏幕刷新率 源码_一块好的手机屏幕应具备什么条件?现在了解还不晚...
  8. cocos creator基础-基本控件知识
  9. BIO-NIO-AIO
  10. 富文本编辑器、日期选择器、软件天堂、防止XSS攻击、字体icon、转pdf
  11. Bibles_Numpy离线文档
  12. 如何使用HTML制作个人网站(如何搭建个人博客)
  13. 3.4 方便快捷的魔棒工具 [Ps教程]
  14. oracle 基础知识(十四)----索引扫描
  15. 【手写源码-设计模式7】-桥接模式-基于苹果小米手机刷机场景
  16. html文本框设置大小,css怎么设置文本框大小
  17. du命令排序文件大小
  18. 解决微软应用商店打不开 代码: 0x80131500
  19. html5图片如何变成圆圈,h5中使用canvas把图片缩放并且剪切成圆形
  20. Flutter自定义主题颜色

热门文章

  1. HTML设计显示表格
  2. 【数据结构-排序】3.图解选择排序两种实现(简单选择排序/堆排序)
  3. 为 hexo 博客添加本地搜索功能
  4. sqlserver的基本介绍
  5. java中打开指定的文件夹
  6. pythone 打开文件 一行_【精品资料】用了这么多年单片机的Hex文件不懂?看这篇就够了...
  7. python好多模块和c相识_Python-Cext名称空间与常规Python子模块混合?
  8. 花五分钟看这篇之前,你才发现你不懂RESTful
  9. 排序算法 | 直接选择排序,算法的图解、实现、复杂度和稳定性分析
  10. TCP的粘包和拆包及Netty中的解决方案