在MacOs上编译和使用IJKPlayer

2,058 阅读3分钟

根据bilibili官方文档跑代码遇到的问题

1.build前安装的几个系统插件

# install homebrew, git, yasm
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install git
brew install yasm

# add these lines to your ~/.bash_profile or ~/.profile
# export ANDROID_SDK=<your sdk path>
# export ANDROID_NDK=<your ndk path>

# on Cygwin (unmaintained)
# install git, make, yasm

2.解决 clone IJKPlayer库 很慢甚至断线的问题。

大家都知道,gitHub国内访问很慢,所以此处需要设置终端代理来进行clone工程,切记此处需要自己电脑有代理才行。

export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:7890

3.clone工程,并checkout最新版本

git clone https://github.com/Bilibili/ijkplayer.git ijkplayer-android
cd ijkplayer-android
git checkout -B latest k0.8.8

4.执行预处理 (根据要编译的包的精简程度)

  • 1.简单的
cd config
rm module.sh
ln -s module-lite.sh module.sh
cd android/contrib
# cd ios
sh compile-ffmpeg.sh clean
  • 2.简单带hevc
cd config
rm module.sh
ln -s module-lite-hevc.sh module.sh
cd android/contrib
# cd ios
sh compile-ffmpeg.sh clean
  • 3.复杂的支持多种格式的
cd config
rm module.sh
ln -s module-default.sh module.sh
cd android/contrib
# cd ios
sh compile-ffmpeg.sh clean

5.执行./init-android.sh

此步骤会去github下载FFmpeg相关资源

./init-android.sh


6. 编译ffmpeg

cd android/contrib
./compile-ffmpeg.sh clean
./compile-ffmpeg.sh all
  • (1)You need the NDKr10e or later 问题 既然它需要这个版本的NDK,那么我们就下载一个,然后设置到环境变量里就行了,下载地址见下面:
https://dl.google.com/android/repository/android-ndk-r10e-darwin-x86_64.zip?hl=zh_cn

下载,解压,配置环境变量。一气呵成。接下来我们继续跑

  • (2)fatal error: linux/perf_event.h: No such file or directory
module.sh文件最后有没有这句话:
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --disable-linux-perf"
没有的话就加上!

编译ijk

cd ..
./compile-ijk.sh all

OK,编译完成!

RUN IJKPlayerDemo

  • 无法build问题 下面是我的配置。

1.修改buildscript和allprojects如下。

repositories {
        //********国内配置 start ************//
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/jcenter' }
        maven { url 'https://maven.aliyun.com/nexus/content/groups/public' }
        //========国内配置 end =============//
    }

2.修改classpath

classpath 'com.android.tools.build:gradle:3.3.0'

3.修改gradle wrapper

distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-bin.zip

4.flavors配置问题

//注释productFlavors
//注释all32Compile 相关
//把minSdkVersion改到21  我自己不需要21以下的^^!

5.不能正确加载目录问题

把程序内默认的 "/" 改成 Environment.getExternalStorageDirectory().toString()

另外程序原本代码有点问题,总是识别到“/”目录,此时我们把程序改成如下即可、

String path = Environment.getExternalStorageDirectory().toString();
doOpenDirectory(path, false);

OK,完成!我们此时就编译好了ijkplayer播放器,并能正确运行此播放器demo了。

使用编译好的资源构建新播放器工程

    1. ijkplayer-armv7a/src/main/libs下的文件拷贝到新的工程的libs下。

image.png

  • 2. 然后把ijkplayer-java/build/outputs/aar/ijkplayer-java-release.aar复制到新工程的libs下

image.png

注意:高版本的androidStudio并不会在demo运行时主动生成aar,此时需要我们在工程根目录(MacOs下)执行./gradlew assembleRelease才能生成。

  • 3.最后就是修改build.gradle,把so和aar文件依赖添加上去
//dependencies->增加
implementation fileTree(dir: 'libs', include: ['*.jar','*.aar'])

//android->defaultConfig->增加 
ndk{ abiFilters "armeabi","armeabi-v7a","arm64-v8a","x86","x86_64" }
此处需要跟你你想要支持的cpu平台而定


//android->增加
sourceSets {
    main {
        jniLibs.srcDirs = ['libs']
    }
}
//最外层->增加
repositories {
    flatDir {
        dir 'libs'
    }
}
    

此时aar和so库的配置就ok了

自制播放器简单Demo

  1. 播放器的自定义view
    IjkVideoView.kt
package com.example.ijkplayerdemo

import android.content.Context
import android.text.TextUtils
import android.util.AttributeSet
import android.view.Gravity
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.widget.FrameLayout
import androidx.annotation.Nullable
import tv.danmaku.ijk.media.player.IMediaPlayer
import tv.danmaku.ijk.media.player.IjkMediaPlayer
import java.io.IOException


class IjkVideoView : FrameLayout {
    private var mContext: Context? = null
    private var mMediaPlayer: IMediaPlayer? = null //视频控制类
    private var mVideoPlayerListener : VideoPlayerListener? = null//自定义监听器
    private var mSurfaceView : SurfaceView? = null//播放视图
    private var mPath = "" //视频文件地址

    constructor(context: Context) : super(context) {
        initVideoView(context)
    }

    constructor(context: Context, @Nullable attrs: AttributeSet?) : super(context, attrs) {
        initVideoView(context)
    }

    constructor(
        context: Context,
        @Nullable attrs: AttributeSet?,
        defStyleAttr: Int
    ) : super(context, attrs, defStyleAttr) {
        initVideoView(context)
    }

    constructor(
        context: Context,
        @Nullable attrs: AttributeSet?,
        defStyleAttr: Int,
        defStyleRes: Int
    ) : super(context, attrs, defStyleAttr, defStyleRes) {
    }

    abstract class VideoPlayerListener : IMediaPlayer.OnPreparedListener,
        IMediaPlayer.OnCompletionListener, IMediaPlayer.OnErrorListener

    private fun initVideoView(context: Context) {
        mContext = context
        isFocusable = true
    }

    fun setPath(path: String) {
        if (TextUtils.equals("", mPath)) {
            mPath = path
            initSurfaceView()
        } else {
            mPath = path
            loadVideo()
        }
    }

    private fun initSurfaceView() {
        mSurfaceView = SurfaceView(mContext)
        mSurfaceView?.holder?.addCallback(LmnSurfaceCallback())
        val layoutParams =LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER)
        mSurfaceView?.layoutParams = layoutParams
        this.addView(mSurfaceView)
    }

    //surfaceView的监听器
    private inner class LmnSurfaceCallback : SurfaceHolder.Callback {
        override fun surfaceCreated(holder: SurfaceHolder) {}
        override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
            loadVideo()
        }

        override fun surfaceDestroyed(holder: SurfaceHolder) {}
    }

    //加载视频
    private fun loadVideo() {
        if (mMediaPlayer != null) {
            mMediaPlayer?.stop()
            mMediaPlayer?.release()
        }
        val ijkMediaPlayer = IjkMediaPlayer()
        mMediaPlayer = ijkMediaPlayer
        if (mVideoPlayerListener != null) {
            mMediaPlayer?.setOnPreparedListener(mVideoPlayerListener)
            mMediaPlayer?.setOnErrorListener(mVideoPlayerListener)
        }
        try {
            mMediaPlayer?.dataSource = mPath
        } catch (e: IOException) {
            e.printStackTrace()
        }
        mMediaPlayer?.setDisplay(mSurfaceView?.holder)
        mMediaPlayer?.prepareAsync()
    }

    fun setListener(listener: VideoPlayerListener?) {
        mVideoPlayerListener = listener
        if (mMediaPlayer != null) {
            mMediaPlayer?.setOnPreparedListener(listener)
        }
    }

    val isPlaying: Boolean
        get() = if (mMediaPlayer != null) {
            mMediaPlayer?.isPlaying?:false
        } else false

    fun start() {
        if (mMediaPlayer != null) {
            mMediaPlayer?.start()
        }
    }

    fun pause() {
        if (mMediaPlayer != null) {
            mMediaPlayer?.pause()
        }
    }

    fun stop() {
        if (mMediaPlayer != null) {
            mMediaPlayer?.stop()
        }
    }

    fun reset() {
        if (mMediaPlayer != null) {
            mMediaPlayer?.reset()
        }
    }

    fun release() {
        if (mMediaPlayer != null) {
            mMediaPlayer?.reset()
            mMediaPlayer?.release()
            mMediaPlayer = null
        }
    }
}

  1. Activity调用

MainActivity.kt

package com.example.ijkplayerdemo

import android.os.Bundle
import android.os.Environment
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import tv.danmaku.ijk.media.player.IMediaPlayer
import tv.danmaku.ijk.media.player.IjkMediaPlayer
import java.io.File

class MainActivity : AppCompatActivity() {
    var ijkVideoView:IjkVideoView?=null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Environment.getStorageDirectory().absolutePath
        // init player
        IjkMediaPlayer.loadLibrariesOnce(null)
        IjkMediaPlayer.native_profileBegin("libijkplayer.so")
        ijkVideoView = findViewById(R.id.player)

        ijkVideoView?.setListener(object: IjkVideoView.VideoPlayerListener() {
            override fun onPrepared(p: IMediaPlayer?) {
                //播放成功处理
                p?.start()
            }

            override fun onCompletion(p0: IMediaPlayer?) {
                Toast.makeText(this@MainActivity,"播放完成",Toast.LENGTH_LONG).show()
            }

            override fun onError(p0: IMediaPlayer?, p1: Int, p2: Int): Boolean {
                Toast.makeText(this@MainActivity,"播放失败",Toast.LENGTH_LONG).show()
                return false
            }
        })
        //路径
        ijkVideoView?.setPath("http://tanzi27niu.cdsb.mobi/wps/wp-content/uploads/2017/05/2017-05-17_17-33-30.mp4");
        ijkVideoView?.start();

    }
}
  1. 布局文件

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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.example.ijkplayerdemo.IjkVideoView
        android:id="@+id/player"
        android:layout_width="match_parent"
        android:layout_height="250dp"/>

</RelativeLayout>
  1. 添加网络和存储权限声明(不要忘记自己在程序中申请存储权限哦!)
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>