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

作者:xiaoyanger

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

项目已添加IjkPlayer支持,项目地址见文末或点击“阅读原文”

为什么使用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中的一个硬件加速层来显示,从而实现视频播放功能。

MediaPlayer介绍

MediaPlayer是Android原生的多媒体播放器,可以用它来实现本地或者在线音视频的播放,同时它支持https和rtsp。

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

MediaPlayer状态图(生命周期)

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

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

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就可以了。

NiceVideoPlayer

首先,需要一个FrameLayout容器mContainer,里面有两层内容,第一层就是展示播放视频内容的TextureView,第二层就是播放器控制器mController。那么自定义一个NiceVideoPlayer继承自FrameLayout,将mContainer添加到当前控件:

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

用户要在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()方法是暂停时继续播放调用。

全屏、小窗口播放的实现

可能最能想到实现全屏的方式就是把当前播放器的宽高给放大到屏幕大小,同时隐藏除播放器以外的其他所有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、清除全屏模式就行了。

切换横竖屏时为了避免Activity重新走生命周期,别忘了需要在Manifest.xml的activity标签下添加如下配置:

android:configChanges= "orientation|keyboardHidden|screenSize"

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

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

当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。

不同项目都可能定制不同的控制器(播放操作界面),这里我就不详细分析实现逻辑了,大致功能就类似腾讯视频的热点列表中的播放器。其中横向滑动改变播放进度、左侧上下滑动改变亮度,右侧上下滑动改变亮度等功能在代码中都有实现。代码有点长,就不贴了,需要的直接下载源码。

使用

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

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

效果图

最后

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

MediaPlayer有很多格式不支持,项目已添加IjkPlayer的扩展支持,可以切换IjkPlayer和原生MediaPlayer,后续还会考虑添加ExoPlayer,同时也会扩展更多功能。

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

源码:https://github.com/xiaoyanger0825/NiceVieoPlayer

参考:

Android TextureView简易教程

视频画面帧的展示控件SurfaceView及TextureView对比

Android 5.0(Lollipop)中的SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

Android MediaPlayer生命周期详解

节操播放器 https://github.com/lipangit/JieCaoVideoPlayer

责任编辑:

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

  1. Android高级开发之【RxJava】详解(附项目源码)

    文章大纲 一.什么是RxJava 二.为什么要用RxJava 三.RxJava使用详解 四.项目源码下载 一.什么是RxJava Rx(Reactive Extensions)是一个库,用来处理事件和 ...

  2. 基于android实现模拟爱奇艺本地视频播放器app项目演示【附项目源码+论文说明】分享

    软件概述 软件用途 本软件的开发是xxxx. a.本软件的开发目的:熟悉android开发的基本知识,包括四大组件的使用方法.列表控件的数据源绑定.数据库的操作等. b.基本原理:通过数据库存储用户和 ...

  3. 基于android系统的单词学习软件设计与开发【附项目源码+论文说明】分享

    基于android系统的单词学习软件设计与开发演示 摘要 随着手机使用的普及,人们获取与保存信息的方式已经发生了激动人心的转变.智能手机正在逐步融入我们的生活,并影响和改变着我们的生活.由于现在各种智 ...

  4. 基于Android的儿童绘本阅读与收听APP的设计与实现【附项目源码+论文说明】

    基于Android的儿童绘本阅读与收听APP的设计与实现演示 摘要 随着人们生活水平的不断提高,儿童学前教育日益受到重视.绘本作为一种历史古老.效果优秀的早教手段,利用图画和简短的文字构筑出一个跌宕起 ...

  5. 基于Android实现日语学习app设计与实现演示【附项目源码+论文说明】分享

    基于Android实现日语学习app设计与实现演示 摘要 随着手机使用的普及,人们获取与保存信息的方式已经发生了激动人心的转变.智能手机正在逐步融入我们的生活,并影响和改变着我们的生活.由于现在各种智 ...

  6. 基于Android平台实现x的跑步应用App设计【附项目源码+论文说明】分享

    基于Android平台实现x的跑步应用App设计演示 摘要 随着手机使用的普及,人们获取与保存信息的方式已经发生了激动人心的转变.智能手机正在逐步融入我们的生活,并影响和改变着我们的生活.由于现在各种 ...

  7. 基于Android实现日语学习app设计与实现演示【附项目源码+论文说明】

    基于Android实现日语学习app设计与实现演示 摘要 随着手机使用的普及,人们获取与保存信息的方式已经发生了激动人心的转变.智能手机正在逐步融入我们的生活,并影响和改变着我们的生活.由于现在各种智 ...

  8. 基于Android平台实现匿名社交论坛【附项目源码】

    基于Android平台实现匿名社交论坛演示 在当今的数字时代,社交网络已经成为人们生活中不可或缺的一部分.社交应用的需求不断增加,人们期望能够在这些应用中获得更多的互动和交流.而匿名社交论坛系统则是一 ...

  9. 基于java SSM框架+微信小程序实现电子书城阅读器演示【附项目源码+论文说明】分享

    基于java SSM框架+微信小程序实现电子书城阅读器演示 摘要 而随着互联网技术的不断发展,互联网已经渗入到我们生活中的各个方面.移动设备的普及使我们的生活发生了翻天覆地的变化,这种变化也深刻影响着 ...

最新文章

  1. PAT1123 Is It a Complete AVL Tree(AVL树完全二叉树)
  2. phpstudy composer 安装YII2
  3. Velocity模板(VM)语言介绍
  4. ubuntu安装与配置nfs服务器
  5. MSCRM2011 Current User has Role 【判定当前用户角色方法】
  6. Spring Boot中使用Swagger CodeGen生成REST client
  7. Laravel向视图传递变量的两种方法
  8. 如何制作macOS Monterey启动U盘
  9. 星益80个在线小游戏HTML网页源码
  10. 计算机中保留两位小数的表示方法,小数点的表示方法_计算机中表示带小数点的数有两种方法,...
  11. 搜索引擎查找计算机硬盘信息,win7系统使用搜索引擎快速查找磁盘文件的方法【图文教程】...
  12. linux7配置dns服务,RHEL7 DNS配置
  13. Vimdiff 使用
  14. 远程 PC 访问软件
  15. eplan 电箱布局_EPLAN电气图实例--控制柜(1)
  16. EXCEL表格怎么让每一列都乘以固定的一列
  17. 【AdaBoost算法】
  18. 学校公文办公处理系统_基于ASP.NET和Swfupload、FlashPaper2.2、校讯通短信发送的开发
  19. mysql 连接配置
  20. STM32 + RT Thread OS 学习笔记[五]

热门文章

  1. 霍尔传感器在汽车电子、工业控制、电力与新能源、家电及消费类产品中的应用
  2. c语言图形学画扇形代码,WPF画图の利用Path画扇形(仅图形)(示例代码)
  3. js中eval方法的使用
  4. 通过动态NAT实现内网访问外网,通过静态NAT实现外网访问内网的WEB服务器(Cisco)
  5. 盘点国内主流数字孪生厂商!你了解几家?
  6. 怎么看协议属于哪一层?
  7. 怎样做时政教育类自媒体,用这4个网站就对了
  8. 计算机组成原理期末考试题
  9. 【cocos2d-js官方文档】二十四、如何在android平台上使用js直接调用Java方法
  10. 开发中时常提到的提交pr是什么意思?