美文网首页音视频开发
IjkPlayer简要学习及应用

IjkPlayer简要学习及应用

作者: s1991721 | 来源:发表于2017-12-23 15:42 被阅读1503次

    引言


    之前学习和使用过EXOPlayer,并结合Shared Elements效果在公司的项目中有应用。文章写的很烂直接看github代码吧!

    相比EXOPlayer,B站的IjkPlayer逼格很高,是基于ffmpeg开源的轻量级视频播放器支持Android&iOS。源码在GitHub,down下来后需要编译才能运行,具体操作官方都有说明且网上资料很多。

    我所编译的版本是0.8.2,本文会对其大体流程梳理一遍并封装一个实用性较高的控件

    正文


    官方的demo跑起来第一个界面形同文件管理


    主页面

    找到本机的视频文件就可以播放了


    文件路径
    播放界面
    也可以通过ActionBar中的Sample选择网络资源。通过后缀.m3u8可以看出是HLS的资源
    播放列表

    还有ActionBar中的Setting,这里是一些播放时所用到的参数后文会有详解。


    参数设置

    播放操作涉及到的界面是VideoActivity,这里有官方封装的播放控件IjkVideoView,在学习了官方设计后,我结合自身的实际需求自己封装了一个控件在后文会提到,这里先来学习一下官方的设计。

    IjkVideoView


    使用时:初始化控件-->设置资源路径-->start。
    控件内部的主要逻辑顺序有以下:

    初始化:
    initRenders() 根据设置初始化渲染器类型(渲染器即SurfaceView、TextureView)
    setRender(int render) 根据渲染器类型初始化渲染器
    setRenderView(IRenderView renderView) 将渲染器添加到视图
    开始播放:
    setVideoURI() 设置资源路径
    openVideo() 初始化播放
    -->createPlayer() 创建播放器
    -->bindSurfaceHolder() 播放器与渲染器绑定
    
        private void initRenders() {
            mAllRenders.clear();//渲染器列表
            //根据设置界面所选的渲染器,将其加入列表。
            //这么做其实是为了在demo播放的时候手动切换渲染器,用以观察毕竟是demo
            //切换到时所用到的方法   toggleRender()
            if (mSettings.getEnableSurfaceView())
                mAllRenders.add(RENDER_SURFACE_VIEW);
            if (mSettings.getEnableTextureView() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
                mAllRenders.add(RENDER_TEXTURE_VIEW);
            if (mSettings.getEnableNoView())
                mAllRenders.add(RENDER_NONE);
    
            if (mAllRenders.isEmpty())
                mAllRenders.add(RENDER_SURFACE_VIEW);
            mCurrentRender = mAllRenders.get(mCurrentRenderIndex);
            setRender(mCurrentRender);
        }
    
    //根据类型初始化渲染器
    //这里将SurfaceView、TextureView进行了封装,用到了模板设计模式,目的是将同一目的不同的操作交由具体的子类
        public void setRender(int render) {
            switch (render) {
                case RENDER_NONE:
                    setRenderView(null);
                    break;
                case RENDER_TEXTURE_VIEW: {
                    TextureRenderView renderView = new TextureRenderView(getContext());
                    if (mMediaPlayer != null) {
                        renderView.getSurfaceHolder().bindToMediaPlayer(mMediaPlayer);
                        renderView.setVideoSize(mMediaPlayer.getVideoWidth(), mMediaPlayer.getVideoHeight());
                        renderView.setVideoSampleAspectRatio(mMediaPlayer.getVideoSarNum(), mMediaPlayer.getVideoSarDen());
                        renderView.setAspectRatio(mCurrentAspectRatio);
                    }
                    setRenderView(renderView);
                    break;
                }
                case RENDER_SURFACE_VIEW: {
                    SurfaceRenderView renderView = new SurfaceRenderView(getContext());
                    setRenderView(renderView);
                    break;
                }
                default:
                    Log.e(TAG, String.format(Locale.getDefault(), "invalid render %d\n", render));
                    break;
            }
        }
    
        public void setRenderView(IRenderView renderView) {
            ...
            //切换渲染器时清楚之前的渲染器
            ...
    
            if (renderView == null)
                return;
    
            mRenderView = renderView;
    
            ...
            //简单起见,将视图的显示比例代码忽略
            ...
    
            View renderUIView = mRenderView.getView();
            FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
                    FrameLayout.LayoutParams.WRAP_CONTENT,
                    FrameLayout.LayoutParams.WRAP_CONTENT,
                    Gravity.CENTER);
            renderUIView.setLayoutParams(lp);
            addView(renderUIView);
    
            mRenderView.addRenderCallback(mSHCallback);//SurfaceView的回调,不详细展开
            mRenderView.setVideoRotation(mVideoRotationDegree);//旋转角度,横竖屏
        }
    

    到此控件已经初始化完毕,在视图上就可以看到自定义控件。但此时并没有初始化播放器,视图显示的只是一个SurfaceView或TextureView。这时就需要给控件设置播放的资源地址了。

        //原类中重载了几次
        private void setVideoURI(Uri uri, Map<String, String> headers) {
            mUri = uri;
            mHeaders = headers;
            mSeekWhenPrepared = 0;
            openVideo();
            requestLayout();
            invalidate();
        }
    

    紧接着就调用了openVideo()方法,在此之前先剖析下createPlayer(),此方法在openVideo()中调用

        public IMediaPlayer createPlayer(int playerType) {
            IMediaPlayer mediaPlayer = null;
            //根据设置界面所选的播放器进行创建,有EXOPlayer和原生的MediaPlayer,这里不是重点直接跳到IjkPlayer
            switch (playerType) {
                
                ...省略其他播放器...
    
                case Settings.PV_PLAYER__IjkMediaPlayer:
                default: {
                    IjkMediaPlayer ijkMediaPlayer = null;
                    if (mUri != null) {
                        ijkMediaPlayer = new IjkMediaPlayer();
                        ijkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG);
       
                        ...
                        此处省略了一堆设置里面设置的播放参数
                        ...
    
                    }
                    mediaPlayer = ijkMediaPlayer;
                }
                break;
            }
            //这里是一个关于TextureView的代理写法
            if (mSettings.getEnableDetachedSurfaceTextureView()) {
                mediaPlayer = new TextureMediaPlayer(mediaPlayer);
            }
    
            return mediaPlayer;
        }
    
        private void openVideo() {
            if (mUri == null || mSurfaceHolder == null) {
                // not ready for playback just yet, will try again later
                return;
            }
            // we shouldn't clear the target state, because somebody might have
            // called start() previously
            //demo切换播放时用
            release(false);
    
            AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
            am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
    
            try {
                mMediaPlayer = createPlayer(mSettings.getPlayer());
    
                // TODO: create SubtitleController in MediaPlayer, but we need
                // a context for the subtitle renderers
                final Context context = getContext();
                // REMOVED: SubtitleController
    
                // REMOVED: mAudioSession
                //一堆监听
                mMediaPlayer.setOnPreparedListener(mPreparedListener);
                mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
                mMediaPlayer.setOnCompletionListener(mCompletionListener);
                mMediaPlayer.setOnErrorListener(mErrorListener);
                mMediaPlayer.setOnInfoListener(mInfoListener);
                mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
                mMediaPlayer.setOnSeekCompleteListener(mSeekCompleteListener);
                mMediaPlayer.setOnTimedTextListener(mOnTimedTextListener);
                mCurrentBufferPercentage = 0;
                //设置资源URI
                String scheme = mUri.getScheme();
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
                        mSettings.getUsingMediaDataSource() &&
                        (TextUtils.isEmpty(scheme) || scheme.equalsIgnoreCase("file"))) {
                    IMediaDataSource dataSource = new FileMediaDataSource(new File(mUri.toString()));
                    mMediaPlayer.setDataSource(dataSource);
                }  else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                    mMediaPlayer.setDataSource(mAppContext, mUri, mHeaders);
                } else {
                    mMediaPlayer.setDataSource(mUri.toString());
                }
                //将渲染器与播放器绑定
                bindSurfaceHolder(mMediaPlayer, mSurfaceHolder);
                mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
                mMediaPlayer.setScreenOnWhilePlaying(true);
                mPrepareStartTime = System.currentTimeMillis();
                mMediaPlayer.prepareAsync();//这里已经开始异步缓冲了,会回调到OnPreparedListener,根据具体状态开始播放
                if (mHudViewHolder != null)
                    mHudViewHolder.setMediaPlayer(mMediaPlayer);
    
                // REMOVED: mPendingSubtitleTracks
    
                // we don't set the target state here either, but preserve the
                // target state that was there before.
                mCurrentState = STATE_PREPARING;
                attachMediaController();
            } catch (IOException ex) {
                Log.w(TAG, "Unable to open content: " + mUri, ex);
                mCurrentState = STATE_ERROR;
                mTargetState = STATE_ERROR;
                mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
            } catch (IllegalArgumentException ex) {
                Log.w(TAG, "Unable to open content: " + mUri, ex);
                mCurrentState = STATE_ERROR;
                mTargetState = STATE_ERROR;
                mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
            } finally {
                // REMOVED: mPendingSubtitleTracks.clear();
            }
        }
    
        //具体的操作已经转到了相应的子类TextureRenderView、SurfaceRenderView
        private void bindSurfaceHolder(IMediaPlayer mp, IRenderView.ISurfaceHolder holder) {
            if (mp == null)
                return;
    
            if (holder == null) {
                mp.setDisplay(null);
                return;
            }
    
            holder.bindToMediaPlayer(mp);
        }
    

    到此官方的IjkVideoView就已经初始化完成并开始播放资源,其余public方法是为了操作控件或增加播放控制控件所使用的。根据其主体思路我自己封装了VideoViewIjk。

    VideoViewIjk


    我命名时习惯功能放前别名放后,大体的思路如下:
    初始化播放器-->初始化播放视图SurfaceView-->实现必要的监听-->公开操作方法

        private void initPlayer() {
            IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();
    
            ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", mediacodec);
            ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", mediacodec_auto_rotate);
            ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", mediacodec_handle_resolution_change);
            ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", opensles);
            ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", overlay_format);
            ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", framedrop);
            ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", start_on_prepared);
            ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "http-detect-range-support", http_detect_range_support);
            ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "skip_loop_filter", skip_loop_filter);
    
            mediaPlayer = ijkMediaPlayer;
            mediaPlayer.setOnPreparedListener(this);
            mediaPlayer.setOnVideoSizeChangedListener(this);
            mediaPlayer.setOnCompletionListener(this);
            mediaPlayer.setOnErrorListener(this);
            mediaPlayer.setOnInfoListener(this);
            mediaPlayer.setOnBufferingUpdateListener(this);
            mediaPlayer.setOnSeekCompleteListener(this);
        }
    
        private void initView() {
            surfaceView = new SurfaceView(getContext());
            surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
                @Override
                public void surfaceCreated(SurfaceHolder holder) {
                    mediaPlayer.setDisplay(surfaceView.getHolder());
                }
    
                @Override
                public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    
                }
    
                @Override
                public void surfaceDestroyed(SurfaceHolder holder) {
    
                }
            });
            LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            addView(surfaceView, 0, layoutParams);
        }
    

    这里其实没什么好贴的,只是将官方demo中不需要的方法移除了,具体的可以看我的GitHub

    相关文章

      网友评论

        本文标题:IjkPlayer简要学习及应用

        本文链接:https://www.haomeiwen.com/subject/vbwowxtx.html