写在前面

PS没错,这就是那篇躺在草稿箱里好几个月的僵尸博客,直到现在(2017年1月中旬)才打算写完,简单总结一下知识点,以备不时之需。

现在的项目是一个电影预告的APP,必然得有个视频播放器,之前是用VideoView写的,并且所有功能写在一个Activity中,都没有针对播放器单独做一下封装,代码有一千两百来行,晕,代码的格式,变量的命名惨不忍睹,所以后期的功能添加和改动可以用大工程三个字来形容,并且老板也对这个播放器提过很多意见,以上各种原因,打算彻底抛弃这个播放器,重新规划。百度了好久,最后在Github上发现了一个视屏播放器(https://github.com/lipangit/JieCaoVideoPlayer),界面也挺不错,但是最终发现不太适用于现在的项目,看了下源码,觉得貌似不难,可以自己写一个,要是以后加功能或改需求,我也好有个心里准备!

Begin

开始的时候基于MediaPlayer来写,写好之后才发现,MediaPlayer真的是从入门到放弃,监听接口的调用非常诡异,比如在视频刚开始播时MediaPlayer.OnErrorListener居然被调用等等,不过这些问题可以写一堆的Boolean变量来规避,最让人受不了的就是当播放器从后台返回当前页面时的一些列问题:奔溃,画面空白,长时间的重新缓冲......,最后发现,最终的实现效果很不好,并且自己已经被这一堆的Boolean变量搞晕了!后来才知道有IJKPlayer这么个东西,接口相比于MediaPlayer就多个一个字母i,接口调用很规律,前后台的切换也无前面那些问题,完美!
先实现一堆的接口:
  1. IMediaPlayer.OnInfoListener 当前视屏播放状态,如正在开始缓存,或缓冲结束开始播放
  2. IMediaPlayer.OnPreparedListener MediaPlayer的初始化,可以做一些初始化操作
  3. IMediaPlayer.OnCompletionListener ,IMediaPlayer.OnErrorListener 看名字都知道是干毛用的
  4. IMediaPlayer.OnBufferingUpdateListener 网络视屏的缓冲进度监听
  5. SurfaceHolder.Callback  用于监听SurfaceView的状态
  6. 再实现一堆的用于更新界面的接口:如OnClickListener, OnTouchListener, SeekBar.OnSeekBarChangeListener OnAudioFocusChangeListener.....
接着就是播放器的初始化,使用SurfaceView播放视频
SurfaceHolder holder= mSurface.getHolder();
holder.addCallback(this);
IjkMediaPlayer player = new IjkMediaPlayer();
player.reset();
try {//设置视频urlmPlayer.setDataSource(getContext(), Uri.parse(url));
} catch (IOException e) {e.printStackTrace();
}@Override
public void surfaceCreated(SurfaceHolder holder) {//指定MediaPlayer在当前的Surface中进行播放mPlayer.setDisplay(mHolder);
}

处理音频相关的操作:

mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
audioFocusListener = new AudioManager.OnAudioFocusChangeListener() {@Overridepublic void onAudioFocusChange(int focusChange) {switch (focusChange) {case AudioManager.AUDIOFOCUS_GAIN:break;case AudioManager.AUDIOFOCUS_LOSS:// 长久的失去音频焦点,释放MediaPlayer//stop();break;case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:// 暂时失去音频焦点,暂停播放等待重新获得音频焦点pause();break;case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:break;}}};
//申请音频焦点
mAudioManager.requestAudioFocus(audioFocusListener,AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);

至于视屏的填充样式,参考Windows的背景做了四种,分别为:适应,填充,拉伸,居中。如果不做任何处理就是拉伸效果,项目中默认使用适应效果,其实这个项目根本用不到填充和居中,我就是写着玩的。

    switch (mode) {case FILL_MODE_ADAPT://适应if ((float) vWidth / vHeight > (float) width / height) {//视屏的高不足以填充屏幕,宽度填充,计算合适的高度params.width = width;params.height = width * vHeight / vWidth;} else {//视屏的宽不足以填充屏幕,高度填充,计算合适的宽度params.width = height * vWidth / vHeight;params.height = height;}break;case FILL_MODE_FILL://填充if ((float) vWidth / vHeight > (float) width / height) {//视屏的高不足以填充屏幕,宽度填充,舍弃部分宽度,高度填充params.width = height * vWidth / vHeight;params.height = height;} else {//视屏的宽不足以填充屏幕,高度填充,舍弃部分高度,宽度度填充params.width = width;params.height = width * vHeight / vWidth;}break;case FILL_MODE_STRETCH://拉伸//不做任何处理就是拉伸break;case FILL_MODE_CENTER://居中params.width = vWidth;params.height = vHeight;break;}

视频的进度调节和音量调节,使用Touch时间去处理,当Touch事件为MotionEvent.ACTION_UP时,使用IjkMediaPlayer.seekTo(long var1)方法定位视频的播放位置,参数为要定为的视频时间点,而视频的总时间可以通过IjkMediaPlayer.getDuration()方法获取,下面是手势处理中的视频进度调节Dialog

/*** 手势视屏进度条** @param dx 当前手指所在的点相对于初始点在X轴上划过的距离*/private void showVideoProgressDialog(float dx) {if (mVideoProgressDialog == null) {View view = LayoutInflater.from(getContext()).inflate(R.layout.player_video_progress, this, false);mProgressProgress = (ProgressBar) view.findViewById(R.id.player_progress_progress);mProgressTips = (ImageView) view.findViewById(R.id.player_progress_tips);mProgressTime = (TextView) view.findViewById(R.id.player_progress_time);mVideoProgressDialog = new Dialog(getContext(), R.style.style_dialog_progress);mVideoProgressDialog.setContentView(view);mVideoProgressDialog.getWindow().setLayout(dip2px(190), dip2px(100));}if (!mVideoProgressDialog.isShowing()) {//初始化进度框的大小及位置WindowManager.LayoutParams localLayoutParams = mVideoProgressDialog.getWindow().getAttributes();localLayoutParams.gravity = (Gravity.CENTER_HORIZONTAL | Gravity.TOP);localLayoutParams.y = (getHeight() - dip2px(100)) / 2;mVideoProgressDialog.getWindow().setAttributes(localLayoutParams);mVideoProgressDialog.show();tempProgress = currentProgress;tempVideoPosition = mPlayer.getCurrentPosition();}//根据屏宽与手指相对于初始点在X轴上划过的距离计算进度条该显示的百分比//  slideProgress 的值也就是手势灵敏度float slideProgress = (dx - minSideDistance) * 1.0f / getSWidth();int progress = (int) (tempProgress + slideProgress * 100);mProgressProgress.setProgress(progress);//进度时间mProgressTime.setText(String.format("%s/%s",stringForTime((int) ((mPlayer.getDuration() * slideProgress) + tempVideoPosition)),stringForTime((int) mPlayer.getDuration())));if (Math.abs(dx - lastX) > minSideDistance) {if (dx > oldDx) {//右滑mProgressTips.setImageResource(R.mipmap.forward_icon);} else {//左滑mProgressTips.setImageResource(R.mipmap.backward_icon);}lastX = dx;}}

通过IMediaPlayer.OnInfoListener的接口可以获取当前视频的播放状态信息,这里我只用了以下几个,还有很多状态,感兴趣可以自己去看官方文档

    @Overridepublic boolean onInfo(IMediaPlayer iMediaPlayer, int what, int extra) {Log.d("xxx", "-----onInfo----    what:  " + what);switch (what) {case IMediaPlayer.MEDIA_INFO_BUFFERING_START://网络不好,视屏卡住了 701updatePlayMark(PLAYER_MARK_BUFFERING_START);//显示缓冲图标isBuffering = true;break;case IMediaPlayer.MEDIA_INFO_BUFFERING_END://网络良好,视屏开始播放了 702isBuffering = false;updatePlayMark(PLAYER_MARK_BUFFERING_END);//隐藏缓冲图标break;case IMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START://每准备一次调用一次 1002mSurface.setBackgroundColor(Color.TRANSPARENT);isStartPlay = true;isBuffering = false;updatePlayMark(PLAYER_MARK_FIRST_PLAY);//首次播放break;}return false;}

然后就可以播放了:

private void startPlay() {if (isStartPlay) {mPlayer.start();updatePlayMark(PLAYER_MARK_PLAY);} else {mPlayer.prepareAsync();//准备并开始播放,这里与MediaPlayer不同updatePlayMark(PLAYER_MARK_BUFFERING_START);isBuffering = true;if (!isPlayNext) {delayHideTopBottom();}}}

那么播放过程中的播放进度改怎么监听呢?没错就是用IMediaPlayer.OnBufferingUpdateListener,但这只是监听缓冲进度而已,而播放进度可以通过在视频准备播放时使用Handler和Runnable形成一个每隔固定时间段的循环来实现,在这个循环中通过IjkMediaplayer.getCurrentPosition()来计算相应的进度信息

    @Overridepublic void onPrepared(IMediaPlayer iMediaPlayer) {...mHandler.post(mRunnable);}
    //每隔0.5秒更新视屏界面信息,如进度条,当前播放时间点等等mRunnable = new Runnable() {@Overridepublic void run() {float position = mPlayer.getCurrentPosition();currentProgress = (int) ((position / mPlayer.getDuration()) * 100);mSeekBar.setProgress(currentProgress);mTipsProgress.setProgress(currentProgress);mCurrentTime.setText(stringForTime((int) position));mHandler.postDelayed(mRunnable, 500);}};

看看最终的实现效果:

至于上下信息栏的显示与隐藏,可以用属性动画来实现。当播放下一个视频时需要注意将IjkMediaPlayer重置,还有那一堆的界面UI和用于判断的Boolean变量。

    /*** 播放下一视屏*/public void playNext(String videoTitle, String videoUrl) {isStartPlay = false;isPlayNext = true;mPlayer.reset();mPlayer.setDisplay(mHolder);setVideoMsg(videoTitle, videoUrl);start();}

视频播放结束时,释放IjkMediaPlayer资源,释放音频焦点,移除Handler的循环。

    /*** 停止播放视屏,释放资源*/public void stop() {if (mPlayer.isPlaying()) {mPlayer.stop();}currentPlayerColum--;if (currentPlayerColum <= 0) {mPlayer.release();currentPlayerColum = 0;}mHandler.removeCallbacks(mRunnable);tbHandler.removeCallbacks(tbRunnable);//释放音频焦点mAudioManager.abandonAudioFocus(audioFocusListener);screenOrientationSwitcher.disable();}

那么最后如何处理屏幕的旋转呢,我们项目中使用的方法,在当前界面完成,旋转屏幕时通过监听屏幕的旋转,重新设置播放器界面的大小,并处理其他UI。其实从8月份到现在github上也有很多优秀的基于IjkPlayer的播放器开源,有的是采用另外一个activity中实现全屏播放。另外默认使用Gradle导入的IjkPlayer默认是不支持HTTPS的,恰巧我们项目中用的就是https,但幸运的是我去掉s视频也是可以播放的,如果需要支持https的话需要自己编译IjkPlayer了,或者你可以直接用github上的别人编译好的。还有一个问题就是当时在做的时候发现,如果项目含有.so文件时,在部分手机上会奔溃,比如说同事的华为和老板的一个锤子手机上,奔溃信息显示来自IjkPlayer的底层,好几个月过去当时截屏的奔溃信息也丢了......

End

通过写这个播放器也学到了不少东西,起码对视频播放有了个大致的了解。播放器代码总共1000行左右,整体来说难度不大,但是需要花费不少时间和精力去完善界面与逻辑。

基于IJKPlayer的简易视频播放器相关推荐

  1. 【基于QMediaPlayer的简易视频播放器】— 3、结合QSlider实现播放进度控制和音量控制

    基于QMediaPlayer的简易视频播放器 1.创建基本布局 2.QMediaPlayer的基本使用 3.结合QSlider实现播放进度控制和音量控制 4.重载QSlider鼠标响应事件,实现单击跳 ...

  2. Linux 基于ffplay的简易视频播放器(网络+本地)

    新手刚开始学习ffmpeg. 参考网上的ffmpeg资料和雷神的博客,简易做了个播放器,边学边做. 暂时未做音频,所以播放时有沙沙声. 视频的播放速度也有问题,需要再调整,后续再处理速度和音频的问题! ...

  3. C语言基于GTK+Libvlc实现的简易视频播放器(二)

    简易视频播放器-全屏播放 一.课程说明 上一次我们使用gtk+libvlc实现了一个最简单的视频播放器,可以实现点击按钮暂定和停止播放视频,以及同步显示视频播放进度,但即使作为一个视频播放器,只有这些 ...

  4. 基于 FFmpeg 的跨平台视频播放器简明教程(四):像素格式与格式转换

    系列文章目录 基于 FFmpeg 的跨平台视频播放器简明教程(一):FFMPEG + Conan 环境集成 基于 FFmpeg 的跨平台视频播放器简明教程(二):基础知识和解封装(demux) 基于 ...

  5. java视频播放器制作_java创建简易视频播放器

    java创建简易视频播放器 发布时间:2020-09-23 04:28:09 来源:脚本之家 阅读:98 作者:南柯一梦xihe 最近有个多媒体的作业,要求使用visualC++和OpenCV编写一个 ...

  6. 视频播放页php,html jquery简易视频播放器

    html jquery js 简易视频播放器 直接上代码:html> Document #durationbar{ width: 500px; height: 20px; } #duration ...

  7. 最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)

    ===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...

  8. 最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器

    ===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...

  9. 基于Django框架的视频播放器设计

    基于Django框架的视频播放器设计 前言 一.简介 二.详细实现步骤 1.路由配置 2.后台代码设计(对云盘接口的访问) 3.后台代码设计(流式视频传输) 4.前端功能设计(视频播放列表) 5.前端 ...

最新文章

  1. IE9 CTP发布了?改名 IE Platform Preview?
  2. 《LeetCode力扣练习》剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 Java
  3. Python Web学习笔记之TCP的3次握手与4次挥手过程
  4. Oracle宣布新的Java Champions
  5. 【ElasticSearch】IK分词加入标点符号
  6. Android P2P语音通话实现 【转】http://macleo.iteye.com/blog/1707455
  7. KSZ9897 switch 交换机
  8. Win10/11 自带输入法一键变五笔86/98/郑码/小鹤音形/表形码
  9. 最简单的直播礼物连刷特效制作(带源码)
  10. 计算机的云是什么意思_云计算是什么意思?为什么叫云计算?
  11. eclipes 快捷键操作:
  12. FastDFS学习笔记 -- day04 与Nginx整合
  13. Mybatis 之 二级缓存
  14. jquery根据name获取对象
  15. mysql的check约束怎么设置_MySQL怎么使用check约束
  16. IDEA插件项目克隆下来后,如何在IDEA中导入?
  17. Servlet模板修改
  18. 加密货币交易所需要怎样的透明度? |链捕手
  19. 临床预测模型评鉴(PMID: 32695060)
  20. csp2019 Emiya 家今天的饭

热门文章

  1. 本地镜像发布到阿里云
  2. 特斯拉员工猝死在生产线上 官方机构介入调查
  3. Cocos Creator微信小游戏添加banner广告
  4. word中间有一条横线怎么都删除不掉
  5. idea中使用Thymeleaf语法编写代码不提示解决方法
  6. 设计数据库的画图软件
  7. Android-RSA算法加密解密
  8. C# 注册DLL至GAC 并在添加引用中使用该DLL
  9. Android集成阿里云旺即时通讯踩坑历程
  10. 【计算机系统结构学习笔记】