本篇博文主要是对播放视频宽高设置的源码进行分析,为了方便讲解,提前也会对EXOPlayer的使用做简单概述。
1.首先我们需要在布局管理器中添加如下xml代码:
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
android:id="@+id/play_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
com.google.android.exoplayer2.ui.SimpleExoPlayerView>
这是exoPlayer的播放控件。
2.创建一个SimpleExoPlayer对象。并把SimpleExoPlayer与SimpleExoPlayerView关联起来。代码如下:
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory trackFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector = new DefaultTrackSelector(trackFactory);
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector);
simpleExoPlayerView.setPlayer(player);
3.最后来创建MediaSource对象,准备播放。
private void preparePlay() {
DefaultBandwidthMeter defaultBandwidthMeter = new DefaultBandwidthMeter();
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(MainActivity.this
, Util.getUserAgent(MainActivity.this, "exoplayer_sample")
, defaultBandwidthMeter);
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
Uri uri = Uri.parse("http://mvvideo11.meitudata.com/59fd64eac92fb5968_H264_3.mp4?k=b953933ba73a3271c9c7d926a0c54fb4&t=5a0a97c9");
MediaSource mediaSource = new ExtractorMediaSource(uri
, dataSourceFactory, extractorsFactory, null, null);
//player.addListener(this);
player.prepare(mediaSource);
}
这里的视频Uri是我从网上找的一个mp4格式的视频,通过EXOplayer的开发者文档我们知道,多媒体的格式和MediaSource实现类的对应关系是:
DASH (DashMediaSource),
SmoothStreaming (SsMediaSource),
HLS (HlsMediaSource),
除此之外其他的格式使用 (ExtractorMediaSource).
通过EXOPLAYER Support Formats 一文我们知道MP4格式应该使用ExtractorMediaSource。
现在我们可以运行程序了(别忘了添加网络权限)。运行效果如下:
控制器是SimpleExoPlayerView自带的一个默认控制器。看到这我们发现一个问题,就是明明我们在布局管理器添加SimpleExoPlayerView控件的时候,设置它的宽高都是match_parent啊,怎么它的宽度并没有填充父布局?
让它充满屏幕的代码很简单,有两种方式。可以用代码实现如下:
simpleExoPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FILL);
或者通过xml文件的属性设置:
app:resize_mode="fill"
设置之后,我们再看一下效果:
通过gif图我们看到,确实,变成了全屏。那我们现在就来分析一下为什么会这样。
我们看关键语句:
simpleExoPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FILL);
看看setResizeMode的内部实现:
public void setResizeMode(@ResizeMode int resizeMode) {
Assertions.checkState(contentFrame != null);
contentFrame.setResizeMode(resizeMode);
}
这里调用了contentFrame的setResizeMode方法,contentFrame是AspectRatioFrameLayout对象。我们来看看AspectRatioFrameLayout里面的setResizeMode方法。
public void setResizeMode(@ResizeMode int resizeMode) {
if (this.resizeMode != resizeMode) {
this.resizeMode = resizeMode;
requestLayout();
}
}
这里是把resizeMode的值传了进来,我们看一下resizeMode值在AspectRatioFrameLayout类中到底起着什么作用吧。
public AspectRatioFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
resizeMode = RESIZE_MODE_FIT;
if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.AspectRatioFrameLayout, 0, 0);
try {
resizeMode = a.getInt(R.styleable.AspectRatioFrameLayout_resize_mode, RESIZE_MODE_FIT);
} finally {
a.recycle();
}
}
}
在AspectRatioFrameLayou的构造方法中,我们看到,resizeMode的默认值是RESIZE_MODE_FIT。这里提到了RESIZE_MODE_FIT,我们就谈谈AspectRatioFrameLayou类中的几种计算视频宽高的模式。
上面呢我演示了设置模式为setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FILL);
的效果,其余的四种效果大家可以自己设置演示,会存在变形的情况哦,所以这玩意还得合理运用。
接下来我们看看resizeMode在哪里使用的,我们看如下代码:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (resizeMode == RESIZE_MODE_FILL || videoAspectRatio <= 0) {
// Aspect ratio not set.
return;
}
int width = getMeasuredWidth();
int height = getMeasuredHeight();
float viewAspectRatio = (float) width / height;
float aspectDeformation = videoAspectRatio / viewAspectRatio - 1;
if (Math.abs(aspectDeformation) <= MAX_ASPECT_RATIO_DEFORMATION_FRACTION) {
// We're within the allowed tolerance.
return;
}
switch (resizeMode) {
case RESIZE_MODE_FIXED_WIDTH:
height = (int) (width / videoAspectRatio);
break;
case RESIZE_MODE_FIXED_HEIGHT:
width = (int) (height * videoAspectRatio);
break;
case RESIZE_MODE_ZOOM:
if (aspectDeformation > 0) {
width = (int) (height * videoAspectRatio);
} else {
height = (int) (width / videoAspectRatio);
}
break;
default:
if (aspectDeformation > 0) {
height = (int) (width / videoAspectRatio);
} else {
width = (int) (height * videoAspectRatio);
}
break;
}
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
这是AspectRatioFrameLayout的onMeasure方法,我们来分析下。
通过代码,我们看到当resizeMode的值为RESIZE_MODE_FILL或者videoAspectRatio<=0时,就return,也就表示它会执行,外面所设置宽高。videoAspectRatio表示想要的视频宽高比。我们接着看,注意到一个if的判断语句。这里的意思是如果想要的宽高比和视频的宽高比的差值如果小于这个临界值MAX_ASPECT_RATIO_DEFORMATION_FRACTION的话,那AspectRatioFrameLayout不需要从新计算他的宽高。这里MAX_ASPECT_RATIO_DEFORMATION_FRACTION=0.01f。
接着我们看switch语句中,对于不同的resizeMode,宽高做了对应的计算。最后把计算的值通过:
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
设置进去。
那最后我们再看下videoAspectRatio这个想要的宽高比是在哪里设置的呢,我们发现在SimpleExoPlayerView中有设置:
@Override
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
float pixelWidthHeightRatio) {
if (contentFrame != null) {
float aspectRatio = height == 0 ? 1 : (width * pixelWidthHeightRatio) / height;
contentFrame.setAspectRatio(aspectRatio);
}
}
onVideoSizeChanged方法是在视频渲染期间视频的size值变化的时候会回调,pixelWidthHeightRatio这个参数表示每一个像素的宽高比。
这里对播放视频的宽高设置源码分析到此结束了,后需还有对EXOPlayer的其他分析。