import { Injectable } from '@angular/core'; import { AlertController } from '@ionic/angular'; import * as Parse from 'parse'; import { AiChatService } from './aichart.service'; import { HttpService } from './http.service'; declare const AgoraRTC: any; @Injectable({ providedIn: 'root', }) export class LiveService { isAnchor: boolean = false; //是否是主播 options: { appid: string; channel: string; token: string; } = { appid: '71d3357d920d4352b39ec8b8d26a7cb9', channel: '', token: '', }; localTracks: any = { audioTrack: null, videoTrack: null, }; rid?: string; //房间id(channel) profile?: any = localStorage.getItem('profile'); client: any; //客户端 company: string = ''; UID: any; tools: any = { audio: false, //是否关闭音频 camera: false, //是否切换摄像头 mute: false, //是否静音 }; user_published_list = new Set(); //已发布列表 timer: any; //轮询获取频道token的定时 alert: any; //提示框 media_devices: { //音视频设备列表 audioDevices: Array; videoDevices: Array; } = { audioDevices: [], videoDevices: [], }; currentUsedDevice: { audioDevice: string; videoDevice: string } = { //当前使用的设备 audioDevice: '', videoDevice: '', }; room?: Parse.Object; //直播间 connection_state?: string; //连接状态 surplusNumber: number = 0; //剩余可通话时长(单位:秒s) countdown: number = 0; // 新增倒计时变量 timer_countdown: any; liveLog?: Parse.Object; //直播记录 constructor( private http: HttpService, private aiServ: AiChatService, private alertController: AlertController ) { this.client?.leave(); this.company = this.aiServ.company; this.getProfile(); } async getProfile() { if (this.profile) return; let queryProfile = new Parse.Query('Profile'); queryProfile.equalTo('user', Parse.User.current()?.id); queryProfile.notEqualTo('isDeleted', true); queryProfile.equalTo('isCross', true); this.profile = await queryProfile.first(); this.profile?.id && localStorage.setItem('profile', JSON.stringify(this.profile.toJSON())); } /* 初始化Agora */ initAgora() { this.timer && clearTimeout(this.timer); this.timer_countdown && clearInterval(this.timer_countdown); this.options['token'] = ''; this.options['channel'] = ''; this.client?.leave(); this.client = AgoraRTC.createClient({ mode: 'rtc', codec: 'h264' }); AgoraRTC.enableLogUpload(); } // 获取所有音视频设备 getDevices() { AgoraRTC.getDevices() .then((devices: any) => { this.media_devices.audioDevices = devices.filter(function ( device: any ) { return device.kind === 'audioinput'; }); this.media_devices.videoDevices = devices.filter(function ( device: any ) { return device.kind === 'videoinput'; }); console.log(this.media_devices); this.currentUsedDevice = { audioDevice: this.media_devices.audioDevices[0].deviceId, videoDevice: this.media_devices.videoDevices[0].deviceId, }; // return Promise.all([ // AgoraRTC.createCameraVideoTrack(), // AgoraRTC.createMicrophoneAudioTrack(), // ]); }) .then((tracks: any) => { console.log(tracks); }) .catch((err: any) => { console.warn('获取媒体权限失败', err); this.alertTips('获取媒体权限失败'); }); } /* 获取token */ async getToken(room: Parse.Object) { this.room = room; this.isAnchor = room?.get('user')?.id === Parse.User.current()?.id; this.timer && clearTimeout(this.timer); let remoteEle = document.getElementById('vice-video'); (remoteEle as any).style.display = 'none'; //获取频道token记录 if (this.isAnchor) { this.UID = 111111; let uid = Parse.User.current()?.id; if (!uid) { this.timer = setTimeout(() => { this.getToken(room); }, 2000); return; } let baseurl = 'https://server.fmode.cn/api/webrtc/build_token'; let reqBody = { company: this.company, // this.aiSer.company, profile: room?.get('profile').id, channelName: room?.get('profile').id, }; let data: any = await this.http.httpRequst(baseurl, reqBody, 'POST'); console.log(data); if (data.code == 200) { this.options.token = data.data.token; this.options.appid = data.data.appid; this.options.channel = room?.get('profile').id; } } else { let data = await this.updateToken(room?.get('profile').id); console.log(data); if (!data?.token) { this.timer = setTimeout(() => { this.getToken(room); }, 2000); return; } this.options.token = data.token; this.options.channel = data.channel; } this.join(); } async updateToken(pid: string) { let sql = `select "rtc"."objectId" as "rid","rtc"."channel", "rtc"."token", "rtc"."expiraTime" from "RtcToken" as "rtc" where "rtc"."profile" = '${pid}' and "rtc"."expiraTime" >= now() order by "createdAt" desc limit 1`; let tokenData: any = await this.http.customSQL(sql); if ( tokenData && tokenData.code == 200 && tokenData.data && tokenData.data.length > 0 ) { return tokenData.data[0]; } else { return null; } } /* 进入频道 */ async join() { let path = location.pathname; if (path.indexOf('/live/link-room/') == -1) return; console.log('频道:', this.options.channel); this.client .join( this.options.appid, this.options.channel, this.options.token, this.UID ) .then(async (uid: any) => { // await this.client.setClientRole('host'); console.log('进入频道当前uid:', uid); this.connection_state = this.client.connectionState; if (!this.isAnchor) { // 观众进入直播间,创建直播记录 await this.createLiveLog(uid); } else { //主播进入直播间直接发送自己推流 await this.publishSelf(); } await this.joinReady(); this.afterJoin(); }) .catch((err: any) => { console.log('进入频道失败:', err); }); } /* 发布本地视频 */ async publishSelf() { try { let data = await Promise.all([ /* 创建音频和视频轨道 */ AgoraRTC.createMicrophoneAudioTrack(this.currentUsedDevice.audioDevice), AgoraRTC.createCameraVideoTrack(this.currentUsedDevice.videoDevice), ]); this.localTracks.audioTrack = data[0]; this.localTracks.videoTrack = data[1]; // console.log(this.localTracks); let remoteEle = document.getElementById('vice-video'); if (remoteEle) { remoteEle.textContent = ''; remoteEle.style.display = 'block'; } this.localTracks.videoTrack.play('vice-video'); //播放自己视频渲染 if (this.tools['audio']) { this.localTracks.audioTrack.setEnabled(false); } else { this.localTracks.audioTrack.setEnabled(true); } await this.client.publish(Object.values(this.localTracks)); } catch (err) { console.log('发布本地视频失败:', err); this.alertTips('发布本地视频失败:'); } } /* 订阅远程视频 */ async joinReady() { this.client.remoteUsers.forEach((user: any) => { console.log('remoteUsers', user.uid); this.client.subscribe(user, 'audio').then((audioTrack: any) => { this.user_published_list.add(user); audioTrack.setVolume(0); audioTrack.play(); }); this.client.subscribe(user, 'video').then((videoTrack: any) => { let remoteEle = document.getElementById('video'); if (remoteEle) { remoteEle.textContent = ''; } videoTrack.play('video'); }); }); this.client.on('user-joined', (user: any) => { console.log(user, `${user.uid} 加入频道`); }); this.client.on('user-published', async (user: any, mediaType: string) => { console.log('用户推流成功', user); await this.client.subscribe(user, mediaType); let remoteEle = document.getElementById('video'); if (remoteEle) { remoteEle.textContent = ''; } if (mediaType === 'video') { user.videoTrack.play('video'); this.alert?.dismiss(); } if (mediaType === 'audio') { user.audioTrack.play(); this.user_published_list.add(user); } }); this.client.on('user-unpublished', async (user: any, mediaType: string) => { if (mediaType === 'audio') { console.log('对方已静音'); this.user_published_list.delete(user); } if (mediaType === 'video') { console.log('用户取消推流'); let remoteEle = document.getElementById('video'); if (remoteEle) { remoteEle.textContent = '对方离开直播间'; } //主播离开,停止计时计费 if (!this.isAnchor) { this.client.leave(); } this.alertTips('对方已离开直播间'); } }); this.client.on( 'connection-state-change', (curState: any, prevState: any) => { this.connection_state = curState; if ( curState == 'RECONNECTING' || curState == 'DISCONNECTED' || curState == 'DISCONNECTING' ) { this.timer && clearTimeout(this.timer); this.timer_countdown && clearInterval(this.timer_countdown); } console.log('状态变更:', this.connection_state); console.log(prevState); } ); this.monitorDevices(); } async createLiveLog(uid: string) { let profile = JSON.parse(localStorage.getItem('profile') || '{}'); let obj = Parse.Object.extend('LiveLog'); let liveLog = new obj(); liveLog.set('title', `与${profile?.name || profile?.mobile}进行直播`); liveLog.set('uid', String(uid)); liveLog.set('company', { __type: 'Pointer', className: 'Company', objectId: this.company, }); liveLog.set('room', { __type: 'Pointer', className: 'Room', objectId: this.room?.id, }); liveLog.set('user', { __type: 'Pointer', className: '_User', objectId: Parse.User.current()?.id, }); liveLog.set('profile', { __type: 'Pointer', className: 'Profile', objectId: this.room?.get('profile')?.id, }); this.liveLog = await liveLog.save(); } /* 连线成功立即创建直播记录,同步获取剩余可通话时长,并且开始计时 */ async afterJoin() { this.timer && clearTimeout(this.timer); this.timer_countdown && clearInterval(this.timer_countdown); if (this.client.remoteUsers.length > 0) { if (this.isAnchor) { //如果是主播,进入获取remoteUsers.user获取livelog再获取对方剩余通话时长 this.getLiveLog(); } else { let query = new Parse.Query('LiveLog'); query.equalTo('objectId', this.liveLog?.id); query.equalTo('isLive', true); query.select('objectId'); const resultData = await query.first(); if (resultData?.id) { //首次进入至少计时2分钟,不足2分钟按2分钟计算扣时 await this.publishSelf(); await this.get_duration(); this.countdown = this.surplusNumber; // 初始化倒计时 this.startCountdown(); this.computeDuration(60000 * 2); } else { this.timer = setTimeout(() => { this.afterJoin(); }, 1000); } } } else { this.timer = setTimeout(() => { this.afterJoin(); }, 1000); } } async getLiveLog() { let uid = this.client.remoteUsers[0].uid; this.timer && clearTimeout(this.timer); let query = new Parse.Query('LiveLog'); query.equalTo('uid', String(uid)); query.equalTo('room', this.room?.id); query.notEqualTo('isDeleted', true); query.notEqualTo('isLive', true); query.descending('createdAt'); this.liveLog = await query.first(); if (this.liveLog?.id) { this.liveLog?.set('isLive', true); await this.liveLog?.save(); this.get_duration(); return; } this.timer = setTimeout(() => { this.getLiveLog(); }, 1000); } async get_duration() { let url = 'https://server.fmode.cn/api/ailiao/remain_second'; let params = { rid: this.room?.id, uid: this.liveLog?.get('user')?.id || this.liveLog?.get('user')?.objectId, }; let data = await this.http.httpRequst(url, params, 'POST'); console.log(data); this.surplusNumber = data.data ?? 0; } startCountdown() { this.timer_countdown = setInterval(() => { if (this.countdown > 0) { this.countdown--; console.log(this.countdown); } else { clearInterval(this.timer_countdown); this.alertTips('通话时间结束'); this.client.leave(); // 结束通话 } }, 1000); } /* 开始计时 */ async computeDuration(num?: number) { //每10秒保存一次数据 this.timer && clearTimeout(this.timer); let p = JSON.parse(this.profile); console.log(p); let url = 'https://server.fmode.cn/api/ailiao/count/duration'; let params = { company: this.company, profile: p?.objectId, rid: this.room?.id, uid: Parse.User.current()?.id, duration: num ? num / 1000 : 10, logid: this.liveLog?.id, }; let data = await this.http.httpRequst(url, params, 'POST'); console.log(data); this.timer = setTimeout(() => { this.computeDuration(); }, num ?? 10000); } /* 监听音视频设备插拔 */ monitorDevices() { AgoraRTC.onMicrophoneChanged = async (changedDevice: any) => { // 插入麦克风设备时,切换到新插入的设备 if (changedDevice.state === 'ACTIVE') { this.localTracks.audioTrack.setDevice(changedDevice.device.deviceId); this.currentUsedDevice.audioDevice = changedDevice.device.deviceId; // 拔出设备为当前设备时,切换到一个已有的设备 } else if ( changedDevice.device.label === this.localTracks.audioTrack.getTrackLabel() ) { const oldMicrophones = await AgoraRTC.getMicrophones(); oldMicrophones[0] && this.localTracks.audioTrack.setDevice(oldMicrophones[0].deviceId); this.currentUsedDevice.audioDevice = oldMicrophones[0].deviceId; } }; AgoraRTC.onCameraChanged = async (changedDevice: any) => { // 插入相机设备时,切换到新插入的设备 if (changedDevice.state === 'ACTIVE') { this.localTracks.videoTrack.setDevice(changedDevice.device.deviceId); this.currentUsedDevice.videoDevice = changedDevice.device.deviceId; // 拔出设备为当前设备时,切换到一个已有的设备 } else if ( changedDevice.device.label === this.localTracks.videoTrack.getTrackLabel() ) { const oldCameras = await AgoraRTC.getCameras(); oldCameras[0] && this.localTracks.videoTrack.setDevice(oldCameras[0].deviceId); this.currentUsedDevice.videoDevice = oldCameras[0].deviceId; } }; } /* 关开麦 */ async updatePublishedAudioTrack() { this.tools['audio'] = !this.tools['audio']; if (this.tools['audio'] && this.localTracks.audioTrack) { await this.localTracks.audioTrack.setEnabled(false); console.log('停止推送音频'); } else { await this.localTracks.audioTrack.setEnabled(true); console.log('恢复推送音频'); } // await this.client.unpublish(this.localTracks.audioTrack); return true; } /* 静音 */ async muteAudio() { this.tools['mute'] = !this.tools['mute']; let list = Array.from(this.user_published_list); for (let index = 0; index < list.length; index++) { const user = list[index]; if (this.tools['mute']) { console.log('静音'); await this.client.unsubscribe(user, 'audio'); } else { await this.client.subscribe(user, 'audio'); console.log('恢复声音'); } } } /* 切换摄像头 */ async changeCamera() { const oldCameras = await AgoraRTC.getCameras(true); let newCamers = oldCameras.find( (item: any) => item.deviceId !== this.currentUsedDevice.videoDevice ); console.log(newCamers); newCamers && this.localTracks.videoTrack .setDevice(newCamers.deviceId) .then(() => { this.tools['camera'] = !this.tools['camera']; this.currentUsedDevice.videoDevice = newCamers.deviceId; }) .catch((err: any) => { console.log('set device error', err); this.alertTips('切换摄像头失败'); }); return true; } /* 提示 */ async alertTips(message: string, title?: string, callBack?: Function) { this.alert = await this.alertController.create({ header: title || '提示', message: message, buttons: [ { text: '确认', handler: () => { callBack && callBack(); }, }, ], }); await this.alert.present(); } }