赞
踩
ExoPlayer架构详解与源码分析(1)——前言
ExoPlayer架构详解与源码分析(2)——Player
ExoPlayer架构详解与源码分析(3)——Timeline
ExoPlayer架构详解与源码分析(4)——整体架构
ExoPlayer架构详解与源码分析(5)——MediaSource
ExoPlayer架构详解与源码分析(6)——MediaPeriod
ExoPlayer架构详解与源码分析(7)——SampleQueue
ExoPlayer架构详解与源码分析(8)——Loader
ExoPlayer架构详解与源码分析(9)——TsExtractor
ExoPlayer架构详解与源码分析(10)——H264Reader
ExoPlayer架构详解与源码分析(11)——DataSource
好久不见各位,间隔了一段时间忙项目,终于有时间补上ProgressiveMediaPeriod最后一块拼图——DataSource。间隔太久先来个前情回顾。
本系列先介绍了ExoPlayer的整体架构还有些基本概念,然后围绕四大组件展开讲解,首先从MediaSource这个组件讲起,MediaSource主要由ProgressiveMediaPeriod来完整工作,先看下ProgressiveMediaPeriod整体结构:
之前的文章已经讲解完上图里用于解析数据的左半部分,而这些用于解析的数据就是从右半部分的DataSource里获取的。还是拿火箭来类比,MediaSource是火箭的燃料系统,那么左半边可以理解为燃油泵控制燃料的多少,右半部分就是油箱,为整个发动机提供源源不断的燃料。
DataSource字面就是数据源的意思,用来读取URI定义的资源数据,扩展了DataReader提供了read方法供外部读取数据
看下主要方法:
DataSource的定义很简单,主要就是和数据源的相关操作,打开、读取、关闭。
它的具体实现有很多这里列出常用的一部分
先过下简单几个DataSource:
BaseDataSource 主要实现了多个TransferListener的监听分发管理。
PlaceholderDataSource 顾名思义一个用于占位的DataSource,不可调用其中的open,read方法,会抛出异常。
StatsDataSource 一个DataSource的包装,会将所有的方法调用转发给子DataSource。
AesCipherDataSource 也是一个DataSource的包装,会将所以的方法调用转发给子DataSource,与StatsDataSource不同的是,可以AES解密加密子的DataSource,会将子DataSource返回的数据经过AES解密返回给上层。
PriorityDataSource 有优先级管理的DataSource,可以使用PriorityTaskManager管理包含的子DataSource ,只有优先级足够高才可以继续调用open和read否则抛出PriorityTooLowException异常通知上层更改执行顺序。
DataSchemeDataSource data作为Scheme的URI的DataSource,如将数据直接转成base64存储在字符串中的方式,这类读取也很简单,直接Base64转成字节数据就行了。
AssetDataSource 顾名思义,用来读取Android Asset文件,通过AssetManager获取到文件流。
ContentDataSource 通过ContentResolver获取到文件描述,然后打开文件流。
RawResourceDataSource 同Resources.openRawResourceFd获取文件描述打开文件流。
FileDataSource 打开文件路径的DataSource,这里看下实现。
@Override public long open(DataSpec dataSpec) throws FileDataSourceException { Uri uri = dataSpec.uri; this.uri = uri; transferInitializing(dataSpec);//触发监听 this.file = openLocalFile(uri);//创建随机访问的文件流,RandomAccessFile try { file.seek(dataSpec.position);//seek到DataSpec 指定的开始位置 bytesRemaining =//获取未读取长度 dataSpec.length == C.LENGTH_UNSET ? file.length() - dataSpec.position : dataSpec.length; } catch (IOException e) { throw new FileDataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); } if (bytesRemaining < 0) {//OUT_OF_RANGE throw new FileDataSourceException( /* message= */ null, /* cause= */ null, PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); } opened = true; transferStarted(dataSpec);//触发监听 return bytesRemaining; } @Override public int read(byte[] buffer, int offset, int length) throws FileDataSourceException { if (length == 0) { return 0; } else if (bytesRemaining == 0) { return C.RESULT_END_OF_INPUT; } else { int bytesRead; try {//在buffer 的offset处开始写入length长度的file数据 bytesRead = castNonNull(file).read(buffer, offset, (int) min(bytesRemaining, length)); } catch (IOException e) { throw new FileDataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); } if (bytesRead > 0) { bytesRemaining -= bytesRead; bytesTransferred(bytesRead);//监听 } return bytesRead; } }
HttpDataSource 网络数据的基类,主要定义了网络请求相关的请求设置,EXO实现了OkHttpDataSource、DefaultHttpDataSource、CronetDataSource,对应3种网络请求库的DataSource实现,重点看下OkHttpDataSource的实现
@Override public long open(DataSpec dataSpec) throws HttpDataSourceException { this.dataSpec = dataSpec; bytesRead = 0; bytesToRead = 0; transferInitializing(dataSpec); Request request = makeRequest(dataSpec); Response response; ResponseBody responseBody; Call call = callFactory.newCall(request); try { this.response = executeCall(call); response = this.response; responseBody = Assertions.checkNotNull(response.body()); responseByteStream = responseBody.byteStream();//获取到数据流 } catch (IOException e) { throw HttpDataSourceException.createForIOException( e, dataSpec, HttpDataSourceException.TYPE_OPEN); } int responseCode = response.code(); ... long bytesToSkip = responseCode == 200 && dataSpec.position != 0 ? dataSpec.position : 0; ... opened = true; transferStarted(dataSpec); try { skipFully(bytesToSkip, dataSpec);//从dataSpec.position位置开始 } catch (HttpDataSourceException e) { closeConnectionQuietly(); throw e; } return bytesToRead; } private int readInternal(byte[] buffer, int offset, int readLength) throws IOException { if (readLength == 0) { return 0; } if (bytesToRead != C.LENGTH_UNSET) { long bytesRemaining = bytesToRead - bytesRead; if (bytesRemaining == 0) { return C.RESULT_END_OF_INPUT; } readLength = (int) min(readLength, bytesRemaining); } //从上面获取的responseByteStream流中读取指定长度的数据到buffer int read = castNonNull(responseByteStream).read(buffer, offset, readLength); if (read == -1) { return C.RESULT_END_OF_INPUT; } bytesRead += read; bytesTransferred(read); return read; }
DefaultDataSource 播放器默认创建的DataSource,当播放的数据源不确定时,DefaultDataSource可以判断URI 的scheme动态的创建上面提到的DataSource,看下Open的源码就明了了
public long open(DataSpec dataSpec) throws IOException { Assertions.checkState(dataSource == null); // Choose the correct source for the scheme. String scheme = dataSpec.uri.getScheme(); if (Util.isLocalFileUri(dataSpec.uri)) { String uriPath = dataSpec.uri.getPath(); if (uriPath != null && uriPath.startsWith("/android_asset/")) { dataSource = getAssetDataSource(); } else { dataSource = getFileDataSource(); } } else if (SCHEME_ASSET.equals(scheme)) { dataSource = getAssetDataSource(); } else if (SCHEME_CONTENT.equals(scheme)) { dataSource = getContentDataSource(); } else if (SCHEME_RTMP.equals(scheme)) { dataSource = getRtmpDataSource(); } else if (SCHEME_UDP.equals(scheme)) { dataSource = getUdpDataSource(); } else if (SCHEME_DATA.equals(scheme)) { dataSource = getDataSchemeDataSource(); } else if (SCHEME_RAW.equals(scheme) || SCHEME_ANDROID_RESOURCE.equals(scheme)) { dataSource = getRawResourceDataSource(); } else { dataSource = baseDataSource; } // Open the source and return. return dataSource.open(dataSpec); }
到这里基本的DataSource介绍完了,对DataSource的基本功能有了一定的了解,对于简单的DataSource实现起来也很容易,但是面对复杂的数据环境这些还是远远不够的,如何保证复杂数据环境下的数据稳定输出,就是下面会重点要讲的CacheDataSource和TeeDataSource。
版权声明 ©
本文为CSDN作者山雨楼原创文章
转载请注明出处
原创不易,觉得有用的话,收藏转发点赞支持
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。