# myijkplayer **Repository Path**: code_tjf/myijkplayer ## Basic Information - **Project Name**: myijkplayer - **Description**: 根据ijkplayer 编写的一个简单的视频播放器 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-02-14 - **Last Updated**: 2023-02-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # myijkplayer #### 介绍 根据ijkplayer 编写的一个简单的视频播放器 #### 软件架构 软件架构说明 ## 老规矩 先上完成效果图 ![视频播放.gif](https://upload-images.jianshu.io/upload_images/12221638-5a1d3c3433f938d8.gif?imageMogr2/auto-orient/strip) #### 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 抽象接口回调类 ```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.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 布局的实现 ``` ``` ##### 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 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(); } } ``` #### 4 完结