UNPKG

zby-live-sdk

Version:

This is a live SDK for weclassroom.

1,709 lines (1,632 loc) 93.8 kB
/** * @File: Rtc 音视频 SDK * @Author: xuyuanrui * @Email: raywhbxyr@163.com * @Date: 2018-08-10 10:22:47 **/ import dataReport from '../network/dataReport.js'; import {dabanDataReport} from '../network/api.js'; import util from '../util/util.js'; import defaultApi from '../default'; import toolApi from '../tool'; import NOTICE from '../notice'; import { deviceListReport } from './device.js'; import zbysdk from '../zby-live-sdk.js'; // 扩展标识 const extensionId = 'live'; // 端提供的 API 入口 const EM = window.EM; // 维护的一份播放器的 rtcPlayerId 与 streamId 的映射 let streamIdToRtcPlayerId = {}; // let streamIdToBlobUrl = {}; let rtcPlayerIdToStreamId = {}; // 已经停止的播放器 let stopedRtcPlayers = []; // 播放器 id 缓存 let rtcPlayerIds = []; // rtc 当前正在使用的摄像头的设备 id let rtcNowUseCamera = ''; // 是否执行麦克风音量回调的标记 let runMicVolumeCb = false; // 是否创建了用于设备检测的采集器的标记 let deviceCheckerCaptureInit = false; // 是否执行过 Rtc 的 AddListener 的标志 let hasAddListener = false; // 是否在课中的标志 let isInRoom = false; // 是否处于网络错误状态中的标志 let hasNetWrong = false; // 是否加入/离开 rtc room 成功 let hasJoinRtcRoom = false; let hasLeaveRtcRoom = false; // rtc 底层流媒体数据上报地址配置 let _confId = null; // rtc小班课=0;rtc大班课=1 const classType = 0; // 监听id let EMListenerId = 0; //静音推流时,音量值是否上抛上层标志 false=没有声音不往上层通知/true=有声音有上层通知 let isNoticeMicVolumeRtc = false; let isNoticeCarVolumeRtc = false; /* 1001--网络错误 1002--播放器内部错误 1003--调度服务器错误 1004--转发服务器错误 1005--无可用转发服务器 2003--对端尚未进入教室 2013–网络断开 */ const rtcPlayerErrorCodes = ['1001', '1002', '1003', '1004', '1005', '2003', '2013', '2008', '1006']; /* 2001-- 网络错误,通知业务层检查网络设置和推流地址, 对于直播, id与AddPushUrl返回值对应 2002-- 网络恢复, 对于直播, id与AddPushUrl返回值对应 2003--网络断开 2004--转发服务器错误 2005--调度服务器错误 2006--无可用转发服务器 2007--转发服务器能力不足 2008--摄像头视频宽高错误 2009--视频编码器初始化错误 2010--错误的摄像头ID 2011--错误的录音设备ID 2012--抓屏参数错误 2013--打开摄像头失败,一种可能是摄像头被其它进程占用 2014–-打开麦克风失败 2015--打开播放文件失败 3206--推流ID被占用 3309--只要 10 秒内和服务器无法建立连接就会触发一次且仅触发一次(仅互动流) */ const rtcCaptureErrorCodes = ['2001', '2002', '2003', '2004', '2005', '2006', '2007', '2008', '2009', '2010', '2012', '2013', '3206', '3309']; // 心跳 let heartBeatDataReportObj = {}; let heartBeatDataReportTimer = null; let isFirstHeartBeatReport = false; let streamIdRtcPlayerInfo = {}; let streamIdRtcPlayerInfo1 = {}; const rtcPlayerCodeToNames = { '1002': 'err_core', '1003': 'err_schedule_server', '1004': 'err_relay_server', '1005': 'err_no_available_relay_server', '2003': 'st_streamNotReady', '2013': 'network_disconnect' }; //上报拼接状态 //window.zbyAVSDK_init_params let paramInfo ; let captureBefore ;//运控获取,一直不变(拼接前摄像头采集分辨率) let captureEncodeBefore;//摄像头拼接前编码分辨率云控拿取 let screenEncodeBefore;//截屏窗拼接前编码分辨率 东奇提供 // let screenBefore;//拼接前截屏窗采集分辨率 let captureAfter ;//一直不变(拼接前摄像头采集分辨率同上captureBefore) let captureEncodeAfter;//摄像头拼接后编码分辨率 let screenEncodeAfter;// // let screenAfter;// let currStreamIdRtcPlayerInfo ; let stStreamNotReadyReport = {}; //记录底层推流回调的值 let dataRtcCapture = {}; /** * @function 调用端提供的回调方法 * @param name:String 回调方法名,必选 * @param args:Object 回调参数,必选 * @return Promise | void */ const callMethod = (name, args) => { // EM 是寄宿于端的,浏览器中并不存在,为防止报错需要先进行能力检测 if (EM) { return new Promise((resolve, reject) => { let noneCamera = (name === 'SetCameraDevice' && !args.close && !args.deviceID); let noneMicrophone = (name === 'SetAudioDevice' && !args.deviceID); let noneSpeaker = (name === 'SetSpeakerDevice' && !args.speakId); if (noneCamera || noneMicrophone || noneSpeaker) { return resolve(); } EM.CallMethod( extensionId, name, JSON.stringify({...args, classType}), (code, msg) => { resolve({ code, msg }); defaultApi.writeLog(`${name} Code: ${code}\nParams: ${JSON.stringify({...args, classType})}\nMessage: ${msg}`); let _msg = msg && JSON.parse(msg); // 数据上报 switch (name) { case 'SetAppId': break; case 'SetConfId': break; case 'SetAudioDevice': if (code === 0 && _msg.ret === true) { window.zbyAVSDK_device_checker_init.microphone.use = args.deviceID; } break; case 'SetCameraDevice': if (code === 0 && _msg.ret === true) { window.zbyAVSDK_device_checker_init.camera.use = args.deviceID; if (args.deviceID) { rtcNowUseCamera = args.deviceID; } } break; case 'SetSpeakerDevice': if (code === 0) { window.zbyAVSDK_device_checker_init.speaker.use = args.speakId; } break; case 'CreateRtcCapture': if (code === 0 && args.renderid === 'deviceCheckerInit') { deviceCheckerCaptureInit = true; } break; } } ); }); } }; /** * @function 加载 Rtc 扩展 * @param extensionVersion:String 扩展版本号,必选 * @return Promise | void */ export const loadRtc = (extensionVersion, cb) => { // EM 是寄宿于端的,浏览器中并不存在,为防止报错需要先进行能力检测 if (EM) { return new Promise((resolve, reject) => { removerListener(); EM.Load( extensionId, extensionVersion, false, (code, msg) => { defaultApi.writeLog(`loadRtc Code: ${code}\nMessage: ${msg}`); addListener(); resolve(); } ); }); } }; /** * @function 卸载 Rtc 扩展 * @return Promise | void */ export const unloadRtc = () => { // EM 是寄宿于端的,浏览器中并不存在,为防止报错需要先进行能力检测 if (EM) { return new Promise((resolve, reject) => { EM.UnLoad( extensionId, (code, msg) => { defaultApi.writeLog(`unloadRtc Code: ${code}\nMessage: ${msg}`); resolve(); } ); }); } }; /** * @function 添加扩展监听机制 * @return void */ const addListener = () => { // EM 是寄宿于端的,浏览器中并不存在,为防止报错需要先进行能力检测 if (EM && !hasAddListener) { hasAddListener = true; EM.AddListener(extensionId, (event, data) => { if (data && data.indexOf(extensionId) > -1) { try { const _data = JSON.parse(data); if (_data[extensionId] !== undefined && EMListenerId == 0) { EMListenerId = _data[extensionId]; defaultApi.writeLog(`RTC::addListener-- EMListenerId: ${EMListenerId}`); } } catch (error) {} } if (!event || !data) { return; } let _d = JSON.parse(data); let _e = JSON.parse(event); if (_d && _d.classType === 1) { return false; } if (_e.hasOwnProperty('event_name')) { let name = _e.event_name; currStreamIdRtcPlayerInfo = streamIdRtcPlayerInfo[_d.streamid]; switch (name) { case 'deviceStateChanged': { const deviceType = _d.deviceType; // 0=添加设备 1=移除设备 string let state = _d.deviceState; let deviceNameCamera = ''; let deviceNameMic = ''; let deviceNameSpeak = ''; switch (deviceType) { // 摄像头热插拔 case '1': { setCameraDevice({deviceId: state == 1 ? '' : _d.deviceID, close: false, operationType: 'hotPlug', deviceState: state, changedDeviceId: _d.deviceID, changedDeviceName: _d.deviceName}); deviceNameCamera = _d.deviceName; } break; // 麦克风热插拔 case '2': { setMicrophoneDevice({deviceId: state == 1 ? '' : _d.deviceID, operationType: 'hotPlug', deviceState: state, changedDeviceId: _d.deviceID, changedDeviceName: _d.deviceName}); deviceNameMic = _d.deviceName; } break; // 扬声器热插拔,暂时不做处理 case '3': { // console.log('hsghsghsg扬声器热插拔',_d,state); setSpeakerDevice({ speakId: state == 1 ? '' : _d.deviceID, operationType: 'hotPlug', deviceState: state, changedDeviceId: _d.deviceID, changedDeviceName: _d.deviceName}); deviceNameSpeak = _d.deviceName; } break; } // try { // dataReport.deviceTest({ // device: deviceType, // fore_state: state == '0' ? 1 : 2 , // camera_name: deviceNameCamera, // microphone_name: deviceNameMic, // speaker_name: deviceNameSpeak // }); // } catch (e){ // }; } break; case 'rtcCaptureCycleDataReport'://推流 fps、bitrate { var keys = Object.keys(_d); if (isNoticeMicVolumeRtc) { if (keys.indexOf('volum') > -1 && window.zbyAVSDK_init_sdk_type === 'rtc') { NOTICE.captureMicVolumeChanged({ volume: +_d.volum }); } } if (keys.indexOf('volum') > -1) { // heartBeatDataReportObj.volume.push(_d.volum); // console.log('声音回调',`${new Date().getMinutes()}:${new Date().getSeconds()}`,_d.volum); heartBeatDataReportObj.volume = heartBeatDataReportObj.volume + _d.volum + ','; } } // 拉流 case 'rtcPlayCycleDataReport': { if (_d.errorcode && _d.errorcode == 7000) { NOTICE.playerVolumeChanged({ userId: util.getUidByStreamId(_d.streamid), streamId: _d.streamid, volume: _d.volume }); // currStreamIdRtcPlayerInfo && currStreamIdRtcPlayerInfo.volume.push(_d.volume); // streamIdRtcPlayerInfo[_d.streamid] && streamIdRtcPlayerInfo[_d.streamid].volume.push(_d.volume); if(streamIdRtcPlayerInfo[_d.streamid]) { streamIdRtcPlayerInfo[_d.streamid].volume = streamIdRtcPlayerInfo[_d.streamid].volume + _d.volume + ','; } }; if (_d.hasOwnProperty('codecBitRate')) { if (name === 'rtcPlayCycleDataReport') { NOTICE.pullQuality({ video: { userId: util.getUidByStreamId(_d.streamid), streamId: _d.streamid, bitrate: Math.round(_d.codecBitRate), fps: Math.round(_d.codecFrameRate), volume: _d.volume, realTimeVolume: _d.realTimeVolume } }); NOTICE.playLossAndDelay({ userId: util.getUidByStreamId(_d.streamid), delay:_d.delay, lostrate:_d.lostrate }); } if (name === 'rtcCaptureCycleDataReport') { NOTICE.pushQuality({ video: { bitrate: Math.round(_d.codecBitRate), fps: Math.round(_d.codecFrameRate) } }); NOTICE.pushLossAndDelay({ userId: util.getUidByStreamId(_d.streamid), delay:_d.delay, lostrate:_d.lostrate }); } heartBeatDataReportCalc(name, _d); } if (_d.hasOwnProperty('realTimeVolume') || _d.hasOwnProperty('volume')) { NOTICE.volumeChange({ userId: util.getUidByStreamId(_d.streamid), streamId: _d.streamid, volume: _d.volume }); } } break; case 'OnLiveCaptureEvent': break; case 'OnRtcCaptureEvent': { if (_d && _d.hasOwnProperty('errorcode')) { let code = _d.errorcode; switch (code) { case '1003': if (window.zbyAVSDK_init_params.rtc.isTurnPush) { sendRecordingMsg({ cmd: 'start' }); } if (hasNetWrong) { hasNetWrong = false; NOTICE.networkRecovery({ message: 'Network recovery' }); } try { dataReport.publishResult({ code: '0', // publish_streamid: window.zbyAVSDK_init_params.rtc.streamId, }); NOTICE.pushFlowSuccess({code:0,publish_streamid: _d.streamid}); } catch (e) {}; break; case '1005': // 摄像头设备打开成功 // 大班数据上报 try { dabanDataReport({ action: 'open_camera', camera_status: 1, uid: window.zbyAVSDK_init_params.rtc.userId }); } catch(e) { } break; case '2011': // 摄像头设备打开失败 // 大班数据上报 try { dabanDataReport({ action: 'open_camera', camera_status: 0, uid: window.zbyAVSDK_init_params.rtc.userId }); } catch(e){} break; case '2008': // 错误的摄像头ID setCameraDevice({deviceId: '', close: false, operationType: 'deviceError'}); break; case '1006': // 麦克风设备打开成功 break; case '2010': // 麦克风设备打开失败 case '2013': // 麦克风设备打开失败 case '2012': // 麦克风设备打开失败 case '2009': // 错误的录音设备ID setMicrophoneDevice({deviceId: '', operationType: 'deviceError'}); break; // 重试推流 case '2003': case '2004': case '2005': case '2006': case '2007': // pushFlowExceptionHandler(); break; case '2001': hasNetWrong = true; defaultApi.writeLog('error', 'Network error'); NOTICE.networkError({ message: 'Network error' }); break; } if (rtcCaptureErrorCodes.includes(code)) { try { dataReport.publishResult({ code: code, // publish_streamid: window.zbyAVSDK_init_params.rtc.streamId, }); } catch (e) {}; } } } break; case 'OnSingleRecordResponse': { NOTICE.recordResponse(_d); } break; case 'live_recovery_start': { console.log('live_recovery_start'); NOTICE.liveRecoveryStart(); } break; case 'recordingMsg': { if (_d.cmd === 0) { const {errorcode, streamId} = _d; try { dataReport.turnPushResult({ errorcode, case: 'turn push', streamid: streamId }); } catch (e) {}; } } break; case 'OnRecvMediaSideInfo': { console.log('我是sei',_data); NOTICE.seiReceived({data: _data}); } break; case 'VideoFrameInterval': { currStreamIdRtcPlayerInfo && (currStreamIdRtcPlayerInfo.video_ifg += _d.video_ifg); } break; case 'AudioFrameInterval': { currStreamIdRtcPlayerInfo && (currStreamIdRtcPlayerInfo.audio_ifg += _d.audio_ifg); } break; case 'OnRtcPlayError': { if (_d && _d.hasOwnProperty('errorcode')) { let code = _d.errorcode; switch (code) { // 重试拉流 case '1002': case '1003': case '1004': case '1005': case '1006': case '1007': // pullFlowExceptionHandler(_d.streamid); NOTICE.playError({ userId: util.getUidByStreamId(_d.streamid), streamId: _d.streamid, message: 'play error' }); break; case '1001': hasNetWrong = true; writeLog('error', 'Network error'); NOTICE.networkError({ message: 'Network error' }); break; } if (rtcPlayerErrorCodes.includes(code)) { pullResultReport(code, _d.streamid); } NOTICE.playErrorDetail({ type: rtcPlayerCodeToNames[code], data: { userId: util.getUidByStreamId(_d.streamid), streamId: _d.streamid, allData: _d } }); } } break; case 'OnRtcPlayStatus': { if (_d && _d.hasOwnProperty('errorcode')) { const code = _d.errorcode; switch (code) { case '2004': if (window.zbyAVSDK_init_params.rtc.role === 'student') { // 大班数据上报 try { dabanDataReport({ action: 'interact_play', teammate_streamid: _d.streamid, uid: window.zbyAVSDK_init_params.rtc.userId }); } catch(e) { } } try{ NOTICE.firstVideoSize({ userId: util.getUidByStreamId(_d.streamid), streamId: _d.streamid, allData: _d }); dataReport.firstVideoSize({ pull_streamid: _d.streamid, pull_uid: util.getUidByStreamIdDr(_d.streamid), code:'0' }); } catch (e) { }; pullResultReport(0, _d.streamid); defaultApi.writeLog(`RTC: pullStream success & haveFirstVideoSize streamId: ${_d.streamid}`); break; case '2014': try{ NOTICE.firstAudioSize({ userId: util.getUidByStreamId(_d.streamid), streamId: _d.streamid, allData: _d }); dataReport.firstAudioSize({ pull_streamid: _d.streamid, pull_uid: util.getUidByStreamIdDr(_d.streamid), code:'0' }); } catch (e) { }; break; default: break; } if (rtcPlayerErrorCodes.includes(code)) { if (code == 2003) { //对端未推流 let first_time_marker = new Date().getTime(); if (!stStreamNotReadyReport.hasOwnProperty(_d.streamid)) { stStreamNotReadyReport[_d.streamid] = new Date().getTime(); pullResultReport(code, _d.streamid); } else { if (first_time_marker - stStreamNotReadyReport[_d.streamid] >= 10*1000) { pullResultReport(code, _d.streamid); stStreamNotReadyReport[_d.streamid] = new Date().getTime(); } } } else { pullResultReport(code, _d.streamid); } }; NOTICE.playErrorDetail({ type: rtcPlayerCodeToNames[code], data: { userId: util.getUidByStreamId(_d.streamid), streamId: _d.streamid, allData: _d } }); } } break; case 'beforeCombineCapInfoMsg': { screenEncodeBefore = _d.width+'*'+_d.height; try { dataReport.splitState({ camera_split_start_resolution : captureBefore ,//云控取一直不变ok camera_split_start_encode_resolution : captureEncodeBefore ,//摄像头拼接前编码分辨率云控拿取 screen_split_start_encode_resolution: screenEncodeBefore ,//截屏窗拼接前编码分辨率 东奇提供 screen_split_start_resolution :screenEncodeBefore ,//截屏窗拼接前采集分辨率 split:'开始拼接' }); } catch (e){}; } break; case 'afterCombineInfoMsg': { captureEncodeAfter = _d.head_width+ '*'+ _d.head_height; screenEncodeAfter = _d.cap_width+'*'+_d.cap_height; try { dataReport.splitState({ camera_split_end_resolution : captureAfter ,//云控取一直不变ok camera_split_end_encode_resolution : captureEncodeAfter ,//摄像头拼接后编码分辨率 screen_split_end_encode_resolution: screenEncodeAfter ,//截屏窗拼接后编码分辨率 screen_split_end_resolution :screenEncodeAfter,//截屏窗拼接后采集分辨率 split:'拼接完成' }); } catch (e){}; } break; } // 去掉声音回调日志 if(zbysdk.openListenerLog) { console.log(`[zby-live-sdk]--RTC::Listener:: event: ${name}, data: ${data}`); } } }); } }; //拉流上报 const pullResultReport = (code, streamid) => { try { dataReport.pullResult({ code: code, pull_uid: util.getUidByStreamId(streamid), pull_streamid: streamid }); NOTICE.pullFlowResult({code:code,pull_streamid: streamid}); } catch (e) {}; }; /** * @function 添加扩展监听机制 * @return void */ const removerListener = () => { hasAddListener = false; defaultApi.writeLog(`RTC::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 RTC::try to pushFlow again'); await stopPush(); await startPush(); }; /** * @function 拉流异常恢复 */ export const pullFlowExceptionHandler = async (streamId) => { await new Promise((resolve, reject) => { setTimeout(() => { resolve(); }, 1000); }); defaultApi.writeLog('断网重拉'); defaultApi.writeLog(`avsdk RTC::try to pullFlow again, streamId : ${streamId}`); await stop(streamId); await play(streamId); }; /** * @function 设置 appId * @param appId:String 长度不能大于 100,必选 * @return Promise | void */ export const setAppId = (appId) => { return callMethod('SetAppId', { appId }); }; /** * @function 设置用户名 * @param userName:String 用户名,必选 * @return Promise | void */ export const setUserName = (userName) => { return callMethod('SetUserName', { userName }); }; /** * @function 设置用户 id * @param userId:Number 用户 id,必选 * @return Promise | void */ export const setUserId = (userId) => { return callMethod('SetUserId', { userId: userId + '' }); }; /** * @function 设置课节 id * @param confId:String 长度不能大于 100 * @return Promise | void */ export const setConfId = (confId) => { defaultApi.writeLog(`RTC SetConfId: ${confId}`); _confId = confId; return callMethod('SetConfId', { confId }); }; /** * @function 进入音视频房间 * @return Promise | void */ export const joinRtcRoom = async () => { await callMethod('JoinRoom', {}); return new Promise((resolve, reject) => { let timer = setInterval(() => { if (hasJoinRtcRoom) { clearInterval(timer); resolve(); } }, 50); }); }; /** * @function 退出音视频房间 * @return Promise | void */ export const leaveRtcRoom = async () => { await callMethod('LeaveRoom', {}); return new Promise((resolve, reject) => { let timer = setInterval(() => { if (hasLeaveRtcRoom) { clearInterval(timer); resolve(); } }, 50); }); }; /** * @function 开始混流 * @param args:Object 混流设置信息 * @return Promise | void * { 'sequence':x,//混流任务序列号 'inputStreams':[ { 'confId':'xxx', 'streamId':'xxxx', 'mediaControl':x,//0所有1音频2视频 'left':x, 'top':x, 'right':x, 'bottom':x } ], 'outputStream':{ 'pushType';x, 'rtmpUrls';'xxx', 'videoFps':x,//视频帧率 'videoRate':x,//视频码率 'audioRate':x,//音频码率 'videoWidth':x,//合成视频宽度 'videoHeight':x,//合成视频高度 'audioFormat':x,//音频格式,0aac 'audioChannels':x,//音频声道数目 'backgroudColor':x//背景色10进制? } } */ export const startRoomMixRecord = (args) => { return callMethod('StartRoomMixRecord', args); }; /** * @function 开始混流 id * @param args: Object 停止混流设置信息 * {'sequence':x}//混流任务序列号 * @return Promise | void */ export const stopRoomMixRecord = (args) => { return callMethod('StopRoomMixRecord', args); }; /** * @function 开始单路录制--暂停使用 * @param sequence: String * @param streamId: String 流 id * @return Promise | void */ export const startSingleRecord = (sequence = '', streamId) => { return callMethod('StartSingleRecord', { sequence, streamId, confId: _getConfId(streamId) }); }; /** * @function 停止单路录制--暂停使用 * @param streamId: String 停止混流设置信息 * @return Promise | void */ export const stopSingleRecord = (streamId) => { return callMethod('StopSingleRecord', { streamId, confId: _getConfId(streamId) }); }; /** * @function 创建 Rtc Capture对象 * @param renderId:String 采集器的渲染 id,必选 * @return Promise | void */ export const createRtcCapture = (renderId) => { if (renderId === 'deviceCheckerInit' && deviceCheckerCaptureInit) { return; } return callMethod('CreateRtcCapture', { source: 'camera', renderid: renderId }); }; // 设置采集器配置 const setRtcCaptureParams = async (args ,from) => { if (!from) { await setCaptureRenderID(args.streamId); await setCaptureStreamId(args.streamId); } await setCameraParam(args.previewResolutionWidth, args.previewResolutionHeight, args.previewCaptureFps); await setEncodeParam(args.encodedResolutionWidth, args.encodedResolutionHeight, args.encodeCaptureFps); await setBitRate(args.encodeCaptureBitrate); }; /** * @function 加载采集插件的输入数据 //调用后,rtc 可以向采集插件传送摄像头数据,用于拼接 * @return: Promise */ const rtcLoadCollectionEntry = () => { return callMethod('RtcLoadCollectionEntry', {}); }; /** * @function 加载采集插件的输出数据 //调用后,rtc 可以向采集插件传送学生流数据(加载截屏插件,获取截屏插件设置拉流头像回调函数) * @param stream_ids array 设置需要拼接的学生头像流id,按数组顺序拼接 * @return: Promise */ export const rtcLoadCollectionEntry2 = (stream_ids) => { return callMethod('RtcLoadCollectionEntry2', {stream_ids}); }; /** * @function 获取摄像头个数 * @return Promise | void */ export const getCameraDeviceCount = () => { return callMethod('GetCameraDeviceCount', {}); }; /** * @function 获取摄像头列表 * @return Promise | void */ export const getCameraDevice = async () => { let cameraListObject = JSON.parse((await callMethod('GetCameraDevice', {})).msg); let cameraKeys = []; if (cameraListObject) { cameraKeys = Object.keys(cameraListObject); } let cameraList = []; for (let i = 0, len = cameraKeys.length; i < len; i++) { cameraList.push({ deviceId: cameraListObject[cameraKeys[i]].deviceID, deviceName: cameraListObject[cameraKeys[i]].deviceName, isDefault: cameraListObject[cameraKeys[i]].bDefault }); deviceListReport.cameraList[cameraListObject[cameraKeys[i]].deviceID] = cameraListObject[cameraKeys[i]].deviceName; // console.log('hsghsghsg_9',deviceListReport.cameraList); } 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 = cameraList; return cameraList; }; /** * @function 获取扬声器个数 * @return Promise | void */ export const getSpeakerDeviceCount = () => { return callMethod('GetSpeakerDeviceCount', {}); }; /** * @function 获取扬声器列表 * @return Promise | void */ export const getSpeakerDevice = async () => { let speakerListObject = JSON.parse((await callMethod('GetSpeakerDevice', {})).msg); let speakerKeys = []; if (speakerListObject) { speakerKeys = Object.keys(speakerListObject); } let speakerList = []; for (let i = 0, len = speakerKeys.length; i < len; i++) { speakerList.push({ deviceId: speakerListObject[speakerKeys[i]].deviceID, deviceName: speakerListObject[speakerKeys[i]].deviceName, isDefault: speakerListObject[speakerKeys[i]].bDefault }); deviceListReport.speakerList[speakerListObject[speakerKeys[i]].deviceID] = speakerListObject[speakerKeys[i]].deviceName; // console.log('hsghsghsg_8',deviceListReport.speakerList); } 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 获取麦克风个数 * @return Promise | void */ export const getMicrophoneDeviceCount = () => { return callMethod('GetMicrophoneDeviceCount', {}); }; /** * @function 获取麦克风列表 * @return Promise | void */ export const getMicrophoneDevice = async () => { let microphoneListObject = JSON.parse((await callMethod('GetMicrophoneDevice', {})).msg); let microphoneKeys = []; if (microphoneListObject) { microphoneKeys = Object.keys(microphoneListObject); } let microphoneList = []; for (let i = 0, len = microphoneKeys.length; i < len; i++) { microphoneList.push({ deviceId: microphoneListObject[microphoneKeys[i]].deviceID, deviceName: microphoneListObject[microphoneKeys[i]].deviceName, isDefault: microphoneListObject[microphoneKeys[i]].bDefault }); deviceListReport.micList[microphoneListObject[microphoneKeys[i]].deviceID] = microphoneListObject[microphoneKeys[i]].deviceName; } 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) => { let {deviceId, operationType, deviceState, changedDeviceId} = args; let deviceName = ''; let microPhoneData; if (!deviceId) { microPhoneData = await getMicrophoneDevice(); 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) {}; await callMethod('SetAudioDevice', { deviceID: deviceId }); if (operationType == 'hotPlug' || operationType == 'deviceError') { if(!microPhoneData){ microPhoneData = await getMicrophoneDevice(); deviceName = deviceListReport.micList[deviceId]; } NOTICE[operationType]({ deviceType: 'microphone', useDeviceId: deviceId, useDeviceName: deviceName, deviceList: microPhoneData, deviceState, changedDeviceId }); } }; /** * @function 获取麦克风音量 * @return Promise | void */ export const getMicrophoneVolume = () => { return callMethod('GetMicrophoneVolume', {}); }; /** * @function 设置麦克风音量 * @param volume:Number 音量,必选 * @param bMuteDevice:Boolean 是否静音,可选 * 不传的话默认为 false 不静音 * @return Promise | void */ export const setMicrophoneVolume = (volume, bMuteDevice) => { if (typeof bMuteDevice === 'undefined') { bMuteDevice = false; } return callMethod('SetMicrophoneVolume', { bMuteDevice, volume }); }; /** * @function 开启/关闭麦克风音量回调 * @param open:Boolean true -> 开启,false -> 关闭,必选 * @return void */ export const openMicVolumeCb = (open) => { // if (open) { // runMicVolumeCb = true; // } else { // runMicVolumeCb = false; // } }; /** * @function 获取当前应用程序音量 * @return Promise | void */ export const getCurrentAppSessionVolume = () => { return callMethod('GetCurrentAppSessionVolume', {}); }; /** * @function 设置当前应用程序音量 * @param volume:Number 音量,必选 * @param mute:Boolean 是否静音,可选 * 不传的话默认为 false 不静音 * @return Promise | void */ export const setCurrentAppSessionVolume = (volume, mute) => { if (typeof mute === 'undefined') { mute = false; } return callMethod('SetCurrentAppSessionVolume', { muted: mute, volume }); }; /** * @function 设置当前应用程序静音 * @param mute:Boolean 是否静音,必选 * @return Promise | void */ export const setApplicationMute = (mute) => { return callMethod('SetApplicationMute', { muted: mute }); }; /** * @function 设置预览窗口 * @param x:Number x 坐标,必选,推荐 0 * @param y:Number y 坐标,必选,推荐 0 * @param width:Number 宽,必选,推荐 300 * @param height:Number 高,必选,推荐 300 * @return Promise | void */ export const setPreviewPos = (x, y, width, height) => { return callMethod('SetPreviewPos', { x, y, width, height }); }; /** * @function 指定摄像头设备 * @param deviceId:String 摄像头设备 id,必选 * @param close:Boolean true -> 关闭摄像头,false -> 不关闭摄像头 * Rtc 关闭摄像头是通过指定空的设备来实现的,因此需要做特化 * @param operationType:String 操作类型,可选 * 'device_error' -> 设备出错处理,'plug_and_unplug' -> 热插拔处理, * 不传即是普通的设置摄像头设备的行为 * @return Promise | void */ export const setCameraDevice = async (args) => { defaultApi.writeLog(`sdk action : setCameraDevice args:${JSON.stringify(args)}`); let {deviceId, close, operationType, deviceState, changedDeviceId} = args; let deviceName = ''; let cameraData; if (!deviceId && !close) { cameraData = await getCameraDevice(); if (cameraData.length) { let hasGetCamare = false; for (let item of cameraData) { if (item.isDefault) { deviceId = item.deviceId; deviceName = item.deviceName; hasGetCamare = true; break; } } if (!hasGetCamare) { deviceId = cameraData[0].deviceId; deviceName = cameraData[0].deviceName; } deviceId = cameraData[0].deviceId; deviceName = cameraData[0].deviceName; } else { deviceId = ''; } if (deviceId === '') { NOTICE.noDevice({ deviceType: 'camera' }); } } try { dataReport.setDevice({ device_type:1, device_id:deviceId, device_name:deviceListReport.cameraList[deviceId], operationType, fore_state: operationType == 'hotPlug' ? +deviceState+1 : '-' }); window.zbyAVSDK_device_checker_init.camera.name = deviceListReport.cameraList[deviceId]; } catch (e) {}; await callMethod('SetCameraDevice', { deviceID: deviceId, close }); if (operationType == 'hotPlug' || operationType == 'deviceError') { if(!cameraData){ cameraData = await getCameraDevice(); deviceName = deviceListReport.cameraList[deviceId]; } NOTICE[operationType]({ deviceType: 'camera', useDeviceId: deviceId, useDeviceName: deviceName, deviceList: cameraData, deviceState, changedDeviceId }); } try { NOTICE.useredCamera({ deviceId, deviceName }); } catch (e){ }; }; /** * @function 获取摄像头所支持的分辨率 * @param deviceId:String 设备id * @return Promise | void */ export const getCameraSupportedRes = (deviceId) => { return callMethod('GetCameraSupportedResolution', { deviceId }); }; /** * @function 设置事件上报链接 * @param reportSvr:String 上报服务器链接,必选 * @return Promise | void */ export const setEventReportSvr = (reportSvr) => { return callMethod('SetEventReportSvr', { reportSvr }); }; /** * @function 设置摄像头参数 * @param width:Number 分辨率宽,必选,推荐 1280 * @param height:Number 分辨率高,必选,推荐 720 * @param frameRate:Number 帧率,必选,推荐 15 * @return Promise | void */ export const setCameraParam = (width, height, frameRate) => { return callMethod('SetCameraParam', { width, height, frameRate }); }; /** * @function 指定扬声器 * @param speakId:String 扬声器 id,必选 * @return Promise | void */ export const setSpeakerDevice = async (args) => { defaultApi.writeLog(`sdk action : setSpeakerDevice args:${JSON.stringify(args)}`); let {speakId, operationType, deviceState, changedDeviceId} = args; let deviceName = ''; let speakerData; if (!speakId) { speakerData = await getSpeakerDevice(); if (speakerData.length) { let hasGetSpeaker = false; for (let item of speakerData) { if (item.isDefault) { speakId = item.deviceId; deviceName = item.deviceName; hasGetSpeaker = true; break; } } if (!hasGetSpeaker) { speakId = speakerData[0].deviceId; deviceName = speakerData[0].deviceName; } } else { speakId = ''; } if (speakId === '') { NOTICE.noDevice({ deviceType: 'speaker' }); } } try { dataReport.setDevice({ device_type:3, device_id:speakId, device_name:deviceListReport.speakerList[speakId], operationType, fore_state: operationType == 'hotPlug' ? +deviceState+1 : '-' }); window.zbyAVSDK_device_checker_init.speaker.name = deviceListReport.speakerList[speakId]; } catch (e) { console.error('error',e); }; // console.log('hsghsghsg3',speakId); await callMethod('SetSpeakerDevice', { speakId }); if (operationType == 'hotPlug' || operationType == 'deviceError') { if(!speakerData){ speakerData = await getSpeakerDevice(); deviceName = deviceListReport.speakerList[speakId]; } NOTICE[operationType]({ deviceType: 'speaker', useDeviceId: speakId, useDeviceName: deviceName, deviceList: speakerData, deviceState, changedDeviceId }); } }; /** * @function 设置扬声器音量 * @param volume:Number 音量,必选 * @param muted:Boolean 是否静音,可选 * 不传的话就默认为 false 不静音 * @return Promise | void */ export const setSpeakerVolume = (volume, muted) => { if (typeof muted === 'undefined') { muted = false; } return callMethod('SetSpeakerVolume', { muted, volume }); }; /** * @function 设置扬声器静音 * @param mute:Boolean 是否静音,可选 * @return Promise | void */ export const setSpeakerMute = (mute) => { // try { // dataReport.deviceChange({ // device: 3, // device_state: mute ? 0 :1 // }); // } catch (e){}; return callMethod('SetSpeakerMute', { muted: mute }); }; /** * @function 获取扬声器音量 * @return Promise | void */ export const getSpeakerVolume = () => { return callMethod('GetSpeakerVolume', {}); }; /** * @function 设置拉流的播放器静音 * @param streamId:String 被拉取的流的 id,必选 * @param mute:Boolean 是否静音,必选 true 静音| false不静音 * @return Promise | void */ export const setMute = (streamId, mute) => { const rtcPlayerId = streamIdToRtcPlayerId[streamId]; return pullAudioFlow(rtcPlayerId, mute); }; /** * @function 是否拉取音频流--通过播放器id控制 * @param operation:Boolean,必选,true 静音| false不静音 * @param playerId:Number,必选,播放器id * @return Promise | void */ export const pullAudioFlow = (playerId, operation, operator, streamid) => { defaultApi.writeLog(`pullAudioFlow -- playerId ${playerId} operation ${operation} streamId ${streamid}`); if(streamid){ if(streamIdToRtcPlayerId[streamid] == undefined) { NOTICE.pullAudioFlowError({streamid}) return }else{ playerId = streamIdToRtcPlayerId[streamid] console.log('pullAudioFlow-playerId',playerId,streamIdToPreviewId) } } let streamId = rtcPlayerIdToStreamId[playerId]; try { streamIdRtcPlayerInfo1[streamId].audio_type = !operation; console.log('pullAudioFlow_rtc ::rtcPlayerIdToStreamId',streamId,rtcPlayerIdToStreamId) console.log('pullAudioFlow_rtc ::streamIdRtcPlayerInfo1',streamId,streamIdRtcPlayerInfo1); } catch (e) { console.log('pullAudioFlow_rtc ::streamIdRtcPlayerInfo1--error',e); }; // console.log('hghghg',streamIdRtcPlayerInfo1[streamId]); try { if(operator) { dataReport.setPullVoice({ code:+!operation, pull_uid: util.getUidByStreamId(streamId), pull_streamid:streamId, playerId }); } } catch (e) {}; return callMethod('SetMute', { muted: operation, id: playerId }); }; /** * @function 控制只拉音频流 * @param streamId:String 被拉取的流的 id,必选 * @param audioOnly:Boolean 是否只拉音频流,必选,true -> 只拉音频流,false -> 音视频都拉 * @return Promise | void */ export const controlPullAudioOnly = (streamId, audioOnly) => { try { streamIdRtcPlayerInfo1[streamId].video_type = !audioOnly; console.log('controlPullAudioOnly_rtc ::streamIdRtcPlayerInfo1',streamId,streamIdRtcPlayerInfo1); } catch (e) { console.log('controlPullAudioOnly_rtc ::streamIdRtcPlayerInfo1--error',e); } const rtcPlayerId = streamIdToRtcPlayerId[streamId]; return callMethod('ControlPullAudioOnly', { id: rtcPlayerId, audioOnly }); }; /** * @function 启动摄像头 * @return Promise | void */ export const startPreview = () => { // try { // dataReport.deviceChange({ // device: 1, // device_state: 1 // }); // } catch (e){}; return callMethod('StartPreview', {}); }; /** * @function 设置推流的编码参数 * @param width:Number 宽,必须,推荐 1280 * @param height:Number 高,必须,推荐 720 * @param frameRate:Number 码率,必须,推荐 15 * @return Promise | void */ export const setEncodeParam = (width, height, frameRate) => { return callMethod('SetEncodeParam', { width, height, frameRate }); }; /** * @function 设置推流的比特率 * @param bps:Number 码率,必选,推荐 512000 * @return Promise | void */ export const setBitRate = (bps) => { return callMethod('SetBitRate', { bps }); }; /** * @function 设置推流的的 streamId * @param streamId:String 推流 id,必选,长度不能超过100个字符 * @return Promise | void */ export const setCaptureStreamId = (streamId) => { return callMethod('SetCaptureStreamId', { streamid: streamId }); }; /** * @function 设置本地摄像头采集器的渲染 id * @param renderId:String 渲染 id,传入推流 id 即可,必选 * @return Promise | void */ export const setCaptureRenderID = (renderId) => { return callMethod('SetCaptureRenderID', { renderid: renderId }); }; /** * @function 开始推流到服务器 * 如果收到 error,SDK 回到 startPush 之前的状态, * 应用程序需要重新设置正确的参数,并 startPush * @return Promise | void */ export const startPush = () => { if (!isFirstHeartBeatReport) { isFirstHeartBeatReport = true; heartBeatDataReport('start'); } return callMethod('StartPush', {}); }; /** * @function 停止推流 * @return Promise | void */ export const stopPush = () => { // try { // dataReport.stopPublish({publish_streamid:window.zbyAVSDK_init_params.rtc.streamId}); // } catch (error) { // } return callMethod('StopPush', {}); }; /** * @function 关闭摄像头 * @return Promise | void */ export const stopPreview = () => { // try { // dataReport.deviceChange({ // device: 1, // device_state: 0 // }); // } catch (e){}; return callMethod('StopPreview', {}); }; /** * @function 销毁采集器对象 * @return Promise | void */ export const captureDestroy = async () => { await stopPreview(); return callMethod('CaptureDestroy', {}); }; /** * @function 销毁引擎 * @return Promise | void */ export const destroyEngine = () => { return callMethod('DestroyEngine', {}); }; /** * @function 创建视频播放器 * @param pos:Object * { * x, // Number,x 坐标,必选,推荐 500 * y, // Number,y 坐标,必选,推荐 500 * width, // Number,宽,必选,推荐 300 * height // Number,高,必选,推荐 300 * } * @param renderId:String,渲染 id,必选 * @return Promise | void */ export const createRtcPlayer = (pos, renderId) => { return callMethod('CreateRtcPlayer', { pos, renderid: renderId }); }; /** * @function 设置被拉取的流的 id * @param streamId:String 被拉取的流的 id,必选 * @return Promise | void */ export const setPlayerStreamId = (rtcPlayerId, streamId) => { const args = { streamid: streamId, id: rtcPlayerId, // 跨组拉流 confId: _getConfId(streamId) }; defaultApi.writeLog(`RTC::SetPlayerStreamId streamId: ${streamId} id: ${rtcPlayerId} confId: ${util.getDataFromStreamUrl('roomId', streamId)} args : ${JSON.stringify(args)} `); return callMethod('SetPlayerStreamId', args); }; /** * @function 设置被拉取的流的渲染 id * @param renderId:String 拉流的渲染 id,必选,传入被拉取的流的 id 即可 * @return Promise | void */ export const setPlayRenderID = (rtcPlayerId, renderId) => { defaultApi.writeLog(`RTC::SetPlayRenderID streamId: ${renderId} id: ${rtcPlayerId}`); return callMethod('SetPlayRenderID', { renderid: renderId, id: rtcPlayerId }); }; /** * @function 关闭客户端 * @param streamId:String 被拉取的流的 id,必选 * @return Promise | void */ export const playDestroy = (streamId) => { const rtcPlayerId = streamIdToRtcPlayerId[streamId]; defaultApi.writeLog(`RTC::PlayDestroy streamId: ${streamId} id: ${rtcPlayerId}`); delete streamIdToRtcPlayerId[streamId]; delete streamIdRtcPlayerInfo[streamId]; return callMethod('PlayDestroy', { id: rtcPlayerId }); }; /** * @function 开启本地或者远程的视频视图 * @param isLocal:Boolean 是否是本地的视频预览,必选 * @param streamId:String 要拉取的视频流的 id,可选,只有拉取远程的视频流的时候才是必选的 * @param domId:String <video> 标签的 id,可选 * 如果传了 domId,就把视频绑定到对应的 <video> 标签上 * @return Promise | void,可从 Promise 中获取 src,Promise.then((src) => {}) */ export const startLocalOrRemotePreview = async (isLocal, streamId, domId, rtcPlayerId) => { defaultApi.writeLog(`${streamId}----streamId-rtc----`); let isUpdateChromeVersion = await util.getChromeVersion(); // 预览本地视频的话,流 id 从全局配置中获取 if (isLocal) { streamId = isInRoom ? (window.zbyAVSDK_init_params && window.zbyAVSDK_init_params.rtc.streamId) : 'deviceCheckerInit'; } const externalConstraints = { audio: false, video: { mandatory: { chromeMediaSource: 'external', chromeMediaSourceId: `ems://TM/${streamId}` } } }; return new Promise((resolve, reject) => { const handleExternalSuccess = (stream) => { defaultApi.writeLog(`${stream}----stream-rtc----`); stream.oninactive = () => { defaultApi.writeLog('Stream inactive'); }; const src = isUpdateChromeVersion? stream : window.URL.createObjectURL(stream); console.log('内核升级', isUpdateChromeVersion); if (domId && document.querySelector(domId)) { if(isUpdateChromeVersion) { console.log('内核升级1'); document.querySelector(domId).srcObject = src; } else { console.log('内核升级2'); document.querySelector(domId).src = src; } } defaultApi.writeLog(`setVidoeSrc, rtcPlayerId: ${rtcPlayerId}, 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 bee