koo視訊播放器整合到flutter專案中

如此風景發表於2021-08-17

注意本文把視訊播放層作為一個外掛,從而方便flutter整合

1.配置外掛

把專案中已有的koo視訊播放器so庫拷貝到專案指定目錄當中

image.png

把libflutter.so檔案拷貝到上圖的資料夾中

配置安卓目錄下的build.gradle檔案(讓專案能夠識別so庫檔案)

android {
    compileSdkVersion 30

    defaultConfig {
        minSdkVersion 16
        ndk{ abiFilters "armeabi-v7a" }//主要是這句
    }
}
複製程式碼

建立KoolMediaPlayer幫助類,用於java程式碼呼叫播放器相關功能

  1. 載入so庫
public static void loadLibraries() {
    System.loadLibrary("kooffmpeg");
    System.loadLibrary("mediaplayer");
}
複製程式碼
  1. 定義常用的本地方法(這裡是進行自己的封裝,並沒有把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方法結束
複製程式碼
  1. java層面的呼叫 -- 方法太多,見下面的類全部內容
  2. 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.遇到的問題及解決方式

  1. 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中的專案下載下來執行。檢視效果。 點我點我

重要的參考文章:

談一談Flutter外接紋理

m3u8測試地址

相關文章