注意本文把视频播放层作为一个插件,从而方便flutter集成
1.配置插件
把项目中已有的koo视频播放器so库拷贝到项目指定目录当中
把libflutter.so文件拷贝到上图的文件夹中
配置安卓目录下的build.gradle文件(让项目能够识别so库文件)
android {
compileSdkVersion 30
defaultConfig {
minSdkVersion 16
ndk{ abiFilters "armeabi-v7a" }//主要是这句
}
}
复制代码
创建KoolMediaPlayer帮助类,用于java代码调用播放器相关功能
- 加载so库
public static void loadLibraries() {
System.loadLibrary("kooffmpeg");
System.loadLibrary("mediaplayer");
}
复制代码
- 定义常用的本地方法(这里是进行自己的封装,并没有把koolSDK内所有的东西都拿过来)
//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方法结束
复制代码
- java层面的调用 -- 方法太多,见下面的类全部内容
- KoolMediaPlayer类全部内容
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;
}
}
}
}
复制代码
创建PlayerPlugin
当flutter端调用安卓层代码时,此plugin负责1,相应调用,2,创建SurfaceTextureEntry 3,接收传递过来的要播放视频的地址,及完成播放操作 4,把相应的纹理id回传给flutter层。以便flutter进行控制
代码
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();
}
}
复制代码
2.配置demo工程
1.Manifest增加网络权限
<uses-permission android:name="android.permission.INTERNET"/>
复制代码
2.android目录下的build文件中增加ndk配置 android->defaultConfig->增加 ndk{ abiFilters "armeabi-v7a" }
3.使用插件
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);
}
}
复制代码
3.遇到的问题及解决方式
- abi问题,播放器abiarmv7a是32位的,flutter提供的是64位的。 解决方式:在测试项目时,我发现打包的时候实际上flutter是有32位flutter生成的只不过是在每次运行时都给除掉了,此时我们只要手动copy32位的flutter.so包到项目jni文件夹里面来就行啦。
- flutter中添加so库配置的问题 flutter中添加so库时一定要进行严格的配置,具体可参考我的demo里面的相关配置。 3.包名签名的问题 我们在创建本地方法辅助类时一定要与c库中定义的包名相同,方法签名也一定要对应上。否则不能正确调用so库中定义的方法。
4.遗留问题
M3U8部分资源在播放时速度不正常,而调整倍速后则能正常播放,目前原因不明。mp4可播放正常。
至此我们的koo播放器移植到flutter项目中的demo已经完成,demo已经可以正常播放m3u8和mp4视频。至于后续具体使用,则需要后面我们来完善细节了。
demo地址
如有需要大家可以把demo中的项目下载下来运行。查看效果。 点我点我