Android 用SurfaceView+PLMediaPlayer 打造一个平板端的网络播放器

Android 使用PLDroidPlayer播放网络视频 根据视频角度自动旋转 最近因为项目需求 ,需要播放网络视频 ,于是乎 研究了一番 ,说说我遇到的那些坑 现在市面上有几个比较主流好用的第三方框架 Vitamio ( 体积比较大,有商业化风险 github:https://github.com/yixia/VitamioBundle/) ijkplayer(B站下开源的框架 体积大 配置环境比较麻烦 github:https://github.com/Bilib 阅读详情

        最近我开始做我接手的项目中最后一个模块:微课。真的太累了,前所未有的累过,感觉身体被掏空。也让我不停的思考,究竟什么是高效,难道人多就是高效?

       之前闲来无事的时候写过一个播放器,但好像跟网络播放器不搭,因为不会有任何一家软件公司会把核心视频不做任何处理的挂在服务器上的。然而关于视频的解码与解密我真的是一窍不通啊,不懂c语言,也就无法再android运用c库,再次暴露了自己的弱点。


 要求:1.对视频边解码边播放边缓存

            2.全屏与半屏

            3.手势


 效果:

   

准备:

        PLMediaPlayer 使用的github上别人封装的播放器  点击打开链接

              遵循的原则:当surfaceview创建的时候,再初始化PLMediaPlayer,当PLMediaPlayer 准备好的时候再播放或                                 者初始化控制栏与手势

先贴上我写的完整代码:(缺滑动seekbar视频跳转)

package microclassroommodule.customview;
import android.content.Context;
import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.jakewharton.rxbinding.view.RxView;
import com.orhanobut.logger.Logger;
import com.pili.pldroid.player.AVOptions;
import com.pili.pldroid.player.PLMediaPlayer;
import com.wyt.hcy.aiyixue_teacher.R;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import utils.BrightnessUtils;
import utils.NetUtil;
import utils.RxUtils;
import utils.TrafficBean;
/**
 * Created by hcy on 2017/5/4 0004.
 */

public class WytVideoPlayView extends FrameLayout implements SurfaceHolder.Callback, PLMediaPlayer.OnPreparedListener, PLMediaPlayer.OnVideoSizeChangedListener, PLMediaPlayer.OnCompletionListener, PLMediaPlayer.OnErrorListener, PLMediaPlayer.OnInfoListener, PLMediaPlayer.OnBufferingUpdateListener, PLMediaPlayer.OnSeekCompleteListener, View.OnTouchListener {
    private static final String TAG = "WytVideoPlayView";
    private SurfaceView mSurfaceView;
    private PLMediaPlayer mMediaPlayer;
    private Context currentContext;
    //播放参数配置
    private AVOptions mAVOptions;
    private ImageView iv_play;
    private FrameLayout fl_control;
    private ImageView iv_full;
    private TextView tv_start;
    private TextView tv_end;
    private int mSurfaceWidth = 0;
    private int mSurfaceHeight = 0;
    private Toast mToast = null;
    private FrameLayout myframe;
    private int initialWidth = 0;
    private int initialHeight = 0;
    private Subscription subscription;
    private SeekBar seekbar;
    private TextView tv_speed;
    private boolean isChongLian = false;//判断是否重连

    private ImageView iv_back;




    public long chongLianPosition;

    public void setInitialWidth(int initialWidth) {
        this.initialWidth = initialWidth;
    }

    public void setInitialHeight(int initialHeight) {
        this.initialHeight = initialHeight;
    }

    private boolean isFullScreen = false;//一开始不是全屏

    public boolean isFullScreen() {
        return isFullScreen;
    }

    private View mLoadingView;

    public WytVideoPlayView(Context context) {
        super(context);
    }

    private Handler mHandler;

    private TrafficBean trafficBean;

    private TextView tv_currentNet;

    private boolean isCountTime = false;
    private Timer timer;
    private static final int PLAY_TIME = 0;

    private String uri;

    private static final int CONTROLLER_AUTO_HIDE_TIME = 5000;//5s自动隐藏控制条


    private WytMediaPlayerimp wytMediaPlayerimp;

    /**
     * 手势控制
     */
    private GestureDetector gestureDetector;

    private Subscription subscribe_forwardorback;
    private Subscription subscribe_volume;
    private Subscription subscribe_bright;


    /**
     * surfaceView的Touch监听
     *
     * @param v
     * @param event
     * @return
     */
    @Override
    public boolean onTouch(View v, MotionEvent event) {

        gestureDetector.onTouchEvent(event);

        int action = event.getActionMasked();
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            mGestureOperationType = GestureOperationType.NONE;
        }
        return true;
    }

   



    private static enum GestureOperationType {

        NONE,//无手势操作类型

        VOLUME,//音量

        BRIGHTNESS,//亮度

        FASTFORWARDORBACKWARD,//快进  快退

    }

    private GestureOperationType mGestureOperationType = GestureOperationType.NONE;


    public interface GestureOperationHelper {


        void SingleTap();
    }

    private GestureOperationHelper gestureOperationHelper;


    private RelativeLayout rl_forward, rl_volume, rl_bright;
    private ImageView iv_jindu_tag;
    private TextView tv_jindu;
    private ProgressBar pb_jindu, pb_jindu_volume, pb_jindu_bright;


    /*音量*/
    private AudioManager manager;

    public void setWytMediaPlayerimp(WytMediaPlayerimp wytMediaPlayerimp) {
        this.wytMediaPlayerimp = wytMediaPlayerimp;
    }

    private void countTime() {

        isCountTime = true;
        if (timer == null) {

            timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    if (isCountTime) {
                        mHandler.sendEmptyMessage(PLAY_TIME);
                    }
                }
            }, 0, 1000);

        }

    }


    /**
     * 为了结局点击surfaceview与控制器出现与隐藏的联动bug
     * func:等上一个Runnable执行完才会执行下一个Runnable
     */

    private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            if (fl_control.getVisibility() == View.VISIBLE) {
                fl_control.setVisibility(View.GONE);
            }
        }
    };


    /**
     * 控制模块的显示或者隐藏
     */
    private void showOrHideController() {


        if (fl_control.getVisibility() == View.VISIBLE) {

            fl_control.setVisibility(View.GONE);

        } else {
            fl_control.setVisibility(View.VISIBLE);
            mHandler.removeCallbacks(mRunnable);
            mHandler.postDelayed(mRunnable, CONTROLLER_AUTO_HIDE_TIME);
        }


    }

    public void startVideo(final String uri, String previewUri) {













        try {
            this.uri = uri;
            isChongLian = false;
            if (mMediaPlayer == null) {
                Logger.e(TAG + "播放:mMediaPlayer==null");
                initMediaPlayer();
            }else{
                try {
                    mMediaPlayer.isPlaying();
                    release();
                    initMediaPlayer();
                } catch (IllegalStateException e) {
                    initMediaPlayer();
                }
                Logger.e(TAG + "播放:mMediaPlayer=isPlaying");

            }

            mMediaPlayer.setDataSource(currentContext, Uri.parse(uri));

            mMediaPlayer.prepareAsync();


        } catch (IOException e) {
            e.printStackTrace();
        }


    }

    private class MyHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            super.handleMessage(msg);
            if (msg.what == 1) {
                if (tv_speed != null) {
                    tv_speed.setText(msg.obj + "kb/s");
                }

                if (tv_currentNet != null) {
                    tv_currentNet.setText("当前网络速度:" + msg.obj + "kb/s");
                }
            }

            if (msg.what == PLAY_TIME) {
                if (mMediaPlayer != null) {
                    try {
                        long currentPosition = mMediaPlayer.getCurrentPosition();
                        if (currentPosition > 0) {
                            int progress = (int) ((currentPosition / (float) mMediaPlayer
                                    .getDuration()) * 100);
                            Log.i("PRogress::", progress + "");
                            seekbar.setProgress(progress);
                            tv_start.setText(formatTime(currentPosition));
                        } else {
                            tv_start.setText("00:00");
                            seekbar.setProgress(0);
                        }
                    } catch (IllegalStateException ex) {
                        // mMediaPlayer.release();

                        Logger.i(TAG + "计时时候出异常");
                    }

                }
            }

        }
    }






    public WytVideoPlayView(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.custom_video, this, true);
        currentContext = context;
        mHandler = new MyHandler();
        initUI(context);
        trafficBean = new TrafficBean(context, mHandler, 12580);
        trafficBean.startCalculateNetSpeed();
    }


    private void initUI(Context context) {
        myframe = (FrameLayout) findViewById(R.id.myframe);

        iv_back= (ImageView) findViewById(R.id.iv_back);



        mSurfaceView = (SurfaceView) findViewById(R.id.SurfaceView);
        // 通过surfaceHolder的addCallBack()方法来监听surfaceCreated。
        mSurfaceView.getHolder().addCallback(this);
        /**
         * 确保底层的表面是一个推送缓冲区表面,用于视频播放和摄像头的浏览
         */



        RxView.clicks(iv_back).throttleFirst(2,TimeUnit.SECONDS).subscribe(new Action1<Void>() {
            @Override
            public void call(Void aVoid) {

                if (isFullScreen){
                    //就让他还原
                    nofullScreen();
                }else {
                    //退出
                    if (wytMediaPlayerimp!=null){
                        wytMediaPlayerimp.finishCurrentView();
                    }

                }

            }
        });
        SurfaceHolder currentSurfaceHolder = mSurfaceView.getHolder();


        currentSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);


        initLoadingView();
        initMediaController();
       


    }

    private void initGestureController() {
        initGestureControllerUI();


        mSurfaceView.setOnTouchListener(this);


        gestureOperationHelper = new GestureOperationHelper() {
            @Override
            public void SingleTap() {

            }
        };

        gestureDetector = new GestureDetector(currentContext, new GestureDetector.SimpleOnGestureListener() {
            //双击
            @Override
            public boolean onDoubleTap(MotionEvent e) {
                if (isFullScreen) {
                    nofullScreen();
                    showToastTips("还原");
                } else {
                    fullScreen();
                    showToastTips("全屏");
                }
                return true;
            }

            @Override
            public void onShowPress(MotionEvent e) {
                super.onShowPress(e);
                showOrHideController();
            }

            /**
             * ,一次单独的轻击抬起操作,也就是轻击一下屏幕
             * @param e
             * @return
             */

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

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {


                if (mGestureOperationType == GestureOperationType.NONE) {

                    if (Math.abs(distanceX) > Math.abs(distanceY)) {
                        //如果X轴的绝对值大于Y轴的绝对值  说明此时在快进或者快退
                        Log.i(TAG + "手势::", "快进快退");
                        mGestureOperationType = GestureOperationType.FASTFORWARDORBACKWARD;
                    } else {

                        if (e1.getX() < mSurfaceView.getWidth() / 2) {
                            //说名在屏幕的左侧,左侧调节亮度
                            Log.i(TAG + "手势::", "亮度");

                            mGestureOperationType = GestureOperationType.BRIGHTNESS;
                        } else {
                            //右侧调节音量
                            Log.i(TAG + "手势::", "音量");
                            mGestureOperationType = GestureOperationType.VOLUME;

                        }
                    }

                }

                return DealWithScrollGestureOperation(e1, e2, distanceX, distanceY);
            }
        });

    }

    private void initGestureControllerUI() {

        /*快进与快退*/
        rl_forward = (RelativeLayout) findViewById(R.id.rl_forward);
        iv_jindu_tag = (ImageView) findViewById(R.id.iv_jindu_tag);
        tv_jindu = (TextView) findViewById(R.id.tv_jindu);
        pb_jindu = (ProgressBar) findViewById(R.id.pb_jindu);
        /*音量*/
        rl_volume = (RelativeLayout) findViewById(R.id.rl_volume);
        pb_jindu_volume = (ProgressBar) findViewById(R.id.pb_jindu_volume);
        // iv_volume_tag = (ImageView) findViewById(R.id.iv_volume_tag);

        /*亮度*/

        rl_bright = (RelativeLayout) findViewById(R.id.rl_bright);
        pb_jindu_bright = (ProgressBar) findViewById(R.id.pb_jindu_bright);


    }

    private void initLoadingView() {
        mLoadingView = findViewById(R.id.LoadingView);
        tv_speed = (TextView) findViewById(R.id.tv_speed);
    }

    private void initMediaController() {

        fl_control = (FrameLayout) findViewById(R.id.fl_control);

        fl_control.setVisibility(VISIBLE);//默认是隐藏的

        iv_play = (ImageView) findViewById(R.id.iv_play);
        //默认他应该是播放状态


        iv_full = (ImageView) findViewById(R.id.iv_full);

        iv_full.setSelected(false);//默认是缩小状态

        tv_start = (TextView) findViewById(R.id.tv_start);

        tv_end = (TextView) findViewById(R.id.tv_end);
        seekbar = (SeekBar) findViewById(R.id.seekbar);


       // seekbar.setOnSeekBarChangeListener(this);
        tv_currentNet = (TextView) findViewById(R.id.tv_currentNet);

        RxView.clicks(iv_play).throttleFirst(1, TimeUnit.SECONDS).subscribe(new Action1<Void>() {
            @Override
            public void call(Void aVoid) {
                if (iv_play.isSelected()) {
                    //正在播放
                    if (mMediaPlayer != null) {
                        Log.i(TAG, "bitrate = " + mMediaPlayer.getVideoBitrate() + " bps, fps = " + mMediaPlayer.getVideoFps() +
                                ", resolution = " + mMediaPlayer.getResolutionInline());
                        mMediaPlayer.pause();
                        iv_play.setSelected(false);
                    }
                } else {
                    //播放暂停
                    if (mMediaPlayer != null) {
                        mMediaPlayer.start();
                        iv_play.setSelected(true);
                    }

                }

            }
        });


        RxView.clicks(iv_full).throttleFirst(2, TimeUnit.SECONDS).subscribe(new Action1<Void>() {
            @Override
            public void call(Void aVoid) {

                if (!isFullScreen) {
                    //设置全屏状态,所谓全屏状态,就是将容器满屏
                    //注意点:容器满屏的高度!=屏幕高度
                    //容器满屏的高度==屏幕高度-状态栏的高度
                    fullScreen();
                } else {

                    nofullScreen();
                }
            }
        });

    }

    //全屏

    public  void fullScreen() {
        DisplayMetrics dm = getResources().getDisplayMetrics();
        Logger.i(TAG + "屏幕" + dm.widthPixels + "/" + dm.heightPixels);
        ViewGroup.LayoutParams layoutParams = getLayoutParams();
        layoutParams.width = dm.widthPixels;
        layoutParams.height = dm.heightPixels - getStatusBarHeight();
        setLayoutParams(layoutParams);
        isFullScreen = true;
        iv_full.setSelected(isFullScreen);
        mSurfaceView.setLayoutParams(layoutParams);
    }

    //还原

    public  void nofullScreen() {
        ViewGroup.LayoutParams layoutParams = getLayoutParams();
        layoutParams.width = initialWidth;
        layoutParams.height = initialHeight;
        setLayoutParams(layoutParams);
        isFullScreen = false;
        iv_full.setSelected(isFullScreen);
        mSurfaceView.setLayoutParams(layoutParams);
    }


    public WytVideoPlayView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 此时surface已创建
     *
     * @param holder
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        initAVOptions();//初始化播放参数
        initMediaPlayer();//初始化播放器
        //
    }


    private void initAVOptions() {

        mAVOptions = new AVOptions();
        //解码类型
        // 解码方式:
        // codec=AVOptions.MEDIA_CODEC_HW_DECODE,硬解
        // codec=AVOptions.MEDIA_CODEC_SW_DECODE, 软解
        // codec=AVOptions.MEDIA_CODEC_AUTO, 硬解优先,失败后自动切换到软解
        // 默认值是:MEDIA_CODEC_SW_DECODE
        mAVOptions.setInteger(AVOptions.KEY_MEDIACODEC, AVOptions.MEDIA_CODEC_AUTO);
        // 准备超时时间,包括创建资源、建立连接、请求码流等,单位是 ms
        // 默认值是:无
        mAVOptions.setInteger(AVOptions.KEY_PREPARE_TIMEOUT, 10 * 1000);


        // 读取视频流超时时间,单位是 ms
        // 默认值是:10 * 1000
        mAVOptions.setInteger(AVOptions.KEY_GET_AV_FRAME_TIMEOUT, 10 * 1000);
        // 当前播放的是否为在线直播,如果是,则底层会有一些播放优化
        // 默认值是:0
        mAVOptions.setInteger(AVOptions.KEY_LIVE_STREAMING, 0);

        // 默认的缓存大小,单位是 ms
        // 默认值是:2000
        mAVOptions.setInteger(AVOptions.KEY_CACHE_BUFFER_DURATION, 2000);
        // 最大的缓存大小,单位是 ms
        // 默认值是:4000
        mAVOptions.setInteger(AVOptions.KEY_MAX_CACHE_BUFFER_DURATION, 4000);


        // 是否自动启动播放,如果设置为 1,则在调用 `prepareAsync` 或者 `setVideoPath` 之后自动启动播放,无需调用 `start()`
        // 默认值是:1
        mAVOptions.setInteger(AVOptions.KEY_START_ON_PREPARED, 1);

        // 播放前最大探测流的字节数,单位是 byte
        // 默认值是:128 * 1024
        mAVOptions.setInteger(AVOptions.KEY_PROBESIZE, 128 * 1024);






/*        //liveStreaming 直播 1 点播0
        mAVOptions.setInteger(AVOptions.KEY_LIVE_STREAMING, 0);
        // 是否开启"延时优化",只在在线直播流中有效
        //  mAVOptions.setInteger(AVOptions.KEY_DELAY_OPTIMIZATION, 1);*/


    }

    private void initMediaPlayer() {

        /*//如果media已创建
        if (mMediaPlayer != null) {
            Logger.i(TAG + "mMediaPlayer != null");
            if (mMediaPlayer.isPlaying()) {
                mMediaPlayer.pause();
                //mMediaPlayer.start();
                mMediaPlayer.setDisplay(mSurfaceView.getHolder());
                mMediaPlayer.seekTo(mMediaPlayer.getCurrentPosition());
                Logger.i(TAG + "重连:isPlaying");
            } else {
                Logger.i(TAG + "重连:NoPlaying");
                mMediaPlayer.setDisplay(mSurfaceView.getHolder());
                mMediaPlayer.seekTo(mMediaPlayer.getCurrentPosition());
            }

            //mMediaPlayer.setDisplay(mSurfaceView.getHolder());

            return;
        *//*    mMediaPlayer.pause();
            mMediaPlayer.setDisplay(mSurfaceView.getHolder());
            mMediaPlayer.prepareAsync();
            mMediaPlayer.seekTo(mMediaPlayer.getCurrentPosition());
            Logger.i(TAG + "MediaPlayer已经创建");
            return;*//*
        }*/

        try {
            Logger.i(TAG + "mMediaPlayer == null");

            //mediaplayer没创建
            mMediaPlayer = new PLMediaPlayer(currentContext, mAVOptions);
            mMediaPlayer.setLooping(false);//不循环播放
               /* *
         * 注册一个回调函数,在seek操作完成后调用。*/
            mMediaPlayer.setOnSeekCompleteListener(this);
            //注册一个回调函数,在视频预处理完成后调用
            mMediaPlayer.setOnPreparedListener(this);


            //注册一个回调函数,在视频大小已知或更新后调用
            mMediaPlayer.setOnVideoSizeChangedListener(this);
            //监听视频播放结束
            mMediaPlayer.setOnCompletionListener(this);
            //播放视频出错的监听
            mMediaPlayer.setOnErrorListener(this);
            //注册一个回调函数,在有警告或错误信息时调用
            mMediaPlayer.setOnInfoListener(this);
            //监听事件,网络流媒体的缓冲监听
            mMediaPlayer.setOnBufferingUpdateListener(this);
            mMediaPlayer.setWakeMode(currentContext, PowerManager.PARTIAL_WAKE_LOCK);
            mMediaPlayer.setDisplay(mSurfaceView.getHolder());


           /* if (wytMediaPlayerimp != null) {
                wytMediaPlayerimp.surfaceAndMediaPlayerCreated();
            }*/

            if (isChongLian) {
                try {
                    mMediaPlayer.setDataSource(currentContext, Uri.parse(uri));
                    mMediaPlayer.prepareAsync();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //if (uri != null && !TextUtils.isEmpty(uri)) {
            Logger.i(TAG + "MediaPlayer初始化");
        } catch (UnsatisfiedLinkError e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } /*catch (IOException e) {
            e.printStackTrace();
        }*/


    }


    /**
     * 当SurfaceView 的底层表面的宽度、高度或者其他参数发生变化时  调用
     *
     * @param holder
     * @param format
     * @param width
     * @param height
     */

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.i(TAG, "surfaceChanged" + width + "/" + height);
        mSurfaceWidth = width;
        mSurfaceHeight = height;
    }

    /**
     * 当销毁 SurfaceView的底层表面时,调用
     *
     * @param holder
     */

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        releaseWithoutStop();
    }


    public void releaseWithoutStop() {
        if (mMediaPlayer != null) {
            mMediaPlayer.setDisplay(null);
        }
    }

    @Override
    public void onPrepared(PLMediaPlayer plMediaPlayer) {
        Logger.i(TAG + "视频准备好了");

        //在mediaplayer准备好的时候再初始化
        initGestureController();//初始化手势

        if (isChongLian) {
            mMediaPlayer.seekTo(chongLianPosition);
            isChongLian = false;
            showToastTips("重连成功");
        } else {
            //隐藏预览图
            mMediaPlayer.start();
            iv_play.setSelected(true);
        }
        //获取视屏的长度
        tv_end.setText(formatTime(mMediaPlayer.getDuration()));

    }


    private String formatTime(long time) {
        SimpleDateFormat formatter = new SimpleDateFormat("mm:ss");
        return formatter.format(new Date(time));
    }

    /**
     * 当视频的大小发生变化的时候调用一次
     * 当指定的数据源和读取视频的元数据后将至少调用它一次
     */
    @Override
    public void onVideoSizeChanged(PLMediaPlayer plMediaPlayer, int width, int height, int videoSar, int videoDen) {


        Log.i(TAG, "onVideoSizeChanged: width = " + width + ", height = " + height + ", sar = " + videoSar + ", den = " + videoDen);
        if (width != 0 && height != 0) {
            float ratioW = (float) width / (float) mSurfaceWidth;
            float ratioH = (float) height / (float) mSurfaceHeight;
            float ratio = Math.min(ratioW, ratioH);
            width = (int) Math.ceil((float) width / ratio);
            height = (int) Math.ceil((float) height / ratio);
            FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(width, height);
            layout.gravity = Gravity.CENTER;
            mSurfaceView.setLayoutParams(layout);
        }
    }

    @Override
    public void onCompletion(PLMediaPlayer plMediaPlayer) {
        Log.i(TAG, "Play Completed !");
        showToastTips("视频播放完毕");
        if (iv_play != null) {
            iv_play.setSelected(false);  //按钮显示暂停

        }
    }

    @Override
    public boolean onError(PLMediaPlayer plMediaPlayer, int errorCode) {
        Log.e(TAG, "Error happened, errorCode = " + errorCode);
        Log.e(TAG, "Error happened, errorCode = " + errorCode);
        boolean isNeedReconnect = false;
        switch (errorCode) {
            case PLMediaPlayer.ERROR_CODE_INVALID_URI:
                showToastTips("无效的视频链接!");
                Logger.e(TAG + "无效的视频链接");
                break;
            case PLMediaPlayer.ERROR_CODE_404_NOT_FOUND:
                showToastTips("播放资源不存在!");
                Logger.e(TAG + "播放资源不存在");
                break;
            case PLMediaPlayer.ERROR_CODE_CONNECTION_REFUSED:
                showToastTips("服务器拒绝连接!");
                Logger.e(TAG + "服务器拒绝连接");
                break;
            case PLMediaPlayer.ERROR_CODE_CONNECTION_TIMEOUT:
                showToastTips("连接超时!");
                Logger.e(TAG + "连接超时");
                isNeedReconnect = true;
                break;
            case PLMediaPlayer.ERROR_CODE_EMPTY_PLAYLIST:
                showToastTips("空的播放列表!");
                Logger.e(TAG + "空的播放列表");
                break;
            case PLMediaPlayer.ERROR_CODE_STREAM_DISCONNECTED:
                showToastTips("与服务器连接断开!");
                Logger.e(TAG + "与服务器连接断开");
                isNeedReconnect = true;
                break;
            case PLMediaPlayer.ERROR_CODE_IO_ERROR:
                showToastTips("网络异常!");
                Logger.e(TAG + "网络异常");
                isNeedReconnect = true;
                break;
            case PLMediaPlayer.ERROR_CODE_UNAUTHORIZED:
                showToastTips("未授权,播放一个禁播的流!");
                Logger.e(TAG + "未授权,播放一个禁播的流!");
                break;
            case PLMediaPlayer.ERROR_CODE_PREPARE_TIMEOUT:
                showToastTips("播放器准备超时!");
                Logger.e(TAG + "播放器准备超时");
                isNeedReconnect = true;
                break;
            case PLMediaPlayer.ERROR_CODE_READ_FRAME_TIMEOUT:
                showToastTips("读取数据超时!");
                Logger.e(TAG + "读取数据超时");
                isNeedReconnect = true;
                break;
            case PLMediaPlayer.ERROR_CODE_HW_DECODE_FAILURE:
                showToastTips("硬解码失败!");
                Logger.e(TAG + "硬解码失败");
                mAVOptions.setInteger(AVOptions.KEY_MEDIACODEC, AVOptions.MEDIA_CODEC_SW_DECODE);
                isNeedReconnect = true;
                break;
            case PLMediaPlayer.MEDIA_ERROR_UNKNOWN:
                showToastTips("未知错误!");
                Logger.e(TAG + "未知错误");
                break;
            default:
                showToastTips("unknown error !");
                break;
        }


        if (isNeedReconnect) {
            if (mMediaPlayer != null) {
                chongLianPosition = mMediaPlayer.getCurrentPosition();
            }
            release();//释放MediaPlayer
            sendReconnectMessage();

        } else {
            //不需要重连
            isChongLian = false;
            release();//释放MediaPlayer
        }

        return true;
    }


    private void sendReconnectMessage() {
        showToastTips("正在重连...");
        isChongLian = true;
        mLoadingView.setVisibility(View.VISIBLE);
        RxUtils.unsubscribe(subscription);
        subscription = Observable.timer(500, TimeUnit.MILLISECONDS).subscribe(new Action1<Long>() {
            @Override
            public void call(Long aLong) {
                if (NetUtil.getNetWorkState(currentContext) == -1) {
                    //没有网
                    Logger.i(TAG + "没网");
                    sendReconnectMessage();

                } else {

                    initMediaPlayer();
                }

            }
        });
        //  mHandler.removeCallbacksAndMessages(null);
        // mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_ID_RECONNECTING), 500);
    }

    public void release() {

        if (mMediaPlayer != null) {
            mMediaPlayer.stop();
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
    }


    private void showToastTips(final String tips) {
//        if (mIsActivityPaused) {
//            return;
//        }


        Observable.just(tips).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                if (mToast != null) {
                    mToast.cancel();
                }
                mToast = Toast.makeText(currentContext, tips, Toast.LENGTH_SHORT);
                mToast.show();
            }
        });

    }

    /**
     * 当出现关于播放媒体的特定信息或者需要发出警告的时候,将调用onInfo
     *
     * @return
     */
    @Override
    public boolean onInfo(PLMediaPlayer plMediaPlayer, int what, int extra) {
        // Log.i(TAG, "OnInfo, what = " + what + ", extra = " + extra);
        // Log.i(TAG, "OnInfo, what = " + what + ", extra = " + extra);
        switch (what) {
            case PLMediaPlayer.MEDIA_INFO_BUFFERING_START:
                //开始缓冲
                Log.i(TAG, "开始缓冲, what = " + what + ", extra = " + extra);
                mLoadingView.setVisibility(View.VISIBLE);
                isCountTime = false;
       /*         long videoBitrate = mMediaPlayer.getVideoBitrate();

                tv_speed.setText((videoBitrate / 1024) + "kb/s");*/
                break;
            case PLMediaPlayer.MEDIA_INFO_BUFFERING_END:
                //停止缓冲
                Log.i(TAG, "停止缓冲, what = " + what + ", extra = " + extra);
                mLoadingView.setVisibility(View.GONE);
                isCountTime = true;
                break;
            case PLMediaPlayer.MEDIA_INFO_UNKNOWN:
                //未知消息
                Log.i(TAG, "未知消息, what = " + what + ", extra = " + extra);
                break;
            case PLMediaPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED:
                //获取到视频的播放角度
                Log.i(TAG, "获取到视频的播放角度, what = " + what + ", extra = " + extra);
                break;
            case PLMediaPlayer.MEDIA_INFO_VIDEO_GOP_TIME:
                //获取视频的I帧间隔
                Log.i(TAG, "获取视频的I帧间隔, what = " + what + ", extra = " + extra);
                break;
            case PLMediaPlayer.MEDIA_INFO_SWITCHING_SW_DECODE:
                //硬解失败,自动切换软解
                Log.i(TAG, "硬解失败,自动切换软解, what = " + what + ", extra = " + extra);
                break;
            case PLMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START:
                // 第一帧音频已成功播放
                Log.i(TAG, "第一帧音频已成功播放, what = " + what + ", extra = " + extra);
                mLoadingView.setVisibility(View.GONE);
                // time=0;
                countTime();
                break;
            case PLMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START:

                //第一帧视频已成功渲染
                Log.i(TAG, "第一帧视频已成功渲染, what = " + what + ", extra = " + extra);
                HashMap<String, String> meta = mMediaPlayer.getMetadata();
                Log.i(TAG, "meta: " + meta.toString());
                showToastTips(meta.toString());
                break;

        /*    case PLMediaPlayer.MEDIA_INFO_DOWNLOAD_RATE_CHANGED:
                //显示 下载速度
                Logger.e("download rate:" + arg2);
                break;*/
            default:
                break;
        }
        return true;
    }

    @Override
    public void onBufferingUpdate(PLMediaPlayer plMediaPlayer, int percent) {
        Log.i(TAG, "onBufferingUpdate: " + percent + "%");
        seekbar.setSecondaryProgress(percent);
    }


    //获取状态栏的高度
    private int getStatusBarHeight() {
        int result = 0;
        int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }

    @Override
    public void onSeekComplete(PLMediaPlayer plMediaPlayer) {
        Log.i(TAG, "onSeekComplete:");
    }


    private boolean DealWithScrollGestureOperation(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {


        Logger.i("HHHGGDD::" + distanceY);

        if (isFullScreen) {
            //满屏的时候才能使用手势

            /**
             * 手势操作的时候如果控制栏是显示的,就要影长
             */
            if (fl_control.getVisibility() == View.VISIBLE) {
                fl_control.setVisibility(View.GONE);
                mHandler.removeCallbacks(mRunnable);
            }
            if (mGestureOperationType == GestureOperationType.FASTFORWARDORBACKWARD) {
                //快进,快退操作的处理

                if (mMediaPlayer == null || !mMediaPlayer.isPlaying()) {
                    //不执行

                    Logger.i(TAG + "手势:" + "不执行");
                    return false;
                }

         /*   if (iv_voice.getVisibility()==View.VISIBLE){
                iv_voice.setVisibility(View.GONE);
            }*/

                /**
                 * 向右边滑动disX为负数 向左边滑动disX为正数
                 * func:无论快进还是快退都是一分钟
                 *
                 */

                if (rl_forward.getVisibility() == GONE) {

                    rl_forward.setVisibility(VISIBLE);
                }

                long pos = mMediaPlayer.getCurrentPosition();
                long total = mMediaPlayer.getDuration();


                if (distanceX < 0) {
                    //右话
                    // iv_flag.setImageResource(R.mipmap.fast);

                    pos = pos + 1 * 10 * 1000;

                    iv_jindu_tag.setSelected(false);

                } else if (distanceX > 0) {
                    //左话
                    // iv_flag.setImageResource(R.mipmap.rewind);
                    pos = pos - 1 * 10 * 1000;
                    iv_jindu_tag.setSelected(true);
                }
                // mHandler.removeMessages(11);
                // mHandler.sendEmptyMessageDelayed(11, 5000);

                int progress = (int) ((pos / (float) total) * 100);

                pb_jindu.setProgress(progress);

                Log.i("GGGGG:", progress + " ");

                seekbar.setProgress(progress);

                if (pos < 0) {
                    pos = 0;
                } else if (pos > total) {
                    pos = total;
                }


                long currentPosition = mMediaPlayer.getCurrentPosition();
                String start = null;

                if (currentPosition > 0) {
                    start = formatTime(currentPosition);

                } else {
                    start = "00:00";

                }

                String str = new StringBuffer().append(start).append("\t/\t").append(tv_end.getText().toString()).toString();

                SpannableStringBuilder builder = new SpannableStringBuilder(str);

                //ForegroundColorSpan 为文字前景色,BackgroundColorSpan为文字背景色
                ForegroundColorSpan redSpan = new ForegroundColorSpan(Color.parseColor("#00b776"));
                builder.setSpan(redSpan, 0, str.indexOf("/"), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                tv_jindu.setText(builder);


                /**
                 * 2000ms后隐藏快进快退的界面
                 */
                final long finalPos = pos;
                RxUtils.unsubscribe(subscribe_forwardorback);
                subscribe_forwardorback = Observable.timer(1000, TimeUnit.MILLISECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<Long>() {
                    @Override
                    public void call(Long aLong) {
                        if (rl_forward.getVisibility() == VISIBLE) {
                            mMediaPlayer.seekTo(finalPos);
                            rl_forward.setVisibility(GONE);
                        }

                    }
                });

            } else if (mGestureOperationType == GestureOperationType.VOLUME) {

            /*音量*/

                if (rl_volume.getVisibility() == GONE) {
                    rl_volume.setVisibility(VISIBLE);
                }


//            iv_flag.setImageResource(R.mipmap.huatong);
//
//            iv_voice.setVisibility(View.VISIBLE);
//
//            if (imageView_main_show.getVisibility() == View.VISIBLE) {
//                imageView_main_show.setVisibility(View.GONE);
//            }
                manager = (AudioManager) currentContext.getSystemService(Context.AUDIO_SERVICE);
                int maxVolume = manager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
                int currentVolume = manager.getStreamVolume(AudioManager.STREAM_MUSIC);


                Logger.i("音量++" + distanceY + "?" + maxVolume + "/" + currentVolume);
                if (Math.abs(distanceY) >= 2) {

                    if (distanceY < 0) {
                        currentVolume--;
                    } else if (distanceY > 0) {
                        currentVolume++;
                    }
                }

                if (currentVolume >= 15) {
                    currentVolume = 15;
                    showToastTips("声音已经调到最大了,请保护耳朵奥");
                } else if (currentVolume <= 0) {
                    currentVolume = 0;
                }


                pb_jindu_volume.setMax(maxVolume);
                pb_jindu_volume.setProgress(currentVolume);
                Log.i("音量:当前", currentVolume + "");
                Log.i("音量:最大", maxVolume + "");

                manager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0);
                RxUtils.unsubscribe(subscribe_volume);
                subscribe_volume = Observable.timer(1000, TimeUnit.MILLISECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<Long>() {
                    @Override
                    public void call(Long aLong) {
                        if (rl_volume.getVisibility() == VISIBLE) {

                            rl_volume.setVisibility(GONE);
                        }

                    }
                });



            /*
             * 发现音量最小0 最大15
             */

                /**
                 * 上滑是正数,下滑是负数
                 */
           /* float percent = distanceY / (float) mSurfaceView.getMeasuredHeight();

            float volumeOffsetAccurate = maxVolume * percent;

            int volumeOffset = (int) volumeOffsetAccurate;

            Logger.i("音量:"+"percent::"+percent+"/volumeOffsetAccurate:"+volumeOffsetAccurate+"/volumeOffset"+volumeOffset);


            if (volumeOffset == 0 && Math.abs(volumeOffsetAccurate) > 0.2f) {
                if (distanceY > 0) {
                    volumeOffset = 1;
                } else if (distanceY < 0) {
                    volumeOffset = -1;
                }
            }

            currentVolume += volumeOffset;
            if (currentVolume < 0) {
                currentVolume = 0;
            } else if (currentVolume >= maxVolume) {
                currentVolume = maxVolume;
            }







*/

         /*   if (currentVolume == 0) {
                iv_voice.setImageResource(R.mipmap.vc_0);
            } else if (currentVolume <= 2) {
                iv_voice.setImageResource(R.mipmap.vc_1);
            } else if (currentVolume <= 4) {
                iv_voice.setImageResource(R.mipmap.vc_2);
            } else if (currentVolume <= 6) {
                iv_voice.setImageResource(R.mipmap.vc_3);
            } else if (currentVolume <= 8) {
                iv_voice.setImageResource(R.mipmap.vc_4);
            } else if (currentVolume <= 11) {
                iv_voice.setImageResource(R.mipmap.vc_5);
            } else if (currentVolume <= 14) {
                iv_voice.setImageResource(R.mipmap.vc_6);
            } else if (currentVolume == 15) {
                iv_voice.setImageResource(R.mipmap.vc_7);
            }*/


            } else {
                if (mGestureOperationType == GestureOperationType.BRIGHTNESS) {


                    if (rl_bright.getVisibility() == GONE) {
                        rl_bright.setVisibility(VISIBLE);
                    }

                    // float percent = distanceY / (float) mSurfaceView.getMeasuredHeight();
                    int currentBrightness = BrightnessUtils.getScreenBrightness(currentContext);
                    Log.i("FFFFFWW", currentBrightness + "");

                    if (Math.abs(distanceY) >= 2) {
                        if (distanceY < 0) {
                            currentBrightness -= 10;
                        } else if (distanceY > 0) {
                            currentBrightness += 10;
                        }
                    }

                    pb_jindu_bright.setMax(255);

                    pb_jindu_bright.setProgress(currentBrightness);

                    BrightnessUtils.setSystemBrightness(currentContext, currentBrightness);


                    RxUtils.unsubscribe(subscribe_bright);

                    subscribe_bright = Observable.timer(1000, TimeUnit.MILLISECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<Long>() {
                        @Override
                        public void call(Long aLong) {
                            if (rl_bright.getVisibility() == VISIBLE) {
                                rl_bright.setVisibility(GONE);
                            }

                        }
                    });
                }
            }


        }


        return true;
    }
}

custom_video

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/myframe"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <SurfaceView
        android:id="@+id/SurfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center" />


 <!--   <ImageView
        android:id="@+id/iv_preview"
        android:visibility="gone"
        android:scaleType="centerCrop"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />-->


    <ImageView
        android:id="@+id/iv_back"
        android:layout_width="46dp"
        android:layout_height="46dp"
        android:src="@drawable/iv_style_01"
        android:scaleType="fitCenter"
        android:layout_marginLeft="24dp"
        android:layout_marginTop="10dp" />


    <LinearLayout
        android:id="@+id/LoadingView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:orientation="horizontal">

        <ProgressBar
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:layout_marginRight="5dp"
            android:indeterminateDrawable="@drawable/progressbar_color" />

        <TextView
            android:id="@+id/tv_speed"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dp"
            android:textColor="@android:color/white"
            android:textSize="20sp" />
    </LinearLayout>





    <include layout="@layout/forwordorbackword"></include>
    <include layout="@layout/volume"></include>
    <include layout="@layout/brightness"></include>



    <FrameLayout
        android:id="@+id/fl_control"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_gravity="bottom"
        android:background="@drawable/bg_controlbar"
        android:visibility="gone">


        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <ImageView
                android:id="@+id/iv_play"
                android:layout_width="23dp"
                android:layout_height="23dp"
                android:layout_alignParentLeft="true"
                android:layout_centerVertical="true"
                android:layout_marginLeft="10dp"
                android:clickable="true"
                android:scaleType="fitCenter"
                android:src="@drawable/iv_play_selected" />


            <ImageView
                android:id="@+id/iv_full"
                android:layout_width="23dp"
                android:layout_height="23dp"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:layout_marginRight="10dp"
                android:clickable="true"
                android:scaleType="fitCenter"
                android:src="@drawable/imageview_fullscreen" />

            <TextView
                android:id="@+id/tv_end"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginRight="23dp"
                android:layout_toLeftOf="@+id/iv_full"
                android:text="00:00"
                android:textColor="#cfcfcf"
                android:textSize="16sp" />


            <TextView
                android:id="@+id/tv_start"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginLeft="25dp"
                android:layout_toRightOf="@+id/iv_play"
                android:text="00:00"
                android:textColor="#cfcfcf"
                android:textSize="16sp" />


            <SeekBar
                android:id="@+id/seekbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginLeft="12dp"
                android:layout_marginRight="12dp"
                android:layout_toLeftOf="@+id/tv_end"
                android:layout_toRightOf="@+id/tv_start"
                android:max="100"
                android:maxHeight="10dp"
                android:minHeight="6dp"
                android:progressDrawable="@drawable/seekbar_style_01"
                android:thumb="@drawable/seekbar_thumb_style_01" />


            <TextView
                android:id="@+id/tv_currentNet"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignRight="@+id/seekbar"
                android:layout_below="@+id/seekbar"
                android:textColor="@android:color/white"
                android:textSize="12sp" />


        </RelativeLayout>


    </FrameLayout>


</FrameLayout>
我遇到的问题:

1.因为是平板端,默认都是横屏的,所以我就需要什么重力切换横竖屏的功能,只需要将屏幕切换到满屏以及还原。所以我必须要记录屏幕的宽高以及原来组件的宽高。测量组件宽高的代码宽高之测量一次():

  ViewTreeObserver vto2 = ll_video.getViewTreeObserver();
        vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                ll_video.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                Logger.i("当前宽度DDD:" + ll_video.getWidth() + "/" + ll_video.getHeight());
                wytVideoPlayView.setInitialWidth(ll_video.getWidth());
                wytVideoPlayView.setInitialHeight(ll_video.getHeight());
                ViewGroup.LayoutParams layoutParams = wytVideoPlayView.getLayoutParams();
                layoutParams.width = ll_video.getWidth();
                layoutParams.height = ll_video.getHeight();
                wytVideoPlayView.setLayoutParams(layoutParams);
            }
        });
至于屏幕的宽高,这里的高度必须要减去系统栏的高度:
    //获取状态栏的高度
    private int getStatusBarHeight() {
        int result = 0;
        int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }
 满屏操作
    public  void fullScreen() {
        DisplayMetrics dm = getResources().getDisplayMetrics();
        Logger.i(TAG + "屏幕" + dm.widthPixels + "/" + dm.heightPixels);
        ViewGroup.LayoutParams layoutParams = getLayoutParams();
        layoutParams.width = dm.widthPixels;
        layoutParams.height = dm.heightPixels - getStatusBarHeight();
        setLayoutParams(layoutParams);
        isFullScreen = true;
        iv_full.setSelected(isFullScreen);
        mSurfaceView.setLayoutParams(layoutParams);
    }

2.用于缓冲的加载的view究竟什么时候显示:

我是在缓冲的时候显示,在缓冲结束隐藏,在获取第一帧图隐藏

3.缓冲的时候如何加载当前网速:

package utils;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.TrafficStats;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.math.BigDecimal;
import java.util.Timer;
import java.util.TimerTask;

/**
 * Created by hcy on 2017/5/8 0008.
 */

public class TrafficBean {
    /**
     * static long getMobileRxBytes()//获取通过Mobile连接收到的字节总数,但不包含WiFi static long
     * getMobileRxPackets()//获取Mobile连接收到的数据包总数 static long
     * getMobileTxBytes()//Mobile发送的总字节数 static long
     * getMobileTxPackets()//Mobile发送的总数据包数 static long
     * getTotalRxBytes()//获取总的接受字节数,包含Mobile和WiFi等 static long
     * getTotalRxPackets()//总的接受数据包数,包含Mobile和WiFi等 static long
     * getTotalTxBytes()//总的发送字节数,包含Mobile和WiFi等 static long
     * getTotalTxPackets()//发送的总数据包数,包含Mobile和WiFi等 static long
     * getUidRxBytes(int uid)//获取某个网络UID的接受字节数 static long getUidTxBytes(int
     * uid) //获取某个网络UID的发送字节数
     */

    /**
     * 不支持状态【标识变量】
     */
    private static final int UNSUPPORT = -1;
    /**
     * 打印信息标志
     */
    private static final String TAG = "TrafficBean";
    /**
     * 当前对象实例
     */
    private static TrafficBean instance;
    /**
     * 当前应用的uid
     */
    static int UUID;
    /**
     * 上一次记录网络字节流
     */
    private long preRxBytes = 0;
    /**
     *
     */
    private Timer mTimer = null;
    /**
     * 上下文对象
     */
    private Context context;
    /**
     * 消息处理器
     */
    private Handler handler;
    /**
     * 更新频率
     */
    private final int UPDATE_FREQUENCY = 1;
    private int times = 1;


    /**
     * 构造方法
     *
     * @param context
     * @param handler
     * @param uid
     */
    public TrafficBean(Context context, Handler handler, int uid) {
        this.context = context;
        this.handler = handler;
        this.UUID = uid;
    }

    public TrafficBean(Context context, Handler handler) {
        this.context = context;
        this.handler = handler;
    }

    /**
     * 获取实例对象
     *
     * @param context
     * @param handler
     * @return
     */
    public static TrafficBean getInstance(Context context, Handler handler) {
        if (instance == null) {
            instance = new TrafficBean(context, handler);
        }
        return instance;
    }

    /**
     * 获取总流量
     *
     * @return
     */
    public long getTrafficInfo() {
        long recTraffic = UNSUPPORT;//下载流量
        long sendTraffic = UNSUPPORT;//上传流量
        recTraffic = getRecTraffic();
        sendTraffic = getSendTraffic();

        if (recTraffic == UNSUPPORT || sendTraffic == UNSUPPORT) {
            return UNSUPPORT;
        } else {
            return recTraffic + sendTraffic;
        }
    }

    /**
     * 获取上传流量
     *
     * @return
     */
    private long getSendTraffic() {
        long sendTraffic = UNSUPPORT;
        sendTraffic = TrafficStats.getUidTxBytes(UUID);
        if (sendTraffic == UNSUPPORT) {
            return UNSUPPORT;
        }
        RandomAccessFile rafSend = null;
        String sndPath = "/proc/uid_stat/" + UUID + "/tcp_snd";
        try {
            rafSend = new RandomAccessFile(sndPath, "r");
            sendTraffic = Long.parseLong(rafSend.readLine());
        } catch (FileNotFoundException e) {
            Log.e(TAG, "FileNotFoundException: " + e.getMessage());
            sendTraffic = UNSUPPORT;
        } catch (IOException e) {
            Log.e(TAG, "IOException: " + e.getMessage());
            e.printStackTrace();
        } finally {
            try {
                if (rafSend != null)
                    rafSend.close();
            } catch (IOException e) {
                Log.w(TAG, "Close RandomAccessFile exception: " + e.getMessage());
            }
        }
        return sendTraffic;
    }

    /**
     * 获取下载流量
     * 某个应用的网络流量数据保存在系统的
     * /proc/uid_stat/$UID/tcp_rcv | tcp_snd文件中
     *
     * @return
     */
    private long getRecTraffic() {
        long recTraffic = UNSUPPORT;
        recTraffic = TrafficStats.getUidRxBytes(UUID);
        if (recTraffic == UNSUPPORT) {
            return UNSUPPORT;
        }
        Log.i(TAG, recTraffic + " ---1");
        //访问数据文件
        RandomAccessFile rafRec = null;
        String rcvPath = "/proc/uid_stat/" + UUID + "/tcp_rcv";
        try {
            rafRec = new RandomAccessFile(rcvPath, "r");
            recTraffic = Long.parseLong(rafRec.readLine()); // 读取流量统计
        } catch (FileNotFoundException e) {
            Log.e(TAG, "FileNotFoundException: " + e.getMessage());
            recTraffic = UNSUPPORT;
        } catch (IOException e) {
            Log.e(TAG, "IOException: " + e.getMessage());
            e.printStackTrace();
        } finally {
            try {
                if (rafRec != null)
                    rafRec.close();
            } catch (IOException e) {
                Log.w(TAG, "Close RandomAccessFile exception: " + e.getMessage());
            }
        }
        Log.i("test", recTraffic + "--2");
        return recTraffic;
    }


    /**
     * 获取当前下载流量总和
     *
     * @return
     */
    public static long getNetworkRxBytes() {
        return TrafficStats.getTotalRxBytes();
    }

    /**
     * 获取当前上传流量总和
     *
     * @return
     */
    public static long getNetworkTxBytes() {
        return TrafficStats.getTotalTxBytes();
    }

    /**
     * 获取当前网速
     *
     * @return
     */
    public double getNetSpeed() {
        long curRxBytes = getNetworkRxBytes();
        if (preRxBytes == 0)
            preRxBytes = curRxBytes;
        long bytes = curRxBytes - preRxBytes;
        preRxBytes = curRxBytes;
        //int kb = (int) Math.floor(bytes / 1024 + 0.5);
        double kb = (double) bytes / (double) 1024;
        BigDecimal bd = new BigDecimal(kb);

        return bd.setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 开启流量监控
     */
    public void startCalculateNetSpeed() {
        preRxBytes = getNetworkRxBytes();
        if (mTimer != null) {
            mTimer.cancel();
            mTimer = null;
        }
        if (mTimer == null) {
            mTimer = new Timer();
            mTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    if (times == UPDATE_FREQUENCY) {
                        Message msg = new Message();
                        msg.what = 1;
                        //msg.arg1 = getNetSpeed();
                        msg.obj = getNetSpeed();
                        handler.sendMessage(msg);
                        times = 1;
                    } else {
                        times++;
                    }
                }
            }, 1000, 1000);
        }
    }

    /**
     * 停止网速监听计算
     */
    public void stopCalculateNetSpeed() {
        if (mTimer != null) {
            mTimer.cancel();
            mTimer = null;
        }
    }

    /**
     * 获取当前应用uid
     *
     * @return
     */
    public int getUid() {
        try {
            PackageManager pm = context.getPackageManager();
            //修改
            ApplicationInfo ai = pm.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
            return ai.uid;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return -1;
    }
}

4.手势滑动不准怎么解决:
 gestureDetector = new GestureDetector(currentContext, new GestureDetector.SimpleOnGestureListener() {
            //双击
            @Override
            public boolean onDoubleTap(MotionEvent e) {
                if (isFullScreen) {
                    nofullScreen();
                    showToastTips("还原");
                } else {
                    fullScreen();
                    showToastTips("全屏");
                }
                return true;
            }

            @Override
            public void onShowPress(MotionEvent e) {
                super.onShowPress(e);
                showOrHideController();
            }

            /**
             * ,一次单独的轻击抬起操作,也就是轻击一下屏幕
             * @param e
             * @return
             */

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

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {


                if (mGestureOperationType == GestureOperationType.NONE) {

                    if (Math.abs(distanceX) > Math.abs(distanceY)) {
                        //如果X轴的绝对值大于Y轴的绝对值  说明此时在快进或者快退
                        Log.i(TAG + "手势::", "快进快退");
                        mGestureOperationType = GestureOperationType.FASTFORWARDORBACKWARD;
                    } else {

                        if (e1.getX() < mSurfaceView.getWidth() / 2) {
                            //说名在屏幕的左侧,左侧调节亮度
                            Log.i(TAG + "手势::", "亮度");

                            mGestureOperationType = GestureOperationType.BRIGHTNESS;
                        } else {
                            //右侧调节音量
                            Log.i(TAG + "手势::", "音量");
                            mGestureOperationType = GestureOperationType.VOLUME;

                        }
                    }

                }

                return DealWithScrollGestureOperation(e1, e2, distanceX, distanceY);
            }
        });

要加上这一段:

  /**
     * surfaceView的Touch监听
     *
     * @param v
     * @param event
     * @return
     */
    @Override
    public boolean onTouch(View v, MotionEvent event) {

        gestureDetector.onTouchEvent(event);

        int action = event.getActionMasked();
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            mGestureOperationType = GestureOperationType.NONE;
        }
        return true;
    }

5.缓存:这个播放器不支持缓存,但它基于的 ijkplayer 是提供的。我用的是这个

                https://github.com/ksvc/ksyhttpcache_android




                

         




          


                  

                  

      

        

Android MediaPlayer+SurfaceView+自定义控制器实现视频播放 VideoView继承了SurfaceView同时实现了MediaPlayerControl接口,MediaController则是安卓封装的辅助控制器,带有暂停,播放,停止,进度条等控件。本demo基于Android11开发,实现了简单的视频播放功能,可以拖动进度条,快进/快退和播放上一个视频/播放下一个视频,同时通过悬浮窗的形式查看视频详情。5、将展示视频详情的布局及组件初始化,创建WindowManager对象并设置窗口对应的值,将设置好的值和布局添加进WindowManager对象。 阅读详情

相关推荐

Waymo数据集里的隐藏彩蛋:5个激光雷达数据融合实战(附Python代码)

本文深入解析Waymo数据集中5个激光雷达的数据融合技术,提供Python实战代码,帮助开发者掌握多传感器协同工作的关键方法。通过时间同步、坐标转换和运动补偿等技术,实现高精度3D场景重建,提升自动驾驶系统的环境感知能力。

weixin_30399821的博客 79

28、从头学Android之多媒体--使用MediaPlayer和SurfaceView播放视频

应用场景: 使用VideoView播放视频简单、方便,但除此之外,我们还可以利用MediaPlayer用来播放视频。但是我们知道MediaPlayer主要用于播放音频,它是没有提供输出图像的输出界面,这时我们就用到了SurfaceView控件,将它与MediaPlayer结合起来,就能达到了视频的输出了。首先来了SurfaceView这个控件类 SurfaceView类 构造方法 ...

mmdev 280

告别OpenAI API调用:手把手在本地CPU/GPU上部署fast-whisper中文语音识别模型

本文详细指导如何在本地CPU/GPU上部署fast-whisper中文语音识别模型,摆脱对OpenAI API的依赖。从环境准备、模型获取与优化,到CPU/GPU环境的具体部署步骤和性能优化技巧,全面覆盖本地化语音识别的实现方案,帮助开发者提升效率并保障数据隐私。

weixin_29988907的博客 305

Android 视频播放器,在线播放

1、Bilibili https://github.com/Bilibili/ijkplayer 1、测试的时候总是崩溃,不知道是我不会用还是怎么回事.2016-04-15 2、AndroidVideoPlayer https://github.com/xiongwei-git/AndroidVideoPlayer 3、UniversalVideoV...

weixin_30670151的博客 241

Unity实时控制佳能单反拍照全流程(附EDSDK 3.6.1配置避坑指南)

本文详细介绍了在Unity中实时控制佳能单反相机进行拍照的完整流程,重点提供了EDSDK 3.6.1的配置与避坑指南。内容涵盖从SDK获取、环境搭建、相机初始化、参数设置到实现拍照控制,并深入探讨了实时取景的延迟优化方案与资源管理,旨在帮助开发者高效集成专业相机,提升AR/VR及摄影测量等应用的视觉输入质量。

go5gopher的博客 184

Flink on k8s 环境搭建(一)

Flink on Yarn的环境搭建过程中,需要进行配置较多,且需要搭建zookeeper Hadoop Yarn 等相关组件,安装流程比较复杂,集群出现问题重新安装的流程也比较复杂,且Yarn的3个节点中 只能起了 3个resourceManager和1个NodeManager,Flink 作业申请资源时只能 向NodeManager的节点申请资源,整体有资源瓶颈的隐患(后继flink作业会越来越多),现在尝试进行Flink on k8s 的环境搭建。

wangqiaowq的博客 3413

一天开发一个播放器Demo:PLDroidPlayer实战

上次对播放器ijkPlayer和PLDroidPlayer进行了评测,总体来说: ijkPlayer可以自己裁剪编译,灵活性较好; PLDroidPlayer不能裁剪,所以不需要编译,集成简单 PLDroidPlayer首开速度要快 PLDroidPlayer在软解码条件下CPU和内存消耗较少 这次,我们和PLDroidPlayer玩耍了一下,集成了PLDroidPlayer的SDK,并且P...

weixin_33874713的博客 713

深度学习-161-Dify工具之对比使用工作流和聊天流生成图表可视化的html文件

分别使用聊天流和工作流进行数据分析结果的可视化,生成html文件。聊天流可以配置带记忆,这样就可以多次交互优化结果。

qq_20466211的博客 1709

Android多媒体功能开发(9)——使用SurfaceView控件+MediaPlayer类播放视频

使用SurfaceView控件+MediaPlayer类播放视频的主要原理是把MediaPlayer对象的显示控件设置为SurfaceView对象。播放控制都是针对MediaPlayer对象,用法与播放音频相同。

nanoage的博客 2928

高电压技术的面试内容整理(详细版本:57题)

涵盖了高电压技术的关键领域,包括气体放电的物理过程、液体和固体介质的绝缘特性、高压外绝缘的分类与特性、电气设备绝缘试验的方法与原理、电力系统的防雷保护措施,以及内部过电压的产生、影响与限制方法,为理解和应对高电压环境下的电气问题提供了系统的知识框架。通过深入探讨气体放电理论、介质极化与损耗、绝缘子的电气特性、绝缘试验技术、防雷策略以及内部过电压的管理。

weixin_44808274的博客 775

2026滑块验证码终极实战:缺口检测算法+人机轨迹模拟,识别率从60%飙升至95%全方案

**摘要:2026年AIGC技术发展导致传统验证码失效,滑块验证码成为主流防护手段。本文深入分析了传统方案识别率低的原因,并提出多级检测融合和行为拟合优化方案。通过图像预处理优化、三级缺口检测引擎(差分图像法、Hu矩轮廓匹配、YOLOv8n模型兜底)将识别率提升至99.2%。同时针对轨迹模拟环节,设计了包含抖动特征、时间随机性和个体差异的行为模型,使通过率从12%提升至87%。该方案解决了传统方法在缺口定位精度和轨迹真实性方面的核心痛点,为应对新一代验证码风控提供了完整的技术框架。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1898

android开发笔记之多媒体—MediaPlayer + SurfaceView播放视频

前面我们讲了用VideoView来播放视频,这里再介绍一种方式:MediaPlayer + SurfaceView 来播放视频,我们都知道:MediaPlayer 是用来播放音频的SurfaceView 是用来绘制界面的所以这里介绍它们结合起来就能播放视频了,话不多说使用步骤: 1)在界面的布局文件中定义SurfaceView组件。和播放按钮 2)创建MediaPlayer对象,加载指定的视频文

minigeek的博客 1806

7、初级视觉皮层(V1)的研究综述

本文综述了初级视觉皮层(V1)的基本功能特性、计算理解、细胞相互作用及理论模型。V1不仅执行方向和空间频率的选择性滤波,还通过相位选择性和上下文调制参与高级视觉处理。研究从信息编码角度解释了V1感受野的形成,并介绍了基于自然图像统计的建模方法。横向与反馈连接揭示了V1在全局视觉整合中的作用,尤其是上下文对细胞响应的快速调节。李的动态模型提出V1可生成视觉显著性图,支持前注意分割和注意力引导。未来研究聚焦于V1在注意力、意识和硬件实现中的潜在应用,推动视觉神经科学与人工智能的交叉发展。

7z8x2c4v6b9的博客 144

android音视频播放器开发百度云,Android 播放端 SDK

1 概述PLDroidPlayer 是一个适用于 Android 平台的音视频播放器 SDK,可高度定制化和二次开发,为 Android 开发者提供了简单、快捷的接口,帮助开发者在 Android 平台上快速开发播放器应用。1 功能特性及版本号功能描述版本PLMediaPlayer类似 Android MediaPlayer,提供了播放器的核心功能1.2.0+PLVideoView类似 Andro...

weixin_36379029的博客 3516

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Android 学习使用 SurfaceView 和 MediaPlayer 播放视频

记录下自己学习用的demo , 具体分析后续补上。 package lah.surfaceviewdemo; import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; import android.content.Context; import android.graphics.BitmapFactory; import android.graphics.Can

weixin_44021334的博客 845

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Android移动开发-使用MediaPlayer和SurfaceView播放视频的实现

使用VideoView播放视频简单、方便,但有些早期的开发者还是更喜欢使用MediaPlayer来播放视频,但由于MediaPlayer主要用于播放音频,因此它没有提供图像输出界面,此时就需要借助于SurfaceView来显示MediaPlayer播放的图像输出。 使用MediaPlayer播放视频的步骤如下: Step1:创建MediaPlayer对象,并让它加载指定的视频文件;

_彼岸雨敲窗_的博客 2301

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Android - SurfaceView + MediaPlayer实现分段视频无缝播放

Android当中实现视频播放的方式有两种,即:通过VideoView实现或者通过SurfaceView + MediaPlayer实现。 由浅至深,首先来看下想要在Android上播放一段视频,我们应当怎么做。 前面我们已经提到了两种方式,这里我们来看一下具有更好的拓展性的第二种方式,也就是通过SurfaceView + MediaPlayer进行实现。 首先,我们来定义一

LaterEqualsNever 1万+

Android_UI开发总结(九):MediaPlayer+SurfaceView+seekbar实现视频播放

package org.test import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import and...

技术总结之路~ 606

Android之 MediaPlayer和SurfaceView播放视频

1、创建MediaPlayer对象,并让它加载指定的视频文件。 2、在界面布局中定义SurfaceView组件,或在程序中创建SurfaceView组件,并为SurfaceView的SurfaceHolder添加Callback监听器。 3、调用MediaPlayer对象的setDisplay(SurfaceHolder sh)将所播放的视频图像输出到指定的SurfaceV...

疯狂.NET 173

Android 使用MediaPlayer与SurfaceView进行视频播放

代码 public class MainActivity extends Activity implements OnBufferingUpdateListener, OnCompletionListener, OnErrorListener { SurfaceView surfaceView; MediaPlayer mPlayer; // 记录当前视频的播放位置

Swer 1158

Android之SurfaceView实现视频播放

1.案例一 布局文件: xml version="1.0" encoding="utf-8"?> LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" and

出头天 1289

Android - SurfaceView+MediaPlayer、VideoView 2种方法播放视频

Android当中实现视频播放的方式有两种,即:通过VideoView实现或者通过SurfaceView + MediaPlayer实现。 首先为大家介绍SurfaceView+MediaPlayer播放视频的方法,这种方法和我的上一篇博文播放音频差不多,只是需要将媒体播放捕捉的画面展示到surfaceView控件上即可,加一句代码即可 mediaPlayer.setDisplay

Spanluobo的博客 1232

安卓本地视频播放SurfaceView+MediaPlayer

MainActivity public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback{ private SurfaceView sfv; private MediaPlayer mediaPlayer; private SurfaceHolder holder

無趣的博客 379

MediaPlayer+SurfaceView实现视频播放器

我这个人走得很慢,但是我从不后退。 之前给大家分享过使用MediaController类实现视频播放器,今天呢给大家分享可以自定义的视频播放器,那就是使用MediaPlayer+SurfaceView来实现。废话不多说,直接上代码。 目录1. 设计思路2. 设计布局3. 实现逻辑4. 权限获取5. Demo展示 1. 设计思路 使用 MediaPlayer+SurfaceView实现视频播放器的流程如图所示,在学习音视频播放器的时候,一个老师画的图,在此直接借用。 2. 设计布局 <?xml v.

小曾同学的博客 2382

Android 播放视频(一)SurfaceView + MediaPlayer

最新项目中使用到了Android自带的Mediaplayer,所以在这里写了一个小Demo供大家参考使用。Java代码部分package club.anhe.surfaceviewdemo.surfaceviewdemo;import android.media.AudioManager; import android.media.MediaPlayer; import android.suppor

kingroc的博客 1505

Android 视频播放之MediaPlayer+SurfaceView

之前用Android系统提供的视频播放组件VideoView实现了播放视频的功能,当然,Android播放视频不只有一种实现方式,今天就记录另一种播放视频的demo。点击下载 1、效果图:这里简单一点,就放一张图片吧,视频资源前两天不小心删掉了,也就显示不出来,不过功能是没问题的。2、分析代码:

xingpidong 1289
上一篇: Android 积累一些常见的开发异常与问题
下一篇: android 积累一些自定义view的知识
泡温泉的咸鱼
博客等级 码龄11年 39粉丝 48原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值