UNPKG

zby-live-sdk

Version:

This is a live SDK for weclassroom.

3,467 lines 118 kB
/**
 * @File: Zego 音视频 SDK
 * @Author: xuyuanrui
 * @Email: raywhbxyr@163.com
 * @Date: 2018-08-08 10:14:41
 **/
import { dabanDataReport } from '../network/api.js';
import dataReport from '../network/dataReport.js';
import {
  getS,
  setS,
  compareS
} from '../util/sessionStorage.js';
import defaultApi from '../default';
import util from '../util/util';
import NOTICE from '../notice';
import { deviceListReport } from './device.js';
import zbysdk from '../zby-live-sdk.js';
import { DataStore, userAndDeviceStatusInfo, getBackgroundStatus } from '../util/statsStore.js';

// 扩展标识
const extensionId = 'zego_ext';
// 端提供的 API 入口
const EM = window.EM;
// 维护的一份当前正在使用的音频设备的 deviceId 的列表
let usingAudioDeviceId = {
  speaker: '',
  microphone: ''
};
// 维护的一份拉流的 streamId 与本地预览通道的映射表
let streamIdToPreviewId = {};
let previewIdToStreamId = {};
// const streamIdToBlobUrl = {};
// 拉流的播放通道初始化从 0 开始依次递增至44(共45路流),-1 为推流的本地视频预览
// const uiChnIndexs = new Array(50).fill(0).map((a, b) => b + 1).reverse();
const chnTotal = 48;
const uiChnIndexs = new Array(chnTotal).fill(2).map((a, b) => a + b).reverse();
// 初始化是否完成的标志,0 为未完成,1 为完成
let isInitFinished = 0;
// 设备检测初始化是否完成的标志,0 为未完成,1 为完成
let isDeviceCheckInitFinished = 0;
// 进入房间是否成功
let hasLoginChannel = 0;
// 是否已经推流
let hasPushFlow = 0;
// 是否logoutChannel成功
let isAVEngineStopped = 0;
// 上层透传的回调函数缓存
// let zegoCallback = null;
// 是否执行过 Zego 的 AddListener 的标志
let hasAddListener = false;
// 是否处于网络错误状态中的标志
let hasNetWrong = false;
// rtc小班课=0;rtc大班课=1
const classType = 0;
// 监听id
let EMListenerId = 0;

let teacherId = null;

//静音推流时,音量值是否上抛上层标志 false=没有声音不往上层通知/true=有声音有上层通知
let isNoticeMicVolumeZego = false;

// 本地录制错误信息
const recordCodeMsg = {
  0: '录制成功',
  1: '路径太长',
  2: '初始化avcontext失败',
  3: '打开文件失败',
  4: '写文件头失败',
  5: '开始录制',
  6: '停止录制',
  7: '剩余空间不够',
  8: '文件句柄异常',
  9: 'IO异常',
};

// 心跳
let heartBeatDataReportObj = {};
let heartBeatDataReportTimer = null;
let isFirstHeartBeatReport = false;
let streamIdRtcPlayerInfo = {};
let streamIdRtcPlayerInfo1 = {};
let currStreamIdRtcPlayerInfo;

let lecturerStreamId = null;
let lecturerUid = null;

const heartBeatItemCount = 15;
const statsStore = new DataStore(5500);

const PullResult = {
  PULL_STREAM_CODE_OK: 0, // 成功
  PULL_STREAM_CODE_STOP: 1, // 停止
  PULL_STREAM_CODE_FAIL: 12101000, // 调用拉流Api时发生异常 - 是个笼统的错误码
  PULL_STREAM_CODE_TIMEOUT: 12102003, // 拉流超时
  PULL_STREAM_CODE_RTC_SIGNAL_FAIL: 12200001, // 加入房间失败
  PULL_STREAM_CODE_RTC_MEDIA_FAIL: 12301011, // 媒体链路建立失败
  PULL_STREAM_CODE_NON_EXIST: 32001004, // 流不存在
};
const pullingStore = new DataStore(/*一小时过期时间*/60 * 60 * 1000);
let pullingStreamInterval = -1;

//记录底层推流回调的值
let dataZegoCapture = {};

//记录当前角色
let currentRole = '';
//记录当前课态类型:小组课=0/小班课=1
let currentClassMode = null;
//记录当前rtmp推流sdk类型
let currentGroupSdkType = '';
//主流ID
let localStreamId = '';
//屏幕流ID
let screenStreamId = '';
/**
 * @function 调用端提供的回调方法
 * @param name:String 回调方法名
 * @param args 回调参数
 * @return Promise | void
 */
const callMethod = (name, args) => {
  // EM 是寄宿于端的,浏览器中并不存在,为防止报错需要先进行能力检测
  if (EM) {
    return new Promise((resolve, reject) => {
      let noneCamera = (name === 'SetVideoDevice' && !args.pszDeviceID);
      let noneMicrophone = (name === 'SetAudioDevice' && args.deviceType === 0 && !args.pszDeviceID);
      let noneSpeaker = (name === 'SetAudioDevice' && args.deviceType === 1 && !args.pszDeviceID);
      if (noneCamera || noneMicrophone || noneSpeaker) {
        return resolve();
      }

      defaultApi.writeLog(`${name} Params: ${JSON.stringify(args)}`);
      EM.CallMethod(
        extensionId,
        name,
        JSON.stringify({ ...args, classType }),
        (code, msg) => {
          defaultApi.writeLog(`${name} Code: ${code} Message: ${msg}`);
          resolve({
            code,
            msg
          });
        }
      );
    });
  }
};

/**
 * @function 加载 Zego 扩展
 * @param extensionVersion:String 扩展版本号,必选
 * @return Promise | void
 */
export const loadZego = (extensionVersion) => {
  // EM 是寄宿于端的,浏览器中并不存在,为防止报错需要先进行能力检测
  if (EM) {
    return new Promise((resolve, reject) => {
      if (getS('zegoVersion')) {
        if (compareS('zegoVersion', extensionVersion)) {
          return resolve();
        }
      }
      EM.Load(
        extensionId,
        extensionVersion,
        false,
        (code, msg) => {
          defaultApi.writeLog(`loadZego Code: ${code}\nMessage: ${msg}`);
          if (code === 0) {
            setS('zegoVersion', extensionVersion);
          }
          resolve();
        }
      );
    });
  }
};

/**
 * @function 卸载 Zego 扩展
 * @return Promise | void
 */
export const unloadZego = () => {
  // EM 是寄宿于端的,浏览器中并不存在,为防止报错需要先进行能力检测
  if (EM) {
    return new Promise((resolve, reject) => {
      EM.UnLoad(
        extensionId,
        (code, msg) => {
          defaultApi.writeLog(`unloadZego Code: ${code}\nMessage: ${msg}`);
          resolve();
        }
      );
    });
  }
};

/**
 * @function 添加扩展监听机制
 * @param userId:Number 用户 id,必选
 * @param userName:String 用户名,必选
 * @param roomId:String 频道(房间) id,必选
 * @param nNetType:Number 网络类型,可选,默认为 1
 * @return void
 */
const addListener = (userId, userName, confId, nNetType, devices, role) => {
  // EM 是寄宿于端的,浏览器中并不存在,为防止报错需要先进行能力检测
  if (EM && !hasAddListener) {
    hasAddListener = true;
    EM.AddListener(extensionId, (event, data) => {
      if (data && data.indexOf(extensionId) > -1) {
        try {
          EMListenerId = JSON.parse(data)[extensionId];
          defaultApi.writeLog(`ZEGO::addListener-- EMListenerId: ${EMListenerId}`);
        } catch (error) { }
      }
      if (!event || !data) {
        return;
      }
      let _data = JSON.parse(data);
      // if (_data && _data.classType === 1) {
      //   return false;
      // }
      switch (event) {
      case 'OnInitSDK':
        // 只有 OnInitSDK 事件成功返回后才可以执行后续操作,比如 loginChannel
        var status = JSON.parse(data).nError;
        var nclassType = JSON.parse(data).nClassType;
        defaultApi.writeLog(`ZEGO::addListener-- OnInitSDK: ${status}--${nclassType}`);
        if (status === 0 && nclassType == 0) {
          if (userId === 'deviceCheckerInit') {
            deviceCheckerInitSDK();
          } else {
            afterInitSDK(userId, userName, confId, nNetType, devices, role);
          }
        }
        break;
      case 'OnLoginChannel':
        var status = JSON.parse(data).uiErrorCode;
        defaultApi.writeLog(`ZEGO::addListener-- OnLoginChannel: ${status}`);
        if (status === 0) {
          hasLoginChannel = 1;
        }
        break;
      case 'OnPublishStateUpdate':
        let res = JSON.parse(data);
        let keys = Object.keys(res);
        if (!window.zbyAVSDK_init_params || !window.zbyAVSDK_init_params.zego) {
          return;
        }
        if (keys.indexOf('eState') > -1) {
          switch (res.eState) {
          case 0:
            hasPushFlow = 1;
            window.zegoHasPushFlow = 1;
            try {
              let ReportArgs = {};
              if (_data.pszStreamID.split('_').length == 5) {
                ReportArgs.publish_rtc_streamid = _data.pszStreamID;
              }
              dataReport.publishResult({
                code: '0',
                ...ReportArgs
                // publish_streamid: window.zbyAVSDK_init_params.zego.streamId,
              });
              NOTICE.pushFlowSuccess({ code: 0, publish_streamid: _data.pszStreamID });
            } catch (e) { };
            if (window.zbyAVSDK_init_params.zego.isTurnPush) {
              singleRecord('start');
            }
            break;
          case 1:
            hasPushFlow = 0;
            window.zegoHasPushFlow = 0;
            break;
          // 推流重试
          case 6:
          case 7:
          case 105:
            // pushFlowExceptionHandler();
            try {
              dataReport.publishResult({
                code: res.eState,
                // publish_streamid: window.zbyAVSDK_init_params.zego.streamId,
              });
            } catch (e) { };
            break;
          case 10:
            if (!hasNetWrong) {
              hasNetWrong = true;
              defaultApi.writeLog('error', 'Network error');
              NOTICE.networkError({
                message: 'Network error'
              });
            }
            try {
              dataReport.publishResult({
                code: res.eState,
                // publish_streamid: window.zbyAVSDK_init_params.zego.streamId,
              });
            } catch (e) { };
            // pushFlowExceptionHandler();
            break;
          default:
            try {
              dataReport.publishResult({
                code: res.eState,
                // publish_streamid: window.zbyAVSDK_init_params.zego.streamId,
              });
            } catch (e) { };
            break;
          }
        }
        break;
      case 'OnPublishQualityUpdate':
        if (_data.hasOwnProperty('pq') && (_data.pszStreamID).split('_').length != 5) {
          // 主辅两路质量回调,丢包延时只上抛一次,过滤掉辅路数据
          _data.opt = 'quality';
          _data.type = 'mediaupqos';
          if (typeof _data.pq.kbps === 'number' && typeof _data.pq.fps === 'number') {
            NOTICE.pushQuality({
              video: {
                streamId: _data.pszStreamID,
                userId: util.getUidByStreamId(_data.pszStreamID),
                bitrate: Math.round(_data.pq.kbps),
                fps: Math.round(_data.pq.fps)
              }
            });
          }
          // console.log('推流延迟丢包zego',_data);
          NOTICE.pushLossAndDelay({
            userId: util.getUidByStreamId(_data.pszStreamID),
            delay: _data.pq.rtt,
            lostrate: _data.pq.pktLostRate
          });
          // heartBeatDataReportCalc(event, _data.pq, _data.pszStreamID);
          statsStore.setData(_data.pszStreamID, _data.pq);
        }
        break;
      case 'OnCaptureSoundLevelUpdate':
        if (isNoticeMicVolumeZego) {
          NOTICE.captureMicVolumeChanged({
            volume: Math.round(_data.pCaptureSoundLevel[0].soundLevel)
          });
        }
        // heartBeatDataReportObj.volume.push(Math.round(_data.pCaptureSoundLevel[0].soundLevel));
        heartBeatDataReportObj.volume = heartBeatDataReportObj.volume + Math.round(_data.pCaptureSoundLevel[0].soundLevel) + ',';
        break;
      case 'OnSoundLevelUpdate':
        _data.pSoundLevelList.forEach(item => {
          const cbData = {
            streamid: item.szStreamID,
            userId: util.getUidByStreamId(item.szStreamID),
            volume: Math.round(item.soundLevel)
          };
          NOTICE.playerVolumeChanged(cbData);
          NOTICE.volumeChange(cbData);
          // currStreamIdRtcPlayerInfo && currStreamIdRtcPlayerInfo.volume.push(Math.round(item.soundLevel));
          // streamIdRtcPlayerInfo[item.szStreamID] && streamIdRtcPlayerInfo[item.szStreamID].volume.push(Math.round(item.soundLevel));
          if (streamIdRtcPlayerInfo[item.szStreamID]) {
            streamIdRtcPlayerInfo[item.szStreamID].volume = streamIdRtcPlayerInfo[item.szStreamID].volume + Math.round(item.soundLevel) + ',';
          }
        });
        break;
      case 'OnPlayStateUpdate':
        if (_data.hasOwnProperty('eState')) {
          if (!window.zbyAVSDK_init_params || !window.zbyAVSDK_init_params.zego) {
            return;
          }
          let code = _data.eState;
          if (code === 3) {
            defaultApi.writeLog('error', `ZEGO::  OnPlayStateUpdate  eState: ${code}`);
          }
          let normalCode = [0, 1, 3, 1011];
          if (code === 10 && !hasNetWrong) {
            hasNetWrong = true;
            defaultApi.writeLog('error', 'Network error');
            NOTICE.networkError({
              message: 'Network error'
            });
            dabanDataReport({
              action: 'zby_live_sdk_player_network_error',
              uid: window.zbyAVSDK_init_params.zego.userId,
              streamid: _data.pszStreamID,
              teacherId: window.zbyAVSDK_init_params.zego.teacherId,
              errorcode: code,
              sdktype: 'zego'
            });
          }
          // 拉流重试
          if (normalCode.indexOf(code) === -1) {
            setTimeout(() => {
              // pullFlowExceptionHandler(_data.pszStreamID);
              NOTICE.playError({
                userId: util.getUidByStreamId(_data.pszStreamID),
                message: 'play error'
              });

              dabanDataReport({
                action: 'zby_live_sdk_player_play_error',
                uid: window.zbyAVSDK_init_params.zego.userId,
                streamid: _data.pszStreamID,
                teacherId: window.zbyAVSDK_init_params.zego.teacherId,
                errorcode: code,
                sdktype: 'zego'
              });
            }, 1000);
          };
          try {
            if (_data.pszStreamID.indexOf('ai') < 0) {
              dataReport.pullResult({
                code: code,
                // pull_uid: _data.pszStreamID.split('_').length == 5 ? _data.pszStreamID : util.getUidByStreamId(_data.pszStreamID),
                pull_uid: util.getUidByStreamId(_data.pszStreamID),
                pull_streamid: _data.pszStreamID
              });
              pullingStore.delete(_data.pszStreamID);
              NOTICE.pullFlowResult({ code: code, pull_streamid: _data.pszStreamID });
            }
            // dataReport.pullResult({
            //   code: code,
            //   pull_uid: util.getUidByStreamId(_data.pszStreamID),
            //   pull_streamid: _data.pszStreamID
            // });
          } catch (e) { };
        }
        break;
      case 'OnPlayQulityUpdate': // 拉流
        if (_data.hasOwnProperty('pq')) {
          // console.log('拉流回调',`${new Date().getMinutes()}:${new Date().getSeconds()}`,_data);
          _data.opt = 'quality';
          _data.type = 'mediadownqos';
          NOTICE.pullQuality({
            video: {
              streamId: _data.pszStreamID,
              userId: util.getUidByStreamId(_data.pszStreamID),
              bitrate: _data.pq.kbps,
              fps: _data.pq.fps,
            }
          });
          // console.log('拉流延迟丢包zego',_data);
          NOTICE.playLossAndDelay({
            userId: util.getUidByStreamId(_data.pszStreamID),
            delay: _data.pq.rtt,
            lostrate: _data.pq.pktLostRate
          });
          currStreamIdRtcPlayerInfo = streamIdRtcPlayerInfo[_data.pszStreamID];
          // heartBeatDataReportCalc(event, _data.pq, _data.pszStreamID);
          statsStore.setData(_data.pszStreamID, _data.pq);
        }
        break;
      case 'OnVideoDeviceStateChanged': // 视频设备状态变更
        // _data.state 0=添加设备 1=移除设备
        // console.log('hsghsghsg摄像头热插拔',_data);
        setVideoDevice({ deviceId: _data.state == 1 ? '' : _data.deviceInfo.szDeviceId, idx: 0, operationType: 'hotPlug', deviceState: _data.state, changedDeviceId: _data.deviceInfo.szDeviceId });
        // try {
        //   dataReport.deviceTest({
        //     device: '1',
        //     fore_state: _data.state == 0 ? 1 : 2 ,
        //     camera_name: _data.deviceInfo && _data.deviceInfo.szDeviceName,
        //   });
        // } catch (e){};
        break;
      case 'OnAudioDeviceStateChanged': // 音频设备状态变更
        // console.log('hsghsghsg扬声器热插拔1',_data);
        if (_data.deviceType === 0) {
          // _data.state 0=添加设备 1=移除设备
          setMicrophoneDevice({ deviceId: _data.state == 1 ? '' : _data.deviceInfo.szDeviceId, operationType: 'hotPlug', deviceState: _data.state, changedDeviceId: _data.deviceInfo.szDeviceId });
        }
        if (_data.deviceType === 1) {
          // console.log('hsghsghsg扬声器热插拔',_data);
          setSpeakerDevice({ deviceId: _data.state == 1 ? '' : _data.deviceInfo.szDeviceId, operationType: 'hotPlug', deviceState: _data.state, changedDeviceId: _data.deviceInfo.szDeviceId });
        }
        // try {
        //   dataReport.deviceTest({
        //     device: _data.deviceType === 0 ? '2' : '3',
        //     fore_state: _data.state == 0 ? 1 : 2 ,
        //     microphone_name: _data.deviceInfo && _data.deviceInfo.szDeviceName,
        //     speaker_name: _data.deviceInfo && _data.deviceInfo.szDeviceName
        //   });
        // } catch (e){};
        break;
      case 'OnAudioDefaultDeviceStateChanged': // 音频默认设备状态变更
        if (_data.deviceType === 0) {
          NOTICE.defaultMicChanged({ deviceId: _data.deviceId });
        } else if (_data.deviceType === 1) {
          NOTICE.defaultSpeakerChanged({ deviceId: _data.deviceId });
        }
        break;
      case 'OnDeviceError':
        //errorCode 无效设备ID (-2), 没有权限	(-3), 采集帧率为0	(-4), 设备被占用 (-5), 设备未插入	(-6), 需要重启系统 (-7), 媒体服务无法恢复	(-8), 设备被SIRI占用 (-9)
        let codemsg = {
          '-2': '无效设备ID',
          '-3': '没有权限',
          '-4': '采集帧率为0',
          '-5': '设备被占用',
          '-6': '设备未插入',
          '-7': '需要重启系统',
          '-8': '媒体服务无法恢复',
          '-9': '设备被SIRI占用'
        };
        defaultApi.writeLog(`avsdk ZEGO:: OnDeviceError ${_data.deviceName} ${_data.errorCode}`);
        dataReport.deviceError({ deviceName: _data.deviceName, errorCode: _data.errorCode, code: _data.errorCode });
        NOTICE.deviceError({code: _data.errorCode, msg: codemsg[_data.errorCode], deviceId: _data.deviceId, deviceName: _data.deviceName});
        // _data.deviceName === 'camera' ? setVideoDevice({deviceId: '', idx: 0, operationType: 'deviceError', deviceState: '', code: codemsg[_data.errorCode]}) : setMicrophoneDevice({deviceId: '', operationType: 'deviceError', deviceState: '', code: codemsg[_data.errorCode]});
        break;
      case 'OnAVKitEvent':
        // defaultApi.writeLog('avsdk ZEGO:: OnAVKitEvent');
        if (_data.event === 4 && hasNetWrong) {
          hasNetWrong = false;
          NOTICE.networkRecovery({
            message: 'Network recovery'
          });
        }
        break;
      case 'OnAVEngineStop':
        isAVEngineStopped = 1;
        defaultApi.writeLog('avsdk ZEGO:: listened AVEngineStop');
        break;
      case 'OnMixStream':
        defaultApi.writeLog('avsdk ZEGO:: listened mix_stream_response');
        break;
      case 'OnVideoSizeChanged':
        if (_data.nWidth > 0 && _data.nHeight > 0) {
          try {
            NOTICE.firstVideoSize({
              streamId: _data.pStreamID,
              // userId: _data.pszStreamID.split('_').length == 5 ? _data.pszStreamID : util.getUidByStreamId(_data.pszStreamID)
              userId: util.getUidByStreamId(_data.pStreamID)
            });
            dataReport.firstVideoSize({
              pull_streamid: _data.pStreamID,
              pull_uid: util.getUidByStreamIdDr(_data.pStreamID),
              code: '0'
            });
          } catch (e) { };
        } else {
          defaultApi.writeLog('avsdk ZEGO:: OnVideoSizeChanged - nWidth and nHeight is 0');
        }
        break;
      case 'OnRenderRemoteVideoFirstFrame':
        if (!window.zbyAVSDK_init_params || !window.zbyAVSDK_init_params.zego) {
          return;
        }
        // 大班数据上报
        if (window.zbyAVSDK_init_params.zego.role === 'student') {
          dabanDataReport({
            action: 'interact_play',
            teammate_streamid: _data.pszStreamID,
            uid: window.zbyAVSDK_init_params.zego.userId
          });
        }
        try {
          NOTICE.firstVideoSize({
            streamId: _data.pszStreamID,
            // userId: _data.pszStreamID.split('_').length == 5 ? _data.pszStreamID : util.getUidByStreamId(_data.pszStreamID)
            userId: util.getUidByStreamId(_data.pszStreamID)
          });
          dataReport.firstVideoSize({
            pull_streamid: _data.pszStreamID,
            pull_uid: util.getUidByStreamIdDr(_data.pszStreamID),
            code: '0'
          });
        } catch (e) { };
        defaultApi.writeLog(`ZEGO: pullStream success & haveFirstVideoSize  streamId: ${_data.pszStreamID}`);
        // try {
        //   dataReport.pullResult({
        //     status: 0,
        //     pull_uid: util.getUidByStreamId(_data.pszStreamID),
        //     pull_streamid: _data.pszStreamID
        //   });
        // } catch (e) {};
        break;
      case 'OnRecvRemoteAudioFirstFrame':
        try {
          NOTICE.firstAudioSize({
            streamId: _data.pszStreamID,
            userId: util.getUidByStreamId(_data.pszStreamID)
          });
          dataReport.firstAudioSize({
            pull_streamid: _data.pszStreamID,
            pull_uid: util.getUidByStreamIdDr(_data.pszStreamID),
            code: '0'
          });
        } catch (e) { };
        break;
      // 转推回调 0=转推停止 1=正在转推 2=正在重试
      case 'onRecvMediaSideInfo':
        defaultApi.writeLog(`avsdk ZEGO:: onRecvMediaSideInfo, ${JSON.stringify(_data)}`);
        NOTICE.seiReceived({ data: _data });
        break;
      case 'zego_recovery_start':
        defaultApi.writeLog(`avsdk ZEGO:: zego_recovery_start ${JSON.stringify(_data)}`);
        NOTICE.zegoRecoveryStart();
        break;
      case 'OnRelayCDNStateUpdate':
        if (_data.statesInfo && _data.statesInfo[0] && _data.statesInfo[0].state === 1) {
          NOTICE.recordResponse({
            streamId: _data.streamID,
            userId: util.getUidByStreamId(_data.streamID)
          });
        }
        //   try {
        //     dataReport.turnPushResult({
        //       errorcode:_data.statesInfo[0].state,
        //       case: 'turn push',
        //       streamid: _data.streamID
        //     })
        // } catch (e) {};
        break;
      case 'OnCaptureVideoFirstFrame':
        if (!window.zbyAVSDK_init_params || !window.zbyAVSDK_init_params.zego) {
          return;
        }
        // 大班数据上报
        dabanDataReport({
          action: 'open_camera',
          camera_status: 1,
          uid: window.zbyAVSDK_init_params.zego.userId
        });
        break;
      case 'OnKaDunEvent':
        if (_data.event === 8) {
          currStreamIdRtcPlayerInfo && (currStreamIdRtcPlayerInfo.video_ifg += _data.kaDunTime);
        }
        if (_data.event === 10) {
          currStreamIdRtcPlayerInfo && (currStreamIdRtcPlayerInfo.audio_ifg += _data.kaDunTime);
        }
        break;
      case 'OnMediaRecord':
        NOTICE.recordStateUpdate(_data);
        if (_data.errorCode === 0) {
          NOTICE.recordFinished({path: _data.storagePath, idx: _data.channelIndex});
        } else if (_data.errorCode === 5) {
          NOTICE.recordBegin({idx: _data.channelIndex});
        } else if (_data.errorCode === 6) {
          NOTICE.recordEnd({idx: _data.channelIndex});
        } else {
          NOTICE.recordError({code: _data.errorCode, idx: _data.channelIndex, msg: recordCodeMsg[_data.errorCode]});
        }
        break;
      case 'onBeautifyEvent':
        if (_data.code === 0) {
          defaultApi.writeLog(`avsdk ZEGO:: onBeautifyEvent success ${JSON.stringify(_data)}`);
        } else {
          const message = [
            '',
            'License错误',
            '渲染错误',
            '参数错误',
          ];
          NOTICE.beautifyError({
            code: _data.code,
            message: message[_data.code],
          });
        }
        break;
      case 'onJoinchannelError':
        defaultApi.writeLog(`ZEGO: event:: onJoinchannelError: ${data}`);
        dataReport.joinRtcChannel(_data);
        break;
      };
      if (zbysdk.openListenerLog) {
        defaultApi.writeLog(`Zego::Listener:: event: ${event}, data: ${JSON.stringify(data)}`);
      }
    });
  }
};

const startPullingStreamObserver = () => {
  pullingStore.restore();

  if (pullingStreamInterval !== -1) {
    clearInterval(pullingStreamInterval);
  }
  pullingStreamInterval = setInterval(() => {
    pullingStore.forEach((value, key) => {
      try {
        if (!pullingStore.checkData(value)) {
          pullingStore.delete(key);
          dataReport.pullResult({
            code: PullResult.PULL_STREAM_CODE_TIMEOUT,
            media_type: value.info,
            pull_uid: util.getUidByStreamIdDr(key),
            pull_streamid: key,
          });
        }
      } catch (e) {
        defaultApi.writeLog(`pullingStreamInterval error ${e}`, undefined, 'error');
      }
    });
  }, 500);
};

/**
 * @function 添加扩展监听机制
 * @return void
 */
const removerListener = () => {
  hasAddListener = false;
  defaultApi.writeLog(`ZEGO::action--removerListener EMListenerId:${EMListenerId}`);
  EM.RemoverListener(extensionId, EMListenerId, (ec, content) => { });
  EMListenerId = 0;
};

/**
 * @function 推流异常恢复
 */
export const pushFlowExceptionHandler = async () => {
  await new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, 1000);
  });
  defaultApi.writeLog('断网重推');
  defaultApi.writeLog('avsdk ZEGO::try to pushFlow again');
  await stopPublish();
  await startPublish2();
};

/**
 * @function 拉流异常恢复
 */
export const pullFlowExceptionHandler = async (streamId) => {
  await new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, 1000);
  });
  defaultApi.writeLog('断网重拉');
  defaultApi.writeLog(`avsdk ZEGO::try to pullFlow again ,streamId : ${streamId}`);
  await stopPlayStream(streamId);
  await playStream(streamId);
};

/**
 * @function 初始化 Engine
 * @param appId:String Zego 秘钥,必选
 * @param appSignature:String 签名,必选
 * @param businessType:Number 业务类型,可选,默认为 0
 * @param videoRenderType:Number zego视频渲染类型,默认为2(只外部渲染),zego小组课需要设置为5(同时支持内部+外部渲染)
 * @return Promise | void
 */
export const startEngine = (appId, appSignature, businessType, videoRenderType = 2) => {
  if (typeof businessType === 'undefined') {
    businessType = 0;
  }
  defaultApi.writeLog('avsdk ZEGO::StartEngine');
  return callMethod('StartEngine', {
    appId,
    appSignature,
    businessType,
    videoRenderType
  });
};

//开启消除回声
export const setEnableAEC = (bEnable) => {
  return callMethod('EnableAEC', { bEnable });
};

//设置回声消除模式 mode :激进模式 =0/中等模式 =1/轻度模式 =2
export const setAECMode = (mode) => {
  return callMethod('SetAECMode', { mode });
};

//开启噪音消除
export const setEnableNoiseSuppress = (bEnable) => {
  return callMethod('EnableNoiseSuppress', { bEnable });
};

//设置噪音消除模式 mode :0:低  1:中   2高
export const setNoiseSuppressMode = (level) => {
  return callMethod('SetNoiseSuppressMode', { level });
};

//新增瞬时降噪接口 瞬时降噪 可以处理掉 敲键盘、敲桌子、敲门等瞬态噪声。非瞬态噪声,此接口无法处理,比如划桌子等摩擦噪音。
export const enableTransientNoiseSuppress = (enable) => {
  return callMethod('EnableTransientNoiseSuppress', { enable });
};

/**
   * @function 加载采集插件的输入数据  //调用后,zego 可以向采集插件传送摄像头数据,用于拼接
   * @return: Promise
   */
export const loadCollectionInputEntry = () => {
  return callMethod('LoadCollectionInputEntry', {});
};

/**
 * @function 加载采集插件的输出数据  //调用后,zego 可以向采集插件传送学生流数据(加载截屏插件,获取截屏插件设置拉流头像回调函数)
 * @param stream_ids array 设置需要拼接的学生头像流id,按数组顺序拼接
 * @return: Promise
 */
export const loadCollectionInputEntry2 = (stream_ids) => {
  return callMethod('LoadCollectionInputEntry2', { stream_ids });
};

/**
 * @function 加载采集插件的输出数据  //调用后,zego 可以从采集插件获取拼接数据
 * @return: Promise
 */
export const loadCollectionOutputEntry = () => {
  return callMethod('LoadCollectionOutputEntry', {});
};
/**
 * @function 设置直推url  //调用后,zego 可以从采集插件获取拼接数据
 * @param pRtmpUrl:String 直推地址
 * @param idx:Number 使用外部采集的通道idx,与StartEngine中channel_idx保持一致
 * @return: Promise
 */
export const setCDNPublishTarget = (pRtmpUrl, idx) => {
  return callMethod('SetCDNPublishTarget', { pRtmpUrl, idx });
};
/**
 * @function 销毁引擎
 * @return Promise | void
 */
export const destroyEngine = () => {
  return callMethod('DestroyEngine', {});
};

/**
 * @function 加入频道
 * @param userId:Number 用户 id,必选
 * @param userName:String 用户名,必选
 * @param channelId:String 频道 id,必选
 * @param nNetType:Number 网络类型,可选,默认为 1
 * @return Promise | void
 */
export const loginChannel = (userId, userName, channelId, nNetType) => {
  if (typeof nNetType === 'undefined') {
    nNetType = 1;
  }
  return callMethod('LoginChannel', {
    pszUserID: userId + '',
    pszUserName: userName,
    pszChannelID: channelId,
    nNetType
  });
};

/**
 * @function 退出直播频道
 * @return Promise | void
 */
export const logoutChannel = () => {
  defaultApi.writeLog('avsdk ZEGO:: CallMethod : LogoutChannel');
  return callMethod('LogoutChannel', {});
};

/**
 * @function 云控时退出房间
 * @return Promise
 */
export const leaveRoomWithChangeSDK = async () => {
  defaultApi.writeLog(`leaveRoomWithChangeSDK--currentClassMode--currentGroupSdkType ${currentClassMode}, ${currentGroupSdkType}`);
  let needDestroyZegoEngine = true; //云控从zego切到其他sdk时,默认释放zego引擎。但是如果zego在推拉RTMP流,则不能释放
  if (currentGroupSdkType == 'zego' || window.zby_isM1Type) {
    switch (currentClassMode) {
    case 0: // 小组
    case 2: // 组合小班
      needDestroyZegoEngine = false;
      break;
    case 1: // 小班
      if (window.zby_sdk_init_params.role === 'teacher' || window.zby_sdk_init_params.role === 'assistant') {
        needDestroyZegoEngine = false;
      }
      break;
    }
  }
  await stopSoundLevelMonitor();
  for (let key in streamIdToPreviewId) {
    await stopPlayStream(key, true);
  }
  if ((isZegoGroupClass() && (currentRole === 'teacher' || currentRole === 'assistant')) || (isZegoCombinationClass() && currentRole === 'assistant')) {
    // zego小组课,切RTC流云控后,授课端不能停止预览
    console.log('zego小组课不可停止预览,会影响rtmp');
  }
  else {
    await stopPreview();
  }
  await stopPublish();
  removerListener();

  if (needDestroyZegoEngine === true) {
    await destroyEngine();
  }
  if ((window.zby_sdk_init_params.role === 'teacher') && window.zby_sdk_init_params.mode === 1) {
    //坐立模式关闭外部采集器,站立关闭外部采集器
    await setVideoCaptureFactory(false);
  }
  heartBeatDataReport('stop');
  isFirstHeartBeatReport = false;
  streamIdToPreviewId = {};
  previewIdToStreamId = {};
  streamIdRtcPlayerInfo = {};
  streamIdRtcPlayerInfo1 = {};
  // heartBeatDataReportObj = {};
  hasLoginChannel = 0;
  hasPushFlow = 0;
  isAVEngineStopped = 0;
  window.zegoHasPushFlow = 0;
};

/**
 * @function 退出房间
 * @return Promise
 */
export const leaveRoom = async (type) => {
  await stopSoundLevelMonitor();
  for (let key in streamIdToPreviewId) {
    await stopPlayStream(key, true);
  }
  await stopPublish();
  removerListener();
  // await logoutChannel();
  // await new Promise((resolve, reject) => {
  //   let timer = setInterval(() => {
  //     if (isAVEngineStopped) {
  //       clearInterval(timer);
  //       resolve();
  //     }
  //   }, 300);
  // });
  if (type == 'reload') {
    defaultApi.writeLog('leaveroom -- reload --小组课刷新不可销毁zego引擎');
  } else {
    await stopPreview();
    await destroyEngine();
  }
  if ((window.zby_sdk_init_params.role === 'teacher') && window.zby_sdk_init_params.mode === 1) {
    //坐立模式关闭外部采集器,站立关闭外部采集器
    await setVideoCaptureFactory(false);
  }
  heartBeatDataReport('stop');
  isFirstHeartBeatReport = false;
  streamIdToPreviewId = {};
  previewIdToStreamId = {};
  streamIdRtcPlayerInfo = {};
  streamIdRtcPlayerInfo1 = {};
  // heartBeatDataReportObj = {};
  hasLoginChannel = 0;
  hasPushFlow = 0;
  isAVEngineStopped = 0;
  window.zegoHasPushFlow = 0;
};

export const stopCheckDevice = async () => {
  removerListener();
  await stopSoundLevelMonitor();
  await stopPreview();
  defaultApi.writeLog('rtc stopCheckDevice and removerListener and captureDestroy');
  return callMethod('DestroyEngine', {});
};

/**
 * @function 获取系统默认的音频设备
 * @param deviceType:Number 音频设备类型,0 -> 麦克风,1 -> 扬声器,必选
 * @return Promise | void
 */
export const getDefaultAudioDeviceId = (deviceType) => {
  return callMethod('GetDefaultAudioDeviceId', {
    deviceType,
    // 以下两个参数仅仅是为了向下向后兼容,使用 hardcore 即可
    deviceId: '',
    deviceIdLength: 0
  });
};

/**
 * @function 获取系统默认的麦克风设备 id
 * @return Promise
 */
export const getDefaultMicrophone = () => {
  return getDefaultAudioDeviceId(0);
};

/**
 * @function 获取系统默认的扬声器设备 id
 * @return Promise
 */
export const getDefaultSpeaker = () => {
  return getDefaultAudioDeviceId(1);
};

/**
 * @function 获取摄像头列表
 * @return Promise | void
 */
export const getVideoDeviceList = async () => {
  let videoListArr = JSON.parse((await callMethod('GetVideoDeviceList', {
    // 这个参数是 C++ 要使用的引用参数,随便传个整型就行
    device_count: 1
  })).msg).ret;
  let videoList = [];
  for (let i = 0, len = videoListArr.length; i < len; i++) {
    videoList.push({
      deviceId: videoListArr[i].szDeviceId,
      deviceName: videoListArr[i].szDeviceName,
      isDefault: i === 0
    });
    deviceListReport.cameraList[videoListArr[i].szDeviceId] = videoListArr[i].szDeviceName;
  }
  if (!window.zbyAVSDK_device_checker_init) {
    window.zbyAVSDK_device_checker_init = {};
  }
  if (!window.zbyAVSDK_device_checker_init.camera) {
    window.zbyAVSDK_device_checker_init.camera = {};
  }
  window.zbyAVSDK_device_checker_init.camera.hasTest = true;
  window.zbyAVSDK_device_checker_init.camera.list = videoList;
  return videoList;
};

/**
 * @function 指定使用的摄像头
 * @param deviceId:String 摄像头 id,必选
 * @param idx:Number 推流通道,可选,默认为 0
 * @param operationType:String 操作类型,可选
 * 'device_error' -> 设备出错处理,'plug_and_unplug' -> 热插拔处理,
 * 不传即是普通的设置摄像头设备的行为
 * @return Promise | void
 */
export const setVideoDevice = async (args) => {
  defaultApi.writeLog(`${JSON.stringify(args)}----setVideoDevice-zego----`);
  let { deviceId, idx, operationType, deviceState, code, changedDeviceId } = args;
  let deviceName = '';
  let cameraData = await getVideoDeviceList();
  if (cameraData.length) {
    let defaultDevice;
    for (let item of cameraData) {
      if (deviceId && item.deviceId === deviceId) {
        deviceName = item.deviceName;
        break;
      }

      if (item.isDefault) {
        defaultDevice = item;
      }
    }

    if (!defaultDevice) {
      defaultDevice = cameraData[0];
    }

    if (!deviceId || !deviceName) {
      NOTICE.deviceError({
        code: -2,
        msg: '设备ID无效',
        deviceId,
        deviceName,
      });

      deviceId = defaultDevice.deviceId;
      deviceName = defaultDevice.deviceName;
    }
  } else {
    deviceId = '';
  }

  if (deviceId === '') {
    NOTICE.noDevice({
      deviceType: 'camera'
    });
  }

  window.zbyAVSDK_device_checker_init.camera.use = deviceId;
  window.zbyAVSDK_device_checker_init.camera.name = deviceListReport.cameraList[deviceId];
  if (typeof idx === 'undefined') {
    idx = 0;
  }
  try {
    dataReport.setDevice({
      device_type: 1,
      device_id: deviceId,
      device_name: deviceListReport.cameraList[deviceId],
      operationType,
      fore_state: operationType == 'hotPlug' ? deviceState + 1 : '-'
    });
  } catch (e) { };
  await callMethod('SetVideoDevice', {
    pszDeviceID: deviceId,
    idx
  });
  if (operationType == 'hotPlug' || operationType == 'deviceError') {
    if (!cameraData) {
      cameraData = await getVideoDeviceList();
      deviceName = deviceListReport.cameraList[deviceId];
    }
    NOTICE[operationType]({
      deviceType: 'camera',
      useDeviceId: deviceId,
      useDeviceName: deviceName,
      deviceList: cameraData,
      messge: code,
      deviceState,
      changedDeviceId
    });
  }
  try {
    NOTICE.useredCamera({
      deviceId,
      deviceName
    });
  } catch (e) { };
};

/**
 * @function 开/关摄像头
 * @param enable:Boolean false -> 关闭,true -> 打开,必选
 * @param idx:Number 推流通道,可选,默认为 0
 * @return Promise | void
 */
export const enableCamera = (enable, idx = 0) => {
  // try {
  //   dataReport.deviceChange({
  //     device: 1,
  //     device_state: enable? 1 : 0
  //   });
  // } catch (e){};
  return callMethod('EnableCamera', {
    bEnable: enable,
    idx
  });
};

/**
 * @function 开/关麦克风
 * @param bEnable:Boolean false -> 关闭,true -> 打开,必选
 * @param idx:Number 推流通道,可选,默认为 0
 * @return Promise | void
 */
export const enableMic = async (bEnable, idx) => {
  defaultApi.writeLog('enableMic --start');
  isNoticeMicVolumeZego = bEnable;
  // if (bEnable) {
  //   await setMicDeviceMute(false);
  //   defaultApi.writeLog('openMic and muteFalse');
  // }
  if (typeof idx === 'undefined') {
    idx = 0;
  }
  // try {
  //   dataReport.deviceChange({
  //     device: 2,
  //     device_state: bEnable ? 1 : 0
  //   });
  // } catch (e){};
  return callMethod('EnableMic', {
    bEnable,
    idx
  });
};

/**
 * @function 是否推声音
 * @param mute:Boolean true/不推声音,false/推声音,必选
 * @return Promise | void
 */
export const setMuteStreamAudio = (mute) => {
  return callMethod('MuteStreamAudio', { mute });
};

/**
 * @function 是否推画面
 * @param mute:Boolean true/不推画面,false/推画面,必选
 * @return Promise | void
 */
export const setMuteStreamVideo = (mute) => {
  return callMethod('MuteStreamVideo', { mute });
};

/**
 * @function 获取音频设备
 * @param deviceType:Number 音频设备类型,0 -> 麦克风,1 -> 扬声器,必选
 * @return Promise | void
 */
export const getAudioDeviceList = (deviceType) => {
  return callMethod('GetAudioDeviceList', {
    deviceType,
    // 这个参数是 C++ 要使用的引用参数,随便传个整型就行
    device_count: 0
  });
};

/**
 * @function 指定音频设备
 * @param deviceType:Number 音频设备类型,0 -> 麦克风,1 -> 扬声器,必选
 * @param deviceId:String 音频设备 id,必选
 * @return Promise | void
 */
export const setAudioDevice = (deviceType, deviceId) => {
  if (deviceType == 0) {
    window.zbyAVSDK_device_checker_init.microphone.use = deviceId;
  } else {
    window.zbyAVSDK_device_checker_init.speaker.use = deviceId;
  }
  return callMethod('SetAudioDevice', {
    deviceType,
    pszDeviceID: deviceId
  }).then((result) => {
    if (deviceType == 1 && result.code === 0) {
      NOTICE.speakerChanged({
        deviceId
      });
    }
    return result;
  });
};

/**
 * 获取当前扬声器设备id
 * @return {String} 当前扬声器设备id
 */
const getCurrentSpeakerDevice = () => {
  defaultApi.writeLog(`avsdk ZEGO::getSpeakerDevice ${usingAudioDeviceId.speaker}`);
  return usingAudioDeviceId.speaker;
};

/**
 * @function 获取扬声器列表
 * @return Promise | void
 */
export const getSpeakerDeviceList = async () => {
  const defaultSpeakerId = JSON.parse((await getDefaultSpeaker()).msg).ret.deviceId;
  let speakerListArr = JSON.parse((await getAudioDeviceList(1)).msg).ret;
  let speakerList = [];
  for (let i = 0, len = speakerListArr.length; i < len; i++) {
    speakerList.push({
      deviceId: speakerListArr[i].szDeviceId,
      deviceName: speakerListArr[i].szDeviceName,
      isDefault: defaultSpeakerId === speakerListArr[i].szDeviceId
    });
    deviceListReport.speakerList[speakerListArr[i].szDeviceId] = speakerListArr[i].szDeviceName;

  }
  if (!window.zbyAVSDK_device_checker_init) {
    window.zbyAVSDK_device_checker_init = {};
  }
  if (!window.zbyAVSDK_device_checker_init.speaker) {
    window.zbyAVSDK_device_checker_init.speaker = {};
  }
  window.zbyAVSDK_device_checker_init.speaker.hasTest = true;
  window.zbyAVSDK_device_checker_init.speaker.list = speakerList;
  return speakerList;
};

/**
 * @function 指定扬声器
 * @param deviceId:String 扬声器 id,必选
 * @param operationType:String 操作类型,可选
 * 'device_error' -> 设备出错处理,'plug_and_unplug' -> 热插拔处理,
 * 不传即是普通的设置扬声器设备的行为
 * @return Promise | void
 */
export const setSpeakerDevice = async (args) => {
  defaultApi.writeLog(`${JSON.stringify(args)}----setSpeakerDevice-zego----`);
  // console.log('hsghsghsg999',args);
  let { deviceId, operationType, deviceState, code, changedDeviceId } = args;
  let deviceName = '';
  let speakerData;
  if (!deviceId) {
    speakerData = await getSpeakerDeviceList();
    // console.log('hsghsghsg0',speakerData.length);
    if (speakerData.length) {
      let hasGetSpeaker = false;
      // console.log('hsghsghsg1',hasGetSpeaker);
      for (let item of speakerData) {
        if (item.isDefault) {
          deviceId = item.deviceId;
          deviceName = item.deviceName;
          hasGetSpeaker = true;
          break;
        }
      }
      // console.log('hsghsghsg2',hasGetSpeaker);
      if (!hasGetSpeaker) {
        deviceId = speakerData[0].deviceId;
        deviceName = speakerData[0].deviceName;
        // console.log('hsghsghsg91',deviceId);
      }
    } else {
      deviceId = '';
    }
    if (deviceId === '') {
      NOTICE.noDevice({
        deviceType: 'speaker'
      });
    }
  }
  // console.log('hsghsghsg999',deviceId);
  try {
    dataReport.setDevice({
      device_type: 3,
      device_id: deviceId,
      device_name: deviceListReport.speakerList[deviceId],
      operationType,
      fore_state: operationType == 'hotPlug' ? deviceState + 1 : '-'
    });
    window.zbyAVSDK_device_checker_init.speaker.name = deviceListReport.speakerList[deviceId];
  } catch (e) { };
  usingAudioDeviceId.speaker = deviceId;
  await setAudioDevice(1, deviceId);
  if (operationType == 'hotPlug' || operationType == 'deviceError') {
    if (!speakerData) {
      speakerData = await getSpeakerDeviceList();
      deviceName = deviceListReport.speakerList[deviceId];
    }
    NOTICE[operationType]({
      deviceType: 'speaker',
      useDeviceId: deviceId,
      useDeviceName: deviceName,
      deviceList: speakerData,
      message: code,
      deviceState,
      changedDeviceId
    });
  }
};

/**
 * @function 获取扬声器音量
 * @return Promise | void
 */
export const getSpeakerDeviceVolume = () => {
  return callMethod('GetSpeakerDeviceVolume', {
    deviceId: usingAudioDeviceId.speaker
  });
};

/**
 * @function 设置扬声器音量
 * @param volume:Number 扬声器音量,必选
 * @return Promise | void
 */
export const setSpeakerDeviceVolume = (volume) => {
  return callMethod('SetSpeakerDeviceVolume', {
    deviceId: usingAudioDeviceId.speaker,
    volume
  });
};

/**
 * @function 设置扬声器静音
 * @param mute:Boolean 必选
 * @return Promise | void
 */
export const setSpeakerDeviceMute = (mute) => {
  // try {
  //   dataReport.deviceChange({
  //     device: 3,
  //     device_state: mute ? 0 :1
  //   });
  // } catch (e){};
  return callMethod('SetSpeakerDeviceMute', {
    deviceId: usingAudioDeviceId.speaker,
    mute
  });
};

/**
 * @function 获取麦克风列表
 * @return Promise | void
 */
export const getMicrophoneDeviceList = async () => {
  const defaultMicrophoneId = JSON.parse((await getDefaultMicrophone()).msg).ret.deviceId;
  let microphoneListArr = JSON.parse((await getAudioDeviceList(0)).msg).ret;
  let microphoneList = [];
  for (let i = 0, len = microphoneListArr.length; i < len; i++) {
    microphoneList.push({
      deviceId: microphoneListArr[i].szDeviceId,
      deviceName: microphoneListArr[i].szDeviceName,
      isDefault: defaultMicrophoneId === microphoneListArr[i].szDeviceId
    });
    deviceListReport.micList[microphoneListArr[i].szDeviceId] = microphoneListArr[i].szDeviceName;

  }
  if (!window.zbyAVSDK_device_checker_init) {
    window.zbyAVSDK_device_checker_init = {};
  }
  if (!window.zbyAVSDK_device_checker_init.microphone) {
    window.zbyAVSDK_device_checker_init.microphone = {};
  }
  window.zbyAVSDK_device_checker_init.microphone.hasTest = true;
  window.zbyAVSDK_device_checker_init.microphone.list = microphoneList;
  return microphoneList;
};

/**
 * @function 指定麦克风
 * @param deviceId:String 麦克风 id,必选
 * @param operationType:String 操作类型,可选
 * 'device_error' -> 设备出错处理,'plug_and_unplug' -> 热插拔处理,
 * 不传即是普通的设置麦克风设备的行为
 * @return Promise | void
 */
export const setMicrophoneDevice = async (args) => {
  defaultApi.writeLog(`${JSON.stringify(args)}----setMicrophoneDevice-zego----`);
  let { deviceId, operationType, deviceState, code, changedDeviceId } = args;
  let deviceName = '';
  let microPhoneData;
  if (!deviceId) {
    microPhoneData = await getMicrophoneDeviceList();
    if (microPhoneData.length) {
      let hasGetMicrophone = false;
      for (let item of microPhoneData) {
        if (item.isDefault) {
          deviceId = item.deviceId;
          deviceName = item.deviceName;
          hasGetMicrophone = true;
          break;
        }
      }
      if (!hasGetMicrophone) {
        deviceId = microPhoneData[0].deviceId;
        deviceName = microPhoneData[0].deviceName;
      }
    } else {
      deviceId = '';
    }
    if (deviceId === '') {
      NOTICE.noDevice({
        deviceType: 'microphone'
      });
    }
  }
  try {
    dataReport.setDevice({
      device_type: 2,
      device_id: deviceId,
      device_name: deviceListReport.micList[deviceId],
      operationType,
      fore_state: operationType == 'hotPlug' ? deviceState + 1 : '-'
    });
    window.zbyAVSDK_device_checker_init.microphone.name = deviceListReport.micList[deviceId];
  } catch (e) { };
  usingAudioDeviceId.microphone = deviceId;
  await setAudioDevice(0, deviceId);
  await setAudioVolumeNotify(0, deviceId);
  if (operationType == 'hotPlug' || operationType == 'deviceError') {
    if (!microPhoneData) {
      microPhoneData = await getMicrophoneDeviceList();
      deviceName = deviceListReport.micList[deviceId];
    }
    NOTICE[operationType]({
      deviceType: 'microphone',
      useDeviceId: deviceId,
      useDeviceName: deviceName,
      deviceList: microPhoneData,
      message: code,
      deviceState,
      changedDeviceId
    });
  }
};

/**
 * @function 获取麦克风音量
 * @return Promise | void
 */
export const getMicDeviceVolume = () => {
  return callMethod('GetMicDeviceVolume', {
    deviceId: usingAudioDeviceId.microphone
  });
};

/**
 * @function 设置麦克风音量
 * @param volume:Number 麦克风音量,必选
 * @return Promise | void
 */
export const setMicDeviceVolume = (volume) => {
  return callMethod('SetMicDeviceVolume', {
    deviceId: usingAudioDeviceId.microphone,
    volume
  });
};

/**
 * @function 设置麦克风静音
 * @param mute:Boolean 必选
 * @return Promise | void
 */
export const setMicDeviceMute = (mute) => {
  return callMethod('SetMicDeviceMute', {
    deviceId: usingAudioDeviceId.microphone,
    mute
  });
};

//设置采集扬声器声音
export const setAudioSpeakerCapture = async (bEnable) => {
  return callMethod('EnableMixSystemPlayout', {
    bEnable,
  });
};

// //采集扬声器
// export const setAudioSpeakerCapture2 = async (bEnable) => {
//   return callMethod('MixEnginePlayout', {
//     bEnable,
//   });
// };

/**
 * @function 获取当前应用程序音量
 * @return Promise | void
 */
export const getSpeakerSimpleVolume = () => {
  return callMethod('GetSpeakerSimpleVolume', {
    deviceId: usingAudioDeviceId.speaker
  });
};

/**
 * @function 设置视频预览载体 view
 * @param idx:Number 直播通道,可选,默认为 0
 * @return Promise | void
 */
export const setPreviewView = (idx) => {
  if (typeof idx === 'undefined') {
    idx = 0;
  }
  return callMethod('SetPreviewView', {
    idx
  });
};

/**
 * @function 设置本地预览视频视图的模式
 * @param mode:Number 模式,必选,0 | 1 | 2
 * 0:等比缩放,可能有黑边
 * 1:等比缩放填充整View,可能有部分被裁减
 * 2:填充整个View
 * @param idx:Number 直播通道,可选,默认为 0
 * @return Promise | void
 */
export const setPreviewViewMode = (mode, idx) => {
  if (typeof idx === 'undefined') {
    idx = 0;
  }
  return callMethod('SetPreviewViewMode', {
    mode,
    idx
  });
};

/**
 * @function 开启预览
 * @param idx:Number 推流通道,可选,默认为 0
 * @return Promise | void
 */
export const startPreview = (idx) => {
  if (typeof idx === 'undefined') {
    idx = 0;
  }
  return callMethod('StartPreview', {
    idx
  });
};

/**
 * @function 停止预览
 * @param idx:Number 推流通道,可选,默认为 0
 * @return Promise | void
 */
export const stopPreview = (idx) => {
  if (typeof idx === 'undefined') {
    idx = 0;
  }
  return callMethod('StopPreview', {
    idx
  });
};

/**
 * @function 设置当前应用程序音量
 * @param volume:Number 应用程序音量,必选
 * @return Promise | void
 */
export const setSpeakerSimpleVolume = (volume) => {
  return callMethod('SetSpeakerSimpleVolume', {
    deviceId: usingAudioDeviceId.speaker,
    volume
  });
};

/**
 * @function 设置当前应用程序静音
 * @param mute:Boolean 是否静音,必选
 * @return Promise | void
 */
export const setSpeakerSimpleMute = (mute) => {
  return callMethod('SetSpeakerSimpleMute', {
    deviceId: usingAudioDeviceId.speaker,
    mute
  });
};

/**
 * @function 开启本地或者远程的视频视图
 * @param isLocal:Boolean 是否是本地的视频预览,必选
 * @param streamId:String 要拉取的视频流的 id,可选,只有拉取远程的视频流的时候才是必选的
 * @param domId:String <video> 标签的 id,可选
 * 如果传了 domId,就把视频绑定到对应的 <video> 标签上
 * @return Promise 可从 Promise 中获取 src,Promise.then((src) => {})
 */
export const startLocalOrRemotePreview = async (isLocal, streamId, domId, isTeacherNewWindowPreview) => {
  defaultApi.writeLog(`${streamId}----streamId-zego----`);
  let playChannel = null;
  let isUpdateChromeVersion = await util.getChromeVersion();
  // zego 的本地视频预览必须使用 -1 通道,为了与远程的视频预览兼容使用,此处需要判断是否是本地预览
  if (isLocal) {
    playChannel = -1;
  } else {
    playChannel = streamIdToPreviewId[streamId];
  }
  const externalConstraints = {
    audio: false,
    video: {
      mandatory: {
        chromeMediaSource: 'external',
        chromeMediaSourceId: `ems://zego/${isLocal || isTeacherNewWindowPreview ? -1 : streamId}`,
        maxWidth: 3840,
        maxHeight: 2160,
        minWidth: 240,
        minHeight: 180,
      }
    }
  };
  defaultApi.writeLog(`avsdk ZEGO::startLocalOrRemotePreview streamId: ${streamId} uiChnIndex: ${playChannel}`);

  return new Promise((resolve, reject) => {
    const handleExternalSuccess = (stream) => {
      defaultApi.writeLog(`${stream}----stream-zego----`);
      stream.oninactive = () => {
        defaultApi.writeLog('Stream inactive');
      };
      const src = isUpdateChromeVersion ? stream : window.URL.createObjectURL(stream);
      if (domId && document.querySelector(domId)) {
        if (isUpdateChromeVersion) {
          document.querySelector(domId).srcObject = src;
        } else {
          document.querySelector(domId).src = src;
        }
      }
      defaultApi.writeLog(`setVidoeSrc, streamId:${streamId}, domId: ${domId}, src: ${src}`);
      resolve(src);
    };

    const handleExternalError = (error) => {
      if (error.name === 'ConstraintNotSatisfiedError') {
        console.error('ConstraintNotSatisfiedError');
      } else if (error.name === 'PermissionDeniedError') {
        console.error(
          'Permissions have not been granted to use your camera and ' +
          'microphone, you need to allow the page access to your devices in ' +
          'order for the demo to work.'
        );
      }
      console.error(`getUserMedia error: ${error.name}`, error);
      if (domId) {
        document.querySelector(domId).src = '';
      }
      reject('');
    };

    if (navigator.webkitGetUserMedia) {
      navigator.webkitGetUserMedia(
        externalConstraints,
        handleExternalSuccess,
        handleExternalError
      );
    }
  });
};

/**
 * @function 设置本地预览以及推流的视频帧率
 * @param nFps:Number 帧率,必选
 * @param idx:Number 推流通道,可选,默认为 0
 * @return Promise | void
 */
export const setVideoFPS = (nFps, idx) => {
  if (typeof idx === 'undefined') {
    idx = 0;
  }
  return callMethod('SetVideoFPS', {
    nFps,
    idx
  });
};

/**
 * @function 设置本地预览以及推流的视频码率
 * @param nBitrate:Number 码率,必选
 * @param idx:Number 推流通道,可选,默认为 0
 * @return Promise | void
 */
export const setVideoBitrate = (nBitrate, idx) => {
  if (typeof idx === 'undefined') {
    idx = 0;
  }
  return callMethod('SetVideoBitrate', {
    nBitrate,
    idx
  });
};

/**
 * @function 设置本地预览以及推流的音频码率
 * @param bitrate:Number 码率,必选
 * @param idx:Number 推流通道,可选,默认为 0
 * @return Promise | void
 */
export const setAudioBitrate = (bitrate, idx) => {
  if (typeof idx === 'undefined') {
    idx = 0;
  }
  return callMethod('SetAudioBitrate', {
    bitrate,
    idx
  });
};

/**
 * @function 设置本地预览的视频分辨率
 * @param nWidth:Number 分辨率宽,必选
 * @param nHeight:Numnber 分辨率高,必选
 * @param idx:Number 推流通道,可选,默认为 0
 * @return Promise | void
 */
export const setCameraParam = async (nWidth, nHeight, idx) => {
  defaultApi.writeLog(`avsdk ZEGO::setCameraParam ${nWidth}--${nHeight}`);
  await enableCamera(false);
  await setVideoCaptureResolution(nWidth, nHeight, idx);
  return enableCamera(true);
};

/**
 * @function 设置本地预览的视频分辨率
 * @param nWidth:Number 分辨率宽,必选
 * @param nHeight:Numnber 分辨率高,必选
 * @param idx:Number 推流通道,可选,默认为 0
 * @return Promise | void
 */
export const setVideoCaptureResolution = async (nWidth, nHeight, idx) => {
  if (typeof idx === 'undefined') {
    idx = 0;
  }
  await setCapturePipelineScaleMode();
  return callMethod('SetVideoCaptureResolution', {
    nWidth,
    nHeight,
    idx
  });
};

export const setCapturePipelineScaleMode = () => {
  return callMethod('SetCapturePipelineScaleMode', {
    scaleMode: 1
  });
};

/**
 * @function 设置推流的视频分辨率
 * @param nWidth:Number 分辨率宽,必选
 * @param nHeight:Numnber 分辨率高,必选
 * @param idx:Number 推流通道,可选,默认为 0
 * @return Promise | void
 */
export const setVideoEncodeResolution = (nWidth, nHeight, idx) => {
  if (typeof idx === 'undefined') {
    idx = 0;
  }
  return callMethod('SetVideoEncodeResolution', {
    nWidth,
    nHeight,
    idx
  });
};

/**
 * @function 初始化
 * @param args:Object
 * {
 *   extensionVersion,    // String,扩展版本号,必选
 *   appId,               // String,Zego 秘钥,必选
 *   appSignature,        // String,签名,必选
 *   businessType,        // Number,业务类型,必选
 *   userId,              // Number,用户 id,必选
 *   userName,            // String,用户名,必选
 *   roomId,              // String,频道(房间) id,必选
 *   nNetType             // Number,网络类型,可选,默认为 1
 * }
 * classMode  0:小组课及其他   1:小班课  2:组合小班课
 * @return Promise
 */
export const init = async (args) => {
  defaultApi.writeLog(`avsdk ZEGO::init ${JSON.stringify(args)}`);
  currentRole = args.role;
  currentClassMode = args.classMode;
  currentGroupSdkType = args.sdkTypeGroup;
  localStreamId = args.streamId;
  teacherId = args.teacherId;
  lecturerStreamId = typeof args.lecturerStreamId === 'string' ? args.lecturerStreamId.toUpperCase() : null;
  lecturerUid = typeof lecturerStreamId === 'string' ? lecturerStreamId.split('_')[2] : null;
  dataReport.setRealSDKType('zego');
  await loadZego(args.extensionVersion);
  if (args.role === 'teacher' && args.mode === 1) {
    //坐立模式开启外部采集器,站立关闭外部采集器,只有小组课坐立模式才会调用,通过这个把rtmp数据给到zego或者rtc(头像),开启了外部采集,zego不会再本地采集数据
    await setVideoCaptureFactory(true);
  }
  // if (args.classMode === 1 || args.classMode === 2){
  //   //如果是小班课保证关闭外部采集器,小组课上一步打开
  //   await setVideoCaptureFactory(false);
  // };
  // 注册回调
  addListener(args.userId, args.userName, args.confId, args.nNetType, args.devices, args.role);
  //rtc同屏以及高光时刻,都开启音频外部采集,默认传1
  if (args.role === 'teacher') {
    defaultApi.writeLog('avsdk ZEGO::init_ open setAudioAuxSource');
    if (args.classMode === 1 || args.classMode === 2) {
      await setAudioAuxSource(1);
    }
    teacherId = null;
  }

  if (args.mode === 1 && ((isZegoGroupClass() && (args.role === 'teacher' || args.role === 'assistant')) || (isZegoCombinationClass() && args.role === 'assistant'))) {
    //zego推拉RTMP小组课,授课端坐立模式、辅导端坐立模式 或 zego推拉RTMP组合小班辅导端坐立模式,需要将摄像头渲染模式设置为 内部渲染+外部渲染
    await startEngine(args.appId, args.appSignature, args.businessType, 5); //5为摄像头内部渲染+外部渲染
  } else {
    await startEngine(args.appId, args.appSignature, args.businessType); //默认为摄像头外部渲染
  }

  //小班课 需要加载采集插件的输入数据,为了拼接zego头像
  if ((args.classMode === 1 || args.classMode === 2) && args.role === 'teacher') {
    screenStreamId = `${window.zby_sdk_init_params.institutionId}_${args.roomId}_${args.userId}_${args.live_id}_1`;
    loadCollectionInputEntry();
  };
  if (args.noiseSuppressMode == -2) {
    defaultApi.writeLog(`noiseSuppressMode 关闭噪声消除 ${args.noiseSuppressMod}`);
    await setEnableNoiseSuppress(false);
  } else if (args.noiseSuppressMode == -1) {
    defaultApi.writeLog(`noiseSuppressMode 由底层进行配置,js层不进行配置 ${args.noiseSuppressMode}`);
  } else {
    defaultApi.writeLog(`noiseSuppressMode 启动噪音消除,模式为: ${args.noiseSuppressMode}`);
    await setEnableNoiseSuppress(true);
    await setNoiseSuppressMode(+args.noiseSuppressMode);
    await enableTransientNoiseSuppress(true);
  };
  //aecMode为-1是关闭回音消除,云控下传
  if (args.aecMode == -2) {
    defaultApi.writeLog(`aecMode 关闭回音消除 ${args.aecMode}`);
    await setEnableAEC(false);
  } else if (args.aecMode == -1) {
    defaultApi.writeLog(`aecMode 由底层进行配置,js层不进行配置 ${args.aecMode}`);
  } else {
    defaultApi.writeLog(`aecMode 启动回音消除,模式为: ${args.aecMode}`);
    await setEnableAEC(true);
    await setAECMode(+args.aecMode);
  };
  await setVideoBitrate(args.encodeCaptureBitrate);
  // await setAudioBitrate(args.encodeCaptureBitrate);
  await setVideoFPS(args.encodeCaptureFps);
  await setVideoCaptureResolution(args.previewResolutionWidth, args.previewResolutionHeight);
  await setVideoEncodeResolution(args.encodedResolutionWidth, args.encodedResolutionHeight);

  if ((isZegoGroupClass() && (args.role === 'teacher' || args.role === 'assistant')) || (isZegoCombinationClass() && args.role === 'assistant')) {
    defaultApi.writeLog('zego小组课或辅导端组合小班课,用第二路推RTMP流,在pyzx初始化采集参数');
  } else {
    await setVideoBitrate(args.screenSameBitrate, 1);
    await setVideoFPS(args.screenSameFps, 1);
    await setVideoCaptureResolution(args.screenSameResolutionWidth, args.screenSameResolutionHeight, 1);
    await setVideoEncodeResolution(args.screenSameResolutionWidth, args.screenSameResolutionHeight, 1);
  }

  // await setVideoBitrate(500000);
  // await setAudioBitrate(500000);
  // await setVideoFPS(15);
  // await setVideoCaptureResolution(1280, 720);
  // await setVideoEncodeResolution(1280, 720);
  await setSoundLevelMonitorCycle(500);
  startPullingStreamObserver();
  if (isInitFinished) {
    defaultApi.writeLog(`avsdk ZEGO::init_isInitFinished ${isInitFinished}`);
    // await afterInitSDK(args.userId, args.userName, args.roomId, args.nNetType, JSON.parse(JSON.stringify(args.devices)));
    isInitFinished = 0;
    window.current_sdk_type = 'zego';
    defaultApi.writeLog('zego init finished current_sdk_type : zego');
  } else {
    await new Promise((resolve, reject) => {
      let timer = null;
      timer = setInterval(() => {
        if (isInitFinished) {
          isInitFinished = 0;
          clearInterval(timer);
          resolve();
        }
      }, 30);
    });
    window.current_sdk_type = 'zego';
    defaultApi.writeLog('zego init finished current_sdk_type : zego');
  }
};

/**
 * @function 在OnInitSDK事件之后调用的初始化方法
 * @param userId:Number 用户 id,必选
 * @param userName:String 用户名,必选
 * @param roomId:String 频道(房间) id,必选
 * @param nNetType:Number 网络类型,可选,默认为 1
 * @return Promise
 */
const afterInitSDK = async (userId, userName, confId, nNetType, devices, role, classMode) => {
  // 指定默认设备
  const usedDevices = window.zbyAVSDK_device_checker_init;
  const _devices = {
    camera: (devices && devices.camera) || (usedDevices && usedDevices.camera && usedDevices.camera.use) || '',
    microphone: (devices && devices.microphone) || (usedDevices && usedDevices.microphone && usedDevices.microphone.use) || '',
    speaker: (devices && devices.speaker) || (usedDevices && usedDevices.speaker && usedDevices.speaker.use) || ''
  };
  defaultApi.writeLog('avsdk ZEGO::afterInitSDK');
  await setDefaultDevice(_devices, 'default');
  await loginChannel(userId, userName, confId, nNetType);
  //设置拉流质量监控周期
  await setPlayQualityMoniterCycle(5000);
  await setPublishQualityMoniterCycle(5000);
  await setMediaSideFlags();
  defaultApi.writeLog(`test: isZegoGroupClass, ${isZegoGroupClass()}`);
  if ((isZegoGroupClass() && (role === 'teacher' || role === 'assistant')) || (isZegoCombinationClass() && role === 'assistant')) {
    defaultApi.writeLog(`avsdk ZEGO::afterInitSDK zegoRtmp, classMode: ${classMode} role: ${role}`);
  } else {
    // 初始化的时候不打开摄像头
    await enableCamera(false);
    // 只有调用了这个方法之后才开启关闭摄像头才有效
    await startPreview();
  }
  // 置为 1 表示初始化完成
  isInitFinished = 1;
};

const deviceCheckerInitSDK = async () => {
  defaultApi.writeLog('avsdk ZEGO::deviceCheckerInitSDK');
  await enableCamera(false);
  await startPreview();
  isDeviceCheckInitFinished = 1;
};

const getDeviceCheckInitStatus = () => {
  return isDeviceCheckInitFinished;
};

const setDeviceCheckInitStatus = () => {
  isDeviceCheckInitFinished = 0;
};

//设置是否发送sei
const setMediaSideFlags = (bStart = true, bOnlyAudioPublish = false, mediaInfoType = 2, seiSendType = 1, idx = 0) => {
  return callMethod('SetMediaSideFlags', { bStart, bOnlyAudioPublish, mediaInfoType, seiSendType, idx });
};
//设置拉流质量监控周期
const setPlayQualityMoniterCycle = (timeInMS) => {
  return callMethod('SetPlayQualityMoniterCycle', { timeInMS });
};

//设置推流质量监控周期
const setPublishQualityMoniterCycle = (timeInMS) => {
  return callMethod('SetPublishQualityMoniterCycle', { timeInMS });
};

/**
 * @function 设置默认的硬件设备,包括摄像头、麦克风以及扬声器
 * @return Promise
 */
export const setDefaultDevice = async (devices, operationType) => {
  // 设置默认的摄像头
  if (devices && devices.camera) {
    if (zbyAVSDK_device_checker_init.camera.list.length == 0) {
      await getVideoDeviceList();
    }
    await setVideoDevice({ deviceId: devices.camera, operationType: `${operationType}_1` });
  } else {
    const cameraData = await getVideoDeviceList();
    if (cameraData.length) {
      let hasSetCamera = false;
      for (let item of cameraData) {
        if (item.isDefault) {
          await setVideoDevice({ deviceId: item.deviceId, operationType: `${operationType}_2` });
          hasSetCamera = true;
          break;
        }
      }
      if (!hasSetCamera) {
        await setVideoDevice({ deviceId: cameraData[0].deviceId, operationType: `${operationType}_3` });
      }
    }
  }
  // 设置默认的麦克风
  if (devices && devices.microphone) {
    if (zbyAVSDK_device_checker_init.microphone.list.length == 0) {
      await getMicrophoneDeviceList();
    }
    await setMicrophoneDevice({ deviceId: devices.microphone, operationType: `${operationType}_1` });
  } else {
    const microPhoneData = await getMicrophoneDeviceList();
    if (microPhoneData.length) {
      let hasSetMicrophone = false;
      for (let item of microPhoneData) {
        if (item.isDefault) {
          await setMicrophoneDevice({ deviceId: item.deviceId, operationType: `${operationType}_2` });
          hasSetMicrophone = true;
          break;
        }
      }
      if (!hasSetMicrophone) {
        await setMicrophoneDevice({ deviceId: microPhoneData[0].deviceId, operationType: `${operationType}_3` });
      }
    }
  }
  // 设置默认的扬声器
  if (devices && devices.speaker) {
    await setSpeakerDevice({ deviceId: devices.speaker, operationType: `${operationType}_1` });
  } else {
    const speakerData = await getSpeakerDeviceList();
    if (speakerData.length) {
      let hasSetSpeaker = false;
      for (let item of speakerData) {
        if (item.isDefault) {
          await setSpeakerDevice({ deviceId: item.deviceId, operationType: `${operationType}_2` });
          hasSetSpeaker = true;
          break;
        }
      }
      if (!hasSetSpeaker) {
        await setSpeakerDevice({ deviceId: speakerData[0].deviceId, operationType: `${operationType}_3` });
      }
    }
  }
};

/**
 * @function 开始推流
 * @param title:String 直播的名称,可选,默认为 ''
 * @param flag:Number 直播属性,可选,默认为 0
 * @param seq:Number 请求序号,可选,默认为 0
 * @param idx:Number 推流通道,可选,默认为 0
 * @return Promise | void
 */
export const startPublish2 = async (streamid, idx, title, flag, seq) => {
  const streamId = streamid || window.zbyAVSDK_init_params.zego.streamId;
  if (typeof title === 'undefined') {
    title = 'none';
  }
  if (typeof flag === 'undefined') {
    flag = 0;
  }
  if (typeof seq === 'undefined') {
    seq = 0;
  }
  if (typeof idx === 'undefined') {
    idx = 0;
  }
  await new Promise((resolve, reject) => {
    let timer = setInterval(() => {
      if (hasLoginChannel) {
        clearInterval(timer);
        resolve();
      }
    }, 30);
  });
  let res = await callMethod('StartPublish2', {
    pszStreamID: streamId,
    pszTitle: title,
    seq,
    idx,
    flag
  });
  await startSoundLevelMonitor();
  await new Promise((resolve, reject) => {
    let timer = setInterval(() => {
      if (hasPushFlow) {
        clearInterval(timer);
        resolve();
      }
    }, 300);
  });
  // 心跳开始
  if (!isFirstHeartBeatReport) {
    isFirstHeartBeatReport = true;
    heartBeatDataReport('start');
  }
  return res;
};

/**
 * @function 结束推流
 * @param msg:String 自定义信息,server 对接流结束回调包含此字段内容,可选,默认为 'none'
 * @param flag:Number 直播属性,可选,默认为 0
 * @param idx:Number 推流通道,可选,默认为 0
 * @return Promise | void
 */
export const stopPublish = (idx, msg, flag) => {
  if (typeof msg === 'undefined') {
    msg = 'none';
  }
  if (typeof flag === 'undefined') {
    flag = 0;
  }
  if (typeof idx === 'undefined') {
    idx = 0;
  }
  // try {
  //   dataReport.stopPublish({publish_streamid:window.zbyAVSDK_init_params.zego.streamId});
  // } catch (error) {}
  return callMethod('StopPublish', {
    pszMsg: msg,
    flag,
    idx
  });
};

/**
 * @function 初始化拉流
 * @param streamId:String 从传来的信道消息中获取,必选
 * @param domId:String <video> 标签的 id,可选
 * 如果传了就把视频绑定到对应的 <video> 标签上
 * @param pInfo:String 多媒体流附加信息,可选,默认为 'none'
 * @return src:String 视频预览地址
 */
export const initPullFlow = async (streamId, domId, mute, pInfo, notAutoPlay, audioOnly) => {
  defaultApi.writeLog(`ZEGO:: initPullFlow_start ,streamId :${streamId}`);
  let playChannel;
  resetStreamIdRtcPlayerInfo1(streamId);
  resetStreamIdRtcPlayerInfo(streamId);
  try {
    if (!isFirstHeartBeatReport) {
      isFirstHeartBeatReport = true;
      heartBeatDataReport('start');
    }
  } catch (error) { }
  if (streamIdToPreviewId[streamId] == undefined) {

    defaultApi.writeLog(`ZEGO::  The streamId queue does not contain this streamId ${uiChnIndexs}`);

    const arr = streamId.split('_');
    const [,,userId] = arr;
    if (String(userId).toUpperCase() === String(lecturerUid)) {
      playChannel = arr.length === 4 ? 0 : 1; // 主流使用0,辅流使用1
    } else {
      if (uiChnIndexs.length === 0) {
        defaultApi.writeLog('error', `ZEGO:: all uiChnIndexs are used ${uiChnIndexs}`);
        delete streamIdRtcPlayerInfo[streamId];
        NOTICE.overMaxConnect({streamId, message: 'reach max connect'});
        return {
          videoSrc: '',
          playerId: -2
        };
      }
      playChannel = uiChnIndexs.pop();
    }
    streamIdToPreviewId[streamId] = playChannel;
    defaultApi.writeLog(`streamIdIsNoExited ${playChannel} ${uiChnIndexs}`);
  } else {
    playChannel = streamIdToPreviewId[streamId];
    defaultApi.writeLog(`ZEGO::  The streamId queue contains this streamId ${playChannel} ${uiChnIndexs}`);
    await stopPlayStream(streamId);
    resetStreamIdRtcPlayerInfo(streamId);
  }
  // 判断是否创建或者获取播放通道成功,不成功就删掉重试
  if (playChannel == undefined) {
    delete streamIdToPreviewId[streamId];
    defaultApi.writeLog('error', 'ZEGO::  uiChnIndex is not exist');
    // delete streamIdToBlobUrl[streamId];
    return initPullFlow(streamId, domId, mute, pInfo, notAutoPlay, audioOnly);
  }
  previewIdToStreamId[playChannel] = streamId;
  //是否拉取声音
  activateAudioPlayStream(streamId, !!!mute);
  //是否拉取画面
  activateVideoPlayStream(streamId, !!!audioOnly);
  if (!notAutoPlay) {
    await playStream(streamId, playChannel, pInfo);
  }
  const videoSrc = await startLocalOrRemotePreview(false, streamId, domId);
  return {
    videoSrc,
    playerId: playChannel
  };
};

/**
 * @function 恢复拉指定的流
 * @param streamId:String 被拉取的流的 id,必选
 * @param uiChnIndex:Number 播放通道,可选
 * @param pInfo:String 多媒体流附加信息,可选,默认为 'none'
 * @return Promise | void
 */
export const playStream = (streamId, uiChnIndex, pInfo) => {
  if (typeof uiChnIndex === 'undefined') {
    uiChnIndex = streamIdToPreviewId[streamId];
  }
  if (typeof pInfo === 'undefined') {
    pInfo = 'none';
  }
  defaultApi.writeLog(`avsdk ZEGO::PlayStream streamId: ${streamId} uiChnIndex: ${uiChnIndex}`);
  pullingStore.setData(streamId, { info: pInfo });
  return callMethod('PlayStream', {
    pszStreamID: streamId,
    uiChnIndex,
    pInfo
  });
};

/**
 * 判断是否回收通道
 * @param {number} id 
 */
const isCollectChannel = (id) => {
  return typeof id === 'number' &&
    !uiChnIndexs.includes(id) &&
    id !== 0 && // 主讲主流不回收
    id !== 1; // 主讲辅流不回收
};

/**
 * @function 停止拉指定的流
 * @param streamId:String 被拉取的流的 id,必选
 * @param recovery:Boolean 是否回收播放器, 可选
 * @return Promise | void
 */
export const stopPlayStream = async (streamId, recovery) => {
  const id = streamIdToPreviewId[streamId];
  delete streamIdRtcPlayerInfo[streamId];
  if (recovery && isCollectChannel(id)) {
    uiChnIndexs.push(id);
    defaultApi.writeLog(`avsdk ZEGO::stopPlayStream streamId: ${streamId} id: ${id} uiChnIndexs: ${uiChnIndexs}`);
    delete streamIdToPreviewId[streamId];
    // delete streamIdToBlobUrl[streamId];
  }
  pullingStore.delete(streamId);
  return callMethod('StopPlayStream', {
    pszStreamID: streamId
  });
};

/**
 * @function 停止并回收所有拉流
 * @return Promise | void
 */
export const stopAllPlayStream = () => {
  const toRecoveryArr = [];
  for (let streamId in streamIdToPreviewId) {
    if (streamIdToPreviewId.hasOwnProperty(streamId)) {
      const id = streamIdToPreviewId[streamId];
      if (isCollectChannel(id)) {
        uiChnIndexs.push(id);
      }
      pullingStore.delete(streamId);
      toRecoveryArr.push(callMethod('StopPlayStream', {
        pszStreamID: streamId
      }));
    }
  }
  defaultApi.writeLog(`avsdk ZEGO::stopAllPlayStream uiChnIndexs: ${uiChnIndexs}`);
  streamIdToPreviewId = {};
  return Promise.all(toRecoveryArr);
};

/**
 * @function 切换播放流
 * @param streamId:String 需要停止流的 id,必选
 * @param toStreamId:String 被拉取的流的 id,必选
 * @param toDomId:String <video> 标签的 id,可选
 * @return Promise | void
 */
export const changePullFlow = async (streamId, toStreamId, toDomId) => {
  await stopPlayStream(streamId, true);
  return initPullFlow(toStreamId, toDomId);
};

/**
 * @function 转推,录课
 * @param type:String 必选,'start' | 'stop'
 * @param seq:Number 可选,默认为 1
 * @return Promise | void
 */
export const mixStream = (type, args, seq) => {
  if (!args) {
    const zego = window.zbyAVSDK_init_params.zego;
    const streamId = zego.streamId;
    const sid = zego.sid;
    let pInputStreamList;
    let nInputStreamCount;
    if (type === 'start') {
      pInputStreamList = [{
        szStreamID: streamId,
        layout: {
          top: 0,
          right: zego.previewResolutionWidth,
          bottom: zego.previewResolutionHeight,
          left: 0
        },
        uSoundLevelID: 0,
        nContentControl: 0
      }];
      nInputStreamCount = 1;
    }
    if (type === 'stop') {
      pInputStreamList = [];
      nInputStreamCount = 0;
    }
    if (typeof seq === 'undefined') {
      seq = 1;
    }

    return callMethod('MixStream', {
      config: {
        szOutputStream: zego.recordUrl + '/' + sid,
        bOutputIsUrl: true,
        nOutputFps: zego.previewCaptureFps,
        nOutputBitrate: zego.previewCaptureBitrate,
        nOutputAudioBitrate: zego.previewCaptureBitrate,
        nOutputWidth: zego.previewResolutionWidth,
        nOutputHeight: zego.previewResolutionHeight,
        nOutputAudioConfig: 1,
        pInputStreamList,
        nInputStreamCount,
        pUserData: '',
        nLenOfUserData: 0,
        nChannels: 0,
        nOutputBackgroundColor: 0,
        pOutputBackgroundImage: '',
        bWithSoundLevel: false
      },
      seq
    });
  } else {
    return callMethod('MixStream', args);
  }

};

/**
 * @function 是否拉取音频流--通过流id控制
 * @param streamId:String 被拉取的流的 id,必选
 * @param bActive:Boolean true -> 拉取,false -> 不拉取,必选
 * @return Promise | void
 */
export const activateAudioPlayStream = (streamId, bActive) => {
  streamIdRtcPlayerInfo1[streamId].audio_type = bActive;
  return callMethod('ActivateAudioPlayStream', {
    nChannelIdx: streamIdToPreviewId[streamId],
    bActive
  });
};

/**
 * @function 是否拉取音频流--通过播放器id控制
 * @param operation:Boolean,必选,true -> 拉取,false -> 不拉取,必选
 * @param playerId:Number,必选,播放器id
 * @return Promise | void
 */
export const pullAudioFlow = (playerId, operation, streamid) => {
  defaultApi.writeLog(`pullAudioFlow -- playerId ${playerId} operation ${operation} streamId ${streamid}`);
  if (streamid) {
    if (streamIdToPreviewId[streamid] == undefined) {
      NOTICE.pullAudioFlowError({ streamid });
      return;
    } else {
      playerId = streamIdToPreviewId[streamid]
      defaultApi.writeLog(`pullAudioFlow-playerId ${playerId}`);
    }
  }
  let streamId = previewIdToStreamId[playerId];
  try {
    streamIdRtcPlayerInfo1[streamId].audio_type = operation;
    defaultApi.writeLog(`pullAudioFlow ${streamId}`);
  } catch (e) {
    defaultApi.writeLog(`pullAudioFlow ${streamId} --error ${JSON.stringify(e)}`, null, 'error');
  }
  try {
    dataReport.setPullVoice({
      code: +operation,
      pull_uid: util.getUidByStreamId(streamId),
      pull_streamid: streamId,
      playerId
      // operator:'client'
    });
  } catch (e) { };
  return callMethod('ActivateAudioPlayStream', {
    bActive: operation,
    nChannelIdx: playerId
  });
};

/**
 * @function 是否拉取画面流
 * @param streamId:String 被拉取的流的 id,必选
 * @param bActive:Boolean true -> 拉取,false -> 不拉取,必选
 * @return Promise | void
 */
export const activateVideoPlayStream = (streamId, bActive) => {
  try {
    streamIdRtcPlayerInfo1[streamId].video_type = bActive;
    defaultApi.writeLog(`activateVideoPlayStream ${streamId}`);
  } catch (e) {
    defaultApi.writeLog(`activateVideoPlayStream ${streamId} --error ${JSON.stringify(e)}`, null, 'error');
  }
  return callMethod('ActivateVideoPlayStream', {
    nChannelIdx: streamIdToPreviewId[streamId],
    bActive
  });
};

/**
 * @function 开启/关闭麦克风音量回调
 * @param open:Boolean true -> 开启,false -> 关闭,必选
 * @return void
 */
export const openMicVolumeCb = (open) => {
  // if (open) {
  //   runMicVolumeCb = true;
  // } else {
  //   runMicVolumeCb = false;
  // }
};

/**
 * @function 监听设备的音量变化
 * @param deviceType:Number 音频设备类型,0 -> 麦克风,1 -> 扬声器,必选
 * @param deviceId:String 音频设备 id,必选
 * @return Promise | void
 */
export const setAudioVolumeNotify = (deviceType, deviceId) => {
  return callMethod('SetAudioVolumeNotify', {
    deviceType,
    deviceId
  });
};

/**
 * @function 关闭监听设备的音量变化
 * @param deviceType:Number 音频设备类型,0 -> 麦克风,1 -> 扬声器,必选
 * @param deviceId:String 音频设备 id,必选
 * @return Promise | void
 */
export const stopAudioVolumeNotify = (deviceType, deviceId) => {
  return callMethod('StopAudioVolumeNotify', {
    deviceType,
    deviceId
  });
};

/**
 * @function 启动 soundLevel 监听
 * StartSoundLevelMonitor 后一定要 StopSoundLevelMonitor
 * @return Promise | void
 */
export const startSoundLevelMonitor = () => {
  return callMethod('StartSoundLevelMonitor', {});
};

/**
 * @function 停止 soundLevel 监听
 * @return Promise | void
 */
export const stopSoundLevelMonitor = () => {
  return callMethod('StopSoundLevelMonitor', {});
};

/**
 * @function 设置 soundLevel 的监控周期
 * @param timeInMS:Number 时间周期,单位为毫秒,取值范围 [100, 3000],必选
 * @return Promise | void
 */
export const setSoundLevelMonitorCycle = (timeInMS) => {
  return callMethod('SetSoundLevelMonitorCycle', {
    timeInMS
  });
};

/**
 * @function 单路录制
 * @param type:String start/stop,必选
 * @param szOutputStream:转推地址(支持rtmp/avertp),必选
 * @param szStreamID:推流ID,必选
 * @return Promise | void
 */
/*export const singleRecord = async (type, szOutputStream, szStreamID, seq = 1) => {
  defaultApi.writeLog(`avsdk ZEGO::singleRecord szStreamID: ${szStreamID} szOutputStream: ${szOutputStream} type: ${type}`);
  const zego = window.zbyAVSDK_init_params.zego;
  let pInputStreamList = [];
  let nInputStreamCount = 0;
  if (type === 'start') {
    pInputStreamList = [{
      szStreamID,
      layout: {
        top: 0,
        right: zego.previewResolutionWidth,
        bottom: zego.previewResolutionHeight,
        left: 0
      },
      uSoundLevelID: 0,
      nContentControl: 0
    }];
    nInputStreamCount = 1;
  }
  return callMethod('MixStream', {
    config: {
      szOutputStream,
      bOutputIsUrl: true,
      nOutputFps: zego.previewCaptureFps,
      nOutputBitrate: zego.previewCaptureBitrate,
      nOutputAudioBitrate: zego.previewCaptureBitrate,
      nOutputWidth: zego.previewResolutionWidth,
      nOutputHeight: zego.previewResolutionHeight,
      nOutputAudioConfig: 1,
      pInputStreamList,
      nInputStreamCount,
      pUserData: '',
      nLenOfUserData: 0,
      nChannels: 0,
      nOutputBackgroundColor: 0,
      pOutputBackgroundImage: '',
      bWithSoundLevel: false
    },
    seq
  });
};
*/
export const hasStream = streamId => {
  return Object.keys(streamIdToPreviewId).includes(streamId);
};

/**
 * @function 获取当前视频流的通道
 * @param  streamId 流 id
 * @return channelIndex
 */
export const getChannelIndex = (streamId) => {
  defaultApi.writeLog(`ZEGO : getChannelIndex  streamId: ${streamId} ${JSON.stringify(streamIdToPreviewId)}`);
  return streamIdToPreviewId[streamId];
};

/**
 * @function 获取当前视频流所有的通道
 * @param  streamId 流 id
 * @return channelIndex
 */
export const getAllChannelIndex = () => {
  defaultApi.writeLog(`ZEGO : getAllChannelIndex ${JSON.stringify(streamIdToPreviewId)}`);
  return streamIdToPreviewId;
};


/**
 * @function 录制转推对外接口
 * @param type:String start/stop,必选
 * @param szOutputStream:转推地址(支持rtmp/avertp),必选
 * @param szStreamID:推流ID,必选
 * @return Promise | void
 */
export const singleRecord = async (type, szOutputStream, szStreamID) => {
  defaultApi.writeLog(`avsdk ZEGO::recordPublishTarget szStreamID: ${szStreamID} szOutputStream: ${szOutputStream} type: ${type}`);
  const zego = window.zbyAVSDK_init_params.zego;
  if (!szOutputStream) {
    const sid = zego.sid;
    // 拼接转推地址
    szOutputStream = zego.recordUrl + '/' + sid;
  }
  if (!szStreamID) {
    szStreamID = window.zbyAVSDK_init_params.zego.streamId;
  }
  if (type === 'start') {
    return addPublishTarget(szOutputStream, szStreamID);
  }
  if (type === 'stop') {
    return deletePublishTarget(szOutputStream, szStreamID);
  }
};

/**
 * @function 开始转推
 * @param szTarget:转推地址(支持rtmp/avertp),必选
 * @param szStreamID:推流ID,必选
 * @return Promise | void
 */
export const addPublishTarget = async (szTarget, szStreamID) => {
  defaultApi.writeLog(`avsdk ZEGO::addPublishTarget szStreamID: ${szStreamID} szTarget: ${szTarget}`);
  let res = await callMethod('AddPublishTarget', {
    strTarget: szTarget,
    pszStreamID: szStreamID,
  });
  try {
    dataReport.turnPushResult({
      errorcode: res == 0 ? 0 : 1,
      case: 'turn push',
      streamid: szStreamID
    });
  } catch (e) { };
  return res;
};

/**
 * @function 结束转推
 * @param szTarget:转推地址(支持rtmp/avertp),必选
 * @param szStreamID:推流ID,必选
 * @param seqNum
 * @return Promise | void
 */
export const deletePublishTarget = async (szTarget, szStreamID) => {
  defaultApi.writeLog(`avsdk ZEGO::deletePublishTarget szStreamID: ${szStreamID} szTarget: ${szTarget}`);
  return callMethod('DeletePublishTarget', {
    strTarget: szTarget,
    pszStreamID: szStreamID
  });
};

/**
 * @function 开始本地视频录制
 * @param enableRecord 开始/结束
 * @param storagePath 录制路径
 * @return Promise | void
 */
export const localRecordVideo = async (enableRecord, storagePath) => {
  defaultApi.writeLog(`avsdk ZEGO::localRecordVideo enableRecord: ${enableRecord}, storagePath: ${storagePath}`);
  if (enableRecord) {
    return callMethod('StartRecord', {
      channelIndex: 0,
      recordType: 3,
      enableStatusCallback: true,
      interval: 3000,
      recordFormat: 2,
      isFragment: false,
      storagePath
    });
  } else {
    return callMethod('StopRecord', {
      channelIndex: 0
    });
  }
};

/**
 * @function 本地音频录制
 * @param enableRecord 开始/结束
 * @param storagePath 录制路径
 * @return Promise | void
 */
export const localRecordAudio = async (enableRecord, storagePath) => {
  defaultApi.writeLog(`avsdk ZEGO::localRecordAudio enableRecord: ${enableRecord}, storagePath: ${storagePath}`);
  return callMethod('EnableSelectedAudioRecord', {
    uMask: 1,
    nSampleRate: 16000,
    nChannels: 1,
    enableRecord,
    storagePath,
  });
};
export const setRecordPath = async (idx = 0, path) => {
  defaultApi.writeLog(`avsdk ZEGO::setRecordPath idx: ${idx}, path: ${path}`);
  return callMethod('SetRecordPath', { idx, path });
};

export const getRecordPath = async (idx = 0) => {
  defaultApi.writeLog(`avsdk ZEGO::getRecordPath idx: ${idx}`);
  return callMethod('GetRecordPath', { idx });
};

export const getDesktopPath = () => {
  defaultApi.writeLog('avsdk ZEGO::getDesktopPath');
  return callMethod('GetDesktopPath', {});
};

/**
 * @function 本地录制
 * @param {string | undefined} fileName 
 * @param {number} fileType 1:flv 2:mp4
 * @param {number} idx 0:主流 1:辅流 2:RTMP流
 * @returns {Promise<any>}
 */
export const startCaptureRecord = async (fileName, fileType, idx, path) => {
  defaultApi.writeLog(`avsdk ZEGO::startCaptureRecord fileName: ${fileName}, fileType: ${fileType}, idx: ${idx}, path: ${path}`);
  await setRecordPath(idx, path);
  await startRecord(fileName, fileType, idx);
  await startCapture(idx);
};

export const stopCaptureRecord = async (idx) => {
  defaultApi.writeLog(`avsdk ZEGO::stopCaptureRecord idx: ${idx}`);
  await stopCapture(idx);
  await stopRecord(idx);
};
/**
 * @function 本地录制
 * @param {string | undefined} fileName 
 * @param {number} fileType 1:flv 2:mp4
 * @param {number} idx 0:主流 1:辅流 2:RTMP流
 * @returns {Promise<any>}
 */
const startRecord = async (fileName, fileType, idx) => {
  defaultApi.writeLog(`avsdk ZEGO::startRecord fileName: ${fileName}, fileType: ${fileType}, idx: ${idx}`);
  if (idx === 1 && !screenStreamId) {
    defaultApi.writeLog('avsdk ZEGO::startRecord screenStreamId is null', null, 'error');
    return;
  }
  if (!fileName) {
    const fileNameMap = [localStreamId, screenStreamId, 'rtmp'];
    fileName = `${fileNameMap[idx]}-${util.currentTimeString().replace(/:/g, '-')}`;
  }
  dataReport.startRecord({ fileName, fileType, idx });
  return callMethod('StartRecord2', {
    idx,
    recordType: 3, // 1:音频 2:视频 3:音视频
    fileName,
    recordFormat: fileType,
  });
};
/**
 * 结束音视频录制 channelIndex(number) 录制通道  0主推流通道/ 1辅助推流通道/ 2辅助推流通道
 * @param {number} idx 0 | 1 | 2
 * @returns 
 */
const stopRecord = async (idx) => {
  defaultApi.writeLog(`avsdk ZEGO::stopRecord idx: ${idx}`);
  dataReport.stopRecord({ idx });
  return callMethod('StopRecord', { idx });
};
/**
 * 开启外部音频采集(通知sdk)推流前,调用此接口,publish_channel,number类型,传2代表第三路流
 * @param {number} idx 0 | 1 | 2
 * @returns 
 */
const startCapture = async (idx) => {
  defaultApi.writeLog(`avsdk ZEGO::startCapture idx: ${idx}`);
  return callMethod('StartCapture', { publish_channel: idx });
};
/**
 * 结束外部音频采集(通知sdk)
 * @param {number} idx 0 | 1 | 2
 * @returns 
 */
const stopCapture = async (idx) => {
  defaultApi.writeLog(`avsdk ZEGO::stopCapture idx: ${idx}`);
  return callMethod('StopCapture', { publish_channel: idx });
};

/**
 * @function 设置外部采集设备模块
 * @param  bUseVideoCapture  是否开启外部采集设备模块
 * @attention  必须在  InitSDK  前调用,置空必须在UninitSDK之后
 * @return Promise | void
 */
const setVideoCaptureFactory = (bUseVideoCapture = false, streamChannel) => {
  defaultApi.writeLog('info', 'avsdk ZEGO::setVideoCaptureFactory');
  return callMethod('SetVideoCaptureFactory', {
    bUseVideoCapture,
    streamChannel
  });
};

//设置音频数据来源
const setAudioAuxSource = (source) => {
  defaultApi.writeLog('info', 'avsdk ZEGO::setAudioAuxSource');
  return callMethod('SetAudioAuxSource', {
    source
  });
};

const setVideoSource = (idx, srcType) => {
  return callMethod('SetVideoSource', { idx, srcType });
};

//RTC同屏 辅助通道音频类型  0:静音 / 1:麦克风  / 2:拉流的声音  / 3:1+2 / 4:麦克风+扬声器 / 5:扬声器(不含拉流)
const setAUXAudioType = (type) => {
  defaultApi.writeLog('info', 'avsdk ZEGO::SetAUXAudioType');
  return callMethod('SetAUXAudioType', {
    type
  });
};

//录制通道音频类型  0:静音 / 1:麦克风  / 2:拉流的声音  / 4:1+2 / 8:麦克风+扬声器(不含拉流)
const setThirdAudioType = (type) => {
  defaultApi.writeLog('info', 'avsdk ZEGO::SetThirdAudioType');
  return callMethod('SetThirdAudioType', {
    type
  });
};

//开启音频外部采集 参数:channel   参数类型:int 通道(传1使用辅通道)
const startAudioExCapture = (channel) => {
  defaultApi.writeLog('info', 'avsdk ZEGO::startAudioExCapture');
  return callMethod('StartAudioExCapture', {
    channel
  });
};
/**
 * 第四路流类型设置
 * @param {number} type 6: 视频:主流 音频:麦克风+扬声器 7: 外部音频源,外部视频源,数据来源player
 * @returns 
 */
const setFourthStreamType = (type) => {
  return callMethod('SetFourthStreamType', {
    type
  });
};

//加载截屏插件
const LoadCollectionOutputEntry2 = () => {
  defaultApi.writeLog('info', 'avsdk ZEGO::LoadCollectionOutputEntry2');
  return callMethod('LoadCollectionOutputEntry2', {});
};

//开启同屏
const startMultiScreen = async () => {
  defaultApi.writeLog('info', 'avsdk ZEGO::startMultiScreen');
  // await setVideoCaptureFactory(true,1);
  await LoadCollectionOutputEntry2();
};


/**
 * @function 设置推流时是否发送视频数据
 * @param  mute  true不发送(仅预览),false  发送
 * @param  idx  推流通道索引  0表示主推流通道,默认;1表示第二路流推流通道,无法推出声音
 * @return Promise | void (0代表设置成功,否则设置失败)
 */
const muteVideoPublish = (mute = true, idx = 0) => {
  return callMethod('MuteVideoPublish', {
    mute,
    idx
  });
};

/**
 * @function 开始连麦
 * @param mode:number 采集器默认, 默认0=站立 1=坐立
 * @return Promise | void
 */
export const teacherStartLinkMic = async (mode = 0) => {
  if (mode === 0) {
    await muteVideoPublish(true, 0);
    await setMicrophoneDevice({});
    await enableMic(true);
  } else {
    //开始连麦后,需要打开摄像头(开启外部采集器后,不会争抢摄像头),否则学生拉到的小班流会没有画面
    await enableCamera(true);
    await enableMic(true);
  }
};

/**
 * @function 结束连麦
 * @param mode:number 采集器默认, 默认0=站立 1=坐立
 * @return Promise | void
 */
export const teacherStopLinkMic = async (mode = 0) => {
  // if (mode === 0) {
  //   await muteVideoPublish(false,0);
  // } else {
  //   await setVideoCaptureFactory(false);
  // }
};

/**
  * @function 设置镜像状态(预览)
  * @param { boolean } type true=开启 false=关闭
  * @return: Promise
  */
export const setMirrorStatus = (isMirror) => {
  // return callMethod('SetPreViewMirror', { isMirror });
  return setVideoMirrorMode(isMirror >> 0);
};

/**
  * @function 设置拉流镜像状态
  * @param { boolean } type true=开启 false=关闭
  * @return: Promise
  */
export const setPlayViewMirror = (isMirror) => {
  return callMethod('SetPlayViewMirror', { isMirror });
};

/**
  * @function 设置推流镜像状态
  * @param { boolean } id 采集器Id
  * @param { 0 | 1 | 2 | 3 } mode 0: 预览启用镜像,推流不启用镜像; 1: 预览启用镜像,推流启用镜像; 2: 预览不启用镜像,推流不启用镜像; 3: 预览不启用镜像,推流启用镜像
  * @return: Promise
  */
export const setVideoMirrorMode = (mode, idx = 0) => {
  let realId = mode == 0 ? 2 : 1;
  defaultApi.writeLog(`ZEGO: setVideoMirrorMode, mode: ${mode}, idx:${idx}, realId:${realId}`);
  return callMethod('SetVideoMirrorMode', { idx, mode: realId });
};

//美颜相关
/**
 * @deprecated
  * @function 开启美颜
  * @return: Promise
*/
export const startSenseMe = async () => {
  return callMethod('StartSenseMe', {});
};
/**
 * @deprecated
  * @function 设置美颜参数
  * * @param { number } type  //美颜类型,1、红润强度,3、磨皮强度,4、美白强度,5、大眼,6、瘦脸,7、小脸,8、对比度强度,9、饱和度强度
  * * @param { number } value //美颜数值,有效值范围0~100
  * @return: Promise
*/
export const setBeautifyParam = async (type, value) => {
  return callMethod('SetBeautifyParam', { type, value });
};
/**
 * @deprecated
  * @function 关闭美颜
  * @return: Promise
*/
export const endSenseMe = async () => {
  return callMethod('EndSenseMe', {});
};

export const enableBeautify = (enable, streamId = localStreamId, idx = 0) => {
  defaultApi.writeLog(`avsdk ZEGO::enableBeautify enable: ${enable} idx: ${idx}`);
  return callMethod('EnableBeautify', { enable, streamId, idx });
};

/**
 * 
 * @param {number} mode 美颜类型 101:美白、103:磨皮、201:瘦脸、605:背景虚化、901:自定义背景、202:大眼、603:锐化
 * @param {number} subMode  美白模式【0,2,3】,当前选项:0,
                            磨皮:【1,2】,当前选项:1
                            瘦脸:【0】,
                            背景虚化:【0,1】、当前选项:1
                            自定义背景:【0】、
                            大眼:【0】
                            锐化:【0】
 * @param {number} value  美白: 默认30 ,0表示不美白;
                          磨皮:默认 74, 0表示不磨皮;
                          瘦脸:默认 11, 0表示不瘦脸
                          背景虚化:默认值 0, 0表示不虚化
                          自定义背景:默认值0;非0表示设置背景(当前设计不支持用户自定义背景,)
                          大眼:默认值13,0表示不做大眼
                          锐化:默认值0,0表示不锐化
 * @param {string} streamId  流id
 * @param {string | undefined} image  自定义背景图片地址
 * @returns 
 */
export const setBeautifyParam2 = (mode, subMode, value, streamId = localStreamId, idx = 0, image) => {
  defaultApi.writeLog(`avsdk ZEGO::setBeautifyParam param: ${JSON.stringify({mode, subMode, value, streamId, idx, image})}`);
  return callMethod('SetBeautifyParam', { mode, subMode, value, streamId, idx, image });
};

/**
 * 
 * @param {'UseLoopbackDefault' | 'UseDefaultDevice'} key 
 * @param {'enable'} value 
 * @returns 
 */
export const setEngineConfig = (key, value) => {
  defaultApi.writeLog(`avsdk ZEGO::setEngineConfig key: ${key}, value: ${value}`);
  return callMethod('SetEngineConfig', { key, value });
};

/**
  * @function zego40路拉流混音接口
  * * @param { number } mixMode //混流模式  0=关闭混流  1=开启混流
  * * @param { array } streamIdArr //表示要突出声音的channel
  * * @param { number } mixNum //表示要突出channel的个数, 需要与参数'channels'的数组大小相同
  * @return: Promise
*/
export const setAudioMixMode = async (mixMode, streamIdArr) => {
  let channelsArr = [];
  if (streamIdArr.length > 0) {
    streamIdArr.forEach(item => {
      channelsArr.push(getChannelIndex(item));
    });
  }
  return callMethod('SetAudioMixMode', { mixMode: mixMode, channels: channelsArr, num: channelsArr.length });
};

// 获取TalMedia.dll版本号
export const getSDKVersion = async () => {
  return callMethod('GetSDKVersion', {}).then((e) => {
    return JSON.parse(e.msg).ret;
  });
};

/**
 * @function 获取摄像头所支持的分辨率
 * @param deviceId:String 设备id
 * @return Promise | void
 */
export const getCameraSupportedRes = (deviceId) => {
  return callMethod('GetCameraSupportedResolution', {
    deviceId
  });
};

/**
 * @function 发送SEI
 * @param inData: String  需要传输的音视频次要信息数据,外部输入。
 * @param dataLen: Number     数据长度,不能超过 4096 bytes
 * @param bPacket: Bool   是否采用外部打包好的包头,填写 false。
 * @param idx:Number 推流通道 index
 * 调用频率不能超过帧率,假设推流采用默认帧率15 fps,即调用频率不能超过 1000/15=66.7 ms/次。
 */
export const sendMediaSideInfo = (info) => {
  return callMethod('SendMediaSideInfo', {
    inData: info,
    dataLen: info.length,
    bPacket: false,
    idx: 0
  });
};

//判断当前课型是否为zego推拉RTMP小组课
export const isZegoGroupClass = () => {
  defaultApi.writeLog(`test: isZegoGroupClass--currentClassMode--currentGroupSdkType', ${currentClassMode}, ${currentGroupSdkType}`);
  return currentClassMode == 0 && currentGroupSdkType == 'zego'/*rtmp流为zego*/;
};
//判断当前课型是否为zego推拉RTMP组合小班课
export const isZegoCombinationClass = () => {
  console.log('test: isZegoCombinationClass--currentClassMode--currentGroupSdkType', currentClassMode, currentGroupSdkType);
  return currentClassMode == 2 && currentGroupSdkType == 'zego'/*rtmp流为zego*/;
};
//判断推流类型是否为音频流
export const isStreamAudioMuted = () => {
  return callMethod('IsStreamAudioMuted', {});
};
//判断推流类型是否为视频流
export const isStreamVideoMuted = () => {
  return callMethod('IsStreamVideoMuted', {});
};
/**
 * 获取麦克风响度
 * @returns {Promise<number>}
 */
export const getMicrophoneLoudness = () => {
  return callMethod('GetMicrophonLoudness', {});
};

/**
 * 获取默认扬声器id
 * @returns {Promise<string | null>}
 */
export const getCurrentSpeakerId = () => {
  return callMethod('GetCurrentSpeakerId', {})
    .then((e) => {
      if (e.code === 0) {
        return JSON.parse(e.msg).speakerCurId;
      } else {
        return null;
      }
    });
};
/**
 * 获取默认麦克风id
 * @returns {Promise<string | null>}
 */
export const getCurrentMicrophoneId = () => {
  return callMethod('GetCurrentMicrophoneId', {})
    .then((e) => {
      if (e.code === 0) {
        return JSON.parse(e.msg).microphoneCurId;
      } else {
        return null;
      }
    });
};
/**
 * 获取默认摄像头id
 * @returns {Promise<string | null>}
 */
export const getCurrentCameraId = () => {
  return callMethod('GetCurrentCameraId', {})
    .then((e) => {
      if (e.code === 0) {
        return JSON.parse(e.msg).cameraCurId;
      } else {
        return null;
      }
    });
};

export const takeLocalSnapShot = (streamId = localStreamId, idx = 0) => {
  return callMethod('takeLocalSnapShot', {streamId, idx});
};

export const takeRemoteSnapShot = (streamId) => {
  return callMethod('takeRemoteSnapShot', {streamId});
};

export const getStreamType = async () => {
  try {
    const pushAudio = JSON.parse((await isStreamAudioMuted()).msg).ret;
    const pushVideo = JSON.parse((await isStreamVideoMuted()).msg).ret;
    let stream_type = 'both';
    if (pushAudio && !pushVideo) {
      stream_type = 'video';
    }
    if (!pushAudio && pushVideo) {
      stream_type = 'audio';
    }
    if (pushAudio && pushVideo) {
      stream_type = 'none';
    }
    return stream_type;
  } catch (e) {
    let stream_type = 'none';
    return stream_type;
  }
};

const heartBeatRealKeys = ['video_fps', 'video_bitrate', 'audio_fps', 'audio_bitrate'];

const _heartBeatDataReport = () => {
  const pullInfo = [];
  Object.keys(streamIdRtcPlayerInfo).forEach(streamid => {
    heartBeatRealKeys.forEach(realKey => {
      if (!streamIdRtcPlayerInfo[streamid].hasOwnProperty(realKey)) {
        streamIdRtcPlayerInfo[streamid][realKey] = [];
      }
    });

    //获取拉流类型,后期可写为函数提出去
    if (streamIdRtcPlayerInfo1[streamid].audio_type && streamIdRtcPlayerInfo1[streamid].video_type) {
      streamIdRtcPlayerInfo1[streamid].stream_type = 'both';
    } else if (!streamIdRtcPlayerInfo1[streamid].audio_type && streamIdRtcPlayerInfo1[streamid].video_type) {
      streamIdRtcPlayerInfo1[streamid].stream_type = 'video';
    } else if (streamIdRtcPlayerInfo1[streamid].audio_type && !streamIdRtcPlayerInfo1[streamid].video_type) {
      streamIdRtcPlayerInfo1[streamid].stream_type = 'audio';
    } else {
      streamIdRtcPlayerInfo1[streamid].stream_type = 'none';
    }

    pullInfo.push({
      streamid,
      ...streamIdRtcPlayerInfo[streamid],
      pull_type: streamIdRtcPlayerInfo1[streamid].stream_type,
      volume: streamIdRtcPlayerInfo[streamid].volume.slice(0, streamIdRtcPlayerInfo[streamid].volume.length - 1)
    });
    resetStreamIdRtcPlayerInfo(streamid);
  });
  if (isFirstHeartBeatReport) {
    try {

      //获取推流类型,后期可写为函数提出去
      if (zbysdk.deviceStatus.camera && zbysdk.deviceStatus.microphone) {
        zbysdk.deviceStatus.stream_type = 'both';
      } else if (!zbysdk.deviceStatus.camera && zbysdk.deviceStatus.microphone) {
        zbysdk.deviceStatus.stream_type = 'audio';
      } else if (zbysdk.deviceStatus.camera && !zbysdk.deviceStatus.microphone) {
        zbysdk.deviceStatus.stream_type = 'video';
      } else {
        zbysdk.deviceStatus.stream_type = 'none';
      };
      defaultApi.writeLog(`push_type_zego,camera: ${zbysdk.deviceStatus.camera},microphone: ${zbysdk.deviceStatus.microphone},type: ${zbysdk.deviceStatus.stream_type},a_fps: ${dataZegoCapture.afps}, a_bit: ${dataZegoCapture.akbps}, v_fps: ${dataZegoCapture.fps}, v_bit: ${dataZegoCapture.kbps}`);
      dataReport.heartbeat({
        ...{ ...heartBeatDataReportObj, push_type: zbysdk.deviceStatus.stream_type, volume: heartBeatDataReportObj.volume.slice(0, heartBeatDataReportObj.volume.length - 1) },
        pull_info: pullInfo,
      });
    } catch (e) {
      console.log(e);
    }
  }
  resetHeartBeatDataReportObj();
};

let pointer = 0;
const runHeartBeatCalc = () => {
  Object.keys(streamIdToPreviewId).forEach((streamId) => {
    const data = statsStore.getData(streamId) || {};
    heartBeatDataReportCalc('OnPlayQulityUpdate', data, streamId);
  });

  const data = statsStore.getData(localStreamId) || {};
  heartBeatDataReportCalc('OnPublishQualityUpdate', data, localStreamId);
  ++pointer;

  if (pointer === heartBeatItemCount) {
    pointer = 0;
    _heartBeatDataReport();
  }
};

const heartBeatDataReport = (type) => {
  try {
    if (type === 'start' && !heartBeatDataReportTimer) {
      runHeartBeatCalc();
      heartBeatDataReportTimer = setInterval(() => {
        runHeartBeatCalc();
      }, 2 * 1000);
    }
    if (type === 'stop') {
      statsStore.clear();
      clearInterval(heartBeatDataReportTimer);
      heartBeatDataReportTimer = null;
    }
  } catch (error) {
    console.error(error);
  }
};

const heartBeatDataReportCalc = (name, _d, streamid) => {
  const ctime = Math.round((new Date().getTime()+dataReport.timestamp)/1000);
  // 拉流
  const pullKeys = ['fps', 'kbps', 'afps', 'akbps'];
  if (name === 'OnPlayQulityUpdate') {
    if (streamIdRtcPlayerInfo && streamIdRtcPlayerInfo.hasOwnProperty(streamid)) {
      let isReport = true;
      // streamIdRtcPlayerInfo[streamid].count++;
      heartBeatRealKeys.forEach((realKey, index) => {
        if (_d.hasOwnProperty(pullKeys[index])) {
          if (streamIdRtcPlayerInfo[streamid][realKey] === undefined) {
            streamIdRtcPlayerInfo[streamid][realKey] = [];
            isReport = false;
          }
          // streamIdRtcPlayerInfo[streamid][realKey].push(parseFloat(parseInt(_d[pullKeys[index]])));
        }
      });
      if (isReport) {
        let audio_fps_zego_pull = 0;
        let audio_bitrate_zego_pull = 0;
        let video_fps_zego_pull = 0;
        let video_bitrate_zego_pull = 0;

        let pull_type = 'none';
        if (streamIdRtcPlayerInfo1[streamid].audio_type && streamIdRtcPlayerInfo1[streamid].video_type) {
          audio_fps_zego_pull = parseFloat(parseInt(_d.afps || 0));
          audio_bitrate_zego_pull = parseFloat(parseInt(_d.akbps || 0));
          video_fps_zego_pull = parseFloat(parseInt(_d.fps || 0));
          video_bitrate_zego_pull = parseFloat(parseInt(_d.kbps || 0));
          pull_type = 'both';
        } else if (!streamIdRtcPlayerInfo1[streamid].audio_type && streamIdRtcPlayerInfo1[streamid].video_type) {
          video_fps_zego_pull = parseFloat(parseInt(_d.fps || 0));
          video_bitrate_zego_pull = parseFloat(parseInt(_d.kbps || 0));
          pull_type = 'video';
        } else if (streamIdRtcPlayerInfo1[streamid].audio_type && !streamIdRtcPlayerInfo1[streamid].video_type) {
          audio_fps_zego_pull = parseFloat(parseInt(_d.afps || 0));
          audio_bitrate_zego_pull = parseFloat(parseInt(_d.akbps || 0));
          pull_type = 'audio';
        }
        streamIdRtcPlayerInfo[streamid].audio_fps[pointer] = audio_fps_zego_pull;
        streamIdRtcPlayerInfo[streamid].audio_bitrate[pointer] = audio_bitrate_zego_pull;
        streamIdRtcPlayerInfo[streamid].video_fps[pointer] = video_fps_zego_pull;
        streamIdRtcPlayerInfo[streamid].video_bitrate[pointer] = video_bitrate_zego_pull;

        streamIdRtcPlayerInfo[streamid].ctime[pointer] = ctime;
        streamIdRtcPlayerInfo[streamid].avtimestampdiff[pointer] = _d.avTimestampDiff || 0;
        streamIdRtcPlayerInfo[streamid].pull_loss[pointer] = _d.pktLostRate || 0;
        streamIdRtcPlayerInfo[streamid].pull_delay[pointer] = _d.rtt || 0;
        streamIdRtcPlayerInfo[streamid].pull_type_ex[pointer] = pull_type;
      }
    }
  }
  // 推流
  const pushKeys = ['fps', 'kbps', 'afps', 'akbps'];
  if (name === 'OnPublishQualityUpdate') {
    // heartBeatDataReportObj.count++;
    // heartBeatRealKeys.forEach((realKey, index) => {
    //   if (heartBeatDataReportObj.hasOwnProperty(realKey) && _d.hasOwnProperty(pushKeys[index])) {
    //     heartBeatDataReportObj[realKey].push(parseFloat(parseInt(_d[pushKeys[index]])));
    //   }
    // });
    let audio_fps_zego_push = 0;
    let audio_bitrate_zego_push = 0;
    let video_fps_zego_push = 0;
    let video_bitrate_zego_push = 0;
    let push_type = 'none';
    dataZegoCapture = _d;

    if (userAndDeviceStatusInfo.isPushingFlow) {
      if (zbysdk.deviceStatus.camera && zbysdk.deviceStatus.microphone) {
        audio_fps_zego_push = parseFloat(parseInt(_d.afps || 0));
        audio_bitrate_zego_push = parseFloat(parseInt(_d.akbps || 0));
        video_fps_zego_push = parseFloat(parseInt(_d.fps || 0));
        video_bitrate_zego_push = parseFloat(parseInt(_d.kbps || 0));
        push_type = 'both';
      } else if (!zbysdk.deviceStatus.camera && zbysdk.deviceStatus.microphone) {
        audio_fps_zego_push = parseFloat(parseInt(_d.afps || 0));
        audio_bitrate_zego_push = parseFloat(parseInt(_d.akbps || 0));
        push_type = 'audio';
      } else if (zbysdk.deviceStatus.camera && !zbysdk.deviceStatus.microphone) {
        video_fps_zego_push = parseFloat(parseInt(_d.fps || 0));
        video_bitrate_zego_push = parseFloat(parseInt(_d.kbps || 0));
        push_type = 'video';
      }
    }
    heartBeatDataReportObj.audio_fps[pointer] = audio_fps_zego_push;
    heartBeatDataReportObj.audio_bitrate[pointer] = audio_bitrate_zego_push;
    heartBeatDataReportObj.video_fps[pointer] = video_fps_zego_push;
    heartBeatDataReportObj.video_bitrate[pointer] = video_bitrate_zego_push;

    heartBeatDataReportObj.push_loss[pointer] = _d.pktLostRate || 0;
    heartBeatDataReportObj.push_delay[pointer] = _d.rtt || 0;
    heartBeatDataReportObj.push_type_ex[pointer] = push_type;
    heartBeatDataReportObj.type_backend[pointer] = getBackgroundStatus();
    heartBeatDataReportObj.ctime[pointer] = ctime;
  }
};
//推流字段
const resetHeartBeatDataReportObj = () => {
  heartBeatDataReportObj = {
    ctime: new Array(heartBeatItemCount).fill(0),
    push_type: 'none',
    video_fps: new Array(heartBeatItemCount).fill(0),
    video_bitrate: new Array(heartBeatItemCount).fill(0),
    audio_fps: new Array(heartBeatItemCount).fill(0),
    audio_bitrate: new Array(heartBeatItemCount).fill(0),
    push_loss: new Array(heartBeatItemCount).fill(0),
    push_delay: new Array(heartBeatItemCount).fill(0),
    push_type_ex: new Array(heartBeatItemCount).fill('none'),
    type_backend: new Array(heartBeatItemCount).fill(0),
    volume: ''
  };
};

//记录拉流类型,用作数据上报
const resetStreamIdRtcPlayerInfo1 = (streamId) => {
  streamIdRtcPlayerInfo1[streamId] = {
    audio_type: false,
    video_type: false,
    stream_type: 'none'
  };
};

resetHeartBeatDataReportObj();

//拉流字段
const resetStreamIdRtcPlayerInfo = (streamId) => {
  streamIdRtcPlayerInfo[streamId] = {
    pull_uid: util.getUidByStreamId(streamId),
    streamid: streamId,
    ctime: new Array(heartBeatItemCount).fill(0),
    pull_type: streamIdRtcPlayerInfo1[streamId].stream_type,
    volume: '',
    video_fps: new Array(heartBeatItemCount).fill(0),
    video_bitrate: new Array(heartBeatItemCount).fill(0),
    audio_fps: new Array(heartBeatItemCount).fill(0),
    audio_bitrate: new Array(heartBeatItemCount).fill(0),
    pull_loss: new Array(heartBeatItemCount).fill(0),
    pull_delay: new Array(heartBeatItemCount).fill(0),
    pull_type_ex: new Array(heartBeatItemCount).fill('none'),
    //音画不同步字段
    avtimestampdiff: new Array(heartBeatItemCount).fill(0),
    // 累加
    audio_ifg: 0,
    video_ifg: 0
  };
};

export default {
  loadZego,
  unloadZego,
  startEngine,
  destroyEngine,
  loginChannel,
  logoutChannel,
  setDefaultDevice,
  getVideoDeviceList,
  setVideoDevice,
  enableCamera,
  enableMic,
  setAudioSpeakerCapture,
  getAudioDeviceList,
  setAudioDevice,
  getSpeakerDeviceList,
  setSpeakerDevice,
  getCurrentSpeakerDevice,
  getSpeakerDeviceVolume,
  setSpeakerDeviceVolume,
  setSpeakerDeviceMute,
  getMicrophoneDeviceList,
  setMicrophoneDevice,
  getMicDeviceVolume,
  setMicDeviceVolume,
  setMicDeviceMute,
  getSpeakerSimpleVolume,
  setSpeakerSimpleVolume,
  setSpeakerSimpleMute,
  setPreviewView,
  startPreview,
  startLocalOrRemotePreview,
  setVideoFPS,
  setVideoBitrate,
  setAudioBitrate,
  setVideoCaptureResolution,
  setCameraParam,
  setVideoEncodeResolution,
  init,
  startPublish2,
  stopPublish,
  initPullFlow,
  playStream,
  stopPlayStream,
  stopAllPlayStream,
  changePullFlow,
  mixStream,
  leaveRoomWithChangeSDK,
  leaveRoom,
  isZegoGroupClass,
  isZegoCombinationClass,
  activateAudioPlayStream,
  pullAudioFlow,
  activateVideoPlayStream,
  setAudioVolumeNotify,
  stopAudioVolumeNotify,
  openMicVolumeCb,
  startSoundLevelMonitor,
  stopSoundLevelMonitor,
  setSoundLevelMonitorCycle,
  singleRecord,
  hasStream,
  getChannelIndex,
  addPublishTarget,
  deletePublishTarget,
  localRecordVideo,
  localRecordAudio,
  startCaptureRecord,
  stopCaptureRecord,
  setMirrorStatus,
  getStreamType,
  startSenseMe,
  setBeautifyParam,
  endSenseMe,
  enableBeautify,
  setBeautifyParam2,
  setEngineConfig,
  setPlayViewMirror,
  setMuteStreamAudio,
  setMuteStreamVideo,
  teacherStartLinkMic,
  teacherStopLinkMic,
  getSDKVersion,
  getCameraSupportedRes,
  setAudioMixMode,
  setVideoMirrorMode,
  startMultiScreen,
  getAllChannelIndex,
  startAudioExCapture,
  setFourthStreamType,
  sendMediaSideInfo,
  setVideoSource,
  setAUXAudioType,
  setThirdAudioType,
  loadCollectionInputEntry2,
  stopCheckDevice,
  addListener,
  getDeviceCheckInitStatus,
  setDeviceCheckInitStatus,
  setRecordPath,
  getRecordPath,
  getDesktopPath,
  getMicrophoneLoudness,
  getCurrentSpeakerId,
  getCurrentMicrophoneId,
  getCurrentCameraId,
  takeLocalSnapShot,
  takeRemoteSnapShot,
};