东莞朝阳网站建设,济南市住房和城乡建设厅官网,北京西站到八达岭长城最快路线,加密系统系列文章目录
ExoPlayer架构详解与源码分析#xff08;1#xff09;——前言 ExoPlayer架构详解与源码分析#xff08;2#xff09;——Player ExoPlayer架构详解与源码分析#xff08;3#xff09;——Timeline ExoPlayer架构详解与源码分析#xff08;4#xff09;—…系列文章目录
ExoPlayer架构详解与源码分析1——前言 ExoPlayer架构详解与源码分析2——Player ExoPlayer架构详解与源码分析3——Timeline ExoPlayer架构详解与源码分析4——整体架构 文章目录 系列文章目录前言Player的实现BasePlayerExoPlayer 线程模型总结 前言
根据前篇ExoPlayer架构详解与源码分析2——Player想要直接实现Player接口需要非常复杂的代码逻辑都写在一个类里肯定不现实需要通过更多层次的扩展简化来实现当然ExoPlayer就是这么做的本篇来讲讲的如何通过BasePlayer来简化设计以及ExoPlayer如何将整个复杂的设计划分给一个个子系统来完成的。
Player的实现
先来看下整体架构 Player接口经过了一层BasePlayer简化和ExoPlayer扩展。然后由ExoPlayerImpl实现ExoPlayerImpl内部又依赖ExoPlayerImplInternalExoPlayerImplInternal再依据功能划分将任务交由各个组件主要为MediaSource、Renderer、TrackSelector、LoadControl四大组件。
BasePlayer
先说BasePlayer 是个抽象类主要作用是简化了Player接口的部分功能。 实现了单文件列表增删改等操作,通过将单个MediaItem转为List交由xxMediaItems实现。 Overridepublic final void setMediaItem(MediaItem mediaItem) {setMediaItems(ImmutableList.of(mediaItem));}实例化出Timeline 中的 Window对象这里主要用于Timeline getWindow 方法时装填的容器因为Timeline 本身不持有Window或者PeriodTimeline获取Window或者Period时都需要传入一个容器去获取通过调用容器的set方法给容器赋值。 protected BasePlayer() {window new Timeline.Window();}Overridepublic final long getContentDuration() {//获取播放的总时长Timeline timeline getCurrentTimeline();//先获取Timeline 由子类实现return timeline.isEmpty()? C.TIME_UNSET//将初始化的window对象传入方法里会将window对象赋值: timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs();}实现了Player关于播放列表管理的设计将MediaItem 播放列表查询相关交由Timeline管理从这里可以看出上面针对MediaItem 的增删改最终都是会封装到或者同步到Timeline里的这里后面看到具体实现。 Overridepublic final int getNextMediaItemIndex() {Timeline timeline getCurrentTimeline();return timeline.isEmpty()? C.INDEX_UNSET: timeline.getNextWindowIndex(getCurrentMediaItemIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled());}OverrideNullablepublic final MediaItem getCurrentMediaItem() {Timeline timeline getCurrentTimeline();return timeline.isEmpty()? null: timeline.getWindow(getCurrentMediaItemIndex(), window).mediaItem;}Overridepublic final int getMediaItemCount() {return getCurrentTimeline().getWindowCount();}Overridepublic final MediaItem getMediaItemAt(int index) {return getCurrentTimeline().getWindow(index, window).mediaItem;}基于Timeline将各种媒体的导航操作如上一曲下一曲SEEK等统一到自己抽象出的一个seekTo方法中。 Overridepublic final void seekToNextMediaItem() {seekToNextMediaItemInternal(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);}private void seekToNextMediaItemInternal(Player.Command int seekCommand) {int nextMediaItemIndex getNextMediaItemIndex();if (nextMediaItemIndex C.INDEX_UNSET) {return;}if (nextMediaItemIndex getCurrentMediaItemIndex()) {repeatCurrentMediaItem(seekCommand);} else {seekToDefaultPositionInternal(nextMediaItemIndex, seekCommand);}}private void repeatCurrentMediaItem(Player.Command int seekCommand) {seekTo(getCurrentMediaItemIndex(),/* positionMs */ C.TIME_UNSET,seekCommand,/* isRepeatingCurrentItem */ true);}private void seekToDefaultPositionInternal(int mediaItemIndex, Player.Command int seekCommand) {seekTo(mediaItemIndex,/* positionMs */ C.TIME_UNSET,seekCommand,/* isRepeatingCurrentItem */ false);}Overridepublic final int getNextMediaItemIndex() {Timeline timeline getCurrentTimeline();return timeline.isEmpty()? C.INDEX_UNSET//通过Timeline获取下一个索引: timeline.getNextWindowIndex(getCurrentMediaItemIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled());}/*** Seek到指定的MediaItem中的指定位置** param mediaItemIndex MediaItem 的索引可以理解成播放列表中的第几个* param positionMs MediaItem 中的位置* param seekCommand Seek 的类型用于权限控制这里可以不用考虑* param isRepeatingCurrentItem 是否重复当前播放项目*/public abstract void seekTo(int mediaItemIndex,long positionMs,Player.Command int seekCommand,boolean isRepeatingCurrentItem); 完成了其他一些可以通过已有方法实现的方法。 //判断当前命令是否可用对应Player设计的第2点Overridepublic final boolean isCommandAvailable(Command int command) {return getAvailableCommands().contains(command);//通过已有的getAvailableCommands来实现getAvailableCommands由子类实现}//播放和暂停实现了Player关于playWhenReady的设计playWhenReady就是一个标记位标记用户的一个播放意图//所以这里的play并不是立即开始播放的意思而是调用者希望开始播放实际播放要等到PlaybackStateSTATE_READY的时候pause同上Overridepublic final void play() {setPlayWhenReady(true);}//实现了Player关于isPlaying的设计Overridepublic final boolean isPlaying() {return getPlaybackState() Player.STATE_READY getPlayWhenReady() getPlaybackSuppressionReason() PLAYBACK_SUPPRESSION_REASON_NONE;}//获取直播流的延时Overridepublic final long getCurrentLiveOffset() {Timeline timeline getCurrentTimeline();if (timeline.isEmpty()) {return C.TIME_UNSET;}long windowStartTimeMs timeline.getWindow(getCurrentMediaItemIndex(), window).windowStartTimeMs;if (windowStartTimeMs C.TIME_UNSET) {return C.TIME_UNSET;}//获取当前播放时间和实际实际的差值使用当前时间取服务端的实时时间如果可用-播放开始时间已播放位置【含广告】return window.getCurrentUnixTimeMs() - window.windowStartTimeMs - getContentPosition();}//获取缓冲百分比Overridepublic final int getBufferedPercentage() {long position getBufferedPosition();long duration getDuration();return position C.TIME_UNSET || duration C.TIME_UNSET? 0: duration 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100);}综上所述BasePlay实现了部分Player接口的设计简化了Player接口实现为后续的子类铺平道路。
ExoPlayer
一个接口定义继承扩展了Player接口实现MediaSource的播放。ExoPlayer播放器本体设计都在这里了将Player接口复杂的设计通过建立一个运行框架将功能分散到各个子系统协调这些子系统完成播放器的播放等最初的设计目标。 我们将文章开头的架构图进一步扩充下。
ExoPlayer 设计理念之一就是高度可定制化主要任务是协调各个组件间工作而对媒体的类型、存储方式、加载方式、如何展示等并不关心。ExoPlayer并不直接实现媒体的加载与渲染而是将这些工作交给播放器创建或者准备时注入的组件这些组件包括
MediaSource 主要作用是定义需要播放的媒体基本信息、加载媒体以及定义了从哪里读取已经加载的媒体数据。通过将MediaItems传入MediaSource.Factory播放器创建时指定创建也可以直接调用setMediaSource方法创建。播放器默认提供了 DefaultMediaSourceFactory可以根据不同类型的MediaItem创建出不同的MediaSource包括progressive HLSDASHSmoothStreaming 。 Renderers 包含了用于渲染媒体的各个组件。提供了像MediaCodecVideoRenderer, MediaCodecAudioRenderer, TextRenderer and MetadataRenderer这些组件用于常见媒体的渲染。Renderer 使用MediaSource提供的数据来渲染。可以通过ExoPlayer接口提供的getRendererCount获取渲染器的数量getRendererType获取各自轨道类型。 TrackSelector 用于选择由MediaSource提供的可用于渲染器的轨道。播放器在创建时默认注入了DefaultTrackSelector可以用于大部分情况的轨道选择 。 LoadControl 主要用于控制MediaSource何时缓冲更多媒体数据以及缓冲多少数据。播放器在创建时默认注入了DefaultLoadControl可以用于大部分情况的数据加载 。
上面的组件在创建ExoPlayer 时都会注入一个默认的实现当默认组件无法满足需求时可以通过自定义的组件来构建播放器。如可以通过设置自定义的LoadControl来更改播放器默认的缓存加载策略或者通过添加子当以的Renderer来支持Android本身不支持的视频编码格式。
上图可以看到不光ExoPlayer使用了注入组件的概念上面列出ExoPlayer组件本身就和ExoPlayer一样也使用了组件注入的概念这些组件本身也是由子组件注入创建而来的将这些的组件本地的功能又再一次细化分配给各自的子组件来完成并且这些子组件同样也支持自定义。如上图默认的在创建MediaSource时就需要注入一个或者多个DataSource 工厂通过提供不同的DataSource工厂可以从不同的数据源加载数据。基于这种设计思路下的系统共同打造了一个高度可定制化的ExoPlayer。
线程模型
下图展示了ExoPlayer的线程模型 可以看出播放器线程主要分为3部分
application thread 应用线程只有一个 大部分情况是应用的主线程对应Android的UI线程。如果使用了ExoPlayer 的UI库或者IMA库也要使用应用的主线程。可以通过在创建播放器时传递“Looper”来显式指定用于访问 ExoPlayer 实例的线程如果未指定“Looper”则使用创建播放器的线程的“Looper”或者如果该线程没有“Looper”则使用应用程序主线程的“Looper”。无论哪种情况都要可以通过Player接口定义的getApplicationLooper获取到访问播放器线程的“Looper”。由于是主线程应用可以直接在主线程中获取播放器的相关信息这些信息通常保存在ExoPlayerImpl中无需异步回调即可立刻获取到数据这也符合ExoPlayer架构详解与源码分析2——Player中关于Player的设计。已注册的监听都是在主线程通过getApplicationLooper获取中回调的这就意味着组测这些监听的地方也必须在同一个主线程中。对于监听类的回调这些都是异步的这个回调最终会使用主线程的Handler分发到主线程里这也是为什么创建ExoPlayer是必须要指定主线程的原因。 internal playback thread 一个播放器实例只有一个主要负责播放。renderer、MediaSources、TrackSelectors 和 LoadControls 等注入到播放器组件都是在这个线程里调用的。这个线程也是一个Looper线程有一个Handler用于将主线程的请求发送到Looper里进行分发。当应用程序在播放器上执行操作如Seek时消息会通过主线程持有的Handler发送到内部播放线程的Looper然后分发到内部线程里并在内部播放线程里调用相关方法执行相应的操作。类似地当内部播放线程上发生播放事件时消息将通过另一个Handler分发到主线程。主线程使用队列中的消息更新应用程序可见状态并调用相应的监听回调。这部分Exoplayer实现在ExoPlayerImplInternal中在其初始化过程中创建了一个HandlerThread来实现后面会讲到。 background threads 各个注入到ExoPlayer中组件的后台线程会有多个。注入的播放器组件可以使用额外的后台线程执行任务。例如MediaSource 可以使用后台线程来加载数据。这些线程都是由不同的MediaSource实现决定的。 总结
可以看到EoxPlayer架构的高度可定制化基本每一个组件都可以在创建时自定义然后注入到播放器中实现自定义的播放器。 EoxPlayer这些设计在后续的分析中都会体现按顺序下篇应该了解下ExoPlayerImpl和ExoPlayerImplInternal但是他们中很多功能都是依赖于4大组件的而且4大组件直接又是相互独立的所以计划后面几篇先把它的4大组件分析下最后通过分析ExoPlayerImpl和ExoPlayerImplInternal将前面将的4大组件串联起来了解ExoPlayerImpl和ExoPlayerImplInternal是如何协调这些组件完成播放的。下篇预计先从最复杂的组件MediaSource开始分析。 版权声明 © 本文为CSDN作者山雨楼原创文章 转载请注明出处 原创不易觉得有用的话收藏转发点赞支持