一:使用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的方式:
Map<String, String> headers = 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 <android/bitmap.h>//需要引入这个包
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 snapshot\n");
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 === %d\n", 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 snapshot\n");
}
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);
别人的汇总:
好啦到这里就完结了,有哪里有遗漏或者问题的话留个评论,谢谢