1. 程式人生 > >自定義Android視訊播放器

自定義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到這裡!!