1. 程式人生 > >Android使用Vitamio框架自定義視訊播放器

Android使用Vitamio框架自定義視訊播放器

        做過Android視訊播放器的碼農們都或多或少知道自帶的VideoView用著沒有那麼順心。需要處理很多東西。於是就各種度娘、Google。終於皇天不負苦心人。找到了一個卻又不大符合。無奈,想自己動手寫吧!又浪費時間。在這裡,附上一個採用Vitamio框架寫的視訊播放器貢獻給大夥。希望對你有用。好了。進入正題:


附上github下載地址:https://github.com/eternityzqf/VitamioTestDemo


先來個效果圖看看:

圖片可能有點糙,但執行在手機上是沒問題的。




功能點:

①:播放網路視訊

②:可以實現快取/快取載入提示

③:豎屏縮放正常畫面、橫屏縮放全屏畫面

④:滑動左邊亮度調節、滑動右邊調節聲音

⑤:自定義媒體控制畫面。增加擴充套件性。


主要類:

一、App:用於全域性初始化VItamio

/**
 * class from
 * Created by zqf
 * Time 2017/7/25 15:43
 */

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        //初始化一次Vitamio
        Vitamio.isInitialized(this);
    }
}


二、CustomMediaController:自定義視訊播放器控制器

/**
 * Created by zqf on 2017/7/25.
 * 自定義視訊控制器
 */
public class CustomMediaController extends MediaController {
    private static final int HIDEFRAM = 0;//控制提示視窗的顯示
    private GestureDetector mGestureDetector;
    private ImageButton img_back;//返回按鈕
    private TextView mFileName;//檔名
    private VideoView videoView;
    private Activity activity;
    private Context context;
    private String videoname;//視訊名稱
    private int controllerWidth = 0;//設定mediaController高度為了使橫屏時top顯示在螢幕頂端
    private View mVolumeBrightnessLayout;//提示視窗
    private ImageView mOperationBg;//提示圖片
    private TextView mOperationTv;//提示文字
    private AudioManager mAudioManager;
    private SeekBar progress;
    private boolean mDragging;
    private MediaPlayerControl player;
    //最大聲音
    private int mMaxVolume;
    //當前聲音
    private int mVolume = -1;
    //當前亮度
    private float mBrightness = -1f;
    //返回監聽
    private View.OnClickListener backListener = new View.OnClickListener() {
        public void onClick(View v) {
            if (activity != null) {
                activity.finish();
            }
        }
    };


    private View.OnClickListener scaleListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (activity != null) {
                switch (activity.getResources().getConfiguration().orientation) {
                    case Configuration.ORIENTATION_LANDSCAPE://橫屏
                        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
                        break;
                    case Configuration.ORIENTATION_PORTRAIT://豎屏
                        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
                        break;
                }
            }
        }
    };

    private Handler myHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            long pos;
            switch (msg.what) {
                case HIDEFRAM://隱藏提示視窗
                    mVolumeBrightnessLayout.setVisibility(View.GONE);
                    mOperationTv.setVisibility(View.GONE);
                    break;
            }
        }
    };
    private ImageView mIvScale;


    //videoview 用於對視訊進行控制的等,activity為了退出
    public CustomMediaController(Context context, VideoView videoView, Activity activity) {
        super(context);
        this.context = context;
        this.videoView = videoView;
        this.activity = activity;
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        controllerWidth = wm.getDefaultDisplay().getWidth();
        mGestureDetector = new GestureDetector(context, new MyGestureListener());
    }

    @Override
    protected View makeControllerView() {
        //此處的   mymediacontroller  為我們自定義控制器的佈局檔名稱
        View v = ((LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(getResources().getIdentifier("mymediacontroller", "layout", getContext().getPackageName()), this);
        v.setMinimumHeight(controllerWidth);
        //獲取控制元件
        img_back = (ImageButton) v.findViewById(getResources().getIdentifier("mediacontroller_top_back", "id", context.getPackageName()));
        mFileName = (TextView) v.findViewById(getResources().getIdentifier("mediacontroller_filename", "id", context.getPackageName()));
        //縮放控制元件
        mIvScale = (ImageView) v.findViewById(getResources().getIdentifier("mediacontroller_scale", "id", context.getPackageName()));

        if (mFileName != null) {
            mFileName.setText(videoname);
        }
        //聲音控制
        mVolumeBrightnessLayout = (RelativeLayout) v.findViewById(R.id.operation_volume_brightness);
        mOperationBg = (ImageView) v.findViewById(R.id.operation_bg);
        mOperationTv = (TextView) v.findViewById(R.id.operation_tv);
        mOperationTv.setVisibility(View.GONE);
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        mMaxVolume = mAudioManager
                .getStreamMaxVolume(AudioManager.STREAM_MUSIC);

        //註冊事件監聽
        img_back.setOnClickListener(backListener);
        mIvScale.setOnClickListener(scaleListener);
        return v;
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        System.out.println("MYApp-MyMediaController-dispatchKeyEvent");
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mGestureDetector.onTouchEvent(event)) return true;
        // 處理手勢結束
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_UP:
                endGesture();
                break;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 手勢結束
     */
    private void endGesture() {
        mVolume = -1;
        mBrightness = -1f;
        // 隱藏
        myHandler.removeMessages(HIDEFRAM);
        myHandler.sendEmptyMessageDelayed(HIDEFRAM, 1);
    }

    private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            return false;
        }

        /**
         * 因為使用的是自定義的mediaController 當顯示後,mediaController會鋪滿螢幕,
         * 所以VideoView的點選事件會被攔截,所以重寫控制器的手勢事件,
         * 將全部的操作全部寫在控制器中,
         * 因為點選事件被控制器攔截,無法傳遞到下層的VideoView,
         * 所以 原來的單機隱藏會失效,作為代替,
         * 在手勢監聽中onSingleTapConfirmed()新增自定義的隱藏/顯示,
         *
         * @param e
         * @return
         */
        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            //當手勢結束,並且是單擊結束時,控制器隱藏/顯示
            toggleMediaControlsVisiblity();
            return super.onSingleTapConfirmed(e);
        }

        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }

        //滑動事件監聽
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            float mOldX = e1.getX(), mOldY = e1.getY();
            int y = (int) e2.getRawY();
            int x = (int) e2.getRawX();
            Display disp = activity.getWindowManager().getDefaultDisplay();
            int windowWidth = disp.getWidth();
            int windowHeight = disp.getHeight();
            if (mOldX > windowWidth * 3.0 / 4.0) {// 右邊滑動 螢幕 3/4
                onVolumeSlide((mOldY - y) / windowHeight);
            } else if (mOldX < windowWidth * 1.0 / 4.0) {// 左邊滑動 螢幕 1/4
                onBrightnessSlide((mOldY - y) / windowHeight);
            }
            return super.onScroll(e1, e2, distanceX, distanceY);
        }

        @Override
        public boolean onDoubleTap(MotionEvent e) {
            playOrPause();
            return true;
        }


        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            return super.onFling(e1, e2, velocityX, velocityY);
        }
    }

    /**
     * 滑動改變聲音大小
     *
     * @param percent
     */
    private void onVolumeSlide(float percent) {
        if (mVolume == -1) {
            mVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
            if (mVolume < 0)
                mVolume = 0;

            // 顯示
            mVolumeBrightnessLayout.setVisibility(View.VISIBLE);
            mOperationTv.setVisibility(VISIBLE);
        }

        int index = (int) (percent * mMaxVolume) + mVolume;
        if (index > mMaxVolume)
            index = mMaxVolume;
        else if (index < 0)
            index = 0;
        if (index >= 10) {
            mOperationBg.setImageResource(R.drawable.volmn_100);
        } else if (index >= 5 && index < 10) {
            mOperationBg.setImageResource(R.drawable.volmn_60);
        } else if (index > 0 && index < 5) {
            mOperationBg.setImageResource(R.drawable.volmn_30);
        } else {
            mOperationBg.setImageResource(R.drawable.volmn_no);
        }
        //DecimalFormat    df   = new DecimalFormat("######0.00");
        mOperationTv.setText((int) (((double) index / mMaxVolume) * 100) + "%");
        // 變更聲音
        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, index, 0);

    }

    /**
     * 滑動改變亮度
     *
     * @param percent
     */
    private void onBrightnessSlide(float percent) {
        if (mBrightness < 0) {
            mBrightness = activity.getWindow().getAttributes().screenBrightness;
            if (mBrightness <= 0.00f)
                mBrightness = 0.50f;
            if (mBrightness < 0.01f)
                mBrightness = 0.01f;

            // 顯示
            mVolumeBrightnessLayout.setVisibility(View.VISIBLE);
            mOperationTv.setVisibility(VISIBLE);

        }


        WindowManager.LayoutParams lpa = activity.getWindow().getAttributes();
        lpa.screenBrightness = mBrightness + percent;
        if (lpa.screenBrightness > 1.0f)
            lpa.screenBrightness = 1.0f;
        else if (lpa.screenBrightness < 0.01f)
            lpa.screenBrightness = 0.01f;
        activity.getWindow().setAttributes(lpa);

        mOperationTv.setText((int) (lpa.screenBrightness * 100) + "%");
        if (lpa.screenBrightness * 100 >= 90) {
            mOperationBg.setImageResource(R.drawable.light_100);
        } else if (lpa.screenBrightness * 100 >= 80 && lpa.screenBrightness * 100 < 90) {
            mOperationBg.setImageResource(R.drawable.light_90);
        } else if (lpa.screenBrightness * 100 >= 70 && lpa.screenBrightness * 100 < 80) {
            mOperationBg.setImageResource(R.drawable.light_80);
        } else if (lpa.screenBrightness * 100 >= 60 && lpa.screenBrightness * 100 < 70) {
            mOperationBg.setImageResource(R.drawable.light_70);
        } else if (lpa.screenBrightness * 100 >= 50 && lpa.screenBrightness * 100 < 60) {
            mOperationBg.setImageResource(R.drawable.light_60);
        } else if (lpa.screenBrightness * 100 >= 40 && lpa.screenBrightness * 100 < 50) {
            mOperationBg.setImageResource(R.drawable.light_50);
        } else if (lpa.screenBrightness * 100 >= 30 && lpa.screenBrightness * 100 < 40) {
            mOperationBg.setImageResource(R.drawable.light_40);
        } else if (lpa.screenBrightness * 100 >= 20 && lpa.screenBrightness * 100 < 20) {
            mOperationBg.setImageResource(R.drawable.light_30);
        } else if (lpa.screenBrightness * 100 >= 10 && lpa.screenBrightness * 100 < 20) {
            mOperationBg.setImageResource(R.drawable.light_20);
        }

    }


    /**
     * 設定視訊檔名
     *
     * @param name
     */
    public void setVideoName(String name) {
        videoname = name;
        if (mFileName != null) {
            mFileName.setText(name);
        }
    }

    /**
     * 隱藏或顯示
     */
    private void toggleMediaControlsVisiblity() {
        if (isShowing()) {
            hide();
        } else {
            show();
        }
    }

    /**
     * 播放/暫停
     */
    private void playOrPause() {
        if (videoView != null)
            if (videoView.isPlaying()) {
                videoView.pause();
            } else {
                videoView.start();
            }
    }
}


自定義控制器類主要是最播放、暫停、播放時間、全屏的一些控制元件介面封裝;通過在MainActivity裡面的

new CustomMediaController();將VideoView傳進來進行一些操作。但播放的相關還是放在主介面裡面操作的。

可在裡面更改介面以適應需求。SeekBar樣式都將採用自定義的。增加擴充套件。

涉及的公共方法:

setVideoName();設定視訊名稱

當然還有什麼新增喜歡,收藏子類的。你們都可以自己新增。


三、ManActivity:主介面

/**
 * class from 主介面
 * Created by zqf
 * Time 2017/7/25 15:43
 */
public class MainActivity extends Activity implements MediaPlayer.OnInfoListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener {

    private String video_path = "http://baobab.wdjcdn.com/145076769089714.mp4";
    private Uri mUri;
    private ProgressBar pb;
    private TextView downloadRateView, loadRateView;
    private CustomMediaController mCustomMediaController;
    private VideoView mVideoView;
    public static long mCurrent_position = 0;//當前播放的位置
    public static final int VIDEO_LAYOUT_ORIGIN = 0;//縮放參數,原始畫面大小0。
    public static final int VIDEO_LAYOUT_SCALE = 1;//縮放參數,畫面全屏1。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //定義全屏引數
        int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN;
        //獲得當前窗體物件
        Window window = MainActivity.this.getWindow();
        //設定當前窗體為全屏顯示
        window.setFlags(flag, flag);
        setContentView(R.layout.activity_main);
        initView();
        initData();
    }

    private void initData() {
        mUri = Uri.parse(video_path);//將地址轉化為Uri
        mVideoView.setVideoURI(mUri);//設定播放視訊的地址
        mCustomMediaController.show(5000);//設定顯示時間差
        mVideoView.setMediaController(mCustomMediaController);//設定媒體控制器。
        mVideoView.setVideoQuality(MediaPlayer.VIDEOQUALITY_HIGH);//設定畫質
        mVideoView.requestFocus();//獲取焦點
        mVideoView.setBufferSize(512 * 1024);//設定緩衝大小(單位Byte)
        /**
         * 監聽在有警告或錯誤資訊時呼叫。例如:開始緩衝、緩衝結束、下載速度變化。
         */
        mVideoView.setOnInfoListener(this);
        /**
         * 監聽在網路視訊流緩衝變化時呼叫。
         */
        mVideoView.setOnBufferingUpdateListener(this);
        /**
         * 視訊播放完成後呼叫。
         */
        mVideoView.setOnCompletionListener(this);
        mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                /**
                 * 在視訊預處理完成後呼叫。在視訊預處理完成後被呼叫。
                 * 此時視訊的寬度、高度、寬高比資訊已經獲取到,
                 * 此時可呼叫seekTo讓視訊從指定位置開始播放。
                 */
                mp.setPlaybackSpeed(1.0f);
            }
        });
    }

    private void initView() {
        mVideoView = (VideoView) findViewById(R.id.vitamio_video);
        mCustomMediaController = new CustomMediaController(this, mVideoView, this);
        mCustomMediaController.setVideoName("此處可以設定視訊名稱");
        pb = (ProgressBar) findViewById(R.id.probar);
        downloadRateView = (TextView) findViewById(R.id.download_rate);
        loadRateView = (TextView) findViewById(R.id.load_rate);
    }


    @Override
    public boolean onInfo(MediaPlayer mp, int what, int extra) {
        switch (what) {
            case MediaPlayer.MEDIA_INFO_BUFFERING_START:
                //開始快取事,執行暫停播放、載入、下載、快取控制元件可見
                if (mVideoView.isPlaying()) {
                    Log.e("Tag", what + "---快取flag----");
                    mVideoView.pause();
                    pb.setVisibility(View.VISIBLE);
                    downloadRateView.setVisibility(View.VISIBLE);
                    loadRateView.setVisibility(View.VISIBLE);
                }
                break;
            case MediaPlayer.MEDIA_INFO_BUFFERING_END:
                //快取完成,執行繼續播放;載入、下載、快取控制元件不可見
                mVideoView.start();
                pb.setVisibility(View.GONE);
                downloadRateView.setVisibility(View.GONE);
                loadRateView.setVisibility(View.GONE);
                break;
            case MediaPlayer.MEDIA_INFO_DOWNLOAD_RATE_CHANGED:
                //快取時顯示下載速度
                //此時下載速度應該實時獲取手機的網速。這以kb/s代替
                Log.e("Tag", what + "----下載flag---" + extra);
                downloadRateView.setText(extra + "kb/s" + "");
                break;
        }
        return true;
    }

    @Override
    public void onBufferingUpdate(MediaPlayer mp, int percent) {
        loadRateView.setText("緩衝" + percent + "%");
        Log.e("Tag", "+++++++" + percent);
    }


    @Override
    protected void onResume() {
        super.onResume();
        Log.e("Tag", "onResume");
        if (mCurrent_position != 0) {
            mVideoView.seekTo(mCurrent_position);
            mVideoView.start();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.e("Tag", "onPause");
        mCurrent_position = mVideoView.getCurrentPosition();
        Log.e("Tag", mCurrent_position + "");
        if (mVideoView.isPlaying()) {
            mVideoView.pause();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.e("Tag", "onStop");
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Log.e("Tag", "onRestoreInstanceState");
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.e("Tag", "onSaveInstanceState");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e("Tag", "onDestroy");
        //停止視訊播放,並釋放資源。
        mVideoView.stopPlayback();
        mCurrent_position = 0;
    }

    /**
     * 視訊播放完成後呼叫。
     *
     * @param mp the MediaPlayer that reached the end of the file
     */
    @Override
    public void onCompletion(MediaPlayer mp) {
        mCurrent_position = 0;
    }

    /**
     * getRequestedOrientation獲取橫豎屏標誌
     * -1 || 1--->豎屏
     * 0 --->橫屏
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            Log.e("Tag", "返回鍵。。。。" + getRequestedOrientation());
            int orient = getRequestedOrientation();
            if (orient == -1 || orient == 1) {
                finish();
            } else if (orient == 0) {
                //切換為豎屏
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            }
            return false;
        }
        return super.onKeyDown(keyCode, event);
    }

    /**
     * 螢幕切換時
     */
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        Log.e("Tag", newConfig.orientation + "");
        if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
            //豎屏-->顯示原始畫面
            if (mVideoView != null) {
                mVideoView.setVideoLayout(VIDEO_LAYOUT_ORIGIN, 0);
            }
        } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            //橫屏-->顯示全屏畫面
            if (mVideoView != null) {
                mVideoView.setVideoLayout(VIDEO_LAYOUT_SCALE, 0);
            }
        }
        super.onConfigurationChanged(newConfig);
    }
}


主介面裡面

一、

先看看Vitamio的VideoView都帶有什麼功能;

設定快取大小(單位Byte):

mVideoView.setBufferSize(512 * 1024);

監聽在有警告或錯誤資訊時呼叫。例如:開始緩衝、緩衝結束、下載速度變化:

mVideoView.setOnInfoListener(this);

    @Override
    public boolean onInfo(MediaPlayer mp, int what, int extra) {
        switch (what) {
            case MediaPlayer.MEDIA_INFO_BUFFERING_START:
                //開始快取事,執行暫停播放、載入、下載、快取控制元件可見
                if (mVideoView.isPlaying()) {
                    Log.e("Tag", what + "---快取flag----");
                    mVideoView.pause();
                    pb.setVisibility(View.VISIBLE);
                    downloadRateView.setVisibility(View.VISIBLE);
                    loadRateView.setVisibility(View.VISIBLE);
                }
                break;
            case MediaPlayer.MEDIA_INFO_BUFFERING_END:
                //快取完成,執行繼續播放;載入、下載、快取控制元件不可見
                mVideoView.start();
                pb.setVisibility(View.GONE);
                downloadRateView.setVisibility(View.GONE);
                loadRateView.setVisibility(View.GONE);
                break;
            case MediaPlayer.MEDIA_INFO_DOWNLOAD_RATE_CHANGED:
                //快取時顯示下載速度
                //此時下載速度應該實時獲取手機的網速。這以kb/s代替
                Log.e("Tag", what + "----下載flag---" + extra);
                downloadRateView.setText(extra + "kb/s" + "");
                break;
        }
        return true;
    }

監聽在網路視訊流緩衝變化時呼叫:

 mVideoView.setOnBufferingUpdateListener(this);

    @Override
    public void onBufferingUpdate(MediaPlayer mp, int percent) {
        loadRateView.setText("緩衝" + percent + "%");
        Log.e("Tag", "+++++++" + percent);
    }
........

二、手機Home切換到候後臺時我們需要在生命週期裡面記錄和恢復播放位置:

記錄:

 @Override
    protected void onPause() {
        super.onPause();
        Log.e("Tag", "onPause");
        mCurrent_position = mVideoView.getCurrentPosition();
        Log.e("Tag", mCurrent_position + "");
        if (mVideoView.isPlaying()) {
            mVideoView.pause();
        }
    }

恢復:

  @Override
    protected void onResume() {
        super.onResume();
        Log.e("Tag", "onResume");
        if (mCurrent_position != 0) {
            mVideoView.seekTo(mCurrent_position);
            mVideoView.start();
        }
    }

當然我們還需要在ConfigurationChanged裡面改一下;
@Override
    public void onConfigurationChanged(Configuration newConfig) {
        Log.e("Tag", newConfig.orientation + "");
        if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
            //豎屏-->顯示原始畫面
            if (mVideoView != null) {
                mVideoView.setVideoLayout(VIDEO_LAYOUT_ORIGIN, 0);
            }
        } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            //橫屏-->顯示全屏畫面
            if (mVideoView != null) {
                mVideoView.setVideoLayout(VIDEO_LAYOUT_SCALE, 0);
            }
        }
        super.onConfigurationChanged(newConfig);
    }

差不多就這些,有些細節就沒貼出來。可以去github下來看看。歡迎各位star和fork。

參考的Vitamio官網地址:https://www.vitamio.org/