自定義Android視訊播放器
Android開發視訊播放器,一般都是使用MediaPlayer+SurfaceView來實現,VideoView也是使用了MediaPlayer+SurfaceView方式(不信看原始碼)。所以,我打算使用MediaPlayer+SurfaceView封裝自己的視訊播放庫。
可以看出SurfaceView佔滿整個螢幕,和match_parent的效果是一樣的,而且視訊被拉伸,嚴重變形!!這顯然不是我想要的。
之前看過VideoView的原始碼,裡面有這種變形的解決辦法。所以,開發視訊播放器,第一步,就是自定義一個適配視訊的SurfaceView。
程式碼如下:
public class VideoSurfaceView extends SurfaceView {
// 視訊寬度
private int videoWidth;
// 視訊高度
private int videoHeight;
public VideoSurfaceView(Context context) {
this(context, null);
}
public VideoSurfaceView(Context context, AttributeSet attrs) {
this(context, attrs, 0 );
}
public VideoSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
videoWidth = 0;
videoHeight = 0;
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
}
/**
* 根據視訊的寬高設定SurfaceView的寬高
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getDefaultSize(videoWidth, widthMeasureSpec);
int height = getDefaultSize(videoHeight, heightMeasureSpec);
if (videoWidth > 0 && videoHeight > 0) {
// 獲取測量模式和測量大小
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
// 分情況設定大小
if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
// layout_width = 確定值或match_parent
// layout_height = 確定值或match_parent
// the size is fixed
width = widthSpecSize;
height = heightSpecSize;
// 做適配,不讓視訊拉伸,保持原來寬高的比例
// for compatibility, we adjust size based on aspect ratio
if ( videoWidth * height < width * videoHeight) {
//Log.i("@@@", "image too wide, correcting");
width = height * videoWidth / videoHeight;
} else if ( videoWidth * height > width * videoHeight) {
//Log.i("@@@", "image too tall, correcting");
height = width * videoHeight / videoWidth;
}
} else if (widthSpecMode == MeasureSpec.EXACTLY) {
// layout_width = 確定值或match_parent
// layout_height = wrap_content
// only the width is fixed, adjust the height to match aspect ratio if possible
width = widthSpecSize;
// 計算高多少,保持原來寬高的比例
height = width * videoHeight / videoWidth;
if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
// couldn't match aspect ratio within the constraints
height = heightSpecSize;
}
} else if (heightSpecMode == MeasureSpec.EXACTLY) {
// layout_width = wrap_content
// layout_height = 確定值或match_parent
// only the height is fixed, adjust the width to match aspect ratio if possible
height = heightSpecSize;
// 計算寬多少,保持原來寬高的比例
width = height * videoWidth / videoHeight;
if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
// couldn't match aspect ratio within the constraints
width = widthSpecSize;
}
} else {
// layout_width = wrap_content
// layout_height = wrap_content
// neither the width nor the height are fixed, try to use actual video size
width = videoWidth;
height = videoHeight;
if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
// too tall, decrease both width and height
height = heightSpecSize;
width = height * videoWidth / videoHeight;
}
if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
// too wide, decrease both width and height
width = widthSpecSize;
height = width * videoHeight / videoWidth;
}
}
} else {
// no size yet, just adopt the given spec sizes
}
// 設定SurfaceView的寬高
setMeasuredDimension(width, height);
}
/**
* 調整大小
* @param videoWidth
* @param videoHeight
*/
public void adjustSize(int videoWidth, int videoHeight) {
if (videoWidth == 0 || videoHeight == 0) return;
// 賦值自己的寬高
this.videoWidth = videoWidth;
this.videoHeight = videoHeight;
// 設定Holder固定的大小
getHolder().setFixedSize(videoWidth, videoHeight);
// 重新設定自己的大小
requestLayout();
}
}
程式碼註釋比較清晰,相信大家看得明白!!
然後需要有個時機去呼叫自定義VideoSurfaceView的adjustSize方法來調整大小,還是參照VideoView原始碼,時機是MediaPlayer監聽到OnVideoSizeChangedListener事件:
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback, SeekBar.OnSeekBarChangeListener,
View.OnClickListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener,
MediaPlayer.OnSeekCompleteListener, MediaPlayer.OnVideoSizeChangedListener {
// 自定義的SurfaceView
private VideoSurfaceView surfaceView;
/**
* 視訊大小改變的時候呼叫
* @param player
* @param width
* @param height
*/
@Override
public void onVideoSizeChanged(MediaPlayer player, int width, int height) {
// 調整SurfaceView的寬高
surfaceView.adjustSize(player.getVideoWidth(), player.getVideoHeight());
}
...
// 初始化MediaPlayer
player = new MediaPlayer();
// 設定視訊大小改變監聽
player.setOnVideoSizeChangedListener(this);
}
效果圖:
如圖,VideoSurfaceView做到了適配視訊的大小。
但是還有個小問題,如果我們一開始把VideoSurfaceView設定高度為wrap_content,當MediaPlayer沒有準備好,視訊還沒有被解析成功時,VideoSurfaceView的高度還是黑黑的全屏,所以我們一開始可以把VideoSurfaceView設為一個固定值。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.johan.video.VideoSurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="250dp"
/>
...
</LinearLayout>
而且注意一點,這個值應該比視訊的高度高一點,為什麼呢?我們分析一下VideoSurfaceView的onMeasure方法:
if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
// layout_width = 確定值或match_parent
// layout_height = 確定值或match_parent
// the size is fixed
width = widthSpecSize;
height = heightSpecSize;
// 做適配,不讓視訊拉伸,保持原來寬高的比例
// for compatibility, we adjust size based on aspect ratio
if ( videoWidth * height < width * videoHeight) {
//Log.i("@@@", "image too wide, correcting");
width = height * videoWidth / videoHeight;
} else if ( videoWidth * height > width * videoHeight) {
//Log.i("@@@", "image too tall, correcting");
height = width * videoHeight / videoWidth;
}
}
如果我們設定的高度比視訊的高度低的話,為了不讓視訊變形,會將width設值為height * videoWidth / videoHeight,也就是width變小,導致SurfaceView不能佔滿整個螢幕的寬度。
當然你也可以把判斷去掉,這樣無論什麼情況都可以佔滿整個螢幕的寬度。不知道之後會出現什麼情況,所以暫時不改這裡的邏輯。
好了,自定義SurfaceView到這裡!!