1: Use the playback mode and screenshot mode of IjkMediaPlayer+SurfaceView
You need to pull your compiled so library from the official website
Pull the Android code git clone https://github.com/Bilibili/ijkplayer.git ijkplayer-android
The compilation steps are the same. There may be some problems with the environment configuration. ndk recommends r14; Too low to compile, too high java version does not support;
If you package a player yourself, you can directly inherit the Surface and implement the methods that need to be inherited,
1: Initialization
Initialize in the constructor and set the playback screen to the Surface
public BlibiliVideoPlayer(Context context) { super(context); init(); }
private void init() { IjkMediaPlayer.loadLibrariesOnce(null); IjkMediaPlayer.native_profileBegin("libijkplayer.so"); ijkMediaPlayer=new IjkMediaPlayer(); //Open log // 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; //off timer 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", "decoding error"); // } // }); }
private void loadVideo() { ijkMediaPlayer.prepareAsync(); ijkMediaPlayer.setDisplay(getHolder()); ijkMediaPlayer.start(); // startProgressUpdateTimer(); }
Add request header, anti-theft chain and refer:
Map<String, String> headers = new HashMap<>(); headers.put("Referer","content");// if (headers != null) { try{ ijkMediaPlayer.setDataSource(url,headers); }catch (Exception e){ } }else{ try{ ijkMediaPlayer.setDataSource(url); }catch (Exception e){ } }//Playback mode with and without header
You can play it normally here.
2 screenshot implementation: need to change to FF under ijkplayer Android / ijkmedia / ijkplayer_ ffplay. c ijkplayer.c ff_ffplay-def.h file, Android MK and ijkplayer of ijkplayer Android / ijkmedia / ijkplayer / Android /_ jni. c
JNI add: ijkplayer_jni.c
#include <android/bitmap. h> / / this package needs to be imported 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 Add in { "getCurrentFrame", "(Landroid/graphics/Bitmap;)Z", (void *) IjkMediaPlayer_getCurrentFrame },//Add this { "_setOption", "(ILjava/lang/String;Ljava/lang/String;)V", (void *) IjkMediaPlayer_setOption },//This one already exists
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//Add this -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 { //Add these variables to this method and put them at the beginning 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;//Is the screenshot one-time? Only one screenshot can be taken at a time int64_t start_pts; int64_t start_dts; }
After modification, start compiling and execute in jkplayer Android / Android / contrib directory
./compile-ffmpeg.sh clean
./ compile-ffmpeg.sh armv7a / / armv7a can be changed to what you want, and all of them can be compiled
Then cd Go to the previous directory jkplayer Android / Android
./ compile-ijk.sh armv7a / / generate so file
The generated so is in the corresponding file directory. For example, the armv7a compiled here is in the ijkplay Android / Android / ijkplay / ijkplay armv7a / SRC / main / LIBS / armeabi v7a directory
tips:
If you have compiled once, you can modify these c files again without executing the above steps
./ compile-ffmpeg.sh clean, which can greatly speed up the compilation speed
2: Add a screenshot method to java,
Put the generated so file into the project and modify imedia player java:
add to
boolean getCurrentFrame(Bitmap bitmap); method
Because there are many files that inherit the imedia player Java. After adding this, you need to implement the getCurrentFrame in the subclass that inherits it, such as ijkmediaplayer Adding nativa method to Java
@Override public native boolean getCurrentFrame(Bitmap bitmap);
There are several files Oh, remember to add them;
Screenshot implementation:
if(ijkMediaPlayer!=null){ int width = getVideoWidth();//Desired width int height = getVideoHeight();//Desired height Bitmap srcBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);//Recommendation 4444 is relatively small, and 8888 reports missing once SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddhhmmss"); String picName = dateFormat.format(new java.util.Date()); //Generate picture files by date //Util.getScreenShotPath() is the path, which is usually placed under data / package name / directory //If this path does not exist, an error will be reported. If you do not have read-write permission, you will also report an error. How to apply for permission yourself Baidu String mPath2 = Util.getScreenShotPath() + picName + ".png"; boolean flag = ijkMediaPlayer.getCurrentFrame(srcBitmap); if (flag) { File imageFile = new File(mPath2); try { if(imageFile!=null){ //Save picture OutputStream fout = new FileOutputStream(imageFile); srcBitmap.compress(Bitmap.CompressFormat.PNG, 100, fout); fout.flush(); fout.close(); } } catch (Exception e) { e.printStackTrace(); callback.onFaild(); } } }
3: Problem record
1. Failed to play the m3u8 stream of https, and failed to request the internal ts file. The problem is that the stream is an https protocol, but the ts file in the m3u8 stream is http. If the protocol switching fails, you can change it to HTTP play, or change the ts file in the m3u8 stream to https in the background, Anyway, it is the same as the beginning of this m3u8 stream. On my side, the cdn has converted all HTTP into https, so I didn't handle it
2. Playing https stream will fail after switching to http
Questions on official git https://github.com/bilibili/ijkplayer/issues/4905
The solution given is to set up dns cleaning, but visual inspection is useless. It doesn't work after switching once
ijkMediaPlayer.setOption( IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_clear", 1);
Summary of others:
Summary of problems encountered in ijkplayer - Nuggets
Well, that's it. If there are any omissions or problems, leave a comment. Thank you 🙏