zby-live-sdk
Version:
This is a live SDK for weclassroom.
1,709 lines (1,632 loc) • 93.8 kB
JavaScript
/**
* @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