Android版多功能视频播放器软件_第1页
Android版多功能视频播放器软件_第2页
Android版多功能视频播放器软件_第3页
Android版多功能视频播放器软件_第4页
Android版多功能视频播放器软件_第5页
已阅读5页,还剩50页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Android版多功能视频播放器软件AndroidAndroid版多功能视频播放器软件绪论1.1课题背景音视频技术是互联网品质生活的连接器。这个“连接器”,一头连接着人类最原始的视听需求、同时也是最直接的感官享受之一,其在现代文明社会中作为社交、商务、效能和娱乐工具丝毫不为任何外部条件所改变。“连接器”的另一头则连接且聚合着信息论、最优化理论、图形图像学、声学、人类视觉系统等一众根基深厚、源远流长的学派。互联网场景下的多媒体技术,作为更加综合和全面性的专业领域,其魅力就在于开发者总能直接面对和满足万千大众的需求,技术上的精进带来业务的发展,也在潜移默化中影响人们的社交、商务、娱乐行为。当前Android系统中视频播放器的现状。国内某些播放器对一些音频格式存在不支持的情况[1]。播放时音画不同步的问题。MoboPlayer存在部分视频不能正常播放,默认软解码,播放过程中会出现bug,譬如画面卡死,突然没声音。枫叶播放器界面太复杂,使用成本高。影音先锋能力较强支持各种格式,但是拉进度条有延迟,而且没办法自定义播放列表。手机里视频扫描不出来。Vplayer没有兼容8.0以上系统,而且无法响应隐式Intent。MxPlayer简洁,清晰,支持自定义添加本地视频,不足之处就是有太多的广告骚扰到用户。因此,开发一款专注于视频高效播放的无广告播放器App对于Android系统用户而言具有很大意义。1.2研究的内容本论文主要研究开发一款基于Android平台的多功能视频播放器,使用ijkplayer、Glide、LoaderManager、ContentProvider来搭建成熟的MVC设计模式应用框架。本系统的开发使用Java与Kotlin编程语言编写代码控制逻辑,UI页面采用xml或者Java代码实现自定义View,采用Groovy语言控制进行应用依赖项的管理。开发工具是AndroidStudio3.3版本,使用的java开发工具包是jdk1.8,开发系统平台是macOSHighSierra10.13.6版本。旨在开发出能够给Android系统用户使用的多功能视频播放器,方便高效播放不同格式视频,提高Android移动端视频播放的体验。1.3本文的组织结构本文一共分为五个章节,分别讨论了论文的选题背景及研究意义、本课题研究使用的相关技术的基本概念和特点,结合播放器系统功能分析和架构设计、详细设计和最终实现,最后是视频播放器在手机系统上的测试结果。本文的具体组织结构如下:第一章为绪论部分,介绍了论文研究的背景及实际意义、安卓视频播放器的发展现状及其发展趋势,最后阐述本文的研究内容及组织结构。第二章对实现本系统功能涉及到的技术进行研究和介绍。主要介绍了系统的软件设计模式及相关技术,如AndroidStudio,git版本控制系统等开发工具,以及涉及到的核心技术点,比如ijkPlayer,RecyclerView等。第三章主要对多功能视频播放器进行软件需求分析以及技术上和经济上的可行性。完成系统需求分析,确定了系统实现具体的可行方案。第四章主要介绍了系统开发设计和实现的详细过程。结合程序流程代码和流程框图实现各个模块,分别阐述了启动页、视频文件夹列表页、视频列表页、视频播放器等核心功能模块的实现。第五章主要阐述了系统开发完成之后对系统进行验证测试工作。验证了多功能播放器的实际运行状况。

2软件设计模式及相关技术本章主要介绍本Android多功能播放器开发过程中所涉及的几个比较重要技术点,诸如AndroidStudio、Java、Kotlin、FFmpeg、ijkplayer、Glide、SmartRefreshLayout、Gradle、RecyclerView等。以方便接下来更好地介绍相关软件功能的技术实现。2.1开发工具介绍1、AndroidStudio简介Android系统刚发布的时候仅支持使用Java开发,开发环境是Eclipse加上ADT(AndroidDevelopmentTools)插件,需要各种配置,上手的门槛较高,而且使用过程中非常容易发生各种奇怪的错误。到了2013年5月,Google才发布了AndroidStudio的早期预览版。2014年12月,AndroidStudio1.0推出后,Google逐步放弃对原来主要的Android开发工具EclipseADT。AndroidStudio也采取了与Chrome类似的版本发布模式的支持,并为Eclipse用户提供了迁移步骤。AndroidStudio基于JetBrains公司的IntelliJIDEA,为Android开发特殊定制,并在Windows、OSX和Linux平台上均可运行。相对于其他开发工具,AndroidStudio更快、更具生产力[2]。AndroidSDK简介AndroidSDK全称AndroidSoftwareDevelopmentKit,包含了Android官方提供给App开发人员编写移动应用程序的各种工具集。包括调试器、函数库、基于虚拟机镜像的仿真器、文档,示例代码和教程。在AndroidSDK安装目录下的tools和platform-tools文件夹中有一些非常重要的工具,如dx,emulator,adb,ddms,aapt等。这些工具保证了Java代码编译并且部署到模拟器上。dx.exe是AndroidSDK的编译器,当运行apk文件时,dx.exe将会创建一个带有.dex后缀的文件,Dalvik虚拟机可以识别并执行该文件。emulator.exe用来启动Android模拟器。Android模拟器被用来在一个虚拟的Android环境中运行你的Android应用程序。adb.exe位于platform-tools文件夹,开发者可以用它在模拟器上安装和启动应用。ddms.exe用于启动Android调试工具。aapt.exe用于查看.apk文件,是安卓程序的反汇编工具。3、AVD简介AVD是AndroidVirtualDevices的简称,也就Android虚拟设备,每个android虚拟设备(AVD)模拟了单一的虚拟设备来运行android平台,这个平台至少要包括自己的内核,系统图像和数据分区.开发者可以创建并保存多种虚拟模拟器配置,每种配置环境有其自己的平台版本,硬件配置以及SD卡和用户数据,还可以有不同的显示外观等个性化设置,运行时只需要制定需要使用哪个。即可实现多平台下的模拟测试[3]。Android系统类库简介Android系统类库API是基于Android核心库实现的Android系统应用框架层的API,即Android系统构架图中从上开始的第二层,也就是应用开发人员所使用的android包,每个包都以android开头。(1)android.util:包含一些底层的辅助工具类,例如,特定的容器类,xml辅助工具类等。(2)android.os:提供基本的操作服务,消息传递和进程间的通行IPC。(3)android.graphics:作为核心的渲染包,提供图形渲染功能。(4)android.text、android.text.method、android.text.style、android.text.util提供一套丰富的文本处理工具,支持富文本,输入模式等。(5)android.database:包含底层API处理数据库,方便操作数据库表和数据。(6)android.content:系统为上层应用提供各种访问数据服务的接口。(7)android.view:核心用户界面框架。(8)android.widget:提供标准用户界面元素,比如列表,按钮,文本显示等,是组用户界面的基本元素。(9)android.app:提供给上层应用程序模型,比如Activity。(10)vider:提供方便调用系统提供的contentproviders的接口.。(11)android.telephony:提供API交互和手机设备的通话接口。(12)android.webkit:包含一系列的运行在web内容上的API。5、Dalvik虚拟机与AndroidRuntime简介Dalvik虚拟机,是Google等厂商合作开发的Android移动设备平台的核心组成部分之一。它可以支持已转换为.dex(即“DalvikExecutable”)格式的Java应用程序的运行。.dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。Dalvik由DanBornstein编写的,名字来源于他的祖先曾经居住过的小渔村达尔维克(Dalvík),位于冰岛埃亚峡湾。大多数虚拟机包括JVM都是一种堆栈机器,而Dalvik虚拟机则是寄存器机。两种架构各有优劣,一般而言,基于堆栈的机器需要更多指令,而基于寄存器的机器指令更长。从Android5.0版起,AndroidRuntime(ART)取代Dalvik成为系统内默认虚拟机。ART能够把应用程序的字节码转换为机器码,是Android所使用的一种新的虚拟机。它与Dalvik的主要不同在于:Dalvik采用的是JIT技术,而ART采用Ahead-of-time(AOT)技术,在应用程序安装的过程中,ART就已经将所有的字节码重新编译成了机器码。ART同时也改善了性能、垃圾回收(GarbageCollection)、应用程序出错以及性能分析。为了保证向下兼容,ART使用了相同的Dalvik字节码文件(dex),即在应用程序目录下保留了dex文件供旧程序调用,然而.odex文件则替换成了可执行与可链接格式(ELF)可执行文件。一旦一个程序被ART的dex2oat命令编译,那么这个程序将会指通过ELF可执行文件来运行[4]。因此,相对于Dalvik虚拟机模式,ART模式下Android应用程序的安装需要消耗更多的时间,同时也会占用更大的内部储存空间,用于储存编译后的代码,但节省了很多Dalvik虚拟机用于实时编译的时间。6、git简介git是一个分布式版本控制软件,最初由Linux之父林纳斯·托瓦兹创作,于2005年以GPL发布。最初目的是为更好地管理Linux内核开发而设计[5]。git本身关心文件的整体性是否有改变,但多数的版本控制系统如CVS或Subversion系统则在乎文件内容的差异。git拒绝保持每个文件的版本修订关系。因此查看一个文件的历史需要遍历各个history快照;git隐式处理文件更名,即同名文件默认为其前身,如果没有同名文件则在前一个版本中搜索具有类似内容的文件[6]。git作为分布式管理系统有这样的优点:大多数的操作可以在本地进行,所以速度更快,而且由于无需联网,所以即使不在公司甚至没有在联网,也可以提交代码、查看历史,从而极大地减小了开发者的网络条件和物理位置的限制。使用git进行版本控制管理,可以将某个文件回溯到之前的状态,也可以将整个项目都回退到过去某个时间点的状态。可以比较文件的变化细节,如果在某个版本出现了问题,可以进行回退,查出最后是谁修改了哪个地方,从而找出导致问题出现的原因[7]。由于系统开发需要分模块有序地进行,并且有一定的复杂性,为了方便追溯问题,以及进行迭代开发,本系统采用git进行版本控制管理,同时在github上创建一个私有仓库作为项目的中央仓库。2.2相关技术介绍1、Java简介Java是Sun公司推出的一门面向对象的高级开发语言,由JamesGosling及其同事共同开发完成,于1995年推出。Java编程语言的风格十分接近C++语言。继承了C++语言面向对象技术的核心,舍弃了容易引起错误的指针,以引用取代;移除了C++中的运算符重载和多重继承特性,用接口取代;增加垃圾回收器功能。在JavaSE1.5版本中引入了泛型编程、类型安全的枚举、不定长参数和自动装/拆箱特性。太阳微系统对Java语言的解释是:“Java编程语言是个简单、面向对象、分布式、解释性、健壮、安全与系统无关、可移植、高性能、多线程和动态的语言。Java不同于一般的编译语言或解释型语言。它首先将源代码编译成字节码,再依赖各种不同平台上的虚拟机来解释执行字节码,从而具有“一次编写,到处运行”的跨平台特性[8][9][10]。2、Kotlin简介GoogleIO2017,Google将Kotlin列为Android官方开发语言,AndroidStudio3.0也默认集成了Kotlinplugin。Kotlin是一种在Java虚拟机上运行的静态类型编程语言[4],它也可以被编译成为JavaScript源代码。然与Java语法并不兼容,但Kotlin被设计成可以和Java代码相互运作,并可以复用现有的诸如Java集合框架的Java类库。Kotlin由JetBrains和Google通过Kotlin基金会赞助[11]。3、Android组件简介Android为应用层开发者提供的组件有四个,称为四大组件。(1)Activity。基本上每一个应用都会有一个Activity。(2)Service。用于在后台完成用户指定的操作。(3)BroadcastReceiver广播接收器,用于异步接收广播Intent,(4)ContentProvider,内容提供器使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据。四大基本组件都需要注册才能使用,每个Activity、service、ContentProvider都需要在AndroidManifest文件中进行配置。AndroidManifest文件中未进行声明的activity、服务以及内容提供者将不为系统所见,从而也就不可用[12]。FFmpeg简介FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源自由软件。它包括了领先的音/视频编码库libavcodec等[13][14][15]。主要由以下几个组件组成:libavformat:用于各种音视频封装格式的生成和解析,包括获取解码所需信息以生成解码上下文结构和读取音视频帧等功能;libavcodec:用于各种类型声音/图像编解码;libavutil:包含一些公共的工具函数;libswscale:用于视频场景比例缩放、色彩映射转换;libpostproc:用于后期效果处理;ffmpeg:该项目提供的一个工具,可用于格式转换、解码或电视卡即时编码等;ffsever:一个HTTP多媒体即时广播串流服务器;ffplay:是一个简单的播放器,使用ffmpeg库解析和解码,通过SDL显示;5、ijkplayer简介ijkplayer是Bilibili开发并开源的轻量级视频播放器,支持本地网络视频播放以及流媒体播放,支持iOS和Android平台。ijkplayer基于FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。FFmpeg采用LGPL或GPL许可证,提供了录制、转换以及流化音视频的完整解决方案,包括了领先的音、视频编码库libavcodec等。6、SurfaceView简介SurfaceView继承自View,特殊之处在于每个SurfaceView创建的时候都会创建一个Window,将SurfaceView和window绑定在一起,一个window对应一个Surface,因此SurfaceView也就内嵌了一个自己的Surface,可以认为SurfaceView是用来控制Surface中View的位置和尺寸的[16][17]。传统View及其派生类的更新只能在UI线程,然而UI线程还同时处理其他交互逻辑,如果可以在16ms以内将绘制工作完成,则没有任何问题,如果绘制过程逻辑很复杂,并且界面更新还非常频繁,这时候就会造成界面的卡顿,影响用户体验[18]。而SurfaceView可以用独立的线程进行绘制,因此可以提供更高的帧率,例如视频,游戏,摄像头取景等场景就比较适合SurfaceView来实现[19]。本系统中,使用了SurfaceView作为默认的视频显示载体。7、Loader简介Android3.0中引入了加载器,支持轻松在Activity或Fragment中异步加载数据。加载器具有以下特征:(1)可用于每个Activity和Fragment。(2)支持异步加载数据。(3)监控其数据源并在内容变化时传递新结果。(4)在某一配置更改后重建加载器时,会自动重新连接上一个加载器的cursor。因此,它们无需重新查询其数据。由于获取设备中的视频文件是一个耗时操作,如果在主线程执行该操作的话,可能会导致应用卡顿甚至发生ANR(ApplicationNotResponse),因此本系统中使用Loader去异步查询Android移动设备中的视频文件夹以及视频文件。8、SmartRefreshLayout简介SmartRefreshLayout的目标是打造一个强大,稳定,成熟的下拉刷新框架,并集成各种的炫酷、多样、实用、美观的Header和Footer。正如名字所说,SmartRefreshLayout是一个“聪明”或者“智能”的下拉刷新布局,由于它的“智能”,它不只是支持所有的View,还支持多层嵌套的视图结构。它继承自ViewGroup而不是FrameLayout或LinearLayout,提高了性能。也吸取了现在流行的各种刷新布局的优点,包括谷歌官方的SwipeRefreshLayout,其他第三方的Ultra-Pull-To-Refresh、TwinklingRefreshLayout。还集成了各种炫酷的Header和Footer。9、Glide简介Glide是一款快速高效的Android开源媒体管理和图像加载框架,它将媒体解码,内存和磁盘缓存以及资源池包装成简单易用的界面。Glide支持网络获取,解码和显示视频静止图像,图像和动画GIF。Glide包含一个灵活的API,允许开发人员自定义插入不同的网络请求技术栈。默认情况下,Glide使用基于HttpUrlConnection的自定义堆栈,但也包括插入Google的Volley项目或Square的OkHttp库的实用程序库。Glide主要解决问题是让图像列表滚动时尽量地平滑和快速,但是Glide也适用于绝大多数需要获取、调整图片大小、形状和显示网络图像的情况[20]。10、Gradle简介Gradle是一个基于ApacheAnt和ApacheMaven概念的自动化建构工具,它使用一种基于Groovy的特定领域语言来声明项目设置,而不是传统的xml。它不仅提供了灵活、定制化的构建方式,还有依赖管理等更多的特性[21]。跟传统的构建工具Ant、Maven对比,Ant与Maven对于Gradle,前者编写容易,但功能有限,需要人工操作的过程也多;后者依托于庞大的依赖仓库,因此有着强大的外部依赖管理,但添加本地依赖并不方便,且项目不能灵活修改。而Gradle能很好地结合Ant与Maven各自的优点,可以随意的编写任务并组合成项目,直接利用Maven仓库,并且能很好的支持传递依赖和内部依赖。Android的编译过程非常复杂(如下图所示):需要一种工具帮开发者更快更方便更简洁地完成Android程序的编译。现在结合AndroidStudio一般使用的工具都是Gradle,使用Gradle,可帮助开发者更快更方便更简洁地完成Android程序的编译。图2.1Android系统编译过程RecyclerView简介RecyclerView是Android官方在Lollipop版本新增在android-support-v7包中新增加的列表组件,是一个强大的滑动组件,可以在有限的窗口中展示大量数据集,实现类似ListView、GridView或瀑布流的效果。与原有的列表组件ListView相比,RecyclerView更加先进和灵活,标准化了ViewHolder,同时可以灵活定制每个Item增删的动画[22]。使用RecyclerView的一个恰当使用场景是:由于尺寸限制,用户的设备不能一次性展现所有条目,用户需要上下滚动以查看更多条目。滚出可见区域的条目将被回收,并在下一个条目可见的时候被复用。因为目前手机的存储容量扩大,用户通常会在手机上存储大量的视频,为了保证数据的完整性以及滑动的流畅性,本系统中的视频文件夹以及视频文件列表展示都采用RecyclerView呈现。图2.2RecyclerView组件图12、AndroidABI简介Android系统本质是一个经过改造的Linux系统。最早,Android系统只支持ARMv5的CPU架构,随着Android系统的发展,又加入了ARMv7(2010),x86(2011),MIPS(2012),ARMv8,MIPS64和x86_64(2014)。每一种CPU架构,都定义了一种ABI(ApplicationBinaryInterface),ABI决定了二进制文件如何与系统进行交互。一般情况下开发者不需要关注ABI。但是当APP中用到了些包含SO库第三方库,或者自己使用NDK来实现了某些功能,就必须要深入理解相关概念[23]。目前,不同的Android手机使用不同的CPU,进而支持不同的指令集。CPU与指令集的每种组合都有专属的应用二进制界面,即ABI。ABI可以非常精确地定义应用的机器代码在运行时如何与系统交互。必须为应用要使用的每个CPU架构指定ABI。典型的ABI包含以下信息:机器代码应使用的CPU指令集。运行时内存存储和加载的字节顺序。可执行二进制文件(例如程序和共享库)的格式,以及其支持的内容类型。用于在代码与系统之间传递数据的各种约定。这些约定包括对齐限制,以及系统调用函数时如何使用堆栈和寄存器。运行时可用于机器代码的函数符号列表,通常来自非常具体的库集。为了支持主流的手机,以及模拟器,本系统采用了arm-v7以及x86架构。LeakCanary简介Android系统中通过手动检测内存泄漏,首先需要dump内存得到.hprof文件,然后用特定工具比如AndroidDeviceMonitor打开.hprof文件,分析具体是哪一些对象占用了较大的内存,并且分析是否为合理的内存占用,如果存在内存泄漏的可能性,需要进一步分析对象的引用链。整个过程十分地繁琐。LeakCanary就是为了解决内存泄漏检测问题而开发的的工具,只需要通过build.gradle中将其引入到项目,并且在合适的位置(通常是Application中)进行初始化,就能自动帮助开发者发现内存泄漏的点,同时以通知栏为入口,告知开发者那些对象发生了内存泄漏,在详情页通过列表的方式将泄漏对象的引用链通过通知的方式呈现出来。工作原理大致如下:内存泄漏的本质是长生命周期的对象持有短生命周期对象的强引用,导致短生命周期对象使用完了之后无法被回收[24]。也就是应该被回收的对象没有被回收。那么问题就变成了什么对象是应该被回收的对象?对于Activity而言,执行完onDestroy方法之后,就是应该被回收了。因此可以将Activity#ondestroy方法作为一个检测点。Application中提供了各个Activity的生命周期回调方法的监听,LeakCanary就是通过注册ActivityLifecycleCallbacks,监听生命周期方法的回调,作为整个内存泄漏分析的入口。每次onActivityDestroyed(Activityactivity)方法被回调之后,都会创建一个KeyedWeakReference对相应Activity的状态进行跟踪,手动调用gc,后台线程(HandlerThread)检查引用是否被清除,如果没有就手动调用一次gc,如果这时还是没有被清除,把heap内存dump到APP对应的文件系统中的一个.hprof文件中。在另一个进程中的HeapAnalyzerService中,HeapAnalyzer会通过haha开源库对文件进行分析。得益于唯一的referencekey,HeapAnalyzer找到KeyedWeakReference,定位内存泄漏。HeapAnalyzer计算到GCroots的最短强引用路径,并确定是否是泄漏。如果是的话,建立导致泄漏的引用链。引用链传递到APP进程中的DisplayLeakService,并以通知的形式展示出来。工作原理如下图所示:图2.3LeakCanary工作原理图

3系统的可行性及需求分析3.1系统的可行性分析系统的开发之前必须对系统进行一定的评估和分析,以此来确保开发的系统是最终可以达到目的的,因此,本节将依次对系统设计的各个方面进行分析。3.1.1系统的技术可行性本系统选用java和Kotlin语言进行开发,java语言具有简单易用,容易掌握,跨平台的优点[18]。代码分包采用按照业务模块进行切分的方式,底层通过common模块给business层中的不同模块提供的通用的基础能力。上层中的各个业务模块之间只对外暴露特定的接口。在开发这个系统之前,我使用过Java和Kotlin编写过不同类型的AndroidApp,比如手机管家、图书管理系统等,同时在寒暑假到国内一线公司进行实习,积累了一定的开发经验,能够按时开发完这个基于Android系统的多功能视频播放器,选择这个课题,在技术上是可行的。3.1.2系统的经济可行性本系统开发使用免费的开发软件AndroidStudio、Glide、ijkplayer等框架,都可以从网上下载到需要的版本,技术方面也基本上可以通过各种论坛博客如掘金、CSDN、StackOverflow、Medium等寻找到解决的资源和方法,而不用花昂贵的价钱去购买相应的课程,或者花钱去购买开发过程中需要用到的各种语言库或者各种插件,这些对于开发本系统来说没有带来经济上的压力。其次,AndroidStudio对运行环境的要求不高,Windows、MacOS、Linux系统各个版本均可以运行,App兼容主流的Android手机,价格低廉,如果投入使用,用户只需要一般配置的Android手机,安装apk文件,并在运行时授予所需的权限即可使用。既不会给开发者带来经济负担,也不会给使用者带来额外的经济压力,因此,开发本系统,具有经济可行性。3.2系统的需求分析本系统是一个基于Android操作系统的多功能视频播放器,能够实现视频播放器的主要功能,包括获取本机视频文件夹列表,点击视频文件夹查看文件夹中的所有视频,列表页与播放页都提供查看视频属性的入口,视频属性需要包括视频的比特率,视频文件的格式,播放时长,文件名,分辨率,编码格式。展示视频的列表页面中,还要支持对视频进行重命名,以及删除指定视频文件的功能。视频播放页面,需要支持播放控制,支持通过进度条调整视频播放进度,进度条定时消失,支持倍速播放调整,手势控制功能,:通过左半屏上下滑动手势调节音量、通过右半屏上下滑动手势屏幕亮度,通过横向滑动控制播放的进度。在有字幕的情况下,字幕需要同步显示、支持主流的视频格式。用户输入在线视频地址,能够播放对应的视频流文件。在用户界面设计上,系统界面要简洁、易用,符合一般的操作习惯;代码上,程序架构符合面向对象设计原则;代码编写符合规范,核心代码段有简明的注释。根据上述需求,分为如下图四个模块。也就是通用模块、欢迎模块,视频列表模块,播放器模块。

图3.1系统功能模块设计

4系统设计及实现在前面的三章中,已经介绍完了开发的背景、目的、所需技术、可行性等方面的基础。本章将基于前面三章的基础内容来详细讲解本系统的开发设计具体流程,业务逻辑和主要技术点。4.1系统框架结构设计按照业务切分为common,list,player,welcome四个模块。各个模块的层级结构如下:图4.1应用层级结构应用包结构1、common存放一些通用的组件,比如工具类(1)media_retriver视频信息获取者(2)utils常用的工具类2、welcome欢迎页与splash页3、list视频列表业务模块(1)folder视频文件夹列表(2)video视频列表4、player播放器模块(1)activities存放视频播放器中用到的Activity(2)fragments存放视频播放器使用到的fragment(3)subtitles存放字幕相关的类(4)widgets存放视频播放模块的自定义view,接口(5)services存放后台播放的服务类4.2动态申请权限从Android6.0(API级别23)开始,修改为在应用运行时向其授予权限,而不是在应用安装时授予。运行时动态申请权限可以简化应用安装过程,因为用户在安装或更新应用时不需要授予权限。它还让用户可以对应用的功能进行更多控制;例如,用户可以选择为相机应用提供相机访问权限,而不提供设备位置的访问权限。用户可以随时进入应用的“Settings”屏幕调用权限。系统权限分为两类:正常权限和危险权限。正常权限不会直接给用户隐私权带来风险。如果应用在其AndroidManifest.xml文件中列出了正常权限,系统将自动授予该权限。危险权限会授予应用访问用户机密数据的权限。如果在其AndroidManifest.xml文件中列出了危险权限,则必须动态向用户申请这些并且得到用户的授权之后,才能使用这些权限[25]。对于需要进行动态申请的权限,本系统采用如下流程进行申请。图4.2动态权限申请流程图4.3启动页实现启动页是一个AndroidApp启动时,用户第一眼看到的界面,通常是显示产品logo和产品名称,以便加深用户的产品印象。启动页实现的要点在于,在AndroidManifest注册文件里面声明启动页所在Activty的intent-filter,指明aciton为MAIN,category为LAUNCHER,使其成为应用默认启动的Activty。默认进入闪屏页面1.5秒之后,启动VideoFolderListActivity,进入视频文件夹列表页面。finish掉自身,从回退栈中移除,避免用户回退时重新进入该页面。Handlerhandler=newHandler();handler.postDelayed(newRunnable(){@Overridepublicvoidrun(){Intentintent=newIntent(SplashActivity.this,VideoFolderListActivity.class);startActivity(intent);finish();}},1500);图4.3App启动页4.4视频列表获取与显示由于获取视频列表是一个耗时操作,所以采用通过Loader异步获取的方式。获取视频文件夹的逻辑封装在VideoFolderCollection中,VideoFolderCollection实现了LoaderManager.LoaderCallbacks接口的三个方法:publicinterfaceLoaderCallbacks<D>{@MainThread@NonNullLoader<D>onCreateLoader(intvar1,@NullableBundlevar2);@MainThreadvoidonLoadFinished(@NonNullLoader<D>var1,Dvar2);@MainThreadvoidonLoaderReset(@NonNullLoader<D>var1);}因为在调用LoaderManager#initLoader的时候,将VideoFolderCollection作为callback对象传递了进去,这三个方法会在相应的时机被回调。

@Override

publicLoader<Cursor>onCreateLoader(intid,Bundleargs){

Contextcontext=mContext.get();

if(context==null){

returnnull;

}

mLoadFinished=false;

returnVideoFolderLoader.newInstance(context);

}在onCreateLoader方法中,返回的是VideoFolderLoader,VideoFolderLoader继承自CursorLoader,其中定义了向系统查询的参数。数据查询完毕之后,最终回调到了VideoFolderListActivity#onAlbumLoad方法,该方法内部调用了CursorAdapter的swapCursor方法,也就是将cursor传递给Adapter,读取cursor数据,然后展示出所有视频文件夹UI。读取特定文件夹下的视频逻辑与上述过程类似,只是需要传递相应的buckid给VideoItemListActivity,然后借助VideoItemCollection,到系统的ContentProvider读取相应的视频文件信息列表,获取完毕之后,设置cursor给对应的适配器,并将视频文件的uri传递给Glide,完成每一个列表项中对应视频文件的第一帧预览。最终视频文件夹列表与视频文件列表图如下所示:图4.4视频文件夹列表图4.5视频文件列表当视频文件夹列表为空或者是特定文件夹里面的所有视频都被删除的情况下,会展示空页面。空页面允许用户刷新,也允许通过网络串流的方式,播放在线视频。图4.6列表空页面4.5视频文件夹列表下拉刷新首先要实现好UI,在列表视图所在的xml文件中,使用SmartRefreshLayout作为原有列表控件的父布局。指定下拉刷新的头部为WaveSwipeHeader。将srlEnableLoadMore置为false,关闭上拉加载的UI。<com.scwang.smartrefresh.layout.SmartRefreshLayoutandroid:id="@+id/refreshLayout"android:layout_width="match_parent"android:layout_height="match_parent"app:srlEnableLoadMore="false"><com.scwang.smartrefresh.header.WaveSwipeHeaderandroid:layout_width="match_parent"android:layout_height="wrap_content"/><FrameLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><android.support.v7.widget.RecyclerViewandroid:id="@+id/mVideoListRecyclerView"android:layout_width="match_parent"android:layout_height="match_parent"/></FrameLayout></com.scwang.smartrefresh.layout.SmartRefreshLayout>代码中,给SmartRefreshLayout设置一个刷新监听器,触发刷新时,回调对应的加载器reload方法,重新获取数据,数据获取完成之后,将cursor传递给列表对应的Adapter,通知列表按照最新的数据重新进行渲染绘制。图4.7列表下拉刷新4.6视频文件的删除由于没有回收站功能,删除文件之后,重新找回文件的成本比较高,所以删除之前需要弹框让用户二次确认,用户确认删除之后,还需要检查是否授予了WRITE_EXTERNAL_STORAGE的权限,如果未授权,需要进行申请。授予权限之后,首先判断uri的scheme是否为content,如果是,则调用contentResolver的接口,否则,先根据文件的uri,获取到文件的path,然后再删除。privatevoiddoDelete(Contextcontext,Uriuri){if(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())){introwDelete=context.getContentResolver().delete(uri,null,null);if(rowDelete>=0){showToast(R.string.delete_success);refreshList();}elseshowToast(R.string.delete_fail);}else{Filefile=newFile(FileUtils.INSTANCE.getRealFilePath(context,uri));if(file.exists()&&file.isFile()){if(file.delete()){showToast(R.string.delete_success);refreshList();}elseshowToast(R.string.delete_fail);}else{showToast(R.string.fileDoesntExist);}}}4.7视频播放播放器内核采用ijkplayer实现,ijkplayer是一个基于ffplay的轻量级Android/iOS视频播放器,实现了跨平台的功能,API易于集成;编译配置可裁剪,方便控制安装包大小[26][27]。为了方便后续升级管理,采用gradle而不是jar包的方式来引入ijkplayer依赖。allprojects{repositories{jcenter()}}dependencies{implementation'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'implementation'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8'//支持在模拟器中运行implementation'tv.danmaku.ijk.media:ijkplayer-x86_64:0.8.8'}引入了依赖之后,还需要进一步封装才能使用,封装之后上层使用到的主要是ijkplayerView,类图如下:图4.8ijkplayerView类图ijkplayerView继承了FrameLayout实现了MediaController.MediaPlayerControl,IPlaySpeedController两个接口。其中的子view包括了一个TextView和一个IRenderView中的View(TextureView或者SurfaceView的一种)。外部先通过IjkVideoView#setMediaController接口,注入播放控制器,然后根据参数类型,调用IjkVideoView#setVideoPath或者IjkVideoView#setVideoURI()方法,将视频的信息传递给IjkVideoView。最后,调用IjkVideoView#start方法,开始播放视频。4.8播放控制器为了提高可拓展性,以及面向接口编程,提供给IjkVideoView的控制器需要实现IMediaController接口,控制器的实现位于IjkMediaController中,IjkMediaController继承了MediaController同时实现了IMediaController。控制播放的工具栏显示5s之后,就隐藏。显示之前,首先更新播放进度条,然后更新中间的控制按钮,是暂停还是在播放中,隐藏不支持的按钮,更新播放/暂停状态,同时发送一个延时5s的隐藏工具栏任务。privatefinalRunnablemFadeOut=newRunnable(){@Overridepublicvoidrun(){hide();}};publicvoidshow(inttimeout){if(!mShowing&&mAnchor!=null){setProgress();if(mPauseButton!=null){mPauseButton.requestFocus();}disableUnsupportedButtons();updateFloatingWindowLayout();mWindowManager.addView(mDecor,mDecorLayoutParams);mShowing=true;}updatePausePlay();post(mShowProgress);if(timeout!=0&&!mAccessibilityManager.isTouchExplorationEnabled()){removeCallbacks(mFadeOut);postDelayed(mFadeOut,timeout);}}同时IjkMediaController由于IjkVideoView实现了MediaPlayerControl接口,并将自身的引用传递给IjkMediaController,所以,可以实现快进、快退、播放、暂停功能。4.9网络串流在入口处输入在线视频的网络地址,输入进行合法性进行检查和处理之后,通过VideoView#setVideoUri方法设置视频网络地址,播放器内部解析完网络地址,会向服务器发出请求。然后经过解协议,解封装,解码,音视频同步的方式四个步骤实现视频的播放。解协议:将流媒体协议的数据解析为相应的封装格式数据,比如RTMP协议解析后得到flv,HLS协议解析后得到ts。流媒体协议在音视频传输的同时,还会包含一些其他的数据,比如RTMP协议会包含一些信令数据,这些信令数据包括对播放的控制(暂停,播放,停止等),或者是对网络状态的描述。HLS协议中会包含索引文件等等。解协议的过程就是只保留音视频数据,去除掉其他的数据。解封装:经过解协议过程,得到视频的封装格式数据后,解封装过程会将其分离成为某种编码格式的音频压缩数据和某种编码格式的视频压缩数据,有的可能还包括字幕和脚本。例如:flv或ts格式的数据,解封装后得到H.264编码的视频码流和AAC编码的音频码流。解码:解封装过程完毕后,分别得到压缩的视频码流和音频码流,解码的过程就是将压缩(编码)后的音视频数据解压,得到系统音频驱动和视频驱动能够识别的音频采样数据(如PCM数据)和视频像素数据(如YUV420P,RGB)。音频视频同步:根据时间,帧率和采样率采用一定的算法,同步解码出来的视频和音频数据,并将视频音频数据送至显卡和声卡播放出来[28]。图4.9在线视频链接输入页面4.10调整播放速度在视频播放页的更多菜单中,提供调整播放速度的入口,播放速度调整的业务逻辑封装在PlayerSpeedController这个类中,播放页面通过PopupWindow呈现。按照设计模式中六大设计原则中的第五条:迪米特法则,一个对象应该对其他对象保持最少的了解,每个类都减少不必要的依赖,而真正能够调节部分速度的接口位于IjkPlayerView中,IjkPlayerView中含有较多的接口,因此定义一个IPlaySpeedController接口,提供速度获取与调整的接口方法。interfaceIPlaySpeedController{funsetSpeed(float:Float)fungetSpeed():Float}播放速度调整操作界面如下图所示:图4.10播放速度调整UI4.11字幕功能移动端中大部分视频格式都是srt格式,srt(Subripper)是最简单的文本字幕格式,扩展名为.srt,其组成为:一行字幕序号,一行时间代码,一行字幕数据,比如:4500:02:52,184-->00:02:53,617慢慢来这表示:第45个字幕,显示时间从该影片开始的第2分52.184秒到第2分53.617秒,内容为:慢慢来。解析的逻辑为:根据给定的字幕文件路径,读取文件,由于不同的文件有不同的编码,为了防止乱码,在读取之前必须先通过EncodingDetect获取到字幕文件的编码格式。按照指定的格式进行读取。为了保证效率,避免频繁进行I/O操作,将InputStreamReader包装为BufferedReader,按照行的顺序读取,采用时间正则表达式判断格式是否符合,如果符合则进行解析,依次解析出显示的时间,中文字幕,英文字幕,不符合就进入下一行。解析的结果存储在一个列表中。/***解析字幕文件*/publicstaticList<SubtitlesEntry>parseSrtFile(StringsrtFilePath){FilesubtitlesFile=newFile(srtFilePath);if(!subtitlesFile.exists()||!subtitlesFile.isFile()){Log.e(TAG,"filenotexist");returnCollections.emptyList();}FileInputStreamis;BufferedReaderbufferedReader=null;finalStringcharset=EncodingDetect.getJavaEncode(subtitlesFile);List<SubtitlesEntry>entryList=newArrayList<>();Stringline;try{is=newFileInputStream(subtitlesFile);bufferedReader=newBufferedReader(newInputStreamReader(is,charset));while((line=bufferedReader.readLine())!=null){parseSentence(line,bufferedReader,entryList);}}catch(FileNotFoundExceptione){Log.e(TAG,"parseFile:FileNotFoundException",e);}catch(UnsupportedEncodingExceptione){Log.e(TAG,"parseFile:UnsupportedEncodingException",e);}catch(IOExceptione){Log.e(TAG,"parseFile:IOException",e);}finally{if(bufferedReader!=null){try{bufferedReader.close();}catch(IOExceptione){e.printStackTrace();}}}returnentryList;}//解析句子privatestaticvoidparseSentence(Stringline,BufferedReaderbufferedReader,List<SubtitlesEntry>entryList)throwsIOException{//以时间为起点解析句子,不符合则跳过if(isSubtitleLine(line)){SubtitlesEntryentry=newSubtitlesEntry();//填充开始时间数据entry.startTime=parseTimeLine(line.substring(0,12));//填充结束时间数据entry.endTime=parseTimeLine(line.substring(17,29));//填充中文数据entry.chinese=bufferedReader.readLine();//填充英文数据entry.english=bufferedReader.readLine();//当前字幕的节点位置entry.node=entryList.size()+1;entryList.add(entry);}}/***@paramline*@return字幕所在的时间节点*@descraption将String类型的时间转换成int的时间类型*/privatestaticintparseTimeLine(Stringline){try{returnInteger.parseInt(line.substring(0,2))*ONE_HOUR//时+Integer.parseInt(line.substring(3,5))*ONE_MINUTE//分+Integer.parseInt(line.substring(6,8))*ONE_SECOND//秒+Integer.parseInt(line.substring(9));//毫秒}catch(NumberFormatExceptione){Log.e(TAG,"parseTimeLine:",e);}return-1;}字幕解析完成之后,需要实现字幕实时同步。由于字幕最终由TextView呈现,而Android中操作UI需要在主线程完成。因此设置一个主线程定时循环任务,时间间隔为500ms。循环任务为获取当前播放位置,使用二分查找算法查找出当前位置对应的字幕,然后展示到TvSrtView中。privateHandlermSubtitlesHandler=newHandler(Looper.getMainLooper()){@OverridepublicvoidhandleMessage(Messagemsg){switch(msg.what){caseSHOW_SUBTITLE_DELAY:displaySubtitleInTime();if(mMediaPlayerControl.isPlaying()){mSubtitlesHandler.sendEmptyMessageDelayed(SHOW_SUBTITLE_DELAY,INTERVAL_TIME);//500ms后再次触发字幕更新}break;}}};二分查找字幕的逻辑如下:检测到某一个字幕的起始时间和结束时间包含了当前播放位置对应的时间,即退出循环返回字幕。如果起始时间大于当前播放位置对应时间,则说明匹配的字幕在当前列表位置的前面,将end置为mid-1。重新计算mid,然后重复上述逻辑。如果结束时间小于当前播放位置对应时间,则说明匹配的字幕在当前列表位置的后面,将start置为mid+1。/***采用二分法查找当前应该播放的字幕**@paramlist全部字幕*@paramcurrentPos当前播放的时间点*/privateSubtitlesEntrybinarySearchCurrentSubtitle(List<SubtitlesEntry>list,intcurrentPos){intstart=0;intend=list.size()-1;while(start<=end){intmiddle=(start+end)/2;if(currentPos<list.get(middle).startTime){if(currentPos>list.get(middle).endTime){returnlist.get(middle);}end=middle-1;}elseif(currentPos>list.get(middle).endTime){if(currentPos<list.get(middle).startTime){returnlist.get(middle);}start=middle+1;}elseif(currentPos>=list.get(middle).startTime&¤tPos<=list.get(middle).endTime){returnlist.get(middle);}}returnnull;}视频字幕实际效果如下图所示:图4.11视频字幕4.12通过手势调节音量、亮度、进度大部分视频播放器中,都存在使用不同的手势来控制播放的音量、播放的亮度功能。Android手机系统中,为开发者提供了GestureDetector以帮助识别一些基本的触摸手势,一定程度上降低了手势控制功能的使用成本。Detector的意思就是探测者,所以GestureDetector就是用来监听手势的发生。它内部有3个Listener接口,用来回调不同类型的触摸事件,用一个简略的类图来显示:图4.12GestureDetector类图类图中的三个接口中的方法:就是相应触摸事件的回调,实现这些方法,就能在当前视图相应不同的用户手势。对于GestureDetector的回调,还需要把它封装才能区分出那些上下左右的手势。在VideoGestureManager.PlayerGestureListener#onScroll封装了这些逻辑。@OverridepublicbooleanonDown(MotionEvente){mFirstTouch=true;//每次按下的时候更新当前亮度和音量,还有进度updateBrightness();updateVolume();returnsuper.onDown(e);}@OverridepublicbooleanonScroll(MotionEvente1,MotionEvente2,floatdistanceX,floatdistanceY){finalfloatlastX=e1.getX();finalfloatlastY=e1.getY();finalfloatdeltaX=lastX-e2.getX();if(mFirstTouch){mFirstTouch=false;mIsVolumeControl=lastX>ScreenUtil.INSTANCE.getScreenWidthInPixel()*0.5f;//右半屏上下滑动调节音量mIsAdjustingProgress=Math.abs(distanceX)>=Math.abs(distanceY);}if(!mIsAdjustingProgress){finalfloatdeltaY=lastY-e2.getY();finalfloatpercent=deltaY/mVideoView.getHeight();if(mIsVolumeControl){onVolumeSlide(percent);}else{onBrightnessSlide(percent);}}returnsuper.onScroll(e1,e2,distanceX,distanceY);}首先计算横向、纵向的差值,如果,纵向偏移大于水平偏移,则说明不是在控制播放进度,然后再判断滑动的水平坐标是否超过屏幕的一半,如果是,则说明是在调节音量应该回调onVolumeSlide方法,否则,说明在调节亮度,应该回调onBrightnessSlide方法。屏幕亮度调节是修改通过当前Window的mWindowAttributes变量中的screenBrightness属性实现的。音量调节需要则是通过AudioManager#setStreamVolume方法实现的。图4.13手势调节音量

图4.14手势调节亮度进度调整的代码逻辑如下:如果水平滑动的距离大于竖直滑动的距离,则视为是调节播放进度。以结束位置的x坐标减去起始位置的x坐标,如果为正数,说明是向右滑,也就是快进,否则为快退。以滑动的距离除以屏幕的宽度,得出调整的百分比。计算出新的进度值,同时,需要二次检查最新进度值在有效区间(0~100)之内,若越界则将进度调整到区间内。将进度乘以视频的总时长,得出当前的位置,最终通过调用seekTo方法,实现进度的调整。privatevoidonProgressSlide(MotionEvente1,MotionEvente2,floatdistanceX,floatdistanceY){floatscrollX=e2.getX()-e1.getX();finalintduration=mVideoView.getDuration();finalintvideoViewWidth=mVideoView.getWidth();longoffsetTime=calculateOffsetTime(scrollX,duration,videoViewWidth);calAndLimitProgressWithinBound(scrollX,videoViewWidth);mTvPosition.setText(DateUtils.formatElapsedTime((long)(mNewProgress*duration/1000)));mTvPositionOffset.setText(String.format("[%s%s]",scrollX>0?"+":"-",DateUtils.formatElapsedTime(offsetTime)));//格式化时间差值并显示showProgressTextContainer();//显示中央进度viewmMediaController.show();//显示进度条所在的容器mVideoView.seekTo((int)(mNewProgress*duration));}//计算并限制进度条在有效区间范围内privatevoidcalAndLimitProgressWithinBound(floatscrollX,intvideoViewWidth){mNewProgress=mOldProgress+scrollX/videoViewWidth;if(scrollX>0){if(mNewProgress>1.f){mNewProgress=1.f;}}else{if(mNewProgress<0){mNewProgress=0;}}}//计算调整的时间差值privatelongcalculateO

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论