注意本文把視訊播放層作為一個外掛,從而方便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資料夾裡面來就行啦。 2. flutter中新增so庫配置的問題 flutter中新增so庫時一定要進行嚴格的配置,具體可參考我的demo裡面的相關配置。 3.包名簽名的問題 我們在建立本地方法輔助類時一定要與c庫中定義的包名相同,方法簽名也一定要對應上。否則不能正確呼叫so庫中定義的方法。
4.遺留問題
M3U8部分資源在播放時速度不正常,而調整倍速後則能正常播放,目前原因不明。mp4可播放正常。
至此我們的koo播放器移植到flutter專案中的demo已經完成,demo已經可以正常播放m3u8和mp4視訊。至於後續具體使用,則需要後面我們來完善細節了。
demo地址
如有需要大家可以把demo中的專案下載下來執行。檢視效果。 點我點我