项目地址
https://github.com/979451341/Myijkplayer

前段时候我觉得FFmpeg做个视频播放器好难,虽然播放上没问题,但暂停还有通过拖动进度条来设置播放进度,这些都即便做得到,可以那个延缓。。。。。

现在学习一下目前移动端最知名的视频播放器的框架ijkplayer,这个框架他是基于FFmpeg、SDL、还有安卓原生API MediaCodec之类的。他是没有播放界面的,这个需要我们去做,所以这个里我就做个基于ijkplayer的视频播放器,随便浅显的说一下ijkplayer的源码,关于ijkplayer的源码以后会专门出一篇博客说一下。

1.首先了解一下ijkplayer咋用

我这里引入ijkplayer是通过添加依赖

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-armv5:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.8.8'

implementation 'tv.danmaku.ijk.media:ijkplayer-exo:0.8.8'

然后说说ijkplayer是如何播放视频的

ijkplayer每一次播放视频都是通过创建Mediaplayer,然后赋值到一个接口类上,这里他创建的时候能够挑选解码的类型,是因为基于安卓原生API MediaCodec的话是硬解,速度快、兼容差,如果是基于FFmpeg则是软解,速度慢、兼容好,不过这个兼容问题,因为我们在引入依赖的时候把各个处理器相应的依赖,所以可以使用硬解,兼容问题基本都是手机处理器不同产生的。

    IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();
    ijkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG);

// //开启硬解码
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);
IMediaPlayer mMediaPlayer = null;
mMediaPlayer = ijkMediaPlayer;

关于IjkMediaPlayer的源码我只贴出一个函数的,从下面几个loadLibrary看出来,他还是基于FFmpeg、SDL底层实现的。

public static void loadLibrariesOnce(IjkLibLoader libLoader) {
    Class var1 = IjkMediaPlayer.class;
    synchronized(IjkMediaPlayer.class) {
        if(!mIsLibLoaded) {
            if(libLoader == null) {
                libLoader = sLocalLibLoader;
            }

            libLoader.loadLibrary("ijkffmpeg");
            libLoader.loadLibrary("ijksdl");
            libLoader.loadLibrary("ijkplayer");
            mIsLibLoaded = true;
        }

    }
}

好了,回到那个接口类的IMediaPlayer,源码不多贴出来看一下,通过这些接口函数我们都可以知道这个ijkplayer如何使用我们都有了一个底,什么setDataSource、setDisplay,设置播放源、设置播放的屏幕信息。还有start、stop、pause,视频播放的开始、停止、暂停,还有一大堆的接口,这些都是为了监听播放器的状态

public interface IMediaPlayer {
。。。。。。

void setDisplay(SurfaceHolder var1);

void setDataSource(Context var1, Uri var2) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;

@TargetApi(14)
void setDataSource(Context var1, Uri var2, Map<String, String> var3) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;

void setDataSource(FileDescriptor var1) throws IOException, IllegalArgumentException, IllegalStateException;

void setDataSource(String var1) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;

String getDataSource();

void prepareAsync() throws IllegalStateException;

void start() throws IllegalStateException;

void stop() throws IllegalStateException;

void pause() throws IllegalStateException;

void setScreenOnWhilePlaying(boolean var1);

int getVideoWidth();

int getVideoHeight();

boolean isPlaying();

void seekTo(long var1) throws IllegalStateException;

long getCurrentPosition();

long getDuration();

void release();

void reset();

void setVolume(float var1, float var2);

int getAudioSessionId();

MediaInfo getMediaInfo();

/** @deprecated */
@Deprecated
void setLogEnabled(boolean var1);

/** @deprecated */
@Deprecated
boolean isPlayable();

void setOnPreparedListener(IMediaPlayer.OnPreparedListener var1);

void setOnCompletionListener(IMediaPlayer.OnCompletionListener var1);

void setOnBufferingUpdateListener(IMediaPlayer.OnBufferingUpdateListener var1);

void setOnSeekCompleteListener(IMediaPlayer.OnSeekCompleteListener var1);

void setOnVideoSizeChangedListener(IMediaPlayer.OnVideoSizeChangedListener var1);

void setOnErrorListener(IMediaPlayer.OnErrorListener var1);

void setOnInfoListener(IMediaPlayer.OnInfoListener var1);

void setOnTimedTextListener(IMediaPlayer.OnTimedTextListener var1);

void setAudioStreamType(int var1);

/** @deprecated */
@Deprecated
void setKeepInBackground(boolean var1);

int getVideoSarNum();

int getVideoSarDen();

/** @deprecated */
@Deprecated
void setWakeMode(Context var1, int var2);

void setLooping(boolean var1);

boolean isLooping();

ITrackInfo[] getTrackInfo();

void setSurface(Surface var1);

void setDataSource(IMediaDataSource var1);

public interface OnTimedTextListener {
    void onTimedText(IMediaPlayer var1, IjkTimedText var2);
}

public interface OnInfoListener {
    boolean onInfo(IMediaPlayer var1, int var2, int var3);
}

public interface OnErrorListener {
    boolean onError(IMediaPlayer var1, int var2, int var3);
}

public interface OnVideoSizeChangedListener {
    void onVideoSizeChanged(IMediaPlayer var1, int var2, int var3, int var4, int var5);
}

public interface OnSeekCompleteListener {
    void onSeekComplete(IMediaPlayer var1);
}

public interface OnBufferingUpdateListener {
    void onBufferingUpdate(IMediaPlayer var1, int var2);
}

public interface OnCompletionListener {
    void onCompletion(IMediaPlayer var1);
}

public interface OnPreparedListener {
    void onPrepared(IMediaPlayer var1);
}

}

2.写界面

全屏播放,潜入式

//正真的全屏,隐藏了状态栏、AtionBar、导航栏
@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus && Build.VERSION.SDK_INT >= 19) {
        View decorView = getWindow().getDecorView();
        decorView.setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
    }
}

然后上下两个栏目,负责一些播放器的控制,顶部负责设置返回、设置按钮,底部需要设置播放/暂停按钮、播放进度条、停止按钮

<com.example.zth.two.VideoPlayerIJK
    android:id="@+id/ijk_player"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

<include
    android:id="@+id/include_play_top"
    layout="@layout/include_play_top"
    android:layout_alignParentTop="true"
    android:layout_width="match_parent"
    android:layout_height="50dp" />

<include
    android:id="@+id/include_play_bottom"
    layout="@layout/include_play_bottom"
    android:layout_alignParentBottom="true"
    android:layout_width="match_parent"
    android:layout_height="50dp" />

关于上下两个栏目在用户观看视频时需要隐藏,在用户点击屏幕则显示两个栏目,供用户使用
这个则是需要通过计时器来完成记录目前距离上一次用户点击屏幕的时间,如果视频超过3秒,则隐藏栏目,如果点击屏幕则恢复,关于隐藏和恢复使用Animation来完成。

    timer = new Timer();
    timerTask = new TimerTask() {
        @Override
        public void run() {
            long t = System.currentTimeMillis();
            if (t - time > 3000 && menu_visible) {
                time = t;
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        Animation animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.move_bottom);
                        rl_bottom.startAnimation(animation);
                        Animation animation_top = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.move_top);
                        rl_top.startAnimation(animation_top);
                        menu_visible = false;
                    }
                });
            }

        }
    };

还需要加载框,这个在视频加载完的接口回调里隐藏他

    <ProgressBar
        android:id="@+id/pb_loading"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_centerInParent="true"
        android:layout_marginTop="60dp"
        android:indeterminate="false"
        android:indeterminateDrawable="@drawable/video_loading"
        android:padding="5dp" />

3.播放实现

这里我是使用了一个另一个博主封装的ijkplayer的类

说一下这个类的运行过程

一开始创建MediaPlayer做一些配置,赋值给一个接口类,并且暴露了接口

    IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();
    ijkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG);

// //开启硬解码
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);
    }

然后设置播放源,这个播放源能够是本地视频路径、网络视频url、还可以是网络RTMP推流url,还要讲SurfaceView的配置信息给他

    try {
        mMediaPlayer.setDataSource(mPath);
    } catch (IOException e) {
        e.printStackTrace();
    }
    //给mediaPlayer设置视图
    mMediaPlayer.setDisplay(surfaceView.getHolder());

    mMediaPlayer.prepareAsync();

想开始播放就调用IMediaPlayer的那些控制函数

public void start() {
    if (mMediaPlayer != null) {
        mMediaPlayer.start();
    }
}

还有关于activity的生命周期控制,其中native_profileEnd就相当于很智能的暂停,当屏幕回的时候就继续播放视频。

@Override
protected void onStop() {
    IjkMediaPlayer.native_profileEnd();
    handler.removeCallbacksAndMessages(null);
    super.onStop();

}

结束Activity的时候就停止播放视频并释放资源

@Override
protected void onDestroy() {
    if (ijkPlayer != null) {
        ijkPlayer.stop();
        ijkPlayer.release();
        ijkPlayer = null;
    }

    super.onDestroy();
}

最后还有播放进度,我这里是通过handler自己调用自己循环更新播放时间显示和进度条

    handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_REFRESH:
                    if (ijkPlayer.isPlaying()) {
                        refresh();
                        handler.sendEmptyMessageDelayed(MSG_REFRESH, 1000);
                    }

                    break;
            }

        }
    };

private void refresh() {
    long current = ijkPlayer.getCurrentPosition() / 1000;
    long duration = ijkPlayer.getDuration() / 1000;
    Log.v("zzw", current + " " + duration);
    long current_second = current % 60;
    long current_minute = current / 60;
    long total_second = duration % 60;
    long total_minute = duration / 60;
    String time = current_minute + ":" + current_second + "/" + total_minute + ":" + total_second;
    tvTime.setText(time);
    if(duration != 0){
        seekBar.setProgress((int) (current * 100 / duration));
    }

}

在用户拖动进度条的时候取消handler发送消息,拖动结束再接续更新播放进度

    seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
            //进度改变
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
            //开始拖动

            handler.removeCallbacksAndMessages(null);

        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            //停止拖动
            ijkPlayer.seekTo(ijkPlayer.getDuration() * seekBar.getProgress() / 100);
            handler.sendEmptyMessageDelayed(MSG_REFRESH, 100);
        }
    });

关于播放网络视频和网络RTMP推流,播放本身没问题就是不能获取视频时长和视频当前时间,不过暂停和停止还有效。

还有就是如果想要重新播放需要重新setVideoPath然后start

看看效果
本地视频播放

播放RTMP推流

播放网络视频

ijkplayer的使用看起来是不是很简单,但是还没有实现小窗口和全屏之间的切换。。。。。。这两天我再看看ijkplayer,再和各位说说
博客首发地址
http://blog.csdn.net/z979451341