VideoView播放视频案例

  前言

     最近公司要做一个发布文章的功能可以添加图片和视频,由于视频功能在项目中应用的不是很多,所以就打算直接用原生VideoView进行视频的播放,写完我把通用的代码抽取出来,这里分享给大家。

  效果图

       

  代码

        1.布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:id="@+id/rlVideo"
                android:layout_width="match_parent"
                android:layout_height="210dp"
                android:layout_marginLeft="@dimen/margin_two"
                android:layout_marginRight="@dimen/margin_two"
                android:background="@color/black">

    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="210dp"/>

    <RelativeLayout
        android:id="@+id/rlMask"
        android:layout_width="match_parent"
        android:layout_height="210dp"
        android:focusableInTouchMode="true">

        <ImageView
            android:id="@+id/ivPicture"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_centerInParent="true"
            android:scaleType="centerCrop"/>

        <ImageView
            android:id="@+id/ivPlay"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:scaleType="centerCrop"
            android:src="@drawable/btn_video_play_selector"/>

    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/rlLoading"
        android:layout_width="match_parent"
        android:layout_height="210dp"
        android:focusableInTouchMode="true"
        android:visibility="gone">

        <ProgressBar
            android:id="@+id/progressbar"
            style="@android:style/Widget.ProgressBar.Small.Inverse"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/progressbar"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="@dimen/margin_five"
            android:text="缓冲中..."
            android:textColor="@android:color/white"
            android:textSize="@dimen/sp_10"/>
    </RelativeLayout>

    <FrameLayout
        android:id="@+id/flDelete"
        android:layout_width="@dimen/dp_36"
        android:layout_height="@dimen/dp_36"
        android:layout_alignParentRight="true"
        android:clickable="true">

        <ImageView
            android:id="@+id/ivDelete"
            android:layout_width="@dimen/margin_twenty"
            android:layout_height="@dimen/margin_twenty"
            android:layout_gravity="right|top"
            android:scaleType="fitXY"
            android:src="@drawable/btn_delete"
            android:visibility="gone"/>

    </FrameLayout>
</RelativeLayout>
    2. 播放管理类

       

/**
 * @Description 视频播放管理类
 * Created by liuchao on 2017/10/24.
 */

public class VideoManager implements MediaPlayer.OnErrorListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnInfoListener, MediaPlayer.OnCompletionListener, View.OnClickListener {

    private VideoView mVideoView;
    private View mPlayButton;
    private View mLoadingLayout;
    private View mCompleteLayout;
    private ImageView mThumbnailView;
    private OnErrorListener mErrorListener;
    private boolean mInitVideoView;


    public interface OnErrorListener {
        void onError(MediaPlayer mp, String errorMsg);
    }

    /**
     * 设置播放出错回调
     *
     * @param listener
     * @return
     */
    public VideoManager setOnErrorListener(OnErrorListener listener) {
        this.mErrorListener = listener;
        return this;
    }

    /**
     * 创建VideoView播放视频管理类
     *
     * @param videoView
     * @return
     */
    public static VideoManager create(VideoView videoView) {
        if (null == videoView) return null;
        return new VideoManager(videoView);
    }

    public VideoManager(VideoView videoView) {
        this.mVideoView = videoView;
        this.mVideoView.requestFocus();
    }

    /**
     * @param playButton     播放按钮
     * @param loadingLayout  缓冲进度布局
     * @param completeLayout 播放完成布局
     * @param thumbnailView  第一帧显示缩略图布局
     * @return
     */
    public VideoManager bindView(View playButton, View loadingLayout, View completeLayout, ImageView thumbnailView) {
        this.mPlayButton = playButton;
        this.mLoadingLayout = loadingLayout;
        this.mCompleteLayout = completeLayout;
        this.mThumbnailView = thumbnailView;
        initController();
        initlistener();
        return this;
    }

    public void init(String videoPath) {
        if (TextUtils.isEmpty(videoPath)) return;
        mInitVideoView = true;
        //加载显示缩略图
        loadThumbnail(videoPath);
        //设置网络视频路径
        Uri uri;
        if (-1 != "http://".indexOf(videoPath) || -1 != "https://".indexOf(videoPath))
            uri = FileUtils.file2Uri(mVideoView.getContext(), new File(videoPath));
        else
            uri = Uri.parse(videoPath);
        mVideoView.setVideoURI(uri);
        setVideoViewLayoutParams(mVideoView, 0);
    }

    @Override
    public void onClick(View v) {
        mLoadingLayout.setVisibility(mInitVideoView ? View.VISIBLE : View.GONE);
        mInitVideoView = false;
        mVideoView.start();
        mVideoView.requestFocus();
        mCompleteLayout.setVisibility(View.GONE);
    }

    /**
     * 初始化VideoView的监听
     */
    private void initlistener() {
        this.mVideoView.setOnErrorListener(this);
        this.mVideoView.setOnPreparedListener(this);
        this.mVideoView.setOnInfoListener(this);
        this.mVideoView.setOnCompletionListener(this);
        this.mPlayButton.setOnClickListener(this);
    }

    /**
     * 初始化VideoView的进度控制器
     */
    private void initController() {
        //初始化videoview控制条
        VideoController mediaController = new VideoController(mVideoView.getContext());
        //设置videoview的控制条
        this.mVideoView.setMediaController(mediaController);
        //设置显示控制条/
        mediaController.show(0);
        mediaController.setPlayListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mLoadingLayout.setVisibility(mVideoView.isPlaying() ? View.GONE : View.VISIBLE);
                mCompleteLayout.setVisibility(mVideoView.isPlaying() ? View.VISIBLE : View.GONE);
            }
        });
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        mLoadingLayout.setVisibility(View.GONE);
        mCompleteLayout.setVisibility(View.VISIBLE);
    }

    @Override
    public boolean onInfo(MediaPlayer mp, int what, int extra) {
        if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
            this.mVideoView.setBackgroundColor(Color.TRANSPARENT);
            this.mLoadingLayout.setVisibility(View.VISIBLE);
        } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
            //此接口每次回调完START就回调END,若不加上判断就会出现缓冲图标一闪一闪的卡顿现象
            if (mp.isPlaying()) {
                mLoadingLayout.setVisibility(View.GONE);
            }
        }
        return true;
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        mInitVideoView = false;
        mLoadingLayout.setVisibility(View.GONE);//缓冲完成就隐藏
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        switch (what) {
            case MediaPlayer.MEDIA_ERROR_UNKNOWN:
                Log.e("text", "发生未知错误");
                if (null != mErrorListener) mErrorListener.onError(mp, "发生未知错误");
                break;
            case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
                if (null != mErrorListener) mErrorListener.onError(mp, "媒体服务器死机");
                Log.e("text", "媒体服务器死机");
                break;
            default:
                Log.e("text", "onError+" + what);
                if (null != mErrorListener) mErrorListener.onError(mp, "未知错误");
                break;
        }
        switch (extra) {
            case MediaPlayer.MEDIA_ERROR_IO:
                //io读写错误
                Log.e("text", "文件或网络相关的IO操作错误");
                if (null != mErrorListener) mErrorListener.onError(mp, "文件或网络相关的IO操作错误");
                break;
            case MediaPlayer.MEDIA_ERROR_MALFORMED:
                //文件格式不支持
                Log.e("text", "比特流编码标准或文件不符合相关规范");
                if (null != mErrorListener) mErrorListener.onError(mp, "比特流编码标准或文件不符合相关规范");
                break;
            case MediaPlayer.MEDIA_ERROR_TIMED_OUT:
                //一些操作需要太长时间来完成,通常超过3 - 5秒。
                Log.e("text", "操作超时");
                if (null != mErrorListener) mErrorListener.onError(mp, "操作超时");
                break;
            case MediaPlayer.MEDIA_ERROR_UNSUPPORTED:
                //比特流编码标准或文件符合相关规范,但媒体框架不支持该功能
                Log.e("text", "比特流编码标准或文件合相关规范,但媒体框架不支持该功能");
                if (null != mErrorListener)
                    mErrorListener.onError(mp, "比特流编码标准或文件符合相关规范,但媒体框架不支持该功能");
                break;
            default:
                Log.e("text", "onError+" + extra);
                if (null != mErrorListener) mErrorListener.onError(mp, "播放出错了");
                break;
        }
        return false;
    }

    /**
     * 加载缩略图
     *
     * @param videoPath
     */
    private void loadThumbnail(final String videoPath) {
        Observable.create(new Observable.OnSubscribe<Bitmap>() {
            @Override
            public void call(Subscriber<? super Bitmap> subscriber) {
                int width = ScreenUtils.getScreenWidth(mVideoView.getContext()) - 50;
                int height = DensityUtil.dip2px(mVideoView.getContext(), 220);
                if (null != videoPath && -1 != videoPath.indexOf("http://")) {
                    subscriber.onNext(VideoUtil.createVideoThumbnail(videoPath, width, height));
                } else {
                    subscriber.onNext(VideoUtil.getVideoThumbnail(videoPath, width, height, MediaStore.Images.Thumbnails.MICRO_KIND));
                }
            }
        }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<Bitmap>() {
            @Override
            public void call(Bitmap bitmap) {
                mThumbnailView.setImageBitmap(bitmap);
            }
        });
    }

    /**
     * 设置videiview的全屏和窗口模式
     *
     * @param paramsType 标识 1为全屏模式 2为窗口模式
     */
    public void setVideoViewLayoutParams(VideoView videoView, int paramsType) {
        //全屏模式
        if (1 == paramsType) {
            //设置充满整个父布局
            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
            //设置相对于父布局四边对齐
            layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
            layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
            layoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
            layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
            //为VideoView添加属性
            videoView.setLayoutParams(layoutParams);
        } else {
            //窗口模式
            //设置窗口模式距离边框50
//            int videoHeight = DensityUtil.getScreenHeight(videoView.getContext()) - 50;
            int videoHeight = DensityUtil.dip2px(videoView.getContext(), 220);
//            int videoWidth = DensityUtil.getScreenWidth(videoView.getContext()) - 50;
            int videoWidth = RelativeLayout.LayoutParams.WRAP_CONTENT;
            RelativeLayout.LayoutParams LayoutParams = new RelativeLayout.LayoutParams(videoWidth, videoHeight);
            //设置居中
            LayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
            //为VideoView添加属性
            videoView.setLayoutParams(LayoutParams);
        }
    }
}

    3. 自定义MediaPlayerController

       3.1 重新MediaPlayerController,修改makeControllerView方法,把布局改成我们自己定义的布局文件。

protected View makeControllerView() {
        LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mRoot = inflate.inflate(R.layout.view_video_controller, null);

        initControllerView(mRoot);

        return mRoot;
    }
      3.2 添加暂停按钮回调监听

   private final View.OnClickListener mPauseListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (mPlayListener != null) mPlayListener.onClick(v);
            doPauseResume();
            show(sDefaultTimeout);
        }
    };
      3.3  注意自定义Controller的布局文件最好用系统的然后调整布局(不要修改控件Id,否则还得修改源码)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:background="#CC000000"
              android:layoutDirection="ltr"
              android:orientation="vertical">

    <LinearLayout
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal"
        android:paddingTop="4dip">

        <ImageButton
            android:id="@+id/prev"
            style="@android:style/MediaButton.Previous"/>

        <ImageButton
            android:id="@+id/rew"
            style="@android:style/MediaButton.Rew"/>

       <!-- <ImageButton
            android:id="@+id/pause"
            style="@android:style/MediaButton.Play"/>-->

        <ImageButton
            android:id="@+id/ffwd"
            style="@android:style/MediaButton.Ffwd"/>

        <ImageButton
            android:id="@+id/next"
            style="@android:style/MediaButton.Next"/>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageButton
            android:id="@+id/pause"
            android:layout_marginLeft="10dp"
            android:layout_width="20dip"
            android:layout_height="20dip"
            android:layout_gravity="center_vertical"
            style="@android:style/MediaButton.Play"/>

        <SeekBar
            android:id="@+id/mediacontroller_progress"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="0dip"
            android:layout_height="32dip"
            android:layout_weight="1"/>

        <TextView
            android:id="@+id/time_current"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:paddingEnd="4dip"
            android:paddingStart="4dip"
            android:paddingTop="4dip"
            android:textColor="#bebebe"
            android:textSize="14sp"
            android:textStyle="bold"/>

        <TextView
            android:visibility="gone"
            android:id="@+id/time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:paddingEnd="4dip"
            android:paddingStart="4dip"
            android:paddingTop="4dip"
            android:textColor="#bebebe"
            android:textSize="14sp"
            android:textStyle="bold"/>
    </LinearLayout>

</LinearLayout>  


        4.  使用VideoManager播放视频

        View rlMask = itemView.findViewById(R.id.rlMask);
        View showPlayerLoading = itemView.findViewById(R.id.rlLoading);
        ImageView ivPlay = (ImageView) itemView.findViewById(R.id.ivPlay);
        ImageView thumbnailView = (ImageView) itemView.findViewById(R.id.ivPicture);
        thumbnailView.setTag(R.id.tag_path, videoPath);
        //初始化VideoView
        VideoView videoView = (VideoView) itemView.findViewById(R.id.videoView);
        rlMask.setOnClickListener(this);
        VideoManager.create(videoView).bindView(ivPlay, showPlayerLoading, rlMask, thumbnailView).init(videoPath);


      5. 最后补上VideoUtil中两个获取本地或服务端视频缩略图代码

     

 /**
     * 获取网路视频缩略
     * 自己的后台线程中调用该方法得到网络视频的缩略图bitmap
     * 然后在主线程中调用imageView.setImageBitmap(bitmap)即可
     *
     * @param url    网路视频地址
     * @param width  指定输出视频缩略图的宽度
     * @param height 指定输出视频缩略图的高度度
     * @return
     */
    public static Bitmap createVideoThumbnail(String url, int width, int height) {
        Bitmap bitmap = null;
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        int kind = MediaStore.Video.Thumbnails.MINI_KIND;
        try {
            if (Build.VERSION.SDK_INT >= 14) {
                retriever.setDataSource(url, new HashMap<String, String>());
            } else {
                retriever.setDataSource(url);
            }
            bitmap = retriever.getFrameAtTime();
        } catch (IllegalArgumentException ex) {
            // Assume this is a corrupt video file
        } catch (RuntimeException ex) {
            // Assume this is a corrupt video file.
        } finally {
            try {
                retriever.release();
            } catch (RuntimeException ex) {
                // Ignore failures while cleaning up.
            }
        }
        if (kind == MediaStore.Images.Thumbnails.MICRO_KIND && bitmap != null) {
            bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
        }
        return bitmap;
    }

    /**
     * 获取本地视频的缩略图
     * 先通过ThumbnailUtils来创建一个视频的缩略图,然后再利用ThumbnailUtils来生成指定大小的缩略图。
     * 如果想要的缩略图的宽和高都小于MICRO_KIND,则类型要使用MICRO_KIND作为kind的值,这样会节省内存。
     *
     * @param videoPath 视频的路径
     * @param width     指定输出视频缩略图的宽度
     * @param height    指定输出视频缩略图的高度度
     * @param kind      参照MediaStore.Images.Thumbnails类中的常量MINI_KIND和MICRO_KIND。
     *                  其中,MINI_KIND: 512 x 384,MICRO_KIND: 96 x 96
     * @return 指定大小的视频缩略图
     */
    public static Bitmap getVideoThumbnail(String videoPath, int width, int height, int kind) {
        Bitmap bitmap = null;
        // 获取视频的缩略图
        bitmap = ThumbnailUtils.createVideoThumbnail(videoPath, kind);
        System.out.println("w" + bitmap.getWidth());
        System.out.println("h" + bitmap.getHeight());
        bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
        return bitmap;
    }

   


   总结

          这里使用VideoManager进行播放视频就是必须使用指定布局,传入四个控件进行控制通过init方法设置播放路径,如果确定播放布局是这样的就可以重复利用布局文件,每次在需要播放的地方把步骤4的代码拷贝过去就好了,如果对布局要做调整,必须把VideoManager对应控件也做相应调整。


  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值