東川印記

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

Exoplayer学习04 从布局绑定到prepare doSomeWork

2021年5月20日星期四



根据昨日的测试结果,后续,就可以去通过光纤线来做双声道上变换六声道再压缩成AC3编码了。。。。

继续

player = new SimpleExoPlayer.Builder(/* context= */ this, renderersFactory) .build(); player.setPlayWhenReady(startAutoPlay); playerView.setPlayer(player); player.setMediaItems(mediaItems, /* resetPosition= */ !haveStartPosition); player.prepare();

第三行就好理解了,绑定player到view

1,绑定player到布局view

/** * {@link Player}媒体播放的高级视图。 它在播放期间显示视频,字幕和专辑封面,并使用{@link StyledPlayerControlView}显示播放控件。 * <p>可以通过设置属性(或调用相应的方法),覆盖可绘制对象,覆盖视图的布局文件或指定自定义视图布局文件来自定义StyledPlayerView。 * A high level view for {@link Player} media playbacks. It displays video, subtitles and album art * during playback, and displays playback controls using a {@link StyledPlayerControlView}. * * <p>A StyledPlayerView can be customized by setting attributes (or calling corresponding methods), * overriding drawables, overriding the view's layout file, or by specifying a custom view layout * file. * * <h3>Attributes</h3> * * 。。。。 */ public class StyledPlayerView extends FrameLayout implements AdsLoader.AdViewProvider {}

这个样式播放布局,继承自帧布局,实现了广告加载接口

/** Provides information about views for the ad playback UI提供有关广告播放界面的视图的信息. */ interface AdViewProvider {}

然后去给布局绑定播放组件

/** * Set the {@link Player} to use. * 设置播放器使用。 * 要将Player从一个视图定位到另一个视图,建议使用switchTargetView(Player,StyledPlayerView,StyledPlayerView)而不是此方法。 * 如果您确实希望直接使用此方法,请确保在调用setPlayer(null)将播放器与旧视图分离之前,将播放器附加到新视图。 * 此排序效率显着提高,并且可以实现更无缝的过渡。 * * 参数: * player –要使用的播放器,或者为null以分离当前播放器。 * 仅支持在主线程上访问的播放器(player.getApplicationLooper()== Looper.getMainLooper())。 * * <p>To transition a {@link Player} from targeting one view to another, it's recommended to use * {@link #switchTargetView(Player, StyledPlayerView, StyledPlayerView)} rather than this method. * If you do wish to use this method directly, be sure to attach the player to the new view * <em>before</em> calling {@code setPlayer(null)} to detach it from the old one. This ordering is * significantly more efficient and may allow for more seamless transitions. * * @param player The {@link Player} to use, or {@code null} to detach the current player. Only * players which are accessed on the main thread are supported ({@code * player.getApplicationLooper() == Looper.getMainLooper()}). */ public void setPlayer(@Nullable Player player) { Assertions.checkState(Looper.myLooper() == Looper.getMainLooper()); Assertions.checkArgument( player == null || player.getApplicationLooper() == Looper.getMainLooper()); if (this.player == player) { return; } @Nullable Player oldPlayer = this.player; if (oldPlayer != null) { oldPlayer.removeListener(componentListener); @Nullable Player.VideoComponent oldVideoComponent = oldPlayer.getVideoComponent(); if (oldVideoComponent != null) { oldVideoComponent.removeVideoListener(componentListener); if (surfaceView instanceof TextureView) { oldVideoComponent.clearVideoTextureView((TextureView) surfaceView); } else if (surfaceView instanceof SphericalGLSurfaceView) { ((SphericalGLSurfaceView) surfaceView).setVideoComponent(null); } else if (surfaceView instanceof SurfaceView) { oldVideoComponent.clearVideoSurfaceView((SurfaceView) surfaceView); } } @Nullable Player.TextComponent oldTextComponent = oldPlayer.getTextComponent(); if (oldTextComponent != null) { oldTextComponent.removeTextOutput(componentListener); } } if (subtitleView != null) { subtitleView.setCues(null); } this.player = player; if (useController()) { controller.setPlayer(player); } updateBuffering(); updateErrorMessage(); updateForCurrentTrackSelections(/* isNewPlayer= */ true); if (player != null) { @Nullable Player.VideoComponent newVideoComponent = player.getVideoComponent(); if (newVideoComponent != null) { if (surfaceView instanceof TextureView) { newVideoComponent.setVideoTextureView((TextureView) surfaceView); } else if (surfaceView instanceof SphericalGLSurfaceView) { ((SphericalGLSurfaceView) surfaceView).setVideoComponent(newVideoComponent); } else if (surfaceView instanceof SurfaceView) { newVideoComponent.setVideoSurfaceView((SurfaceView) surfaceView); } newVideoComponent.addVideoListener(componentListener); } @Nullable Player.TextComponent newTextComponent = player.getTextComponent(); if (newTextComponent != null) { newTextComponent.addTextOutput(componentListener); if (subtitleView != null) { subtitleView.setCues(newTextComponent.getCurrentCues()); } } player.addListener(componentListener); maybeShowController(false); } else { hideController(); } }

中途换帅难道不是兵家大忌吗?不讲武德啊。。。。

根据传入的新player,分别更新Player接口下的几个Component,更新了Controller(view),最后maybeShowController。

/** * A view for controlling {@link Player} instances.用于控制{@link Player}实例的视图。 * * <p>A StyledPlayerControlView can be customized by setting attributes (or calling corresponding * methods), overriding drawables, overriding the view's layout file, or by specifying a custom view * layout file.可以通过设置属性(或调用相应的方法),覆盖可绘制对象, * 覆盖视图的布局文件或指定自定义视图布局文件来自定义StyledPlayerControlView。 * * 。。。 */ public class StyledPlayerControlView extends FrameLayout {}


2,添加播放列表

传入的是 list mediaItems,转换成了mediaSource.

@Override public void setMediaItems(List<MediaItem> mediaItems, boolean resetPosition) { setMediaSources(createMediaSources(mediaItems), resetPosition); }

直接循环转换

private List<MediaSource> createMediaSources(List<MediaItem> mediaItems) { List<MediaSource> mediaSources = new ArrayList<>(); for (int i = 0; i < mediaItems.size(); i++) { mediaSources.add(mediaSourceFactory.createMediaSource(mediaItems.get(i))); } return mediaSources; }

明明是Factory也变成了接口

/** * Factory for creating {@link MediaSource}s from URIs. * * <h3>DrmSessionManager creation for protected content</h3> * * <p>In case a {@link DrmSessionManager} is passed to {@link * #setDrmSessionManager(DrmSessionManager)}, it will be used regardless of the drm configuration of * the media item. * * <p>For a media item with a {@link MediaItem.DrmConfiguration}, a {@link DefaultDrmSessionManager} * is created based on that configuration. The following setter can be used to optionally configure * the creation: * * <ul> * <li>{@link #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}: Sets the data source factory * to be used by the {@link HttpMediaDrmCallback} for network requests (default: {@link * DefaultHttpDataSourceFactory}). * </ul> */ public interface MediaSourceFactory {}

还可以进行Drm....

/** * Creates a new {@link MediaSource} with the specified {@link MediaItem}. * * @param mediaItem The media item to play. * @return The new {@link MediaSource media source}. */ MediaSource createMediaSource(MediaItem mediaItem);

果然CRUD的逻辑最简单。。。。

转换成了MediaSource,依然是接口

/** * 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 {}

这个mediaSourceFactory接口,果然来自于 SimpleExoPlayer.Builder创建的default.....

new DefaultMediaSourceFactory(context, extractorsFactory)

参数extractorsFactory又来自于

new DefaultExtractorsFactory()

提取器extractors

/** * 一个{@link ExtractorsFactory},它提供以下格式的提取器数组: * An {@link ExtractorsFactory} that provides an array of extractors for the following formats: * * <ul> * <li>MP4, including M4A ({@link Mp4Extractor}) * <li>fMP4 ({@link FragmentedMp4Extractor}) * <li>Matroska and WebM ({@link MatroskaExtractor}) * <li>Ogg Vorbis/FLAC ({@link OggExtractor} * <li>MP3 ({@link Mp3Extractor}) * <li>AAC ({@link AdtsExtractor}) * <li>MPEG TS ({@link TsExtractor}) * <li>MPEG PS ({@link PsExtractor}) * <li>FLV ({@link FlvExtractor}) * <li>WAV ({@link WavExtractor}) * <li>AC3 ({@link Ac3Extractor}) * <li>AC4 ({@link Ac4Extractor}) * <li>AMR ({@link AmrExtractor}) * <li>FLAC * <ul> * <li>If available, the FLAC extension's {@code * com.google.android.exoplayer2.ext.flac.FlacExtractor} is used. * <li>Otherwise, the core {@link FlacExtractor} is used. Note that Android devices do not * generally include a FLAC decoder before API 27. This can be worked around by using * the FLAC extension or the FFmpeg extension. * </ul> * <li>JPEG ({@link JpegExtractor}) * </ul> */ public final class DefaultExtractorsFactory implements ExtractorsFactory {}

同样的,定义于接口

/** Factory for arrays of {@link Extractor} instances. */ public interface ExtractorsFactory { /** * Extractor factory that returns an empty list of extractors. Can be used whenever {@link * Extractor Extractors} are not required. */ ExtractorsFactory EMPTY = () -> new Extractor[] {}; /** Returns an array of new {@link Extractor} instances. */ Extractor[] createExtractors(); /** * Returns an array of new {@link Extractor} instances. * * @param uri The {@link Uri} of the media to extract. * @param responseHeaders The response headers of the media to extract, or an empty map if there * are none. The map lookup should be case-insensitive. * @return The {@link Extractor} instances. */ default Extractor[] createExtractors(Uri uri, Map<String, List<String>> responseHeaders) { return createExtractors(); } }

回到 setMediaItems,转换成了list mediaSource,然后给了 list media source holder,设置holder给了player的源

private void setMediaSourcesInternal( List<MediaSource> mediaSources, int startWindowIndex, long startPositionMs, boolean resetToDefaultPosition) { int currentWindowIndex = getCurrentWindowIndexInternal(); long currentPositionMs = getCurrentPosition(); pendingOperationAcks++; if (!mediaSourceHolderSnapshots.isEmpty()) { removeMediaSourceHolders( /* fromIndex= */ 0, /* toIndexExclusive= */ mediaSourceHolderSnapshots.size()); } List<MediaSourceList.MediaSourceHolder> holders = addMediaSourceHolders(/* index= */ 0, mediaSources); Timeline timeline = createMaskingTimeline(); if (!timeline.isEmpty() && startWindowIndex >= timeline.getWindowCount()) { throw new IllegalSeekPositionException(timeline, startWindowIndex, startPositionMs); } // Evaluate the actual start position. if (resetToDefaultPosition) { startWindowIndex = timeline.getFirstWindowIndex(shuffleModeEnabled); startPositionMs = C.TIME_UNSET; } else if (startWindowIndex == C.INDEX_UNSET) { startWindowIndex = currentWindowIndex; startPositionMs = currentPositionMs; } PlaybackInfo newPlaybackInfo = maskTimelineAndPosition( playbackInfo, timeline, getPeriodPositionOrMaskWindowPosition(timeline, startWindowIndex, startPositionMs)); // Mask the playback state. int maskingPlaybackState = newPlaybackInfo.playbackState; if (startWindowIndex != C.INDEX_UNSET && newPlaybackInfo.playbackState != STATE_IDLE) { // Position reset to startWindowIndex (results in pending initial seek). if (timeline.isEmpty() || startWindowIndex >= timeline.getWindowCount()) { // Setting an empty timeline or invalid seek transitions to ended. maskingPlaybackState = STATE_ENDED; } else { maskingPlaybackState = STATE_BUFFERING; } } newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(maskingPlaybackState); internalPlayer.setMediaSources( holders, startWindowIndex, C.msToUs(startPositionMs), shuffleOrder); updatePlaybackInfo( newPlaybackInfo, /* positionDiscontinuity= */ false, /* ignored */ Player.DISCONTINUITY_REASON_INTERNAL, /* timelineChangeReason= */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* seekProcessed= */ false); }

holder的实现

/** Data class to hold playlist media sources together with meta data needed to process them.数据类,用于保存播放列表媒体源以及处理它们所需的元数据 */ /* package */ static final class MediaSourceHolder implements MediaSourceInfoHolder {}

/** A holder of information about a {@link MediaSource}. */ /* package */ interface MediaSourceInfoHolder { /** Returns the uid of the {@link MediaSourceList.MediaSourceHolder}. */ Object getUid(); /** Returns the timeline. */ Timeline getTimeline(); }

设置给holder后,还创建了播放时间线timeline

/** * A flexible representation of the structure of media. A timeline is able to represent the * structure of a wide variety of media, from simple cases like a single media file through to * complex compositions of media such as playlists and streams with inserted ads. Instances are * immutable. For cases where media is changing dynamically (e.g. live streams), a timeline provides * a snapshot of the current state. * * 媒体结构的灵活表示。时间线能够代表各种媒体的结构,从简单的情况(例如单个媒体文件)到复杂的媒体组成(例如播放列表和带有插入广告的流)。 * 实例是不可变的。对于媒体动态变化的情况(例如,实时流),时间线可提供当前状态的快照。 * 时间线由Windows和句点组成。 * 一个Timeline.Window通常对应一个播放列表项。它可能跨越一个或多个时间段,并定义了当前可用于播放的那些时间段内的区域。 * 窗口还提供其他信息,例如,窗口内是否支持搜索以及默认位置,默认位置是播放器开始播放窗口时从其开始播放的位置。 * Timeline.Period定义单个逻辑媒体,例如媒体文件。它还可以定义插入媒体的广告组,以及有关这些广告是否已加载和播放的信息。 * 以下示例说明了各种用例的时间表。 * 单个媒体文件或点播流 * * 单个媒体文件或点播流的时间轴由单个时段和窗口组成。该窗口跨越整个周期,指示媒体的所有部分都可以播放。窗口的默认位置通常在时间段的开始(由上图中的黑点表示)。 * 媒体文件或点播流的播放列表 * * 媒体文件或点播流播放列表的时间轴包含多个时段,每个时段都有其自己的窗口。每个窗口跨越整个相应的期间,通常在该期间的开始处具有默认位置。周期和窗口的属性(例如它们的持续时间以及窗口是否可搜索)通常仅在播放器开始缓冲相应的文件或流时才知道。 * 直播能力有限 * * 实时流的时间轴由一个持续时间未知的时段组成,因为随着广播更多内容,该持续时间会不断延长。如果内容仅在有限的时间段内保持可用,则窗口可能会在非零位置开始,从而定义仍可以播放的内容区域。该窗口将从Timeline.Window.isLive()返回true,以指示它是实时流,并且只要我们期望对实时窗口进行更改,Timeline.Window.isDynamic将设置为true。它的默认位置通常靠近活动边缘(由上图中的黑点表示)。 * 具有无限可用性的实时流 * * 具有无限可用性的实时流的时间线与具有有限可用性的实时流的时间线相似,不同之处在于窗口从时段开始时开始,以指示仍可以播放所有先前广播的内容。 * 多个时段的即时串流 * * 当实况流明确地划分为单独的时间段时(例如在内容边界处),就会出现这种情况。这种情况类似于“具有有限可用性的实时流”情况,不同之处在于窗口可能跨越一个以上的时间段。在无限可用性的情况下,也可能有多个期间。 * 点播流和直播流 * * 这种情况是“单个”媒体文件或点播流和带有多个句点的实时流的串联。当点播流的回放结束时,实时流的回放将从其在实时边缘附近的默认位置开始。 * 带有插播广告的点播流 * * 这种情况包括插播广告组,它们被定义为时间轴的单个时间段的一部分。可以查询该期间以获取有关广告组及其包含的广告的信息。 * ExoPlayer.library-common * * <p>A timeline consists of {@link Window Windows} and {@link Period Periods}. * * <ul> * <li>A {@link Window} usually corresponds to one playlist item. It may span one or more periods * and it defines the region within those periods that's currently available for playback. The * window also provides additional information such as whether seeking is supported within the * window and the default position, which is the position from which playback will start when * the player starts playing the window. * <li>A {@link Period} defines a single logical piece of media, for example a media file. It may * also define groups of ads inserted into the media, along with information about whether * those ads have been loaded and played. * </ul> * * <p>The following examples illustrate timelines for various use cases. * * <h3 id="single-file">Single media file or on-demand stream</h3> * * <p style="align:center"><img src="doc-files/timeline-single-file.svg" alt="Example timeline for a * single file"> * * <p>A timeline for a single media file or on-demand stream consists of a single period and window. * The window spans the whole period, indicating that all parts of the media are available for * playback. The window's default position is typically at the start of the period (indicated by the * black dot in the figure above). * * <h3>Playlist of media files or on-demand streams</h3> * * <p style="align:center"><img src="doc-files/timeline-playlist.svg" alt="Example timeline for a * playlist of files"> * * <p>A timeline for a playlist of media files or on-demand streams consists of multiple periods, * each with its own window. Each window spans the whole of the corresponding period, and typically * has a default position at the start of the period. The properties of the periods and windows * (e.g. their durations and whether the window is seekable) will often only become known when the * player starts buffering the corresponding file or stream. * * <h3 id="live-limited">Live stream with limited availability</h3> * * <p style="align:center"><img src="doc-files/timeline-live-limited.svg" alt="Example timeline for * a live stream with limited availability"> * * <p>A timeline for a live stream consists of a period whose duration is unknown, since it's * continually extending as more content is broadcast. If content only remains available for a * limited period of time then the window may start at a non-zero position, defining the region of * content that can still be played. The window will return true from {@link Window#isLive()} to * indicate it's a live stream and {@link Window#isDynamic} will be set to true as long as we expect * changes to the live window. Its default position is typically near to the live edge (indicated by * the black dot in the figure above). * * <h3>Live stream with indefinite availability</h3> * * <p style="align:center"><img src="doc-files/timeline-live-indefinite.svg" alt="Example timeline * for a live stream with indefinite availability"> * * <p>A timeline for a live stream with indefinite availability is similar to the <a * href="#live-limited">Live stream with limited availability</a> case, except that the window * starts at the beginning of the period to indicate that all of the previously broadcast content * can still be played. * * <h3 id="live-multi-period">Live stream with multiple periods</h3> * * <p style="align:center"><img src="doc-files/timeline-live-multi-period.svg" alt="Example timeline * for a live stream with multiple periods"> * * <p>This case arises when a live stream is explicitly divided into separate periods, for example * at content boundaries. This case is similar to the <a href="#live-limited">Live stream with * limited availability</a> case, except that the window may span more than one period. Multiple * periods are also possible in the indefinite availability case. * * <h3>On-demand stream followed by live stream</h3> * * <p style="align:center"><img src="doc-files/timeline-advanced.svg" alt="Example timeline for an * on-demand stream followed by a live stream"> * * <p>This case is the concatenation of the <a href="#single-file">Single media file or on-demand * stream</a> and <a href="#multi-period">Live stream with multiple periods</a> cases. When playback * of the on-demand stream ends, playback of the live stream will start from its default position * near the live edge. * * <h3 id="single-file-midrolls">On-demand stream with mid-roll ads</h3> * * <p style="align:center"><img src="doc-files/timeline-single-file-midrolls.svg" alt="Example * timeline for an on-demand stream with mid-roll ad groups"> * * <p>This case includes mid-roll ad groups, which are defined as part of the timeline's single * period. The period can be queried for information about the ad groups and the ads they contain. */ public abstract class Timeline {}

层层继承

createMaskingTimeline方法 new final class PlaylistTimeline extends adb class  AbstractConcatenatedTimeline extends abs class Timeline.

最终设置给了internalPlayer

internalPlayer.setMediaSources( holders, startWindowIndex, C.msToUs(startPositionMs), shuffleOrder);

internalPlayer就是个傀儡,发了个MSG_SET_MEDIA_SOURCES消息。。。。

public void setMediaSources( List<MediaSourceList.MediaSourceHolder> mediaSources, int windowIndex, long positionUs, ShuffleOrder shuffleOrder) { handler .obtainMessage( MSG_SET_MEDIA_SOURCES, new MediaSourceListUpdateMessage(mediaSources, shuffleOrder, windowIndex, positionUs)) .sendToTarget(); }

收到消息后,各种更新操作

private void setMediaItemsInternal(MediaSourceListUpdateMessage mediaSourceListUpdateMessage) throws ExoPlaybackException { playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1); if (mediaSourceListUpdateMessage.windowIndex != C.INDEX_UNSET) { pendingInitialSeekPosition = new SeekPosition( new PlaylistTimeline( mediaSourceListUpdateMessage.mediaSourceHolders, mediaSourceListUpdateMessage.shuffleOrder), mediaSourceListUpdateMessage.windowIndex, mediaSourceListUpdateMessage.positionUs); } Timeline timeline = mediaSourceList.setMediaSources( mediaSourceListUpdateMessage.mediaSourceHolders, mediaSourceListUpdateMessage.shuffleOrder); handleMediaSourceListInfoRefreshed(timeline); }

private void handleMediaSourceListInfoRefreshed(Timeline timeline) throws ExoPlaybackException { PositionUpdateForPlaylistChange positionUpdate = 。。。 MediaPeriodId newPeriodId = positionUpdate.periodId; 。。。 try { 。。。 } finally { updateLivePlaybackSpeedControl( /* newTimeline= */ timeline, newPeriodId, /* oldTimeline= */ playbackInfo.timeline, /* oldPeriodId= */ playbackInfo.periodId, /* positionForTargetOffsetOverrideUs */ positionUpdate.setTargetLiveOffset ? newPositionUs : C.TIME_UNSET); 。。。 resetPendingPauseAtEndOfPeriod(); resolvePendingMessagePositions( /* newTimeline= */ timeline, /* previousTimeline= */ playbackInfo.timeline); playbackInfo = playbackInfo.copyWithTimeline(timeline); if (!timeline.isEmpty()) { // Retain pending seek position only while the timeline is still empty. pendingInitialSeekPosition = null; } handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false); } }

private void handleLoadingMediaPeriodChanged(boolean loadingTrackSelectionChanged) { MediaPeriodHolder loadingMediaPeriodHolder = queue.getLoadingPeriod(); MediaPeriodId loadingMediaPeriodId = loadingMediaPeriodHolder == null ? playbackInfo.periodId : loadingMediaPeriodHolder.info.id; 。。。 if ((loadingMediaPeriodChanged || loadingTrackSelectionChanged) && loadingMediaPeriodHolder != null && loadingMediaPeriodHolder.prepared) { updateLoadControlTrackSelection( loadingMediaPeriodHolder.getTrackGroups(), loadingMediaPeriodHolder.getTrackSelectorResult()); } }

private void updateLoadControlTrackSelection( TrackGroupArray trackGroups, TrackSelectorResult trackSelectorResult) { loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections); }

而这个loadControl,当然又是个接口

/** Controls buffering of media. */ public interface LoadControl {}

实例,当然来自于 SimpleExoPlayer.Builder.....

new DefaultLoadControl()

一个很普通的实现

/** The default {@link LoadControl} implementation. */ public class DefaultLoadControl implements LoadControl {

所以,做了什么?

传入了list media item,然后转换成了list media source,封装转换成了list media source holder.

holder用来维护需要播放的媒体及对应的时间线、元数据,然后传递给了民工internalPlayer,发了个MSG_SET_MEDIA_SOURCES的消息。

internalPlayer收到消息后,进行了一系列的更新操作。


3,准备播放

准备妥当,就可以进行最后的斗争了。。。。

@Override public void prepare() { verifyApplicationThread(); boolean playWhenReady = getPlayWhenReady(); @AudioFocusManager.PlayerCommand int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, Player.STATE_BUFFERING); updatePlayWhenReady( playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playWhenReady, playerCommand)); player.prepare(); }

取配置,是否准备妥当自动播放,配置来自于playbackInfo

@Override public boolean getPlayWhenReady() { return playbackInfo.playWhenReady; }

定义

/** Whether playback should proceed when {@link #playbackState} == {@link Player#STATE_READY}. */ public final boolean playWhenReady;

定义于playbackInfo类

/** * Information about an ongoing playback.有关正在进行的播放的信息。 */ /* package */ final class PlaybackInfo {}

拿到了是否准备就绪自动播放的结果后,就去更新audio 焦点及视频,这两行跟前面03篇setPlayWhenReady()一致。。。。

然后就执行prepare了。。。。

@Override public void prepare() { if (playbackInfo.playbackState != Player.STATE_IDLE) { return; } PlaybackInfo playbackInfo = this.playbackInfo.copyWithPlaybackError(null); playbackInfo = playbackInfo.copyWithPlaybackState( playbackInfo.timeline.isEmpty() ? Player.STATE_ENDED : Player.STATE_BUFFERING); // Trigger internal prepare first before updating the playback info and notifying external // listeners to ensure that new operations issued in the listener notifications reach the // player after this prepare. The internal player can't change the playback info immediately // because it uses a callback. // 在更新播放信息之前,先触发内部准备,然后通知外部侦听器,以确保在此准备之后, // 侦听器通知中发出的新操作会到达播放器。 内部播放器无法立即更改播放信息,因为它使用了回调 pendingOperationAcks++; internalPlayer.prepare(); updatePlaybackInfo( playbackInfo, /* positionDiscontinuity= */ false, /* ignored */ DISCONTINUITY_REASON_INTERNAL, /* ignored */ TIMELINE_CHANGE_REASON_SOURCE_UPDATE, /* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, /* seekProcessed= */ false); }

调用了 internalPlayer.prepare()

public void prepare() { handler.obtainMessage(MSG_PREPARE).sendToTarget(); }

实际就是发了个 MSG_PREPARE消息

private void prepareInternal() { playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1); resetInternal( /* resetRenderers= */ false, /* resetPosition= */ false, /* releaseMediaSourceList= */ false, /* resetError= */ true); loadControl.onPrepared(); setState(playbackInfo.timeline.isEmpty() ? Player.STATE_ENDED : Player.STATE_BUFFERING); mediaSourceList.prepare(bandwidthMeter.getTransferListener()); handler.sendEmptyMessage(MSG_DO_SOME_WORK); }

控制loadControl、mediaSourceList去prepare操作.

loadControl更新当前新资源加载进来。

mediaSourceList更新准备播放列表

/** * 连接多个MediaSource。 可以在播放期间修改MediaSource的列表。 同一MediaSource实例在播放列表中多次出现是有效的。 * 除构造函数外,所有方法均在播放线程上调用。 * Concatenates multiple {@link MediaSource}s. The list of {@link MediaSource}s can be modified * during playback. It is valid for the same {@link MediaSource} instance to be present more than * once in the playlist. * * <p>With the exception of the constructor, all methods are called on the playback thread. */ /* package */ final class MediaSourceList {}

准备播放列表

/** Prepares the playlist准备播放列表. */ public void prepare(@Nullable TransferListener mediaTransferListener) { Assertions.checkState(!isPrepared); this.mediaTransferListener = mediaTransferListener; for (int i = 0; i < mediaSourceHolders.size(); i++) { MediaSourceHolder mediaSourceHolder = mediaSourceHolders.get(i); prepareChildSource(mediaSourceHolder); enabledMediaSourceHolders.add(mediaSourceHolder); } isPrepared = true; }

最后,发送了一个MSG_DO_SOME_WORK消息。。。。

这个就跟之前03的doSomeWork一样了。。。。


--
senRsl
2021年05月20日10:47:18

没有评论 :

发表评论