package im.zego.reactnative;

import android.app.Application;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.view.View;
import android.util.Log;
import android.view.TextureView;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.NativeViewHierarchyManager;
import com.facebook.react.uimanager.UIBlock;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.common.UIManagerType;

import org.json.JSONObject;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import im.zego.zegoexpress.*;
import im.zego.zegoexpress.callback.IZegoApiCalledEventHandler;
import im.zego.zegoexpress.callback.IZegoAudioEffectPlayerEventHandler;
import im.zego.zegoexpress.callback.IZegoAudioEffectPlayerLoadResourceCallback;
import im.zego.zegoexpress.callback.IZegoAudioEffectPlayerSeekToCallback;
import im.zego.zegoexpress.callback.IZegoCopyrightedMusicDownloadCallback;
import im.zego.zegoexpress.callback.IZegoCopyrightedMusicEventHandler;
import im.zego.zegoexpress.callback.IZegoCopyrightedMusicGetKrcLyricByTokenCallback;
import im.zego.zegoexpress.callback.IZegoCopyrightedMusicGetLrcLyricCallback;
import im.zego.zegoexpress.callback.IZegoCopyrightedMusicGetSharedResourceCallback;
import im.zego.zegoexpress.callback.IZegoCopyrightedMusicGetStandardPitchCallback;
import im.zego.zegoexpress.callback.IZegoCopyrightedMusicInitCallback;
import im.zego.zegoexpress.callback.IZegoCopyrightedMusicRequestResourceCallback;
import im.zego.zegoexpress.callback.IZegoCopyrightedMusicSendExtendedRequestCallback;
import im.zego.zegoexpress.callback.IZegoDataRecordEventHandler;
import im.zego.zegoexpress.callback.IZegoDestroyCompletionCallback;
import im.zego.zegoexpress.callback.IZegoEventHandler;
import im.zego.zegoexpress.callback.IZegoIMSendBarrageMessageCallback;
import im.zego.zegoexpress.callback.IZegoIMSendBroadcastMessageCallback;
import im.zego.zegoexpress.callback.IZegoIMSendCustomCommandCallback;
import im.zego.zegoexpress.callback.IZegoMediaPlayerEventHandler;
import im.zego.zegoexpress.callback.IZegoMediaPlayerLoadResourceCallback;
import im.zego.zegoexpress.callback.IZegoMediaPlayerSeekToCallback;
import im.zego.zegoexpress.callback.IZegoMediaPlayerTakeSnapshotCallback;
import im.zego.zegoexpress.callback.IZegoMixerStartCallback;
import im.zego.zegoexpress.callback.IZegoMixerStopCallback;
import im.zego.zegoexpress.callback.IZegoPlayerTakeSnapshotCallback;
import im.zego.zegoexpress.callback.IZegoPublisherSetStreamExtraInfoCallback;
import im.zego.zegoexpress.callback.IZegoPublisherTakeSnapshotCallback;
import im.zego.zegoexpress.callback.IZegoPublisherUpdateCdnUrlCallback;
import im.zego.zegoexpress.callback.IZegoRealTimeSequentialDataEventHandler;
import im.zego.zegoexpress.callback.IZegoRealTimeSequentialDataSentCallback;
import im.zego.zegoexpress.callback.IZegoRoomLoginCallback;
import im.zego.zegoexpress.callback.IZegoRoomLogoutCallback;
import im.zego.zegoexpress.callback.IZegoRoomSetRoomExtraInfoCallback;
import im.zego.zegoexpress.constants.ZegoAECMode;
import im.zego.zegoexpress.constants.ZegoANSMode;
import im.zego.zegoexpress.constants.ZegoAlphaLayoutType;
import im.zego.zegoexpress.constants.ZegoAudioCaptureStereoMode;
import im.zego.zegoexpress.constants.ZegoAudioChannel;
import im.zego.zegoexpress.constants.ZegoAudioCodecID;
import im.zego.zegoexpress.constants.ZegoAudioDeviceMode;
import im.zego.zegoexpress.constants.ZegoAudioEffectPlayState;
import im.zego.zegoexpress.constants.ZegoAudioRoute;
import im.zego.zegoexpress.constants.ZegoAudioSampleRate;
import im.zego.zegoexpress.constants.ZegoAudioSourceType;
import im.zego.zegoexpress.constants.ZegoBackgroundBlurLevel;
import im.zego.zegoexpress.constants.ZegoBackgroundProcessType;
import im.zego.zegoexpress.constants.ZegoDataRecordState;
import im.zego.zegoexpress.constants.ZegoDataRecordType;
import im.zego.zegoexpress.constants.ZegoDeviceExceptionType;
import im.zego.zegoexpress.constants.ZegoDeviceType;
import im.zego.zegoexpress.constants.ZegoElectronicEffectsMode;
import im.zego.zegoexpress.constants.ZegoEngineState;
import im.zego.zegoexpress.constants.ZegoLiveAudioEffectMode;
import im.zego.zegoexpress.constants.ZegoMediaPlayerAudioChannel;
import im.zego.zegoexpress.constants.ZegoMediaPlayerAudioTrackMode;
import im.zego.zegoexpress.constants.ZegoMediaPlayerFirstFrameEvent;
import im.zego.zegoexpress.constants.ZegoMediaPlayerNetworkEvent;
import im.zego.zegoexpress.constants.ZegoMediaPlayerState;
import im.zego.zegoexpress.constants.ZegoMediaStreamType;
import im.zego.zegoexpress.constants.ZegoMixerInputContentType;
import im.zego.zegoexpress.constants.ZegoMultimediaLoadType;
import im.zego.zegoexpress.constants.ZegoNetworkMode;
import im.zego.zegoexpress.constants.ZegoNetworkSpeedTestType;
import im.zego.zegoexpress.constants.ZegoObjectSegmentationState;
import im.zego.zegoexpress.constants.ZegoObjectSegmentationType;
import im.zego.zegoexpress.constants.ZegoOrientation;
import im.zego.zegoexpress.constants.ZegoPlayerMediaEvent;
import im.zego.zegoexpress.constants.ZegoPlayerState;
import im.zego.zegoexpress.constants.ZegoPublishChannel;
import im.zego.zegoexpress.constants.ZegoPublisherState;
import im.zego.zegoexpress.constants.ZegoRemoteDeviceState;
import im.zego.zegoexpress.constants.ZegoReverbPreset;
import im.zego.zegoexpress.constants.ZegoRoomMode;
import im.zego.zegoexpress.constants.ZegoRoomState;
import im.zego.zegoexpress.constants.ZegoRoomStateChangedReason;
import im.zego.zegoexpress.constants.ZegoScenario;
import im.zego.zegoexpress.constants.ZegoScreenCaptureExceptionType;
import im.zego.zegoexpress.constants.ZegoStreamAlignmentMode;
import im.zego.zegoexpress.constants.ZegoStreamEvent;
import im.zego.zegoexpress.constants.ZegoStreamQualityLevel;
import im.zego.zegoexpress.constants.ZegoStreamResourceMode;
import im.zego.zegoexpress.constants.ZegoUpdateType;
import im.zego.zegoexpress.constants.ZegoVideoBufferType;
import im.zego.zegoexpress.constants.ZegoVideoCodecID;
import im.zego.zegoexpress.constants.ZegoVideoMirrorMode;
import im.zego.zegoexpress.constants.ZegoVideoSourceType;
import im.zego.zegoexpress.constants.ZegoViewMode;
import im.zego.zegoexpress.constants.ZegoVideoStreamType;
import im.zego.zegoexpress.constants.ZegoVoiceChangerPreset;
import im.zego.zegoexpress.entity.ZegoAudioConfig;
import im.zego.zegoexpress.entity.ZegoAudioEffectPlayConfig;
import im.zego.zegoexpress.entity.ZegoAudioFrameParam;
import im.zego.zegoexpress.entity.ZegoBackgroundConfig;
import im.zego.zegoexpress.entity.ZegoBarrageMessageInfo;
import im.zego.zegoexpress.entity.ZegoBeautifyOption;
import im.zego.zegoexpress.entity.ZegoBroadcastMessageInfo;
import im.zego.zegoexpress.entity.ZegoCDNConfig;
import im.zego.zegoexpress.entity.ZegoCanvas;
import im.zego.zegoexpress.entity.ZegoCopyrightedMusicConfig;
import im.zego.zegoexpress.entity.ZegoCopyrightedMusicGetLyricConfig;
import im.zego.zegoexpress.entity.ZegoCopyrightedMusicGetSharedConfigV2;
import im.zego.zegoexpress.entity.ZegoCopyrightedMusicQueryCacheConfigV2;
import im.zego.zegoexpress.entity.ZegoCopyrightedMusicRequestConfigV2;
import im.zego.zegoexpress.entity.ZegoCustomAudioConfig;
import im.zego.zegoexpress.entity.ZegoCustomVideoCaptureConfig;
import im.zego.zegoexpress.entity.ZegoCustomVideoProcessConfig;
import im.zego.zegoexpress.entity.ZegoDataRecordConfig;
import im.zego.zegoexpress.entity.ZegoDataRecordProgress;
import im.zego.zegoexpress.entity.ZegoEffectsBeautyParam;
import im.zego.zegoexpress.entity.ZegoEngineConfig;
import im.zego.zegoexpress.entity.ZegoEngineProfile;
import im.zego.zegoexpress.entity.ZegoLogConfig;
import im.zego.zegoexpress.entity.ZegoMediaPlayerMediaInfo;
import im.zego.zegoexpress.entity.ZegoMediaPlayerResource;
import im.zego.zegoexpress.entity.ZegoMediaPlayerStatisticsInfo;
import im.zego.zegoexpress.entity.ZegoMixerAudioConfig;
import im.zego.zegoexpress.entity.ZegoMixerInput;
import im.zego.zegoexpress.entity.ZegoMixerOutput;
import im.zego.zegoexpress.entity.ZegoMixerTask;
import im.zego.zegoexpress.entity.ZegoMixerVideoConfig;
import im.zego.zegoexpress.entity.ZegoNetworkSpeedTestConfig;
import im.zego.zegoexpress.entity.ZegoNetworkSpeedTestQuality;
import im.zego.zegoexpress.entity.ZegoNetworkTimeInfo;
import im.zego.zegoexpress.entity.ZegoObjectSegmentationConfig;
import im.zego.zegoexpress.entity.ZegoPlayStreamQuality;
import im.zego.zegoexpress.entity.ZegoPlayerConfig;
import im.zego.zegoexpress.entity.ZegoPublisherConfig;
import im.zego.zegoexpress.entity.ZegoPublishStreamQuality;
import im.zego.zegoexpress.entity.ZegoReverbAdvancedParam;
import im.zego.zegoexpress.entity.ZegoReverbEchoParam;
import im.zego.zegoexpress.entity.ZegoRoomConfig;
import im.zego.zegoexpress.entity.ZegoRoomExtraInfo;
import im.zego.zegoexpress.entity.ZegoScreenCaptureConfig;
import im.zego.zegoexpress.entity.ZegoSoundLevelConfig;
import im.zego.zegoexpress.entity.ZegoStream;
import im.zego.zegoexpress.entity.ZegoStreamRelayCDNInfo;
import im.zego.zegoexpress.entity.ZegoUser;
import im.zego.zegoexpress.entity.ZegoVideoConfig;
import im.zego.zegoexpress.entity.ZegoVoiceChangerParam;
import im.zego.zegoexpress.entity.ZegoNetWorkResourceCache;
import im.zego.zegoexpress.entity.ZegoCustomAudioProcessConfig;

public class RCTZegoExpressNativeModule extends ReactContextBaseJavaModule {

    private static final String Prefix = "im.zego.reactnative.";

    private static boolean kIsInited = false;

    private static boolean pluginReported = false;
    private final ReactApplicationContext reactContext;

    private HashMap<Integer, ZegoMediaPlayer> mediaPlayerMap;
    private HashMap<Integer, ZegoAudioEffectPlayer> audioEffectPlayerMap;
    private HashMap<Integer, ZegoRealTimeSequentialDataManager> realTimeSequentialDataManagerHashMap;
    private ZegoCopyrightedMusic copyrightedMusic = null;

    private boolean destroyWhenKilled = false;

    public RCTZegoExpressNativeModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
        this.reactContext.addLifecycleEventListener(new LifecycleEventListener() {
            @Override
            public void onHostResume() {

            }

            @Override
            public void onHostPause() {

            }

            @Override
            public void onHostDestroy() {
                if (destroyWhenKilled) {
                    ZegoExpressEngine.destroyEngine(null);
                }
            }
        });
    }

    @NonNull
    @Override
    public String getName() {
        return "ZegoExpressNativeModule";
    }

    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put("prefix", Prefix);
        return constants;
    }

    private void sendEvent(String eventName,
                           @Nullable WritableMap params) {
        this.reactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(Prefix + eventName, params);
    }

    private WritableMap getCallbackArgs(Object ...objects) {
        WritableMap map = Arguments.createMap();
        WritableArray data = Arguments.createArray();
        for(Object obj : objects) {
            if(obj instanceof Integer) {
                data.pushInt((Integer) obj);
            } else if(obj instanceof Long) {
                data.pushInt(((Long) obj).intValue());
            } else if(obj instanceof String) {
                data.pushString((String) obj);
            } else if(obj instanceof Double) {
                data.pushDouble((Double)obj);
            } else if(obj instanceof Float) {
                data.pushDouble((Float)obj);
            } else if(obj instanceof Boolean) {
                data.pushBoolean((Boolean)obj);
            } else if(obj instanceof ReadableArray) {
                data.pushArray((ReadableArray) obj);
            } else if(obj instanceof ReadableMap) {
                data.pushMap((ReadableMap)obj);
            }
        }
        /*if(data.size() == 0) {
            data.pushNull();
        }*/
        map.putArray("data", data);

        return map;
    }

    private WritableMap getMediaPlayerCallbackArgs(int index, Object ...objects) {
        WritableMap map = Arguments.createMap();
        WritableArray data = Arguments.createArray();
        for(Object obj : objects) {
            if(obj instanceof Integer) {
                data.pushInt((Integer) obj);
            } else if(obj instanceof Long) {
                data.pushInt(((Long) obj).intValue());
            } else if(obj instanceof String) {
                data.pushString((String) obj);
            } else if(obj instanceof Double) {
                data.pushDouble((Double)obj);
            } else if(obj instanceof Float) {
                data.pushDouble((Float)obj);
            } else if(obj instanceof Boolean) {
                data.pushBoolean((Boolean)obj);
            } else if(obj instanceof ReadableArray) {
                data.pushArray((ReadableArray) obj);
            } else if(obj instanceof ReadableMap) {
                data.pushMap((ReadableMap)obj);
            }
        }
        /*if(data.size() == 0) {
            data.pushNull();
        }*/
        map.putArray("data", data);
        map.putInt("idx", index);
        return map;
    }

    private void reportPluginInfo() {

        if (pluginReported) { return; }

        pluginReported = true;

        HashMap<String, String> advancedConfigMap = new HashMap<>();
        advancedConfigMap.put("thirdparty_framework_info", "reactnative");

        ZegoEngineConfig configObject = new ZegoEngineConfig();
        configObject.advancedConfig = advancedConfigMap;

        ZegoExpressEngine.setEngineConfig(configObject);
    }

    // Required for rn built in EventEmitter Calls.
    @ReactMethod
    public void addListener(String eventName) {

    }

    @ReactMethod
    public void removeListeners(Integer count) {

    }

    @ReactMethod
    public void getVersion(Promise promise) {
        promise.resolve(ZegoExpressEngine.getVersion());
    }

    @ReactMethod
    public void createEngineWithProfile(ReadableMap profileParam, Promise promise) {

        // Report framework info
        reportPluginInfo();

        // Fix hot update did not destroy the engine
        if (ZegoExpressEngine.getEngine() != null) {
            ZegoExpressEngine.destroyEngine(null);
        }

        ZegoExpressEngine.setApiCalledCallback(zegoApiCalledEventHandler);

        double appID = profileParam.getDouble("appID");
        int scenario = profileParam.getInt("scenario");

        ZegoEngineProfile profile = new ZegoEngineProfile();
        profile.appID = (long) appID;
        profile.scenario = ZegoScenario.getZegoScenario(scenario);
        profile.application = (Application) this.reactContext.getApplicationContext();

        if (profileParam.hasKey("appSign")) {
            profile.appSign = profileParam.getString("appSign");
        }

        ZegoExpressEngine.createEngine(profile, zegoEventHandler);
        kIsInited = true;
        ZegoExpressEngine.getEngine().setCustomAudioProcessHandler(ZegoCustomAudioProcessManager.sharedInstance());

        promise.resolve(null);
    }

    @ReactMethod
    public void createEngine(Double appID, String appSign, boolean isTestEnv, int scenario, Promise promise) {

        // Report framework info
        reportPluginInfo();

        // Fix hot update did not destroy the engine
        if (ZegoExpressEngine.getEngine() != null) {
            ZegoExpressEngine.destroyEngine(null);
        }

        ZegoExpressEngine.setApiCalledCallback(zegoApiCalledEventHandler);

        ZegoExpressEngine.createEngine(appID.longValue(), appSign, isTestEnv, ZegoScenario.getZegoScenario(scenario), (Application) this.reactContext.getApplicationContext(), zegoEventHandler);

        kIsInited = true;

        promise.resolve(null);
    }

    @ReactMethod
    public void destroyEngine(final Promise promise) {
        if(kIsInited) {
            ZegoExpressEngine.destroyEngine(new IZegoDestroyCompletionCallback() {
                @Override
                public void onDestroyCompletion() {
                    promise.resolve(null);
                }
            });
            kIsInited = false;
        } else {
            promise.resolve(null);
        }
    }

    @ReactMethod
    public void setEngineConfig(ReadableMap config, Promise promise) {

        // Report framework info
        reportPluginInfo();

        ZegoEngineConfig configObj = new ZegoEngineConfig();
        ReadableMap logConfig = config.getMap("logConfig");
        if(logConfig != null) {
            ZegoLogConfig logConfigObj = new ZegoLogConfig();
            String logPath = logConfig.getString("logPath");
            if(logPath != null) {
                logConfigObj.logPath = logPath;
            }
            if (logConfig.hasKey("logSize")) {
                logConfigObj.logSize = logConfig.getInt("logSize");
            }
            configObj.logConfig = logConfigObj;
        }

        ReadableMap advancedConfig = config.getMap("advancedConfig");
        if(advancedConfig != null) {
            HashMap<String, Object> adMap = advancedConfig.toHashMap();
            for(Map.Entry<String, Object> entry: adMap.entrySet()) {
                if (entry.getKey().equals("destroy_when_killed")) {
                    destroyWhenKilled = entry.getValue().toString().equals("true");
                } else {
                    configObj.advancedConfig.put(entry.getKey(), entry.getValue().toString());
                }
            }
        }

        ZegoExpressEngine.setEngineConfig(configObj);

        promise.resolve(null);
    }

    @ReactMethod
    public void setRoomMode(int mode, Promise promise) {
        ZegoRoomMode roomMode = ZegoRoomMode.getZegoRoomMode(mode);

        ZegoExpressEngine.setRoomMode(roomMode);
        promise.resolve(null);
    }

    @ReactMethod
    public void uploadLog(Promise promise) {
        ZegoExpressEngine.getEngine().uploadLog();

        promise.resolve(null);
    }


    @ReactMethod
    public void callExperimentalAPI(String params, Promise promise) {
        String result = ZegoExpressEngine.getEngine().callExperimentalAPI(params);

        promise.resolve(result);
    }

    @ReactMethod
    public void loginRoom(String roomID, ReadableMap user, ReadableMap config, final Promise promise) {
        ZegoUser userObj = new ZegoUser(user.getString("userID"), user.getString("userName"));

        ZegoRoomConfig roomConfigObj = new ZegoRoomConfig();
        if(config != null) {
            if (config.hasKey("isUserStatusNotify")) {
                roomConfigObj.isUserStatusNotify = config.getBoolean("isUserStatusNotify");
            }
            if (config.hasKey("maxMemberCount")) {
                roomConfigObj.maxMemberCount = config.getInt("maxMemberCount");
            }
            if (config.hasKey("token")) {
                roomConfigObj.token = config.getString("token");
            }
        }
        ZegoExpressEngine.getEngine().loginRoom(roomID, userObj, roomConfigObj, new IZegoRoomLoginCallback() {
            @Override
            public void onRoomLoginResult(int errorCode, JSONObject extendedData) {
                WritableMap returnMap = Arguments.createMap();
                returnMap.putInt("errorCode", errorCode);
                returnMap.putString("extendedData", extendedData.toString());
                promise.resolve(returnMap);
            }
        });
    }

    @ReactMethod
    public void logoutRoom(String roomID, final Promise promise) {
        if (roomID != null) {
            ZegoExpressEngine.getEngine().logoutRoom(roomID, new IZegoRoomLogoutCallback() {
                @Override
                public void onRoomLogoutResult(int errorCode, JSONObject extendedData) {
                    WritableMap returnMap = Arguments.createMap();
                    returnMap.putInt("errorCode", errorCode);
                    returnMap.putString("extendedData", extendedData.toString());
                    promise.resolve(returnMap);
                }
            });
        } else {
            ZegoExpressEngine.getEngine().logoutRoom(new IZegoRoomLogoutCallback() {
                @Override
                public void onRoomLogoutResult(int errorCode, JSONObject extendedData) {
                    WritableMap returnMap = Arguments.createMap();
                    returnMap.putInt("errorCode", errorCode);
                    returnMap.putString("extendedData", extendedData.toString());
                    promise.resolve(returnMap);
                }
            });
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void switchRoom(String fromRoomID, String toRoomID, ReadableMap config, Promise promise) {

        ZegoRoomConfig roomConfig = new ZegoRoomConfig();
        if (config != null) {
            if (config.hasKey("isUserStatusNotify")) {
                roomConfig.isUserStatusNotify = config.getBoolean("isUserStatusNotify");
            }
            if (config.hasKey("maxMemberCount")) {
                roomConfig.maxMemberCount = config.getInt("maxMemberCount");
            }
            if (config.hasKey("token")) {
                roomConfig.token = config.getString("token");
            }
        }
        ZegoExpressEngine.getEngine().switchRoom(fromRoomID, toRoomID, roomConfig);

        promise.resolve(null);
    }

    @ReactMethod
    public void renewToken(String roomID, String token, Promise promise) {
        ZegoExpressEngine.getEngine().renewToken(roomID, token);

        promise.resolve(null);
    }

    @ReactMethod
    public void setRoomExtraInfo(String roomID, String key, String value, final Promise promise) {
        ZegoExpressEngine.getEngine().setRoomExtraInfo(roomID, key, value, new IZegoRoomSetRoomExtraInfoCallback() {
            @Override
            public void onRoomSetRoomExtraInfoResult(int i) {
                WritableMap returnMap = Arguments.createMap();
                returnMap.putInt("errorCode", i);
                promise.resolve(returnMap);
            }
        });
    }

    @ReactMethod
    public void setStreamExtraInfo(String extraInfo, int channel, final Promise promise) {
        ZegoExpressEngine.getEngine().setStreamExtraInfo(extraInfo, ZegoPublishChannel.getZegoPublishChannel(channel), new IZegoPublisherSetStreamExtraInfoCallback() {
            @Override
            public void onPublisherSetStreamExtraInfoResult(int i) {
                WritableMap returnMap = Arguments.createMap();
                returnMap.putInt("errorCode", i);
                promise.resolve(returnMap);
            }
        });
    }

    @ReactMethod
    public void startPublishingStream(String streamID, int channel, ReadableMap config, Promise promise) {

        ZegoPublishChannel publishChannel = ZegoPublishChannel.getZegoPublishChannel(channel);

        if (config != null) {
            ZegoPublisherConfig publisherConfig = new ZegoPublisherConfig();
            publisherConfig.roomID = config.getString("roomID");
            publisherConfig.forceSynchronousNetworkTime = config.getInt("forceSynchronousNetworkTime");
            ZegoExpressEngine.getEngine().startPublishingStream(streamID, publisherConfig, publishChannel);
        } else {
            ZegoExpressEngine.getEngine().startPublishingStream(streamID, publishChannel);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void stopPublishingStream(int channel, Promise promise) {
        ZegoExpressEngine.getEngine().stopPublishingStream(ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void startPreview(final ReadableMap view, final int channel, final Promise promise) {
        if (view != null) {
            final int viewTag = view.getInt("reactTag");
            if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
                UIManager uiManager = UIManagerHelper.getUIManager(reactContext, UIManagerType.FABRIC);
                View nativeView = uiManager.resolveView(viewTag);
                ZegoCanvas canvas = createCanvas(nativeView, view);
                if (ZegoExpressEngine.getEngine() != null) {
                    ZegoExpressEngine.getEngine().startPreview(canvas, ZegoPublishChannel.getZegoPublishChannel(channel));
                }
                promise.resolve(null);
            } else {
                UIManagerModule uiMgr = this.reactContext.getNativeModule(UIManagerModule.class);
                uiMgr.addUIBlock(new UIBlock() {
                    @Override
                    public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
                        View nativeView = nativeViewHierarchyManager.resolveView(viewTag);
                        ZegoCanvas canvas = createCanvas(nativeView, view);
                        if (ZegoExpressEngine.getEngine() != null) {
                            ZegoExpressEngine.getEngine().startPreview(canvas, ZegoPublishChannel.getZegoPublishChannel(channel));
                        }
                        promise.resolve(null);
                    }
                });
            }
        } else {
            ZegoExpressEngine.getEngine().startPreview(null, ZegoPublishChannel.getZegoPublishChannel(channel));
            promise.resolve(null);
        }
    }

    @ReactMethod
    public void stopPreview(int channel, Promise promise) {
        ZegoExpressEngine.getEngine().stopPreview(ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void addPublishCdnUrl(String streamID, String targetURL, final Promise promise) {
        ZegoExpressEngine.getEngine().addPublishCdnUrl(streamID, targetURL, new IZegoPublisherUpdateCdnUrlCallback() {
            @Override
            public void onPublisherUpdateCdnUrlResult(int i) {
                WritableMap returnMap = Arguments.createMap();
                returnMap.putInt("errorCode", i);
                promise.resolve(returnMap);
            }
        });
    }

    @ReactMethod
    public void removePublishCdnUrl(String streamID, String targetURL, final Promise promise) {
        ZegoExpressEngine.getEngine().removePublishCdnUrl(streamID, targetURL, new IZegoPublisherUpdateCdnUrlCallback() {
            @Override
            public void onPublisherUpdateCdnUrlResult(int i) {
                WritableMap returnMap = Arguments.createMap();
                returnMap.putInt("errorCode", i);
                promise.resolve(returnMap);
            }
        });
    }

    @ReactMethod
    public void enablePublishDirectToCDN(boolean enable, ReadableMap configMap, int channel, Promise promise) {

        ZegoCDNConfig cdnConfig = new ZegoCDNConfig();
        if (configMap != null) {
            cdnConfig.url = configMap.getString("url");
            cdnConfig.authParam = configMap.getString("authParam");
            cdnConfig.protocol = configMap.getString("protocol");
            cdnConfig.quicVersion = configMap.getString("quicVersion");
        }

        ZegoExpressEngine.getEngine().enablePublishDirectToCDN(enable, cdnConfig, ZegoPublishChannel.getZegoPublishChannel(channel));
        promise.resolve(null);
    }

    @ReactMethod
    public void setVideoConfig(ReadableMap config, int channel, Promise promise) {
        ZegoVideoConfig configObj = new ZegoVideoConfig();
        configObj.captureWidth = config.getInt("captureWidth");
        configObj.captureHeight = config.getInt("captureHeight");
        configObj.encodeWidth = config.getInt("encodeWidth");
        configObj.encodeHeight = config.getInt("encodeHeight");
        configObj.bitrate = config.getInt("bitrate");
        configObj.fps = config.getInt("fps");
        configObj.codecID = ZegoVideoCodecID.getZegoVideoCodecID(config.getInt("codecID"));

        ZegoExpressEngine.getEngine().setVideoConfig(configObj, ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void getVideoConfig(int channel, Promise promise) {
        ZegoVideoConfig config = ZegoExpressEngine.getEngine().getVideoConfig(ZegoPublishChannel.getZegoPublishChannel(channel));

        WritableMap map = Arguments.createMap();
        map.putInt("captureWidth", config.captureWidth);
        map.putInt("captureHeight", config.captureHeight);
        map.putInt("encodeWidth", config.encodeWidth);
        map.putInt("encodeHeight", config.encodeHeight);
        map.putInt("bitrate", config.bitrate);
        map.putInt("fps", config.fps);
        map.putInt("codecID", config.codecID.value());

        promise.resolve(map);
    }

    @ReactMethod
    public void setVideoMirrorMode(int mode, int channel, Promise promise) {
        ZegoExpressEngine.getEngine().setVideoMirrorMode(ZegoVideoMirrorMode.getZegoVideoMirrorMode(mode), ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void setAppOrientation(int orientation, int channel, Promise promise) {
        ZegoExpressEngine.getEngine().setAppOrientation(ZegoOrientation.getZegoOrientation(orientation), ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void setAudioConfig(ReadableMap config, int channel, Promise promise) {
        ZegoAudioConfig configObj = new ZegoAudioConfig();
        configObj.bitrate = config.getInt("bitrate");
        configObj.channel = ZegoAudioChannel.getZegoAudioChannel(config.getInt("channel"));
        configObj.codecID = ZegoAudioCodecID.getZegoAudioCodecID(config.getInt("codecID"));

        ZegoExpressEngine.getEngine().setAudioConfig(configObj, ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void getAudioConfig(Promise promise) {
        ZegoAudioConfig config = ZegoExpressEngine.getEngine().getAudioConfig();

        WritableMap map = Arguments.createMap();
        map.putInt("bitrate", config.bitrate);
        map.putInt("channel", config.channel.value());
        map.putInt("codecID", config.codecID.value());

        promise.resolve(map);
    }

    @ReactMethod
    public void takePublishStreamSnapshot(int channel, final Promise promise) {
        ZegoExpressEngine.getEngine().takePublishStreamSnapshot(new IZegoPublisherTakeSnapshotCallback() {
            @Override
            public void onPublisherTakeSnapshotResult(int i, Bitmap bitmap) {
                WritableMap returnMap = Arguments.createMap();
                returnMap.putInt("errorCode", i);
                returnMap.putString("imageBase64", ZegoUtils.bitmapToBase64(bitmap));

                promise.resolve(returnMap);
            }
        }, ZegoPublishChannel.getZegoPublishChannel(channel));
    }

    @ReactMethod
    public void takePlayStreamSnapshot(String streamID, final Promise promise) {
        ZegoExpressEngine.getEngine().takePlayStreamSnapshot(streamID, new IZegoPlayerTakeSnapshotCallback() {
            @Override
            public void onPlayerTakeSnapshotResult(int i, Bitmap bitmap) {
                WritableMap map = Arguments.createMap();
                map.putInt("errorCode", i);
                map.putString("imageBase64", ZegoUtils.bitmapToBase64(bitmap));

                promise.resolve(map);
            }
        });
    }

    @ReactMethod
    public void mutePublishStreamAudio(boolean mute, int channel, Promise promise) {
        ZegoExpressEngine.getEngine().mutePublishStreamAudio(mute, ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void mutePublishStreamVideo(boolean mute, int channel, Promise promise) {
        ZegoExpressEngine.getEngine().mutePublishStreamVideo(mute, ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void setCaptureVolume(int volume, Promise promise) {
        ZegoExpressEngine.getEngine().setCaptureVolume(volume);

        promise.resolve(null);
    }

    @ReactMethod
    public void sendSEI(ReadableArray data, int channel, Promise promise) {

        byte[] bytes = new byte[data.size()];
        for (int i = 0; i < data.size(); i++) {
            bytes[i] = (byte) data.getInt(i);
        }

        ZegoExpressEngine.getEngine().sendSEI(bytes, ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void enableHardwareEncoder(boolean enable, Promise promise) {
        ZegoExpressEngine.getEngine().enableHardwareEncoder(enable);

        promise.resolve(null);
    }

    @ReactMethod
    public void enableH265EncodeFallback(boolean enable, Promise promise) {
        ZegoExpressEngine.getEngine().enableH265EncodeFallback(enable);

        promise.resolve(null);
    }

    @ReactMethod
    public void isVideoEncoderSupported(int codecID, Promise promise) {
        promise.resolve(ZegoExpressEngine.getEngine().isVideoEncoderSupported(ZegoVideoCodecID.getZegoVideoCodecID(codecID)));
    }

    @ReactMethod
    public void setVideoSource(int source, int channel, Promise promise) {
        ZegoVideoSourceType sourceType = ZegoVideoSourceType.getZegoVideoSourceType(source);
        ZegoPublishChannel publishChannel = ZegoPublishChannel.getZegoPublishChannel(channel);

        int result = ZegoExpressEngine.getEngine().setVideoSource(sourceType, publishChannel);
        promise.resolve(result);
    }

    @ReactMethod
    public void setAudioSource(int source, int channel, Promise promise) {
        ZegoAudioSourceType sourceType = ZegoAudioSourceType.getZegoAudioSourceType(source);
        ZegoPublishChannel publishChannel = ZegoPublishChannel.getZegoPublishChannel(channel);

        int result = ZegoExpressEngine.getEngine().setAudioSource(sourceType, publishChannel);
        promise.resolve(result);
    }

    @ReactMethod
    public void enableVideoObjectSegmentation(boolean enable, final ReadableMap config, int channel, Promise promise) {

        ZegoObjectSegmentationConfig segmentationConfig = new ZegoObjectSegmentationConfig();
        if (config != null) {
            ZegoBackgroundConfig backgroundConfig = new ZegoBackgroundConfig();
            ReadableMap backgroundConfigMap = config.getMap("backgroundConfig");
            if (backgroundConfigMap != null) {
                backgroundConfig.processType = ZegoBackgroundProcessType.getZegoBackgroundProcessType(backgroundConfigMap.getInt("processType"));
                backgroundConfig.blurLevel = ZegoBackgroundBlurLevel.getZegoBackgroundBlurLevel(backgroundConfigMap.getInt("blurLevel"));
                backgroundConfig.color = backgroundConfigMap.getInt("color");
                backgroundConfig.imageURL = backgroundConfigMap.getString("imageURL");
                backgroundConfig.videoURL = backgroundConfigMap.getString("videoURL");

                segmentationConfig.backgroundConfig = backgroundConfig;
            }

            segmentationConfig.objectSegmentationType = ZegoObjectSegmentationType.getZegoObjectSegmentationType(config.getInt("objectSegmentationType"));
        }

        ZegoExpressEngine.getEngine().enableVideoObjectSegmentation(enable, segmentationConfig, ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void enableAlphaChannelVideoEncoder(boolean enable, int alphaLayout, int channel, Promise promise) {

        ZegoExpressEngine.getEngine().enableAlphaChannelVideoEncoder(enable, ZegoAlphaLayoutType.getZegoAlphaLayoutType(alphaLayout), ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void startPlayingStream(final String streamID, final ReadableMap view, final ReadableMap config, final Promise promise) {

        ZegoPlayerConfig configObj = null;

        if(config != null) {
            configObj = new ZegoPlayerConfig();
            ZegoCDNConfig cdnConfigObj;
            if(config.hasKey("cdnConfig")) {
                cdnConfigObj = new ZegoCDNConfig();
                ReadableMap cdnConfig = config.getMap("cdnConfig");
                if (cdnConfig != null) {
                    cdnConfigObj.url = cdnConfig.getString("url");
                    cdnConfigObj.authParam = cdnConfig.getString("authParam");
                }
                configObj.cdnConfig = cdnConfigObj;
            }
            if (config.hasKey("roomID")) {
                configObj.roomID = config.getString("roomID");
            }
            if (config.hasKey("videoCodecID")) {
                configObj.videoCodecID = ZegoVideoCodecID.getZegoVideoCodecID(config.getInt("videoCodecID"));
            }
            if (config.hasKey("resourceMode")) {
                configObj.resourceMode = ZegoStreamResourceMode.getZegoStreamResourceMode(config.getInt("resourceMode"));
            }
        }

        if (view != null) {
            final int viewTag = view.getInt("reactTag");
            if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
                UIManager uiManager = UIManagerHelper.getUIManager(reactContext, UIManagerType.FABRIC);
                View nativeView = uiManager.resolveView(viewTag);
                ZegoCanvas canvas = createCanvas(nativeView, view);
                if (ZegoExpressEngine.getEngine() != null) {
                    ZegoExpressEngine.getEngine().startPlayingStream(streamID, canvas, configObj);
                }
                promise.resolve(null);
            } else {
                final ZegoPlayerConfig finalConfigObj = configObj;
                UIManagerModule uiMgr = this.reactContext.getNativeModule(UIManagerModule.class);
                uiMgr.addUIBlock(new UIBlock() {
                    @Override
                    public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
                        View nativeView = nativeViewHierarchyManager.resolveView(viewTag);
                        ZegoCanvas canvas = createCanvas(nativeView, view);
                        if (ZegoExpressEngine.getEngine() != null) {
                            ZegoExpressEngine.getEngine().startPlayingStream(streamID, canvas, finalConfigObj);
                        }
                        promise.resolve(null);
                    }
                });
            }
        } else {
            ZegoExpressEngine.getEngine().startPlayingStream(streamID, null, configObj);
            promise.resolve(null);
        }
    }

    @ReactMethod
    public void stopPlayingStream(String streamID, Promise promise) {
        ZegoExpressEngine.getEngine().stopPlayingStream(streamID);

        promise.resolve(null);
    }

    @ReactMethod
    public void setPlayVolume(String streamID, int volume, Promise promise) {
        ZegoExpressEngine.getEngine().setPlayVolume(streamID, volume);

        promise.resolve(null);
    }

    @ReactMethod
    public void setAllPlayStreamVolume(int volume, Promise promise) {
        ZegoExpressEngine.getEngine().setAllPlayStreamVolume(volume);

        promise.resolve(null);
    }

    @ReactMethod
    public void setPlayStreamVideoType(String streamID, int streamType, Promise promise) {
        ZegoExpressEngine.getEngine().setPlayStreamVideoType(streamID, ZegoVideoStreamType.getZegoVideoStreamType(streamType));

        promise.resolve(null);
    }

    @ReactMethod
    public void mutePlayStreamAudio(String streamID, boolean mute, Promise promise) {
        ZegoExpressEngine.getEngine().mutePlayStreamAudio(streamID, mute);

        promise.resolve(null);
    }

    @ReactMethod
    public void muteAllPlayStreamAudio(boolean mute, Promise promise) {
        ZegoExpressEngine.getEngine().muteAllPlayStreamAudio(mute);

        promise.resolve(null);
    }

    @ReactMethod
    public void mutePlayStreamVideo(String streamID, boolean mute, Promise promise) {
        ZegoExpressEngine.getEngine().mutePlayStreamVideo(streamID, mute);

        promise.resolve(null);
    }

    @ReactMethod
    public void muteAllPlayStreamVideo(boolean mute, Promise promise) {
        ZegoExpressEngine.getEngine().muteAllPlayStreamVideo(mute);

        promise.resolve(null);
    }

    @ReactMethod
    public void enableHardwareDecoder(boolean enable, Promise promise) {
        ZegoExpressEngine.getEngine().enableHardwareDecoder(enable);

        promise.resolve(null);
    }

    @ReactMethod
    public void isVideoDecoderSupported(int codecID, Promise promise) {
        promise.resolve(ZegoExpressEngine.getEngine().isVideoDecoderSupported(ZegoVideoCodecID.getZegoVideoCodecID(codecID)));
    }


    @ReactMethod
    public void muteMicrophone(boolean mute, Promise promise) {
        ZegoExpressEngine.getEngine().muteMicrophone(mute);

        promise.resolve(null);
    }

    @ReactMethod
    public void isMicrophoneMuted(Promise promise) {
        promise.resolve(ZegoExpressEngine.getEngine().isMicrophoneMuted());
    }

    @ReactMethod
    public void muteSpeaker(boolean mute, Promise promise) {
        ZegoExpressEngine.getEngine().muteSpeaker(mute);

        promise.resolve(null);
    }

    @ReactMethod
    public void isSpeakerMuted(Promise promise) {
        promise.resolve(ZegoExpressEngine.getEngine().isSpeakerMuted());
    }

    @ReactMethod
    public void enableAudioCaptureDevice(boolean enable, Promise promise) {
        ZegoExpressEngine.getEngine().enableAudioCaptureDevice(enable);

        promise.resolve(null);
    }

    @ReactMethod
    public void getAudioRouteType(Promise promise) {
        promise.resolve(ZegoExpressEngine.getEngine().getAudioRouteType().value());
    }

    @ReactMethod
    public void setAudioRouteToSpeaker(boolean defaultToSpeaker, Promise promise) {
        ZegoExpressEngine.getEngine().setAudioRouteToSpeaker(defaultToSpeaker);

        promise.resolve(null);
    }

    @ReactMethod
    public void enableCamera(boolean enable, int channel, Promise promise) {
        ZegoExpressEngine.getEngine().enableCamera(enable, ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void useFrontCamera(boolean enable, int channel, Promise promise) {
        ZegoExpressEngine.getEngine().useFrontCamera(enable, ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void startSoundLevelMonitor(ReadableMap config, Promise promise) {
        if (config != null) {
            ZegoSoundLevelConfig soundLevelConfig = new ZegoSoundLevelConfig();
            if (config.hasKey("millisecond")) {
                soundLevelConfig.millisecond = config.getInt("millisecond");
            }
            if (config.hasKey("enableVAD")) {
                soundLevelConfig.enableVAD = config.getBoolean("enableVAD");
            }
            ZegoExpressEngine.getEngine().startSoundLevelMonitor(soundLevelConfig);
        } else {
            ZegoExpressEngine.getEngine().startSoundLevelMonitor();
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void stopSoundLevelMonitor(Promise promise) {
        ZegoExpressEngine.getEngine().stopSoundLevelMonitor();

        promise.resolve(null);
    }

    @ReactMethod
    public void enableHeadphoneMonitor(boolean enable, Promise promise) {
        ZegoExpressEngine.getEngine().enableHeadphoneMonitor(enable);

        promise.resolve(null);
    }

    @ReactMethod
    public void enableAEC(boolean enable, Promise promise) {
        ZegoExpressEngine.getEngine().enableAEC(enable);

        promise.resolve(null);
    }

    @ReactMethod
    public void enableHeadphoneAEC(boolean enable, Promise promise) {
        ZegoExpressEngine.getEngine().enableHeadphoneAEC(enable);

        promise.resolve(null);
    }

    @ReactMethod
    public void setAECMode(int mode, Promise promise) {
        ZegoExpressEngine.getEngine().setAECMode(ZegoAECMode.getZegoAECMode(mode));

        promise.resolve(null);
    }

    @ReactMethod
    public void enableAGC(boolean enable, Promise promise) {
        ZegoExpressEngine.getEngine().enableAGC(enable);

        promise.resolve(null);
    }

    @ReactMethod
    public void enableANS(boolean enable, Promise promise) {
        ZegoExpressEngine.getEngine().enableANS(enable);

        promise.resolve(null);
    }

    @ReactMethod
    public void setANSMode(int mode, Promise promise) {
        ZegoExpressEngine.getEngine().setANSMode(ZegoANSMode.getZegoANSMode(mode));

        promise.resolve(null);
    }

    @ReactMethod
    public void enableBeautify(int feature, int channel, Promise promise) {
        ZegoExpressEngine.getEngine().enableBeautify(feature, ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void setBeautifyOption(ReadableMap option, int channel, Promise promise) {
        ZegoBeautifyOption optionObj = new ZegoBeautifyOption();
        optionObj.polishStep = option.getDouble("polishStep");
        optionObj.whitenFactor = option.getDouble("whitenFactor");
        optionObj.sharpenFactor = option.getDouble("sharpenFactor");

        ZegoExpressEngine.getEngine().setBeautifyOption(optionObj, ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void sendBroadcastMessage(String roomID, String message, final Promise promise) {
        ZegoExpressEngine.getEngine().sendBroadcastMessage(roomID, message, new IZegoIMSendBroadcastMessageCallback() {
            @Override
            public void onIMSendBroadcastMessageResult(int errorCode, long messageID) {
                WritableMap returnMap = Arguments.createMap();
                returnMap.putInt("errorCode", errorCode);
                returnMap.putDouble("messageID", messageID);
                promise.resolve(returnMap);
            }
        });
    }

    @ReactMethod
    public void sendBarrageMessage(String roomID, String message, final Promise promise) {
        ZegoExpressEngine.getEngine().sendBarrageMessage(roomID, message, new IZegoIMSendBarrageMessageCallback() {
            @Override
            public void onIMSendBarrageMessageResult(int errorCode, String messageID) {
                WritableMap returnMap = Arguments.createMap();
                returnMap.putInt("errorCode", errorCode);
                returnMap.putString("messageID", messageID);
                promise.resolve(returnMap);
            }
        });
    }

    @ReactMethod
    public void sendCustomCommand(String roomID, String command, ReadableArray toUserListArray, final Promise promise) {

        ArrayList<ZegoUser> toUserList = null;
        if(toUserListArray != null && toUserListArray.size() > 0) {
            toUserList = new ArrayList<>();
            for (Object userMapObj: toUserListArray.toArrayList()) {
                HashMap<String, Object> userMap = (HashMap<String, Object>)userMapObj;
                String userID = (String) userMap.get("userID");
                String userName = (String) userMap.get("userName");
                ZegoUser user = new ZegoUser(userID, userName);
                toUserList.add(user);
            }
        }

        ZegoExpressEngine.getEngine().sendCustomCommand(roomID, command, toUserList, new IZegoIMSendCustomCommandCallback() {
            @Override
            public void onIMSendCustomCommandResult(int errorCode) {
                WritableMap returnMap = Arguments.createMap();
                returnMap.putInt("errorCode", errorCode);
                promise.resolve(returnMap);
            }
        });
    }

    @ReactMethod
    public void startRecordingCapturedData(ReadableMap configMap, int channel, Promise promise) {
        ZegoDataRecordConfig recordConfig = new ZegoDataRecordConfig();
        if (configMap != null) {
            if (configMap.hasKey("filePath")) {
                recordConfig.filePath = configMap.getString("filePath");
            }
            if (configMap.hasKey("recordType")) {
                int type = configMap.getInt("recordType");
                recordConfig.recordType = ZegoDataRecordType.getZegoDataRecordType(type);
            }
        }
        ZegoExpressEngine.getEngine().setDataRecordEventHandler(zegoDataRecordEventHandler);
        ZegoExpressEngine.getEngine().startRecordingCapturedData(recordConfig, ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void stopRecordingCapturedData(int channel, Promise promise) {
        ZegoExpressEngine.getEngine().stopRecordingCapturedData(ZegoPublishChannel.getZegoPublishChannel(channel));

        promise.resolve(null);
    }

    @ReactMethod
    public void startMixerTask(ReadableMap taskMap, final Promise promise) {
        String taskID = taskMap.getString("taskID");
        ZegoMixerTask taskObject = new ZegoMixerTask(taskID);

        // MixerInput
        ReadableArray inputListMap = taskMap.getArray("inputList");
        if (inputListMap != null) {
            ArrayList<ZegoMixerInput> inputListObject= new ArrayList<>();
            for (Object inputMapObj: inputListMap.toArrayList()) {
                HashMap<String, Object> inputMap = (HashMap<String, Object>)inputMapObj;
                String streamID = (String) inputMap.get("streamID");
                int contentType = ZegoUtils.intValue((Number) inputMap.get("contentType"));;
                HashMap rectMap = (HashMap) inputMap.get("layout");
                int left = ZegoUtils.intValue((Number) rectMap.get("x"));
                int top = ZegoUtils.intValue((Number) rectMap.get("y"));
                int right = left + ZegoUtils.intValue((Number) rectMap.get("width"));
                int bottom = top + ZegoUtils.intValue((Number) rectMap.get("height"));
                Rect rect = new Rect(left, top, right, bottom);
                int soundLevelID = ZegoUtils.intValue((Number) inputMap.get("soundLevelID"));
                ZegoMixerInput inputObject = new ZegoMixerInput(streamID, ZegoMixerInputContentType.getZegoMixerInputContentType(contentType), rect, soundLevelID);
                inputListObject.add(inputObject);
            }
            taskObject.setInputList(inputListObject);
        }

        // MixerOutput
        ReadableArray outputListMap = taskMap.getArray("outputList");
        if (outputListMap != null) {
            ArrayList<ZegoMixerOutput> outputListObject = new ArrayList<>();
            for (Object outputMapObj: outputListMap.toArrayList()) {
                HashMap<String, Object> outputMap = (HashMap<String, Object>)outputMapObj;
                String target = (String) outputMap.get("target");
                ZegoMixerOutput outputObject = new ZegoMixerOutput(target);
                outputListObject.add(outputObject);
            }
            taskObject.setOutputList(outputListObject);
        }

        // AudioConfig
        ReadableMap audioConfigMap = taskMap.getMap("audioConfig");
        if (audioConfigMap != null) {
            int bitrate = audioConfigMap.getInt("bitrate");
            int channel = audioConfigMap.getInt("channel");
            int codecID = audioConfigMap.getInt("codecID");
            ZegoMixerAudioConfig audioConfigObject = new ZegoMixerAudioConfig();
            audioConfigObject.bitrate = bitrate;
            audioConfigObject.channel = ZegoAudioChannel.getZegoAudioChannel(channel);
            audioConfigObject.codecID = ZegoAudioCodecID.getZegoAudioCodecID(codecID);

            taskObject.setAudioConfig(audioConfigObject);
        }

        // VideoConfig
        ReadableMap videoConfigMap = taskMap.getMap("videoConfig");
        if (videoConfigMap != null) {
            int width = videoConfigMap.getInt("width");
            int height = videoConfigMap.getInt("height");
            int fps = videoConfigMap.getInt("fps");
            int bitrate = videoConfigMap.getInt("bitrate");
            ZegoMixerVideoConfig videoConfigObject = new ZegoMixerVideoConfig(width, height, fps, bitrate);

            taskObject.setVideoConfig(videoConfigObject);
        }

        // Watermark
        /*HashMap<String, Object> watermarkMap = call.argument("watermark");
        if (watermarkMap != null && !watermarkMap.isEmpty()) {
            String imageURL = (String) watermarkMap.get("imageURL");
            if (imageURL != null && imageURL.length() > 0) {
                int left = ZegoUtils.intValue((Number) watermarkMap.get("left"));
                int top = ZegoUtils.intValue((Number) watermarkMap.get("top"));
                int right = ZegoUtils.intValue((Number) watermarkMap.get("right"));
                int bottom = ZegoUtils.intValue((Number) watermarkMap.get("bottom"));
                Rect rect = new Rect(left, top, right, bottom);
                ZegoWatermark watermarkObject = new ZegoWatermark(imageURL, rect);

                taskObject.setWatermark(watermarkObject);
            }
        }*/

        // Background Image
        /*String backgroundImageURL = call.argument("backgroundImageURL");
        if (backgroundImageURL != null && backgroundImageURL.length() > 0) {
            taskObject.setBackgroundImageURL(backgroundImageURL);
        }*/

        // Enable SoundLevel
        boolean enableSoundLevel = taskMap.getBoolean("enableSoundLevel");
        taskObject.enableSoundLevel(enableSoundLevel);

        // Set AdvancedConfig
        /*HashMap<String, String> advancedConfig = call.argument("advancedConfig");
        taskObject.setAdvancedConfig(advancedConfig);*/

        ZegoExpressEngine.getEngine().startMixerTask(taskObject, new IZegoMixerStartCallback() {
            @Override
            public void onMixerStartResult(int errorCode, JSONObject extendedData) {
                WritableMap returnMap = Arguments.createMap();
                returnMap.putInt("errorCode", errorCode);
                returnMap.putString("extendedData", extendedData.toString());
                promise.resolve(returnMap);
            }
        });
    }

    @ReactMethod
    public void stopMixerTask(ReadableMap taskMap, final Promise promise) {
        String taskID = taskMap.getString("taskID");
        ZegoMixerTask taskObject = new ZegoMixerTask(taskID);

        // MixerInput
        ReadableArray inputListMap = taskMap.getArray("inputList");
        if (inputListMap != null) {
            ArrayList<ZegoMixerInput> inputListObject= new ArrayList<>();
            for (Object inputMapObj: inputListMap.toArrayList()) {
                HashMap<String, Object> inputMap = (HashMap<String, Object>)inputMapObj;
                String streamID = (String) inputMap.get("streamID");
                int contentType = ZegoUtils.intValue((Number) inputMap.get("contentType"));;
                int left = ZegoUtils.intValue((Number) inputMap.get("x"));
                int top = ZegoUtils.intValue((Number) inputMap.get("y"));
                int right = left + ZegoUtils.intValue((Number) inputMap.get("width"));
                int bottom = top + ZegoUtils.intValue((Number) inputMap.get("height"));
                Rect rect = new Rect(left, top, right, bottom);
                int soundLevelID = ZegoUtils.intValue((Number) inputMap.get("soundLevelID"));
                ZegoMixerInput inputObject = new ZegoMixerInput(streamID, ZegoMixerInputContentType.getZegoMixerInputContentType(contentType), rect, soundLevelID);
                inputListObject.add(inputObject);
            }
            taskObject.setInputList(inputListObject);
        }

        // MixerOutput
        ReadableArray outputListMap = taskMap.getArray("inputList");
        if (outputListMap != null) {
            ArrayList<ZegoMixerOutput> outputListObject = new ArrayList<>();
            for (Object outputMapObj : outputListMap.toArrayList()) {
                HashMap<String, Object> outputMap = (HashMap<String, Object>)outputMapObj;
                String target = (String) outputMap.get("target");
                ZegoMixerOutput outputObject = new ZegoMixerOutput(target);
                outputListObject.add(outputObject);
            }
            taskObject.setOutputList(outputListObject);
        }

        // no need to set audio config

        // no need to set video config

        // no need to set watermark

        // no need to set background image

        // no need to set enable sound level

        ZegoExpressEngine.getEngine().stopMixerTask(taskObject, new IZegoMixerStopCallback() {
            @Override
            public void onMixerStopResult(int errorCode) {
                WritableMap returnMap = Arguments.createMap();
                returnMap.putInt("errorCode", errorCode);
                promise.resolve(returnMap);
            }
        });
    }

    @ReactMethod
    public void startEffectsEnv(Promise promise) {
        ZegoExpressEngine.getEngine().startEffectsEnv();

        promise.resolve(null);
    }

    @ReactMethod
    public void stopEffectsEnv(Promise promise) {
        ZegoExpressEngine.getEngine().stopEffectsEnv();

        promise.resolve(null);
    }


    @ReactMethod
    public void enableEffectsBeauty(boolean enable, Promise promise) {
        ZegoExpressEngine.getEngine().enableEffectsBeauty(enable);

        promise.resolve(null);
    }

    @ReactMethod
    public void setEffectsBeautyParam(ReadableMap configMap, Promise promise) {
        ZegoEffectsBeautyParam param = new ZegoEffectsBeautyParam();
        if (configMap != null) {
            param.sharpenIntensity = configMap.getInt("sharpenIntensity");
            param.rosyIntensity = configMap.getInt("rosyIntensity");
            param.smoothIntensity = configMap.getInt("smoothIntensity");
            param.whitenIntensity = configMap.getInt("whitenIntensity");
        }
        ZegoExpressEngine.getEngine().setEffectsBeautyParam(param);

        promise.resolve(null);
    }

    @ReactMethod
    public void setVoiceChangerPreset(int preset, Promise promise) {
        ZegoVoiceChangerPreset changerPreset = ZegoVoiceChangerPreset.getZegoVoiceChangerPreset(preset);
        ZegoExpressEngine.getEngine().setVoiceChangerPreset(changerPreset);

        promise.resolve(null);
    }

    @ReactMethod
    public void setVoiceChangerParam(ReadableMap configMap, Promise promise) {
        ZegoVoiceChangerParam param = new ZegoVoiceChangerParam();
        if (configMap != null) {
            param.pitch = configMap.getInt("pitch");
        }

        ZegoExpressEngine.getEngine().setVoiceChangerParam(param);
        promise.resolve(null);
    }

    @ReactMethod
    public void setAudioEqualizerGain(int bandIndex, float bandGain, Promise promise) {
        ZegoExpressEngine.getEngine().setAudioEqualizerGain(bandIndex, bandGain);

        promise.resolve(null);
    }

    @ReactMethod
    public void setReverbPreset(int preset, Promise promise) {
        ZegoReverbPreset reverbPreset = ZegoReverbPreset.getZegoReverbPreset(preset);
        ZegoExpressEngine.getEngine().setReverbPreset(reverbPreset);

        promise.resolve(null);
    }

    @ReactMethod
    public void setReverbAdvancedParam(ReadableMap configMap, Promise promise) {
        ZegoReverbAdvancedParam param = new ZegoReverbAdvancedParam();
        if (configMap != null) {
            param.roomSize = (float) configMap.getDouble("roomSize");
            param.reverberance = (float) configMap.getDouble("reverberance");
            param.damping = (float) configMap.getDouble("damping");
            param.wetOnly = configMap.getBoolean("wetOnly");
            param.wetGain = (float) configMap.getDouble("wetGain");
            param.dryGain = (float) configMap.getDouble("dryGain");
            param.toneLow = (float) configMap.getDouble("toneLow");
            param.toneHigh = (float) configMap.getDouble("toneHigh");
            param.preDelay = (float) configMap.getDouble("preDelay");
            param.stereoWidth = (float) configMap.getDouble("stereoWidth");
        }

        ZegoExpressEngine.getEngine().setReverbAdvancedParam(param);
        promise.resolve(null);
    }

    @ReactMethod
    public void setReverbEchoParam(ReadableMap configMap, Promise promise) {
        ZegoReverbEchoParam param = new ZegoReverbEchoParam();
        if (configMap != null) {
            param.numDelays = configMap.getInt("numDelays");
            param.inGain = (float) configMap.getDouble("inGain");
            param.outGain = (float) configMap.getDouble("outGain");
            ReadableArray delayArray = configMap.getArray("delay");
            if (delayArray.size() <= 7) {
                int[] delay = new int[7];
                for (int i = 0; i < delayArray.size(); i++) {
                    delay[i] = delayArray.getInt(i);
                }
                param.delay = delay;
            }

            ReadableArray decayArray = configMap.getArray("decay");
            if (decayArray.size() <= 7) {
                float[] decay = new float[7];
                for (int i = 0; i < decayArray.size(); i++) {
                    decay[i] = (float) decayArray.getDouble(i);
                }
                param.decay = decay;
            }
        }

        ZegoExpressEngine.getEngine().setReverbEchoParam(param);
        promise.resolve(null);
    }

    @ReactMethod
    public void setElectronicEffects(boolean enable, int mode, int tonal, Promise promise) {
        ZegoElectronicEffectsMode effectsMode = ZegoElectronicEffectsMode.getZegoElectronicEffectsMode(mode);

        ZegoExpressEngine.getEngine().setElectronicEffects(enable, effectsMode, tonal);
        promise.resolve(null);
    }

    @ReactMethod
    public void enableCustomAudioIO(boolean enable, ReadableMap configMap, int channel, Promise promise) {

        ZegoCustomAudioConfig audioConfig = new ZegoCustomAudioConfig();
        if (configMap != null && configMap.hasKey("sourceType")) {
            int sourceType = configMap.getInt("sourceType");
            audioConfig.sourceType = ZegoAudioSourceType.getZegoAudioSourceType(sourceType);
        }

        ZegoPublishChannel publishChannel = ZegoPublishChannel.getZegoPublishChannel(channel);
        ZegoExpressEngine.getEngine().enableCustomAudioIO(enable, audioConfig, publishChannel);

        promise.resolve(null);
    }

    @ReactMethod
    public void enableCustomVideoCapture(boolean enable, ReadableMap configMap, int channel, Promise promise) {
        ZegoCustomVideoCaptureConfig config = new ZegoCustomVideoCaptureConfig();

        if (configMap != null && configMap.hasKey("bufferType")) {
            int bufferType = configMap.getInt("bufferType");
            config.bufferType = ZegoVideoBufferType.getZegoVideoBufferType(bufferType);
        }
        if (enable) {
            ZegoExpressEngine.getEngine().setCustomVideoCaptureHandler(ZegoCustomVideoCaptureManager.getInstance());
        } else {
            ZegoExpressEngine.getEngine().setCustomVideoCaptureHandler(null);
        }

        ZegoPublishChannel publishChannel = ZegoPublishChannel.getZegoPublishChannel(channel);
        ZegoExpressEngine.getEngine().enableCustomVideoCapture(enable, config, publishChannel);

        promise.resolve(null);
    }

    @ReactMethod
    public void enableCustomVideoProcessing(boolean enable, ReadableMap configMap, int channel, Promise promise) {
        ZegoCustomVideoProcessConfig videoProcessConfig = new ZegoCustomVideoProcessConfig();
        videoProcessConfig.bufferType = ZegoVideoBufferType.GL_TEXTURE_2D;

        if (configMap != null && configMap.hasKey("bufferType")) {
            int bufferType = configMap.getInt("bufferType");
            videoProcessConfig.bufferType = ZegoVideoBufferType.getZegoVideoBufferType(bufferType);
        }
        if (enable) {
            ZegoExpressEngine.getEngine().setCustomVideoProcessHandler(ZegoCustomVideoProcessManager.getInstance().rtcVideoProcessHandler);
        } else {
            ZegoExpressEngine.getEngine().setCustomVideoProcessHandler(null);
        }

        ZegoPublishChannel publishChannel = ZegoPublishChannel.getZegoPublishChannel(channel);
        ZegoExpressEngine.getEngine().enableCustomVideoProcessing(enable, videoProcessConfig, publishChannel);

        promise.resolve(null);
    }

    @ReactMethod
    public void enableBeforeAudioPrepAudioData(boolean enable, ReadableMap param, Promise promise) {
        ZegoAudioFrameParam frameParam = new ZegoAudioFrameParam();
        if (param != null) {
            frameParam.sampleRate = ZegoAudioSampleRate.getZegoAudioSampleRate(param.getInt("sampleRate"));
            frameParam.channel = ZegoAudioChannel.getZegoAudioChannel(param.getInt("channel"));
        }
        ZegoExpressEngine.getEngine().enableBeforeAudioPrepAudioData(enable, frameParam);
        promise.resolve(null);
    }

    @ReactMethod
    public void enableCustomAudioPlaybackProcessing(boolean enable, ReadableMap config, Promise promise) {
        ZegoCustomAudioProcessConfig processConfig = new ZegoCustomAudioProcessConfig();
        if (config != null) {
            processConfig.sampleRate = ZegoAudioSampleRate.getZegoAudioSampleRate(config.getInt("sampleRate"));
            processConfig.channel = ZegoAudioChannel.getZegoAudioChannel(config.getInt("channel"));
            processConfig.samples = config.getInt("samples");
        }
        ZegoExpressEngine.getEngine().enableCustomAudioPlaybackProcessing(enable, processConfig);
        promise.resolve(null);
    }

    @ReactMethod
    public void enableCustomAudioRemoteProcessing(boolean enable, ReadableMap config, Promise promise) {
        ZegoCustomAudioProcessConfig processConfig = new ZegoCustomAudioProcessConfig();
        if (config != null) {
            processConfig.sampleRate = ZegoAudioSampleRate.getZegoAudioSampleRate(config.getInt("sampleRate"));
            processConfig.channel = ZegoAudioChannel.getZegoAudioChannel(config.getInt("channel"));
            processConfig.samples = config.getInt("samples");
        }
        ZegoExpressEngine.getEngine().enableCustomAudioRemoteProcessing(enable, processConfig);
        promise.resolve(null);
    }
    
    @ReactMethod
    public void enableCustomAudioCaptureProcessingAfterHeadphoneMonitor(boolean enable, ReadableMap config, Promise promise) {
        ZegoCustomAudioProcessConfig processConfig = new ZegoCustomAudioProcessConfig();
        if (config != null) {
            processConfig.sampleRate = ZegoAudioSampleRate.getZegoAudioSampleRate(config.getInt("sampleRate"));
            processConfig.channel = ZegoAudioChannel.getZegoAudioChannel(config.getInt("channel"));
            processConfig.samples = config.getInt("samples");
        }
        ZegoExpressEngine.getEngine().enableCustomAudioCaptureProcessingAfterHeadphoneMonitor(enable, processConfig);
        promise.resolve(null);
    }

    @ReactMethod
    public void startScreenCapture(ReadableMap config, Promise promise) {
        ZegoScreenCaptureConfig screenCaptureConfig = new ZegoScreenCaptureConfig();
        if (config != null) {
            if (config.hasKey("captureVideo")) {
                screenCaptureConfig.captureVideo = config.getBoolean("captureVideo");
            }
            if (config.hasKey("captureAudio")) {
                screenCaptureConfig.captureAudio = config.getBoolean("captureAudio");
            }
            if (config.hasKey("audioParam")) {
                ZegoAudioFrameParam audioFrameParam = new ZegoAudioFrameParam();
                ReadableMap paramMap = config.getMap("audioParam");
                if (paramMap != null) {
                    audioFrameParam.channel = ZegoAudioChannel.getZegoAudioChannel(paramMap.getInt("channel"));
                    audioFrameParam.sampleRate = ZegoAudioSampleRate.getZegoAudioSampleRate(paramMap.getInt("sampleRate"));
                }
                screenCaptureConfig.audioParam = audioFrameParam;
            }
        }
        ZegoExpressEngine.getEngine().startScreenCapture(screenCaptureConfig);

        promise.resolve(null);
    }

    @ReactMethod
    public void stopScreenCapture(Promise promise) {
        ZegoExpressEngine.getEngine().stopScreenCapture();

        promise.resolve(null);
    }

    @ReactMethod
    public void updateScreenCaptureConfig(ReadableMap config, Promise promise) {
        promise.resolve(null);
    }

    @ReactMethod
    public void startNetworkSpeedTest(ReadableMap configMap, int interval, Promise promise) {

        ZegoNetworkSpeedTestConfig testConfig = new ZegoNetworkSpeedTestConfig();
        if (configMap != null) {
            testConfig.testUplink = configMap.getBoolean("testUplink");
            testConfig.testDownlink = configMap.getBoolean("testDownlink");
            testConfig.expectedUplinkBitrate = configMap.getInt("expectedUplinkBitrate");
            testConfig.expectedDownlinkBitrate = configMap.getInt("expectedDownlinkBitrate");
        }

        ZegoExpressEngine.getEngine().startNetworkSpeedTest(testConfig, interval);
        promise.resolve(null);
    }

    @ReactMethod
    public void stopNetworkSpeedTest(Promise promise) {
        ZegoExpressEngine.getEngine().stopNetworkSpeedTest();
        promise.resolve(null);
    }

    @ReactMethod
    public void getNetworkTimeInfo(Promise promise) {
        ZegoNetworkTimeInfo info = ZegoExpressEngine.getEngine().getNetworkTimeInfo();
        WritableMap infoMap = Arguments.createMap();
        infoMap.putInt("maxDeviation", info.maxDeviation);
        infoMap.putDouble("timestamp", info.timestamp);
        promise.resolve(infoMap);
    }

    @ReactMethod
    public void setStreamAlignmentProperty(int alignment, int channel, Promise promise) {
        ZegoExpressEngine.getEngine().setStreamAlignmentProperty(alignment, ZegoPublishChannel.getZegoPublishChannel(channel));
        promise.resolve(null);
    }

    @ReactMethod
    public void setAudioCaptureStereoMode(int mode, Promise promise) {
        ZegoExpressEngine.getEngine().setAudioCaptureStereoMode(ZegoAudioCaptureStereoMode.getZegoAudioCaptureStereoMode(mode));
        promise.resolve(null);
    }

    @ReactMethod
    public void setPlayStreamBufferIntervalRange(String streamID, int minBufferInterval, int maxBufferInterval, Promise promise) {
        ZegoExpressEngine.getEngine().setPlayStreamBufferIntervalRange(streamID, minBufferInterval, maxBufferInterval);
        promise.resolve(null);
    }

    @ReactMethod
    public void setPlayStreamsAlignmentProperty(int mode, Promise promise) {
        ZegoExpressEngine.getEngine().setPlayStreamsAlignmentProperty(ZegoStreamAlignmentMode.getZegoStreamAlignmentMode(mode));
        promise.resolve(null);
    }

    @ReactMethod
    public void setAudioDeviceMode(int mode, Promise promise) {
        ZegoExpressEngine.getEngine().setAudioDeviceMode(ZegoAudioDeviceMode.getZegoAudioDeviceMode(mode));
        promise.resolve(null);
    }

    @ReactMethod
    public void enableVirtualStereo(boolean enable, int angle, Promise promise) {
        ZegoExpressEngine.getEngine().enableVirtualStereo(enable, angle);
        promise.resolve(null);
    }

    @ReactMethod
    public void enableCustomAudioCaptureProcessing(boolean enable, ReadableMap config, Promise promise) {
        ZegoCustomAudioProcessConfig processConfig = new ZegoCustomAudioProcessConfig();
        if (config != null) {
            processConfig.sampleRate = ZegoAudioSampleRate.getZegoAudioSampleRate(config.getInt("sampleRate"));
            processConfig.channel = ZegoAudioChannel.getZegoAudioChannel(config.getInt("channel"));
        }

        ZegoExpressEngine.getEngine().enableCustomAudioCaptureProcessing(enable, processConfig);
        promise.resolve(null);
    }

    @ReactMethod
    public void enableAlignedAudioAuxData(boolean enable, ReadableMap param, Promise promise) {
        ZegoAudioFrameParam frameParam = new ZegoAudioFrameParam();
        if (param != null) {
            frameParam.sampleRate = ZegoAudioSampleRate.getZegoAudioSampleRate(param.getInt("sampleRate"));
            frameParam.channel = ZegoAudioChannel.getZegoAudioChannel(param.getInt("channel"));
        }

        ZegoExpressEngine.getEngine().enableAlignedAudioAuxData(enable, frameParam);
        promise.resolve(null);
    }

    @ReactMethod
    public void createMediaPlayer(Promise promise) {
        if(this.mediaPlayerMap == null) {
            this.mediaPlayerMap = new HashMap<>();
        }

        ZegoMediaPlayer mediaPlayer = ZegoExpressEngine.getEngine().createMediaPlayer();

        if(mediaPlayer != null) {
            int index = mediaPlayer.getIndex();

            mediaPlayer.setEventHandler(new IZegoMediaPlayerEventHandler() {
                @Override
                public void onMediaPlayerStateUpdate(ZegoMediaPlayer mediaPlayer, ZegoMediaPlayerState state, int errorCode) {
                    super.onMediaPlayerStateUpdate(mediaPlayer, state, errorCode);

                    WritableMap args = getMediaPlayerCallbackArgs(mediaPlayer.getIndex(), state.value(), errorCode);
                    sendEvent("mediaPlayerStateUpdate", args);
                }

                @Override
                public void onMediaPlayerNetworkEvent(ZegoMediaPlayer mediaPlayer, ZegoMediaPlayerNetworkEvent networkEvent) {
                    super.onMediaPlayerNetworkEvent(mediaPlayer, networkEvent);

                    WritableMap args = getMediaPlayerCallbackArgs(mediaPlayer.getIndex(), networkEvent.value());
                    sendEvent("mediaPlayerNetworkEvent", args);
                }

                @Override
                public void onMediaPlayerPlayingProgress(ZegoMediaPlayer mediaPlayer, long millisecond) {
                    super.onMediaPlayerPlayingProgress(mediaPlayer, millisecond);

                    WritableMap args = getMediaPlayerCallbackArgs(mediaPlayer.getIndex(), millisecond);
                    sendEvent("mediaPlayerPlayingProgress", args);
                }

                @Override
                public void onMediaPlayerRenderingProgress(ZegoMediaPlayer mediaPlayer, long millisecond) {
                    super.onMediaPlayerRenderingProgress(mediaPlayer, millisecond);

                    WritableMap args = getMediaPlayerCallbackArgs(mediaPlayer.getIndex(), millisecond);
                    sendEvent("mediaPlayerRenderingProgress", args);
                }

                @Override
                public void onMediaPlayerSoundLevelUpdate(ZegoMediaPlayer mediaPlayer, float soundLevel) {
                    super.onMediaPlayerSoundLevelUpdate(mediaPlayer, soundLevel);

                    WritableMap args = getMediaPlayerCallbackArgs(mediaPlayer.getIndex(), soundLevel);
                    sendEvent("mediaPlayerSoundLevelUpdate", args);
                }

                @Override
                public void onMediaPlayerFrequencySpectrumUpdate(ZegoMediaPlayer mediaPlayer, float[] spectrumList) {
                    super.onMediaPlayerFrequencySpectrumUpdate(mediaPlayer, spectrumList);

                    WritableArray spectrumArray = Arguments.createArray();
                    for (double spectrum : spectrumList) {
                        spectrumArray.pushDouble(spectrum);
                    }
                    WritableMap args = getMediaPlayerCallbackArgs(mediaPlayer.getIndex(), spectrumArray);
                    sendEvent("mediaPlayerFrequencySpectrumUpdate", args);
                }

                @Override
                public void onMediaPlayerFirstFrameEvent(ZegoMediaPlayer mediaPlayer, ZegoMediaPlayerFirstFrameEvent event) {
                    super.onMediaPlayerFirstFrameEvent(mediaPlayer, event);

                    WritableMap args = getMediaPlayerCallbackArgs(mediaPlayer.getIndex(), event.value());
                    sendEvent("mediaPlayerFirstFrameEvent", args);
                }

                @Override
                public void onMediaPlayerLocalCache(ZegoMediaPlayer mediaPlayer, int errorCode, String resource, String cachedFile) {
                    super.onMediaPlayerLocalCache(mediaPlayer, errorCode, resource, cachedFile);

                    WritableMap args = getMediaPlayerCallbackArgs(mediaPlayer.getIndex(), errorCode, resource, cachedFile);
                    sendEvent("mediaPlayerLocalCache", args);
                }

                @Override
                public void onMediaPlayerVideoSizeChanged(ZegoMediaPlayer mediaPlayer, int width, int height) {
                    super.onMediaPlayerVideoSizeChanged(mediaPlayer, width, height);

                    WritableMap args = getMediaPlayerCallbackArgs(mediaPlayer.getIndex(), width, height);
                    sendEvent("mediaPlayerVideoSizeChanged", args);
                }

                @Override
                public void onMediaPlayerRecvSEI(ZegoMediaPlayer mediaPlayer, byte[] data) {
                    super.onMediaPlayerRecvSEI(mediaPlayer, data);

                    WritableArray byteArray = Arguments.createArray();
                    for (byte datum : data) {
                        byteArray.pushInt(datum);
                    }
                    WritableMap args = getMediaPlayerCallbackArgs(mediaPlayer.getIndex(), byteArray);
                    sendEvent("mediaPlayerRecvSEI", args);
                }
            });
            this.mediaPlayerMap.put(index, mediaPlayer);

            promise.resolve(index);
        } else {
            promise.resolve(-1);
        }
    }

    @ReactMethod
    public void destroyMediaPlayer(int index, Promise promise) {
        if(this.mediaPlayerMap != null) {
            ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

            if(mediaPlayer != null) {
                ZegoExpressEngine.getEngine().destroyMediaPlayer(mediaPlayer);
            }
            this.mediaPlayerMap.remove(index);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerSetPlayerCanvas(final int index, final ReadableMap view, final Promise promise) {

        if (view != null) {
            final int viewTag = view.getInt("reactTag");
            if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
                UIManager uiManager = UIManagerHelper.getUIManager(reactContext, UIManagerType.FABRIC);
                View nativeView = uiManager.resolveView(viewTag);
                ZegoCanvas canvas = createCanvas(nativeView, view);
                ZegoMediaPlayer mediaPlayer = mediaPlayerMap.get(index);
                if(mediaPlayer != null) {
                    mediaPlayer.setPlayerCanvas(canvas);
                }
                promise.resolve(null);
            } else {
                UIManagerModule uiMgr = this.reactContext.getNativeModule(UIManagerModule.class);
                uiMgr.addUIBlock(new UIBlock() {
                    @Override
                    public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
                        View nativeView = nativeViewHierarchyManager.resolveView(viewTag);
                        ZegoCanvas canvas = createCanvas(nativeView, view);
                        ZegoMediaPlayer mediaPlayer = mediaPlayerMap.get(index);
                        if(mediaPlayer != null) {
                            mediaPlayer.setPlayerCanvas(canvas);
                        }
                        promise.resolve(null);
                    }
                });
            }
        } else {
            promise.resolve(null);
        }
    }

    @ReactMethod
    public void mediaPlayerLoadResource(int index, String path, final Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.loadResource(path, new IZegoMediaPlayerLoadResourceCallback() {
                @Override
                public void onLoadResourceCallback(int errorCode) {
                    WritableMap map = Arguments.createMap();
                    map.putInt("errorCode", errorCode);
                    promise.resolve(map);
                }
            });
        }
    }

    @ReactMethod
    public void mediaPlayerLoadResourceWithConfig(int index, ReadableMap configMap, final Promise promise) {

        ZegoMediaPlayerResource resource = new ZegoMediaPlayerResource();
        if (configMap != null) {
            resource.resourceID = configMap.getString("resourceID");
            resource.filePath = configMap.getString("filePath");
            resource.loadType = ZegoMultimediaLoadType.getZegoMultimediaLoadType(configMap.getInt("loadType"));
            resource.alphaLayout = ZegoAlphaLayoutType.getZegoAlphaLayoutType(configMap.getInt("alphaLayout"));
            resource.startPosition = (long)configMap.getDouble("startPosition");
            ReadableArray dataArray = configMap.getArray("memory");
            if (dataArray != null) {
                byte[] bytes = new byte[dataArray.size()];
                for (int i = 0; i < dataArray.size(); i++) {
                    bytes[i] = (byte) dataArray.getInt(i);
                }
                resource.memory = ByteBuffer.wrap(bytes);
                resource.memoryLength = configMap.getInt("memoryLength");
            }
        }

        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.loadResourceWithConfig(resource, new IZegoMediaPlayerLoadResourceCallback() {
                @Override
                public void onLoadResourceCallback(int errorCode) {
                    WritableMap map = Arguments.createMap();
                    map.putInt("errorCode", errorCode);
                    promise.resolve(map);
                }
            });
        }
    }

    @ReactMethod
    public void mediaPlayerStart(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.start();
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerStop(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.stop();
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerPause(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.pause();
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerResume(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.resume();
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerSeekTo(int index, double millisecond, final Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.seekTo((long) millisecond, new IZegoMediaPlayerSeekToCallback() {
                @Override
                public void onSeekToTimeCallback(int errorCode) {
                    WritableMap map = Arguments.createMap();
                    map.putInt("errorCode", errorCode);
                    promise.resolve(map);
                }
            });
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerEnableRepeat(int index, boolean enable, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.enableRepeat(enable);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerSetPlaySpeed(int index, float speed, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.setPlaySpeed(speed);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerEnableAux(int index, boolean enable, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.enableAux(enable);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerMuteLocal(int index, boolean mute, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.muteLocal(mute);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerSetVolume(int index, int volume, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.setVolume(volume);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerSetPlayVolume(int index, int volume, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.setPlayVolume(volume);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerSetPublishVolume(int index, int volume, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.setPublishVolume(volume);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerSetProgressInterval(int index, double millisecond, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.setProgressInterval((long) millisecond);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerGetPlayVolume(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            promise.resolve(mediaPlayer.getPlayVolume());
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void mediaPlayerGetPublishVolume(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            promise.resolve(mediaPlayer.getPublishVolume());
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void mediaPlayerGetTotalDuration(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            double duration = mediaPlayer.getTotalDuration();
            promise.resolve(duration);
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void mediaPlayerGetCurrentProgress(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            double progress = mediaPlayer.getCurrentProgress();
            promise.resolve(progress);
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void mediaPlayerGetAudioTrackCount(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            promise.resolve(mediaPlayer.getAudioTrackCount());
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void mediaPlayerSetAudioTrackIndex(int index, int audioTrackIndex, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            mediaPlayer.setAudioTrackIndex(audioTrackIndex);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerGetCurrentState(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            promise.resolve(mediaPlayer.getCurrentState().value());
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void mediaPlayerGetCurrentRenderingProgress(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);
        if(mediaPlayer != null) {
            double progress = mediaPlayer.getCurrentRenderingProgress();
            promise.resolve(progress);
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void mediaPlayerSetAudioTrackMode(int index, int mode, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            mediaPlayer.setAudioTrackMode(ZegoMediaPlayerAudioTrackMode.getZegoMediaPlayerAudioTrackMode(mode));
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerSetAudioTrackPublishIndex(int index, int trackIndex, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            mediaPlayer.setAudioTrackPublishIndex(trackIndex);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerEnableVoiceChanger(int index, int audioChannel, boolean enable, ReadableMap param, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            ZegoVoiceChangerParam voiceChangerParam = new ZegoVoiceChangerParam();
            voiceChangerParam.pitch = param.getInt("pitch");

            mediaPlayer.enableVoiceChanger(ZegoMediaPlayerAudioChannel.getZegoMediaPlayerAudioChannel(audioChannel), enable, voiceChangerParam);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerTakeSnapshot(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            mediaPlayer.takeSnapshot(new IZegoMediaPlayerTakeSnapshotCallback() {
                @Override
                public void onPlayerTakeSnapshotResult(int errorCode, Bitmap image) {
                    WritableMap result = Arguments.createMap();
                    result.putInt("errorCode", errorCode);
                    if (image != null) {
                        result.putString("imageBase64", ZegoUtils.bitmapToBase64(image));
                    }
                    promise.resolve(result);
                }
            });
        } else {
            WritableMap result = Arguments.createMap();
            result.putInt("errorCode", -1);
            promise.resolve(result);
        }
    }

    @ReactMethod
    public void mediaPlayerSetNetWorkResourceMaxCache(int index, int time, int size, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            mediaPlayer.setNetWorkResourceMaxCache(time, size);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerGetNetWorkResourceCache(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            ZegoNetWorkResourceCache cache = mediaPlayer.getNetWorkResourceCache();
            WritableMap map = Arguments.createMap();
            map.putInt("time", cache.time);
            map.putInt("size", cache.size);
            promise.resolve(map);
        } else {
            promise.resolve(null);
        }
    }

    @ReactMethod
    public void mediaPlayerSetNetWorkBufferThreshold(int index, int threshold, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            mediaPlayer.setNetWorkBufferThreshold(threshold);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerEnableSoundLevelMonitor(int index, boolean enable, int millisecond, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            mediaPlayer.enableSoundLevelMonitor(enable, millisecond);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerEnableFrequencySpectrumMonitor(int index, boolean enable, int millisecond, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            mediaPlayer.enableFrequencySpectrumMonitor(enable, millisecond);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerSetActiveAudioChannel(int index, int channel, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            mediaPlayer.setActiveAudioChannel(ZegoMediaPlayerAudioChannel.getZegoMediaPlayerAudioChannel(channel));
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerGetMediaInfo(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            ZegoMediaPlayerMediaInfo info = mediaPlayer.getMediaInfo();
            WritableMap mediaInfo = Arguments.createMap();
            mediaInfo.putInt("width", info.width);
            mediaInfo.putInt("height", info.height);
            mediaInfo.putInt("frameRate", info.frameRate);
            promise.resolve(mediaInfo);
        } else {
            promise.resolve(null);
        }
    }

    @ReactMethod
    public void mediaPlayerUpdatePosition(int index, ReadableArray position, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            float[] bytes = new float[position.size()];
            for (int i = 0; i < position.size(); i++) {
                bytes[i] = (byte) position.getDouble(i);
            }
            mediaPlayer.updatePosition(bytes);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerSetHttpHeader(int index, ReadableMap header, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            HashMap<String, String> headers = new HashMap<>();
            for (String key : header.toHashMap().keySet()) {
                headers.put(key, header.getString(key));
            }
            mediaPlayer.setHttpHeader(headers);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerSetPlayMediaStreamType(int index, int streamType, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            mediaPlayer.setPlayMediaStreamType(ZegoMediaStreamType.getZegoMediaStreamType(streamType));
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerClearView(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            mediaPlayer.clearView();
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerEnableLiveAudioEffect(int index, boolean enable, int mode, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            mediaPlayer.enableLiveAudioEffect(enable, ZegoLiveAudioEffectMode.getZegoLiveAudioEffectMode(mode));
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerEnableLocalCache(int index, boolean enable, String cacheDir, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            mediaPlayer.enableLocalCache(enable, cacheDir);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void mediaPlayerGetPlaybackStatistics(int index, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            ZegoMediaPlayerStatisticsInfo info = mediaPlayer.getPlaybackStatistics();
            WritableMap statisticsInfo = Arguments.createMap();
            statisticsInfo.putDouble("videoSourceFps", info.videoSourceFps);
            statisticsInfo.putDouble("videoDecodeFps", info.videoDecodeFps);
            statisticsInfo.putDouble("videoRenderFps", info.videoRenderFps);
            statisticsInfo.putDouble("audioSourceFps", info.audioSourceFps);
            statisticsInfo.putDouble("audioDecodeFps", info.audioDecodeFps);
            statisticsInfo.putDouble("audioRenderFps", info.audioRenderFps);
            promise.resolve(statisticsInfo);
        } else {
            promise.resolve(null);
        }
    }

    @ReactMethod
    public void mediaPlayerEnableViewMirror(int index, boolean enable, Promise promise) {
        ZegoMediaPlayer mediaPlayer = this.mediaPlayerMap.get(index);

        if (mediaPlayer != null) {
            mediaPlayer.enableViewMirror(enable);
        }

        promise.resolve(null);
    }

    /// Copyrighted Music
    @ReactMethod
    public void createCopyrightedMusic(Promise promise) {
        if (this.copyrightedMusic == null) {
            this.copyrightedMusic = ZegoExpressEngine.getEngine().createCopyrightedMusic();
            this.copyrightedMusic.setEventHandler(new IZegoCopyrightedMusicEventHandler() {
                @Override
                public void onDownloadProgressUpdate(ZegoCopyrightedMusic copyrightedMusic, String resourceID, float progressRate) {
                    super.onDownloadProgressUpdate(copyrightedMusic, resourceID, progressRate);

                    WritableMap args = getMediaPlayerCallbackArgs(0, resourceID, (double)progressRate);
                    sendEvent("CopyrightedMusic" + "downloadProgressUpdate", args);
                }

                @Override
                public void onCurrentPitchValueUpdate(ZegoCopyrightedMusic copyrightedMusic, String resourceID, int currentDuration, int pitchValue) {
                    super.onCurrentPitchValueUpdate(copyrightedMusic, resourceID, currentDuration, pitchValue);

                    WritableMap args = getMediaPlayerCallbackArgs(0, resourceID, currentDuration, pitchValue);
                    sendEvent("CopyrightedMusic" + "currentPitchValueUpdate", args);
                }
            });
        }
        if (this.copyrightedMusic != null) {
            promise.resolve(1);
        } else {
            promise.resolve(-1);
        }
    }

    @ReactMethod
    public void destroyCopyrightedMusic(Promise promise) {
        if (this.copyrightedMusic != null) {
            ZegoExpressEngine.getEngine().destroyCopyrightedMusic(this.copyrightedMusic);
            this.copyrightedMusic = null;
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void copyrightedMusicInit(ReadableMap config, Promise promise) {
        if (this.copyrightedMusic != null) {
            ZegoCopyrightedMusicConfig musicConfig = new ZegoCopyrightedMusicConfig();
            ReadableMap user = config.getMap("user");
            if (user != null) {
                musicConfig.user = new ZegoUser(user.getString("userID"), user.getString("userName"));
                this.copyrightedMusic.initCopyrightedMusic(musicConfig, new IZegoCopyrightedMusicInitCallback() {
                    @Override
                    public void onInitCallback(int errorCode) {
                        WritableMap result = Arguments.createMap();
                        result.putInt("errorCode", errorCode);
                        promise.resolve(result);
                    }
                });
            } else {
                WritableMap result = Arguments.createMap();
                result.putInt("errorCode", -1);
                promise.resolve(result);
            }
        } else {
            WritableMap result = Arguments.createMap();
            result.putInt("errorCode", -1);
            promise.resolve(result);
        }
    }

    @ReactMethod
    public void copyrightedMusicGetCacheSize(Promise promise) {
        if (this.copyrightedMusic != null) {
            double cacheSize = this.copyrightedMusic.getCacheSize();
            promise.resolve(cacheSize);
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void copyrightedMusicClearCache(Promise promise) {
        if (this.copyrightedMusic != null) {
            this.copyrightedMusic.clearCache();
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void copyrightedMusicSendExtendedRequest(String resourceID, String request, Promise promise) {
        if (this.copyrightedMusic != null) {
            this.copyrightedMusic.sendExtendedRequest(resourceID, request, new IZegoCopyrightedMusicSendExtendedRequestCallback() {
                @Override
                public void onSendExtendedRequestCallback(int errorCode, String command, String result) {
                    WritableMap resultMap = Arguments.createMap();
                    resultMap.putInt("errorCode", errorCode);
                    resultMap.putString("command", command);
                    resultMap.putString("result", result);
                    promise.resolve(resultMap);
                }
            });
        } else {
            WritableMap result = Arguments.createMap();
            result.putInt("errorCode", -1);
            promise.resolve(result);
        }
    }

    @ReactMethod
    public void copyrightedMusicGetLrcLyric(ReadableMap config, Promise promise) {
        if (this.copyrightedMusic != null) {
            ZegoCopyrightedMusicGetLyricConfig lyricConfig = new ZegoCopyrightedMusicGetLyricConfig();
            lyricConfig.songID = config.getString("songID");
            lyricConfig.vendorID = config.getInt("vendorID");
            this.copyrightedMusic.getLrcLyric(lyricConfig, new IZegoCopyrightedMusicGetLrcLyricCallback() {
                @Override
                public void onGetLrcLyricCallback(int errorCode, String lyrics) {
                    WritableMap result = Arguments.createMap();
                    result.putInt("errorCode", errorCode);
                    result.putString("lyrics", lyrics);
                    promise.resolve(result);
                }
            });
        } else {
            WritableMap result = Arguments.createMap();
            result.putInt("errorCode", -1);
            promise.resolve(result);
        }
    }

    @ReactMethod
    public void copyrightedMusicGetKrcLyricByToken(String krcToken, Promise promise) {
        if (this.copyrightedMusic != null) {
            this.copyrightedMusic.getKrcLyricByToken(krcToken, new IZegoCopyrightedMusicGetKrcLyricByTokenCallback() {
                @Override
                public void onGetKrcLyricByTokenCallback(int errorCode, String lyric) {
                    WritableMap result = Arguments.createMap();
                    result.putInt("errorCode", errorCode);
                    result.putString("lyrics", lyric);
                    promise.resolve(result);
                }
            });
        } else {
            WritableMap result = Arguments.createMap();
            result.putInt("errorCode", -1);
            promise.resolve(result);
        }
    }

    @ReactMethod
    public void copyrightedMusicRequestResource(ReadableMap config, Promise promise) {
        if (this.copyrightedMusic != null) {
            ZegoCopyrightedMusicRequestConfigV2 requestConfig = new ZegoCopyrightedMusicRequestConfigV2();
            requestConfig.songID = config.getString("songID");
            requestConfig.vendorID = config.getInt("vendorID");
            requestConfig.mode = config.getInt("mode");
            requestConfig.roomID = config.getString("roomID");
            requestConfig.masterID = config.getString("masterID");
            requestConfig.sceneID = config.getInt("sceneID");
            requestConfig.resourceType = config.getInt("resourceType");
            this.copyrightedMusic.requestResource(requestConfig, new IZegoCopyrightedMusicRequestResourceCallback() {
                @Override
                public void onRequestResourceCallback(int errorCode, String resource) {
                    WritableMap result = Arguments.createMap();
                    result.putInt("errorCode", errorCode);
                    result.putString("resource", resource);
                    promise.resolve(result);
                }
            });
        } else {
            WritableMap result = Arguments.createMap();
            result.putInt("errorCode", -1);
            promise.resolve(result);
        }
    }

    @ReactMethod
    public void copyrightedMusicGetSharedResource(ReadableMap config, Promise promise) {
        if (this.copyrightedMusic != null) {
            ZegoCopyrightedMusicGetSharedConfigV2 sharedConfig = new ZegoCopyrightedMusicGetSharedConfigV2();
            sharedConfig.songID = config.getString("songID");
            sharedConfig.vendorID = config.getInt("vendorID");
            sharedConfig.roomID = config.getString("roomID");
            sharedConfig.resourceType = config.getInt("resourceType");
            this.copyrightedMusic.getSharedResource(sharedConfig, new IZegoCopyrightedMusicGetSharedResourceCallback() {
                @Override
                public void onGetSharedResourceCallback(int errorCode, String resource) {
                    WritableMap result = Arguments.createMap();
                    result.putInt("errorCode", errorCode);
                    result.putString("resource", resource);
                    promise.resolve(result);
                }
            });
        } else {
            WritableMap result = Arguments.createMap();
            result.putInt("errorCode", -1);
            promise.resolve(result);
        }
    }

    @ReactMethod
    public void copyrightedMusicDownload(String resourceID, Promise promise) {
        if (this.copyrightedMusic != null) {
            this.copyrightedMusic.download(resourceID, new IZegoCopyrightedMusicDownloadCallback() {
                @Override
                public void onDownloadCallback(int errorCode) {
                    WritableMap result = Arguments.createMap();
                    result.putInt("errorCode", errorCode);
                    promise.resolve(result);
                }
            });
        } else {
            WritableMap result = Arguments.createMap();
            result.putInt("errorCode", -1);
            promise.resolve(result);
        }
    }

    @ReactMethod
    public void copyrightedMusicCancelDownload(String resourceID, Promise promise) {
        if (this.copyrightedMusic != null) {
            this.copyrightedMusic.cancelDownload(resourceID);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void copyrightedMusicGetDuration(String resourceID, Promise promise) {
        if (this.copyrightedMusic != null) {
            double duration = this.copyrightedMusic.getDuration(resourceID);
            promise.resolve(duration);
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void copyrightedMusicQueryCache(ReadableMap config, Promise promise) {
        if (this.copyrightedMusic != null) {
            ZegoCopyrightedMusicQueryCacheConfigV2 cacheConfig = new ZegoCopyrightedMusicQueryCacheConfigV2();
            cacheConfig.songID = config.getString("songID");
            cacheConfig.vendorID = config.getInt("vendorID");
            cacheConfig.resourceQualityType = config.getInt("resourceQualityType");
            cacheConfig.resourceType = config.getInt("resourceType");
            promise.resolve(this.copyrightedMusic.queryCache(cacheConfig));
        } else {
            promise.resolve(false);
        }
    }

    @ReactMethod
    public void copyrightedMusicSetScoringLevel(int level, Promise promise) {
        if (this.copyrightedMusic != null) {
            this.copyrightedMusic.setScoringLevel(level);
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void copyrightedMusicStartScore(String resourceID, int pitchValueInterval, Promise promise) {
        if (this.copyrightedMusic != null) {
            promise.resolve(this.copyrightedMusic.startScore(resourceID, pitchValueInterval));
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void copyrightedMusicPauseScore(String resourceID, Promise promise) {
        if (this.copyrightedMusic != null) {
            promise.resolve(this.copyrightedMusic.pauseScore(resourceID));
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void copyrightedMusicResumeScore(String resourceID, Promise promise) {
        if (this.copyrightedMusic != null) {
            promise.resolve(this.copyrightedMusic.resumeScore(resourceID));
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void copyrightedMusicStopScore(String resourceID, Promise promise) {
        if (this.copyrightedMusic != null) {
            promise.resolve(this.copyrightedMusic.stopScore(resourceID));
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void copyrightedMusicResetScore(String resourceID, Promise promise) {
        if (this.copyrightedMusic != null) {
            promise.resolve(this.copyrightedMusic.resetScore(resourceID));
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void copyrightedMusicGetPreviousScore(String resourceID, Promise promise) {
        if (this.copyrightedMusic != null) {
            promise.resolve(this.copyrightedMusic.getPreviousScore(resourceID));
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void copyrightedMusicGetAverageScore(String resourceID, Promise promise) {
        if (this.copyrightedMusic != null) {
            promise.resolve(this.copyrightedMusic.getAverageScore(resourceID));
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void copyrightedMusicGetTotalScore(String resourceID, Promise promise) {
        if (this.copyrightedMusic != null) {
            promise.resolve(this.copyrightedMusic.getTotalScore(resourceID));
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void copyrightedMusicGetFullScore(String resourceID, Promise promise) {
        if (this.copyrightedMusic != null) {
            promise.resolve(this.copyrightedMusic.getFullScore(resourceID));
        } else {
            promise.resolve(0);
        }
    }

    @ReactMethod
    public void copyrightedMusicGetStandardPitch(String resourceID, Promise promise) {
        if (this.copyrightedMusic != null) {
            this.copyrightedMusic.getStandardPitch(resourceID, new IZegoCopyrightedMusicGetStandardPitchCallback() {
                @Override
                public void onGetStandardPitchCallback(int errorCode, String pitch) {
                    WritableMap result = Arguments.createMap();
                    result.putInt("errorCode", errorCode);
                    result.putString("pitch", pitch);
                    promise.resolve(result);
                }
            });
        } else {
            WritableMap result = Arguments.createMap();
            result.putInt("errorCode", -1);
            result.putString("pitch", "");
            promise.resolve(result);
        }
    }

    @ReactMethod
    public void copyrightedMusicGetCurrentPitch(String resourceID, Promise promise) {
        if (this.copyrightedMusic != null) {
            promise.resolve(this.copyrightedMusic.getCurrentPitch(resourceID));
        } else {
            promise.resolve(0);
        }
    }

    /// Audio Effect
    @ReactMethod
    public void createAudioEffectPlayer(Promise promise) {
        if (this.audioEffectPlayerMap == null) {
            this.audioEffectPlayerMap = new HashMap<>();
        }

        ZegoAudioEffectPlayer audioEffectPlayer = ZegoExpressEngine.getEngine().createAudioEffectPlayer();

        if (audioEffectPlayer != null) {
            final int index = audioEffectPlayer.getIndex();

            audioEffectPlayer.setEventHandler(new IZegoAudioEffectPlayerEventHandler() {
                @Override
                public void onAudioEffectPlayStateUpdate(ZegoAudioEffectPlayer audioEffectPlayer, int audioEffectID, ZegoAudioEffectPlayState state, int errorCode) {
                    super.onAudioEffectPlayStateUpdate(audioEffectPlayer, audioEffectID, state, errorCode);

                    WritableMap args = getMediaPlayerCallbackArgs(audioEffectPlayer.getIndex(), audioEffectID, state.value(), errorCode);
                    sendEvent("audioEffectPlayerStateUpdate", args);
                }
            });
            this.audioEffectPlayerMap.put(index, audioEffectPlayer);

            promise.resolve(index);
        } else {
            promise.resolve(-1);
        }
    }

    @ReactMethod
    public void destroyAudioEffectPlayer(int index, Promise promise) {
        if (this.audioEffectPlayerMap != null) {
            ZegoAudioEffectPlayer audioEffectPlayer  = this.audioEffectPlayerMap.get(index);

            if (audioEffectPlayer != null) {
                ZegoExpressEngine.getEngine().destroyAudioEffectPlayer(audioEffectPlayer);
            }
            this.audioEffectPlayerMap.remove(index);
        }

        promise.resolve(null);
    }

    @ReactMethod
    public void audioEffectPlayerLoadResource(int index, int audioEffectID, String path, final Promise promise) {
        ZegoAudioEffectPlayer audioEffectPlayer = this.audioEffectPlayerMap.get(index);

        if (audioEffectPlayer != null) {
            audioEffectPlayer.loadResource(audioEffectID, path, new IZegoAudioEffectPlayerLoadResourceCallback() {
                @Override
                public void onLoadResourceCallback(int errorCode) {
                    WritableMap map = Arguments.createMap();
                    map.putInt("errorCode", errorCode);
                    promise.resolve(map);
                }
            });
        } else {
            promise.resolve(null);
        }
    }

    @ReactMethod
    public void audioEffectPlayerUnloadResource(int index, int audioEffectID, Promise promise) {
        ZegoAudioEffectPlayer audioEffectPlayer = this.audioEffectPlayerMap.get(index);

        if (audioEffectPlayer != null) {
            audioEffectPlayer.unloadResource(audioEffectID);
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void audioEffectPlayerStart(int index, int audioEffectID, String path, ReadableMap config, Promise promise) {
        ZegoAudioEffectPlayer audioEffectPlayer = this.audioEffectPlayerMap.get(index);
        if (audioEffectPlayer != null) {
            ZegoAudioEffectPlayConfig audioEffectPlayConfig = null;
            if (config != null) {
                audioEffectPlayConfig = new ZegoAudioEffectPlayConfig();
                audioEffectPlayConfig.playCount = config.getInt("playCount");
                audioEffectPlayConfig.isPublishOut = config.getBoolean("isPublishOut");
            }
            audioEffectPlayer.start(audioEffectID, path, audioEffectPlayConfig);
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void audioEffectPlayerStop(int index, int audioEffectID, Promise promise) {
        ZegoAudioEffectPlayer audioEffectPlayer = this.audioEffectPlayerMap.get(index);

        if (audioEffectPlayer != null) {
            audioEffectPlayer.stop(audioEffectID);
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void audioEffectPlayerPause(int index, int audioEffectID, Promise promise) {
        ZegoAudioEffectPlayer audioEffectPlayer = this.audioEffectPlayerMap.get(index);

        if (audioEffectPlayer != null) {
            audioEffectPlayer.pause(audioEffectID);
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void audioEffectPlayerResume(int index, int audioEffect, Promise promise) {
        ZegoAudioEffectPlayer audioEffectPlayer = this.audioEffectPlayerMap.get(index);

        if (audioEffectPlayer != null) {
            audioEffectPlayer.resume(audioEffect);
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void audioEffectPlayerStopAll(int index, Promise promise) {
        ZegoAudioEffectPlayer audioEffectPlayer = this.audioEffectPlayerMap.get(index);

        if (audioEffectPlayer != null) {
            audioEffectPlayer.stopAll();
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void audioEffectPlayerPauseAll(int index, Promise promise) {
        ZegoAudioEffectPlayer audioEffectPlayer = this.audioEffectPlayerMap.get(index);

        if (audioEffectPlayer != null) {
            audioEffectPlayer.pauseAll();
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void audioEffectPlayerResumeAll(int index, Promise promise) {
        ZegoAudioEffectPlayer audioEffectPlayer = this.audioEffectPlayerMap.get(index);

        if (audioEffectPlayer != null) {
            audioEffectPlayer.resumeAll();
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void audioEffectPlayerSeekTo(int index, int audioEffectID, double millisecond, final Promise promise) {
        ZegoAudioEffectPlayer audioEffectPlayer = this.audioEffectPlayerMap.get(index);

        if (audioEffectPlayer != null) {
            audioEffectPlayer.seekTo(audioEffectID, (long)millisecond, new IZegoAudioEffectPlayerSeekToCallback() {
                @Override
                public void onSeekToCallback(int errorCode) {
                    WritableMap map = Arguments.createMap();
                    map.putInt("errorCode", errorCode);
                    promise.resolve(map);
                }
            });
        } else {
            promise.resolve(null);
        }
    }

    @ReactMethod
    public void audioEffectPlayerSetVolume(int index, int audioEffectID, int volume, Promise promise) {
        ZegoAudioEffectPlayer audioEffectPlayer = this.audioEffectPlayerMap.get(index);

        if (audioEffectPlayer != null) {
            audioEffectPlayer.setVolume(audioEffectID, volume);
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void audioEffectPlayerSetVolumeAll(int index, int volume, Promise promise) {
        ZegoAudioEffectPlayer audioEffectPlayer = this.audioEffectPlayerMap.get(index);

        if (audioEffectPlayer != null) {
            audioEffectPlayer.setVolumeAll(volume);
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void audioEffectPlayerGetTotalDuration(int index, int audioEffectID, Promise promise) {
        ZegoAudioEffectPlayer audioEffectPlayer = this.audioEffectPlayerMap.get(index);

        if (audioEffectPlayer != null) {
            double duration = audioEffectPlayer.getTotalDuration(audioEffectID);
            promise.resolve(duration);
        } else {
            promise.resolve(null);
        }
    }

    @ReactMethod
    public void audioEffectPlayerGetCurrentProgress(int index, int audioEffectID, Promise promise) {
        ZegoAudioEffectPlayer audioEffectPlayer = this.audioEffectPlayerMap.get(index);

        if (audioEffectPlayer != null) {
            double progress = audioEffectPlayer.getCurrentProgress(audioEffectID);
            promise.resolve(progress);
        } else {
            promise.resolve(null);
        }
    }

    @ReactMethod
    public void createRealTimeSequentialDataManager(String roomID, Promise promise) {
        if (this.realTimeSequentialDataManagerHashMap == null) {
            this.realTimeSequentialDataManagerHashMap = new HashMap<>();
        }

        ZegoRealTimeSequentialDataManager dataManager = ZegoExpressEngine.getEngine().createRealTimeSequentialDataManager(roomID);

        if (dataManager != null) {
            final int index = dataManager.getIndex();

            dataManager.setEventHandler(new IZegoRealTimeSequentialDataEventHandler() {
                @Override
                public void onReceiveRealTimeSequentialData(ZegoRealTimeSequentialDataManager manager, byte[] data, String streamID) {
                    super.onReceiveRealTimeSequentialData(manager, data, streamID);
                    WritableArray byteArray = Arguments.createArray();
                    for (byte datum : data) {
                        byteArray.pushInt(datum);
                    }
                    WritableMap args = getCallbackArgs(manager.getIndex(), byteArray, streamID);
                    sendEvent("receiveRealTimeSequentialData", args);
                }
            });
            this.realTimeSequentialDataManagerHashMap.put(index, dataManager);

            promise.resolve(index);
        } else {
            promise.resolve(-1);
        }
    }

    @ReactMethod
    public void destroyRealTimeSequentialDataManager(int index, Promise promise) {
        if (this.realTimeSequentialDataManagerHashMap != null) {
            ZegoRealTimeSequentialDataManager dataManager = this.realTimeSequentialDataManagerHashMap.get(index);
            if (dataManager != null) {
                dataManager.setEventHandler(null);
                ZegoExpressEngine.getEngine().destroyRealTimeSequentialDataManager(dataManager);
            }
            this.realTimeSequentialDataManagerHashMap.remove(index);
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void realTimeSequentialDataManagerStartBroadcasting(int index, String streamID, Promise promise) {
        ZegoRealTimeSequentialDataManager dataManager = this.realTimeSequentialDataManagerHashMap.get(index);
        if (dataManager != null) {
            dataManager.startBroadcasting(streamID);
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void realTimeSequentialDataManagerStopBroadcasting(int index, String streamID, Promise promise) {
        ZegoRealTimeSequentialDataManager dataManager = this.realTimeSequentialDataManagerHashMap.get(index);
        if (dataManager != null) {
            dataManager.stopBroadcasting(streamID);
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void realTimeSequentialDataManagerSendRealTimeSequentialData(int index, ReadableArray data, String streamID, Promise promise) {
        ZegoRealTimeSequentialDataManager dataManager = this.realTimeSequentialDataManagerHashMap.get(index);
        if (dataManager != null) {
            byte[] bytes = new byte[data.size()];
            for (int i = 0; i < data.size(); i++) {
                bytes[i] = (byte) data.getInt(i);
            }
            dataManager.sendRealTimeSequentialData(bytes, streamID, new IZegoRealTimeSequentialDataSentCallback() {
                @Override
                public void onRealTimeSequentialDataSent(int errorCode) {
                    WritableMap map = Arguments.createMap();
                    map.putInt("errorCode", errorCode);
                    promise.resolve(map);
                }
            });
        } else {
            promise.resolve(null);
        }
    }

    @ReactMethod
    public void realTimeSequentialDataManagerStartSubscribing(int index, String streamID, Promise promise) {
        ZegoRealTimeSequentialDataManager dataManager = this.realTimeSequentialDataManagerHashMap.get(index);
        if (dataManager != null) {
            dataManager.startSubscribing(streamID);
        }
        promise.resolve(null);
    }

    @ReactMethod
    public void realTimeSequentialDataManagerStopSubscribing(int index, String streamID, Promise promise) {
        ZegoRealTimeSequentialDataManager dataManager = this.realTimeSequentialDataManagerHashMap.get(index);
        if (dataManager != null) {
            dataManager.stopSubscribing(streamID);
        }
        promise.resolve(null);
    }

    private final IZegoEventHandler zegoEventHandler = new IZegoEventHandler() {

        @Override
        public void onEngineStateUpdate(ZegoEngineState state) {
            super.onEngineStateUpdate(state);

            WritableMap args = getCallbackArgs(state.value());
            sendEvent("engineStateUpdate", args);
        }

        @Override
        public void onDebugError(int errorCode, String funcName, String info) {
            super.onDebugError(errorCode, funcName, info);

            WritableMap args = getCallbackArgs(errorCode, funcName, info);
            sendEvent("debugError", args);
        }

        @Override
        public void onRecvExperimentalAPI(String content) {
            super.onRecvExperimentalAPI(content);

            WritableMap args = getCallbackArgs(content);
            sendEvent("recvExperimentalAPI", args);
        }

        @Override
        public void onRoomStateChanged(String s, ZegoRoomStateChangedReason zegoRoomStateChangedReason, int i, JSONObject jsonObject) {
            super.onRoomStateChanged(s, zegoRoomStateChangedReason, i, jsonObject);

            WritableMap args = getCallbackArgs(s, zegoRoomStateChangedReason.value(), i, jsonObject.toString());
            sendEvent("roomStateChanged", args);
        }

        @Override
        public void onRoomStateUpdate(String roomID, ZegoRoomState state, int errorCode, JSONObject extendedData) {
            super.onRoomStateUpdate(roomID, state, errorCode, extendedData);

            WritableMap args = getCallbackArgs(roomID, state.value(), errorCode, extendedData.toString());
            sendEvent("roomStateUpdate", args);
        }

        @Override
        public void onRoomUserUpdate(String roomID, ZegoUpdateType updateType, ArrayList<ZegoUser> userList) {
            super.onRoomUserUpdate(roomID, updateType, userList);

            WritableArray userListArray = Arguments.createArray();
            for (ZegoUser user : userList) {
                WritableMap userMap = Arguments.createMap();
                userMap.putString("userID", user.userID);
                userMap.putString("userName", user.userName);
                userListArray.pushMap(userMap);
            }
            WritableMap args = getCallbackArgs(roomID, updateType.value(), userListArray);
            sendEvent("roomUserUpdate", args);
        }

        @Override
        public void onRoomOnlineUserCountUpdate(String roomID, int count) {
            super.onRoomOnlineUserCountUpdate(roomID, count);

            WritableMap args = getCallbackArgs(roomID, count);
            sendEvent("roomOnlineUserCountUpdate", args);
        }

        @Override
        public void onRoomTokenWillExpire(String roomID, int remainTimeInSecond) {
            super.onRoomTokenWillExpire(roomID, remainTimeInSecond);

            WritableMap args = getCallbackArgs(roomID, remainTimeInSecond);
            sendEvent("roomTokenWillExpire", args);
        }

        @Override
        public void onRoomExtraInfoUpdate(String s, ArrayList<ZegoRoomExtraInfo> arrayList) {
            super.onRoomExtraInfoUpdate(s, arrayList);

            WritableArray infoListArray = Arguments.createArray();
            for (ZegoRoomExtraInfo info : arrayList) {
                WritableMap infoMap = Arguments.createMap();
                infoMap.putString("key", info.key);
                infoMap.putString("value", info.value);
                infoMap.putDouble("updateTime", info.updateTime);

                WritableMap userMap = Arguments.createMap();
                userMap.putString("userID", info.updateUser.userID);
                userMap.putString("userName", info.updateUser.userName);
                infoMap.putMap("updateUser", userMap);

                infoListArray.pushMap(infoMap);
            }
            WritableMap args = getCallbackArgs(s, infoListArray);
            sendEvent("roomExtraInfoUpdate", args);
        }

        @Override
        public void onRoomStreamExtraInfoUpdate(String s, ArrayList<ZegoStream> streamList) {
            super.onRoomStreamExtraInfoUpdate(s, streamList);

            WritableArray streamListArray = Arguments.createArray();
            for(ZegoStream stream : streamList) {
                WritableMap streamMap = Arguments.createMap();
                streamMap.putString("streamID", stream.streamID);
                streamMap.putString("extraInfo", stream.extraInfo);

                WritableMap userMap = Arguments.createMap();
                userMap.putString("userID", stream.user.userID);
                userMap.putString("userName", stream.user.userName);

                streamMap.putMap("user", userMap);

                streamListArray.pushMap(streamMap);
            }
            WritableMap args = getCallbackArgs(s, streamListArray);
            sendEvent("roomStreamExtraInfoUpdate", args);
        }

        @Override
        public void onRoomStreamUpdate(String roomID, ZegoUpdateType updateType, ArrayList<ZegoStream> streamList, JSONObject extendedData) {
            super.onRoomStreamUpdate(roomID, updateType, streamList, extendedData);

            WritableArray streamListArray = Arguments.createArray();
            for(ZegoStream stream : streamList) {
                WritableMap streamMap = Arguments.createMap();
                streamMap.putString("streamID", stream.streamID);
                streamMap.putString("extraInfo", stream.extraInfo);

                WritableMap userMap = Arguments.createMap();
                userMap.putString("userID", stream.user.userID);
                userMap.putString("userName", stream.user.userName);

                streamMap.putMap("user", userMap);

                streamListArray.pushMap(streamMap);
            }
            WritableMap args = getCallbackArgs(roomID, updateType.value(), streamListArray);
            sendEvent("roomStreamUpdate", args);
        }

        @Override
        public void onPublisherStateUpdate(String streamID, ZegoPublisherState state, int errorCode, JSONObject extendedData) {
            super.onPublisherStateUpdate(streamID, state, errorCode, extendedData);

            WritableMap args = getCallbackArgs(streamID, state.value(), errorCode, extendedData.toString());
            sendEvent("publisherStateUpdate", args);
        }

        @Override
        public void onPublisherQualityUpdate(String streamID, ZegoPublishStreamQuality quality) {
            super.onPublisherQualityUpdate(streamID, quality);

            WritableMap qualityMap = Arguments.createMap();
            qualityMap.putDouble("videoCaptureFPS", quality.videoCaptureFPS);
            qualityMap.putDouble("videoEncodeFPS", quality.videoEncodeFPS);
            qualityMap.putDouble("videoSendFPS", quality.videoSendFPS);
            qualityMap.putDouble("videoKBPS", quality.videoKBPS);
            qualityMap.putDouble("audioCaptureFPS", quality.audioCaptureFPS);
            qualityMap.putDouble("audioSendFPS", quality.audioSendFPS);
            qualityMap.putDouble("audioKBPS", quality.audioKBPS);
            qualityMap.putInt("rtt", quality.rtt);
            qualityMap.putDouble("packetLostRate", quality.packetLostRate);
            qualityMap.putInt("level", quality.level.value());
            qualityMap.putBoolean("isHardwareEncode", quality.isHardwareEncode);
            qualityMap.putDouble("totalSendBytes", quality.totalSendBytes);
            qualityMap.putDouble("audioSendBytes", quality.audioSendBytes);
            qualityMap.putDouble("videoSendBytes", quality.videoSendBytes);

            WritableMap args = getCallbackArgs(streamID, qualityMap);
            sendEvent("publisherQualityUpdate", args);
        }

        @Override
        public void onPublisherCapturedAudioFirstFrame() {
            super.onPublisherCapturedAudioFirstFrame();

            WritableMap args = getCallbackArgs();
            sendEvent("publisherCapturedAudioFirstFrame", args);
        }

        @Override
        public void onPublisherCapturedVideoFirstFrame(ZegoPublishChannel channel) {
            super.onPublisherCapturedVideoFirstFrame(channel);

            WritableMap args = getCallbackArgs(channel.value());
            sendEvent("publisherCapturedVideoFirstFrame", args);
        }

        @Override
        public void onPublisherRenderVideoFirstFrame(ZegoPublishChannel zegoPublishChannel) {
            super.onPublisherRenderVideoFirstFrame(zegoPublishChannel);

            WritableMap args = getCallbackArgs(zegoPublishChannel.value());
            sendEvent("publisherRenderVideoFirstFrame", args);
        }

        @Override
        public void onPublisherVideoSizeChanged(int width, int height, ZegoPublishChannel channel) {
            super.onPublisherVideoSizeChanged(width, height, channel);

            WritableMap args = getCallbackArgs(width, height, channel.value());
            sendEvent("publisherVideoSizeChanged", args);
        }

        @Override
        public void onPublisherRelayCDNStateUpdate(String s, ArrayList<ZegoStreamRelayCDNInfo> arrayList) {
            super.onPublisherRelayCDNStateUpdate(s, arrayList);

            WritableArray infoListArray = Arguments.createArray();
            for (ZegoStreamRelayCDNInfo info : arrayList) {
                WritableMap infoMap = Arguments.createMap();
                infoMap.putString("url", info.url);
                infoMap.putInt("state", info.state.value());
                infoMap.putInt("updateReason", info.updateReason.value());
                infoMap.putDouble("stateTime", info.stateTime);

                infoListArray.pushMap(infoMap);
            }
            WritableMap args = getCallbackArgs(s, infoListArray);
            sendEvent("publisherRelayCDNStateUpdate", args);
        }

        @Override
        public void onPublisherVideoEncoderChanged(ZegoVideoCodecID zegoVideoCodecID, ZegoVideoCodecID zegoVideoCodecID1, ZegoPublishChannel zegoPublishChannel) {
            super.onPublisherVideoEncoderChanged(zegoVideoCodecID, zegoVideoCodecID1, zegoPublishChannel);

            WritableMap args = getCallbackArgs(zegoVideoCodecID.value(), zegoVideoCodecID1.value(), zegoPublishChannel.value());
            sendEvent("publisherVideoEncoderChanged", args);
        }

        @Override
        public void onPublisherStreamEvent(ZegoStreamEvent zegoStreamEvent, String s, String s1) {
            super.onPublisherStreamEvent(zegoStreamEvent, s, s1);

            WritableMap args = getCallbackArgs(zegoStreamEvent.value(), s, s1);
            sendEvent("publisherStreamEvent", args);
        }

        @Override
        public void onVideoObjectSegmentationStateChanged(ZegoObjectSegmentationState state, ZegoPublishChannel channel, int errorCode) {
            super.onVideoObjectSegmentationStateChanged(state, channel, errorCode);

            WritableMap args = getCallbackArgs(state.value(), channel.value(), errorCode);
            sendEvent("videoObjectSegmentationStateChanged", args);
        }

        @Override
        public void onPlayerStateUpdate(String streamID, ZegoPlayerState state, int errorCode, JSONObject extendedData) {
            super.onPlayerStateUpdate(streamID, state, errorCode, extendedData);

            WritableMap args = getCallbackArgs(streamID, state.value(), errorCode, extendedData.toString());
            sendEvent("playerStateUpdate", args);
        }

        @Override
        public void onPlayerQualityUpdate(String streamID, ZegoPlayStreamQuality quality) {
            super.onPlayerQualityUpdate(streamID, quality);

            WritableMap qualityMap = Arguments.createMap();
            qualityMap.putDouble("videoRecvFPS", quality.videoRecvFPS);
            qualityMap.putDouble("videoDecodeFPS", quality.videoDecodeFPS);
            qualityMap.putDouble("videoRenderFPS", quality.videoRenderFPS);
            qualityMap.putDouble("videoKBPS", quality.videoKBPS);
            qualityMap.putDouble("audioRecvFPS", quality.audioRecvFPS);
            qualityMap.putDouble("audioDecodeFPS", quality.audioDecodeFPS);
            qualityMap.putDouble("audioRenderFPS", quality.audioRenderFPS);
            qualityMap.putDouble("audioKBPS", quality.audioKBPS);
            qualityMap.putInt("rtt", quality.rtt);
            qualityMap.putDouble("packetLostRate", quality.packetLostRate);
            qualityMap.putDouble("peerToPeerPacketLostRate", quality.peerToPeerPacketLostRate);
            qualityMap.putDouble("peerToPeerDelay", quality.peerToPeerDelay);
            qualityMap.putInt("level", quality.level.value());
            qualityMap.putInt("delay", quality.delay);
            qualityMap.putBoolean("isHardwareDecode", quality.isHardwareDecode);
            qualityMap.putDouble("totalRecvBytes", quality.totalRecvBytes);
            qualityMap.putDouble("audioRecvBytes", quality.audioRecvBytes);
            qualityMap.putDouble("videoRecvBytes", quality.videoRecvBytes);

            WritableMap args = getCallbackArgs(streamID, qualityMap);
            sendEvent("playerQualityUpdate", args);
        }

        @Override
        public void onPlayerMediaEvent(String streamID, ZegoPlayerMediaEvent event) {
            super.onPlayerMediaEvent(streamID, event);

            WritableMap args = getCallbackArgs(streamID, event);
            sendEvent("playerMediaEvent", args);
        }

        @Override
        public void onPlayerRecvAudioFirstFrame(String streamID) {
            super.onPlayerRecvAudioFirstFrame(streamID);

            WritableMap args = getCallbackArgs(streamID);
            sendEvent("playerRecvAudioFirstFrame", args);
        }

        @Override
        public void onPlayerRecvVideoFirstFrame(String streamID) {
            super.onPlayerRecvVideoFirstFrame(streamID);

            WritableMap args = getCallbackArgs(streamID);
            sendEvent("playerRecvVideoFirstFrame", args);
        }

        @Override
        public void onPlayerRecvSEI(String streamID, byte[] data) {
            super.onPlayerRecvSEI(streamID, data);

            WritableArray byteArray = Arguments.createArray();
            for (byte datum : data) {
                byteArray.pushInt(datum);
            }

            WritableMap args = getCallbackArgs(streamID, byteArray);
            sendEvent("playerRecvSEI", args);
        }

        @Override
        public void onPlayerRenderVideoFirstFrame(String streamID) {
            super.onPlayerRenderVideoFirstFrame(streamID);

            WritableMap args = getCallbackArgs(streamID);
            sendEvent("playerRenderVideoFirstFrame", args);
        }

        @Override
        public void onPlayerVideoSizeChanged(String streamID, int width, int height) {
            super.onPlayerVideoSizeChanged(streamID, width, height);

            WritableMap args = getCallbackArgs(streamID, width, height);
            sendEvent("playerVideoSizeChanged", args);
        }

        @Override
        public void onCapturedSoundLevelUpdate(float soundLevel) {
            super.onCapturedSoundLevelUpdate(soundLevel);

            WritableMap args = getCallbackArgs(soundLevel);
            sendEvent("capturedSoundLevelUpdate", args);
        }

        @Override
        public void onRemoteSoundLevelUpdate(HashMap<String, Float> soundLevels) {
            super.onRemoteSoundLevelUpdate(soundLevels);

            WritableMap soundLevelsMap = Arguments.createMap();
            for(Map.Entry<String, Float> entry: soundLevels.entrySet()) {
                soundLevelsMap.putDouble(entry.getKey(), entry.getValue());
            }
            WritableMap args = getCallbackArgs(soundLevelsMap);
            sendEvent("remoteSoundLevelUpdate", args);
        }


        @Override
        public void onLocalDeviceExceptionOccurred(ZegoDeviceExceptionType exceptionType, ZegoDeviceType deviceType, String deviceID) {
            super.onLocalDeviceExceptionOccurred(exceptionType, deviceType, deviceID);

            WritableMap args = getCallbackArgs(exceptionType.value(), deviceType.value(), deviceID);
            sendEvent("localDeviceExceptionOccurred", args);
        }

        @Override
        public void onRemoteCameraStateUpdate(String streamID, ZegoRemoteDeviceState state) {
            super.onRemoteCameraStateUpdate(streamID, state);

            WritableMap args = getCallbackArgs(streamID, state.value());
            sendEvent("remoteCameraStateUpdate", args);
        }

        @Override
        public void onRemoteMicStateUpdate(String streamID, ZegoRemoteDeviceState state) {
            super.onRemoteMicStateUpdate(streamID, state);

            WritableMap args = getCallbackArgs(streamID, state.value());
            sendEvent("remoteMicStateUpdate", args);
        }

        @Override
        public void onAudioRouteChange(ZegoAudioRoute zegoAudioRoute) {
            super.onAudioRouteChange(zegoAudioRoute);

            WritableMap args = getCallbackArgs(zegoAudioRoute.value());
            sendEvent("audioRouteChange", args);
        }

        @Override
        public void onIMRecvBroadcastMessage(String roomID, ArrayList<ZegoBroadcastMessageInfo> messageList){
            super.onIMRecvBroadcastMessage(roomID, messageList);

            WritableArray messageListArray = Arguments.createArray();
            for(ZegoBroadcastMessageInfo messageInfo : messageList) {
                WritableMap messageMap = Arguments.createMap();
                messageMap.putString("message", messageInfo.message);
                messageMap.putDouble("messageID", messageInfo.messageID);
                messageMap.putDouble("sendTime", messageInfo.sendTime);

                WritableMap userMap = Arguments.createMap();
                userMap.putString("userID", messageInfo.fromUser.userID);
                userMap.putString("userName", messageInfo.fromUser.userName);

                messageMap.putMap("fromUser", userMap);

                messageListArray.pushMap(messageMap);
            }
            WritableMap args = getCallbackArgs(roomID, messageListArray);
            sendEvent("IMRecvBroadcastMessage", args);
        }

        @Override
        public void onIMRecvBarrageMessage(String roomID, ArrayList<ZegoBarrageMessageInfo> messageList) {
            super.onIMRecvBarrageMessage(roomID, messageList);

            WritableArray messageListArray = Arguments.createArray();
            for(ZegoBarrageMessageInfo messageInfo : messageList) {
                WritableMap messageMap = Arguments.createMap();
                messageMap.putString("message", messageInfo.message);
                messageMap.putString("messageID", messageInfo.messageID);
                messageMap.putDouble("sendTime", messageInfo.sendTime);

                WritableMap userMap = Arguments.createMap();
                userMap.putString("userID", messageInfo.fromUser.userID);
                userMap.putString("userName", messageInfo.fromUser.userName);

                messageMap.putMap("fromUser", userMap);

                messageListArray.pushMap(messageMap);
            }
            WritableMap args = getCallbackArgs(roomID, messageListArray);
            sendEvent("IMRecvBarrageMessage", args);
        }

        @Override
        public void onIMRecvCustomCommand(String roomID, ZegoUser fromUser, String command) {
            super.onIMRecvCustomCommand(roomID, fromUser, command);

            WritableMap userMap = Arguments.createMap();
            userMap.putString("userID", fromUser.userID);
            userMap.putString("userName", fromUser.userName);

            WritableMap args = getCallbackArgs(roomID, userMap, command);
            sendEvent("IMRecvCustomCommand", args);
        }

        public void onMixerRelayCDNStateUpdate(String taskID, ArrayList<ZegoStreamRelayCDNInfo> infoList){
            super.onMixerRelayCDNStateUpdate(taskID, infoList);
            WritableArray infoListArray = Arguments.createArray();
            for(ZegoStreamRelayCDNInfo info : infoList) {
                WritableMap infoMap = Arguments.createMap();
                infoMap.putString("url", info.url);
                infoMap.putInt("state", info.state.value());
                infoMap.putInt("updateReason", info.updateReason.value());
                infoMap.putDouble("stateTime", info.stateTime);

                infoListArray.pushMap(infoMap);
            }
            WritableMap args = getCallbackArgs(taskID, infoListArray);
            sendEvent("mixerRelayCDNStateUpdate", args);

        }

        public void onMixerSoundLevelUpdate(HashMap<Integer, Float> soundLevels){
            super.onMixerSoundLevelUpdate(soundLevels);

            WritableMap soundLevelsMap = Arguments.createMap();
            for(Map.Entry<Integer, Float> entry: soundLevels.entrySet()) {
                // WritableMap 的 key 只能是 String 类型，到 JS 层会自动转换成 number
                soundLevelsMap.putDouble(entry.getKey().toString(), entry.getValue());
            }
            WritableMap args = getCallbackArgs(soundLevelsMap);
            sendEvent("mixerSoundLevelUpdate", args);
        }

        @Override
        public void onNetworkModeChanged(ZegoNetworkMode zegoNetworkMode) {
            super.onNetworkModeChanged(zegoNetworkMode);

            WritableMap args = getCallbackArgs(zegoNetworkMode.value());
            sendEvent("networkModeChanged", args);
        }

        @Override
        public void onNetworkSpeedTestError(int i, ZegoNetworkSpeedTestType zegoNetworkSpeedTestType) {
            super.onNetworkSpeedTestError(i, zegoNetworkSpeedTestType);

            WritableMap args = getCallbackArgs(i, zegoNetworkSpeedTestType.value());
            sendEvent("networkSpeedTestError", args);
        }

        @Override
        public void onNetworkSpeedTestQualityUpdate(ZegoNetworkSpeedTestQuality zegoNetworkSpeedTestQuality, ZegoNetworkSpeedTestType zegoNetworkSpeedTestType) {
            super.onNetworkSpeedTestQualityUpdate(zegoNetworkSpeedTestQuality, zegoNetworkSpeedTestType);

            WritableMap qualityMap = Arguments.createMap();
            qualityMap.putInt("quality", zegoNetworkSpeedTestQuality.quality.value());
            qualityMap.putInt("rtt", zegoNetworkSpeedTestQuality.rtt);
            qualityMap.putInt("connectCost", zegoNetworkSpeedTestQuality.connectCost);
            qualityMap.putDouble("packetLostRate", zegoNetworkSpeedTestQuality.packetLostRate);

            WritableMap args = getCallbackArgs(qualityMap, zegoNetworkSpeedTestType.value());
            sendEvent("networkSpeedTestQualityUpdate", args);
        }

        @Override
        public void onNetworkQuality(String userID, ZegoStreamQualityLevel upstreamQuality, ZegoStreamQualityLevel downstreamQuality) {
            super.onNetworkQuality(userID, upstreamQuality, downstreamQuality);

            WritableMap args = getCallbackArgs(userID, upstreamQuality.value(), downstreamQuality.value());
            sendEvent("networkQuality", args);
        }

        @Override
        public void onScreenCaptureStart() {
            super.onScreenCaptureStart();
            WritableMap args = getCallbackArgs();
            sendEvent("mobileScreenCaptureStart", args);
        }

        @Override
        public void onScreenCaptureExceptionOccurred(ZegoScreenCaptureExceptionType exceptionType) {
            super.onScreenCaptureExceptionOccurred(exceptionType);

            WritableMap args = getCallbackArgs(exceptionType.value());
            sendEvent("mobileScreenCaptureExceptionOccurred", args);
        }
    };

    private IZegoDataRecordEventHandler zegoDataRecordEventHandler = new IZegoDataRecordEventHandler() {
        @Override
        public void onCapturedDataRecordStateUpdate(ZegoDataRecordState state, int errorCode, ZegoDataRecordConfig config, ZegoPublishChannel channel) {
            super.onCapturedDataRecordStateUpdate(state, errorCode, config, channel);

            WritableMap configMap = Arguments.createMap();
            configMap.putString("filePath", config.filePath);
            configMap.putInt("recordType", config.recordType.value());

            WritableMap args = getCallbackArgs(state.value(), errorCode, configMap, channel.value());
            sendEvent("capturedDataRecordStateUpdate", args);
        }

        @Override
        public void onCapturedDataRecordProgressUpdate(ZegoDataRecordProgress progress, ZegoDataRecordConfig config, ZegoPublishChannel channel) {
            super.onCapturedDataRecordProgressUpdate(progress, config, channel);
            WritableMap configMap = Arguments.createMap();
            configMap.putString("filePath", config.filePath);
            configMap.putInt("recordType", config.recordType.value());

            WritableMap progressMap = Arguments.createMap();
            progressMap.putDouble("currentFileSize", progress.currentFileSize);
            progressMap.putDouble("duration", progress.duration);

            WritableMap args = getCallbackArgs(progressMap, configMap, channel.value());
            sendEvent("capturedDataRecordProgressUpdate", args);
        }
    };

    private IZegoApiCalledEventHandler zegoApiCalledEventHandler = new IZegoApiCalledEventHandler() {
        @Override
        public void onApiCalledResult(int i, String s, String s1) {
            super.onApiCalledResult(i, s, s1);

            WritableMap args = getCallbackArgs(i, s, s1);
            sendEvent("apiCalledResult", args);
        }
    };

    private ZegoCanvas createCanvas(View nativeView, ReadableMap view) {
        ZegoCanvas canvas = null;
        boolean alphaBlend = view.hasKey("alphaBlend") && view.getBoolean("alphaBlend");

        if(nativeView instanceof ZegoSurfaceView) {
            ZegoSurfaceView sv = (ZegoSurfaceView)nativeView;
            canvas = new ZegoCanvas(sv.getView());
            if (alphaBlend) {
                sv.setPixelFormat(PixelFormat.TRANSLUCENT);
                sv.setZOrderOnTop(true);
            }
        } else if(nativeView instanceof TextureView) {
            canvas = new ZegoCanvas(nativeView);
            if (alphaBlend) {
                ((TextureView) nativeView).setOpaque(false);
            }
        }

        if (canvas != null) {
            canvas.viewMode = ZegoViewMode.getZegoViewMode(view.getInt("viewMode"));
            canvas.backgroundColor = view.getInt("backgroundColor");
            canvas.alphaBlend = alphaBlend;
        }

        return canvas;
    }
}