老规矩 先上完成效果图
视频播放.gif
编译 环境 及工具
AS4.0 jdk1.8 测试的android系统在7.1安卓板
1.app下的build.gradle 下的 Ijkplayer相关依赖
//使用之前记得把ndk 的版本配置了
android {
// 指定jdk 1.8 版本
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
//https://github.com/bilibili/ijkplayer/blob/master/README.md 播放器
implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8';
implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8';
implementation 'tv.danmaku.ijk.media:ijkplayer-exo:0.8.8';
// Android常用的工具库 这个是用到的一个工具类库
https://github.com/Blankj/AndroidUtilCode/blob/master/lib/utilcode/README-CN.md
implementation 'com.blankj:utilcodex:1.31.1'
}
2. 根据 Ijkplayer 自定义播放器View
实现流程及思路
根据Ijkplayer 提供的 api 需要一个surfaceView 进行视频预览显示
1 自定义一个View 继承FrameLayout
2 创建成功后 进行 IjkMediaPlayer 的实例化及其配置参数
3 设置要播放的视频地址 这里要进行设置 是否是第一次播放 需要先创建surfaceView 及其配置
如果不是第一次则执行进行 mMediaPlayer 的视图设置 进行surfaceView 的绑定
5 设置其加载及其播放和播放完成 异常等回调
4 动态实例化一个surfaceView 获得 Holder()句柄 进行设置创建回调
6 封装其IjkMediaPlayer 的启动 停止 暂停 进度 重置 释放 等api 具体看其自定义播放器的实现
7 添加了进度条和播放时间及总时间的布局及事件
8 动态配置了是否展示进度条及播放时间的UI和动态设置播放器的高度随着视频的高度进行变化
// 后俩步是自行实现的使用方式
9 设置视频的加载回调 根据准备完毕的回调进行视频播放 IjkMediaPlayer .start();
10 根据视频的完成回调 进行视频播放 IjkMediaPlayer .start();或者下一个播放的逻辑
11 使用完记得进行停止和释放
2.1 抽象接口回调类
public abstract class VideoPlayerListener implements IMediaPlayer.OnBufferingUpdateListener,
IMediaPlayer.OnCompletionListener, IMediaPlayer.OnPreparedListener,
IMediaPlayer.OnInfoListener, IMediaPlayer.OnErrorListener, IMediaPlayer.OnSeekCompleteListener {
}
2.2 抽象播放接口类如下
import tv.danmaku.ijk.media.player.IMediaPlayer;
public abstract class VideoPlayerListener implements IMediaPlayer.OnBufferingUpdateListener,
IMediaPlayer.OnCompletionListener, IMediaPlayer.OnPreparedListener,
IMediaPlayer.OnInfoListener, IMediaPlayer.OnErrorListener, IMediaPlayer.OnSeekCompleteListener {
}
2.3 自定义播放器实现类如下
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.blankj.utilcode.util.ScreenUtils;
import com.blankj.utilcode.util.SizeUtils;
import java.io.IOException;
import tv.danmaku.ijk.media.player.IMediaPlayer;
import tv.danmaku.ijk.media.player.IjkMediaPlayer;
public class VideoPlayerIJK extends FrameLayout {
private final String TAG = "VideoPlayerIJK";
private static final int MSG_REFRESH = 100;//查询当前播放进度
/**
* 由ijkplayer提供,用于播放视频,需要给他传入一个surfaceView
*/
private IMediaPlayer mMediaPlayer = null;
/**
* 视频文件地址
*/
private String mPath = "";
private SurfaceView surfaceView;
private VideoPlayerListener listener;
private Context mContext;
private SeekBar seekBar;
private TextView currentTime, totalTime;
private int mVideoWidth, mVideoHeight;
private int screenWidth;
private boolean mVideoControlBar;//是否启动进度条和播放时间的显示
private boolean isFollowTheVideoHeight = false;//是否跟随视频高度显示
public void setFollowTheVideoHeight(boolean followTheVideoHeight) {
isFollowTheVideoHeight = followTheVideoHeight;
}
public VideoPlayerIJK(@NonNull Context context) {
super(context);
initVideoView(context);
}
public VideoPlayerIJK(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initVideoView(context);
}
public VideoPlayerIJK(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
initVideoView(context);
}
private void initVideoView(Context context) {
mContext = context;
screenWidth = ScreenUtils.getScreenWidth();
}
/**
* 设置视频地址。
* 根据是否第一次播放视频,做不同的操作。
*
* @param path 视频地址
*/
public void setVideoPath(String path) {
if (TextUtils.equals("", mPath)) {
//如果是第一次播放视频,那就创建一个新的surfaceView
mPath = path;
createSurfaceView();
} else {
//否则就直接load
defaultBar();
mPath = path;
load();
}
}
/**
* 新建一个surfaceview
*/
private void createSurfaceView() {
//生成一个新的surface view
surfaceView = new SurfaceView(mContext);
addView(surfaceView);
surfaceView.getHolder().addCallback(new MySurfaceCallback());
if (mVideoControlBar) {
addVideoControlBar();
defaultBar();
}
}
/**
* surfaceView的监听器
*/
private class MySurfaceCallback implements SurfaceHolder.Callback {
@Override
public void surfaceCreated(SurfaceHolder holder) {
//surfaceview创建成功后,加载视频
load();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
/**
* 加载视频
*/
private void load() {
Log.e(TAG, "loads");
//每次都要重新创建IMediaPlayer
createPlayer();
try {
mMediaPlayer.setDataSource(mPath);
} catch (IOException e) {
e.printStackTrace();
}
//给mediaPlayer设置视图
mMediaPlayer.setDisplay(surfaceView.getHolder());
mMediaPlayer.prepareAsync();
}
/**
* 创建一个新的player
*/
private void createPlayer() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.setDisplay(null);
mMediaPlayer.release();
}
IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();
ijkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG);
ijkMediaPlayer.setLogEnabled(false);
//开启硬解码
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);
mMediaPlayer = ijkMediaPlayer;
if (listener != null) {
mMediaPlayer.setOnPreparedListener(listener);
mMediaPlayer.setOnInfoListener(listener);
mMediaPlayer.setOnSeekCompleteListener(listener);
mMediaPlayer.setOnBufferingUpdateListener(listener);
mMediaPlayer.setOnErrorListener(listener);
mMediaPlayer.setOnCompletionListener(listener);
}
}
public void setListener(VideoPlayerListener listener) {
this.listener = listener;
}
//-------======--------- 下面封装了一下控制视频的方法
/**
* 启动播放
*/
public void start() {
if (mMediaPlayer != null) {
mMediaPlayer.start();
setVideoSize();
if (mVideoControlBar) {
handler.sendEmptyMessageDelayed(MSG_REFRESH, 1000);
}
}
}
/**
* 释放
*/
public void release() {
if (mMediaPlayer != null) {
stop();
mMediaPlayer.release();
mMediaPlayer = null;
}
}
/**
* 暂停
*/
public void pause() {
if (mMediaPlayer != null) {
mMediaPlayer.pause();
}
}
/**
* 停止
*/
public void stop() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
handler.removeCallbacksAndMessages(null);
}
}
/**
* 重置
*/
public void reset() {
if (mMediaPlayer != null) {
mMediaPlayer.reset();
}
}
/**
* 获取视频总时间
*
* @return
*/
public long getDuration() {
if (mMediaPlayer != null) {
return mMediaPlayer.getDuration();
} else {
return 0;
}
}
/**
* 当前播放时间
*
* @return
*/
public long getCurrentPosition() {
if (mMediaPlayer != null) {
return mMediaPlayer.getCurrentPosition();
} else {
return 0;
}
}
/**
* 设置播放进度
*
* @param l
*/
public void seekTo(long l) {
if (mMediaPlayer != null) {
mMediaPlayer.seekTo(l);
}
}
/**
* 是否循环播放
*
* @param isLoop
*/
public void loop(boolean isLoop) {
if (mMediaPlayer != null) {
mMediaPlayer.setLooping(isLoop);
}
}
public void setVideoControlBar(boolean isVideoControlBar) {
mVideoControlBar = isVideoControlBar;
}
//给播放器动态设置一个进度条及当前播放时间和总时间
public void addVideoControlBar() {
LayoutParams videoBarParams = new LayoutParams(LayoutParams.MATCH_PARENT
, SizeUtils.dp2px(50), Gravity.BOTTOM);
LinearLayout videoBar = new LinearLayout(mContext);
videoBar.setHorizontalGravity(LinearLayout.HORIZONTAL);
videoBar.setLayoutParams(videoBarParams);
videoBar.setGravity(Gravity.CENTER_VERTICAL);
currentTime = new TextView(mContext);
currentTime.setTextColor(Color.WHITE);
currentTime.setTextSize(SizeUtils.sp2px(24));
LinearLayout.LayoutParams currentTimeParams = new LinearLayout.
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
currentTimeParams.setMarginStart(SizeUtils.dp2px(30));
currentTimeParams.setMarginEnd(SizeUtils.dp2px(30));
currentTime.setLayoutParams(currentTimeParams);
videoBar.addView(currentTime);
LinearLayout.LayoutParams seekBarParams = new LinearLayout.
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 1.0f);
seekBar = new SeekBar(mContext);
seekBar.setMinimumWidth(LayoutParams.MATCH_PARENT);
seekBar.setMax(100);
seekBar.setLayoutParams(seekBarParams);
videoBar.addView(seekBar);
LinearLayout.LayoutParams totalmTimeParams = new LinearLayout.
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
totalmTimeParams.setMarginStart(SizeUtils.dp2px(30));
totalmTimeParams.setMarginEnd(SizeUtils.dp2px(30));
totalTime = new TextView(mContext);
totalTime.setTextColor(Color.WHITE);
totalTime.setTextSize(SizeUtils.sp2px(24));
totalTime.setLayoutParams(totalmTimeParams);
videoBar.addView(totalTime);
addView(videoBar);
}
//刷新进度条和时间的主线程
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REFRESH:
if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
refresh();
handler.sendEmptyMessageDelayed(MSG_REFRESH, 100);
}
break;
}
}
};
//计算当前时间和总时间
private void refresh() {
long current = getCurrentPosition() / 1000;
long duration = getDuration() / 1000;
long current_second = current % 60;
long current_minute = current / 60;
long total_second = duration % 60;
long total_minute = duration / 60;
if (currentTime != null) {
currentTime.setText(current_minute + ":" + current_second);
}
if (totalTime != null) {
totalTime.setText(total_minute + ":" + total_second);
}
if (duration != 0 && current != 0) {
seekBar.setProgress((int) (current * 100 / duration));
}
}
/**
* 时间及进度的默认值
*/
@SuppressLint("SetTextI18n")
public void defaultBar() {
if (mVideoControlBar && seekBar != null) {
seekBar.setProgress(0);
currentTime.setText("00:00");
totalTime.setText("00:00");
}
}
/**
* 设置surfaceView 跟随视频的高度
*/
public void setVideoSize() {
if (surfaceView != null && isFollowTheVideoHeight) {
mVideoWidth = mMediaPlayer.getVideoWidth();
mVideoHeight = mMediaPlayer.getVideoHeight();
Log.e(TAG, "h:" + mVideoHeight);
if (mVideoWidth != 0 && mVideoHeight != 0) {
//根据video的大小进行自适应设置
double currentLayoutHeight;
if (screenWidth > mVideoWidth) {
double s = (double) screenWidth / (double) mVideoWidth;
currentLayoutHeight = s * mVideoHeight;
} else {
double s = (double) mVideoWidth / (double) screenWidth;
currentLayoutHeight = s * mVideoHeight;
}
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT
, (int) currentLayoutHeight, Gravity.CENTER);
surfaceView.setLayoutParams(layoutParams);
Log.e(TAG, "h1:" + currentLayoutHeight);
}
}
}
}
3. 具体的使用
3.1 activity_main 布局的实现
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.tjf.myijkplayer.views.VideoPlayerIJK
android:id="@+id/mijkvideo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
3.2 MainActivity 主页的流程实现
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import com.blankj.utilcode.util.LogUtils;
import com.tjf.myijkplayer.views.VideoPlayerIJK;
import com.tjf.myijkplayer.views.VideoPlayerListener;
import java.util.ArrayList;
import java.util.List;
import tv.danmaku.ijk.media.player.IMediaPlayer;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
List<String> videoList = new ArrayList<>();
int videoIndex;
VideoPlayerIJK mIjkvideo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mIjkvideo = findViewById(R.id.mijkvideo);
setVideoUri();
startPlayVideo();
}
public void setVideoUri() {
videoList.clear();
String videoPath = "http://v-cdn.zjol.com.cn/280443.mp4";
String videoPath1 = "http://rbv01.ku6.com/omtSn0z_PTREtneb3GRtGg.mp4";
videoList.add(videoPath);
videoList.add(videoPath1);
}
public void startPlayVideo() {
videoIndex = 0;
mIjkvideo.setFollowTheVideoHeight(true);
mIjkvideo.setVideoControlBar(true);
mIjkvideo.setListener(new VideoPlayerListener() {
@Override
public void onBufferingUpdate(IMediaPlayer iMediaPlayer, int i) {
}
@Override
public void onCompletion(IMediaPlayer iMediaPlayer) {
LogUtils.iTag(TAG, "onCompletion");
videoIndex++;
if (videoIndex >= videoList.size()) {
videoIndex = 0;
}
mIjkvideo.setVideoPath(videoList.get(videoIndex));
}
@Override
public boolean onError(IMediaPlayer iMediaPlayer, int i, int i1) {
return false;
}
@Override
public boolean onInfo(IMediaPlayer iMediaPlayer, int i, int i1) {
return false;
}
@Override
public void onPrepared(IMediaPlayer iMediaPlayer) {
LogUtils.iTag("播放器-", iMediaPlayer.getVideoHeight());
mIjkvideo.start();
}
@Override
public void onSeekComplete(IMediaPlayer iMediaPlayer) {
LogUtils.iTag(TAG, "onSeekComplete");
}
});
mIjkvideo.setVideoPath(videoList.get(videoIndex));
}
@Override
protected void onPause() {
super.onPause();
mIjkvideo.release();
}
}