import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { AlertController } from '@ionic/angular'; import * as Parse from 'parse'; import { AiChatService } from './aichart.service'; import { HttpService } from './http.service'; import { MessageService } from './message.service'; declare const AgoraRTC: any; @Injectable({ providedIn: 'root', }) export class LiveService { isAnchor: boolean = false; //是否是主播 options: { appid: string; channel: string; token: string; } = { appid: '', 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; //直播记录 isOpenEvaluate: boolean = false; //是否开启评价 constructor( private http: HttpService, private router: Router, private aiServ: AiChatService, private alertController: AlertController, private msgSer: MessageService ) { 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记录 let baseurl = 'https://server.fmode.cn/api/ailiao/token'; if (this.isAnchor) { this.UID = 111111; 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; } else { this.timer = setTimeout(() => { this.getToken(room); }, 2000); return; } this.options.channel = room?.get('profile').id; 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'); this.connection_state = this.client.connectionState; console.log('进入频道当前uid:', uid, '状态:', this.connection_state); let user = Parse.User.current(); this.msgSer?.publishMessage( `${user?.get('name') || user?.get('nickname')}加入频道直播`, this.room?.get('user')?.id ); 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); // history.back() this.alertTips('发布本地视频失败,请检查摄像头是否授权或正常', '提示'); this.client.leave(); } } /* 订阅远程视频 */ 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(100); 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(); // } // history.back() 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); if (!this.isAnchor) { //用户离开直播间,断开与主播状态连接频道 this.msgSer.unsubscribeMessage(this.room?.get('user')?.id); this.isOpenEvaluate = true; } console.log(this.router.url); if(this.router.url.indexOf('/live/link-room/') == 0) curState == 'DISCONNECTED' && history.back(); } console.log('live状态变更:', this.connection_state); } ); 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); const targetUser = this.client.remoteUsers?.find( (user: any) => user.uid !== 100001 ); //排出超管 console.log(targetUser); if (this.client.remoteUsers.length > 0 && targetUser) { if (this.isAnchor) { //如果是主播,进入获取remoteUsers.user获取livelog再获取对方剩余通话时长 this.getLiveLog(targetUser.uid); } 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.computeDuration(60000 * 2); } else { this.timer = setTimeout(() => { this.afterJoin(); }, 1000); } } } else { this.timer = setTimeout(() => { this.afterJoin(); }, 1000); } } async getLiveLog(tarUid: string) { // let uid = this.client.remoteUsers[0].uid; this.timer && clearTimeout(this.timer); let query = new Parse.Query('LiveLog'); query.equalTo('uid', String(tarUid)); 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(); await this.get_duration(); this.getCallDuration(); return; } this.timer = setTimeout(() => { this.getLiveLog(tarUid); }, 1000); } async get_duration() { this.timer_countdown && clearInterval(this.timer_countdown); 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; this.countdown = this.surplusNumber; // 初始化倒计时 let arr = ['CONNECTING', 'CONNECTED']; if (arr.includes(this.connection_state as any)) { if (this.countdown <= 120) { this.alertTips('剩余通话时间不足2分钟,请及时充值'); } this.startCountdown(); } } startCountdown() { this.timer_countdown = setInterval(() => { if (this.countdown > 0) { this.countdown--; // console.log(this.countdown); } else { clearInterval(this.timer_countdown); // history.back() 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); } /* 主播每10s获取一次对方可通话时长 */ getCallDuration() { this.timer && clearTimeout(this.timer); this.timer = setTimeout(async () => { await this.get_duration(); let arr = ['CONNECTING', 'CONNECTED']; if (arr.includes(this.connection_state as any)) { this.getCallDuration(); } }, 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, backdropDismiss: false, buttons: [ { text: '确认', handler: () => { callBack && callBack(); }, }, ], }); await this.alert.present(); } }