Android video player dkplayer

gihub address :

https://github.com/Doikki/DKVideoPlayer

GitHub - Doikki/DKVideoPlayer: Android Video Player. Android video player, encapsulating MediaPlayer, ExoPlayer, and IjkPlayer. Imitate Douyin and implement preloading, list playback, floating playback, ad playback, barrages, video watermarks, and video filters

List playback is as shown in the figure:

1. Dependence

     //添加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'

2. Tools

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;
    }

}

3. Layout

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>

4. Realization

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();
    }


}

Guess you like

Origin blog.csdn.net/qq_19688207/article/details/132186213