赞
踩
项目从MediaPlayer迁移到ExoPlayer也有一段时间了.之前看过VideoView的源码,就是对MediaPlayer的一层封装.网上对该类的分析太多了,所以不多赘述了.
当前ExoPlayer已经更新到2.8.1,在此之前,简单播放视频使用SimpleExoPlayerView, 类似于内置的VideoView.不过当前该类已经废弃,改为PlayerView.所以我们基于ExoPlayer V2.8.1对该类进行分析.
public class PlayerView extends FrameLayout {
private static final int SURFACE_TYPE_NONE = 0;
private static final int SURFACE_TYPE_SURFACE_VIEW = 1;
private static final int SURFACE_TYPE_TEXTURE_VIEW = 2;
**private final AspectRatioFrameLayout contentFrame;**
private final View shutterView;
**private final View surfaceView;**
private final ImageView artworkView;
private final SubtitleView subtitleView;
private final @Nullable View bufferingView;
private final @Nullable TextView errorMessageView;
**private final PlayerControlView controller;**
**private final ComponentListener componentListener;**
private final FrameLayout overlayFrameLayout;
**private Player player;**
private boolean useController;
private boolean useArtwork;
private Bitmap defaultArtwork;
private boolean showBuffering;
private boolean keepContentOnPlayerReset;
private @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
private @Nullable CharSequence customErrorMessage;
private int controllerShowTimeoutMs;
private boolean controllerAutoShow;
private boolean controllerHideDuringAds;
private boolean controllerHideOnTouch;
private int textureViewRotation;
}
通过成员变量可以知道,该类主要的有5个成员变量.分别是AspectRatioFrameLayout, surfaceView, PlayerControlView,
ComponentListener, Player;
1.首先是AspectRatioFrameLayout.
可以查看到源码
/**
* Sets the aspect ratio that this view should satisfy.
* 设置视频宽高比例,然后重新布局
* @param widthHeightRatio The width to height ratio.
*/
public void setAspectRatio(float widthHeightRatio) {
if (this.videoAspectRatio != widthHeightRatio) {
this.videoAspectRatio = widthHeightRatio;
requestLayout();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (videoAspectRatio <= 0) {
// Aspect ratio not set.
return;
}
int width = getMeasuredWidth();
int height = getMeasuredHeight();
float viewAspectRatio = (float) width / height;
float aspectDeformation = videoAspectRatio / viewAspectRatio - 1;
//宽高比和目标的宽高比小于0.01f,不用刷新,主要是有一些视频,可能默认是1280 x 720 这样,但是某些机型获取到的回是1280 x 719这样的奇怪宽高
if (Math.abs(aspectDeformation) <= MAX_ASPECT_RATIO_DEFORMATION_FRACTION) {
// We're within the allowed tolerance.
aspectRatioUpdateDispatcher.scheduleUpdate(videoAspectRatio, viewAspectRatio, false);
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;
case RESIZE_MODE_FIT:
if (aspectDeformation > 0) {
height = (int) (width / videoAspectRatio);
} else {
width = (int) (height * videoAspectRatio);
}
break;
case RESIZE_MODE_FILL:
default:
// Ignore target aspect ratio
break;
}
aspectRatioUpdateDispatcher.scheduleUpdate(videoAspectRatio, viewAspectRatio, true);
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
2.surfaceView
我们可以看到
// Create a surface view and insert it into the content frame, if there is one.
if (contentFrame != null && surfaceType != SURFACE_TYPE_NONE) {
ViewGroup.LayoutParams params =
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
surfaceView =
surfaceType == SURFACE_TYPE_TEXTURE_VIEW
? new TextureView(context)
: new SurfaceView(context);
surfaceView.setLayoutParams(params);
contentFrame.addView(surfaceView, 0);
} else {
surfaceView = null;
}
surfaceview在该类里面有两种类型,一种是Surfaceview(GLSurfaceView);一种是TextureView.默认情况下,会使用TextureView.主要是因为TextureView可以做一些矩阵变化之类的操作,而SurfaceView不具备类似的操作.这样的好处在于播放视频衔接的地方,会有
@Override
public void onVideoSizeChanged(
int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
if (contentFrame == null) {
return;
}
float videoAspectRatio =
(height == 0 || width == 0) ? 1 : (width * pixelWidthHeightRatio) / height;
if (surfaceView instanceof TextureView) {
// Try to apply rotation transformation when our surface is a TextureView.
if (unappliedRotationDegrees == 90 || unappliedRotationDegrees == 270) {
// We will apply a rotation 90/270 degree to the output texture of the TextureView.
// In this case, the output video's width and height will be swapped.
videoAspectRatio = 1 / videoAspectRatio;
}
if (textureViewRotation != 0) {
surfaceView.removeOnLayoutChangeListener(this);
}
textureViewRotation = unappliedRotationDegrees;
if (textureViewRotation != 0) {
// The texture view's dimensions might be changed after layout step.
// So add an OnLayoutChangeListener to apply rotation after layout step.
surfaceView.addOnLayoutChangeListener(this);
}
applyTextureViewRotation((TextureView) surfaceView, textureViewRotation);
}
contentFrame.setAspectRatio(videoAspectRatio);
}
我们知道,ExoPlayer支持视频无缝衔接,这也是当初项目之所以选择ExoPlayer的一个原因.在安卓5.0以上的机器,ExoPlayer在解码的时候,会设置视频的角度,那么解码出来的数据已经旋转好角度,我们无需自己再进行翻转.但是对于5.0一下的机器.我们可以看到,对于TextureView.在视频衔接的时候,会默认对视频进行翻转,所以我们无需适配视频的角度问题.但是对于surfaceview,我们就需要自己处理了.更加具体的解释可以看一下issus:
3.PlayerControlView
这只是一个简单的布局,用于控制视频的播放,暂停,快进之类. 我们可以通过在xml的局部文件配置exo_controller.修改控制器的局部文件,但是对应的id不能修改
<declare-styleable name="PlayerControlView">
<attr name="show_timeout"/> //控制器展示时间
<attr name="rewind_increment"/> //回退时间
<attr name="fastforward_increment"/> //快进时间
<attr name="repeat_toggle_modes"/> //播放模式(循环播放/播放单个视频/播放到结尾结束)
<attr name="show_shuffle_button"/> //是否需要自摸
<attr name="controller_layout_id"/>
</declare-styleable>
4.ComponentListener
我们主要关注以下
private final class ComponentListener extends Player.DefaultEventListener
implements TimeBar.OnScrubListener, OnClickListener {
//开始滑动进度条
@Override
public void onScrubStart(TimeBar timeBar, long position) {
removeCallbacks(hideAction);
scrubbing = true;
}
//滑动进度条
@Override
public void onScrubMove(TimeBar timeBar, long position) {
if (positionView != null) {
positionView.setText(Util.getStringForTime(formatBuilder, formatter, position));
}
}
//停止滑动进度条
@Override
public void onScrubStop(TimeBar timeBar, long position, boolean canceled) {
scrubbing = false;
if (!canceled && player != null) {
seekToTimeBarPosition(position);
}
hideAfterTimeout();
}
/**视频状态改变的回调,分别有一下几种状态
//还未初始化的状态
case Player.STATE_IDLE:
//初始化结束,在缓冲,还不能直接播放
case Player.STATE_BUFFERING:
//准备好了,可以直接播放
case Player.STATE_READY:
//播放到视频流结尾
case Player.STATE_ENDED:
**/
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
updatePlayPauseButton();
updateProgress();
}
/**
播放模式(循环播放/播放单个视频/播放到结尾结束)改变
**/
@Override
public void onRepeatModeChanged(int repeatMode) {
updateRepeatModeButton();
updateNavigation();
}
//字幕是否可以改变
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
updateShuffleButton();
updateNavigation();
}
/**
视频的播放位置发生改变的回调
//Seek adjustment due to being unable to seek to the requested position or because the seek was permitted to be inexact.
case DISCONTINUITY_REASON_SEEK_ADJUSTMENT:
break;
//Seek within the current period or to another period.
case DISCONTINUITY_REASON_SEEK:
break;
//Discontinuity to or from an ad within one period in the timeline.
case DISCONTINUITY_REASON_AD_INSERTION:
break;
//Discontinuity introduced internally by the source.
case DISCONTINUITY_REASON_INTERNAL:
break;
//Automatic playback transition from one period in the timeline to the next.
case DISCONTINUITY_REASON_PERIOD_TRANSITION:
DISCONTINUITY_REASON_PERIOD_TRANSITION是在视频连播的状态下,从A视频播放到B视频的时候的回调,这里我们可以处理一些自己的业务.其他状态可以根据自身需要去处理
**/
@Override
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
updateNavigation();
updateProgress();
}
//时间轴改变
@Override
public void onTimelineChanged(
Timeline timeline, Object manifest, @Player.TimelineChangeReason int reason) {
updateNavigation();
updateTimeBarMode();
updateProgress();
}
5.Player
Player自然是指我们外部传进去的ExoPlayer了
分析完,我们会发现,其实PlayerView的逻辑挺简单,即是包装了一层自适应layout的播放器,外部通过设置视频的展示模式(Fit, Full之类)控制视频的展示大小.对应的通过设置PlayerControlView即达到修改ui的效果.但是局限性也比较大.如果需要自身拓展视频的播放的话(例如,添加滤镜之类),不能直接用该类.但是其中的设计思想我们可以借鉴.
最后,欢迎大家给我的项目点一波star,或者关注我一下
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。