Android ijkplayer的使用方法解析
ijkplayer是bilibili基于ffmpeg开发并开源的轻量级视频播放器,支持播放本地网络视频,也支持流媒体播放。支持android&ios。
ijkplayer的编译这里不多阐述,我也是直接获取别人编译完成的so库文件,直接使用的。如果你对ijkplayer的编译感兴趣,可以百度一下,有很多文章。
使用ijkplayer
导包
ijkplayer源码官方下载地址:https://github.com/bilibili/ijkplayer
上面是官方提供的ijkplayer的源码地址,但是它是没有编译过的。下面我给大家分享一份编译好的ijkplayer源码,由于比较大,分了三个包才上传完成,需要三个包都下载后才能一起解压:
我们下载完成,进入android/ijkplayer目录:
- ijkplayer-java:ijkplayer的一些操作封装及定义。这里面是通用的api接口,里面最主要的是imediaplayer,它是用来渲染显示多媒体的。
- ijkplayer-exo:google开源的一个新的播放器exoplayer,在demo中和ijkplayer对比用的。通过安装ijkplayer可以发现setting里面可以选择不同player来渲染多媒体显示,该模块下面就是一个mediaplayer。
- ijkplayer-example:测试程序
- ijkplayer-{arch}:编译出来的各个版本的.so文件。
官方提供的demo的代码还是挺多的,甚至还用了otto,需要对官方的demo进行精简,去除一些用不到的代码。
首先需要的是ijkplayer-{arch}、ijkplayer-java两个库。exo是google提供的新的播放器,这里不需要,直接砍掉。其次是ijkplayer-example里的,我们需要的只有tv.danmaku.ijk.media.example.widget.media包下的部分类。
注:
链接库ijkplayer-arm64,ijkplayer-armv5,ijkplayer-armv7a,ijkplayer-x86,ijkplayer-x86_64是不同体系架构的动态链接库,在当前工程结构里面作为一个模块,如果不想做兼容多平台问题,可以删除其他目录结构,单独保留自己需要的平台目录。
新建一个工程:
(1)把ijkplayer-armv7a/src/main/libs下的文件拷贝到新工程app目录的libs下。
(2)把ijkplayer-java/build/outputs/aar/ijkplayer-java-release.aar复制到新工程app目录的libs下。
(3)修改app下的build.gradle, 主要设置.so及.aar的位置:
apply plugin: 'com.android.application' android { compilesdkversion 24 buildtoolsversion "25.0.0" defaultconfig { applicationid "com.hx.ijkplayer_demo" minsdkversion 14 targetsdkversion 24 versioncode 1 versionname "1.0" testinstrumentationrunner "android.support.test.runner.androidjunitrunner" } buildtypes { release { minifyenabled false proguardfiles getdefaultproguardfile('proguard-android.txt'), 'proguard-rules.pro' } } sourcesets { main { jnilibs.srcdirs = ['libs'] /**在libs文件夹下找so文件*/ } } } repositories { mavencentral() flatdir { dirs 'libs' /**在libs文件夹下找aar文件*/ } } dependencies { compile filetree(dir: 'libs', include: ['*.jar']) androidtestcompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:24.2.1' testcompile 'junit:junit:4.12' compile(name: 'ijkplayer-java-release', ext: 'aar') /**编译ijkplayer-java-release.aar文件*/ }
(4)复制ijkplayer-example下面的tv.danmaku.ijk.media.example.widget.media到新的工程,删掉一些不需要类。
(5)ijkvideoview里面还是有很多如exo等没用的东西,删!具体可以参见我后面的demo。
(6)manifest
... <activity android:name=".mainactivity" android:screenorientation="sensorlandscape" android:configchanges="orientation|keyboardhidden"> ... </activity> ... <uses-permission android:name="android.permission.internet"/>
播放实现
xml代码
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".mainactivity"> <com.hx.ijkplayer_demo.widget.media.ijkvideoview android:id="@+id/video_view" android:layout_width="match_parent" android:layout_height="match_parent"/> </relativelayout>
java代码
public class mainactivity extends appcompatactivity { private ijkvideoview videoview; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); videoview = (ijkvideoview) findviewbyid(r.id.video_view); videoview.setaspectratio(irenderview.ar_aspect_fit_parent); videoview.setvideouri(uri.parse("http://zv.3gv.ifeng.com/live/zhongwen800k.m3u8")); videoview.start(); } }
常用函数
/** * 参数aspectratio(缩放参数)参见irenderview的常量:irenderview.ar_aspect_fit_parent, irenderview.ar_aspect_fill_parent, irenderview.ar_aspect_wrap_content, irenderview.ar_match_parent, irenderview.ar_16_9_fit_parent, irenderview.ar_4_3_fit_parent */ public void setaspectratio(int aspectratio); //改变视频缩放状态。 public int toggleaspectratio(); //设置视频路径。 public void setvideopath(string path); //设置视频uri。(可以是网络视频地址) public void setvideouri(uri uri); //停止视频播放,并释放资源。 public void stopplayback(); /** * 设置媒体控制器。 * 参数controller:媒体控制器,注意是com.hx.ijkplayer_demo.widget.media.imediacontroller。 */ public void setmediacontroller(imediacontroller controller); //改变媒体控制器显隐 private void togglemediacontrolsvisiblity(); //注册一个回调函数,在视频预处理完成后调用。在视频预处理完成后被调用。此时视频的宽度、高度、宽高比信息已经获取到,此时可调用seekto让视频从指定位置开始播放。 public void setonpreparedlistener(onpreparedlistener l); //播放完成回调 public void setoncompletionlistener(imediaplayer.oncompletionlistener l); //播放错误回调 public void setonerrorlistener(imediaplayer.onerrorlistener l); //事件发生回调 public void setoninfolistener(imediaplayer.oninfolistener l); //获取总长度 public int getduration(); //获取当前播放位置。 public long getcurrentposition(); //设置播放位置。单位毫秒 public void seekto(long msec); //是否正在播放。 public boolean isplaying(); //获取缓冲百分比。 public int getbufferpercentage();
封装ijkplayer
上面只是ijkplayer的一个基本用法。我们可以对ijkplayer进行一次封装,让ijkplayer使用起来更加简单。
功能:
- 使用vitamio的videoview进行视频播放
- 视频左侧界面(左1/2以内)上下滑动调节亮度
- 视频右侧界面(右1/2以外)上下滑动调节声音
- 双击切换视频窗口布局
- 非直播状态,可以左右滑动调节当前播放进度
public class playermanager { /** * 可能会剪裁,保持原视频的大小,显示在中心,当原视频的大小超过view的大小超过部分裁剪处理 */ public static final string scaletype_fitparent="fitparent"; /** * 可能会剪裁,等比例放大视频,直到填满view为止,超过view的部分作裁剪处理 */ public static final string scaletype_fillparent="fillparent"; /** * 将视频的内容完整居中显示,如果视频大于view,则按比例缩视频直到完全显示在view中 */ public static final string scaletype_wrapcontent="wrapcontent"; /** * 不剪裁,非等比例拉伸画面填满整个view */ public static final string scaletype_fitxy="fitxy"; /** * 不剪裁,非等比例拉伸画面到16:9,并完全显示在view中 */ public static final string scaletype_16_9="16:9"; /** * 不剪裁,非等比例拉伸画面到4:3,并完全显示在view中 */ public static final string scaletype_4_3="4:3"; /** * 状态常量 */ private final int status_error=-1; private final int status_idle=0; private final int status_loading=1; private final int status_playing=2; private final int status_pause=3; private final int status_completed=4; private final activity activity; private final ijkvideoview videoview; private final audiomanager audiomanager; public gesturedetector gesturedetector; private boolean playersupport; private boolean islive = false;//是否为直播 private boolean fullscreenonly; private boolean portrait; private final int mmaxvolume; private int screenwidthpixels; private int currentposition; private int status=status_idle; private long pausetime; private string url; private float brightness=-1; private int volume=-1; private long newposition = -1; private long defaultretrytime=5000; private orientationeventlistener orientationeventlistener; private playerstatelistener playerstatelistener; public void setplayerstatelistener(playerstatelistener playerstatelistener) { this.playerstatelistener = playerstatelistener; } private onerrorlistener onerrorlistener=new onerrorlistener() { @override public void onerror(int what, int extra) { } }; private oncompletelistener oncompletelistener=new oncompletelistener() { @override public void oncomplete() { } }; private oninfolistener oninfolistener=new oninfolistener(){ @override public void oninfo(int what, int extra) { } }; private oncontrolpanelvisibilitychangelistener oncontrolpanelvisibilitychangelistener=new oncontrolpanelvisibilitychangelistener() { @override public void change(boolean isshowing) { } }; /** * try to play when error(only for live video) * @param defaultretrytime millisecond,0 will stop retry,default is 5000 millisecond */ public void setdefaultretrytime(long defaultretrytime) { this.defaultretrytime = defaultretrytime; } public playermanager(final activity activity) { try { ijkmediaplayer.loadlibrariesonce(null); ijkmediaplayer.native_profilebegin("libijkplayer.so"); playersupport=true; } catch (throwable e) { log.e("giraffeplayer", "loadlibraries error", e); } this.activity=activity; screenwidthpixels = activity.getresources().getdisplaymetrics().widthpixels; videoview = (ijkvideoview) activity.findviewbyid(r.id.video_view); videoview.setoncompletionlistener(new imediaplayer.oncompletionlistener() { @override public void oncompletion(imediaplayer mp) { statuschange(status_completed); oncompletelistener.oncomplete(); } }); videoview.setonerrorlistener(new imediaplayer.onerrorlistener() { @override public boolean onerror(imediaplayer mp, int what, int extra) { statuschange(status_error); onerrorlistener.onerror(what,extra); return true; } }); videoview.setoninfolistener(new imediaplayer.oninfolistener() { @override public boolean oninfo(imediaplayer mp, int what, int extra) { switch (what) { case imediaplayer.media_info_buffering_start: statuschange(status_loading); break; case imediaplayer.media_info_buffering_end: statuschange(status_playing); break; case imediaplayer.media_info_network_bandwidth: //显示下载速度 // toast.show("download rate:" + extra); break; case imediaplayer.media_info_video_rendering_start: statuschange(status_playing); break; } oninfolistener.oninfo(what,extra); return false; } }); audiomanager = (audiomanager) activity.getsystemservice(context.audio_service); mmaxvolume = audiomanager.getstreammaxvolume(audiomanager.stream_music); gesturedetector = new gesturedetector(activity, new playergesturelistener()); if (fullscreenonly) { activity.setrequestedorientation(activityinfo.screen_orientation_landscape); } portrait=getscreenorientation()== activityinfo.screen_orientation_portrait; if (!playersupport) { debuglog.e("播放器不支持此设备"); } } private void statuschange(int newstatus) { status = newstatus; if (!islive && newstatus==status_completed) { debuglog.d("statuschange status_completed..."); if (playerstatelistener != null){ playerstatelistener.oncomplete(); } }else if (newstatus == status_error) { debuglog.d("statuschange status_error..."); if (playerstatelistener != null){ playerstatelistener.onerror(); } } else if(newstatus==status_loading){ // $.id(r.id.app_video_loading).visible(); if (playerstatelistener != null){ playerstatelistener.onloading(); } debuglog.d("statuschange status_loading..."); } else if (newstatus == status_playing) { debuglog.d("statuschange status_playing..."); if (playerstatelistener != null){ playerstatelistener.onplay(); } } } public void onpause() { pausetime= system.currenttimemillis(); if (status==status_playing) { videoview.pause(); if (!islive) { currentposition = videoview.getcurrentposition(); } } } public void onresume() { pausetime=0; if (status==status_playing) { if (islive) { videoview.seekto(0); } else { if (currentposition>0) { videoview.seekto(currentposition); } } videoview.start(); } } public void ondestroy() { orientationeventlistener.disable(); videoview.stopplayback(); } public void play(string url) { this.url = url; if (playersupport) { videoview.setvideopath(url); videoview.start(); } } private string generatetime(long time) { int totalseconds = (int) (time / 1000); int seconds = totalseconds % 60; int minutes = (totalseconds / 60) % 60; int hours = totalseconds / 3600; return hours > 0 ? string.format("%02d:%02d:%02d", hours, minutes, seconds) : string.format("%02d:%02d", minutes, seconds); } private int getscreenorientation() { int rotation = activity.getwindowmanager().getdefaultdisplay().getrotation(); displaymetrics dm = new displaymetrics(); activity.getwindowmanager().getdefaultdisplay().getmetrics(dm); int width = dm.widthpixels; int height = dm.heightpixels; int orientation; // if the device's natural orientation is portrait: if ((rotation == surface.rotation_0 || rotation == surface.rotation_180) && height > width || (rotation == surface.rotation_90 || rotation == surface.rotation_270) && width > height) { switch (rotation) { case surface.rotation_0: orientation = activityinfo.screen_orientation_portrait; break; case surface.rotation_90: orientation = activityinfo.screen_orientation_landscape; break; case surface.rotation_180: orientation = activityinfo.screen_orientation_reverse_portrait; break; case surface.rotation_270: orientation = activityinfo.screen_orientation_reverse_landscape; break; default: orientation = activityinfo.screen_orientation_portrait; break; } } // if the device's natural orientation is landscape or if the device // is square: else { switch (rotation) { case surface.rotation_0: orientation = activityinfo.screen_orientation_landscape; break; case surface.rotation_90: orientation = activityinfo.screen_orientation_portrait; break; case surface.rotation_180: orientation = activityinfo.screen_orientation_reverse_landscape; break; case surface.rotation_270: orientation = activityinfo.screen_orientation_reverse_portrait; break; default: orientation = activityinfo.screen_orientation_landscape; break; } } return orientation; } /** * 滑动改变声音大小 * * @param percent */ private void onvolumeslide(float percent) { if (volume == -1) { volume = audiomanager.getstreamvolume(audiomanager.stream_music); if (volume < 0) volume = 0; } int index = (int) (percent * mmaxvolume) + volume; if (index > mmaxvolume) { index = mmaxvolume; } else if (index < 0){ index = 0; } // 变更声音 audiomanager.setstreamvolume(audiomanager.stream_music, index, 0); // 变更进度条 int i = (int) (index * 1.0 / mmaxvolume * 100); string s = i + "%"; if (i == 0) { s = "off"; } debuglog.d("onvolumeslide:"+s); } private void onprogressslide(float percent) { long position = videoview.getcurrentposition(); long duration = videoview.getduration(); long deltamax = math.min(100 * 1000, duration - position); long delta = (long) (deltamax * percent); newposition = delta + position; if (newposition > duration) { newposition = duration; } else if (newposition <= 0) { newposition=0; delta=-position; } int showdelta = (int) delta / 1000; if (showdelta != 0) { string text = showdelta > 0 ? ("+" + showdelta) : "" + showdelta; debuglog.d("onprogressslide:" + text); } } /** * 滑动改变亮度 * * @param percent */ private void onbrightnessslide(float percent) { if (brightness < 0) { brightness = activity.getwindow().getattributes().screenbrightness; if (brightness <= 0.00f){ brightness = 0.50f; }else if (brightness < 0.01f){ brightness = 0.01f; } } debuglog.d("brightness:"+brightness+",percent:"+ percent); windowmanager.layoutparams lpa = activity.getwindow().getattributes(); lpa.screenbrightness = brightness + percent; if (lpa.screenbrightness > 1.0f){ lpa.screenbrightness = 1.0f; }else if (lpa.screenbrightness < 0.01f){ lpa.screenbrightness = 0.01f; } activity.getwindow().setattributes(lpa); } public void setfullscreenonly(boolean fullscreenonly) { this.fullscreenonly = fullscreenonly; tryfullscreen(fullscreenonly); if (fullscreenonly) { activity.setrequestedorientation(activityinfo.screen_orientation_landscape); } else { activity.setrequestedorientation(activityinfo.screen_orientation_sensor); } } private void tryfullscreen(boolean fullscreen) { if (activity instanceof appcompatactivity) { actionbar supportactionbar = ((appcompatactivity) activity).getsupportactionbar(); if (supportactionbar != null) { if (fullscreen) { supportactionbar.hide(); } else { supportactionbar.show(); } } } setfullscreen(fullscreen); } private void setfullscreen(boolean fullscreen) { if (activity != null) { windowmanager.layoutparams attrs = activity.getwindow().getattributes(); if (fullscreen) { attrs.flags |= windowmanager.layoutparams.flag_fullscreen; activity.getwindow().setattributes(attrs); activity.getwindow().addflags(windowmanager.layoutparams.flag_layout_no_limits); } else { attrs.flags &= (~windowmanager.layoutparams.flag_fullscreen); activity.getwindow().setattributes(attrs); activity.getwindow().clearflags(windowmanager.layoutparams.flag_layout_no_limits); } } } /** * <pre> * fitparent:可能会剪裁,保持原视频的大小,显示在中心,当原视频的大小超过view的大小超过部分裁剪处理 * fillparent:可能会剪裁,等比例放大视频,直到填满view为止,超过view的部分作裁剪处理 * wrapcontent:将视频的内容完整居中显示,如果视频大于view,则按比例缩视频直到完全显示在view中 * fitxy:不剪裁,非等比例拉伸画面填满整个view * 16:9:不剪裁,非等比例拉伸画面到16:9,并完全显示在view中 * 4:3:不剪裁,非等比例拉伸画面到4:3,并完全显示在view中 * </pre> * @param scaletype */ public void setscaletype(string scaletype) { if (scaletype_fitparent.equals(scaletype)) { videoview.setaspectratio(irenderview.ar_aspect_fit_parent); }else if (scaletype_fillparent.equals(scaletype)) { videoview.setaspectratio(irenderview.ar_aspect_fill_parent); }else if (scaletype_wrapcontent.equals(scaletype)) { videoview.setaspectratio(irenderview.ar_aspect_wrap_content); }else if (scaletype_fitxy.equals(scaletype)) { videoview.setaspectratio(irenderview.ar_match_parent); }else if (scaletype_16_9.equals(scaletype)) { videoview.setaspectratio(irenderview.ar_16_9_fit_parent); }else if (scaletype_4_3.equals(scaletype)) { videoview.setaspectratio(irenderview.ar_4_3_fit_parent); } } public void start() { videoview.start(); } public void pause() { videoview.pause(); } public boolean onbackpressed() { if (!fullscreenonly && getscreenorientation() == activityinfo.screen_orientation_landscape) { activity.setrequestedorientation(activityinfo.screen_orientation_portrait); return true; } return false; } class query { private final activity activity; private view view; public query(activity activity) { this.activity=activity; } public query id(int id) { view = activity.findviewbyid(id); return this; } public query image(int resid) { if (view instanceof imageview) { ((imageview) view).setimageresource(resid); } return this; } public query visible() { if (view != null) { view.setvisibility(view.visible); } return this; } public query gone() { if (view != null) { view.setvisibility(view.gone); } return this; } public query invisible() { if (view != null) { view.setvisibility(view.invisible); } return this; } public query clicked(view.onclicklistener handler) { if (view != null) { view.setonclicklistener(handler); } return this; } public query text(charsequence text) { if (view!=null && view instanceof textview) { ((textview) view).settext(text); } return this; } public query visibility(int visible) { if (view != null) { view.setvisibility(visible); } return this; } private void size(boolean width, int n, boolean dip){ if(view != null){ viewgroup.layoutparams lp = view.getlayoutparams(); if(n > 0 && dip){ n = dip2pixel(activity, n); } if(width){ lp.width = n; }else{ lp.height = n; } view.setlayoutparams(lp); } } public void height(int height, boolean dip) { size(false,height,dip); } public int dip2pixel(context context, float n){ int value = (int) typedvalue.applydimension(typedvalue.complex_unit_dip, n, context.getresources().getdisplaymetrics()); return value; } public float pixel2dip(context context, float n){ resources resources = context.getresources(); displaymetrics metrics = resources.getdisplaymetrics(); float dp = n / (metrics.densitydpi / 160f); return dp; } } public class playergesturelistener extends gesturedetector.simpleongesturelistener { private boolean firsttouch; private boolean volumecontrol; private boolean toseek; /** * 双击 */ @override public boolean ondoubletap(motionevent e) { videoview.toggleaspectratio(); return true; } @override public boolean ondown(motionevent e) { firsttouch = true; return super.ondown(e); } /** * 滑动 */ @override public boolean onscroll(motionevent e1, motionevent e2, float distancex, float distancey) { float moldx = e1.getx(), moldy = e1.gety(); float deltay = moldy - e2.gety(); float deltax = moldx - e2.getx(); if (firsttouch) { toseek = math.abs(distancex) >= math.abs(distancey); volumecontrol=moldx > screenwidthpixels * 0.5f; firsttouch = false; } if (toseek) { if (!islive) { onprogressslide(-deltax / videoview.getwidth()); } } else { float percent = deltay / videoview.getheight(); if (volumecontrol) { onvolumeslide(percent); } else { onbrightnessslide(percent); } } return super.onscroll(e1, e2, distancex, distancey); } @override public boolean onsingletapup(motionevent e) { return true; } } /** * is player support this device * @return */ public boolean isplayersupport() { return playersupport; } /** * 是否正在播放 * @return */ public boolean isplaying() { return videoview!=null?videoview.isplaying():false; } public void stop(){ videoview.stopplayback(); } public int getcurrentposition(){ return videoview.getcurrentposition(); } /** * get video duration * @return */ public int getduration(){ return videoview.getduration(); } public playermanager playinfullscreen(boolean fullscreen){ if (fullscreen) { activity.setrequestedorientation(activityinfo.screen_orientation_landscape); } return this; } public playermanager onerror(onerrorlistener onerrorlistener) { this.onerrorlistener = onerrorlistener; return this; } public playermanager oncomplete(oncompletelistener oncompletelistener) { this.oncompletelistener = oncompletelistener; return this; } public playermanager oninfo(oninfolistener oninfolistener) { this.oninfolistener = oninfolistener; return this; } public playermanager oncontrolpanelvisibilitychange(oncontrolpanelvisibilitychangelistener listener){ this.oncontrolpanelvisibilitychangelistener = listener; return this; } /** * set is live (can't seek forward) * @param islive * @return */ public playermanager live(boolean islive) { this.islive = islive; return this; } public playermanager toggleaspectratio(){ if (videoview != null) { videoview.toggleaspectratio(); } return this; } public interface playerstatelistener{ void oncomplete(); void onerror(); void onloading(); void onplay(); } public interface onerrorlistener{ void onerror(int what, int extra); } public interface oncompletelistener{ void oncomplete(); } public interface oncontrolpanelvisibilitychangelistener{ void change(boolean isshowing); } public interface oninfolistener{ void oninfo(int what, int extra); } }
使用封装后的playermanager播放视频:
public class mainactivity extends appcompatactivity implements playermanager.playerstatelistener{ private string url1 = "rtmp://203.207.99.19:1935/live/cctv5"; private string url2 = "http://zv.3gv.ifeng.com/live/zhongwen800k.m3u8"; private string url3 = "rtsp://184.72.239.149/vod/mp4:bigbuckbunny_115k.mov"; private string url4 = "http://42.96.249.166/live/24035.m3u8"; private playermanager player; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); initplayer(); } private void initplayer() { player = new playermanager(this); player.setfullscreenonly(true); player.setscaletype(playermanager.scaletype_fillparent); player.playinfullscreen(true); player.setplayerstatelistener(this); player.play(url1); } @override public boolean ontouchevent(motionevent event) { if (player.gesturedetector.ontouchevent(event)) return true; return super.ontouchevent(event); } @override public void oncomplete() { } @override public void onerror() { } @override public void onloading() { } @override public void onplay() { } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。