• 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.csdn.net/z979451341

  • 相关阅读:
    [LeetCode] Kth Smallest Element in a BST
    Dojo入门篇
    不要小看了get 与set
    怎样安装Windows7操作系统
    MFC Wizard创建的空应用程序中各个文件内容的解析
    hadoop hdfs空间满后重新启动不了
    树形结构——基本原理
    R语言pdf输出中文乱码处理
    Javascript基本概念梳理
    Java动态代理
  • 原文地址:https://www.cnblogs.com/jianpanwuzhe/p/8534757.html
Copyright © 2020-2023  润新知