基于IJKPlayer的简易视频播放器
写在前面
PS:没错,这就是那篇躺在草稿箱里好几个月的僵尸博客,直到现在(2017年1月中旬)才打算写完,简单总结一下知识点,以备不时之需。
现在的项目是一个电影预告的APP,必然得有个视频播放器,之前是用VideoView写的,并且所有功能写在一个Activity中,都没有针对播放器单独做一下封装,代码有一千两百来行,晕,代码的格式,变量的命名惨不忍睹,所以后期的功能添加和改动可以用大工程三个字来形容,并且老板也对这个播放器提过很多意见,以上各种原因,打算彻底抛弃这个播放器,重新规划。百度了好久,最后在Github上发现了一个视屏播放器(https://github.com/lipangit/JieCaoVideoPlayer),界面也挺不错,但是最终发现不太适用于现在的项目,看了下源码,觉得貌似不难,可以自己写一个,要是以后加功能或改需求,我也好有个心里准备!
Begin
开始的时候基于MediaPlayer来写,写好之后才发现,MediaPlayer真的是从入门到放弃,监听接口的调用非常诡异,比如在视频刚开始播时MediaPlayer.OnErrorListener居然被调用等等,不过这些问题可以写一堆的Boolean变量来规避,最让人受不了的就是当播放器从后台返回当前页面时的一些列问题:奔溃,画面空白,长时间的重新缓冲......,最后发现,最终的实现效果很不好,并且自己已经被这一堆的Boolean变量搞晕了!后来才知道有IJKPlayer这么个东西,接口相比于MediaPlayer就多个一个字母i,接口调用很规律,前后台的切换也无前面那些问题,完美!先实现一堆的接口:
- IMediaPlayer.OnInfoListener 当前视屏播放状态,如正在开始缓存,或缓冲结束开始播放
- IMediaPlayer.OnPreparedListener MediaPlayer的初始化,可以做一些初始化操作
- IMediaPlayer.OnCompletionListener ,IMediaPlayer.OnErrorListener 看名字都知道是干毛用的
- IMediaPlayer.OnBufferingUpdateListener 网络视屏的缓冲进度监听
- SurfaceHolder.Callback 用于监听SurfaceView的状态
- 再实现一堆的用于更新界面的接口:如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的简易视频播放器相关推荐
- 【基于QMediaPlayer的简易视频播放器】— 3、结合QSlider实现播放进度控制和音量控制
基于QMediaPlayer的简易视频播放器 1.创建基本布局 2.QMediaPlayer的基本使用 3.结合QSlider实现播放进度控制和音量控制 4.重载QSlider鼠标响应事件,实现单击跳 ...
- Linux 基于ffplay的简易视频播放器(网络+本地)
新手刚开始学习ffmpeg. 参考网上的ffmpeg资料和雷神的博客,简易做了个播放器,边学边做. 暂时未做音频,所以播放时有沙沙声. 视频的播放速度也有问题,需要再调整,后续再处理速度和音频的问题! ...
- C语言基于GTK+Libvlc实现的简易视频播放器(二)
简易视频播放器-全屏播放 一.课程说明 上一次我们使用gtk+libvlc实现了一个最简单的视频播放器,可以实现点击按钮暂定和停止播放视频,以及同步显示视频播放进度,但即使作为一个视频播放器,只有这些 ...
- 基于 FFmpeg 的跨平台视频播放器简明教程(四):像素格式与格式转换
系列文章目录 基于 FFmpeg 的跨平台视频播放器简明教程(一):FFMPEG + Conan 环境集成 基于 FFmpeg 的跨平台视频播放器简明教程(二):基础知识和解封装(demux) 基于 ...
- java视频播放器制作_java创建简易视频播放器
java创建简易视频播放器 发布时间:2020-09-23 04:28:09 来源:脚本之家 阅读:98 作者:南柯一梦xihe 最近有个多媒体的作业,要求使用visualC++和OpenCV编写一个 ...
- 视频播放页php,html jquery简易视频播放器
html jquery js 简易视频播放器 直接上代码:html> Document #durationbar{ width: 500px; height: 20px; } #duration ...
- 最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)
===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...
- 最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器
===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...
- 基于Django框架的视频播放器设计
基于Django框架的视频播放器设计 前言 一.简介 二.详细实现步骤 1.路由配置 2.后台代码设计(对云盘接口的访问) 3.后台代码设计(流式视频传输) 4.前端功能设计(视频播放列表) 5.前端 ...
最新文章
- IE9 CTP发布了?改名 IE Platform Preview?
- 《LeetCode力扣练习》剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 Java
- Python Web学习笔记之TCP的3次握手与4次挥手过程
- Oracle宣布新的Java Champions
- 【ElasticSearch】IK分词加入标点符号
- Android P2P语音通话实现 【转】http://macleo.iteye.com/blog/1707455
- KSZ9897 switch 交换机
- Win10/11 自带输入法一键变五笔86/98/郑码/小鹤音形/表形码
- 最简单的直播礼物连刷特效制作(带源码)
- 计算机的云是什么意思_云计算是什么意思?为什么叫云计算?
- eclipes 快捷键操作:
- FastDFS学习笔记 -- day04 与Nginx整合
- Mybatis 之 二级缓存
- jquery根据name获取对象
- mysql的check约束怎么设置_MySQL怎么使用check约束
- IDEA插件项目克隆下来后,如何在IDEA中导入?
- Servlet模板修改
- 加密货币交易所需要怎样的透明度? |链捕手
- 临床预测模型评鉴(PMID: 32695060)
- csp2019 Emiya 家今天的饭