系列文章目录
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
ExoPlayer架构详解与源码分析(12)------Cache
ExoPlayer架构详解与源码分析(13)------TeeDataSource和CacheDataSource
ExoPlayer架构详解与源码分析(14)------ProgressiveMediaPeriod
文章目录
前言
中途间隔了一段时间,之前写了那么多铺垫,终于看到ProgressiveMediaPeriod实现部分了
ProgressiveMediaPeriod
有了之前的那些铺垫,这里直接看源码了
java
@Override
public void prepare(Callback callback, long positionUs) {
this.callback = callback;
loadCondition.open();//保证继续加载的开关打开,Loader不阻塞
startLoading();
}
private void startLoading() {
ExtractingLoadable loadable =//创建loadable 共Loader加载
new ExtractingLoadable(//extractorOutput监听,也就是Loader加载过程中会回调ProgressiveMediaPeriod的track,endTracks
uri, dataSource, progressiveMediaExtractor, /* extractorOutput= */ this, loadCondition);
if (prepared) {//如果已经准备完成
Assertions.checkState(isPendingReset());
if (durationUs != C.TIME_UNSET && pendingResetPositionUs > durationUs) {
//当前定位位置已经超过总时长,直接加载结束
loadingFinished = true;
pendingResetPositionUs = C.TIME_UNSET;
return;
}
//通过seekMap查找出当前时间对于的数据位置
loadable.setLoadPosition(
checkNotNull(seekMap).getSeekPoints(pendingResetPositionUs).first.position,
pendingResetPositionUs);
for (SampleQueue sampleQueue : sampleQueues) {
//将所有的轨道开始时间同步
sampleQueue.setStartTimeUs(pendingResetPositionUs);
}
pendingResetPositionUs = C.TIME_UNSET;
}
//获取开始加载时所有轨道已经提前的数据块总数,后面通过当前的数据块总数和开始的数量对比,可以判断出是否加载了新的数据
extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount();
long elapsedRealtimeMs =
loader.startLoading(//Loader开始加载,具体过程参照Loader部分的文章,此时加载状态的回调this,也就是加载完成后会调用ProgressiveMediaPeriod onLoadCompleted,seekMap
loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(dataType));
DataSpec dataSpec = loadable.dataSpec;
//触发监听
mediaSourceEventDispatcher.loadStarted(
new LoadEventInfo(loadable.loadTaskId, dataSpec, elapsedRealtimeMs),
C.DATA_TYPE_MEDIA,
C.TRACK_TYPE_UNKNOWN,
/* trackFormat= */ null,
C.SELECTION_REASON_UNKNOWN,
/* trackSelectionData= */ null,
/* mediaStartTimeUs= */ loadable.seekTimeUs,
durationUs);
}
@Override
//Loader加载时如果解析器需要输出Sample数据会先回调track,获取TrackOutput
public TrackOutput track(int id, int type) {
return prepareTrackOutput(new TrackId(id, /* isIcyTrack= */ false));
}
//构建TrackOutput
private TrackOutput prepareTrackOutput(TrackId id) {
int trackCount = sampleQueues.length;
for (int i = 0; i < trackCount; i++) {
//查询当前的sampleQueue是否已创建直接返回
if (id.equals(sampleQueueTrackIds[i])) {
return sampleQueues[i];
}
}
SampleQueue trackOutput =
//创建新的SampleQueue对应一个新的轨道,这里传入了缓存分配器allocator
SampleQueue.createWithDrm(allocator, drmSessionManager, drmEventDispatcher);
trackOutput.setUpstreamFormatChangeListener(this);//设置Format改变的监听
@NullableType
TrackId[] sampleQueueTrackIds = Arrays.copyOf(this.sampleQueueTrackIds, trackCount + 1);
sampleQueueTrackIds[trackCount] = id;
this.sampleQueueTrackIds = Util.castNonNullTypeArray(sampleQueueTrackIds);
//更新全局的SampleQueue数组
@NullableType SampleQueue[] sampleQueues = Arrays.copyOf(this.sampleQueues, trackCount + 1);
sampleQueues[trackCount] = trackOutput;
this.sampleQueues = Util.castNonNullTypeArray(sampleQueues);
return trackOutput;
}
@Override
//当解析器已经将所有轨道解析出来时会调用此方法,参照H264Reader部分
public void endTracks() {
sampleQueuesBuilt = true;
//当前方法在解析的子线程中回调,需要将方法方法当前ProgressiveMediaPeriod的线程中执行maybeFinishPrepare
handler.post(maybeFinishPrepareRunnable);
}
private void maybeFinishPrepare() {
if (released || prepared || !sampleQueuesBuilt || seekMap == null) {
return;
}
//确保所有轨道均已解析
for (SampleQueue sampleQueue : sampleQueues) {
if (sampleQueue.getUpstreamFormat() == null) {
return;
}
}
//阻塞住loader的解析,防止继续更新sampleQueues
loadCondition.close();
//创建TrackGroup,trackState 供后续的selectTracks使用
int trackCount = sampleQueues.length;
TrackGroup[] trackArray = new TrackGroup[trackCount];
boolean[] trackIsAudioVideoFlags = new boolean[trackCount];
for (int i = 0; i < trackCount; i++) {
Format trackFormat = checkNotNull(sampleQueues[i].getUpstreamFormat());
@Nullable String mimeType = trackFormat.sampleMimeType;
boolean isAudio = MimeTypes.isAudio(mimeType);
boolean isAudioVideo = isAudio || MimeTypes.isVideo(mimeType);
trackIsAudioVideoFlags[i] = isAudioVideo;
haveAudioVideoTracks |= isAudioVideo;
...
trackFormat = trackFormat.copyWithCryptoType(drmSessionManager.getCryptoType(trackFormat));
trackArray[i] = new TrackGroup(/* id= */ Integer.toString(i), trackFormat);
}
trackState = new TrackState(new TrackGroupArray(trackArray), trackIsAudioVideoFlags);
prepared = true;//标记准备完成
checkNotNull(callback).onPrepared(this);//通知上层prepared ,上层接下就会去调用ProgressiveMediaPeriod.selectTracks获取轨道信息
}
@Override
//获取轨道
public long selectTracks(
@NullableType ExoTrackSelection[] selections,//TrackSelector 部分会说到
boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams,//Renderer部分会说的
boolean[] streamResetFlags,
long positionUs) {
assertPrepared();//确保已经准备完成
TrackGroupArray tracks = trackState.tracks;
boolean[] trackEnabledStates = trackState.trackEnabledStates;
int oldEnabledTrackCount = enabledTrackCount;
// 去除原来mayRetainStreamFlags标记的无需保留的轨道
for (int i = 0; i < selections.length; i++) {
if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) {
int track = ((SampleStreamImpl) streams[i]).track;
Assertions.checkState(trackEnabledStates[track]);
enabledTrackCount--;
trackEnabledStates[track] = false;
streams[i] = null;
}
}
//如果是第一次selectTracks,而且positionUs 位置又不为0,或者之前一次selectTracks禁用了所有的轨道,这2种情况就需要Seek
boolean seekRequired = seenFirstTrackSelection ? oldEnabledTrackCount == 0 : positionUs != 0;
// 开始通过selections选择新的轨道
for (int i = 0; i < selections.length; i++) {
if (streams[i] == null && selections[i] != null) {
ExoTrackSelection selection = selections[i];
Assertions.checkState(selection.length() == 1);
Assertions.checkState(selection.getIndexInTrackGroup(0) == 0);
int track = tracks.indexOf(selection.getTrackGroup());
Assertions.checkState(!trackEnabledStates[track]);
enabledTrackCount++;
trackEnabledStates[track] = true;
streams[i] = new SampleStreamImpl(track);//向入参中赋值
streamResetFlags[i] = true;
if (!seekRequired) {
SampleQueue sampleQueue = sampleQueues[track];
seekRequired =
!sampleQueue.seekTo(positionUs, /* allowTimeBeyondBuffer= */ true)
&& sampleQueue.getReadIndex() != 0;
}
}
}
if (enabledTrackCount == 0) {
pendingDeferredRetry = false;
notifyDiscontinuity = false;
if (loader.isLoading()) {
// Discard as much as we can synchronously.
for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.discardToEnd();
}
loader.cancelLoading();
} else {
for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.reset();
}
}
} else if (seekRequired) {
positionUs = seekToUs(positionUs);
// We'll need to reset renderers consuming from all streams due to the seek.
for (int i = 0; i < streams.length; i++) {
if (streams[i] != null) {
streamResetFlags[i] = true;
}
}
}
seenFirstTrackSelection = true;
return positionUs;
}
@Override
//解析器解析出SeekMap时回调
public void seekMap(SeekMap seekMap) {
handler.post(() -> setSeekMap(seekMap));
}
@Override
public boolean continueLoading(long playbackPositionUs) {
if (loadingFinished//加载完成,出错等情况返回false
|| loader.hasFatalError()
|| pendingDeferredRetry
|| (prepared && enabledTrackCount == 0)) {
return false;
}
boolean continuedLoading = loadCondition.open();
if (!loader.isLoading()) {//当前没有正在加载
startLoading();//再次startLoading
continuedLoading = true;
}
return continuedLoading;
}
@Override
//Loader加载完成后回调
public void onLoadCompleted(
ExtractingLoadable loadable, long elapsedRealtimeMs, long loadDurationMs) {
if (durationUs == C.TIME_UNSET && seekMap != null) {//此时durationUs 未知,seekMap 已经有了
boolean isSeekable = seekMap.isSeekable();
long largestQueuedTimestampUs =//查询所有轨道中时间戳最大值
getLargestQueuedTimestampUs(/* includeDisabledTracks= */ true);
durationUs =//将最大值+10毫秒作为当前媒体的时长
largestQueuedTimestampUs == Long.MIN_VALUE
? 0
: largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US;
//此时durationUs 等信息已知可以通过MediaSource更新一把TimeLine
listener.onSourceInfoRefreshed(durationUs, isSeekable, isLive);
}
StatsDataSource dataSource = loadable.dataSource;
//StatsDataSource可以缓存Uri和ResponseHeader,获取这些值构建LoadEventInfo
LoadEventInfo loadEventInfo =
new LoadEventInfo(
loadable.loadTaskId,
loadable.dataSpec,
dataSource.getLastOpenedUri(),
dataSource.getLastResponseHeaders(),
elapsedRealtimeMs,
loadDurationMs,
dataSource.getBytesRead());
loadErrorHandlingPolicy.onLoadTaskConcluded(loadable.loadTaskId);
//触发监听
mediaSourceEventDispatcher.loadCompleted(
loadEventInfo,
C.DATA_TYPE_MEDIA,
C.TRACK_TYPE_UNKNOWN,
/* trackFormat= */ null,
C.SELECTION_REASON_UNKNOWN,
/* trackSelectionData= */ null,
/* mediaStartTimeUs= */ loadable.seekTimeUs,
durationUs);
loadingFinished = true;//此时的loadingFinished 已为true,没设置为false前是无法continueLoading的
//请求上层继续加载,是否继续加载由上层决定,如果上层同意继续加载会调用ProgressiveMediaPeriod的continueLoading
checkNotNull(callback).onContinueLoadingRequested(this);
}
这里再总结下ProgressiveMediaPeriod执行过程:
- 首先调用prepare 方法启动Loader去获取资源的轨道信息,此处参考ExoPlayer架构详解与源码分析(8)------Loader
- Loader中的解析器获取到信息后回回调endTrack通知ProgressiveMediaPeriod prepare完成
- ProgressiveMediaPeriod 会进一步通知上层此时MeidaSource已经准备完成,上层再Renderer(四大组件之一)绘制前会通过ExoPlayer的另一大组件TrackSelector (后面会说到),调用ProgressiveMediaPeriod 的selectTracks 选择轨道数据,也就将SampleQueue中的数据提供出去,此处参考ExoPlayer架构详解与源码分析(7)------SampleQueue
- 同时Loader从打开到当前加载数据量超过1M(默认值)时就会阻塞当前的Loader,然后询问上层是否继续加载
- 上层主要是通过后面要将LoadControl判断,如果上层决定继续加载更多数据,就会调用ProgressiveMediaPeriod continueLoading解开阻塞的锁继续加载。因为我们知道数据是加载到内存中的,如果无限制的加载肯定是不行的,需要有节制的加载和释放数据。而LoadControl就负责这块工作后面会讲到。
- 而上面过程当需要释放已经播放的内存时就会调用discardBuffer方法释放Sample中的内存
- 当所有的数据加载完成的时候会调用onLoadCompleted将loadingFinished 标记为true
总结
到这里Exoplayer中最复杂的组件MediaSource就全部分析完毕了,之前说了MediaSource在整个运载火箭中的角色就类似于燃料系统,确保火箭顺利升空,在运行过程中持续稳定的为火箭提供燃料。这些燃料提供给谁了呢,或者说是被谁消耗了呢。这就是下面要讲的火箭的核心发动机Renderers,也是Exoplayer四大组件中另一个重要的角色。
版权声明 ©
本文为CSDN作者山雨楼原创文章
转载请注明出处
原创不易,觉得有用的话,收藏转发点赞支持