Note that this article uses the video playback layer as a plug-in to facilitate flutter integration
1. Configure the plug-in
Copy the koo video player so library from the project to the project directory
Copy the libfluence.so file to the folder above
Configure the build.gradle file in the Android directory (to enable the project to recognize so library files)
Android {compileSdkVersion 30 defaultConfig {minSdkVersion 16 NDK {abiFilters "armeababi -v7a"}Copy the code
Create a KoolMediaPlayer helper class for Java code to invoke player related functions
- Load so library
public static void loadLibraries() {
System.loadLibrary("kooffmpeg");
System.loadLibrary("mediaplayer");
}
Copy the code
- Define common native methods (this is a case of wrapping yourself, not taking everything from the koolSDK)
Public static final native long nativeInit(); public native float nativeGetSpeed() throws IllegalStateException; public native long nativeSetup(Object var1) throws IllegalStateException; public native void nativeSetDataSource(String var1, String[] var2, String[] var3) throws IllegalStateException; public native void nativePrepare() throws IllegalStateException; public native void nativePrepareAsync() throws IllegalStateException; public native void nativeSetSurface(Surface var1, int var2) throws IllegalStateException; public native int nativeGetVideoWidth() throws IllegalStateException; public native int nativeGetVideoHeight() throws IllegalStateException; public native int nativeGetCurrentPosition() throws IllegalStateException, IllegalArgumentException; public native void nativeSeekTo(int var1) throws IllegalStateException; public native void nativeSetAudioVolume(int var1) throws IllegalStateException; public native void nativeSetSpeed(float var1) throws IllegalStateException; public native void nativeStop() throws IllegalStateException; public native int nativeGetMediaDuration() throws IllegalStateException; public native void nativeStart() throws IllegalStateException; public native void nativeMPause() throws IllegalStateException; public native void nativeRelease() throws IllegalStateException; public native void nativeSetNetTimeout(int var1) throws IllegalStateException; public native void nativeSetCacheEnable(boolean var1) throws IllegalStateException; public native void nativeSetHwDecEnable(boolean var1, boolean var2) throws IllegalStateException; public native void nativeReset() throws IllegalStateException; //-------> End with method CCopy the code
- Calls at the Java level — too many methods, see the full class below
- The entire KoolMediaPlayer class
import android.content.ContentResolver;
import android.content.Context;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import org.json.JSONObject;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Calendar;
import java.util.Map;
public class KoolMediaPlayer {
private static boolean mBrokenLibraries;
private long mNativeContext;
private Context mContext;
private boolean mStayAwake;
private boolean mScreenOnWhilePlaying;
protected SurfaceHolder mSurfaceHolder;
protected Surface mSurface;
private int mVideoRenderType = 4;
private PowerManager.WakeLock mWakeLock = null;
private KoolMediaPlayer.NativeMsgHandler mEventHandler;
public static void loadLibraries() {
System.loadLibrary("kooffmpeg");
System.loadLibrary("mediaplayer");
}
public KoolMediaPlayer(Context context) {
Looper looper;
if ((looper = Looper.myLooper()) != null) {
this.mEventHandler = new KoolMediaPlayer.NativeMsgHandler(this, looper);
} else if ((looper = Looper.getMainLooper()) != null) {
this.mEventHandler = new KoolMediaPlayer.NativeMsgHandler(this, looper);
} else {
this.mEventHandler = null;
}
this.mContext=context;
if (mBrokenLibraries) {
this.mNativeContext = 0L;
Log.e("KoolMediaPlayer", "Error in load library!\n");
} else {
long nNativeContext = this.nativeSetup(new WeakReference<KoolMediaPlayer>(this));
if (nNativeContext != this.mNativeContext) {
Log.e("KoolMediaPlayer", "Error in create native mediaplayer\n");
}
}
}
//C方法开始
public static final native long nativeInit();
public native float nativeGetSpeed() throws IllegalStateException;
public native long nativeSetup(Object var1) throws IllegalStateException;
public native void nativeSetDataSource(String var1, String[] var2, String[] var3) throws IllegalStateException;
public native void nativePrepare() throws IllegalStateException;
public native void nativePrepareAsync() throws IllegalStateException;
public native void nativeSetSurface(Surface var1, int var2) throws IllegalStateException;
public native int nativeGetVideoWidth() throws IllegalStateException;
public native int nativeGetVideoHeight() throws IllegalStateException;
public native int nativeGetCurrentPosition() throws IllegalStateException, IllegalArgumentException;
public native void nativeSeekTo(int var1) throws IllegalStateException;
public native void nativeSetAudioVolume(int var1) throws IllegalStateException;
public native void nativeSetSpeed(float var1) throws IllegalStateException;
public native void nativeStop() throws IllegalStateException;
public native int nativeGetMediaDuration() throws IllegalStateException;
public native void nativeStart() throws IllegalStateException;
public native void nativeMPause() throws IllegalStateException;
public native void nativeRelease() throws IllegalStateException;
public native void nativeSetNetTimeout(int var1) throws IllegalStateException;
public native void nativeSetCacheEnable(boolean var1) throws IllegalStateException;
public native void nativeSetHwDecEnable(boolean var1, boolean var2) throws IllegalStateException;
public native void nativeReset() throws IllegalStateException;
//------->C方法结束
//setDataSource一系列
public void setDataSource(Uri uri) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
this.setDataSource(mContext, uri, null);
}
public void setDataSource(Context context, Uri uri, Map<String, String> headers) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
ContentResolver resolver = context.getContentResolver();
String scheme = uri.getScheme();
if ("file".equals(scheme)) {
this.setDataSource(uri.getPath());
} else {
if ("content".equals(scheme) && "settings".equals(uri.getAuthority())) {
int type = RingtoneManager.getDefaultType(uri);
Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
if (actualUri == null) {
throw new FileNotFoundException("Failed to resolve default ringtone");
}
}
this.setDataSource(uri.toString(), headers);
}
}
public void setDataSource(String path) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
this.setDataSource((String)path, (String[])null, (String[])null);
}
//setDataSource一系列 end
public void setSurface(Surface surface) {
if (this.mScreenOnWhilePlaying && surface != null) {
Log.w("KoolMediaPlayer", "setScreenOnWhilePlaying(true) is ineffective for Surface");
}
this.mSurfaceHolder = null;
this.nativeSetSurface(surface, this.mVideoRenderType);
this.mSurface = surface;
this.updateSurfaceScreenOn();
}
private void updateSurfaceScreenOn() {
if (this.mSurfaceHolder != null) {
this.mSurfaceHolder.setKeepScreenOn(this.mScreenOnWhilePlaying && this.mStayAwake);
}
}
public void setScreenOnWhilePlaying(boolean screenOn) {
if (this.mScreenOnWhilePlaying != screenOn) {
if (screenOn && this.mSurfaceHolder == null) {
Log.w("KoolMediaPlayer", "setScreenOnWhilePlaying(true) is ineffective without a SurfaceHolder");
}
this.mScreenOnWhilePlaying = screenOn;
this.updateSurfaceScreenOn();
}
}
public void setAudioStreamType(int type) {
}
public void setVolume(int volume) {
if (this.isValidNativeObjId(this.mNativeContext)) {
this.nativeSetAudioVolume(volume);
}
}
public int getDuration() {
int mediaDuration = 0;
if (this.isValidNativeObjId(this.mNativeContext)) {
mediaDuration = this.nativeGetMediaDuration();
}
return mediaDuration;
}
private boolean isValidNativeObjId(long id) {
boolean ret = true;
if (id <= 0L) {
ret = false;
}
return ret;
}
public void setHwDecEnable(boolean flag, boolean fastRenderFlag) {
if (this.isValidNativeObjId(this.mNativeContext)) {
this.nativeSetHwDecEnable(flag, fastRenderFlag);
}
}
public void prepare() {
if (this.isValidNativeObjId(this.mNativeContext)) {
this.nativePrepare();
}
}
public void setNetTimeout(int seconds) {
if (this.isValidNativeObjId(this.mNativeContext)) {
this.nativeSetNetTimeout(seconds);
}
}
public void setCacheEnable(boolean flag) {
if (this.isValidNativeObjId(this.mNativeContext)) {
this.nativeSetCacheEnable(flag);
}
}
public void setSpeed(float speed) {
if (this.isValidNativeObjId(this.mNativeContext)) {
this.nativeSetSpeed(speed);
}
}
public float getSpeed() {
float speed = 1.0F;
if (this.isValidNativeObjId(this.mNativeContext)) {
speed = this.nativeGetSpeed();
}
return speed;
}
public void prepareAsync() {
if (this.isValidNativeObjId(this.mNativeContext)) {
this.nativePrepareAsync();
}
}
public void seekTo(int msec) {
if (this.isValidNativeObjId(this.mNativeContext)) {
this.nativeSeekTo(msec);
}
}
public void stop() {
this.stayAwake(false);
if (this.isValidNativeObjId(this.mNativeContext)) {
this.nativeStop();
}
}
private void stayAwake(boolean awake) {
if (this.mWakeLock != null) {
if (awake && !this.mWakeLock.isHeld()) {
this.mWakeLock.acquire();
} else if (!awake && this.mWakeLock.isHeld()) {
this.mWakeLock.release();
}
}
this.mStayAwake = awake;
this.updateSurfaceScreenOn();
}
//获得当前正在播放的位置
public int getCurrentPosition() {
int currPosition = 0;
if (this.isValidNativeObjId(this.mNativeContext)) {
currPosition = this.nativeGetCurrentPosition();
}
return currPosition;
}
private static void postEventFromNative(Object mediaplayer, int what, int arg1, int arg2, Object obj) {
Log.d("------------>postEvent执行了", "Get event what = " + what + ",arg1 = " + arg1 + ",arg2 = " + arg2);
KoolMediaPlayer mp = null;
try {
mp = (KoolMediaPlayer)((WeakReference)mediaplayer).get();
} catch (Exception var7) {
var7.printStackTrace();
}
if (mp != null) {
if (obj != null) {
Log.i("error", obj.toString());
}
if (mp.mEventHandler != null) {
Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
mp.mEventHandler.sendMessage(m);
}
}
}
public void setDataSource(String path, Map<String, String> headers) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
//TODO 此处如有需要,则需要实现
String[] keys = null;
String[] values = null;
// if (headers != null) {
// keys = new String[headers.size()];
// values = new String[headers.size()];
// int i = 0;
// for(Iterator var6 = headers.entrySet().iterator(); var6.hasNext(); ++i) {
//
// Map.Entry<String, String> entry = (Map.Entry<String, String>)var6.next();
// keys[i] = (String)entry.getKey();
// values[i] = (String)entry.getValue();
// }
// }
this.setDataSource(path, keys, values);
}
private void setDataSource(String path, String[] keys, String[] values) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
Uri uri = Uri.parse(path);
String scheme = uri.getScheme();
if ("file".equals(scheme)) {
path = uri.getPath();
} else if (scheme != null) {
this.nativeSetDataSource(path, keys, values);
return;
}
File file = new File(path);
if (file.exists()) {
this.nativeSetDataSource(path, keys, values);
} else {
throw new IOException("setDataSource failed.");
}
}
static {
String errorMsgBrokenLib = "";
try {
loadLibraries();
nativeInit();
} catch (UnsatisfiedLinkError var2) {
System.err.println(var2.getMessage());
mBrokenLibraries = true;
errorMsgBrokenLib = var2.getMessage();
} catch (Exception var3) {
System.err.println(var3.getMessage());
mBrokenLibraries = true;
errorMsgBrokenLib = var3.getMessage();
}
if (mBrokenLibraries) {
Log.e("KoolMediaPlayer", errorMsgBrokenLib);
}
}
public void start() {
this.stayAwake(true);
if (this.isValidNativeObjId(this.mNativeContext)) {
this.nativeStart();
}
}
public void pause() {
this.stayAwake(false);
if (this.isValidNativeObjId(this.mNativeContext)) {
this.nativeMPause();
}
}
public void reset() {
this.stayAwake(false);
if (this.isValidNativeObjId(this.mNativeContext)) {
this.nativeReset();
}
}
public void release() {
this.stayAwake(false);
this.updateSurfaceScreenOn();
//TODO
// this.mOnPreparedListener = null;
// this.mOnBufferingUpdateListener = null;
// this.mOnCompletionListener = null;
// this.mOnSeekCompleteListener = null;
// this.mOnErrorListener = null;
// this.mOnInfoListener = null;
// this.mOnVideoSizeChangedListener = null;
if (this.isValidNativeObjId(this.mNativeContext)) {
this.nativeRelease();
}
}
public int getVideoWidth() {
int nVideoWidth = 0;
if (this.isValidNativeObjId(this.mNativeContext)) {
nVideoWidth = this.nativeGetVideoWidth();
}
return nVideoWidth;
}
public int getVideoHeight() {
int nVideoHeight = 0;
if (this.isValidNativeObjId(this.mNativeContext)) {
nVideoHeight = this.nativeGetVideoHeight();
}
return nVideoHeight;
}
public class NativeMsgHandler extends Handler {
private KoolMediaPlayer mMediaPlayer;
private long last_bps_time = 0L;
private long last_fps_time = 0L;
private long last_bandwidth_time = 0L;
private long last_duration_time = 0L;
private long last_start_buffering_time = 0L;
private long last_buffering_time = 0L;
private boolean is_first_frame = true;
private boolean is_seeking = false;
private int is_report_buffering = 0;
public static final int MEDIA_REPORTER_EVENT_FIRST_FRAME_BUFFERING = 1;
public static final int MEDIA_REPORTER_EVENT_SEEKING_BUFFERING = 2;
public static final int MEDIA_REPORTER_EVENT_NORNAL_BUFFERING = 3;
public NativeMsgHandler(KoolMediaPlayer mp, Looper looper) {
super(looper);
this.mMediaPlayer = mp;
}
public void sendInfoData(int arg1, int arg2, Object obj, JSONObject eventData) {
String eventName = "";
Calendar cal = Calendar.getInstance();
if (eventData != null) {
short event;
try {
switch(arg1) {
case 701:
if (this.is_first_frame) {
this.is_report_buffering = 1;
} else if (this.is_seeking) {
this.is_report_buffering = 2;
} else {
this.is_report_buffering = 3;
}
if (cal.getTimeInMillis() > this.last_buffering_time + 10000L) {
this.last_start_buffering_time = cal.getTimeInMillis();
return;
}
case 702:
if (cal.getTimeInMillis() <= this.last_buffering_time + 10000L) {
return;
}
event = 2201;
if (this.is_report_buffering == 1) {
eventName = "first_buffering_end";
} else if (this.is_report_buffering == 2) {
eventName = "seeking_buffering_end";
} else {
eventName = "buffering_end";
}
eventData.put("elapsed_time", arg2);
this.last_buffering_time = cal.getTimeInMillis();
break;
case 703:
if (cal.getTimeInMillis() <= this.last_bandwidth_time + 60000L) {
return;
}
event = 2202;
eventName = "bandwidth";
eventData.put("bandwidth", arg2);
this.last_bandwidth_time = cal.getTimeInMillis();
break;
case 704:
if (cal.getTimeInMillis() <= this.last_duration_time + 60000L) {
return;
}
event = 2203;
eventName = "buffering_duration";
eventData.put("buffer_duration", arg2);
this.last_duration_time = cal.getTimeInMillis();
break;
case 705:
event = 2206;
eventName = "tv_info";
break;
case 706:
event = 2204;
eventName = "buffering_timeout";
break;
case 707:
event = 2205;
eventName = "buffering_maxfreq";
break;
case 708:
event = 2207;
eventName = "dns_time";
eventData.put("dns_time", arg2);
break;
case 1000:
event = 2300;
eventName = "demuxer_time";
eventData.put("avg_time", arg2);
break;
case 1001:
event = 2301;
eventName = "auddec_time";
eventData.put("avg_time", arg2);
break;
case 1002:
event = 2302;
eventName = "viddec_time";
eventData.put("avg_time", arg2);
break;
case 1003:
event = 2303;
eventName = "audfil_time";
eventData.put("avg_time", arg2);
break;
case 1004:
event = 2304;
eventName = "vidfil_time";
eventData.put("avg_time", arg2);
break;
case 1015:
event = 2305;
eventName = "audren_time";
eventData.put("avg_time", arg2);
break;
case 1016:
event = 2306;
eventName = "vidren_time";
eventData.put("avg_time", arg2);
break;
case 2000:
event = 2100;
eventName = "first_audio_time";
eventData.put("first_frame_time", arg2);
break;
case 2001:
this.is_first_frame = false;
event = 2101;
eventName = "first_video_time";
eventData.put("first_frame_time", arg2);
break;
case 2002:
if (cal.getTimeInMillis() <= this.last_fps_time + 30000L) {
return;
}
event = 2102;
eventName = "fps";
eventData.put("fps", arg2);
this.last_fps_time = cal.getTimeInMillis();
break;
case 2100:
event = 2106;
eventName = "dec_info";
eventData.put("is_hw_decode", 1);
eventData.put("codec_id", arg2);
break;
case 2101:
event = 2106;
eventName = "dec_info";
eventData.put("is_hw_decode", 0);
eventData.put("codec_id", arg2);
break;
case 3000:
if (cal.getTimeInMillis() <= this.last_bps_time + 60000L) {
return;
}
event = 2103;
eventName = "bps";
eventData.put("bps", arg2);
this.last_bps_time = cal.getTimeInMillis();
break;
case 4000:
event = 2400;
eventName = "mark_enable";
eventData.put("mark_enable", arg2);
break;
case 4001:
event = 2401;
eventName = "mark_time";
eventData.put("mark_time", arg2);
break;
case 4100:
event = 2500;
eventName = "screenshot";
eventData.put("screenshot", arg2);
break;
default:
if (arg1 >= 0) {
return;
}
event = 2800;
eventName = "errorinfo";
eventData.put("code1", arg1);
eventData.put("code2", arg2);
if (obj != null) {
eventData.put("desc", obj);
}
}
} catch (Exception var9) {
var9.printStackTrace();
return;
}
// if (KoolMediaPlayer.this.mKoolMediaReporter != null) {
// int ret = KoolMediaPlayer.this.mKoolMediaReporter.sendMessage(event, eventName, eventData);
// if (ret != 0) {
// Log.e("KoolMediaPlayer", "SendMessage error " + ret);
// }
// }
}
}
public void sendStatisticalMessage(int what, int arg1, int arg2, Object obj) {
int event = 0;
String eventName = "";
JSONObject eventData = new JSONObject();
Calendar cal = Calendar.getInstance();
if (true) {
try {
switch(what) {
case 0:
break;
case 1:
event = 2003;
eventName = "prepared";
eventData.put("elapsed_time", arg1);
break;
case 2:
this.is_seeking = false;
event = 2011;
eventName = "complete";
break;
case 3:
if (cal.getTimeInMillis() <= this.last_duration_time + 10000L) {
return;
}
event = 2203;
eventName = "buffering_update";
eventData.put("buffer_duration", arg1);
this.last_duration_time = cal.getTimeInMillis();
break;
case 4:
event = 2007;
eventName = "seek_complete";
break;
case 6:
event = 2004;
eventName = "started";
eventData.put("elapsed_time", arg1);
break;
case 7:
event = 2005;
eventName = "pause";
break;
case 8:
event = 2008;
eventName = "stopped";
break;
case 10:
event = 2009;
eventName = "release";
break;
case 11:
event = 2010;
eventName = "reset";
break;
case 12:
event = 2000;
eventName = "initialize";
break;
case 13:
this.is_first_frame = true;
event = 2001;
eventName = "set_url";
break;
case 14:
event = 2002;
eventName = "prepare";
break;
case 15:
this.is_seeking = true;
event = 2006;
eventName = "seek";
eventData.put("seek_time", arg1);
break;
case 16:
event = 2104;
eventName = "speedx";
eventData.put("speedx", arg1);
break;
case 17:
event = 2105;
eventName = "volumex";
eventData.put("volumex", arg1);
break;
case 100:
event = -1;
eventName = "error";
eventData.put("module", arg1);
eventData.put("code", arg2);
if (obj != null) {
eventData.put("desc", obj);
}
break;
case 200:
this.sendInfoData(arg1, arg2, obj, eventData);
return;
default:
Log.e("KoolMediaPlayer", "unrecognized message");
return;
}
} catch (Exception var10) {
var10.printStackTrace();
return;
}
// if (mKoolMediaReporter != null) {
// int ret = mKoolMediaReporter.sendMessage(event, eventName, eventData);
// if (ret != 0) {
// Log.e("KoolMediaPlayer", "SendMessage error " + ret + eventName + eventData);
// }
// }
}
}
public void handleMessage(Message msg) {
this.sendStatisticalMessage(msg.what, msg.arg1, msg.arg2, msg.obj);
switch(msg.what) {
case 0:
Log.d("KoolMediaPlayer", "Media NOP Message!");
break;
case 1:
Log.d("KoolMediaPlayer", "MediaPrepared!");
mMediaPlayer.start();
mMediaPlayer.setSpeed(2f);
break;
case 2:
Log.d("KoolMediaPlayer", "MediaCompleted!");
// MediaPlayer.OnCompletionListener onCompletionListener = KoolMediaPlayer.this.mOnCompletionListener;
// if (onCompletionListener != null) {
// onCompletionListener.onCompletion(this.mMediaPlayer);
// }
break;
case 3:
Log.d("KoolMediaPlayer", "MediaBuffering percent = " + msg.arg1 + "%\n");
// MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = KoolMediaPlayer.this.mOnBufferingUpdateListener;
// if (onBufferingUpdateListener != null) {
// onBufferingUpdateListener.onBufferingUpdate(this.mMediaPlayer, msg.arg1);
// }
break;
case 4:
Log.d("KoolMediaPlayer", "Media Player Seek Completed!\n");
// MediaPlayer.OnSeekCompleteListener onSeekCompleteListener = KoolMediaPlayer.this.mOnSeekCompleteListener;
// if (onSeekCompleteListener != null) {
// onSeekCompleteListener.onSeekComplete(this.mMediaPlayer);
// }
break;
case 5:
Log.d("KoolMediaPlayer", "Media Player video size changed!\n");
// if (KoolMediaPlayer.this.mOnVideoSizeChangedListener != null) {
// KoolMediaPlayer.this.mOnVideoSizeChangedListener.onVideoSizeChanged(this.mMediaPlayer, msg.arg1, msg.arg2);
// }
break;
case 6:
Log.d("KoolMediaPlayer", "Media Player status started");
break;
case 7:
Log.d("KoolMediaPlayer", "Media Player status paused");
break;
case 8:
Log.d("KoolMediaPlayer", "Media Player status stopped!");
break;
case 9:
Log.d("KoolMediaPlayer", "Media Player skipped!");
break;
case 10:
Log.d("KoolMediaPlayer", "Media Player release!");
break;
case 99:
Log.d("KoolMediaPlayer", "Media Player timed text!");
break;
case 100:
Log.d("KoolMediaPlayer", "Media Player error, what = " + msg.arg1 + ",arg = " + msg.arg2);
// OnErrorListener onErrorListener = KoolMediaPlayer.this.mOnErrorListener;
// if (onErrorListener != null) {
// onErrorListener.onError(this.mMediaPlayer, msg.arg1, msg.arg2, msg.obj);
// }
break;
case 200:
Log.d("KoolMediaPlayer", "Media Player info, what = " + msg.arg1 + "arg1 = " + msg.arg2);
// OnInfoListener onInfoListener = KoolMediaPlayer.this.mOnInfoListener;
// if (onInfoListener != null) {
// onInfoListener.onInfo(this.mMediaPlayer, msg.arg1, msg.arg2);
// }
break;
case 201:
Log.d("KoolMediaPlayer", "Media Player subtitle data!");
break;
case 202:
Log.d("KoolMediaPlayer", "Media Player meta data!");
break;
default:
Log.e("KoolMediaPlayer", "Unknown message type " + msg.what);
return;
}
}
}
}
Copy the code
Create PlayerPlugin
When the Android layer code is called on the Flutter side, this plugin is responsible for 1, calling the android layer code accordingly, 2, creating SurfaceTextureEntry 3, receiving the address of the video to be played, and completing the playback operation 4, sending the corresponding texture ID back to the Flutter layer. So that the flutter can be controlled
code
import android.media.AudioManager; import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.view.Surface; import androidx.annotation.NonNull; import org.koolearn.mediaplayer.KoolMediaPlayer; import java.io.IOException; import java.util.logging.LogRecord; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.view.TextureRegistry; /** Playertest1Plugin */ public class Playertest1Plugin implements FlutterPlugin, MethodCallHandler { /// The MethodChannel that will the communication between Flutter and native Android /// /// This local reference serves to register the plugin with the Flutter Engine and unregister it /// when the Flutter Engine is detached from the Activity private MethodChannel channel; private KoolMediaPlayer mPlayer; private FlutterPlugin.FlutterPluginBinding mFlutterPluginBinding; @Override public void onAttachedToEngine(@NonNull FlutterPlugin.FlutterPluginBinding binding) { mFlutterPluginBinding = binding; mPlayer = new KoolMediaPlayer(binding.getApplicationContext()); channel = new MethodChannel(binding.getBinaryMessenger(), "playertest"); channel.setMethodCallHandler(this); } @Override public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { if (call.method.equals("getTextureId")) { // val textures: TextureRegistry = this.registrarFor("video").textures() TextureRegistry.SurfaceTextureEntry textureEntry = mFlutterPluginBinding.getTextureRegistry().createSurfaceTexture(); final Surface surface = new Surface(textureEntry.surfaceTexture()); mPlayer.setSurface(surface); // AudioManager am = (AudioManager)mFlutterPluginBinding.getSystemService("audio"); // am.requestAudioFocus(this.mOnAudioFocusChangeListener, 3, 1); this.mPlayer.setHwDecEnable(true, true); this.mPlayer.setAudioStreamType(3); try { // mPlayer.setDataSource(Uri.parse("http://tanzi27niu.cdsb.mobi/wps/wp-content/uploads/2017/05/2017-05-17_17-33-30.mp4")); // mPlayer.setDataSource(Uri.parse("http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8")); MPlayer. SetDataSource (Uri. Parse (" http://10.155.41.114/videoname/video_name.m3u8 ")); } catch (IOException e) { e.printStackTrace(); } mPlayer.setCacheEnable(true); mPlayer.prepareAsync(); // Handler h=new Handler(Looper.getMainLooper()); // h.postDelayed(new Runnable() { // @Override // public void run() { // mPlayer.getVideoWidth(); // mPlayer.getVideoHeight(); // mPlayer.setSpeed(1); // mPlayer.start(); / / / /}}, 5000); // mPlayer.setSpeed(3); result.success(textureEntry.id()); } else { result.notImplemented(); } } @Override public void onDetachedFromEngine(@NonNull FlutterPlugin.FlutterPluginBinding binding) { channel.setMethodCallHandler(null); mPlayer.reset(); mPlayer.release(); }}Copy the code
2. Configure the Demo project
1.Manifest increases network permissions
<uses-permission android:name="android.permission.INTERNET"/>
Copy the code
Addndk {abiFilters “armeabi-v7a”} addndK {abiFilters “armeabi-v7a”} addndK {abiFilters “armeabi-v7a”}
3. Use plug-ins
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:playertest1/playertest1.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int _textureId = -1;
@override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
int textureId;
// Platform messages may fail, so we use a try/catch PlatformException.
// We also handle the message potentially returning null.
try {
textureId = await Playertest1.getTextureId;
} on PlatformException {
textureId = -1;
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_textureId = textureId;
});
}
@override
Widget build(BuildContext context) {
print("_textureId = $_textureId");
return Container(
padding: EdgeInsets.only(bottom: 600),
child: Texture(textureId: _textureId),
width: 400,
height: 300,
);
return Texture(textureId: _textureId);
}
}
Copy the code
3. Problems encountered and solutions
- Abi problem, the player AbiarmV7A is 32 bit, the flutter is 64 bit.
Solution: While testing the project, I found that the packaging of the flutter was actually generated with 32 bit flutter but was removed each time we ran it, so we just manually copythe 32 bit package into the project jNI folder. 2. The problem of adding so library configuration to the FLUTTER must be strictly configured when adding so library to the flutter. For details, please refer to relevant configuration in my demo. 3. Package name signature Problem When we create the local method helper class, it must be the same as the package name defined in the C library, and the method signature must also correspond to it. Otherwise, the methods defined in the SO library cannot be called correctly.
4. Legacy issues
The speed of some M3U8 resources is abnormal, but they can be played properly after the speed is adjusted. The cause is unknown. Mp4 can play normally.
So far our koo player has been transplanted into the Flutter demo. The demo can play m3U8 and MP4 videos normally. As for the subsequent specific use, we need to refine the details later.
The demo address
If necessary, you can download the project in the demo down to run. See the effect. My PM me
Important reference articles:
Talk about the Flutter external texture
M3u8 Test address