ExoPlayer架構詳解與源碼分析(14)——ProgressiveMediaPeriod

系列文章目錄

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執行過程:

  1. 首先調用prepare方法啟動Loader去獲取資源的軌道信息,此處參考ExoPlayer架構詳解與源碼分析(8)——Loader
  2. Loader中的解析器獲取到信息后回回調endTrack通知ProgressiveMediaPeriod prepare完成
  3. ProgressiveMediaPeriod 會進一步通知上層此時MeidaSource已經準備完成,上層再Renderer繪制前會調用ProgressiveMediaPeriod selectTracks選擇軌道數據,也就將SampleQueue中的數據提供出去,此處參考ExoPlayer架構詳解與源碼分析(7)——SampleQueue
  4. 同時Loader從打開到當前加載數據量超過1M(默認值)時就會阻塞當前的Loader,然后詢問上層是否繼續加載
  5. 上層主要是通過后面要將LoadControl判斷,如果上層決定繼續加載更多數據,就會調用ProgressiveMediaPeriod continueLoading解開阻塞的鎖繼續加載。因為我們知道數據是加載到內存中的,如果無限制的加載肯定是不行的,需要有節制的加載和釋放數據。而LoadControl就負責這塊工作后面會講到。
  6. 而上面過程當需要釋放已經播放的內存時就會調用discardBuffer方法釋放Sample中的內存
  7. 當所有的數據加載完成的時候會調用onLoadCompleted將loadingFinished 標記為true

總結

到這里Exoplayer中最復雜的組件MediaSource就全部分析完畢了,之前說了MediaSource在整個運載火箭中的角色就類似于燃料系統,確保火箭順利升空,在運行過程中持續穩定的為火箭提供燃料。這些燃料提供給誰了呢,或者說是被誰消耗了呢。這就是下面要講的火箭的核心發動機Renderers,也是Exoplayer四大組件中另一個重要的角色。


版權聲明 ?
本文為CSDN作者山雨樓原創文章
轉載請注明出處
原創不易,覺得有用的話,收藏轉發點贊支持

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/38769.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/38769.shtml
英文地址,請注明出處:http://en.pswp.cn/web/38769.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

高考完的假期想學c語言 要注意那些問題?

在開始前剛好我有一些資料&#xff0c;是我根據網友給的問題精心整理了一份「c語言的資料從專業入門到高級教程」&#xff0c; 點個關注在評論區回復“666”之后私信回復“666”&#xff0c;全部無償共享給大家&#xff01;&#xff01;&#xff01;其實建議高考完之后好好玩一…

線上問題定位分析寶典——Linux中定位JVM問題常用命令

查詢Java進程ID #ps axu | grep java #ps elf | grep java查看機器負載及CPU信息 #top -p 1(進程ID) #top (查看所有進程)獲取CPU飆升線程堆棧 1. top -c 找到CPU飆升進程ID&#xff1b; 2. top -Hbp 9702(替換成進程ID) 找到CPU飆升線程ID&#xff1b; 3. $ printf &quo…

Java 7新特性深度解析:提升效率與功能

文章目錄 Java 7新特性深度解析&#xff1a;提升效率與功能一、Switch中添加對String類型的支持二、數字字面量的改進三、異常處理&#xff08;捕獲多個異常&#xff09;四、增強泛型推斷五、NIO2.0&#xff08;AIO&#xff09;新IO的支持六、SR292與InvokeDynamic七、Path接口…

64.ThreadLocal造成的內存泄漏

內存泄漏 程序中已動態分配的堆內存,由于某種原因程序為釋放和無法釋放,造成系統內存的浪費,導致程序運行速度減慢甚至系統崩潰等嚴重后果。內存泄漏的堆積終將導致內存溢出。 內存溢出 沒有足夠的內存提供申請者使用。 ThreadLocal出現內存泄漏的真實原因 內存泄漏的發…

Java中的多線程與并發編程詳解

Java中的多線程與并發編程詳解 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01; 在當今軟件開發中&#xff0c;利用多核處理器的能力并行執行任務已成為提高應用…

Transformer拆積木

文章目錄 ConceptsEmbeddingEncoderDecoderSelf-Attention matric calculationFinal Linear and Softmax LayerLoss function 參考 學一下已經問鼎中原七年之久的Transformer Concepts 開始拆積木&#xff01; Embedding Encoder Decoder Self-Attention matric calculati…

【文檔+源碼+調試講解】科研經費管理系統

目 錄 目 錄 摘 要 ABSTRACT 1 緒論 1.1 課題背景 1.2 研究現狀 1.3 研究內容 2 系統開發環境 2.1 vue技術 2.2 JAVA技術 2.3 MYSQL數據庫 2.4 B/S結構 2.5 SSM框架技術 3 系統分析 3.1 可行性分析 3.1.1 技術可行性 3.1.2 操作可行性 3.1.3 經濟可行性 3.1…

解析服務器地址異常的原因和解決方法

在網絡利用開發和運維進程中&#xff0c;解析服務器地址異常是常見的問題之一。特別是在觸及到跨境業務和國際網絡傳輸時&#xff0c;由于網絡環境的復雜性&#xff0c;解析服務器地址異常可能會致使用戶沒法正常訪問網站或利用程序。 解析服務器地址異常可能由多種緣由引發&am…

虛擬機的網絡配置

&#x1f4d1;打牌 &#xff1a; da pai ge的個人主頁 &#x1f324;?個人專欄 &#xff1a; da pai ge的博客專欄 ?? 每一步都向著夢想靠近&#xff0c;堅持就是勝利的序曲 一 …

手機系統設置選項

通用設置選項 1. 忽略電池優化選項 參考 https://blog.csdn.net/dodod2012/article/details/132045963 <uses-permission android:name"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>public static boolean isIgnoreBatteryOption(Context c…

俄羅斯ozon運費計算工具,跨境電商ozon物流運費計算工具

OZON平臺服裝類目賣家而言&#xff0c;如何快速、準確地為產品定價&#xff0c;并有效管理運費成本&#xff0c;直接關系到市場競爭力與利潤空間。接下來我們看看俄羅斯ozon運費計算工具&#xff0c;跨境電商ozon物流運費計算工具。 萌啦Ozon定價工具&#xff1a;智能模擬&…

Cesium----加載SuperMap的S3M地形

在原生Cesium中加載S3M地形&#xff0c;需要用到Supermap發布的一個插件&#xff1a;iClient3D-for-WebGL&#xff0c; 在vite vure3&#xff0c;cesium 1.119中進行了實現&#xff0c;注意的點在于需要把SuperMap3D 放置在cesium的Build路徑下 然后在代碼中直接調用SuperMap3…

windows重裝系統

一、下載Ventoy工具&#xff0c;制作啟動盤 官網地址&#xff1a;https://www.ventoy.net/cn/download.html 電腦插入用來制作系統盤的U盤&#xff0c;建議大小在8G以上。 雙擊打開剛解壓出來的Ventoy2Disk.exe文件。打開界面如圖&#xff1a; 確認U盤&#xff0c;如圖&am…

【HICE】基于httpd下的web服務器搭建

1.下載httpd&#xff1a; dnf install httpd -y 2.進入httpd中&#xff1a; cd /etc/httpd cd conf.d 3.編輯一個新的vhost.conf 4.重啟httpd服務 systemctl restart httpd 5.關閉防火墻 systemctl stop firewalld setenforce 0 6.文本寫入&#xff08;網頁編輯&…

8年經驗之談!自動化測試框架該如何搭建?

前言 最近好多小伙伴都在說接口自動化測試&#xff0c;那么究竟什么是接口自動化測試呢&#xff1f;讓我們一起往下看就知道了&#xff0c;首先我們得先弄清楚下面這個問題。 為什么要做&#xff08;自動化&#xff09;接口測試&#xff1f; 1、由于現在各個系統的復雜度不斷…

springboot的MultipartFile轉File讀取

在Spring Boot中&#xff0c;處理文件上傳時&#xff0c;MultipartFile接口被用來封裝上傳的文件信息。 如果需要將MultipartFile轉換為Java標準的File對象進行讀取。 以下是具體的操作流程&#xff1a; 1. 創建臨時文件 首先&#xff0c;需要將接收到的MultipartFile對象轉…

準化 | 水系統碳中和標準體系初見成效

2024年5月31日&#xff0c;中華環保聯合會發布《團體標準公告 2024年第10號&#xff08;總第78號&#xff09;》&#xff0c;批準發布了由中華環保聯合會提出并歸口的《廢水處理溫室氣體監測技術規程》(T/ACEF 142-2024)、《工業水系統碳排放核算方法與報告指南》(T/ACEF143-20…

yarn不同操作系統的安裝與配置

Yarn 是一個快速、可靠且安全的依賴包管理工具&#xff0c;用于替代 npm。以下是在不同操作系統上安裝和配置 Yarn 的步驟。 1. 安裝 Node.js 在安裝 Yarn 之前&#xff0c;請確保已經安裝了 Node.js&#xff0c;因為 Yarn 需要 Node.js 環境。你可以在 Node.js — Run JavaSc…

昇思25天學習打卡營第十五天|基于MobileNetv2的垃圾分類

基于MobileNetv2的垃圾分類 MobileNetv2模型原理介紹 MobileNet網絡是由Google團隊于2017年提出的專注于移動端、嵌入式或IoT設備的輕量級CNN網絡&#xff0c;相比于傳統的卷積神經網絡&#xff0c;MobileNet網絡使用深度可分離卷積&#xff08;Depthwise Separable Convolut…

Zabbix 6.0 案例

自定義監控內容 案列&#xff1a;自定義監控客戶端服務器登錄的人數 需求&#xff1a;限制登錄人數不超過 3 個&#xff0c;超過 3 個就發出報警信息 1.在客戶端創建自定義 key 明確需要執行的 linux 命令 who | wc -l 2.在被監控主機的配置文件目錄中&#xff08;/etc/za…