Android多媒體之視訊播放器(基於MediaPlayer)

張風捷特烈發表於2019-03-09

零、前言

對於視訊的播放,Android有內建的VideoView,用起來非常簡單
本篇從自定義VideoView來封裝MediaPlayer開始說起

<VideoView
    android:id="@+id/id_vv"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

---->[使用:PlayerActivity.kt]------------------------------------------------
id_vv.setMediaController(MediaController(this))
id_vv.setVideoPath("/sdcard/toly/sh.mp4")
複製程式碼

本文聚焦
[1].自定義VideoView結合SurfaceView和MediaPlayer來播放視訊
[2].使用媒體庫的ContentProvider查詢手機中視訊,並列表顯示
[3].更改視訊的寬高以及適應橫豎屏切換
[4].自定義控制介面以及倍速播放
[5].視訊封面圖(視訊幀)的獲取
[6].播放網路視訊及seekBar的第二進度和快取進度監聽
複製程式碼

一、簡易版:MediaPlayer + SurfaceView + MediaController

角色:
MediaPlayer 視訊處理器
SurfaceView 視訊顯示介面
MediaController 視訊控制器
複製程式碼

Android多媒體之視訊播放器(基於MediaPlayer)


1.自定義VideoView繼承自SurfaceView
/**
 * 作者:張風捷特烈<br/>
 * 時間:2019/3/8/008:12:43<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:視訊播放:MediaPlayer + SurfaceView + MediaController
 */
public class VideoView extends SurfaceView implements MediaController.MediaPlayerControl {
    private SurfaceHolder mSurfaceHolder;//SurfaceHolder
    private MediaPlayer mMediaPlayer;//媒體播放器
    private MediaController mMediaController;//媒體控制器
    
    private int mVideoHeight;//視訊寬高
    private int mVideoWidth;//視訊高
    private int mSurfaceHeight;//SurfaceView高
    private int mSurfaceWidth;//SurfaceView寬
    
    private boolean isPrepared;//是否已準備好
    private Uri mUri;//播放的地址
    private int mCurrentPos;//當前進度
    private int mDuration = -1;//當前播放視訊時長
    private int mCurrentBufferPer;//當前緩衝進度--網路
    
    public VideoView(Context context) {
        this(context, null);
    }
    public VideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    private void init() {
        setFocusable(true);
        setFocusableInTouchMode(true);
        requestFocus();
        getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                mSurfaceHolder = holder;
                openVideo();
            }
            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                mSurfaceHeight = height;
                mSurfaceWidth = width;
                if (mMediaPlayer != null && isPrepared) {
                    initPosition();
                    mMediaPlayer.start();//開始播放
                    showCtrl();
                }
            }
            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                mSurfaceHolder = null;
                hideController();
                releasePlayer();
            }
        });
    }
    /**
     * 顯示控制器
     */
    private void showCtrl() {
        if (mMediaController != null) {
            mMediaController.show();
        }
    }
    /**
     * 隱藏控制器
     */
    private void hideController() {
        if (mMediaController != null) {
            mMediaController.hide();
        }
    }
    /**
     * 初始化最初位置
     */
    private void initPosition() {
        if (mCurrentPos != 0) {
            mMediaPlayer.seekTo(mCurrentPos);
            mCurrentPos = 0;
        }
    }
    private void openVideo() {
        if (mUri == null || mSurfaceHolder == null) {
            return;
        }
        isPrepared = false;//沒有準備完成
        releasePlayer();
        mMediaPlayer = new MediaPlayer();
        try {
            mMediaPlayer.setDataSource(getContext(), mUri);
            mMediaPlayer.setDisplay(mSurfaceHolder);
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mMediaPlayer.setScreenOnWhilePlaying(true);//播放時螢幕一直亮著
            mMediaPlayer.prepareAsync();//非同步準備
            attach2Ctrl();//繫結媒體控制器
        } catch (IOException e) {
            e.printStackTrace();
        }
        //準備監聽
        mMediaPlayer.setOnPreparedListener(mp -> {
            isPrepared = true;
            if (mMediaController != null) {//控制器可用
                mMediaController.setEnabled(true);
            }
            if (mOnPreparedListener != null) {//補償回撥
                mOnPreparedListener.onPrepared(mp);
            }
            mVideoWidth = mp.getVideoWidth();
            mVideoHeight = mp.getVideoHeight();
            if (mVideoWidth != 0 && mVideoHeight != 0) {
                getHolder().setFixedSize(mVideoWidth, mVideoHeight);
                //開始初始化
                initPosition();
                if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
                    if (!isPlaying() && mCurrentPos != 0 || getCurrentPosition() > 0) {
                        if (mMediaController != null) {
                            mMediaController.show(0);
                        }
                    }
                }
            }
        });
        //尺寸改變監聽
        mMediaPlayer.setOnVideoSizeChangedListener((mp, width, height) -> {
            mVideoWidth = mp.getVideoWidth();
            mVideoHeight = mp.getVideoHeight();
            if (mOnSizeChanged != null) {
                mOnSizeChanged.onSizeChange();
            }
            if (mVideoWidth != 0 && mVideoHeight != 0) {
                getHolder().setFixedSize(mVideoWidth, mVideoHeight);
            }
        });
        //完成監聽
        mMediaPlayer.setOnCompletionListener(mp -> {
            hideController();
            start();
            if (mOnCompletionListener != null) {
                mOnCompletionListener.onCompletion(mp);
            }
        });
        //錯誤監聽
        mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
            hideController();
            if (mOnErrorListener != null) {
                mOnErrorListener.onError(mp, what, extra);
            }
            return true;
        });
        mMediaPlayer.setOnBufferingUpdateListener((mp, pre) -> {
            mCurrentBufferPer = pre;
        });
    }
    /**
     * 釋放播放器
     */
    private void releasePlayer() {
        if (mMediaPlayer != null) {
            mMediaPlayer.reset();
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
    }

    private void attach2Ctrl() {
        if (mMediaPlayer != null && mMediaController != null) {
            mMediaController.setMediaPlayer(this);
            View anchor = this.getParent() instanceof View ? (View) this.getParent() : this;
            mMediaController.setAnchorView(anchor);
            mMediaController.setEnabled(true);
        }
    }
    
    public void setVideoPath(String path) {
        mUri = Uri.parse(path);
        setVideoURI(mUri);
    }
    public void setVideoURI(Uri uri) {
        mUri = uri;
        mCurrentPos = 0;
        openVideo();//開啟視訊
        requestLayout();//更新介面
        invalidate();
    }
    
    public void setMediaController(MediaController mediaController) {
        hideController();
        mMediaController = mediaController;
        attach2Ctrl();
    }
    
    public void stopPlay() {
        if (mMediaPlayer != null) {
            mMediaPlayer.stop();
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
    }
    private void toggle() {
        if (mMediaController.isShowing()) {
            mMediaController.hide();
        } else {
            mMediaController.show();
        }
    }
    private boolean canPlay() {
        return mMediaPlayer != null && isPrepared;
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (isPrepared && mMediaController != null && mMediaPlayer != null) {
            toggle();
        }
        return false;
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int w = adjustSize(mVideoWidth, widthMeasureSpec);
        int h = adjustSize(mVideoHeight, heightMeasureSpec);
        setMeasuredDimension(w, h);
    }
    public int adjustSize(int size, int measureSpec) {
        int result = 0;
        int mode = MeasureSpec.getMode(measureSpec);
        int len = MeasureSpec.getMode(measureSpec);
        switch (mode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
                result = Math.min(size, len);
                break;
            case MeasureSpec.EXACTLY:
                result = len;
                break;
        }
        return result;
    }
    //----------------------------------------------------------------
    //------------MediaPlayerControl介面函式---------------------------
    //----------------------------------------------------------------
    @Override
    public void start() {
        if (canPlay()) {
            mMediaPlayer.start();
        }
    }
    @Override
    public void pause() {
        if (canPlay() && mMediaPlayer.isPlaying()) {
            mMediaPlayer.pause();
        }
    }
    @Override
    public int getDuration() {
        if (canPlay()) {
            if (mDuration > 0) {
                return mDuration;
            }
            mDuration = mMediaPlayer.getDuration();
            return mDuration;
        }
        mDuration = -1;
        return mDuration;
    }
    @Override
    public int getCurrentPosition() {
        if (canPlay()) {
            return mMediaPlayer.getCurrentPosition();
        }
        return 0;
    }
    @Override
    public void seekTo(int pos) {
        if (canPlay()) {
            mMediaPlayer.seekTo(pos);
        } else {
            mCurrentPos = pos;
        }
    }
    @Override
    public boolean isPlaying() {
        if (canPlay()) {
            return mMediaPlayer.isPlaying();
        }
        return false;
    }
    @Override
    public int getBufferPercentage() {
        if (canPlay()) {
            return mCurrentBufferPer;
        }
        return 0;
    }
    @Override
    public boolean canPause() {
        return true;
    }
    @Override
    public boolean canSeekBackward() {
        return true;
    }
    @Override
    public boolean canSeekForward() {
        return true;
    }
    @Override
    public int getAudioSessionId() {
        return 0;
    }
    //----------------------------------------------------------------
    //------------補償回撥---------------------------
    //----------------------------------------------------------------
    private MediaPlayer.OnPreparedListener mOnPreparedListener;
    private MediaPlayer.OnCompletionListener mOnCompletionListener;
    private MediaPlayer.OnErrorListener mOnErrorListener;
    public void setOnPreparedListener(MediaPlayer.OnPreparedListener onPreparedListener) {
        mOnPreparedListener = onPreparedListener;
    }
    public void setOnCompletionListener(MediaPlayer.OnCompletionListener onCompletionListener) {
        mOnCompletionListener = onCompletionListener;
    }
    public void setOnErrorListener(MediaPlayer.OnErrorListener onErrorListener) {
        mOnErrorListener = onErrorListener;
    }
    public interface OnSizeChanged {
        void onSizeChange();
    }
    private OnSizeChanged mOnSizeChanged;
    public void setOnSizeChanged(OnSizeChanged onSizeChanged) {
        mOnSizeChanged = onSizeChanged;
    }
}
複製程式碼

2.根據路徑使用測試

簡單一點,可以用系統自帶的控制器:MediaController,不過醜到爆炸
檔案許可權自理:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

自帶的預設控制器.png

---->[activity_main.xml]------------------------------------------------
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <com.toly1994.ivideo.widget.VideoView
            android:id="@+id/id_vv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
</android.support.constraint.ConstraintLayout>


---->[使用:PlayerActivity.kt]------------------------------------------------
id_vv.setMediaController(MediaController(this))
id_vv.setVideoPath("/sdcard/toly/sh.mp4")
複製程式碼

3.獲取所有的視訊並根據插入時間降序排列

查詢所有的視訊檔案.png

/**
 * 作者:張風捷特烈<br/>
 * 時間:2018/10/30 0030:18:38<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:視訊ContentProvide相關操作---生成視訊List
 */
public class VideoScanner {
    static String[] projection = new String[]{
            MediaStore.Video.Media._ID,//ID
            MediaStore.Video.Media.TITLE,//名稱
            MediaStore.Video.Media.DURATION,//時長
            MediaStore.Video.Media.DATA,//路徑
            MediaStore.Video.Media.SIZE,//大小
            MediaStore.Video.Media.DATE_ADDED//新增的時間
    };
    /**
     * 歌曲集合
     */
    private static List<VideoInfo> videos = new ArrayList<>();
    /**
     * 讀取音訊
     */
    public static List<VideoInfo> loadVideo(final Context context) {
        if (videos.size() != 0) {
            return videos;
        }
        Cursor cursor = context.getContentResolver().query(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                projection, "", null,
                "date_added desc", null);
        // 根據欄位獲取資料庫中資料的索引
        int songIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
        int titleIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE);
        int durationIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION);
        int dataUrlIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);
        int sizeIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE);
        int addDateIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATE_ADDED);
        while (cursor.moveToNext()) {
                long videoId = cursor.getLong(songIdIdx);//獲取id
                String title = cursor.getString(titleIdx);//獲取名字
                String dataUrl = cursor.getString(dataUrlIdx);//獲取路徑
                long duration = cursor.getLong(durationIdx);//獲取時長
                long size = cursor.getLong(sizeIdx);//獲取大小
                long addDate = cursor.getLong(addDateIdx);//加入時間
                videos.add(new VideoInfo(videoId, title, dataUrl, duration, size, addDate));
        }
        return videos;
    }
}
複製程式碼

4.RecyclerView裝一下Video資訊

關於封面預覽圖等會在倒騰,佈局什麼的就不貼了,自己寫
當點選的時候,跳轉到剛才的那個播放Activity,用Intent傳遞視訊路徑

列表展示.png

---->[HomeAdapter#onBindViewHolder]-------------------------------------------
holder.mIvCover.setOnClickListener(v -> {
    Intent intent = new Intent(mContext, PlayerActivity.class);
    intent.putExtra("video-path", videoInfo.getDataUrl());
    mContext.startActivity(intent);
});

---->[附贈一個視訊時間轉化的方法]----------------------------------------
private String format(long duration) {
    long time = duration / 1000;
    String result = "";
    long minus = time / 60;
    int hour = 0;
    if (minus > 60) {
        hour = (int) (minus / 60);
        minus = minus % 60;
    }
    long second = time % 60;
    if (hour < 60) {
        result = handleNum(hour) + ":" + handleNum(minus)+":"+handleNum(second);
    }
    return result;
}

private String handleNum(long num) {
    return num < 10 ? ("0" + num) : (num + "");
}

---->[PlayerActivity]-------------------------------------------
val path = intent.getStringExtra("video-path")
id_vv.setMediaController(MediaController(this))
id_vv.setUri(path)
複製程式碼

Android多媒體之視訊播放器(基於MediaPlayer)

OK 簡易版的視訊播放器就OK了。


二、介面橫豎屏問題

這轉個屏,D 都變成 A 了,怎麼能忍,趕快修一下

螢幕轉向問題.png


1.關於縮放
getHolder().setFixedSize(w,h)  測試了一下,然並卵,解析度沒有改變
|-- 來翻一下原始碼

/**
 * Make the surface a fixed size.  It will never change from this size.
 * When working with a {@link SurfaceView}, this must be called from the
 * same thread running the SurfaceView's window.
 * 使surface的大小固定。它的大小永遠不會改變。
 * 當使用SurfaceView時,必須從執行SurfaceView視窗的同一執行緒呼叫它。
 * @param width The surface's width. surface寬
 * @param height The surface's height. surface高
 */
public void setFixedSize(int width, int height);
複製程式碼

看來此路不通,那隻能求他路


2.直接變更View的尺寸

直接變更View的尺寸.png

public void changeVideoFitSize(int videoW, int videoH, int surfaceW, int surfaceH) {
    float videoSizeRate = videoW * 1.0f / videoH;
    //橫屏下的切換 -- 正常寬高比例
    float widthRatePortrait = videoW * 1.0f / surfaceW;
    float heightRatePortrait = videoH * 1.0f / surfaceH;
    //橫屏下的切換 View寬高互換-- 寬高比例
    float widthRateLand = videoW * 1.0f / surfaceH;
    float heightRateLand = videoH * 1.0f / surfaceW;
    float ratio;
    if (getResources().getConfiguration().orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {//橫屏
        //豎屏模式下
        ratio = Math.max(widthRatePortrait, heightRatePortrait);
    } else {
        //橫屏模式下
        if (videoSizeRate > 1) {
            ratio = Math.min(widthRateLand, heightRateLand);
        } else {
            ratio = Math.max(widthRateLand, heightRateLand);
        }
    }
    //視訊寬高分別/最大倍數值 計算出放大後的視訊尺寸
    videoW = (int) Math.ceil(videoW * 1.0f / ratio);
    videoH = (int) Math.ceil(videoH * 1.0f / ratio);
    //根據將視訊尺寸變更View
    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(videoW, videoH);
    setLayoutParams(params);
}

|--- 使用:
---->[setOnVideoSizeChangedListener中]---------------------------------------------
changeVideoFitSize(mVideoWidth, mVideoHeight, mSurfaceWidth, mSurfaceHeight);
複製程式碼

3.不滿屏時居中

至於怎麼居中,我天真的以為在xml裡改一下就行了,but,並沒用,因為這裡是自己玩LayoutParams
所以居中也要用LayoutParams,沒辦法,走波原始碼唄。

---->[RelativeLayout#CENTER_IN_PARENT]---------------------
public static final int CENTER_IN_PARENT         = 13;

CENTER_IN_PARENT是一個int型控制的,看一下LayoutParams的原始碼,暴露的方法就那幾個,
addRule恰只有一個int入參,應該就是它了

---->[RelativeLayout.LayoutParams#addRule(int)]---------------------
public void addRule(int verb) {
    addRule(verb, TRUE);
}

---->[.VideoView#changeVideoFitSize(int, int, int, int)]-------------
---- 輕輕寫語句,即可
params.addRule(13);
複製程式碼

居中設定.png

居中ok.png


3.自定義寬高縮放比例
public void changeVideoSize(float rateX, float rateY) {
    changeVideoFitSize(mVideoWidth, mVideoHeight, mSurfaceWidth, mSurfaceHeight, rateX, rateY);
}

public void changeVideoFitSize(
        int videoW, int videoH, int surfaceW, int surfaceH,
        float rateX, float rateY) {
    ...
    //視訊寬高分別/最大倍數值 計算出放大後的視訊尺寸
    videoW = (int) Math.ceil(videoW * 1.0f / ratio * rateX);
    videoH = (int) Math.ceil(videoH * 1.0f / ratio * rateY);
    //無法直接設定視訊尺寸,將計算出的視訊尺寸設定到surfaceView 讓視訊自動填充。
    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(videoW, videoH);
    params.addRule(13);
    setLayoutParams(params);
}
複製程式碼

設定比例.png


三、定製操作介面

1.介面操作

自定義的介面就是根據VideoView中的Api自己實現控制邏輯,細心一點還是不難的,就是麻煩
介面如下,不貼布局了,比較簡單,也挺多的,這裡說一下顯示皮膚後5秒後隱藏的邏輯

控制介面.png

private val mHandler = Handler(Looper.getMainLooper())

root.setOnClickListener {//點選顯示皮膚
    showPanel(mHandler)
}

private fun hidePanel() {
    id_ll_top.visibility = View.GONE
    id_ll_bottom.visibility = View.GONE
    id_iv_lock.visibility = View.GONE
}
private fun showPanel(handler: Handler) {
    id_ll_top.visibility = View.VISIBLE
    id_ll_bottom.visibility = View.VISIBLE
    id_iv_lock.visibility = View.VISIBLE
    handler.postDelayed(::hidePanel, 5000)
}
複製程式碼

2.倍速播放

二倍速聽mv挺搞笑的,API 23 + 也就是一句Api的事,很方便

/**
 * 變速
 * @param speed
 */
public void changeSpeed(float speed) {
    //API 23 + 支援
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (mMediaPlayer.isPlaying()) {
            mMediaPlayer.setPlaybackParams(mMediaPlayer.getPlaybackParams().setSpeed(speed));
        } else {
            mMediaPlayer.setPlaybackParams(mMediaPlayer.getPlaybackParams().setSpeed(speed));
            mMediaPlayer.pause();
        }
    }
}

|-- 使用陣列來控制-----------------------
private var speeds = floatArrayOf(0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2.0f)
private var curSpeedIdx = 2

id_tv_speed.setOnClickListener {
    curSpeedIdx++
    if (curSpeedIdx == speeds.size) {
        curSpeedIdx = 0
    }
    val speed = speeds[curSpeedIdx]
    id_vv.changeSpeed(speed)
    id_tv_speed.text = "$speed X"
}
複製程式碼

3.封面圖的獲取

獲取幀.png

基本上也就這麼多了,最後講一下視訊封面幀圖片的獲取:數了一下這幀大概在15秒
測試了一下秒數越大,獲取圖片的速度越慢,也就是越卡,所以還是給0吧
如果在Adapter裡實時載入會很卡,最好查詢的時候就把bitmap放到實體類裡,由於封面圖不要很大
別把原圖給放進去了,小心直接OOM。Bitmap的操作本文就不贅述了。

圖片尺寸.png

---->[HomeAdapter]------------------------
private final MediaMetadataRetriever retriever;    

retriever = new MediaMetadataRetriever();

/**
 * 獲取視訊某一幀
 *
 * @param path 路徑
 * @param timeMs 毫秒
 */
public Bitmap decodeFrame(String path,long timeMs) {
    retriever.setDataSource(path);
    Bitmap bitmap = retriever.getFrameAtTime(timeMs * 1000, MediaMetadataRetriever.OPTION_CLOSEST);
    if (bitmap == null) {
        return null;
    }
    return bitmap;
}

複製程式碼

此選項與{@link #getFrameAtTime(long,int)}一起使用,以檢索與位於給定時間附近或給定時間的資料來源相關聯的幀(不一定是關鍵幀)。
 * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
 * a frame (not necessarily a key frame) associated with a data source that
 * is located closest to or at the given time.
public static final int OPTION_CLOSEST          = 0x03;

此選項與{@link #getFrameAtTime(long,int)}一起使用,以檢索與位於(時間上)最接近或給定時間的資料來源相關聯的同步(或鍵)幀。
 * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
 * a sync (or key) frame associated with a data source that is located
 * closest to (in time) or at the given time.
public static final int OPTION_CLOSEST_SYNC     = 0x02;


此選項與{@link #getFrameAtTime(long,int)}一起使用,以檢索與位於給定時間之後或指定時間的資料來源關聯的同步(或鍵)幀。
 * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
 * a sync (or key) frame associated with a data source that is located
 * right after or at the given time.
public static final int OPTION_NEXT_SYNC        = 0x01;

此選項與{@link #getFrameAtTime(long,int)}一起使用,以檢索與位於給定時間之前或指定時間的資料來源關聯的同步(或鍵)幀。
* This option is used with {@link #getFrameAtTime(long, int)} to retrieve
* a sync (or key) frame associated with a data source that is located
* right before or at the given time.
public static final int OPTION_PREVIOUS_SYNC    = 0x00;
複製程式碼

獲取幀.png


四、網路視訊的播放

Android多媒體之視訊播放器(基於MediaPlayer)


1.網路視訊

放在伺服器上了,地址:http://www.toly1994.com:8089/imgs/sh.mp4,就一句話

id_vv.setVideoPath("http://www.toly1994.com:8089/imgs/sh.mp4")
複製程式碼

2.SeekBar的第二進度
---->[drawable/seekbar_bg.xml]--------------------------------------------
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background">
        <shape>
            <solid android:color="#eee" />
        </shape>
    </item>
    <item android:id="@android:id/secondaryProgress">
        <clip>
            <shape>
                <solid android:color="#2db334"/>
            </shape>
        </clip>
    </item>
    <item android:id="@android:id/progress">
        <clip>
            <shape>
                <solid android:color="@color/colorAccent"/>
            </shape>
        </clip>
    </item>
</layer-list>

---->[layout/in_player_panel_bottom.xml]---------------------------
 <SeekBar
    ...
    android:progressDrawable="@drawable/seekbar_bg"
複製程式碼

3.快取監聽
---->[com.toly1994.ivideo.widget.VideoView]------------------
mMediaPlayer.setOnBufferingUpdateListener((mp, pre) -> {
    mCurrentBufferPer = pre;
    if (mOnBufferingUpdateListener != null) {
        mOnBufferingUpdateListener.update(pre);
    }
});

public interface OnBufferingUpdateListener {
    void update(int pre);
}
private OnBufferingUpdateListener mOnBufferingUpdateListener;
public void setOnBufferingUpdateListener(OnBufferingUpdateListener onBufferingUpdateListener) {
    mOnBufferingUpdateListener = onBufferingUpdateListener;
}

使用:
id_vv.setOnBufferingUpdateListener {
    id_sb_progress.secondaryProgress = it
}
複製程式碼

Ok 這樣就完成了。本篇就這樣,更多的功能可以自己去擴充,
搭個後臺,弄個簡單的網路播放器也未嘗不可。


後記:捷文規範

1.本文成長記錄及勘誤表
專案原始碼 日期 備註
2018-3-9 Android多媒體之視訊播放器(基於MediaPlayer)
2.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
我的github 我的簡書 我的掘金 個人網站
3.宣告

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援


icon_wx_200.png

相關文章