Android 音视频深入 十九 使用ijkplayer做个视频播放器(附源码下载)

项目地址
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.51cto.com/13591594/2084627

时间: 2024-11-05 14:41:50

Android 音视频深入 十九 使用ijkplayer做个视频播放器(附源码下载)的相关文章

Android学习笔记(十五)——碎片的生命周期(附源码)

碎片的生命周期 点击下载源码 与活动类似,碎片具有自己的生命周期.理解了碎片的生命周期后,我们可以在碎片被销毁时正确地保存其实例,在碎片被重建时将其还原到前一个状态. 1.使用上一篇的项目Fragments,在Fragment1.java文件中添加如下代码: package net.zenail.Fragments; import android.app.Activity; import android.os.Bundle; import android.support.v4.app.Fragm

Android学习笔记(十六)——碎片之间进行交互(附源码)

碎片之间进行交互 点击下载源码 很多时候,一个活动中包含一个或者多个碎片,它们彼此协作,向用户展示一个一致的UI.在这种情况下,碎片之间能进行通信并交换数据十分重要. 1.使用上一篇中创建的同一个项目,在fragment.xml中添加TextView的标识id: android:id="@+id/lblFragment1" 2.在fragment2.xml中添加一个Button,用于与fragment1进行交互: <Button android:id="@+id/btn

android Listview分批加载+自动加载(附源码下载)

直接上代码,代码有注释: public class TestForListviewActivity extends Activity implements OnScrollListener { private ListView mListview = null; private View mFooterView; private PaginationAdapter mAdapter; private Handler handler=new Handler(); private boolean i

Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)

Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载) POSTED ON 2014年6月27日 BY 天边的星星 本文内容: 1.横向ListView的所有实现思路; 2.其中一个最通用的思路HorizontalListView,并基于横向ListView开发一个简单的相册: 3.实现的横向ListView在点击.浏览时item背景会变色,并解决了listview里setSelected造成item的选择状态混乱的问题.

Android网络:HTTP之利用HttpURLConnection访问网页、获取网络图片实例 (附源码)

http://blog.csdn.net/yanzi1225627/article/details/22222735 如前文所示的TCP局域网传送东西,除了对传输层的TCP/UDP支持良好外,Android对HTTP(超文本传输协议)也提供了很好的支持,这里包括两种接口: 1.标准Java接口(java.net) ----HttpURLConnection,可以实现简单的基于URL请求.响应功能: 2.Apache接口(org.appache.http)----HttpClient,使用起来更方

Android中Loader及LoaderManager的使用(附源码下载)

managedQuery方法的缺陷 Loader是用来更好地加载数据的,在我们谈论Loader之前,我们先研究一下Activity的managedQuery方法,该方法也是用于在Activity中加载数据的.在Android 3.0之前的版本中,我们如果想在Activity中通过ContentResolver对ContentProvider进行查询,我们可以方便的调用Activity的managedQuery方法,该方法的源码如下: @Deprecated public final Cursor

Android 音视频深入 十六 FFmpeg 推流手机摄像头,实现直播 (附源码下载)

源码地址https://github.com/979451341/RtmpCamera/tree/master 配置RMTP服务器,虽然之前说了,这里就直接粘贴过来吧 1.配置RTMP服务器 这个我不多说贴两个博客分别是在mac和windows环境上的,大家跟着弄MAC搭建RTMP服务器https://www.jianshu.com/p/6fcec3b9d644这个是在windows上的,RTMP服务器搭建(crtmpserver和nginx) https://www.jianshu.com/p

Android 音视频深入 十八 FFmpeg播放视频,有声音(附源码下载)

项目地址https://github.com/979451341/AudioVideoStudyCodeTwo/tree/master/FFmpegv%E6%92%AD%E6%94%BE%E8%A7%86%E9%A2%91%E6%9C%89%E5%A3%B0%E9%9F%B3%EF%BC%8C%E6%9A%82%E5%81%9C%EF%BC%8C%E9%87%8A%E6%94%BE%E3%80%81%E5%BF%AB%E8%BF%9B%E3%80%81%E9%80%80%E5%90%8E 这个项

Android 音视频深入 四 录视频MP4(附源码下载)

本篇项目地址,名字是<录音视频(有的播放器不能放,而且没有时长显示)>,求star https://github.com/979451341/Audio-and-video-learning-materials1.MediaMuser说明 MediaMuser:将封装编码后的视频流和音频流到mp4容器中,说白了能够将音视频整合成一个MP4文件,MediaMuxer最多仅支持一个视频track和一个音频track,所以如果有多个音频track可以先把它们混合成为一个音频track然后再使用Med