Android 视频播放器dkplayer

gihub地址

https://github.com/Doikki/DKVideoPlayer

GitHub - Doikki/DKVideoPlayer: Android Video Player. 安卓视频播放器,封装MediaPlayer、ExoPlayer、IjkPlayer。模仿抖音并实现预加载,列表播放,悬浮播放,广告播放,弹幕,视频水印,视频滤镜

列表播放如图所示:

一、依赖

     //添加RecyclerView的依赖包
    implementation 'androidx.recyclerview:recyclerview:1.2.1'
    // 异步加载图片依赖
    implementation 'com.squareup.picasso:picasso:2.5.2'
    // 上拉刷新、下来加载依赖
    implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.3'
    // --  Android:视频播放器dkplayer
    //# 必选,内部默认使用系统mediaplayer进行解码
    implementation 'com.github.dueeeke.dkplayer:dkplayer-java:3.2.6'
    //# 可选,包含StandardVideoController的实现
    implementation 'com.github.dueeeke.dkplayer:dkplayer-ui:3.2.6'
    //# 可选,使用exoplayer进行解码
    implementation 'com.github.dueeeke.dkplayer:player-exo:3.2.6'
    //# 可选,使用ijkplayer进行解码
    implementation 'com.github.dueeeke.dkplayer:player-ijk:3.2.6'
    //# 可选,如需要缓存或者抖音预加载功能请引入此库
    implementation 'com.github.dueeeke.dkplayer:videocache:3.2.6'

二、工具类

Tag.java

package com.chy.permission;

/**
 * 播放器标签
 */
public final class Tag {
    //列表播放
    public static final String LIST = "list";
    //无缝播放
    public static final String SEAMLESS = "seamless";
    //画中画
    public static final String PIP = "pip";
}
Utils.java
package com.chy.permission;

import android.view.View;
import android.view.ViewParent;
import android.widget.FrameLayout;

import com.dueeeke.videoplayer.player.VideoView;
import com.dueeeke.videoplayer.player.VideoViewConfig;
import com.dueeeke.videoplayer.player.VideoViewManager;

import java.lang.reflect.Field;

public final class Utils {

    private Utils() {
    }


    /**
     * 获取当前的播放核心
     */
    public static Object getCurrentPlayerFactory() {
        VideoViewConfig config = VideoViewManager.getConfig();
        Object playerFactory = null;
        try {
            Field mPlayerFactoryField = config.getClass().getDeclaredField("mPlayerFactory");
            mPlayerFactoryField.setAccessible(true);
            playerFactory = mPlayerFactoryField.get(config);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return playerFactory;
    }

    /**
     * 将View从父控件中移除
     */
    public static void removeViewFormParent(View v) {
        if (v == null) return;
        ViewParent parent = v.getParent();
        if (parent instanceof FrameLayout) {
            ((FrameLayout) parent).removeView(v);
        }
    }

    /**
     * Returns a string containing player state debugging information.
     */
    public static String playState2str(int state) {
        String playStateString;
        switch (state) {
            default:
            case VideoView.STATE_IDLE:
                playStateString = "idle";
                break;
            case VideoView.STATE_PREPARING:
                playStateString = "preparing";
                break;
            case VideoView.STATE_PREPARED:
                playStateString = "prepared";
                break;
            case VideoView.STATE_PLAYING:
                playStateString = "playing";
                break;
            case VideoView.STATE_PAUSED:
                playStateString = "pause";
                break;
            case VideoView.STATE_BUFFERING:
                playStateString = "buffering";
                break;
            case VideoView.STATE_BUFFERED:
                playStateString = "buffered";
                break;
            case VideoView.STATE_PLAYBACK_COMPLETED:
                playStateString = "playback completed";
                break;
            case VideoView.STATE_ERROR:
                playStateString = "error";
                break;
        }
        return String.format("playState: %s", playStateString);
    }

    /**
     * Returns a string containing player state debugging information.
     */
    public static String playerState2str(int state) {
        String playerStateString;
        switch (state) {
            default:
            case VideoView.PLAYER_NORMAL:
                playerStateString = "normal";
                break;
            case VideoView.PLAYER_FULL_SCREEN:
                playerStateString = "full screen";
                break;
            case VideoView.PLAYER_TINY_SCREEN:
                playerStateString = "tiny screen";
                break;
        }
        return String.format("playerState: %s", playerStateString);
    }


}

VideoAdapter.java

package com.chy.demoprj.adapter;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.chy.demoprj.R;
import com.dueeeke.videocontroller.component.PrepareView;
import com.squareup.picasso.Picasso;

import java.util.HashMap;
import java.util.List;

public class VideoAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private Context context;
    private List<HashMap<String,String>> datas;

    private OnItemChildClickListener mOnItemChildClickListener;// 播放控件点击事件

    /**
     * 构造函数
     * */
    public VideoAdapter(Context context,List<HashMap<String,String>> datas){
        this.context = context;
        this.datas = datas;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.item_video_layout,parent,false);
         ViewHolder viewHolder = new ViewHolder(view);

        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder,int position) {
        ViewHolder vh = (ViewHolder) holder;
        HashMap<String,String> entity = datas.get(position);

        // 设置播放占位图(不设置默认黑色)
        Picasso.with(context)
                .load(entity.get("thumbUrl"))
                .into(vh.mThumb);

        vh.mPosition = position;
    }

    @Override
    public int getItemCount() {
        return datas.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
        public FrameLayout mPlayerContainer;// 容器
        public PrepareView mPrepareView;// 播放视图
        public ImageView mThumb;// 缩略图
        public int mPosition;// 当前视图index

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            mPlayerContainer = itemView.findViewById(R.id.player_container);
            mPrepareView = itemView.findViewById(R.id.prepare_view);
            mThumb = mPrepareView.findViewById(R.id.thumb);

            /**
             * 添加点击事件
             * */
            if (mOnItemChildClickListener != null) {
                mPlayerContainer.setOnClickListener(this);
            }


            //通过tag将ViewHolder和itemView绑定
            itemView.setTag(this);
        }

        @Override
        public void onClick(View v) {
            if (v.getId() == R.id.player_container){

                if (mOnItemChildClickListener != null){
                    mOnItemChildClickListener.onItemChildClick(mPosition);
                }

            }
        }
    }

    /**
     * 点击事件接口
     * */
    public interface OnItemChildClickListener{
        void onItemChildClick(int position);
    }

    /**
     * 点击事件回调函数
     * */
    public void setOnItemChildClickListener(OnItemChildClickListener onItemChildClickListener) {
        mOnItemChildClickListener = onItemChildClickListener;
    }

}

三、布局

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<com.scwang.smartrefresh.layout.SmartRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/refreshLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>


</com.scwang.smartrefresh.layout.SmartRefreshLayout>

item_video_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/player_container"
        android:layout_width="match_parent"
        android:layout_height="187dp"
        android:layout_marginTop="8dp"
        android:background="@android:color/black"
        app:layout_constraintDimensionRatio="16:9"
        app:layout_constraintTop_toTopOf="parent">

        <com.dueeeke.videocontroller.component.PrepareView
            android:id="@+id/prepare_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </FrameLayout>


</LinearLayout>

四、实现

package com.chy.demoprj;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.Manifest;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.Toast;
import com.chy.demoprj.adapter.VideoAdapter;
import com.chy.permission.PermissionUtils;
import com.chy.permission.Tag;
import com.chy.permission.Utils;
import com.dueeeke.videocontroller.StandardVideoController;
import com.dueeeke.videocontroller.component.CompleteView;
import com.dueeeke.videocontroller.component.ErrorView;
import com.dueeeke.videocontroller.component.GestureView;
import com.dueeeke.videocontroller.component.TitleView;
import com.dueeeke.videocontroller.component.VodControlView;
import com.dueeeke.videoplayer.player.VideoView;
import com.dueeeke.videoplayer.player.VideoViewManager;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnLoadMoreListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private static final int REQUEST_PERMISSION_CODE = 0;// 权限所用
    // 动态申请权限
    private String[] permissions = {
            Manifest.permission.INTERNET,// 网络权限
            Manifest.permission.WRITE_EXTERNAL_STORAGE,// 写入数据权限
            Manifest.permission.READ_EXTERNAL_STORAGE,// 读取数据权限
            Manifest.permission.ACCESS_FINE_LOCATION,// 定位权限
            Manifest.permission.ACCESS_COARSE_LOCATION // 获取基站的服务信号权限,以便获取位置信息
    };


    private RefreshLayout refreshLayout;
    private RecyclerView recyclerView;// 列表
    private LinearLayoutManager layoutManager;
    private List<HashMap<String,String>> datas = new ArrayList<>();

    // 视频播放-控件
    protected VideoView mVideoView;
    protected StandardVideoController mController;
    protected ErrorView mErrorView;
    protected CompleteView mCompleteView;
    protected TitleView mTitleView;

    /**
     * 当前播放的位置
     * */
    protected int mCurPos = -1;
    /**
     * 上次播放的位置,用于页面切换回来继续播放
     * */
    protected int mLastPos = mCurPos;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getPermission();


        initData();
        initControls();
        initVideoView();

    }

    /**
     * 权限
     * */
    private void getPermission(){
        boolean flage = PermissionUtils.hasPermissions(MainActivity.this,permissions);
        if (flage) {
            System.out.println("权限获取成功!");
        } else {
            PermissionUtils.requestPermissions(MainActivity.this, REQUEST_PERMISSION_CODE, permissions);
        }
    }


    /**
     * 数据初始化
     * */
    private void initData(){
        HashMap<String,String> hashMap = null;
        // 占位图
        String thumbUrl = "https://p1-xg.byteimg.com/img/tos-cn-p-0000/527b08d0f31d4705a4d8f4a72120948c~tplv-crop-center:1041:582.jpg";
        // 视频播放地址
        String playerUrl = "https://vd4.bdstatic.com/mda-pdhb52ikamv3bdb7/sc/cae_h264/1681819063478400576/mda-pdhb52ikamv3bdb7.mp4";
        // 循环添加数据
        for (int i=0,len=8;i<=len;i++){
            hashMap = new HashMap<>();
            hashMap.put("thumbUrl",thumbUrl);
            hashMap.put("playerUrl",playerUrl);
            hashMap.put("vtitle","播放标题"+i);

            datas.add(hashMap);
        }

    }

    /**
     * 初始化控件
     * */
    private void initControls(){
        refreshLayout = findViewById(R.id.refreshLayout);
        // 下拉刷新事件
        refreshLayout.setOnRefreshListener(new OnRefreshListener() {
            @Override
            public void onRefresh(@NonNull RefreshLayout refreshLayout) {
                refreshLayout.finishRefresh(2000/*,false*/);// 传入false表示刷新失败

            }
        });
        // 上拉加载事件
        refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
                refreshLayout.finishLoadMore(2000/*,false*/);// 传入false表示加载失败
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getApplication(),"没有更多数据",Toast.LENGTH_SHORT).show();
                    }
                });

            }
        });


        recyclerView = findViewById(R.id.recyclerView);
        // 设置布局
        layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);

        // 配置器
        VideoAdapter videoAdapter = new VideoAdapter(this,datas);
        // 设置点击事件
        videoAdapter.setOnItemChildClickListener(new VideoAdapter.OnItemChildClickListener() {
            @Override
            public void onItemChildClick(int position) {
                /**
                 * PrepareView被点击
                 */
                startPlay(position);
            }
        });

        recyclerView.setAdapter(videoAdapter);
        // RecyclerView监听事件
        recyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
            @Override
            public void onChildViewAttachedToWindow(@NonNull View view) {

            }

            @Override
            public void onChildViewDetachedFromWindow(@NonNull View view) {
                FrameLayout playerContainer = view.findViewById(R.id.player_container);
                View v = playerContainer.getChildAt(0);
                if (v != null && v == mVideoView && !mVideoView.isFullScreen()){
                    releaseVideoView();
                }
            }
        });

    }


    /**
     * 播放控件初始化
     * */
    protected void initVideoView() {
        mVideoView = new VideoView(this);
        mVideoView.setOnStateChangeListener(new VideoView.SimpleOnStateChangeListener() {
            @Override
            public void onPlayStateChanged(int playState) {
                //监听VideoViewManager释放,重置状态
                if (playState == com.dueeeke.videoplayer.player.VideoView.STATE_IDLE) {
                    Utils.removeViewFormParent(mVideoView);
                    mLastPos = mCurPos;
                    mCurPos = -1;
                }
            }
        });

        mController = new StandardVideoController(this);
        mErrorView = new ErrorView(this);
        mController.addControlComponent(mErrorView);
        mCompleteView = new CompleteView(this);
        mController.addControlComponent(mCompleteView);
        mTitleView = new TitleView(this);
        mController.addControlComponent(mTitleView);
        mController.addControlComponent(new VodControlView(this));
        mController.addControlComponent(new GestureView(this));
        mController.setEnableOrientation(true);
        mVideoView.setVideoController(mController);
    }

    @Override
    public void onPause() {
        super.onPause();
        pause();
    }

    /**
     * 由于onPause必须调用super。故增加此方法,
     * 子类将会重写此方法,改变onPause的逻辑
     */
    protected void pause() {
        releaseVideoView();
    }

    @Override
    public void onResume() {
        super.onResume();
        resume();
    }


    /**
     * 由于onResume必须调用super。故增加此方法,
     * 子类将会重写此方法,改变onResume的逻辑
     */
    protected void resume() {
        if (mLastPos == -1)
            return;
        //恢复上次播放的位置
        startPlay(mLastPos);
    }



    /**
     * 开始播放
     *
     * @param position 列表位置
     */
    protected void startPlay(int position) {
        if (mCurPos == position) return;
        if (mCurPos != -1) {
            releaseVideoView();
        }
        HashMap<String,String> entity = datas.get(position);
        //边播边存
//        String proxyUrl = ProxyVideoCacheManager.getProxy(getActivity()).getProxyUrl(videoBean.getUrl());
//        mVideoView.setUrl(proxyUrl);
        String playurl = entity.get("playerUrl");
        mVideoView.setUrl(playurl);
        mTitleView.setTitle(entity.get("vtitle"));
        View itemView = layoutManager.findViewByPosition(position);
        if (itemView == null) return;
        VideoAdapter.ViewHolder viewHolder = (VideoAdapter.ViewHolder) itemView.getTag();
        //把列表中预置的PrepareView添加到控制器中,注意isPrivate此处只能为true。
        mController.addControlComponent(viewHolder.mPrepareView, true);
        Utils.removeViewFormParent(mVideoView);
        viewHolder.mPlayerContainer.addView(mVideoView, 0);
        //播放之前将VideoView添加到VideoViewManager以便在别的页面也能操作它
        getVideoViewManager().add(mVideoView, Tag.LIST);
        mVideoView.start();
        mCurPos = position;

    }


    /**
     * 释放播放控件
     * */
    private void releaseVideoView() {
        mVideoView.release();
        if (mVideoView.isFullScreen()) {
            mVideoView.stopFullScreen();
        }
        if (this.getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
            this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        }
        mCurPos = -1;
    }

    /**
     * 创建播放管理类
     * */
    protected VideoViewManager getVideoViewManager(){
        return VideoViewManager.instance();
    }


}

猜你喜欢

转载自blog.csdn.net/qq_19688207/article/details/132186213