原文地址:https://www.jianshu.com/p/4dede867739d

ExoPlayer is an application level media player for Android. It provides an alternative to Android’s MediaPlayer API for playing audio and video both locally and over the Internet. ExoPlayer supports features not currently supported by Android’s MediaPlayer API, including DASH and SmoothStreaming adaptive playbacks. Unlike the MediaPlayer API, ExoPlayer is easy to customize and extend, and can be updated through Play Store application updates.

首先看看ExoPlayer类之间的继承关系,对这个框架有一个大致的印象

基本类图(不完整).png

ExoPlayer被定义为Interface,然后又几个内部类:Factory,Listener,其中,Factory负责初始化ExoPlayer的操作,其关键代码如下:

public static ExoPlayer newInstance(int rendererCount, int minBufferMs, int minRebufferMs) {  return new ExoPlayerImpl(rendererCount, minBufferMs, minRebufferMs);}

Listener则负责向外界回调ExoPlayer状态变化和错误信息。

ExoPlayer有一个子类:ExoPlayerImpl,它继承了ExoPlayer的所有方法,并且负责接收转发外界传递的消息,为什么是转发,不是接收呢?因为真正干活的不是ExoPlayerImpl,而是另外一个隐藏类,ExoPlayerImplInternal,几乎所有的操作都是在ExoPlayerImplInternal中完成的。

Start

我们看一个官方的使用Demo:

// 1. Instantiate the player.
player = ExoPlayer.Factory.newInstance(RENDERER_COUNT);
// 2. Construct renderers.
MediaCodecVideoTrackRenderer videoRenderer = ...
MediaCodecAudioTrackRenderer audioRenderer = ...
// 3. Inject the renderers through prepare.
player.prepare(videoRenderer, audioRenderer);
// 4. Pass the surface to the video renderer.
player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
// 5. Start playback.
player.setPlayWhenReady(true);
...
player.release();
// Don’t forget to release when done!

我们下面的探索过程都是按照这个Demo一步一步进行的

1.Instantiate the player.

首先,用户调用ExoPlayer.Factory.newInstance(...)方法得到ExoPlayerImpl的实例,这个过程中,我们看看做了什么:

public ExoPlayerImpl(int rendererCount, int minBufferMs, int minRebufferMs) {Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION);//首先初始化一些状态this.playWhenReady = false;this.playbackState = STATE_IDLE;//ExoPlayer的Listener是通过andListener(listener:Listener)方法添加的,所以需要一个数组去记录所有的Listenerthis.listeners = new CopyOnWriteArraySet<>();//初始化轨道格式数组this.trackFormats = new MediaFormat[rendererCount][];//选中的轨道索引this.selectedTrackIndices = new int[rendererCount];//初始化一个Handler,并将收到的消息传递给ExoPlayerImpl的handleEvent()方法处理eventHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {ExoPlayerImpl.this.handleEvent(msg);}};//初始化ExoPlayerImplInternalinternalPlayer = new ExoPlayerImplInternal(eventHandler, playWhenReady, selectedTrackIndices,minBufferMs, minRebufferMs);
}

然后我们继续看ExoPlayerImplInternal的构造方法:

public ExoPlayerImplInternal(Handler eventHandler, boolean playWhenReady,int[] selectedTrackIndices, int minBufferMs, int minRebufferMs) {//接受从ExoPlayerImpl传递进来的Handlerthis.eventHandler = eventHandler;//初始化this.playWhenReady = playWhenReady;this.minBufferUs = minBufferMs * 1000L;this.minRebufferUs = minRebufferMs * 1000L;//拷贝this.selectedTrackIndices = Arrays.copyOf(selectedTrackIndices, selectedTrackIndices.length);this.state = ExoPlayer.STATE_IDLE;this.durationUs = TrackRenderer.UNKNOWN_TIME_US;this.bufferedPositionUs = TrackRenderer.UNKNOWN_TIME_US;//初始化StandaloneMediaClock类,它是一个时钟类,原理是通过获取手机启动时间进行差值计算standaloneMediaClock = new StandaloneMediaClock();//初始化一个自增IntegerpendingSeekCount = new AtomicInteger();enabledRenderers = new ArrayList<TrackRenderer>(selectedTrackIndices.length);trackFormats = new MediaFormat[selectedTrackIndices.length][];// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can// not normally change to this priority" is incorrect.//初始化和启动一个HandlerThreadinternalPlaybackThread = new PriorityHandlerThread("ExoPlayerImplInternal:Handler",Process.THREAD_PRIORITY_AUDIO);internalPlaybackThread.start();//为HandlerThread添加一个Handlerhandler = new Handler(internalPlaybackThread.getLooper(), this);
}

至此,ExoPlayerImpl和ExoPlayerImplInternal两个类的状态都被初始化,启动一个Process.THREAD_PRIORITY_AUDIO的线程,准备接受任务。

2.Construct renderers.

ExoPlayer被初始化后,用户需要调用ExoPlayer.prepare(...)进行准备工作:

public void prepare(TrackRenderer... renderers);

TrackRenderer和它的孩子们

我们看到,prepare形参是TrackRenderer数组,那么这个TrackRenderer是个什么东东呢?

ExoPlayer的媒体组件,都是通过注入的方式实现的,而TrackRenderer就是媒体组件的基类。

public abstract class TrackRenderer implements ExoPlayerComponent {}

从源码看,TrackRenderer是个抽象类,继承自ExoPlayerComponent,只有一个属性:

private int state;

大部分方法都是围绕state实现的,剩下的都是抽象方法,TrackRenderer类用来维护state,而具体的工作需要子类去实现,而做法比较巧妙,如TrackRenderer的prepare()方法:

//prepare方法维护state属性的状态,具体的执行则是调用doPrepare()方法
final int prepare(long positionUs) throws ExoPlaybackException {Assertions.checkState(state == STATE_UNPREPARED);state = doPrepare(positionUs) ? STATE_PREPARED : STATE_UNPREPARED;return state;
}//抽象方法,由子类实现
protected abstract boolean doPrepare(long positionUs) throws ExoPlaybackException;

再看看ExoPlayerComponent

public interface ExoPlayerComponent {void handleMessage(int messageType, Object message) throws     ExoPlaybackException;
}

从名字看,它是一个组件,用于在播放线程接受消息,所有实现它的类都可以在播放线程接受消息,所以TrackRenderer可以接收来自其他线程的消息。

那我们看TrackRenderer有哪些子类

TrackRenderer.png

从类图来看,TrackRenderer有很多子类,其中,SampleSourceTrackRenderer比较重要,我们看一下官方文档对这个类的介绍:

SampleSourceTrackRenderer.png

SampleSource and SampleSourceReader

TrackRenderer的实例,渲染来从SampleSource采集的样本,SampleSource是什么呢,从名字看应该是样本源:

SampleSource.png

媒体样本源,SampleSource一般暴漏一个或多个轨道,轨道的个数和每个轨道的格式可以通过 SampleSource.SampleSourceReader.getTrackCount()和SampleSource.SampleSourceReader.getFormat(int)得到。

再回头看SampleSourceTrackRenderer的构造方法:

public SampleSourceTrackRenderer(SampleSource... sources) {this.sources = new SampleSourceReader[sources.length];for (int i = 0; i < sources.length; i++) {this.sources[i] = sources[i].register();}
}

接受一个或者多个SampleSource数组,然后调用了SampleSource的register()方法

/*** A consumer of samples should call this method to register themselves and gain access to the* source through the returned {@link SampleSourceReader}.* <p>* {@link SampleSourceReader#release()} should be called on the returned object when access is no* longer required.** @return A {@link SampleSourceReader} that provides access to the source.*/
public SampleSourceReader register();

从官方介绍来看,消费者(获取样本的类,这里是指SampleSourceTrackRenderer)通过调用register()方法来获得对媒体样本读取的能力。

register()方法返回SampleSourceReader类:

/***An interface providing read access to a {@link SampleSource}.   */
public interface SampleSourceReader

是一个接口,定义了一些访问媒体样本的方法,以下列举一些重要的方法,详细可以去com.google.android.exoplayer.SampleSource.SampleSourceReader类查看:

  • prepare(long positionUS):boolean
  • getTrackCount():int
  • getFormat(int track):MediaFormat
  • enable(int track,long position)
  • disable(int track)
  • readData(int track,long positionUs,MediaFormatHolder formatHolder,SampleHolder sampleHolder):int
  • seekToUs(long positionUs)
  • release()

继续看SampleSourceTrackRenderer的构造方法:

this.sources[i] = sources[i].register();

SampleSourceTrackRenderer中定义一个全局变量,存储所有的SampleSourceReader,方便其他方法访问SampleSource中的资源。

到这里,ExoPlayer的框架结构就比较清晰了,TrackRenderer负责渲染由SampleSource提供的媒体样本。

3. Inject the renderers through prepare.

player.prepare(videoRenderer, audioRenderer);

前两步分别初始化ExoPlayer、TrackRenderer和SampleSource,并将SampleSource注入到TrackRenderer,但是直到现在,TrackRenderer都没有和ExoPlayer产生关系,客官们是不是等的不耐烦了

ExoPlayer源码浅析(转载)相关推荐

  1. 内核启动流程分析(四)源码浅析

    目录 kernel(四)源码浅析 建立工程 启动简析 head.s 入口点 查询处理器 查询机器ID 启动MMU 其他操作 start_kernel 处理命令行 分区 kernel(四)源码浅析 建立 ...

  2. 【flink】Flink 1.12.2 源码浅析 : Task数据输入

    1.概述 转载:Flink 1.12.2 源码浅析 : Task数据输入 在 Task 中,InputGate 是对输入的封装,InputGate 是和 JobGraph 中 JobEdge 一一对应 ...

  3. 【flink】Flink 1.12.2 源码浅析 :Task数据输出

    1.概述 转载:Flink 1.12.2 源码浅析 :Task数据输出 Stream的计算模型采用的是PUSH模式, 上游主动向下游推送数据, 上下游之间采用生产者-消费者模式, 下游收到数据触发计算 ...

  4. 【flink】Flink 1.12.2 源码浅析 : StreamTask 浅析

    1.概述 转载:Flink 1.12.2 源码浅析 : StreamTask 浅析 在Task类的doRun方法中, 首先会构建一个运行环境变量RuntimeEnvironment . 然后会调用lo ...

  5. 【flink】Flink 1.12.2 源码浅析 : Task 浅析

    1.概述 转载:Flink 1.12.2 源码浅析 : Task 浅析 Task 表示TaskManager上并行 subtask 的一次执行. Task封装了一个Flink operator(也可能 ...

  6. 【Flink】Flink 1.12.2 源码浅析 : TaskExecutor

    1.概述 转载:Flink 1.12.2 源码浅析 : TaskExecutor TaskExecutor 是TaskManger的具体实现. 二 .TaskExecutorGateway TaskE ...

  7. 【flink】Flink 1.12.2 源码浅析 : yarn-per-job模式解析 TaskMasger 启动

    1.概述 转载:Flink 1.12.2 源码浅析 : yarn-per-job模式解析 [四] 上一篇: [flink]Flink 1.12.2 源码浅析 : yarn-per-job模式解析 Jo ...

  8. 【flink】Flink 1.12.2 源码浅析 : yarn-per-job模式解析 JobMasger启动 YarnJobClusterEntrypoint

    1.概述 转载:Flink 1.12.2 源码浅析 : yarn-per-job模式解析 [三] 上一章:[flink]Flink 1.12.2 源码浅析 : yarn-per-job模式解析 yar ...

  9. 【flink】Flink 1.12.2 源码浅析 : yarn-per-job模式解析 yarn 提交过程解析

    1.概述 转载:Flink 1.12.2 源码浅析 : yarn-per-job模式解析 [二] 请大家看原文去. 接上文Flink 1.12.2 源码分析 : yarn-per-job模式浅析 [一 ...

最新文章

  1. 如何有效利用项目管理工具提高工作效率?
  2. 人脸对齐--Face Alignment at 3000 FPS via Regressing Local Binary Features
  3. mysql按特定的顺序_如何在MySQL中按特定顺序排列数据?
  4. Qt Creator编写代码
  5. LeetCode 1022. 从根到叶的二进制数之和(递归)
  6. 【AI视野·今日CV 计算机视觉论文速览 第240期】Thu, 4 Nov 2021
  7. php环境informix,在Nginx + php-fpm(fastcgi)环境下配置informix的连接
  8. jquery实现的时间轴
  9. oracle连接中出现错误ORA-12541,ORA-12514,ORA-01017的解决方法
  10. Zabbix 配置钉钉脚本告警(4)
  11. 离开Autodesk,开启新篇章
  12. 使用bat命令一键启动常用浏览器
  13. java access_Java 连接Access数据库的两种方式
  14. 角度计算公式 角度换算 想知道1是多少度; 度和弧度的关系 弧微分: 曲率,曲半径,曲率圆:
  15. 解决Eclipse保存web.xml卡的问题
  16. RSA加密 - Java
  17. 惠海半导体H7230直接替换BP1371 BP1361方案设计
  18. Win11电脑一边耳机没声音怎么解决
  19. 【金猿产品展】Sensingtech便携式人脸识别一体机:让罪犯无处遁寻
  20. 金蝶软件虚拟服务器,金蝶软件服务器远程设置

热门文章

  1. 参加训练营的心得体会
  2. Java处理全角半角问题
  3. 新版本的AutoCAD2018 怎样删除 A360 Drive盘符
  4. 工具 | 常用函数拟合工具(matlab)
  5. 云服务器win系统下架设手游白日门传奇详细教程
  6. idea 使用Java单元测试类运行慢
  7. VGG16神经网络模型复现
  8. 数据可视化-Echarts官网及社区整理
  9. CodeForces - 719A Vitya in the Countryside(暴力)
  10. 面试总结(zhuan)