IjkPlayer简要学习及应用

引言


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

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

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

正文


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


IjkPlayer简要学习及应用_第1张图片
主页面

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


IjkPlayer简要学习及应用_第2张图片
文件路径

播放界面

也可以通过ActionBar中的Sample选择网络资源。通过后缀.m3u8可以看出是HLS的资源
IjkPlayer简要学习及应用_第3张图片
播放列表

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


IjkPlayer简要学习及应用_第4张图片
参数设置

播放操作涉及到的界面是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 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简要学习及应用)