Android VideoView播放网络视频

Android系统API已经给我们封装简单播放视频的控件,常用的VideoView和MediaPlayer+SurfaceView,当然如果需要支持各种格式的视频文件播放,那么这两种方式就有点不太那么友好了,那么你可以选择IjkPlayer、或者还有其他的类库比如维他命等

本文只介绍VideoView的使用:

添加权限

<uses-permission android:name="android.permission.INTERNET" />

播放视频的布局

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

    <com.jbh.zhaotuomobile.ui.view.MyVideoView
        android:id="@+id/mVideoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center" />

    <ImageView
        android:id="@+id/placeholder"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="@color/pageColor"/>

    <ImageView
        android:id="@+id/play"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/icon_video_play"
        android:layout_gravity="center"/>

    <ProgressBar
        android:id="@+id/progress"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="gone"/>


    <LinearLayout
        android:id="@+id/lay_playControl"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dimen_40"
        android:background="#54000000"
        android:layout_gravity="bottom"
        android:paddingRight="@dimen/dp_10"
        android:paddingLeft="@dimen/dp_10"
        android:visibility="gone"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tv_current_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="00:00:00"
            android:textColor="@color/white"
            android:layout_marginRight="@dimen/dp_10"
            android:layout_marginLeft="@dimen/dp_10"/>

        <SeekBar
            android:id="@+id/mSeekBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>

        <TextView
            android:id="@+id/tv_total_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="00:00:00"
            android:textColor="@color/white"
            android:layout_marginRight="@dimen/dp_10"
            android:layout_marginLeft="@dimen/dp_10"/>
    </LinearLayout>

    <ProgressBar
        android:id="@+id/mCurrentProgressBar"
        android:layout_width="match_parent"
        android:layout_height="3dp"
        android:max="100"
        android:layout_gravity="bottom"
        android:progressDrawable="@drawable/progressbar_bg"
        style="?android:attr/progressBarStyleHorizontal"/>
</FrameLayout>

VideoView满屏播放实现的方法


import android.content.Context;
import android.util.AttributeSet;
import android.widget.VideoView;

public class MyVideoView extends VideoView {
    public MyVideoView(Context context) {
        super(context);
    }

    public MyVideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //Log.i("@@@@", "onMeasure");
                int width = getDefaultSize(getWidth(), widthMeasureSpec);
                int height = getDefaultSize(getHeight(), heightMeasureSpec);
               /**//*if (mVideoWidth > 0 && mVideoHeight > 0) {
            if ( mVideoWidth * height  > width * mVideoHeight ) {
                //Log.i("@@@", "image too tall, correcting");
                height = width * mVideoHeight / mVideoWidth;
            } else if ( mVideoWidth * height  < width * mVideoHeight ) {
                //Log.i("@@@", "image too wide, correcting");
                width = height * mVideoWidth / mVideoHeight;
            } else {
                //Log.i("@@@", "aspect ratio is correct: " +
                        //width+"/"+height+"="+
                        //mVideoWidth+"/"+mVideoHeight);
            }
        }*/

//Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);
        setMeasuredDimension(width,height);
    }
}

设置视频来源

  videoView.setVideoURI(Uri.parse(url));

这时候的VideoView显示的是一坨黑,用户体验肯定不过关了,那需要怎么处理呢,我们可以在点击播放视频之前给一张预览图片,预览图可以从视频源中提取某一帧就OK了,

获取视频中的某一帧的方法

 /**
     * 显示预览图片
     *
     * @param url
     */
    public void getPreviewImage(final String url){
        new Thread(new Runnable() {
            @Override
            public void run() {
                MediaMetadataRetriever retriever = new MediaMetadataRetriever();
                Bitmap bitmap = null;
                try {
                    //这里要用FileProvider获取的Uri
                    if (url.contains("http")) {
                        retriever.setDataSource(url, new HashMap<String, String>());
                    } else {
                        retriever.setDataSource(url);
                    }
                    bitmap = retriever.getFrameAtTime();

                    final Bitmap finalBitmap = bitmap;
                    Observable.empty().subscribeOn(AndroidSchedulers.mainThread())
                            .doOnCompleted(new Action0() {
                        @Override
                        public void call() {
                            placeholder.setImageBitmap(finalBitmap);
                        }
                    }).subscribe();

                } catch (Exception ex) {
                    ex.printStackTrace();
                } finally {
                    try {
                        retriever.release();
                    } catch (RuntimeException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }).start();
    }

设置一堆的监听

        /*准备完成后回调*/
        videoView.setOnPreparedListener(this);
        /*播放内容监听*/
        videoView.setOnInfoListener(this);
        /*播放完成回调*/
        videoView.setOnCompletionListener(this);

当网络比较慢,那播放视频需要缓冲加载,以下为缓冲加载转圈的一种常用的方式,

 int oldPosition ;
    
 private Handler handler=new Handler();

    public void sendTime() {
        handler.postDelayed(runnable,200);
    }

    private Runnable runnable=new Runnable() {

        @Override
        public void run() {
            sendTime();
            int currentPosition = videoView.getCurrentPosition();
            if (videoView.isPlaying()){
                tv_current_time.setText(playCurrentTime());
                if (currentPosition==oldPosition){
                    progressBar.setVisibility(VISIBLE);
                }else {
                    progressBar.setVisibility(GONE);
                    if (placeholder!=null)
                        placeholder.setVisibility(GONE);
                }
                oldPosition  = currentPosition ;
            }
        }
    };

完整的实现列子:


import android.content.Context;
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.RequiresApi;
import com.jbh.zhaotuomobile.R;
import com.jbh.zhaotuomobile.util.LogUtils;

import java.util.Formatter;
import java.util.HashMap;
import java.util.Locale;

import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;

public class MaterialVideoView extends LinearLayout implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener,MediaPlayer.OnInfoListener{

    /*测试地址*/
    public static final String url="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4";
    public static final int VIDEO_TYPE_URI = 1 ;
    public static final int VIDEO_TYPE_FILE_PATH = 2 ;
    public int VIDEO_TYPE;
    private boolean isCompletion = false;
    private SeekBar mSeekBar;
    private ProgressBar progressBar;
    private MyVideoView videoView;
    private ImageView play;
    private LinearLayout lay_playControl;
    private TextView tv_current_time;
    private TextView tv_total_time;
    private ImageView placeholder;
    private ProgressBar mCurrentProgressBar;


    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
    public MaterialVideoView(Context context) {
        this(context,null);
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
    public MaterialVideoView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
    public MaterialVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOrientation(VERTICAL);
        setGravity(Gravity.CENTER);
        setBackgroundColor(getResources().getColor(R.color.black));
        View view = LayoutInflater.from(context).inflate(R.layout.material_video_layout,null);
        initView(view);
        addView(view);
    }


    /**
     * @param view
     */
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
    private void initView(View view) {
        tv_total_time = view.findViewById(R.id.tv_total_time);
        tv_current_time = view.findViewById(R.id.tv_current_time);
        lay_playControl = view.findViewById(R.id.lay_playControl);
        mSeekBar = view.findViewById(R.id.mSeekBar);
        mCurrentProgressBar = view.findViewById(R.id.mCurrentProgressBar);
        placeholder = view.findViewById(R.id.placeholder);
        SeekBarListener();
        progressBar = view.findViewById(R.id.progress);
        play = view.findViewById(R.id.play);
        videoView = view.findViewById(R.id.mVideoView);
        videoView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (videoView.isPlaying()){
                    pause();
                }else {
                    start();
                }
            }
        });


        videoView.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        downX = (int) event.getRawX();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        moveX = (int) (event.getRawX() - downX);
                        break;
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_CANCEL:
                        if (moveX < 0 &&Math.abs(moveX)> ViewConfiguration.getTouchSlop()){
                            left = true ;
                        }else {
                            left = false ;
                        }
                        if (onMoveListener!=null&&left)
                            onMoveListener.onMove();
                        LogUtils.LOG_V("====left===="+left);
                        break;
                }
                return false;
            }
        });
    }

    int downX;
    int moveX ;
    boolean left;


    /**
     * @param visibility
     */
    public void setPlaceholder(int visibility){
        if (placeholder!=null)
            placeholder.setVisibility(visibility);
    }



   private boolean isTrackingTouch = false;
    /**
     *
     */
    private void SeekBarListener() {
        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (isTrackingTouch){
                    float scale= (float) (progress*1.0/100);
                    float msec =  videoView.getDuration()*scale;
                    seekTo(msec);
                }
            }


            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                isTrackingTouch = true ;
                pause();
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                isTrackingTouch=false;
                start();
            }
        });
    }


    /**
     * 暂停播放
     *
     */
    public void pause(){
        if (videoView!=null&&videoView.isPlaying()){
            videoView.pause();
        }
        play.setVisibility(View.VISIBLE);
    }



    /**
     * 指定位置播放
     *
     * @param m
     */
    public void seekTo(float m){
        play.setVisibility(GONE);
        if (videoView!=null){
            videoView.seekTo((int) m);
        }
    }



    /**
     * 开始播放
     */
    public void start(){
        if (!videoView.isPlaying()){
            videoView.start();
        }
        play.setVisibility(View.GONE);


        if (placeholder!=null)
            placeholder.setVisibility(VISIBLE);
        progressBar.setVisibility(VISIBLE);
    }



    /**
     * 重新播放
     */
    public void reStart(){
        if (videoView!=null&&isCompletion){
            videoView.seekTo(0);
            isCompletion = false ;
            videoView.start();
            play.setVisibility(View.INVISIBLE);
        }
    }




    /**
     * 设置视频源
     *
     * @param url
     */
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
    public void setData(String url,int TYPE){
        this.VIDEO_TYPE = TYPE ;
        switch (TYPE){
            case VIDEO_TYPE_FILE_PATH:
                /*设置播放源*/
                videoView.setVideoPath(url);
                break;
            case VIDEO_TYPE_URI:
                /*设置播放源*/
                videoView.setVideoURI(Uri.parse(url));
                break;
        }
        getPreviewImage(url);
        /*准备完成后回调*/
        videoView.setOnPreparedListener(this);
        /*播放内容监听*/
        videoView.setOnInfoListener(this);
        /*播放完成回调*/
        videoView.setOnCompletionListener(this);

        videoView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (videoView.isPlaying()){
                    pause();
                }else {
                    start();
                }
            }
        });
    }





    /**
     * 停止播放
     */
    public  void stopLoad() {
        videoView.canPause();
        videoView.stopPlayback();
    }




    /**
     * 重置
     */
    public void reset(){
        tv_current_time.setText("00:00:00");
        mSeekBar.setProgress(0);
        mCurrentProgressBar.setProgress(0);
        seekTo(0);
        play.setVisibility(VISIBLE);
    }




    /**
     * 销毁
     */
    public void destory(){
        handler.removeCallbacks(runnable);
        videoView.stopPlayback();
        videoView = null ;
    }




    /**
     * 视频准完成
     *
     * @param mp
     */
    @Override
    public void onPrepared(MediaPlayer mp) {
        tv_total_time.setText(DurationTime());
        sendTime();
    }


    /**
     * 显示预览图片
     *
     * @param url
     */
    public void getPreviewImage(final String url){
        new Thread(new Runnable() {
            @Override
            public void run() {
                MediaMetadataRetriever retriever = new MediaMetadataRetriever();
                Bitmap bitmap = null;
                try {
                    //这里要用FileProvider获取的Uri
                    if (url.contains("http")) {
                        retriever.setDataSource(url, new HashMap<String, String>());
                    } else {
                        retriever.setDataSource(url);
                    }
                    bitmap = retriever.getFrameAtTime();

                    final Bitmap finalBitmap = bitmap;
                    Observable.empty().subscribeOn(AndroidSchedulers.mainThread())
                            .doOnCompleted(new Action0() {
                        @Override
                        public void call() {
                            placeholder.setImageBitmap(finalBitmap);
                        }
                    }).subscribe();

                } catch (Exception ex) {
                    ex.printStackTrace();
                } finally {
                    try {
                        retriever.release();
                    } catch (RuntimeException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }).start();
    }


    /**
     * 视频播放完成
     *
     * @param mp
     */
    @Override
    public void onCompletion(MediaPlayer mp) {
        isCompletion = true ;
        placeholder.setVisibility(VISIBLE);
        reset();
    }



    @Override
    public boolean onInfo(MediaPlayer mp, int what, int extra) {
        if(what==MediaPlayer.MEDIA_INFO_BUFFERING_START){
            if (progressBar!=null) progressBar.setVisibility(View.VISIBLE);
        }else{
            if (videoView.isPlaying()){
                if (progressBar!=null) progressBar.setVisibility(View.GONE);
                play.setVisibility(View.GONE);
                lay_playControl.setVisibility(GONE);
            }
        }
        return true;
    }





    private Handler handler=new Handler();

    public void sendTime() {
        handler.postDelayed(runnable,200);
    }

    private Runnable runnable=new Runnable() {

        @Override
        public void run() {
            sendTime();
            int currentPosition = videoView.getCurrentPosition();
            if (videoView.isPlaying()){
                tv_current_time.setText(playCurrentTime());
                if (currentPosition==oldPosition){
                    progressBar.setVisibility(VISIBLE);
                }else {
                    progressBar.setVisibility(GONE);
                    if (placeholder!=null)
                        placeholder.setVisibility(GONE);
                }
                oldPosition  = currentPosition ;
            }
        }
    };



    int oldPosition ;


    /**
     * 当前播放进度
     *
     * @return
     */
    public String playCurrentTime(){
        int CurrentPosition=videoView.getCurrentPosition();
        float scale= (float) ((CurrentPosition * 1.0) / videoView.getDuration());
        mSeekBar.setProgress((int) (scale*100));
        mCurrentProgressBar.setProgress((int) (scale*100));
        return stringForTime(CurrentPosition);
    }



    /**
     * 视频的总长度
     * @return
     */
    public String DurationTime(){
        return stringForTime(videoView.getDuration());
    }



    //将长度转换为时间
    StringBuilder mFormatBuilder = new StringBuilder();
    Formatter mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());


    /**
     * 将长度转换为时间
     *
     * @param timeMs
     * @return
     */
    private String stringForTime(int timeMs) {
        int totalSeconds = timeMs / 1000;
        int seconds = totalSeconds % 60;
        int minutes = (totalSeconds / 60) % 60;
        int hours = totalSeconds / 3600;

        mFormatBuilder.setLength(0);
        if (hours > 0) {
            return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
        } else {
            return mFormatter.format("%02d:%02d", minutes, seconds).toString();
        }
    }



    private OnMoveListener onMoveListener;

    public void setOnMoveListener(OnMoveListener onMoveListener) {
        this.onMoveListener = onMoveListener;
    }

    public interface OnMoveListener{
        void onMove();
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

将哥哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值