import { Injectable } from '@angular/core';
import { Observable, Subject, BehaviorSubject } from 'rxjs';
import { UserService } from './user.service';
declare var $: any;
declare var TRTC: any;
@Injectable({
providedIn: 'root'
})
export class TrtcService {
sdkAppId: number;
userSig: string;
cameras: Array<any> = [];
microphones: Array<any> = [];
cameraId: any;
microphoneId: any;
client: any = null;
public isJoined: boolean = false;
public IsInitDevice: boolean = false;
public initDeviceMsg: string = "正在检测设备中,请稍后再试";
config: TRTCConfig;
constructor(private userService: UserService) {
this.config = new TRTCConfig();
}
/*
* 监听进房/退房的用户
*/
socketSub: Subject<any> = new BehaviorSubject<any>(null);
/*
* 初始化配置
*/
init() {
this.initDevice();
}
/*
* 生成用户签名
*/
generateUserSig() {
return new Promise((resolve, reject) => {
var that = this;
this.userService.getTrtcUserSig({ Id: this.config.userId, Remark: this.config.fkTb }).subscribe(result => {
if (result.code == 100 && result.data) {
that.sdkAppId = result.data.SdkAppId;
that.userSig = result.data.UserSig;
} else {
alert.bind('生成签名失败,请联系管理员');
}
resolve(true);
});
});
}
/*
* 创建 Client 对象
*/
async createClient() {
if (this.client) {
return;
}
try {
this.client = TRTC.create();
this.installEventHandlers();
console.log(`Client [${this.config.userId}] created.`);
} catch (e) {
alert(`Failed to create Client [${this.config.userId}].`);
}
}
/*
* 进房
*/
async join() {
if (this.isJoined) {
console.warn(`has been joined ${this.config.userId}`);
return;
}
if (this.client == null) {
await this.createClient();
}
if (!this.userSig) {
await this.generateUserSig();
if (!this.userSig) {
alert.bind('生成签名失败,请联系管理员');
return;
}
}
try {
await this.client.enterRoom({
strRoomId: this.config.roomId,
sdkAppId: this.sdkAppId,
userId: this.config.userId,
userSig: this.userSig
});
this.isJoined = true;
if (this.config.video) this.setLocalVideo(true);
if (this.config.audio) this.setLocalAudio(true);
} catch (e) {
console.error(`Join room ${this.config.roomId} failed, please check your params. Error: ${e.message}`);
this.reportTrtcflow('Join room failed, please check your params. Error:', '2joinRoom', e);
}
}
/*
* 退房
*/
async leave() {
if (!this.isJoined) {
console.warn('leave() - please join() firstly');
return;
}
try {
await this.client.exitRoom();
this.isJoined = false;
if (this.config.video) this.setLocalVideo(false);
if (this.config.audio) this.setLocalAudio(false);
} catch (error) {
console.error(`Leave room failed. Error: ${error.message_}`);
this.reportTrtcflow('leaveRoom:', '5leaveRoom', error);
}
}
/*
* 监听事件
* https://web.sdk.qcloud.com/trtc/webrtc/v5/doc/zh-cn/module-EVENT.html
*/
installEventHandlers() {
this.client.on(TRTC.EVENT.KICKED_OUT, this.handleKickedOut.bind(this));
this.client.on(TRTC.EVENT.REMOTE_USER_ENTER, this.handleRemoteUserEnter.bind(this));
this.client.on(TRTC.EVENT.REMOTE_USER_EXIT, this.handleRemoteUserExit.bind(this));
this.client.on(TRTC.EVENT.REMOTE_VIDEO_AVAILABLE, this.handleRemoteVideoAvailable.bind(this));
this.client.on(TRTC.EVENT.REMOTE_VIDEO_UNAVAILABLE, this.handleRemoteVideoUnavailable.bind(this));
this.client.on(TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, this.handleRemoteAudioAvailable.bind(this));
this.client.on(TRTC.EVENT.REMOTE_AUDIO_UNAVAILABLE, this.handleRemoteAudioUnavailable.bind(this));
this.client.on(TRTC.EVENT.SCREEN_SHARE_STOPPED, this.handleScreenShareStopped.bind(this));
}
uninstallEventHandlers(offAll: boolean = false) {
if (offAll) {
this.client.off('*');
} else {
this.client.off(TRTC.EVENT.KICKED_OUT, this.handleKickedOut.bind(this));
this.client.off(TRTC.EVENT.REMOTE_USER_ENTER, this.handleRemoteUserEnter.bind(this));
this.client.off(TRTC.EVENT.REMOTE_USER_EXIT, this.handleRemoteUserExit.bind(this));
this.client.off(TRTC.EVENT.REMOTE_VIDEO_AVAILABLE, this.handleRemoteVideoAvailable.bind(this));
this.client.off(TRTC.EVENT.REMOTE_VIDEO_UNAVAILABLE, this.handleRemoteVideoUnavailable.bind(this));
this.client.off(TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, this.handleRemoteAudioAvailable.bind(this));
this.client.off(TRTC.EVENT.REMOTE_AUDIO_UNAVAILABLE, this.handleRemoteAudioUnavailable.bind(this));
this.client.off(TRTC.EVENT.SCREEN_SHARE_STOPPED, this.handleScreenShareStopped.bind(this));
}
}
handleKickedOut(event) {
const { userId } = event;
this.socketSub.next({ Id: this.config.userId, IsJoined: false });
console.log('KickedOut ' + userId);
}
handleRemoteUserEnter(event) {
const { userId } = event;
this.socketSub.next({ Id: userId, IsJoined: true });
console.log("handleRemoteUserEnter " + event);
}
handleRemoteUserExit(event) {
const { userId } = event;
this.socketSub.next({ Id: userId, IsJoined: false });
console.log('UserExit ' + userId);
}
handleRemoteVideoAvailable(event) {
var that = this;
const { userId, streamType } = event;
if (that.config.subscribeUser.length == 0 || that.config.subscribeUser.indexOf(userId) > -1) {
setTimeout(function () {
that.setRemoteVideo(userId, true, streamType);
console.log('RemoteVideoAvailable ' + userId);
}, 10);
}
}
handleRemoteVideoUnavailable(event) {
const { userId, streamType } = event;
this.setRemoteVideo(userId, false, streamType);
console.log('RemoteVideoUnavailable ' + userId);
}
handleRemoteAudioUnavailable(event) {
}
handleRemoteAudioAvailable(event) {
}
handleScreenShareStopped() {
}
/*
* 设置本地音频(开/关)
* https://web.sdk.qcloud.com/trtc/webrtc/v5/doc/zh-cn/tutorial-15-basic-dynamic-add-video.html
*/
setLocalAudio(mute: boolean = true) {
if (mute) {
this.client.startLocalAudio();
} else {
this.client.stopLocalAudio();
}
}
/*
* 设置本地视频(开/关)
*/
setLocalVideo(mute: boolean = true) {
if (mute) {
this.client.startLocalVideo({ view: `video-${this.config.userId}`});
} else {
this.client.stopLocalVideo();
}
}
/*
* 设置本地视频流编码,120p为小流
* @param isSmall 表示是否拉取小流
*/
setLocalVideoProfile(profile: number = 120) {
if (profile == 120) {
this.client.updateLocalVideo({ option: { small: `${profile}p` } });
} else {
this.client.updateLocalVideo({ option: { small: false, profile: `${profile}p` } });
}
}
/*
* 设置远端视频(开/关)
*/
setRemoteVideo(userId: string, mute: boolean = true, streamType: string = "main") {
if (mute) {
this.client.startRemoteVideo({ userId, streamType, view: `video-${userId}`, option: { small: this.config.remoteVideoSmall } });
} else {
this.client.stopRemoteVideo({ userId, streamType });
}
}
/*
* 静音远端用户
* @param userId 用户ID,*表示所有用户
* @param mute 表示是否静音
*/
muteRemoteAudio(userId: string, mute: boolean) {
this.client.muteRemoteAudio(userId, mute);
}
/*
* 设置远端用户音量
* @param userId 用户ID,*表示所有用户
* @param volume 音量大小,取值范围为0 - 100,默认值为 100
*/
setRemoteAudioVolume(userId: string, volume: number) {
this.client.setRemoteAudioVolume(userId, volume);
}
/*
* 动态开关远端视频小流
* @param userId 用户ID,*表示所有用户
* @param isSmall 表示是否拉取小流
*/
smallRemoteVideo(userId: string, isSmall: boolean = false) {
this.client.updateRemoteVideo({ userId, streamType: "main", option: { small: isSmall } });
}
/*
* 检查设备
*/
async initDevice() {
try {
let that = this;
await navigator.mediaDevices.getUserMedia({
audio: this.config.audio,
video: this.config.video
}).then(function (stream) {
stream.getTracks().forEach(track => track.stop());
that.reportTrtcflow('', '0initDevice', null);
});
this.IsInitDevice = true;
console.log("检查设备完成");
} catch (err) {
this.IsInitDevice = false;
console.error(`${err.name}: ${err.message}`);
switch (err.name) {
case "NotFoundError":
case "DevicesNotFoundError":
this.initDeviceMsg = "未找到麦克风或摄像头,无法开启视频会议。";
break;
case "NotReadableError":
case "TrackStartError":
this.initDeviceMsg = "麦克风和摄像头被使用,开启视频将会失败。";
break;
case "OverconstrainedError":
case "ConstraintNotSatisfiedErrror":
this.initDeviceMsg = "获取麦克风或摄像头失败,请联系系统管理员。";
break;
case "NotAllowedError":
case "PermissionDeniedError":
this.initDeviceMsg = "如果不允许当前页面访问麦克风和摄像头权限,您在发布本地流的时候可能会失败。";
break;
default:
this.initDeviceMsg = "如果不允许当前页面访问麦克风和摄像头权限,您在发布本地流的时候可能会失败。";
break;
}
confirm.bind(this.initDeviceMsg);
this.reportTrtcflow(`摄像头或麦克风权限出错. Error: `, '0initDevice', err);
}
}
/*
* 更新设备
*/
async updateDevice() {
try {
const updateDevice = async () => {
this.cameras = await TRTC.getCameraList();
this.microphones = await TRTC.getMicrophoneList();
console.log("cameras", this.cameras);
console.log("microphones", this.microphones);
}
await updateDevice();
// 设备更新
navigator.mediaDevices.addEventListener('devicechange', async () => {
await updateDevice();
});
} catch (e) {
console.error('get device failed', e);
}
}
/*
* 切换视频设备
*/
async switchDeviceVideo(videoId) {
if (videoId) {
try {
await this.client.updateLocalVideo({ option: { cameraId: videoId } });
console.log('Switch video device success');
} catch (error) {
console.error('switchDevice failed', error);
}
}
}
/*
* 切换音频设备
*/
async switchDeviceAudio(audioId) {
if (audioId) {
try {
await this.client.updateLocalAudio({ option: { microphoneId: audioId } });
console.log('Switch audio device success');
} catch (error) {
console.error('switchDevice failed', error);
}
}
}
/**
* 上报事件到自己的日志平台,如果是正式发布
* @param msg
* @param mode 类型:初始化本地语音流、视频流等等
*/
reportTrtcflow(msg: string, mode: string, error: any) {
if (mode && mode.length) {
let errmsg = '';
if (error) {
errmsg = error.message_ ? error.message_ : error.message;
}
let errcode = '';
if (error) {
if (error.getCode) {
errcode = error.getCode();
}
}
let name = '';
if (error) {
if (error.name) {
name = error.name;
}
}
let fixedRoom = '';
if (this.config) {
fixedRoom = `${this.config.roomId}::${this.config.userId}`;
}
let data = `${fixedRoom}::${mode}::${msg} ${errmsg} :: ${errcode} :: ${name}`;
this.userService.addReportTrtc({ NotTitle: data });
}
}
}
@Injectable({
providedIn: 'root'
})
export class TRTCConfig {
userId: string;//用户id(必填)
roomId: string;//房间号(必填)
audio: boolean = true;// true 表示开启语音
video: boolean = true;// true 表示开启视频
isSubscribe: boolean = true;// true 表示订阅远端视频流
subscribeUser: Array<any> = [];//为空表示订阅全部,有值表示订阅指定部分
remoteVideoSmall: boolean = true;//拉流端选择拉大流或小流 true 表示拉小流,false 表示拉大流
fkTb: string = "notice";//验证
}