一:使用IjkMediaPlayer+SurfaceView的播放方式,和截图方式
需要到官网拉取自己编译的so库
拉取安卓代码 git clone https://github.com/Bilibili/ijkplayer.git ijkplayer-android
编译步骤百度搜索一下吧,都一样,可能会有些环境配置的问题,ndk推荐使用r14的版本;太低编译不过去,太高java版本不支持;
自己封装一个播放器的话,直接继承Surface ,实现需要继承的方法,
1:初始化
在构造函数中初始化,设置播放画面到Surface
public BlibiliVideoPlayer(Context context) { super(context); init(); }
private void init() { IjkMediaPlayer.loadLibrariesonce(null); IjkMediaPlayer.native_profileBegin("libijkplayer.so"); ijkMediaPlayer=new IjkMediaPlayer(); //开启日志 // ijkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG); getHolder().addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { ijkMediaPlayer.setDisplay(holder); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { ijkMediaPlayer.setDisplay(holder); // ijkMediaPlayer.prepareAsync(); // loadVideo(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { if(ijkMediaPlayer!=null){ ijkMediaPlayer.setDisplay(null); } } }); ijkMediaPlayer.setonSeekCompleteListener(new IMediaPlayer.onSeekCompleteListener() { @Override public void onSeekComplete(IMediaPlayer iMediaPlayer) { inSeek = false; } }); ijkMediaPlayer.setonCompletionListener(new IMediaPlayer.onCompletionListener() { @Override public void onCompletion(IMediaPlayer iMediaPlayer) { inSeek = false; //关闭定时器 stopProgressUpdateTimer(); try{ if(null != listener) { listener.completed(); } }catch (Exception e){ } } }); ijkMediaPlayer.setonInfoListener(new IMediaPlayer.onInfoListener() { @Override public boolean onInfo(IMediaPlayer iMediaPlayer, int i, int i1) { if(null != listener) { listener.info(VideoPlayerConstants.ALI,i,0); } return false; } }); ijkMediaPlayer.setonPreparedListener(new IMediaPlayer.onPreparedListener() { @Override public void onPrepared(IMediaPlayer iMediaPlayer) { prepared = true; if(null != listener){ listener.prepared(); } } }); // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_clear", 1); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "http-detect-range-support", 1); // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_timeout", -1); // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 0); // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32); // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1); // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 0); // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0); // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0); // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT,"safe",0); // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "protocol_whitelist", "hls,async,cache,crypto,file,http,https,ijkhttphook,ijkinject,ijklivehook,ijklongurl,ijksegment,ijktcphook,pipe,rtp,tcp,tls,udp,ijkurlhook,data,concat,subfile,udp,ffconcat"); // // ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1); // ijkMediaPlayer. // ijkMediaPlayer.setonVideoDecoderErrorListener(new IMediaPlayer.onVideoDecoderErrorListener() { // @Override // public void onVideoDecoderError(IMediaPlayer iMediaPlayer) { // Log.d("log","解码错误"); // } // }); }
private void loadVideo() { ijkMediaPlayer.prepareAsync(); ijkMediaPlayer.setDisplay(getHolder()); ijkMediaPlayer.start(); // startProgressUpdateTimer(); }
添加请求头,防盗链,refer的方式:
Mapheaders = new HashMap<>(); headers.put("Referer","内容");// if (headers != null) { try{ ijkMediaPlayer.setDataSource(url,headers); }catch (Exception e){ } }else{ try{ ijkMediaPlayer.setDataSource(url); }catch (Exception e){ } }//有header和没header的播放方式
到这里就可以正常播放啦。
2截图实现:需要改到ijkplayer-android/ijkmedia/ijkplayer下的ff_ffplay.c ijkplayer.c ff_ffplay-def.h文件,Android.mk以及ijkplayer-android/ijkmedia/ijkplayer/android/的ijkplayer_jni.c
jni添加:ijkplayer_jni.c
#include //需要引入这个包 static jboolean IjkMediaPlayer_getCurrentframe(JNIEnv *env, jobject thiz, jobject bitmap) { jboolean retval = JNI_TRUE; IjkMediaPlayer *mp = jni_get_media_player(env, thiz); JNI_CHECK_GOTO(mp, env, NULL, "mpjni: getCurrentframe: null mp", LABEL_RETURN); uint8_t *frame_buffer = NULL; if (0 > AndroidBitmap_lockPixels(env, bitmap, (void **)&frame_buffer)) { (*env)->ThrowNew(env, "java/io/IOException", "Unable to lock pixels."); return JNI_FALSE; } ijkmp_get_current_frame(mp, frame_buffer); if (0 > AndroidBitmap_unlockPixels(env, bitmap)) { (*env)->ThrowNew(env, "java/io/IOException", "Unable to unlock pixels."); return JNI_FALSE; }/Users/hoge/ijkplayer-android/ijkmedia/ijkplayer/android/ijkplayer_jni.c LABEL_RETURN: ijkmp_dec_ref_p(&mp); return retval; } JNINativeMethod中添加 { "getCurrentframe", "(Landroid/graphics/Bitmap;)Z", (void *) IjkMediaPlayer_getCurrentframe },//添加这条 { "_setOption", "(ILjava/lang/String;Ljava/lang/String;)V", (void *) IjkMediaPlayer_setOption },//这条已经有了
Android.mk:
include $(CLEAR_VARS) # -mfloat-abi=soft is a workaround for FP register corruption on Exynos 4210 # http://www.spinics.net/lists/arm-kernel/msg368417.html ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) LOCAL_CFLAGS += -mfloat-abi=soft endif LOCAL_CFLAGS += -std=c99 LOCAL_LDLIBS += -llog -landroid -ljnigraphics//添加这个-ljnigraphics
ijkplayer.c:
static void ijkmp_get_current_frame_l(IjkMediaPlayer *mp, uint8_t *frame_buf) { ffp_get_current_frame_l(mp->ffplayer, frame_buf); } void ijkmp_get_current_frame(IjkMediaPlayer *mp, uint8_t *frame_buf) { assert(mp); pthread_mutex_lock(&mp->mutex); ijkmp_get_current_frame_l(mp, frame_buf); pthread_mutex_unlock(&mp->mutex); }
ff_ffplay.c
void ffp_get_current_frame_l(FFPlayer *ffp, uint8_t *frame_buf) { ALOGD("=============>start snapshotn"); VideoState *is = ffp->is; frame *vp; int i = 0, linesize = 0, pixels = 0; uint8_t *src; vp = &is->pictq.queue[is->pictq.rindex]; int height = vp->bmp->h; int width = vp->bmp->w; ALOGD("=============>%d X %d === %dn", width, height, vp->bmp->pitches[0]); // copy data to bitmap in java code linesize = vp->bmp->pitches[0]; src = vp->bmp->pixels[0]; pixels = width * 4; for (i = 0; i < height; i++) { memcpy(frame_buf + i * pixels, src + i * linesize, pixels); } ALOGD("=============>end snapshotn"); }
ff_ffplay-def.h:
typedef struct FFPlayer { //在这个方法里添加这些变量,就放在最开头吧 AVFormatContext *m_ofmt_ctx; AVOutputFormat *m_ofmt; pthread_mutex_t record_mutex; char *screen_file_name; int is_record; int record_error; int is_first; int is_screenshot;//是否截图,是一次性的,一次只能截一张图 int64_t start_pts; int64_t start_dts; }
修改完毕开始编译,在jkplayer-android/android/contrib目录下执行
./compile-ffmpeg.sh clean
./compile-ffmpeg.sh armv7a //armv7a可以改成想要的,活着all编译全部
然后cd .. 到上一个目录 jkplayer-android/android下执行
./compile-ijk.sh armv7a//生成so文件
生成的so在对应的文件目录下,比如这边编译的armv7a就在ijkplayer-android/android/ijkplayer/ijkplayer-armv7a/src/main/libs/armeabi-v7a目录下
tips:
如果有编译过一次了,再次修改上面的这些c文件就可以不用再执行上面的
./compile-ffmpeg.sh clean,可以大大加快编译速度
二:java 添加截图方法,
把生成的so文件放到项目中,修改IMediaPlayer.java:
添加
boolean getCurrentframe(Bitmap bitmap);方法
因为有好多个文件继承这个IMediaPlayer.java,添加这个后需要到继承他的子类中实现这个getCurrentframe,比如IjkMediaPlayer.java中添加nativa方法
@Override public native boolean getCurrentframe(Bitmap bitmap);
有好几个文件哦,记得都添加上;
截图实现:
if(ijkMediaPlayer!=null){ int width = getVideoWidth();//想要的宽度 int height = getVideoHeight();//想要的高度 Bitmap srcBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);//建议4444比较小,8888报错过一次 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddhhmmss"); String picName = dateFormat.format(new java.util.Date()); //按照日期生成图片文件 //Util.getScreenShotPath() 是路径,一般放data/包名/目录下. //这个路径不存在会报错,没读写权限也会报错,怎么申请权限自己百度 String mPath2 = Util.getScreenShotPath() + picName + ".png"; boolean flag = ijkMediaPlayer.getCurrentframe(srcBitmap); if (flag) { File imageFile = new File(mPath2); try { if(imageFile!=null){ //保存图片 OutputStream fout = new FileOutputStream(imageFile); srcBitmap.compress(Bitmap.CompressFormat.PNG, 100, fout); fout.flush(); fout.close(); } } catch (Exception e) { e.printStackTrace(); callback.onFaild(); } } }
三:问题记录
1,播放https的m3u8流失败,请求内部的ts文件失败,没有解决这个问题,问题点在于这个流是https的协议,但是这个m3u8流里面的ts文件是http的,切换协议失败,可以改成http播放,或者后台把这个m3u8流里面的ts文件都改成https,反正就是要跟这个m3u8流一样的开头,我这边是cdn上有把http的都转换成https,所以就没处理了
2播放https的流切换到http后会播放失败
官方git上的提问https://github.com/bilibili/ijkplayer/issues/4905
给出的解决方案是设置清理dns,但是目测没啥用,切换一次后又不行了
ijkMediaPlayer.setOption( IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_clear", 1);
别人的汇总:
ijkplayer中遇到的问题汇总 - 掘金
好啦到这里就完结了,有哪里有遗漏或者问题的话留个评论,谢谢
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)