Exoplayer是一个android平台的播放器,支持格式比android系统的mediaplayer更好,确定性更好,mediaplayer是可以进行厂家定制的,各平台一致性比较差,这里简单介绍一下Exoplayer的最基础的使用接口,方便之后阅读源码
播放器一般分为三部分,获取DataSource,解码以及视输出。因为exoplayer解码基本上是通过android系统mediacode实现的,或者一些分必要的插件实现的,代码比较少,输出包括声音输出以及视频渲染,相对来说业务也不是很复杂,所以资源输入进行了多次抽象。主要分成三层
Uri uri = Uri.parse("/sdcard/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv")
DataSource.Factory dataSourceFactory = new FileDataSource.Factory();
mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory,MatroskaExtractor.FACTORY).
createMediaSource(MediaItem.fromUri(uri));
大概就是传入文件地址,得到mediaitem,然后通过FileDataSource.Factory(),生成FileDataSource去可以读取文件内容,这里的复杂操作都是通过ProgressiveMediaSource通过类的聚合,把FileDataSource、Extractor和MediaItem统一进行调度,实现文件的读取,注意这里ProgressiveMediaSource是针对单个文件的定制类,如果hls或者dash需要特定的mediaSource。
播放的话,直接调用如下:
ExoPlayer player = new ExoPlayer.Builder(getApplicationContext()).build();
player.setMediaSource(mediaSource);
player.prepare();
player.play();
player.setVideoSurface(surface);
大概是这样
在这里我们简单的分析一下ProgressiveMediaSource和player的主要流程,
MediaSource最核心的功能有两个
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
DataSource dataSource = dataSourceFactory.createDataSource();
if (transferListener != null) {
dataSource.addTransferListener(transferListener);
}
return new ProgressiveMediaPeriod(
localConfiguration.uri,
dataSource,
progressiveMediaExtractorFactory.createProgressiveMediaExtractor(),
drmSessionManager,
createDrmEventDispatcher(id),
loadableLoadErrorHandlingPolicy,
createEventDispatcher(id),
this,
allocator,
localConfiguration.customCacheKey,
continueLoadingCheckIntervalBytes);
}
这里只是提供了调用接口,最终交给player获取MediaPeriod,因为这里面包含了MediaExtractor以及DataSource,完全可以解封装出我们需要的东西,这里我们记录一下,这就是mkv的MediaExtractor以及FileDataSource。关于mediasource的阅读到此为止,下部分分析Player相关流程。
Player构造比较简单,主要是通过builder模式实现的
public Builder(Context context) {
this(
context,
() -> new DefaultRenderersFactory(context),
() -> new DefaultMediaSourceFactory(context, new DefaultExtractorsFactory()));
}
private Builder(
Context context,
Supplier<RenderersFactory> renderersFactorySupplier,
Supplier<MediaSourceFactory> mediaSourceFactorySupplier) {
this(
context,
renderersFactorySupplier,
mediaSourceFactorySupplier,
() -> new DefaultTrackSelector(context),
DefaultLoadControl::new,
() -> DefaultBandwidthMeter.getSingletonInstance(context),
/* analyticsCollectorSupplier= */ null);
}
private Builder(
Context context,
Supplier<RenderersFactory> renderersFactorySupplier,
Supplier<MediaSourceFactory> mediaSourceFactorySupplier,
Supplier<TrackSelector> trackSelectorSupplier,
Supplier<LoadControl> loadControlSupplier,
Supplier<BandwidthMeter> bandwidthMeterSupplier,
@Nullable Supplier<AnalyticsCollector> analyticsCollectorSupplier) {
this.context = context;
this.renderersFactorySupplier = renderersFactorySupplier;
this.mediaSourceFactorySupplier = mediaSourceFactorySupplier;
this.trackSelectorSupplier = trackSelectorSupplier;
this.loadControlSupplier = loadControlSupplier;
this.bandwidthMeterSupplier = bandwidthMeterSupplier;
this.analyticsCollectorSupplier =
analyticsCollectorSupplier != null
? analyticsCollectorSupplier
: () -> new AnalyticsCollector(checkNotNull(clock));
looper = Util.getCurrentOrMainLooper();
audioAttributes = AudioAttributes.DEFAULT;
wakeMode = C.WAKE_MODE_NONE;
videoScalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
videoChangeFrameRateStrategy = C.VIDEO_CHANGE_FRAME_RATE_STRATEGY_ONLY_IF_SEAMLESS;
useLazyPreparation = true;
seekParameters = SeekParameters.DEFAULT;
seekBackIncrementMs = C.DEFAULT_SEEK_BACK_INCREMENT_MS;
seekForwardIncrementMs = C.DEFAULT_SEEK_FORWARD_INCREMENT_MS;
livePlaybackSpeedControl = new DefaultLivePlaybackSpeedControl.Builder().build();
clock = Clock.DEFAULT;
releaseTimeoutMs = DEFAULT_RELEASE_TIMEOUT_MS;
detachSurfaceTimeoutMs = DEFAULT_DETACH_SURFACE_TIMEOUT_MS;
}
public ExoPlayer build() {
return buildSimpleExoPlayer();
}
/* package */ SimpleExoPlayer buildSimpleExoPlayer() {
checkState(!buildCalled);
buildCalled = true;
return new SimpleExoPlayer(/* builder= */ this);
}
player =
new ExoPlayerImpl(
renderers,
builder.trackSelectorSupplier.get(),
builder.mediaSourceFactorySupplier.get(),
builder.loadControlSupplier.get(),
builder.bandwidthMeterSupplier.get(),
analyticsCollector,
builder.useLazyPreparation,
builder.seekParameters,
builder.seekBackIncrementMs,
builder.seekForwardIncrementMs,
builder.livePlaybackSpeedControl,
builder.releaseTimeoutMs,
builder.pauseAtEndOfMediaItems,
builder.clock,
builder.looper,
/* wrappingPlayer= */ this,
additionalPermanentAvailableCommands);
}
注意这里是通过builder初始化了比较多的一个default的factory类,主要解决类型定制的问题,这里主要注意一下:
static class MyRenderersFactory implements RenderersFactory{
private final Context context;
MyRenderersFactory(Context context){
this.context = context;
}
@Override
public Renderer[] createRenderers(Handler eventHandler, VideoRendererEventListener videoRendererEventListener,
AudioRendererEventListener audioRendererEventListener, TextOutput textRendererOutput, MetadataOutput metadataRendererOutput) {
MediaCodecVideoRenderer videoRenderer =
new MediaCodecVideoRenderer(
context,
new DefaultMediaCodecAdapterFactory(),
MediaCodecSelector.DEFAULT,
5000,
false,
eventHandler,
videoRendererEventListener,
50);
@Nullable
AudioSink audioSink =
new DefaultAudioSink(AudioCapabilities.getCapabilities(context), new DefaultAudioSink.DefaultAudioProcessorChain(),
true, true, DefaultAudioSink.OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED);
MediaCodecAudioRenderer audioRenderer =
new MediaCodecAudioRenderer(
context,
new DefaultMediaCodecAdapterFactory(),
MediaCodecSelector.DEFAULT,
false,
eventHandler,
audioRendererEventListener,
audioSink);
Renderer[] renderers = {videoRenderer,audioRenderer};
return renderers;
}
}
这里比较明显的就是提供一个render列表。
2. 核心是:MediaSourceList
3. 核心是: MediaPeriodQueue
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();
}
其实就是通过handler处理消息,以及把mediasource,传入
关于如何读取data。以及如何解封装,相关调用比较复杂,不过在我们场景下,就是通过ProgressiveMediaPeriod获取解封装后的数据,解码器中,大概代码如下:
/* package */ int readData(
int sampleQueueIndex,
FormatHolder formatHolder,
DecoderInputBuffer buffer,
@ReadFlags int readFlags) {
int result =
sampleQueues[sampleQueueIndex].read(formatHolder, buffer, readFlags, loadingFinished);
return result;
}
然后通知解码器去处理解码,
其实整体是有两个线程,一个是mediasource负责读取解封装,一个是负责解码渲染。我们大概介绍一下.核心类就是MediaDataSource。具体类图如下:
其核心就是被ExoPlayerImplInternal控制,进行必要的初始化,然后在调用prepare后,启动loader,进行自治,实现自动加载内容,最终把数据缓存在SampleQueue中,被render读取,大概流程
就是ExoPlayerImplInternal调用MediaPerid中的startloading。启动一个loader。然后自治加载解封装内容,大概流程如下:
关于externalLoadable的加载其实也相对比较复杂,但是大概内容就是通过dataSource加载内容,通过Extractor解封装,传入ExtractorOutput,最终保存到sampleQueues中,具体流程这里就不在详细介绍。
我们把目光关注render中的解码以及输出,
这里大概介绍了一下exoplayer的架构,文中跳过不少过程细节,以及状态判断,但是整体流程基本保留,有机会重新整理一下。