東川印記

一本東川,笑看爭龍斗虎;寰茫兦者,度橫佰昧人生。

Exoplayer学习03 player setPlayWhenReady方法

2021年5月19日星期三



近日持续 使用 HDMI及S/PDIF线,传递Dolby Digital AC-3音频给功放,经过八轮测试后发现:

通过SPDIF线,输出给杜比解码器再给功放,直通输出5.1声道正常,解码输出为两声道,亦属正常现象。

通过HDMI线,输出给放映服务器, 再传递给杜比解码器再给功放,直通输出为两声道噪音,哒哒哒哒哒的噪音;解码输出为两声道,虽音频内容正常,但非协议正常现象。

继续水源码

上回书说到,build成功了player,然后就可以对player进行设置了

player = new SimpleExoPlayer.Builder(/* context= */ this) .build(); player.setPlayWhenReady(startAutoPlay); playerView.setPlayer(player);

player.setMediaItems(mediaItems, /* resetPosition= */ !haveStartPosition); player.prepare();


设置当准备好时播放

player.setPlayWhenReady(startAutoPlay);

直接看SimpleExoPlayer的实现

@Override public void setPlayWhenReady(boolean playWhenReady) { verifyApplicationThread(); @AudioFocusManager.PlayerCommand int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState()); updatePlayWhenReady( playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand)); }

接口定义来自于 Player接口

/** * Sets whether playback should proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. * * <p>If the player is already in the ready state then this method pauses and resumes playback. * * @param playWhenReady Whether playback should proceed when ready. */ void setPlayWhenReady(boolean playWhenReady);

注释说,准备就绪后,配置是否播放。

这里link了播放器的状态

/** The player does not have any media to play播放器没有任何媒体可播放. */ int STATE_IDLE = 1; /** * The player is not able to immediately play from its current position. This state typically * occurs when more data needs to be loaded播放器无法立即从其当前位置播放。 此状态通常在需要加载更多数据时发生. */ int STATE_BUFFERING = 2; /** * The player is able to immediately play from its current position. The player will be playing if * {@link #getPlayWhenReady()} is true, and paused otherwise.可以立即从其当前位置开始播放。

* 如果{@link #getPlayWhenReady()}为true,则播放器将在播放,否则暂停。 */ int STATE_READY = 3; /** The player has finished playing the media播放器已完成播放媒体. */ int STATE_ENDED = 4;

player的四种播放状态,分别对应 空闲、缓冲中、准备就绪、播放结束。

回到 setPlayWhenReady方法,总共三行:

第一行,鉴定当前必须是应用程序线程,否则抛异常 Player is accessed on the wrong thread. See 。

第二行,根据playWhenReady,持有或舍弃音频焦点,这个百度这种流氓公司最在行。。。。

/** * Called by the player to abandon or request audio focus based on the desired player state. * 播放器调用以根据所需播放器状态放弃或请求音频焦点。 * * @param playWhenReady The desired value of playWhenReady. * @param playbackState The desired playback state. * @return A {@link PlayerCommand} to execute on the player. */ @PlayerCommand public int updateAudioFocus(boolean playWhenReady, @Player.State int playbackState) { if (shouldAbandonAudioFocus(playbackState)) { abandonAudioFocus(); return playWhenReady ? PLAYER_COMMAND_PLAY_WHEN_READY : PLAYER_COMMAND_DO_NOT_PLAY; } return playWhenReady ? requestAudioFocus() : PLAYER_COMMAND_DO_NOT_PLAY; }

这里又是仨枚举

/** Do not play. */ public static final int PLAYER_COMMAND_DO_NOT_PLAY = -1; /** Do not play now. Wait for callback to play. */ public static final int PLAYER_COMMAND_WAIT_FOR_CALLBACK = 0; /** Play freely. */ public static final int PLAYER_COMMAND_PLAY_WHEN_READY = 1;

一个 不播放、一个等回调控制播放、一个播放。

第三行,根据前两个配置,去实际配置自动播放

public void setPlayWhenReady( boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason, @PlayWhenReadyChangeReason int playWhenReadyChangeReason) { if (playbackInfo.playWhenReady == playWhenReady && playbackInfo.playbackSuppressionReason == playbackSuppressionReason) { return; } pendingOperationAcks++; PlaybackInfo playbackInfo = this.playbackInfo.copyWithPlayWhenReady(playWhenReady, playbackSuppressionReason); internalPlayer.setPlayWhenReady(playWhenReady, playbackSuppressionReason); updatePlaybackInfo( playbackInfo, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, playWhenReadyChangeReason, /* seekProcessed= */ false); }

这里又会发现,实际调用的,依然是 internalPlayer,之后更新playbackInfo信息。

internalPlayer发了条消息

public void setPlayWhenReady( boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason) { handler .obtainMessage(MSG_SET_PLAY_WHEN_READY, playWhenReady ? 1 : 0, playbackSuppressionReason) .sendToTarget(); }

验证了,上一篇的 干活的ExoPlayerImplInternal类基于消息驱动。。。。

收到消息后的处理

private void setPlayWhenReadyInternal( boolean playWhenReady, @PlaybackSuppressionReason int playbackSuppressionReason, boolean operationAck, @Player.PlayWhenReadyChangeReason int reason) throws ExoPlaybackException { playbackInfoUpdate.incrementPendingOperationAcks(operationAck ? 1 : 0); playbackInfoUpdate.setPlayWhenReadyChangeReason(reason); playbackInfo = playbackInfo.copyWithPlayWhenReady(playWhenReady, playbackSuppressionReason); isRebuffering = false; notifyTrackSelectionPlayWhenReadyChanged(playWhenReady); if (!shouldPlayWhenReady()) { stopRenderers(); updatePlaybackPositions(); } else { if (playbackInfo.playbackState == Player.STATE_READY) { startRenderers(); handler.sendEmptyMessage(MSG_DO_SOME_WORK); } else if (playbackInfo.playbackState == Player.STATE_BUFFERING) { handler.sendEmptyMessage(MSG_DO_SOME_WORK); } } }

直接大else,如果状态为ready,那就启动渲染器们发MSG_DO_SOME_WORK,如果是在缓冲,就直接发消息MSG_DO_SOME_WORK.

启动渲染器们

private void startRenderers() throws ExoPlaybackException { isRebuffering = false; mediaClock.start(); for (Renderer renderer : renderers) { Logger.w(TAG,renderer,"是否启用 "+isRendererEnabled(renderer)); if (isRendererEnabled(renderer)) { renderer.start(); } } }

先启动了一个mediaClock,不出意外,还是接口

/** * Default {@link MediaClock} which uses a renderer media clock and falls back to a * {@link StandaloneMediaClock} if necessary。默认的{@link MediaClock}使用渲染器媒体时钟,并在必要时退回到{@link StandaloneMediaClock}。 */ /* package */ final class DefaultMediaClock implements MediaClock {}

这个应该是 跟踪位置的

/** * Tracks the progression of media time.跟踪媒体时间的进度。 */ public interface MediaClock { /** * Returns the current media position in microseconds. */ long getPositionUs(); /** * Attempts to set the playback parameters. The media clock may override the speed if changing the * playback parameters is not supported. * * @param playbackParameters The playback parameters to attempt to set. */ void setPlaybackParameters(PlaybackParameters playbackParameters); /** Returns the active playback parameters. */ PlaybackParameters getPlaybackParameters(); }

启动,内部又启动了一个

/** * Starts the standalone fallback clock.启动独立的后备时钟。 */ public void start() { standaloneClockIsStarted = true; standaloneClock.start(); }

何必呢。。。。

/** * A {@link MediaClock} whose position advances with real time based on the playback parameters when * started.一个{@link MediaClock},其位置在启动时会根据播放参数实时地前进。 */ public final class StandaloneMediaClock implements MediaClock {}

 终于启动了

/** * Starts the clock. Does nothing if the clock is already started. */ public void start() { if (!started) { baseElapsedMs = clock.elapsedRealtime(); started = true; } }

然后这里面的clock,又是个接口。。。。

/**一个接口,通过该接口可以读取系统时钟并创建{@link HandlerWrapper}。 {@link #DEFAULT}实现必须用于所有非测试用例。 * An interface through which system clocks can be read and {@link HandlerWrapper}s created. The * {@link #DEFAULT} implementation must be used for all non-test cases. */ public interface Clock { /** * Default {@link Clock} to use for all non-test cases. */ Clock DEFAULT = new SystemClock(); /** * Returns the current time in milliseconds since the Unix Epoch. * * @see System#currentTimeMillis() */ long currentTimeMillis(); /** @see android.os.SystemClock#elapsedRealtime() */ long elapsedRealtime(); /** @see android.os.SystemClock#uptimeMillis() */ long uptimeMillis(); /** @see android.os.SystemClock#sleep(long) */ void sleep(long sleepTimeMs); /** * Creates a {@link HandlerWrapper} using a specified looper and a specified callback for handling * messages. * * @see Handler#Handler(Looper, Handler.Callback) */ HandlerWrapper createHandler(Looper looper, @Nullable Handler.Callback callback); }

找了一遭,这个clock的实例,最终来源于 SimpleExoPlayer.Builder 的初始化操作。。。。

初始化的时候调用了Clock.DEFAULT,也就是上面的 new SystemClock()....

而SystemClock,又是Clock接口的系统时间实现。。。。

/** * The standard implementation of {@link Clock}, an instance of which is available via {@link * SystemClock#DEFAULT}. */ public class SystemClock implements Clock { protected SystemClock() {} @Override public long currentTimeMillis() { return System.currentTimeMillis(); } @Override public long elapsedRealtime() { return android.os.SystemClock.elapsedRealtime(); } @Override public long uptimeMillis() { return android.os.SystemClock.uptimeMillis(); } @Override public void sleep(long sleepTimeMs) { android.os.SystemClock.sleep(sleepTimeMs); } @Override public HandlerWrapper createHandler(Looper looper, @Nullable Callback callback) { return new SystemHandlerWrapper(new Handler(looper, callback)); } }

大厂就是会玩。。。。

也就是说,启动 渲染器们的时候,先启动了一个SystemClock。。。。

实际调用的是这个,自启动以来的毫秒数

/**
 * Returns milliseconds since boot, including time spent in sleep.
 *
 * @return elapsed milliseconds since boot.
 */
@CriticalNative
native public static long elapsedRealtime();

然后去启动renderers.....

当然,Renderers肯定又是个接口。。。。

/** * 呈现从SampleStream读取的媒体。 * 在内部,渲染器的生命周期由拥有的ExoPlayer管理。 * 随着总体播放状态和启用的轨道发生变化,渲染器会通过各种状态进行转换。 有效状态转换如下所示,并标有每次转换期间调用的方法。 * Renders media read from a {@link SampleStream}. * * <p>Internally, a renderer's lifecycle is managed by the owning {@link ExoPlayer}. The renderer is * transitioned through various states as the overall playback state and enabled tracks change. The * valid state transitions are shown below, annotated with the methods that are called during each * transition. * * <p style="align:center"><img src="doc-files/renderer-states.svg" alt="Renderer state * transitions"> */ public interface Renderer extends PlayerMessage.Target {}

不只是接口,还继承了接口

/** * 定义可以与PlayerMessage.Sender发送并由PlayerMessage.Target接收的播放器消息。 * Defines a player message which can be sent with a {@link Sender} and received by a {@link * Target}. */ public final class PlayerMessage { /** A target for messages. */ public interface Target { /** * Handles a message delivered to the target. * * @param messageType The message type. * @param payload The message payload. * @throws ExoPlaybackException If an error occurred whilst handling the message. Should only be * thrown by targets that handle messages on the playback thread. */ void handleMessage(int messageType, @Nullable Object payload) throws ExoPlaybackException; } /** A sender for messages. */ public interface Sender { /** * Sends a message. * * @param message The message to be sent. */ void sendMessage(PlayerMessage message); } private final Target target; private final Sender sender; private final Clock clock; private final Timeline timeline;

。。。。

}

一个典型的收发结构。。。。

启动

/** * Starts the renderer, meaning that calls to {@link #render(long, long)} will cause media to be * rendered. * <p> * This method may be called when the renderer is in the following states: * {@link #STATE_ENABLED}. * * @throws ExoPlaybackException If an error occurs. */ void start() throws ExoPlaybackException;

Renderer接口,定义了几种状态

/** * The renderer is disabled. A renderer in this state will not proactively acquire resources that * it requires for rendering (e.g., media decoders), but may continue to hold any that it already * has. {@link #reset()} can be called to force the renderer to release such resources. * 渲染器被禁用。 处于此状态的渲染器将不会主动获取渲染所需的资源(例如媒体解码器),但可能会继续保留其已经拥有的资源。 * 可以调用{@link #reset()}来强制渲染器释放此类资源。 */ int STATE_DISABLED = 0; /** * The renderer is enabled but not started. A renderer in this state may render media at the * current position (e.g. an initial video frame), but the position will not advance. A renderer * in this state will typically hold resources that it requires for rendering (e.g. media * decoders). * 渲染器已启用但尚未启动。 在此状态下的渲染器可以在初始视频帧处渲染媒体,但位置不会前进。 * 处于此状态的渲染器通常会保存渲染所需的资源(例如媒体解码器)。 */ int STATE_ENABLED = 1; /** * The renderer is started. Calls to {@link #render(long, long)} will cause media to be rendered. * 渲染器已启动。 调用{@link #render(long,long)}将导致呈现媒体。 */ int STATE_STARTED = 2;

这样,就启动了吧。。。。

setPlayWhenReady(),先去根据参数配置音频焦点,控制音频是否播放。再去发MSG_SET_PLAY_WHEN_READY消息通知给民工 ExoPlayerImplInternal,挂参数是否自动播放。民工收到后,判断当前player的状态是准备就绪还是 缓冲中,如果是准备就绪,就去启动渲染器们,发送MSG_DO_SOME_WORK,如果是缓冲中,就直接发送MSG_DO_SOME_WORK。

启动渲染器们做了两件事,1,启动了系统时间clock,2,遍历启动渲染器们。

按上面说的,还少个do some work....

果然是do some work....

private void doSomeWork() throws ExoPlaybackException, IOException { long operationStartTimeMs = clock.uptimeMillis();//返回自启动以来的毫秒数,不计算深度睡眠所花费的时间。 updatePeriods(); if (playbackInfo.playbackState == Player.STATE_IDLE || playbackInfo.playbackState == Player.STATE_ENDED) { // Remove all messages. Prepare (in case of IDLE) or seek (in case of ENDED) will resume. handler.removeMessages(MSG_DO_SOME_WORK); return; } @Nullable MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); if (playingPeriodHolder == null) { // We're still waiting until the playing period is available. scheduleNextWork(operationStartTimeMs, ACTIVE_INTERVAL_MS); return; } TraceUtil.beginSection("doSomeWork"); //开始了 updatePlaybackPositions(); boolean renderersEnded = true; boolean renderersAllowPlayback = true; if (playingPeriodHolder.prepared) { long rendererPositionElapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; playingPeriodHolder.mediaPeriod.discardBuffer( playbackInfo.positionUs - backBufferDurationUs, retainBackBufferFromKeyframe); for (int i = 0; i < renderers.length; i++) { Renderer renderer = renderers[i]; if (!isRendererEnabled(renderer)) { continue; } // TODO: Each renderer should return the maximum delay before which it wishes to be called // again. The minimum of these values should then be used as the delay before the next // invocation of this method.每个渲染器应该返回希望再次调用的最大延迟。 这些值中的最小值应用作下一次调用此方法之前的延迟。 renderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs); //看着就很核心 renderersEnded = renderersEnded && renderer.isEnded(); // Determine whether the renderer allows playback to continue. Playback can continue if the // renderer is ready or ended. Also continue playback if the renderer is reading ahead into // the next stream or is waiting for the next stream. This is to avoid getting stuck if // tracks in the current period have uneven durations and are still being read by another // renderer. See: https://github.com/google/ExoPlayer/issues/1874. //确定渲染器是否允许继续播放。 如果渲染器准备就绪或结束,则播放可以继续。 // 如果渲染器正在向前阅读下一个流或正在等待下一个流,则还可以继续播放。 // 如果当前时间段中的轨道持续时间不均匀,并且仍被其他渲染器读取,则可以避免卡死。 // 请参阅:https://github.com/google/ExoPlayer/issues/1874。 boolean isReadingAhead = playingPeriodHolder.sampleStreams[i] != renderer.getStream(); boolean isWaitingForNextStream = !isReadingAhead && renderer.hasReadStreamToEnd(); boolean allowsPlayback = isReadingAhead || isWaitingForNextStream || renderer.isReady() || renderer.isEnded(); renderersAllowPlayback = renderersAllowPlayback && allowsPlayback; if (!allowsPlayback) { renderer.maybeThrowStreamError(); } } } else { playingPeriodHolder.mediaPeriod.maybeThrowPrepareError(); } long playingPeriodDurationUs = playingPeriodHolder.info.durationUs; boolean finishedRendering = renderersEnded && playingPeriodHolder.prepared && (playingPeriodDurationUs == C.TIME_UNSET || playingPeriodDurationUs <= playbackInfo.positionUs); if (finishedRendering && pendingPauseAtEndOfPeriod) { pendingPauseAtEndOfPeriod = false; setPlayWhenReadyInternal( /* playWhenReady= */ false, playbackInfo.playbackSuppressionReason, /* operationAck= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM); } if (finishedRendering && playingPeriodHolder.info.isFinal) { setState(Player.STATE_ENDED); stopRenderers();//播放万恒,停止渲染器们 } else if (playbackInfo.playbackState == Player.STATE_BUFFERING && shouldTransitionToReadyState(renderersAllowPlayback)) { setState(Player.STATE_READY); //准备就绪 pendingRecoverableError = null; // Any pending error was successfully recovered from. if (shouldPlayWhenReady()) { startRenderers(); //启动渲染器们 } } else if (playbackInfo.playbackState == Player.STATE_READY && !(enabledRendererCount == 0 ? isTimelineReady() : renderersAllowPlayback)) { isRebuffering = shouldPlayWhenReady(); setState(Player.STATE_BUFFERING); //缓冲中 if (isRebuffering) { notifyTrackSelectionRebuffer(); livePlaybackSpeedControl.notifyRebuffer(); } stopRenderers(); } if (playbackInfo.playbackState == Player.STATE_BUFFERING) { for (int i = 0; i < renderers.length; i++) { if (isRendererEnabled(renderers[i]) && renderers[i].getStream() == playingPeriodHolder.sampleStreams[i]) { renderers[i].maybeThrowStreamError(); } } if (!playbackInfo.isLoading && playbackInfo.totalBufferedDurationUs < 500_000 && isLoadingPossible()) { // Throw if the LoadControl prevents loading even if the buffer is empty or almost empty. We // can't compare against 0 to account for small differences between the renderer position // and buffered position in the media at the point where playback gets stuck. throw new IllegalStateException("Playback stuck buffering and not loading"); } } if (offloadSchedulingEnabled != playbackInfo.offloadSchedulingEnabled) { playbackInfo = playbackInfo.copyWithOffloadSchedulingEnabled(offloadSchedulingEnabled); } boolean sleepingForOffload = false; if ((shouldPlayWhenReady() && playbackInfo.playbackState == Player.STATE_READY) || playbackInfo.playbackState == Player.STATE_BUFFERING) { sleepingForOffload = !maybeScheduleWakeup(operationStartTimeMs, ACTIVE_INTERVAL_MS); } else if (enabledRendererCount != 0 && playbackInfo.playbackState != Player.STATE_ENDED) { scheduleNextWork(operationStartTimeMs, IDLE_INTERVAL_MS); } else { handler.removeMessages(MSG_DO_SOME_WORK); } if (playbackInfo.sleepingForOffload != sleepingForOffload) { playbackInfo = playbackInfo.copyWithSleepingForOffload(sleepingForOffload); } requestForRendererSleep = false; // A sleep request is only valid for the current doSomeWork. TraceUtil.endSection(); }

这个函数,看起来是 播放状态跟渲染器的关系。。。。

doSomeWork1,首先,update Periods

private void updatePeriods() throws ExoPlaybackException, IOException { if (playbackInfo.timeline.isEmpty() || !mediaSourceList.isPrepared()) { // No periods available. return; } maybeUpdateLoadingPeriod(); maybeUpdateReadingPeriod(); maybeUpdateReadingRenderers(); maybeUpdatePlayingPeriod(); }

看起来是更新不同的状态

更新第一个,loading period

private void maybeUpdateLoadingPeriod() throws ExoPlaybackException { queue.reevaluateBuffer(rendererPositionUs); if (queue.shouldLoadNextMediaPeriod()) { @Nullable MediaPeriodInfo info = queue.getNextMediaPeriodInfo(rendererPositionUs, playbackInfo); if (info != null) { MediaPeriodHolder mediaPeriodHolder = queue.enqueueNextMediaPeriodHolder( rendererCapabilities, trackSelector, loadControl.getAllocator(), mediaSourceList, info, emptyTrackSelectorResult); mediaPeriodHolder.mediaPeriod.prepare(this, info.startPositionUs); if (queue.getPlayingPeriod() == mediaPeriodHolder) { resetRendererPosition(mediaPeriodHolder.getStartPositionRendererTime()); } handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); } } if (shouldContinueLoading) { // We should still be loading, except when there is nothing to load or we have fully loaded // the current period. shouldContinueLoading = isLoadingPossible(); updateIsLoading(); } else { maybeContinueLoading(); } }

几个新的实例

/** Holds a {@link MediaPeriod} with information required to play it as part of a timeline. */ /* package */ final class MediaPeriodHolder {}

/** Stores the information required to load and play a {@link MediaPeriod}.存储加载和播放{@link MediaPeriod}所需的信息。 */ /* package */ final class MediaPeriodInfo {}

/** * Defines and provides media to be played by an {@link com.google.android.exoplayer2.ExoPlayer}. A * MediaSource has two main responsibilities: * 定义并提供由{@link com.google.android.exoplayer2.ExoPlayer}播放的媒体。 MediaSource有两个主要职责: * * <ul> * <li>To provide the player with a {@link Timeline} defining the structure of its media, and to * provide a new timeline whenever the structure of the media changes. The MediaSource * provides these timelines by calling {@link MediaSourceCaller#onSourceInfoRefreshed} on the * {@link MediaSourceCaller}s passed to {@link #prepareSource(MediaSourceCaller, * TransferListener)}. * 为播放器提供定义其媒体结构的{@link时间线},并在媒体结构发生变化时提供新的时间线。 * MediaSource通过在传递给{@link #prepareSource(MediaSourceCaller,TransferListener)}的 * {@link MediaSourceCaller}上调用{@link MediaSourceCaller#onSourceInfoRefreshed}来提供这些时间表 * <li>To provide {@link MediaPeriod} instances for the periods in its timeline. MediaPeriods are * obtained by calling {@link #createPeriod(MediaPeriodId, Allocator, long)}, and provide a * way for the player to load and read the media. * 在其时间轴中的时间段内提供{@link MediaPeriod}实例。 * MediaPeriods是通过调用{@link #createPeriod(MediaPeriodId,Allocator,long)}获得的,并为播放器提供了一种加载和读取媒体的方式。 * </ul> * * All methods are called on the player's internal playback thread, as described in the {@link * com.google.android.exoplayer2.ExoPlayer} Javadoc. They should not be called directly from * application code. Instances can be re-used, but only for one {@link * com.google.android.exoplayer2.ExoPlayer} instance simultaneously. */ public interface MediaSource {}

看起来又是一套大玩家。。。。

doSomeWork2,update Playback Positions

直译是更新播放位置,但是写点注释也行啊。。。。

private void updatePlaybackPositions() throws ExoPlaybackException { MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod(); if (playingPeriodHolder == null) { return; } // Update the playback position. long discontinuityPositionUs = playingPeriodHolder.prepared ? playingPeriodHolder.mediaPeriod.readDiscontinuity() : C.TIME_UNSET; if (discontinuityPositionUs != C.TIME_UNSET) { resetRendererPosition(discontinuityPositionUs); // A MediaPeriod may report a discontinuity at the current playback position to ensure the // renderers are flushed. Only report the discontinuity externally if the position changed. //MediaPeriod可能会报告当前播放位置的不连续性,以确保刷新渲染器。 如果位置发生变化,则仅在外部报告不连续性。 if (discontinuityPositionUs != playbackInfo.positionUs) { playbackInfo = handlePositionDiscontinuity( playbackInfo.periodId, discontinuityPositionUs, playbackInfo.requestedContentPositionUs); playbackInfoUpdate.setPositionDiscontinuity(Player.DISCONTINUITY_REASON_INTERNAL); } } else { rendererPositionUs = mediaClock.syncAndGetPositionUs( /* isReadingAhead= */ playingPeriodHolder != queue.getReadingPeriod()); long periodPositionUs = playingPeriodHolder.toPeriodTime(rendererPositionUs); maybeTriggerPendingMessages(playbackInfo.positionUs, periodPositionUs); playbackInfo.positionUs = periodPositionUs; } // Update the buffered position and total buffered duration. MediaPeriodHolder loadingPeriod = queue.getLoadingPeriod(); playbackInfo.bufferedPositionUs = loadingPeriod.getBufferedPositionUs(); playbackInfo.totalBufferedDurationUs = getTotalBufferedDurationUs(); // Adjust live playback speed to new position. if (playbackInfo.playWhenReady && playbackInfo.playbackState == Player.STATE_READY && shouldUseLivePlaybackSpeedControl(playbackInfo.timeline, playbackInfo.periodId) && playbackInfo.playbackParameters.speed == 1f) { float adjustedSpeed = livePlaybackSpeedControl.getAdjustedPlaybackSpeed( getCurrentLiveOffsetUs(), getTotalBufferedDurationUs()); if (mediaClock.getPlaybackParameters().speed != adjustedSpeed) { mediaClock.setPlaybackParameters(playbackInfo.playbackParameters.withSpeed(adjustedSpeed)); handlePlaybackParameters( playbackInfo.playbackParameters, /* currentPlaybackSpeed= */ mediaClock.getPlaybackParameters().speed, /* updatePlaybackInfo= */ false, /* acknowledgeCommand= */ false); } } }

看起来是不停地调用此方法,然后去更新时钟的位置。。。。

doSomeWork3,renderer.render()增量渲染

/** * 增量渲染SampleStream。 * Incrementally renders the {@link SampleStream}. * * <p>If the renderer is in the {@link #STATE_ENABLED} state then each call to this method will do * work toward being ready to render the {@link SampleStream} when the renderer is started. If the * renderer is in the {@link #STATE_STARTED} state then calls to this method will render the * {@link SampleStream} in sync with the specified media positions. * 如果渲染器处于STATE_ENABLED状态,则在启动渲染器时,对此方法的每次调用都将为准备渲染SampleStream做准备。 * 如果渲染器处于STATE_STARTED状态,则对该方法的调用将渲染SampleStream与指定的媒体位置同步。 * * <p>The renderer may also render the very start of the media at the current position (e.g. the * first frame of a video stream) while still in the {@link #STATE_ENABLED} state, unless it's the * initial start of the media after calling {@link #enable(RendererConfiguration, Format[], * SampleStream, long, boolean, boolean, long, long)} with {@code mayRenderStartOfStream} set to * {@code false}. * 渲染器还可以在媒体仍处于STATE_ENABLED状态的情况下,在当前位置(例如视频流的第一帧)渲染媒体的最开始, * 除非它是在调用enable(RendererConfiguration,Format [], * 将mayRenderStartOfStream设置为false的SampleStream,long,boolean,boolean,long,long)。 * * <p>This method should return quickly, and should not block if the renderer is unable to make * useful progress.此方法应快速返回,并且如果渲染器无法取得有用的进展,则不应阻塞。 * * <p>This method may be called when the renderer is in the following states: {@link * #STATE_ENABLED}, {@link #STATE_STARTED}. * 当渲染器处于以下状态时,可以调用此方法:STATE_ENABLED,STATE_STARTED。 * * @param positionUs The current media time in microseconds, measured at the start of the current * iteration of the rendering loop.在渲染循环的当前迭代开始时测量的当前媒体时间(以微秒为单位)。 * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, * measured at the start of the current iteration of the rendering loop. * {@link android.os.SystemClock#elapsedRealtime()}(以微秒为单位),在渲染循环的当前迭代开始时进行测量。 * @throws ExoPlaybackException If an error occurs. */ void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException;

然后就是那一堆子类的实现,这个具体实现涉及编解码的还是留到后面再说。。。。


doSomeWork4,迭代

private void scheduleNextWork(long thisOperationStartTimeMs, long intervalMs) { handler.removeMessages(MSG_DO_SOME_WORK); handler.sendEmptyMessageAtTime(MSG_DO_SOME_WORK, thisOperationStartTimeMs + intervalMs); }

看起来在一定的条件下,又回来了。。。。

又水了一天。。。。


--
senRsl
2021年05月18日11:06:20

没有评论 :

发表评论