一、播放器创建
1、创建ExoPlayer的实例
通过ExoPlayerFactory工厂创建出SimpleExoPlayer,其内部则创建ExoPlayerImplInternal对象,MediaCodecVideoRenderer(视频编解码渲染
器)、MediaCodecAudioRenderer(音频编解码渲染器)、TextRenderer(字幕渲染)等渲染器。
1)创建渲染器
2)创建ExoPlayerImpl
2、创建DataSource.Factory工厂
何为DataSource:它是一种可以被打开并且能够从中读取数据流的组件。
初始化阶段创建DefaultDataSourceFactory,接收一个DefaultHttpDataSourceFactory作为基础的DataSource.Factory。调用DefaultDataSourceFactory的createDataSource()方法能够创建DefaulDataSource:
DefaultDataSource是一个超级DataSource,在初始化时会同时创建其它不同类型的DataSource备用:
这些DataSource都提供基本的数据读取功能,但打开时会根据视频的scheme只确定一种被使用的DataSource:
确定了具体的DataSource,之后的数据读取都会使用这个DataSource:
在之后的数据准备过程中会用到这些Factory创建的DataSource实例。
如,
DefaultHttpDataSource:读取在线视频资源
FileDataSource:读取本地视频资源
Aes128DataSource:读取加密资源,上游数据还是来自FileDataSource或DefaultHttpDataSource
二、播放准备阶段
1、创建多媒体资源(MediaSource)
要播放HLS,需要创建HlsMediaSource。HLSMediaSource接收视频URI,以及播放器
创建阶段初始化的DataSource.Factory作为参数:
2、创建MediaPeriod
MediaPeriod的类型取决于MediaSource,由于之前建了HlsMediaSource,因此准备
阶段播放器会调用HlsMediaSource的createPeriod()方法来创建HlsMediaPeriod:
3、准备MediaPeriod
MediaPeriod.prepare()开始进行资源准备:
1)创建Loadable的实例
Loadable是一种可被Loader加载的类,这里的Loadable实例是ParsingLoadable,
需要清单的视频资源都需要在ParsingLoadable中进行资源列表解析,如HLS、Dash、SmoothStreaming等。
ParsingLoadable接收DataSource、Uri以及Parser作为构造参数。(DataSource由DataSource.Factory创建出来。Uri来自MediaSource。Parser为HlsPlaylistParser。不同的流有不同的Parser,如DashManifestParser对应Dash、SsManifestParser对应SmoothStreaming。)
2)使用Loadable加载数据:
创建完成ParsingLoadable后,传入Loader。调用Loader.startLoading()函数,
让Loadable在线程池中执行。
load()执行过程中创建输入流——DataSourceInputStream (DataSourceInputStream接收DataSource和DataSpec作为构造参数,DataSource由之前创建的DefaultDataSourceFactory工厂创建而来,得到DefaultDataSource。DefaultDataSource中持有一个baseDataSource,由DefaultDataSourceFactory创建时指定的构造参数DefaultHttpDataSourceFactory创建得到。)
(DataSourceInputStream接收DataSource和DataSpec作为构造参数,DataSource由之前创建的DefaultDataSourceFactory工厂创建而来,得到DefaultDataSource。DefaultDataSource中持有一个baseDataSource,由DefaultDataSourceFactory创建时指定的构造参数DefaultHttpDataSourceFactory创建得到。)
除了baseDataSource外,DefaultDataSource内部会自动创建出FileDataSource/
AssetDataSource/ContentDataSource三个本地文件相关的DataSource,用来读取本地数据,上文也有提到。在打开过程会根据uri的Scheme判断资源是方式本地资源,如果是本地资源(file、android_asset、asset、content),则调用对应的DataSource实体。DefaultDataSource的关系如下图:
创建完DataSourceInputStream,交由Parser进行转换,这里由HlsPlaylistParser对InputStream进行转换,得到HlsPlaylist,如果m3u8清单中是ts链接,则返回HlsMediaPlaylist,如果m3u8中含有其它m3u8链接(如,不同清晰度的链接),则返回HlsMasterPlaylist。
在加载并解析完m3u8的uri之后,HlsMediaPeriod中会收到onLoadCompleted()的回调,将进行下一步操作:HlsMediaChunk数据准备。
4、HlsMediaChunk数据准备
HlsMediaChunk是HLS的媒体样本(MediaChunk medias amples)
在HlsMediaPeriod中创建HlsSampleStreamWrapper,接收HlsChunkSource作为构造参数:HlsChunkSource的uri来自HlsMediaPlaylist的baseUri(也就是m3u8链接),DataSource来自播放器创建时期的DefaultDataSourceFactory工厂。
HlsSampleStreamWrapper一旦创建完成,就开始使用HlsChunkSource对HlsMediaChunk的数据进行加载 :
在HlsSampleStreamWrapper里,调用HlsChunkSource.getNextChunk()获取下一个Chunk数据放在HlsChunkHolder里。如果是初始播放,getNextChunk()中会创建MediaPlaylistChunk,如果已经解析m3u8,则返回HlsMediaChunk。
MediaPlaylistChunk的主要功能是从m3u8中解析出ts列表。
HlsMediaChunk则是用来从TsExtractor(ts获取器)获取ts数据,TsExtractor内部包含对ts文件的解析。
四、播放阶段
1、流的加载
在HlsMediaChunk的构造阶段,先根据HlsChunkSource中的encryptionKey、encryptionIv判断视频是否加密。(IV从m3u8标签#EXT-X-KEY中读取,如:#EXT-X-KEY:METHOD=AES-128,URI=”https://priv.example.com/key.php?r=52″,IV=0x9c7db8778570d05c3177c349fd9236a默认情况下,加密视频的key是EncryptionKeyChunk通过URI中的地址获取出字节数组。)
如果未加密,则还是使用和m3u8获取是相同的DataSource(DefaultDataSource)进行ts流的读取;
如果是加密文件,则创建新的Aes128DataSource进行流的读取:
Aes128DataSource还需要将播放器创建阶段指定的DataSource(DefaultDataSource)传入构造器通过DataSourceInputStream包装作为上游输入流,用来加载加密数据。解密是在Aes128DataSource使用CipherInputStream通过进行的,从Aes128DataSource中read的数据已经是经过Cipher解密的字节数据:
这里建立的输入流为CipherInputStream:
读取数据:
整个DataSource体系的结构如下如:
读取到流后,播放器会持续调用read方法,进行流的读取,并写入buffer区。
直到读取的数据量达到了缓存设置的上限或者已经读取到文件末尾便停止读取。
2、渲染
在加载到一定量(配置的可开始播放的数据量大小)的数据后,
MediaCodecVideoRenderer和MediaCodecAudioRenderer便开始调用系统层的MediaCodec进行解码播放。
播放器中会每隔10毫秒调用一次doSomeWork()函数,内部调用每个render的render方法,进行数据渲染:
在这个过程中,如果某个渲染器资源未就绪,播放器就会停止渲染数据,并进入缓冲状态:
*比如某个HLS资源,视频很短,音频很长,播放器进度显示的是playlist中的总是长,
但播放完视频数据后,MediaCodecVideoRenderer的状态变为isReady=false。这时即使还有音频可以渲染,播放也不会继续进行,因为有一个渲染器数据未就绪。就会出现进度卡在某个位置不动的现象。