系列文章目錄
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實現部分了
ProgressiveMediaPeriod
有了之前的那些鋪墊,這里直接看源碼了
@Overridepublic 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,endTracksuri, 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,seekMaploadable, 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,獲取TrackOutputpublic TrackOutput track(int id, int type) {return prepareTrackOutput(new TrackId(id, /* isIcyTrack= */ false));}//構建TrackOutputprivate 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對應一個新的軌道,這里傳入了緩存分配器allocatorSampleQueue.createWithDrm(allocator, drmSessionManager, drmEventDispatcher);trackOutput.setUpstreamFormatChangeListener(this);//設置Format改變的監聽@NullableTypeTrackId[] 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的線程中執行maybeFinishPreparehandler.post(maybeFinishPrepareRunnable);}private void maybeFinishPrepare() {if (released || prepared || !sampleQueuesBuilt || seekMap == null) {return;}//確保所有軌道均已解析for (SampleQueue sampleQueue : sampleQueues) {if (sampleQueue.getUpstreamFormat() == null) {return;}}//阻塞住loader的解析,防止繼續更新sampleQueuesloadCondition.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種情況就需要Seekboolean 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));}@Overridepublic boolean continueLoading(long playbackPositionUs) {if (loadingFinished//加載完成,出錯等情況返回false|| loader.hasFatalError()|| pendingDeferredRetry|| (prepared && enabledTrackCount == 0)) {return false;}boolean continuedLoading = loadCondition.open();if (!loader.isLoading()) {//當前沒有正在加載startLoading();//再次startLoadingcontinuedLoading = 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更新一把TimeLinelistener.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的continueLoadingcheckNotNull(callback).onContinueLoadingRequested(this);}
這里再總結下rogressiveMediaPeriod執行過程:
- 首先調用prepare方法啟動Loader去獲取資源的軌道信息,此處參考ExoPlayer架構詳解與源碼分析(8)——Loader
- Loader中的解析器獲取到信息后回回調endTrack通知ProgressiveMediaPeriod prepare完成
- ProgressiveMediaPeriod 會進一步通知上層此時MeidaSource已經準備完成,上層再Renderer繪制前會調用ProgressiveMediaPeriod selectTracks選擇軌道數據,也就將SampleQueue中的數據提供出去,此處參考ExoPlayer架構詳解與源碼分析(7)——SampleQueue
- 同時Loader從打開到當前加載數據量超過1M(默認值)時就會阻塞當前的Loader,然后詢問上層是否繼續加載
- 上層主要是通過后面要將LoadControl判斷,如果上層決定繼續加載更多數據,就會調用ProgressiveMediaPeriod continueLoading解開阻塞的鎖繼續加載。因為我們知道數據是加載到內存中的,如果無限制的加載肯定是不行的,需要有節制的加載和釋放數據。而LoadControl就負責這塊工作后面會講到。
- 而上面過程當需要釋放已經播放的內存時就會調用discardBuffer方法釋放Sample中的內存
- 當所有的數據加載完成的時候會調用onLoadCompleted將loadingFinished 標記為true
總結
到這里Exoplayer中最復雜的組件MediaSource就全部分析完畢了,之前說了MediaSource在整個運載火箭中的角色就類似于燃料系統,確保火箭順利升空,在運行過程中持續穩定的為火箭提供燃料。這些燃料提供給誰了呢,或者說是被誰消耗了呢。這就是下面要講的火箭的核心發動機Renderers,也是Exoplayer四大組件中另一個重要的角色。
版權聲明 ?
本文為CSDN作者山雨樓原創文章
轉載請注明出處
原創不易,覺得有用的話,收藏轉發點贊支持