栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

从0开发打造视频播放器

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

从0开发打造视频播放器

音视频开发框架主要是ijkplayer、VLC、ExoPlayer和其他付费三方库这几种,本项目采用的是ijkplayer,学习难度低。

首先是ijkplayer的引入,为了支持更多格式,这里使用了GSYVideoPlayer的so包

allprojects {
    repositories {
        jcenter()
        maven { url 'https://jitpack.io' }
        maven { url "https://maven.aliyun.com/repository/public" }
    }
}

//必须
implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'
//更多ijk的编码支持(mpeg,rtsp, concat、crypto协议)
implementation 'com.github.CarGuo.GSYVideoPlayer:gsyVideoPlayer-ex_so:v8.2.0-release-jitpack'

混淆

-keep class tv.danmaku.ijk.media.player.** {*;}
-keep class tv.danmaku.ijk.media.player.IjkMediaPlayer{*;}
-keep class tv.danmaku.ijk.media.player.ffmpeg.FFmpegApi{*;}
1.搭建基类

为了方便以后扩展使用,这里搭建视频基类

public abstract class BaseVideoPlayer extends FrameLayout {
    
    public BaseVideoPlayer(@NonNull Context context) {
        this(context, null);
    }

    public BaseVideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BaseVideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {

    }
}
初始化SurfaceView并添加到布局中
    private Context mContext;
    private SurfaceView mSurfaceView;

    private void init(Context context) {
        mContext = context;
        setBackgroundColor(Color.BLACK);
        createSurfaceView();
    }

    private void createSurfaceView() {
        mSurfaceView = new SurfaceView(mContext);
        mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder surfaceHolder) {

            }

            @Override
            public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
                
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder surfaceHolder) {

            }
        });
        LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT
                , Gravity.CENTER);

        addView(mSurfaceView, 0, layoutParams);
    }
设置播放链接并加载
    
    
    private IMediaPlayer mMediaPlayer = null;
    
    private Map mHeader;

    public void setPathLoad(String path) throws IOException {
        setPathLoad(path, null);
    }

    public void setPathLoad(String path, Map header) throws IOException {
        mHeader = header;
        if (mMediaPlayer != null) {
            mMediaPlayer.stop();
            mMediaPlayer.release();
        }
        mMediaPlayer = createPlayer();
        setListener(mMediaPlayer);
        mMediaPlayer.setDisplay(mSurfaceView.getHolder());
        mMediaPlayer.setDataSource(mContext, Uri.parse(path), mHeader);

        mMediaPlayer.prepareAsync();
    }

由子类实现IMediaPlayer

public abstract IMediaPlayer createPlayer();

设置事件回调方法

    private void setListener(IMediaPlayer player) {
        //在视频预处理完成后被调用。此时视频的宽度、高度、宽高比信息已经获取到,此时可调用seekTo让视频从指定位置开始播放。
        player.setOnPreparedListener(iMediaPlayer -> {
            if (videoListener != null) {
                videoListener.onPrepared(iMediaPlayer);
            }
        });
        player.setOnVideoSizeChangedListener((iMediaPlayer, i, i1, i2, i3) -> {
            if (videoListener != null) {
                videoListener.onVideoSizeChanged(iMediaPlayer, i, i1, i2, i3);
            }
        });
        player.setOnCompletionListener(iMediaPlayer -> {
            if (videoCompletionListener != null) {
                videoCompletionListener.onCompletion(iMediaPlayer);
            }
        });
    }

    
    public void setVideoListener(VideoListener listener) {
        videoListener = listener;
    }

    
    public void setVideoCompletionListener(VideoCompletionListener videoCompletionListener) {
        this.videoCompletionListener = videoCompletionListener;
    }

    private VideoListener videoListener;

    public interface VideoListener {
        void onPrepared(IMediaPlayer iMediaPlayer);

        void onVideoSizeChanged(IMediaPlayer iMediaPlayer, int i, int i1, int i2, int i3);
    }

    private VideoCompletionListener videoCompletionListener;

    public interface VideoCompletionListener {
        void onCompletion(IMediaPlayer iMediaPlayer);
    }

在SurfaceView的surfaceChanged中获取surfaceHolder

    mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder surfaceHolder) {

            }

            @Override
            public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
                if (mMediaPlayer != null) {
                    mMediaPlayer.setDisplay(surfaceHolder);
                }
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder surfaceHolder) {

            }
        });

设置是否开启硬件解码,默认开启

    private boolean mEnableMediaCodec = true;   
 
    public void setEnableMediaCodec(IjkMediaPlayer ijkMediaPlayer) {
        int value = mEnableMediaCodec ? 1 : 0;
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", value);//开启硬解码
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", value);
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", value);
    }

    
    public void setEnableMediaCodec(boolean isEnable) {
        mEnableMediaCodec = isEnable;
    }
播放暂停等方法
    public void start() {
        if (mMediaPlayer != null) {
            mMediaPlayer.start();
        }
    }

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

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

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

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

    public long getDuration() {
        if (mMediaPlayer != null) {
            return mMediaPlayer.getDuration();
        } else {
            return 0;
        }
    }

    public long getCurrentPosition() {
        if (mMediaPlayer != null) {
            return mMediaPlayer.getCurrentPosition();
        } else {
            return 0;
        }
    }

    public void seekTo(long l) {
        if (mMediaPlayer != null) {
            mMediaPlayer.seekTo(l);
        }
    }

    public boolean isPlaying() {
        if (mMediaPlayer != null) {
            return mMediaPlayer.isPlaying();
        }
        return false;
    }

    public void setVolume(float var1, float var2) {
        if (mMediaPlayer != null) {
            mMediaPlayer.setVolume(var1, var2);
        }
    }
修改视频比例

首先设置视频比例格式

    
    private boolean isOpenProportion = false;    

    public static final int PROPORTION_16_9 = 1;
    public static final int PROPORTION_4_3 = 2;
    public static final int PROPORTION_18_9 = 3;
    public static final int PROPORTION_2_39 = 4;

    @IntDef({PROPORTION_16_9, PROPORTION_4_3, PROPORTION_18_9, PROPORTION_2_39})
    @Retention(RetentionPolicy.SOURCE)
    private @interface ProportionType {
    }

    private int proportion = PROPORTION_16_9;

    public int getProportion() {
        return proportion;
    }

    
    public void setInitProportion(@ProportionType int proportion) {
        this.proportion = proportion;
    }

    
    public void setProportion(@ProportionType int proportion) {
        this.proportion = proportion;
        setSurfaceViewSize();
    }

    
    public void setOpenProportion() {
        isOpenProportion = true;
    }

设置播放器大小

    private int mWidth;   
    private int mHeight;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int mWidthMode = MeasureSpec.getMode(widthMeasureSpec);
        int mWidthSize = MeasureSpec.getSize(widthMeasureSpec);

        int mHeightMode = MeasureSpec.getMode(heightMeasureSpec);
        int mHeightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (isOpenProportion) {
            float proportion1 = getHeightVideoRatio();
            float proportion2 = mHeightSize * 1f / mWidthSize;
            if (proportion1 > proportion2) {
                mHeight = mHeightSize;

                if (mWidthMode == MeasureSpec.EXACTLY) {
                    mWidth = mWidthSize;
                } else {
                    int maxWidth = (int) (mHeight * proportion1 + .5);
                    mWidth = getPaddingStart() + maxWidth + getPaddingEnd();
                    if (mWidthMode == MeasureSpec.AT_MOST) {
                        mWidth = Math.min(mWidth, mWidthSize);
                    }
                }
            } else {
                mWidth = mWidthSize;

                if (mHeightMode == MeasureSpec.EXACTLY) {
                    mHeight = mHeightSize;
                } else {
                    int maxHeight = (int) (mWidth * proportion1 + .5);
                    mHeight = getPaddingTop() + maxHeight + getPaddingBottom();
                    if (mHeightMode == MeasureSpec.AT_MOST) {
                        mHeight = Math.min(mHeight, mHeightSize);
                    }
                }
            }
        } else {
            if (mWidthMode == MeasureSpec.EXACTLY) {
                mWidth = mWidthSize;
            } else {
                mWidth = getPaddingStart() + getPaddingEnd();
                if (mWidthMode == MeasureSpec.AT_MOST) {
                    mWidth = Math.min(mWidth, mWidthSize);
                }
            }

            if (mHeightMode == MeasureSpec.EXACTLY) {
                mHeight = mHeightSize;
            } else {
                mHeight = getPaddingTop() + getPaddingBottom();
                if (mHeightMode == MeasureSpec.AT_MOST) {
                    mHeight = Math.min(mHeight, mHeightSize);
                }
            }
        }

        setMeasuredDimension(mWidth, mHeight);
    }

    private float getHeightVideoRatio() {
        if (proportion == PROPORTION_4_3) {
            return 3 / 4f;
        } else if (proportion == PROPORTION_18_9) {
            return 9 / 18f;
        } else if (proportion == PROPORTION_2_39) {
            return 1 / 2.39f;
        } else {
            return 9 / 16f;
        }
    }

    private float getWidthVideoRatio() {
        if (proportion == PROPORTION_4_3) {
            return 4 / 3f;
        } else if (proportion == PROPORTION_18_9) {
            return 18 / 9f;
        } else if (proportion == PROPORTION_2_39) {
            return 2.39f;
        } else {
            return 16 / 9f;
        }
    }

设置SurfaceView大小

    mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder surfaceHolder) {
                setSurfaceViewSize();
            }

            @Override
            public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
                if (mMediaPlayer != null) {
                    mMediaPlayer.setDisplay(surfaceHolder);
                }
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder surfaceHolder) {

            }
        });

    
    private void setSurfaceViewSize() {
        if (isOpenProportion) {
            float proportion1 = getHeightVideoRatio();
            float proportion2 = mHeight * 1f / mWidth;
            ViewGroup.LayoutParams layoutParam = mSurfaceView.getLayoutParams();
            if (proportion1 > proportion2) {
                layoutParam.width = (int) (mHeight * getWidthVideoRatio() + .5);
                layoutParam.height = mHeight;
            } else {
                layoutParam.width = mWidth;
                layoutParam.height = (int) (mWidth * proportion1 + .5);
            }
            mSurfaceView.setLayoutParams(layoutParam);
        }
    }

到此视频播放基类基本完工

2.视频播放类
public class VideoPlayer extends BaseVideoPlayer {
    
    public VideoPlayer(@NonNull Context context) {
        this(context, null);
    }

    public VideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setOpenProportion();
    }

    @Override
    public IMediaPlayer createPlayer() {
        IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();

        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 0);
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48);
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 1);
        ijkMediaPlayer.setOption(1, "probesize", 10240L);
        ijkMediaPlayer.setOption(1, "flush_packets", 1L);
        ijkMediaPlayer.setOption(4, "packet-buffering", 0L);
        ijkMediaPlayer.setOption(4, "framedrop", 1L);

        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fps", 25);

        //设置音频输出API
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 1);
        //指定视频帧图像格式
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV16);
        //预读缓存最小帧数
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "min-frames", 100);
        //某些视频在SeekTo的时候,会跳回到拖动前的位置,这是因为视频的关键帧的问题,通俗一点就是FFMPEG不兼容,视频压缩过于厉害,seek只支持关键帧,出现这个情况就是原始的视频文件中i 帧比较少
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "enable-accurate-seek", 1);
        // 清空DNS,有时因为在APP里面要播放多种类型的视频(如:MP4,直播,直播平台保存的视频,和其他http视频), 有时会造成因为DNS的问题而报10000问题
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_clear", 1);
        //播放重连次数
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "reconnect", 1);
        ijkMediaPlayer.setVolume(1.0f, 1.0f);

        setEnableMediaCodec(ijkMediaPlayer);
        //不息屏
        //ijkMediaPlayer.setScreenOnWhilePlaying(true);
        return ijkMediaPlayer;
    }
}

加载资源

        try {
            videoPlayer.setPathLoad("url");
        } catch (IOException e) {
            e.printStackTrace();
        }

异步播放

videoPlayer.setVideoListener(new BaseVideoPlayer.VideoListener() {
    @Override
    public void onPrepared(IMediaPlayer iMediaPlayer) {
        iMediaPlayer.start();
    }

    @Override
    public void onVideoSizeChanged(IMediaPlayer iMediaPlayer, int i, int i1, int i2, int i3) {

    }
});

播放完毕回调

videoPlayer.setVideoCompletionListener(iMediaPlayer -> {
    
});

设置视频比例

videoPlayer.setProportion(VideoPlayer.PROPORTION_16_9);

3.流播放器
public class StreamPlayer extends BaseVideoPlayer {
    
    public StreamPlayer(@NonNull Context context) {
        super(context);
    }

    public StreamPlayer(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public StreamPlayer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public IMediaPlayer createPlayer() {
        IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();

        // 如果是rtsp协议,可以优先用tcp(默认是用udp)
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_transport", "tcp");
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_flags", "prefer_tcp");

        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 0);
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0);
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48);
        ijkMediaPlayer.setOption(1, "analyzemaxduration", 100L);
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 1);
        ijkMediaPlayer.setOption(1, "probesize", 10240L);
        ijkMediaPlayer.setOption(1, "flush_packets", 1L);
        ijkMediaPlayer.setOption(4, "packet-buffering", 0L);
        ijkMediaPlayer.setOption(4, "framedrop", 1L);

        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fps", 25);

        //设置音频输出API
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 1);
        //指定视频帧图像格式,SDL_FCC_YV12消耗更低
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV16);
        //预读缓存最小帧数
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "min-frames", 100);
        //某些视频在SeekTo的时候,会跳回到拖动前的位置,这是因为视频的关键帧的问题,通俗一点就是FFMPEG不兼容,视频压缩过于厉害,seek只支持关键帧,出现这个情况就是原始的视频文件中i 帧比较少
        //ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "enable-accurate-seek", 1);
        // 清空DNS,有时因为在APP里面要播放多种类型的视频(如:MP4,直播,直播平台保存的视频,和其他http视频), 有时会造成因为DNS的问题而报10000问题
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_clear", 1);
        //播放重连次数
        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "reconnect", 1);
        //设置没有声音
        ijkMediaPlayer.setVolume(0f, 0f);

        setEnableMediaCodec(ijkMediaPlayer);
        //不息屏
        //ijkMediaPlayer.setScreenOnWhilePlaying(true);
        return ijkMediaPlayer;
    }
}

4.全屏

在清单文件中配置

android:configChanges="orientation|keyboardHidden|screenSize"

在onConfigurationChanged中重新初始化

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        init();
    }
    
    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        init();
    }
}

全屏切换

private void init() {
    setContentView(R.layout.activity_main);

    videoPlayer = findViewById(R.id.videoPlayer);
    videoPlayer.setVideoListener(new BaseVideoPlayer.VideoListener() {
        @Override
        public void onPrepared(IMediaPlayer iMediaPlayer) {
            //跳转到记录位置
            iMediaPlayer.seekTo(currentPosition);
            iMediaPlayer.start();
        }

        @Override
        public void onVideoSizeChanged(IMediaPlayer iMediaPlayer, int i, int i1, int i2, int i3) {

        }
    });

    videoPlayer.setPath("http://vfx.mtime.cn/Video/2019/03/19/mp4/190319212559089721.mp4");
    try {
        videoPlayer.load();
    } catch (IOException e) {
        e.printStackTrace();
    }

    findViewById(R.id.button).setOnClickListener(v -> {
        videoPlayer.pause();
        //获取当前进度
        currentPosition = videoPlayer.getCurrentPosition();
        //判断当前屏幕方向
        if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
            //切换竖屏
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        } else {
            //切换横屏
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        }
    });
}

建立横竖屏布局

竖屏布局




    

    

    

横屏布局




    

    

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/959616.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号