ijkplayer接入使用

我叫、王凱發表於2021-03-09

1、ijkplayer簡介

ijkplayer是一個基於FFmpeg的輕量級Android/iOS視訊播放器。FFmpeg的是全球領先的多媒體框架,能夠解碼,編碼, 轉碼,複用,解複用,流,過濾器和播放大部分的視訊格式。它提供了錄製、轉換以及流化音視訊的完整解決方案。它包含了非常先進的音訊/視訊編解碼庫libavcodec,為了保證高可移植性和編解碼質量,libavcodec裡很多code都是從頭開發的。

2、ijkplayer匯入方式

ijk的匯入方式有兩種,第一種是使用gradle匯入ijkplayer釋出到jcenter,已經打包好的依賴包,第二種是去github中下載ijkplayer原始碼,自己進行編譯。

  1. gradle方式匯入:

    allprojects {
        repositories {
            jcenter()
        }
    }
    dependencies {
        # required, enough for most devices.
        compile 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8' 
        compile 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8'
    
        # Other ABIs: optional
        compile 'tv.danmaku.ijk.media:ijkplayer-armv5:0.8.8'
        compile 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8'
        compile 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.8'
        compile 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.8.8'
    
        # ExoPlayer as IMediaPlayer: optional, experimental
        compile 'tv.danmaku.ijk.media:ijkplayer-exo:0.8.8' 
    }
    
  2. 從github下載ijkplayer原始碼,進行編譯

    1. 從github中拉取程式碼,並且cd到程式碼的目錄下
      git clone https://github.com/Bilibili/ijkplayer.git ijkplayer-android
      cd ijkplayer-android
      
    2. 選擇k0.8.8分支
      git checkout -B latest k0.8.8
      
    3. 執行ijkplayer專案中的init-android.sh進行初始化,包括了把ffmpeg的程式碼拉取到本地等操作
      ./init-android.sh
      
    4. 進入android/contrib目錄下,clean,然後編譯一下ffmpeg軟解碼庫
      cd android/contrib
      ./compile-ffmpeg.sh clean
      ./compile-ffmpeg.sh all
      
    5. cd到上一級目錄,也就是android目錄下,執行compile-ijk.sh進行編譯
      cd ..
      ./compile-ijk.sh all
      

    執行完成之後,會得到ijk所需的so檔案,目錄如圖所示:
    ijk的so檔案生成目錄
    在各個cpu架構下,都會有相應的so檔案,比如armv7a的so檔案,就在ijkplayer-armv7a/src/main/libs/armeabi-v7a目錄下。

    編譯完了so檔案,我們看到上圖中還有一個ijkplayer-java的檔案,這個就是ijk中,jni呼叫so檔案的包,我們可以編譯這個包,也可以直接使用gradle方式,直接引入相應版本的ijkplayer-java包。
    編譯ijkplayer的方式很簡單,進入到ijkplayer-java工程下,執行程式碼:

    ./gradlew clean build
    

    然後把build/out中的aar,放到自己工程並依賴就可以了

    gradle依賴java包,與上面的gradle依賴一樣,在自己工程下新增如下程式碼:

    由於我下載編譯的是k0.8.8,所以我依賴的java包也是0.8.8,避免出現版本不相容的問題

    compile 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8' 
    

    最後把自己編譯的好so檔案,放到工程的jniLib下面就好了。

3、簡單的ijkplayer使用

  1. 引入.so檔案:這裡用的是原始碼編譯後,匯入so檔案的方式,把相應的so檔案放在自己工程的jniLibs資料夾中。
  2. 使用gradle匯入ijkplayer-java包(此包是ijkplayer的jni程式碼)。
    compile 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8' 
    
  3. 自定義播放控制元件,類名自取,繼承自FrameLayout。
    /**
     * 由ijkplayer提供,用於播放視訊,需要給他傳入一個surfaceView
     */
    private IMediaPlayer mMediaPlayer = null;
    /**
     * 視訊檔案地址
     */
    private String mPath ;
    /**
     * 視訊請求header
     */
    private Map<String,String> mHeader;
    
    private SurfaceView mSurfaceView;
    
    private Context mContext;
    private boolean mEnableMediaCodec;
    
    public VideoPlayer(@NonNull Context context) {
        this(context, null);
    }
    
    public VideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
    
    public VideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }
    
     //初始化
    private void init(Context context) {
        mContext = context;
        setBackgroundColor(Color.BLACK);
        createSurfaceView();
        mAudioManager = (AudioManager)mContext.getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
        mAudioFocusHelper = new AudioFocusHelper();
    }
    
    //建立surfaceView
    private void createSurfaceView() {
        mSurfaceView = new SurfaceView(mContext);
        mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder surfaceHolder) {
    
            }
    
            @Override
            public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
                if (mMediaPlayer != null) {
                    mMediaPlayer.setDisplay(surfaceHolder);
                }
            }
    
            @Override
            public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
    
            }
        });
        LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT
                , LayoutParams.MATCH_PARENT, Gravity.CENTER);
    //        mSurfaceView.setLayoutParams(layoutParams);
        addView(mSurfaceView,0,layoutParams);
    }
    
    //建立一個新的player
    private IMediaPlayer createPlayer() {
        IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 1);
    
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32);
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1);
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 0);
    
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "http-detect-range-support", 1);
    
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48);
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "min-frames", 100);
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "enable-accurate-seek", 1);
    
        ijkMediaPlayer.setVolume(1.0f, 1.0f);
    
        setEnableMediaCodec(ijkMediaPlayer,mEnableMediaCodec);
        return ijkMediaPlayer;
    }
    
    //設定是否開啟硬解碼
    private void setEnableMediaCodec(IjkMediaPlayer ijkMediaPlayer, boolean isEnable) {
        int value = isEnable ? 1 : 0;
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", value);//開啟硬解碼
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", value);
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", value);
    }
    
    public void setEnableMediaCodec(boolean isEnable){
        mEnableMediaCodec = isEnable;
    }
    
    //設定ijkplayer的監聽
    private void setListener(IMediaPlayer player){
        player.setOnPreparedListener(mPreparedListener);
        player.setOnVideoSizeChangedListener(mVideoSizeChangedListener);
    }
    
    /**
     * 設定自己的player回撥
     */
    public void setVideoListener(VideoListener listener){
        mListener = listener;
    }
    
    //設定播放地址
    public void setPath(String path) {
        setPath(path,null);
    }
    
    public void setPath(String path,Map<String,String> header){
        mPath = path;
        mHeader = header;
    }
    
    //開始載入視訊
    public void load() throws IOException {
        if(mMediaPlayer != null){
            mMediaPlayer.stop();
            mMediaPlayer.release();
        }
        mMediaPlayer = createPlayer();
        setListener(mMediaPlayer);
        mMediaPlayer.setDisplay(mSurfaceView.getHolder());
        mMediaPlayer.setDataSource(mContext, Uri.parse(mPath),mHeader);
    
        mMediaPlayer.prepareAsync();
    }
    
    public void start() {
        if (mMediaPlayer != null) {
            mMediaPlayer.start();
            mAudioFocusHelper.requestFocus();
        }
    }
    
    public void release() {
        if (mMediaPlayer != null) {
            mMediaPlayer.reset();
            mMediaPlayer.release();
            mMediaPlayer = null;
            mAudioFocusHelper.abandonFocus();
        }
    }
    
    public void pause() {
        if (mMediaPlayer != null) {
            mMediaPlayer.pause();
            mAudioFocusHelper.abandonFocus();
        }
    }
    
    public void stop() {
        if (mMediaPlayer != null) {
            mMediaPlayer.stop();
            mAudioFocusHelper.abandonFocus();
        }
    }
    
    
    public void reset() {
        if (mMediaPlayer != null) {
            mMediaPlayer.reset();
            mAudioFocusHelper.abandonFocus();
        }
    }
    
    
    public long getDuration() {
        if (mMediaPlayer != null) {
            return mMediaPlayer.getDuration();
        } else {
            return 0;
        }
    }
    
    
    public long getCurrentPosition() {
        if (mMediaPlayer != null) {
            return mMediaPlayer.getCurrentPosition();
        } else {
            return 0;
        }
    }
    
    
    public void seekTo(long l) {
        if (mMediaPlayer != null) {
            mMediaPlayer.seekTo(l);
        }
    }
    
    public boolean isPlaying(){
        if(mMediaPlayer != null) {
            return mMediaPlayer.isPlaying();
        }
        return false;
    }
    
  4. 之後在activity的佈局中使用這個自定義videoView就可以了。
    activity裡,基本程式碼:
    private VideoPlayer videoPlayer;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        videoPlayer = findViewById(R.id.video);
    
        videoPlayer.setVideoListener(this);
        videoPlayer.setPath("http://ipfs.ztgame.com.cn/QmRRGU4aUZEqJsHxKzBb1ns97GHw45eCRRZFe6Eu8GCmZ4.m3u8");
        try {
            videoPlayer.load();
        } catch (IOException e) {
            Toast.makeText(this,"播放失敗",Toast.LENGTH_SHORT);
            e.printStackTrace();
        }
    }
    
    @Override
    public void onBufferingUpdate(IMediaPlayer iMediaPlayer, int i) {
    
    }
    
    @Override
    public void onCompletion(IMediaPlayer iMediaPlayer) {
    
    }
    
    @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) {
        videoPlayer.start();
    }
    
    @Override
    public void onSeekComplete(IMediaPlayer iMediaPlayer) {
    
    }
    
    @Override
    public void onVideoSizeChanged(IMediaPlayer iMediaPlayer, int i, int i1, int i2, int i3) {
    
    }
    

4、結束語

到這裡,播放器就可以執行了,如果有問題歡迎留言。
還有一個ijkplayer踩坑記錄,裡面會有一些我的踩坑記錄,不定時更新。
最後附上播放器demo原始碼地址
demo程式碼也會不定時更新,慢慢變成一套可擴充套件,複用的播放器。

相關文章