当前位置:   article > 正文

Android 在项目中使用 JiaoZiVideoPlayer 开源框架集成视频播放功能_jzmediaijk

jzmediaijk

背景

  • 对开源技术的学习以及在项目中快速集成使用。
  • 涉及 MediaPlayer 和 ijk 播放内核的集成。
  • 避免重复造轮子(ps:其实技术达不到那个水准,咳咳!下一个)。
  • 集思广益,对性能优化或者是各种情况分析都做的比较全面,减少问题的出现。

框架 Github 地址

目前在 github 上有 10.2kstar

在这里插入图片描述


说明

框架的优缺点不再说了,同学们自己去 github 上阅读 readme.md 文件即可,写的很详细。

我最初按照 github 上的集成说明自己集成,发现了一些问题,不过后面通过自己下载项目源码,自己运行起来后经过阅读,把问题给解决了,不过还是浪费了一点时间。

这里就不得不吐槽一句,网上好多平台的博客文章都不知道作者在写什么东西,我感觉都在复制粘贴 readme.md 文件里的文档说明,人家都已经有了,你为什么又要再写一遍呢?搞得我看标题跟文章内容根本不符合,就很机车

可能也是有真正教同学们集成使用的,但是我经过那么几篇文章的洗礼后就失去找下去的动力了,想着还不如自己阅读源码,然后自己根据 demo 中的实现方式去自己照搬也会快很多,而且还能加深一些理解,了解开发者们的一些设计思路,为什么要这么设计接口,要怎么封装才能扩展性更强等。

说了这么多题外话,下面开始集成。


添加相关依赖

模块下添加

implementation 'cn.jzvd:jiaozivideoplayer:7.4.2'
  • 1

等待 gradle 同步完成


播放页布局

很简单的一些布局组件,如下:

<?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:orientation="vertical"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="250dp">

        <cn.jzvd.JzvdStd
            android:id="@+id/jz_video"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>


    </FrameLayout>


</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

在这里插入图片描述

记得在布局中给引入的 JzvdStd 外面包裹一层 Layout,这里使用 FrameLayout


集成使用 Mediaplayer 播放内核

当上面的步骤你做好之后,下面就只需简单的几行代码即可实现视频的播放功能,清单配置文件简单配置即可。

MainActivity 中代码:

private static final String url = "http://flv2.bn.netease.com/65a92f105d9a0b107f2eaecd6d457ec1054850baab15642cf9c7df558457666d7f9ce8bb1e8c663f1c13193111d55509d89397afa118181dc08e81da0d9bd0b22970e411e6062ebbc276733f362a8510aeba04de8d25fea568426adcac5072ff86fa7750fd41300ba84641dc027c68953d3430c97385d507.mp4";

private JzvdStd jzvdStd;

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

        jzvdStd = (JzvdStd) findViewById(R.id.jz_video);
        jzvdStd.setUp(url, "一直以为男友家真的很穷,今天来到一看,我承认我太羡慕了!", Jzvd.SCREEN_NORMAL);
        // 播放前页显示图片我简单的设置了一下。 
        // 推荐同学们使用Glide加载网络图片或本地图片,Glide优点明显
        jzvdStd.posterImageView.setImageResource(R.mipmap.ic_launcher_round);
    }

	@Override
    protected void onPause() {
        super.onPause();
        Jzvd.releaseAllVideos();
    }

	@Override
    public void onBackPressed() {
        if (Jzvd.backPress()){
            return;
        }
        super.onBackPressed();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 模块下的 build.gradle 文件添加 java 编译配置:
compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述

该编译配置为了防止意外报错:

在这里插入图片描述

清单配置

  • 添加网络访问权限:
<uses-permission android:name="android.permission.INTERNET"/>
  • 1
  • application 标签添加属性:
android:usesCleartextTraffic="true"
  • 1

预防高版本 Android 手机因为不信任非加密流量而导致加载http地址开头的视频失败。

  • activity 标签添加属性:
			android:configChanges="orientation|screenSize|keyboardHidden"
            android:screenOrientation="portrait"
  • 1
  • 2

防止 activity 在部分状态改变时重新回调各个生命周期,导致播放异常或失败

在这里插入图片描述

针对使用 Mediaplayer 播放内核,到这里就完成了所有的集成操作。

怎么看出使用的是 Mediaplayer 播放内核

同学们来到上面的 jzvdStd.setUp(url, "一直以为男友家真的很穷,今天来到一看,我承认我太羡慕了!", Jzvd.SCREEN_NORMAL); 这行代码,我们跟踪一下源码:

在这里插入图片描述
我们继续看 JZMediaSystem.class 这个类:

在这里插入图片描述

柳暗花明。


最终演示功能 gif 图 - 使用 MediaPlayer 播放内核

在这里插入图片描述
集成完成。


集成使用 ijk 播放内核

ijk 封装成了 ijkplayer ,而 ijkplayer 又是:B 站开源的基于 FFmpeg 的轻量级 Android/iOS 视频播放器。

关于 ijkplayer 的相关资料这里不再说明,同学们自己去 github 上学习,地址为 bilibili/ijkplayer

  • 框架中并没有默认实现 ijk 播放内核,所以这个时候我们就需要自己写一个类继承框架的 JZMediaInterface 来实现该内核

  • 实现过程中需要用到 IjkMediaPlayer ,所以在模块下的 build.gradle 文件中添加相关依赖:

implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'
  • 1

又因为 IjkMediaPlayer 使用过程中需要用到 so 库,so 库可以让我们在 java 中调用 C 代码,C 代码效率高,故 ndk 开发同学们也需要简单的了解。

那么我们需要在项目中引入这个 so 库,IjkMediaPlayer 加载了下面三个 so 库:

  • ijkffmpeg
  • ijksdl
  • ijkplayer

这三个 so 库同学们可以自己在 B 站开源的 ijkplayer 项目中自行下载编译获取,网上的教程很多,这里不说。

或者是直接使用已经编译好的,这里提供给同学们一个地址下载,我把这三个 so 库添上传到了我的百度云盘,方便大家免费下载,资源 - 提取码 - ozbt

  • 引入 so 库

将 so 文件放到对应的文件夹与目录下,没有则自行创建即可:

在这里插入图片描述
这里采用了 armeabi-v7a CPU 架构,当然也有其他的架构,不过目前用到的这个并未发现问题,同学们自己权衡添加即可。

  • ndk配置如下:

在这里插入图片描述

	ndk {
            // add support lib
            abiFilters 'armeabi-v7a' //, 'arm64-v8a'//, "mips"  //,'armeabi''x86',, 'x86_64',
        }
  • 1
  • 2
  • 3
  • 4

说明:当引入了上面的 so 库后,ijk 就支持 https 协议的播放地址了,否则默认是不支持的,所以引入 so 库还是很必要的。

  • 自定义 JZMediaIjk 继承 JZMediaInterface
package com.imxiaoqi.jiaozivideodemo;

import android.graphics.SurfaceTexture;
import android.media.AudioManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.view.Surface;

import java.io.IOException;

import cn.jzvd.JZMediaInterface;
import cn.jzvd.Jzvd;
import tv.danmaku.ijk.media.player.IMediaPlayer;
import tv.danmaku.ijk.media.player.IjkMediaPlayer;
import tv.danmaku.ijk.media.player.IjkTimedText;

/**
 * ijk 播放内核
 */

public class JZMediaIjk extends JZMediaInterface implements IMediaPlayer.OnPreparedListener, IMediaPlayer.OnVideoSizeChangedListener, IMediaPlayer.OnCompletionListener, IMediaPlayer.OnErrorListener, IMediaPlayer.OnInfoListener, IMediaPlayer.OnBufferingUpdateListener, IMediaPlayer.OnSeekCompleteListener, IMediaPlayer.OnTimedTextListener {
    IjkMediaPlayer ijkMediaPlayer;

    public JZMediaIjk(Jzvd jzvd) {
        super(jzvd);
    }

    @Override
    public void start() {
        if (ijkMediaPlayer != null) ijkMediaPlayer.start();
    }

    @Override
    public void prepare() {

        release();
        mMediaHandlerThread = new HandlerThread("IMXIAOQI");
        mMediaHandlerThread.start();
        mMediaHandler = new Handler(mMediaHandlerThread.getLooper());//主线程还是非主线程,就在这里
        handler = new Handler();

        mMediaHandler.post(new Runnable() {
            @Override
            public void run() {

                ijkMediaPlayer = new IjkMediaPlayer();

                ijkMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
                1为硬解 0为软解
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0);
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 1);
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 1);
                //使用opensles把文件从java层拷贝到native层
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 0);
                //视频格式
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32);
                //跳帧处理(-1~120)。CPU处理慢时,进行跳帧处理,保证音视频同步
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1);
                //0为一进入就播放,1为进入时不播放
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 0);
                域名检测
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0);
                //设置是否开启环路过滤: 0开启,画面质量高,解码开销大,48关闭,画面质量差点,解码开销小
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48);
                //最大缓冲大小,单位kb
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", 1024 * 1024);
                //某些视频在SeekTo的时候,会跳回到拖动前的位置,这是因为视频的关键帧的问题,通俗一点就是FFMPEG不兼容,视频压缩过于厉害,seek只支持关键帧,出现这个情况就是原始的视频文件中i 帧比较少
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "enable-accurate-seek", 1);
                //是否重连
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "reconnect", 1);
                //http重定向https
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_clear", 1);
                //设置seekTo能够快速seek到指定位置并播放
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "fastseek");
                //播放前的探测Size,默认是1M, 改小一点会出画面更快
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 1024 * 10);
                //1变速变调状态 0变速不变调状态
                ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "soundtouch", 1);

                ijkMediaPlayer.setOnPreparedListener(JZMediaIjk.this);
                ijkMediaPlayer.setOnVideoSizeChangedListener(JZMediaIjk.this);
                ijkMediaPlayer.setOnCompletionListener(JZMediaIjk.this);
                ijkMediaPlayer.setOnErrorListener(JZMediaIjk.this);
                ijkMediaPlayer.setOnInfoListener(JZMediaIjk.this);
                ijkMediaPlayer.setOnBufferingUpdateListener(JZMediaIjk.this);
                ijkMediaPlayer.setOnSeekCompleteListener(JZMediaIjk.this);
                ijkMediaPlayer.setOnTimedTextListener(JZMediaIjk.this);

                try {
                    ijkMediaPlayer.setDataSource(jzvd.jzDataSource.getCurrentUrl().toString());
                    ijkMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
                    ijkMediaPlayer.setScreenOnWhilePlaying(true);
                    ijkMediaPlayer.prepareAsync();

                    ijkMediaPlayer.setSurface(new Surface(jzvd.textureView.getSurfaceTexture()));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

    }

    @Override
    public void pause() {
        ijkMediaPlayer.pause();
    }

    @Override
    public boolean isPlaying() {
        return ijkMediaPlayer.isPlaying();
    }

    @Override
    public void seekTo(long time) {
        ijkMediaPlayer.seekTo(time);
    }

    @Override
    public void release() {
        if (mMediaHandler != null && mMediaHandlerThread != null && ijkMediaPlayer != null) {//不知道有没有妖孽
            final HandlerThread tmpHandlerThread = mMediaHandlerThread;
            final IjkMediaPlayer tmpMediaPlayer = ijkMediaPlayer;
            JZMediaInterface.SAVED_SURFACE = null;

            mMediaHandler.post(new Runnable() {
                @Override
                public void run() {
                    tmpMediaPlayer.setSurface(null);
                    tmpMediaPlayer.release();
                    tmpHandlerThread.quit();
                }
            });
            ijkMediaPlayer = null;
        }
    }

    @Override
    public long getCurrentPosition() {
        return ijkMediaPlayer.getCurrentPosition();
    }

    @Override
    public long getDuration() {
        if (ijkMediaPlayer == null) return 0;
        return ijkMediaPlayer.getDuration();
    }

    @Override
    public void setVolume(float leftVolume, float rightVolume) {
        ijkMediaPlayer.setVolume(leftVolume, rightVolume);
    }

    @Override
    public void setSpeed(float speed) {
        ijkMediaPlayer.setSpeed(speed);
    }

    @Override
    public void onPrepared(IMediaPlayer iMediaPlayer) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                jzvd.onPrepared();
            }
        });
    }

    @Override
    public void onVideoSizeChanged(final IMediaPlayer iMediaPlayer, int i, int i1, int i2, int i3) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                jzvd.onVideoSizeChanged(iMediaPlayer.getVideoWidth(), iMediaPlayer.getVideoHeight());
            }
        });
    }

    @Override
    public boolean onError(IMediaPlayer iMediaPlayer, final int what, final int extra) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                jzvd.onError(what, extra);
            }
        });
        return true;
    }

    @Override
    public boolean onInfo(IMediaPlayer iMediaPlayer, final int what, final int extra) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                jzvd.onInfo(what, extra);
            }
        });
        return false;
    }

    @Override
    public void onBufferingUpdate(IMediaPlayer iMediaPlayer, final int percent) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                jzvd.setBufferProgress(percent);
            }
        });
    }

    @Override
    public void onSeekComplete(IMediaPlayer iMediaPlayer) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                jzvd.onSeekComplete();
            }
        });
    }

    @Override
    public void onTimedText(IMediaPlayer iMediaPlayer, IjkTimedText ijkTimedText) {

    }

    @Override
    public void setSurface(Surface surface) {
        ijkMediaPlayer.setSurface(surface);
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        if (SAVED_SURFACE == null) {
            SAVED_SURFACE = surface;
            prepare();
        } else {
            jzvd.textureView.setSurfaceTexture(SAVED_SURFACE);
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }

    @Override
    public void onCompletion(IMediaPlayer iMediaPlayer) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                jzvd.onCompletion();
            }
        });
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266

上面的类我直接从源码中拿过来用了,同学们自己看源码即可,这里不过多说明了。

下面来分析 so 库的加载

在自定义的 JZMediaIjk 类中,找到如下代码:
在这里插入图片描述
我们进入 new IjkMediaPlayer() 方法中,如下:

在这里插入图片描述

然后我们进到 this.initPlayer(libLoader); 方法中,如下:

在这里插入图片描述
然后我们进入到 loadLibrariesOnce(libLoader); 中,如下:
在这里插入图片描述
柳暗花明。

  • MainActivity 中的代码如下:
private static final String url = "https://v-cdn.zjol.com.cn/280443.mp4";

private JzvdStd jzvdStd;

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

        jzvdStd = (JzvdStd) findViewById(R.id.jz_video);
        // 切换 ijk 内核
        jzvdStd.setUp(url, "买就完事了哦!铁汁们!", Jzvd.SCREEN_NORMAL, JZMediaIjk.class);
        jzvdStd.posterImageView.setImageResource(R.mipmap.ic_launcher_round);
    }

	@Override
    protected void onPause() {
        super.onPause();
        Jzvd.releaseAllVideos();
    }

    @Override
    public void onBackPressed() {
        if (Jzvd.backPress()){
            return;
        }
        super.onBackPressed();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

针对使用 ijk 播放内核,到这里就完成了所有的集成操作。


最终演示功能 gif 图 - 使用 ijk 播放内核

在这里插入图片描述
集成完成。


总结

靠自己没错,多阅读源码,弄懂一些关键的地方,理解实现思路关键且必要。

这篇文章只是简单集成了一个播放的功能,对一些列表项视频或者是仿抖音的视频也都是可以基于该框架去实现的,这里就不再细说了。实现方法就是看源码,运行代码安装到手机上去定位到相关实现代码,没那么多花里胡哨的东西。

依葫芦画瓢这事真的不需要动脑。


技术永不眠!我们下期见!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/949484?site
推荐阅读
相关标签
  

闽ICP备14008679号