ExoPlayer is an application level media player for Android. It provides an alternative to Android’s MediaPlayer API for playing audio and video both locally and over the Internet. ExoPlayer supports features not currently supported by Android’s MediaPlayer API, including DASH and SmoothStreaming adaptive playbacks. Unlike the MediaPlayer API, ExoPlayer is easy to customize and extend, and can be updated through Play Store application updates.
首先看看ExoPlayer類之間的繼承關(guān)系已烤,對(duì)這個(gè)框架有一個(gè)大致的印象
ExoPlayer被定義為Interface稽荧,然后又幾個(gè)內(nèi)部類:Factory娄涩,Listener系忙,其中,F(xiàn)actory負(fù)責(zé)初始化ExoPlayer的操作迹卢,其關(guān)鍵代碼如下:
public static ExoPlayer newInstance(int rendererCount, int minBufferMs, int minRebufferMs) { return new ExoPlayerImpl(rendererCount, minBufferMs, minRebufferMs);}
Listener則負(fù)責(zé)向外界回調(diào)ExoPlayer狀態(tài)變化和錯(cuò)誤信息辽故。
ExoPlayer有一個(gè)子類:ExoPlayerImpl,它繼承了ExoPlayer的所有方法腐碱,并且負(fù)責(zé)接收轉(zhuǎn)發(fā)外界傳遞的消息誊垢,為什么是轉(zhuǎn)發(fā),不是接收呢症见?因?yàn)檎嬲苫畹牟皇荅xoPlayerImpl喂走,而是另外一個(gè)隱藏類,ExoPlayerImplInternal谋作,幾乎所有的操作都是在ExoPlayerImplInternal中完成的芋肠。
Start
我們看一個(gè)官方的使用Demo:
// 1. Instantiate the player.
player = ExoPlayer.Factory.newInstance(RENDERER_COUNT);
// 2. Construct renderers.
MediaCodecVideoTrackRenderer videoRenderer = ...
MediaCodecAudioTrackRenderer audioRenderer = ...
// 3. Inject the renderers through prepare.
player.prepare(videoRenderer, audioRenderer);
// 4. Pass the surface to the video renderer.
player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
// 5. Start playback.
player.setPlayWhenReady(true);
...
player.release();
// Don’t forget to release when done!
我們下面的探索過(guò)程都是按照這個(gè)Demo一步一步進(jìn)行的
1.Instantiate the player.
首先,用戶調(diào)用ExoPlayer.Factory.newInstance(...)方法得到ExoPlayerImpl的實(shí)例遵蚜,這個(gè)過(guò)程中帖池,我們看看做了什么:
public ExoPlayerImpl(int rendererCount, int minBufferMs, int minRebufferMs) {
Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION);
//首先初始化一些狀態(tài)
this.playWhenReady = false;
this.playbackState = STATE_IDLE;
//ExoPlayer的Listener是通過(guò)andListener(listener:Listener)方法添加的,所以需要一個(gè)數(shù)組去記錄所有的Listener
this.listeners = new CopyOnWriteArraySet<>();
//初始化軌道格式數(shù)組
this.trackFormats = new MediaFormat[rendererCount][];
//選中的軌道索引
this.selectedTrackIndices = new int[rendererCount];
//初始化一個(gè)Handler吭净,并將收到的消息傳遞給ExoPlayerImpl的handleEvent()方法處理
eventHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
ExoPlayerImpl.this.handleEvent(msg);
}
};
//初始化ExoPlayerImplInternal
internalPlayer = new ExoPlayerImplInternal(eventHandler, playWhenReady, selectedTrackIndices,
minBufferMs, minRebufferMs);
}
然后我們繼續(xù)看ExoPlayerImplInternal的構(gòu)造方法:
public ExoPlayerImplInternal(Handler eventHandler, boolean playWhenReady,int[] selectedTrackIndices, int minBufferMs, int minRebufferMs) {
//接受從ExoPlayerImpl傳遞進(jìn)來(lái)的Handler
this.eventHandler = eventHandler;
//初始化
this.playWhenReady = playWhenReady;
this.minBufferUs = minBufferMs * 1000L;
this.minRebufferUs = minRebufferMs * 1000L;
//拷貝
this.selectedTrackIndices = Arrays.copyOf(selectedTrackIndices, selectedTrackIndices.length);
this.state = ExoPlayer.STATE_IDLE;
this.durationUs = TrackRenderer.UNKNOWN_TIME_US;
this.bufferedPositionUs = TrackRenderer.UNKNOWN_TIME_US;
//初始化StandaloneMediaClock類睡汹,它是一個(gè)時(shí)鐘類,原理是通過(guò)獲取手機(jī)啟動(dòng)時(shí)間進(jìn)行差值計(jì)算
standaloneMediaClock = new StandaloneMediaClock();
//初始化一個(gè)自增Integer
pendingSeekCount = new AtomicInteger();
enabledRenderers = new ArrayList<TrackRenderer>(selectedTrackIndices.length);
trackFormats = new MediaFormat[selectedTrackIndices.length][];
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
// not normally change to this priority" is incorrect.
//初始化和啟動(dòng)一個(gè)HandlerThread
internalPlaybackThread = new PriorityHandlerThread("ExoPlayerImplInternal:Handler",Process.THREAD_PRIORITY_AUDIO);
internalPlaybackThread.start();
//為HandlerThread添加一個(gè)Handler
handler = new Handler(internalPlaybackThread.getLooper(), this);
}
至此寂殉,ExoPlayerImpl和ExoPlayerImplInternal兩個(gè)類的狀態(tài)都被初始化囚巴,啟動(dòng)一個(gè)Process.THREAD_PRIORITY_AUDIO
的線程,準(zhǔn)備接受任務(wù)不撑。
2.Construct renderers.
ExoPlayer被初始化后文兢,用戶需要調(diào)用ExoPlayer.prepare(...)進(jìn)行準(zhǔn)備工作:
public void prepare(TrackRenderer... renderers);
TrackRenderer和它的孩子們
我們看到晤斩,prepare形參是TrackRenderer數(shù)組焕檬,那么這個(gè)TrackRenderer是個(gè)什么東東呢?
ExoPlayer的媒體組件澳泵,都是通過(guò)注入的方式實(shí)現(xiàn)的实愚,而TrackRenderer就是媒體組件的基類。
public abstract class TrackRenderer implements ExoPlayerComponent {}
從源碼看,TrackRenderer是個(gè)抽象類腊敲,繼承自ExoPlayerComponent击喂,只有一個(gè)屬性:
private int state;
大部分方法都是圍繞state實(shí)現(xiàn)的,剩下的都是抽象方法碰辅,TrackRenderer類用來(lái)維護(hù)state懂昂,而具體的工作需要子類去實(shí)現(xiàn),而做法比較巧妙没宾,如TrackRenderer的prepare()方法:
//prepare方法維護(hù)state屬性的狀態(tài)凌彬,具體的執(zhí)行則是調(diào)用doPrepare()方法
final int prepare(long positionUs) throws ExoPlaybackException {
Assertions.checkState(state == STATE_UNPREPARED);
state = doPrepare(positionUs) ? STATE_PREPARED : STATE_UNPREPARED;
return state;
}
//抽象方法,由子類實(shí)現(xiàn)
protected abstract boolean doPrepare(long positionUs) throws ExoPlaybackException;
再看看ExoPlayerComponent
public interface ExoPlayerComponent {
void handleMessage(int messageType, Object message) throws ExoPlaybackException;
}
從名字看循衰,它是一個(gè)組件铲敛,用于在播放線程接受消息,所有實(shí)現(xiàn)它的類都可以在播放線程接受消息会钝,所以TrackRenderer可以接收來(lái)自其他線程的消息伐蒋。
那我們看TrackRenderer有哪些子類
從類圖來(lái)看,TrackRenderer有很多子類迁酸,其中先鱼,SampleSourceTrackRenderer比較重要,我們看一下官方文檔對(duì)這個(gè)類的介紹:
SampleSource and SampleSourceReader
TrackRenderer的實(shí)例奸鬓,渲染來(lái)從SampleSource采集的樣本型型,SampleSource是什么呢,從名字看應(yīng)該是樣本源:
媒體樣本源全蝶,SampleSource一般暴漏一個(gè)或多個(gè)軌道闹蒜,軌道的個(gè)數(shù)和每個(gè)軌道的格式可以通過(guò) SampleSource.SampleSourceReader.getTrackCount()和SampleSource.SampleSourceReader.getFormat(int)得到。
再回頭看SampleSourceTrackRenderer的構(gòu)造方法:
public SampleSourceTrackRenderer(SampleSource... sources) {
this.sources = new SampleSourceReader[sources.length];
for (int i = 0; i < sources.length; i++) {
this.sources[i] = sources[i].register();
}
}
接受一個(gè)或者多個(gè)SampleSource數(shù)組抑淫,然后調(diào)用了SampleSource的register()方法
/**
* A consumer of samples should call this method to register themselves and gain access to the
* source through the returned {@link SampleSourceReader}.
* <p>
* {@link SampleSourceReader#release()} should be called on the returned object when access is no
* longer required.
*
* @return A {@link SampleSourceReader} that provides access to the source.
*/
public SampleSourceReader register();
從官方介紹來(lái)看绷落,消費(fèi)者(獲取樣本的類,這里是指SampleSourceTrackRenderer)通過(guò)調(diào)用register()方法來(lái)獲得對(duì)媒體樣本讀取的能力始苇。
register()方法返回SampleSourceReader類:
/**
*An interface providing read access to a {@link SampleSource}.
*/
public interface SampleSourceReader
是一個(gè)接口砌烁,定義了一些訪問(wèn)媒體樣本的方法,以下列舉一些重要的方法催式,詳細(xì)可以去com.google.android.exoplayer.SampleSource.SampleSourceReader
類查看:
- prepare(long positionUS):boolean
- getTrackCount():int
- getFormat(int track):MediaFormat
- enable(int track,long position)
- disable(int track)
- readData(int track,long positionUs,MediaFormatHolder formatHolder,SampleHolder sampleHolder):int
- seekToUs(long positionUs)
- release()
繼續(xù)看SampleSourceTrackRenderer的構(gòu)造方法:
this.sources[i] = sources[i].register();
SampleSourceTrackRenderer中定義一個(gè)全局變量函喉,存儲(chǔ)所有的SampleSourceReader,方便其他方法訪問(wèn)SampleSource中的資源荣月。
到這里管呵,ExoPlayer的框架結(jié)構(gòu)就比較清晰了,TrackRenderer負(fù)責(zé)渲染由SampleSource提供的媒體樣本哺窄。
3. Inject the renderers through prepare.
player.prepare(videoRenderer, audioRenderer);
前兩步分別初始化ExoPlayer捐下、TrackRenderer和SampleSource账锹,并將SampleSource注入到TrackRenderer,但是直到現(xiàn)在坷襟,TrackRenderer都沒(méi)有和ExoPlayer產(chǎn)生關(guān)系奸柬,客官們是不是等的不耐煩了??,那么婴程,prepare正是將TrackRenderer注入ExoPlayer廓奕,我們通過(guò)源碼,一步一步看看prepare都做了哪些工作档叔。
ExoPlayerImpl:
@Override
public void prepare(TrackRenderer... renderers) {
Arrays.fill(trackFormats, null);
internalPlayer.prepare(renderers);
}
在ExoPlayerImpl中懂从,首先初始化了trackFormats數(shù)組,然后調(diào)用ExoPlayerImplInternal的prepare(...)方法蹲蒲。
ExoPlayerImplInternal:
public void prepare(TrackRenderer... renderers) {
handler.obtainMessage(MSG_PREPARE, renderers).sendToTarget();
}
ExoPlayerImplInternal.prepare(...)方法通過(guò)handler發(fā)送一個(gè)MSG_PREPARE的指令并傳入renderers數(shù)組番甩。而這個(gè)Handler所在的線程是我們之前講到的初始化過(guò)程中啟動(dòng)工作線程的HandlerThread對(duì)應(yīng)的Handler,那我們找到接受Handler的handleMessage(...)方法:
@Override
public boolean handleMessage(Message msg) {
try {
switch (msg.what) {
...
case MSG_PREPARE: {
//如果消息類型是MSG_PREPARE届搁,則調(diào)用下面這個(gè)方法并返回true
prepareInternal((TrackRenderer[]) msg.obj);
return true;
}
...
} catch (ExoPlaybackException e) {
Log.e(TAG, "Internal track renderer error.", e);
eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget();
stopInternal();
return true;
} catch (RuntimeException e) {
Log.e(TAG, "Internal runtime error.", e);
eventHandler.obtainMessage(MSG_ERROR, new ExoPlaybackException(e, true)).sendToTarget();
stopInternal();
return true;
}
}
private void prepareInternal(TrackRenderer[] renderers) throws ExoPlaybackException {
//執(zhí)行重置工作缘薛,主要有:移除handler消息隊(duì)列中的MSG_DO_SOME_WORD、MSG_INCREMENTAL_PREPARE
//停止時(shí)鐘卡睦,停止TrackRenderer宴胧,釋放TrackRenderer,清除緩存等
resetInternal();
//將傳入的renderers賦值給全局變量
this.renderers = renderers;
//將trackFormats置為空
Arrays.fill(trackFormats, null);
for (int i = 0; i < renderers.length; i++) {
//遍歷所有的renderer
//TrackRenderer的getMediaClock()的方法介紹在下面
MediaClock mediaClock = renderers[i].getMediaClock();
if (mediaClock != null) {
Assertions.checkState(rendererMediaClock == null);
//如果該renderer提供MediaClock表锻,則將該MediaClock賦值為全局變量
rendererMediaClock = mediaClock;
rendererMediaClockSource = renderers[i];
}
}
//修改狀態(tài)為STATE_PREPARING
setState(ExoPlayer.STATE_PREPARING);
//增量準(zhǔn)備恕齐?應(yīng)該是為了復(fù)用代碼進(jìn)行封裝的,具體看里面的代碼吧
incrementalPrepareInternal();
}
TrackRenderer.getMediaClock()方法介紹:如果這個(gè)renderer提供了他自己的播放位置瞬逊,那么显歧,這個(gè)方法會(huì)返回對(duì)應(yīng)的MediaClock,如果有确镊,播放器會(huì)使用它提供的MediaClock作為視頻播放周期士骤,一個(gè)播放器中至少有一個(gè)Renderer提供MediaClock蕾域。
private void incrementalPrepareInternal() throws ExoPlaybackException {
//獲取當(dāng)前系統(tǒng)啟動(dòng)時(shí)間
long operationStartTimeMs = SystemClock.elapsedRealtime();
boolean prepared = true;
for (int rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) {
TrackRenderer renderer = renderers[rendererIndex];
//如果當(dāng)前狀態(tài)為STATE_UNPREPARED拷肌,調(diào)用TrackRenderer.prepare(...)方法
if (renderer.getState() == TrackRenderer.STATE_UNPREPARED) {
int state = renderer.prepare(positionUs);
//檢查狀態(tài)是否還是STATE_UNPREPARED,如果是則拋出異常
if (state == TrackRenderer.STATE_UNPREPARED) {
renderer.maybeThrowError();
prepared = false;
}
}
}
if (!prepared) {
// We're still waiting for some sources to be prepared.
//如果未準(zhǔn)備成功旨巷,則在PREPARE_INTERVAL_MS時(shí)間后重試
scheduleNextOperation(MSG_INCREMENTAL_PREPARE, operationStartTimeMs, PREPARE_INTERVAL_MS);
return;
}
long durationUs = 0;
boolean allRenderersEnded = true;
boolean allRenderersReadyOrEnded = true;
//再次遍歷所有的TrackRenderer
for (int rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) {
TrackRenderer renderer = renderers[rendererIndex];
//獲取每個(gè)TrackRenderer的TrackCount(軌道個(gè)數(shù))
int rendererTrackCount = renderer.getTrackCount();
MediaFormat[] rendererTrackFormats = new MediaFormat[rendererTrackCount];
for (int trackIndex = 0; trackIndex < rendererTrackCount; trackIndex++) {
rendererTrackFormats[trackIndex] = renderer.getFormat(trackIndex);
}
//記錄每個(gè)TrackRenderer的每個(gè)Track的格式
trackFormats[rendererIndex] = rendererTrackFormats;
if (rendererTrackCount > 0) {
//如果時(shí)間為未知時(shí)間巨缘,則不作處理,(這塊沒(méi)有太看懂采呐,為什么上一個(gè)TrackRenderer的durationUs作為這個(gè)TrackRenderer的判斷依據(jù)若锁,
// 如果上一個(gè)TrackRenderer的durationUs = TrackRenderer.UNKNOWN_TIME_US,則之后所有的durationUs都是TrackRenderer.UNKNOWN_TIME_US懈万?)
if (durationUs == TrackRenderer.UNKNOWN_TIME_US) {
// We've already encountered a track for which the duration is unknown, so the media
// duration is unknown regardless of the duration of this track.
} else {
long trackDurationUs = renderer.getDurationUs();
if (trackDurationUs == TrackRenderer.UNKNOWN_TIME_US) {
durationUs = TrackRenderer.UNKNOWN_TIME_US;
} else if (trackDurationUs == TrackRenderer.MATCH_LONGEST_US) {
// Do nothing.
} else {
//如果上一個(gè)TrackRenderer的Duration和這個(gè)TrackRenderer的Duration不一致拴清,則取大的
durationUs = Math.max(durationUs, trackDurationUs);
}
}
//selectedTrackIndices是從ExoPlayerImpl傳入的,每個(gè)Renderer同時(shí)只能有一個(gè)track在工作
int trackIndex = selectedTrackIndices[rendererIndex];
if (0 <= trackIndex && trackIndex < rendererTrackFormats.length) {
//打開(kāi)對(duì)應(yīng)的Track
renderer.enable(trackIndex, positionUs, false);
enabledRenderers.add(renderer);
allRenderersEnded = allRenderersEnded && renderer.isEnded();
allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded(renderer);
}
}
}
this.durationUs = durationUs;
//更新ExoPlayer的state
if (allRenderersEnded
&& (durationUs == TrackRenderer.UNKNOWN_TIME_US || durationUs <= positionUs)) {
// We don't expect this case, but handle it anyway.
state = ExoPlayer.STATE_ENDED;
} else {
state = allRenderersReadyOrEnded ? ExoPlayer.STATE_READY : ExoPlayer.STATE_BUFFERING;
}
//通知ExoPlayerImpl会通,更改狀態(tài)
// Fire an event indicating that the player has been prepared, passing the initial state and
// renderer track information.
eventHandler.obtainMessage(MSG_PREPARED, state, 0, trackFormats).sendToTarget();
// Start the renderers if required, and schedule the first piece of work.
if (playWhenReady && state == ExoPlayer.STATE_READY) {
startRenderers();
}
// 向Handler發(fā)送 MSG_DO_SOME_WORK 命令
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
緊接著又向handler發(fā)送MSG_DO_SOME_WORK命令:
private void doSomeWork() throws ExoPlaybackException {
TraceUtil.beginSection("doSomeWork");
//獲取系統(tǒng)啟動(dòng)時(shí)間
long operationStartTimeMs = SystemClock.elapsedRealtime();
//緩存位置口予,默認(rèn)值為durationUs 或 Long.MAX_VALUE
long bufferedPositionUs = durationUs != TrackRenderer.UNKNOWN_TIME_US ? durationUs
: Long.MAX_VALUE;
boolean allRenderersEnded = true;
boolean allRenderersReadyOrEnded = true;
//刷新播放時(shí)間
updatePositionUs();
//遍歷所有開(kāi)啟的Renderer
for (int i = 0; i < enabledRenderers.size(); i++) {
TrackRenderer renderer = enabledRenderers.get(i);
// TODO: Each renderer should return the maximum delay before which it wishes to be
// invoked again. The minimum of these values should then be used as the delay before the next
// invocation of this method.
//調(diào)用TrackRenderer的doSomeWork,這個(gè)后面再說(shuō)
renderer.doSomeWork(positionUs, elapsedRealtimeUs);
allRenderersEnded = allRenderersEnded && renderer.isEnded();
// Determine whether the renderer is ready (or ended). If it's not, throw an error that's
// preventing the renderer from making progress, if such an error exists.
boolean rendererReadyOrEnded = rendererReadyOrEnded(renderer);
if (!rendererReadyOrEnded) {
renderer.maybeThrowError();
}
allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded;
if (bufferedPositionUs == TrackRenderer.UNKNOWN_TIME_US) {
// We've already encountered a track for which the buffered position is unknown. Hence the
// media buffer position unknown regardless of the buffered position of this track.
} else {
//獲取實(shí)際的緩存位置
long rendererDurationUs = renderer.getDurationUs();
long rendererBufferedPositionUs = renderer.getBufferedPositionUs();
if (rendererBufferedPositionUs == TrackRenderer.UNKNOWN_TIME_US) {
bufferedPositionUs = TrackRenderer.UNKNOWN_TIME_US;
} else if (rendererBufferedPositionUs == TrackRenderer.END_OF_TRACK_US
|| (rendererDurationUs != TrackRenderer.UNKNOWN_TIME_US
&& rendererDurationUs != TrackRenderer.MATCH_LONGEST_US
&& rendererBufferedPositionUs >= rendererDurationUs)) {
// This track is fully buffered.
} else {
//一般情況會(huì)進(jìn)入這個(gè)條件
bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs);
}
}
}
//刷新全局緩存位置
this.bufferedPositionUs = bufferedPositionUs;
//如果所有的Renderer都是結(jié)束狀態(tài)涕侈,或者durationUs = TrackRenderer.UNKNOWN_TIME_US沪停,或者durationUs <= positionUs,
//則停止渲染
if (allRenderersEnded
&& (durationUs == TrackRenderer.UNKNOWN_TIME_US || durationUs <= positionUs)) {
setState(ExoPlayer.STATE_ENDED);
stopRenderers();
} else if (state == ExoPlayer.STATE_BUFFERING && allRenderersReadyOrEnded) {
//如果狀態(tài)是STATE_BUFFERING裳涛,但是所有的Renderer已經(jīng)準(zhǔn)備就緒木张,則開(kāi)始渲染,并將狀態(tài)改為STATE_READY
setState(ExoPlayer.STATE_READY);
if (playWhenReady) {
startRenderers();
}
} else if (state == ExoPlayer.STATE_READY && !allRenderersReadyOrEnded) {
//如果狀態(tài)已經(jīng)是READY端三,但不是allRenderersReadyOrEnded舷礼,則當(dāng)前視頻正在緩存,停止渲染郊闯,等待緩存
rebuffering = playWhenReady;
setState(ExoPlayer.STATE_BUFFERING);
stopRenderers();
}
handler.removeMessages(MSG_DO_SOME_WORK);
//如何state為STATE_READY或者STATE_BUFFERING妻献,則定時(shí)RENDERING_INTERVAL_MS重新執(zhí)行該方法
if ((playWhenReady && state == ExoPlayer.STATE_READY) || state == ExoPlayer.STATE_BUFFERING) {
scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, RENDERING_INTERVAL_MS);
} else if (!enabledRenderers.isEmpty()) {
//否則,則定時(shí)IDLE_INTERVAL_MS重新執(zhí)行該方法
scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, IDLE_INTERVAL_MS);
}
TraceUtil.endSection();
}
當(dāng)prepare調(diào)用doSomeWork()之后团赁,在整個(gè)播放期間育拨,doSomeWork()會(huì)一直重復(fù)執(zhí)行。
看完ExoPlayerImplInternal類欢摄,我們?cè)偃rackRenderer看一看:
/**
* Prepares the renderer. This method is non-blocking, and hence it may be necessary to call it
* more than once in order to transition the renderer into the prepared state.
*
* @param positionUs The player's current playback position.
* @return The current state (one of the STATE_* constants), for convenience.
* @throws ExoPlaybackException If an error occurs.
*/
/* package */
final int prepare(long positionUs) throws ExoPlaybackException {
Assertions.checkState(state == STATE_UNPREPARED);
state = doPrepare(positionUs) ? STATE_PREPARED : STATE_UNPREPARED;
return state;
}
/**
* Invoked to make progress when the renderer is in the {@link #STATE_UNPREPARED} state. This
* method will be called repeatedly until {@code true} is returned.
* <p>
* This method should return quickly, and should not block if the renderer is currently unable to
* make any useful progress.
*
* @param positionUs The player's current playback position.
* @return True if the renderer is now prepared. False otherwise.
* @throws ExoPlaybackException If an error occurs.
*/
protected abstract boolean doPrepare(long positionUs) throws ExoPlaybackException;
TrackRenderer的prepare(...)只是修改了state的狀態(tài)熬丧,具體執(zhí)行交給了子類,上面介紹過(guò)的TrackRenderer的子類DummyTrackRenderer,SampleSourceTrackRenderer怀挠,其中DummyTrackRenderer我們這里用不到析蝴,所以直接看SampleSourceTrackRenderer:
@Override
protected final boolean doPrepare(long positionUs) throws ExoPlaybackException {
boolean allSourcesPrepared = true;
for (int i = 0; i < sources.length; i++) {
//這里調(diào)用SampleSourceReader.prepare(...)準(zhǔn)備資源 :( 層層調(diào)用啊,感覺(jué)快被繞瘋了
allSourcesPrepared &= sources[i].prepare(positionUs);
}
//如其中有資源無(wú)法準(zhǔn)備就緒绿淋,直接返回false嫌变,夠狠
if (!allSourcesPrepared) {
return false;
}
// The sources are all prepared.
// 記錄一下所有的軌道個(gè)數(shù)
int totalSourceTrackCount = 0;
for (int i = 0; i < sources.length; i++) {
totalSourceTrackCount += sources[i].getTrackCount();
}
long durationUs = 0;
int handledTrackCount = 0;
int[] handledSourceIndices = new int[totalSourceTrackCount];
int[] handledTrackIndices = new int[totalSourceTrackCount];
int sourceCount = sources.length;
// 遍歷所有的軌道
for (int sourceIndex = 0; sourceIndex < sourceCount; sourceIndex++) {
SampleSourceReader source = sources[sourceIndex];
int sourceTrackCount = source.getTrackCount();
for (int trackIndex = 0; trackIndex < sourceTrackCount; trackIndex++) {
MediaFormat format = source.getFormat(trackIndex);
boolean handlesTrack;
try {
//判斷是否可以處理該媒體格式的軌道
handlesTrack = handlesTrack(format);
} catch (DecoderQueryException e) {
throw new ExoPlaybackException(e);
}
if (handlesTrack) {
handledSourceIndices[handledTrackCount] = sourceIndex;
handledTrackIndices[handledTrackCount] = trackIndex;
handledTrackCount++;
//獲得軌道的時(shí)長(zhǎng)
if (durationUs == TrackRenderer.UNKNOWN_TIME_US) {
// We've already encountered a track for which the duration is unknown, so the media
// duration is unknown regardless of the duration of this track.
} else {
long trackDurationUs = format.durationUs;
if (trackDurationUs == TrackRenderer.UNKNOWN_TIME_US) {
durationUs = TrackRenderer.UNKNOWN_TIME_US;
} else if (trackDurationUs == TrackRenderer.MATCH_LONGEST_US) {
// Do nothing.
} else {
durationUs = Math.max(durationUs, trackDurationUs);
}
}
}
}
}
this.durationUs = durationUs;
//記錄所有可以處理的Source和SourceTrack
//這塊不知道為什么拆成兩個(gè)數(shù)組,其實(shí)一個(gè)二維數(shù)組也是可以的
this.handledSourceIndices = Arrays.copyOf(handledSourceIndices, handledTrackCount);
this.handledSourceTrackIndices = Arrays.copyOf(handledTrackIndices, handledTrackCount);
return true;
}
在上面的代碼中躬它,調(diào)用的SampleSourceReader的prepare()方法腾啥,這里的SampleSourceReader實(shí)際是ExtractorSampleSource,其主要從URL冯吓、File倘待、Assets等讀取數(shù)據(jù)做準(zhǔn)備,對(duì)數(shù)據(jù)的加載后面會(huì)單獨(dú)分析组贺。
我們?cè)俎垡槐閜repare的執(zhí)行過(guò)程:
4. Pass the surface to the video renderer.
player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
在進(jìn)行之前凸舵,我們有必要講一下MediaCodec這個(gè)類,呃失尖,其實(shí)官方已經(jīng)介紹的很清楚了啊奄,這個(gè)類好復(fù)雜呀渐苏,我們了解一下工作原理就可以了。
先看這一張圖菇夸,介紹MediaCodec的工作原理琼富,它主要負(fù)責(zé)兩件事:
- 輸入外界提供的數(shù)據(jù):外界提供一個(gè)Input Buffer,發(fā)送給MediaCodec處理
- 輸出合成后的數(shù)據(jù):外界獲取由MediaCodec處理過(guò)的Output Buffer
支持的數(shù)據(jù)類型
MediaCodec支持三種數(shù)據(jù)
- 壓縮數(shù)據(jù)
- 音頻數(shù)據(jù)
- 視頻數(shù)據(jù)
三種數(shù)據(jù)都支持通過(guò)ByteBuffers的方式傳入庄新,但如果傳遞的是視頻數(shù)據(jù)鞠眉,需要傳遞一個(gè)Surface提高M(jìn)ediaCodec的性能,Surface使用的是原始視頻Buffer择诈,無(wú)需轉(zhuǎn)換或拷貝到ByteBuffers械蹋,所以它比較高效。
如果使用Surface羞芍,通常情況下無(wú)法獲取到二進(jìn)制數(shù)據(jù)哗戈,但是你可以用ImageReader讀取視頻幀,而如果你使用ByteBuffers荷科,你也可以使用Image類讀取視頻幀谱醇。
這是我從別處抄來(lái)的一個(gè)Demo,具體如何使用MediaCodec播放視頻
現(xiàn)在我們繼續(xù)看ExoPlayer的代碼步做,上面看到副渴,調(diào)用者向ExoPlayer發(fā)送一個(gè)消息,消息類型是MSG_SET_SURFACE并傳入一個(gè)surface全度,這是因?yàn)橹缶纾琈ediaCodec需要一個(gè)Surface將視頻原始Buffer數(shù)據(jù)直接傳遞給Surface,那這個(gè)消息最后的接受者是誰(shuí)呢将鸵?
答案是:MediaCodecVideoTrackRenderer
@Override
public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
if (messageType == MSG_SET_SURFACE) {
setSurface((Surface) message);
} else {
super.handleMessage(messageType, message);
}
}
在MediaCodecVideoTrackRenderer中可以找到這個(gè)方法勉盅,如果消息類型是MSG_SET_SURFACE,則接受并調(diào)用setSurface(surface:Surface)顶掉,否則不處理草娜,到這一步以后的處理就屬于ExoPlayer調(diào)用Android系統(tǒng)的MediaCodec的方法了,在上面我們已經(jīng)講過(guò)痒筒,不再重復(fù)宰闰。
到這里,ExoPlayer的結(jié)構(gòu)大致說(shuō)完了簿透,如果有什么不懂的移袍,也不用問(wèn)我,其實(shí)...我也不會(huì)老充,O(∩_∩)O哈哈~葡盗,去看代碼,去看代碼啡浊,去看代碼觅够,重要的事情要說(shuō)三遍......如果單用一篇文章去徹底弄清楚ExoPlayer是不現(xiàn)實(shí)的胶背,而且經(jīng)過(guò)我的過(guò)濾,可能會(huì)丟掉一些很重要的東西喘先,所以我希望這篇文章只是在你學(xué)習(xí)ExoPlayer過(guò)程中的參考钳吟,主要的路徑依然是看官方講解和源碼,另外苹祟,這篇文章中可能會(huì)有錯(cuò)誤砸抛,如果發(fā)現(xiàn)评雌,請(qǐng)及時(shí)的告訴我树枫,謝謝!