import { Injectable } from '@angular/core'; import { HttpService } from './http.service'; import * as Parse from 'parse'; import { AlertController, ToastController } from '@ionic/angular'; // import AgoraRTM from 'agora-rtmClient'; import { Subject } from 'rxjs'; import { Router } from '@angular/router'; import { AiChatService } from './aichart.service'; declare const AgoraRTM: any; import { VapInit } from '../lib/vap-player/index' @Injectable({ providedIn: 'root', }) export class MessageService { private eventSource = new Subject(); event$ = this.eventSource.asObservable(); private eventplay = new Subject(); eventplay$ = this.eventplay.asObservable(); company: string = 'Qje9D4bqol'; rtmClient: any; // RTM实例 // rtmClientMap: any = {}; channelNameList: any = {}; //订阅频道状态 msChannelName: string = 'global_room'; // 全球频道 options: any = { connectState: false, }; appid?: string; userId: string = Parse.User.current()?.id!; pageFun?: Function; //页面传入的方法 alert: any; // 弹窗 messageMapList: any = {}; giftLogMap: any = []; giftList: Array = []; //礼物列表 timeg: any; isPlayer: boolean = false; private audioPlayer: HTMLAudioElement | null = null; private loopPlayAudio(audioUrl: string, rep?:boolean) { if (this.audioPlayer) { this.audioPlayer.pause(); this.audioPlayer.currentTime = 0; } this.audioPlayer = new Audio(audioUrl); this.audioPlayer.loop = rep; // 设置循环播放 this.audioPlayer.play().catch(error => { console.error('播放音频失败:', error); }); } reset() { this.options = { connectState: false }; this.rtmClient = null; this.channelNameList = {}; this.messageMapList = {}; } constructor( private alertController: AlertController, private router: Router, private toastController: ToastController, private http: HttpService, private aiServ: AiChatService ) { this.aiServ.getGift().then((data) => (this.giftList = data)); } /* 获取token */ async getToken() { this.userId = Parse.User.current()?.id!; //获取频道token记录 let uid = Parse.User.current()?.id; let baseurl = 'https://server.fmode.cn/api/webrtc/build_token'; let reqBody = { company: this.company, channelName: this.msChannelName, type: 'withrtm', account: uid, }; let data: any = await this.http.httpRequst(baseurl, reqBody, 'POST'); console.log(data); if (data.code == 200) { // this.options[channel] = { // token: data.data.token, // }; this.options.token = data.data.token; this.appid = data.data.appid; } } async initRTM() { // let states = ['CONNECTED', 'CONNECTING']; if (this.options.connectState) return; await this.getToken(); const rtmConfig = { logLevel: 'error', logUpload: false }; this.rtmClient = new AgoraRTM.RTM(this.appid, this.userId, rtmConfig); this.joinReady(); await this.loginRTM(); // this.subscribeMessage(channelName); // this.publishMessage('user-online', channelName, 'USER'); //用户上线通知 } /**@监听频道消息 *@'USERCALLINVITATION': 用户通话邀请 *@'CLOASEINVITATION': 取消通话邀请 *@'REFUSEINVITATION_' + uid: 拒绝通话邀请 *@'RESPONSEINVITOIN_' + uid: 接受邀请 *@'USERGREETING_' + '': 问候 */ joinReady() { this.rtmClient?.addEventListener('message', async (event: any) => { console.log('接收到一条消息:', event); let states = [ 'USERCALLINVITATION', 'REFUSEINVITATION_' + this.userId, 'REFUSEINVITATION_' + event.publisher, 'CLOASEINVITATION', 'RESPONSEINVITOIN_' + this.userId, 'RESPONSEINVITOIN_' + event.publisher, ]; const message = JSON.parse(event.message); let is_self = event.publisher == this.userId; console.log('自己发出的消息:', is_self, message.text); if ( states.includes(message.text) || message.text.indexOf('ONUSERSENDGIFT_') > -1 || message.text.indexOf('USERGREETING_') > -1 ) { if (!is_self) { this.callPresence(message.text, event.publisher, event.channelName); } //呼出播放声音 if (message.text == 'USERCALLINVITATION') { this.loopPlayAudio('mp3/call.mp3',true); } else if (message.text == 'CLOASEINVITATION') { if (this.audioPlayer) { this.audioPlayer.pause(); this.audioPlayer.currentTime = 0; } } return; } this.showMessage(event); }); this.rtmClient?.addEventListener('presence', (event: any) => { console.log('频道人员状态变化 ', event); let is_self = event.publisher == this.userId; //远端用户离开频道,主播在线状态 if ( !is_self && event.eventType === 'REMOTE_LEAVE' && event.channelName === this.userId ) { this.setConnectState(this.userId, 'ONLINE'); } }); this.rtmClient?.addEventListener('linkState', (event: any) => { console.log('连接状态: ', event); }); } /* 设置频道状态 */ async setConnectState(channelName: string, mode: string) { const channelType = 'MESSAGE'; const states = { Mode: mode, }; try { await this.rtmClient?.presence.setState(channelName, channelType, states); console.log('频道状态发生更改:', mode); } catch (err: any) { console.log(err); } } /* 呼叫事件 */ async callPresence(message: string, publisher: string, channelName: string) { let userData = await this.getUserMetadata(publisher); console.log('向我发出事件用户:', userData); let toast; if (message.indexOf('ONUSERSENDGIFT_') > -1) { let arr = message.split('_'); let gift = this.giftList.find((item: any) => item.id == arr[1]); // let r = await this.aiServ.getGift(undefined,arr[1]) // let gift = r[0] this.giftLogMap.push({ gift, count: arr?.[2], user: userData, }); !this.isPlayer && setTimeout(() => { // this.playGift(this.giftLogMap.slice(-1)[0]); let obj = this.giftLogMap.slice(-1)[0] this.eventplay.next({ video: obj.gift.video, config: obj.gift.config, }); }, 0); return; } if (message.indexOf('USERGREETING_') > -1) { this.loopPlayAudio('mp3/message.mp3') let arr = message.split('_'); this.alert = await this.alertController.create({ cssClass: 'my-custom-class', header: '收到打招呼消息', message: `${userData?.nickname?.value ?? '未知用户'}:${arr[1]}`, backdropDismiss: false, buttons: [ { text: '关闭', role: 'cancel', handler: async (blah) => { }, }, { text: '查看主页', cssClass: 'secondary', handler: async () => { this.router.navigate(['user/profile/' + publisher]); }, }, ], }); await this.alert.present(); return; } switch (message) { case 'USERCALLINVITATION': this.loopPlayAudio('mp3/call.mp3',true); await this.setConnectState(this.userId, 'CONNECTING'); // console.log(`收到${userData?.nickname?.value ?? '未知用户'}通话邀请`); this.alert = await this.alertController.create({ cssClass: 'my-custom-class', header: '通话邀请', message: `收到${userData?.nickname?.value ?? '未知用户'}通话邀请`, backdropDismiss: false, buttons: [ { text: '拒绝', role: 'cancel', handler: async (blah) => { await this.setConnectState(this.userId, 'ONLINE'); this.publishMessage( 'REFUSEINVITATION_' + publisher, channelName ); // 停止播放音频 if (this.audioPlayer) { this.audioPlayer.pause(); this.audioPlayer.currentTime = 0; } }, }, { text: '接受', cssClass: 'secondary', handler: async () => { this.publishMessage( 'RESPONSEINVITOIN_' + publisher, channelName ); let rid = await this.getRoom(this.userId); this.router.navigate(['live/link-room/' + rid]); // 停止播放音频 if (this.audioPlayer) { this.audioPlayer.pause(); this.audioPlayer.currentTime = 0; } }, }, ], }); await this.alert.present(); break; case 'CLOASEINVITATION': await this.setConnectState(this.userId, 'ONLINE'); // console.log(`${userData?.nickname?.value ?? '未知用户'}取消通话`); this.alert?.dismiss(); toast = await this.toastController.create({ message: '对方已取消通话邀请', color: 'warning', duration: 1500, }); toast.present(); // 停止播放音频 if (this.audioPlayer) { this.audioPlayer.pause(); this.audioPlayer.currentTime = 0; } break; case 'REFUSEINVITATION_' + this.userId: // console.log(`${userData?.nickname?.value ?? '未知用户'}拒绝通话`); this.alert?.dismiss(); this.eventSource.next(false); // 停止播放音频 if (this.audioPlayer) { this.audioPlayer.pause(); this.audioPlayer.currentTime = 0; } break; case 'RESPONSEINVITOIN_' + this.userId: // console.log(`${userData?.nickname?.value ?? '未知用户'}同意通话,进入视频通话`); this.alert?.dismiss(); this.eventSource.next(true); // 停止播放音频 if (this.audioPlayer) { this.audioPlayer.pause(); this.audioPlayer.currentTime = 0; } break; } } // 礼物消息 // showGiftDecrement() { // this.timeg = setTimeout(() => { // this.giftLogMap.shift(); // if (this.giftLogMap.length > 0) { // this.showGiftDecrement(); // } else { // clearTimeout(this.timeg); // this.timeg = null; // } // }, 5000); // } playGift(giftModule: any) { console.log(giftModule); this.isPlayer = true let dom = document.getElementById('vap-gift'); console.log(dom); let vapPlayer = VapInit({ container: dom, // 要渲染的载体,dom元素 src: giftModule.gift?.video, // vap动画地址 config: giftModule.gift?.config, // 播放vap动画需要的 json文件。必填 width: window.innerWidth, // 容器宽度 height: window.innerHeight, // 容器高度 fps: 30, // 帧数,json文件中有这个视频的帧数的,可以看一下, mute: false, // 静音 type: 1, // 组件基于type字段做了实例化缓存,不同的VAP实例应该使用不同的type值(如0、1、2等) loop: false, // 循环 precache: true, // 预加载视频,下载完再播。小动画建议边下边播,大动画还是先下后播吧,因为太大了或者网络不好,会一卡一卡的。 beginPoint: 0, // 起始播放时间点(单位秒),在一些浏览器中可能无效 accurate: true, // 是否启用精准模式(使用requestVideoFrameCallback提升融合效果,浏览器不兼容时自动降级) }); vapPlayer.play(); // 开始播放 vapPlayer.on('ended', () => { // 监听播放完成的事件 vapPlayer.destroy(); vapPlayer = null; console.log('播放结束'); this.giftLogMap.pop(); if (this.giftLogMap.length > 0) { this.playGift(this.giftLogMap.slice(-1)[0]); } else { this.isPlayer = false } }); vapPlayer.on('playering', function () { console.log('playering'); }) } async getRoom(uid: string): Promise { let query = new Parse.Query('Room'); query.equalTo('company', this.company); query.equalTo('user', uid); query.notEqualTo('isDeleted', true); query.select('objectId'); let r = await query.first(); return r?.id; } /* 加入频道 */ // async join(channelName: string) { // const options = { // token: this.options[channelName].token, // withPresence: true, // withLock: true, // withMetadata: true, // }; // try { // const streamChannel = // this.rtmClientMap[channelName].rtmClient.createStreamChannel( // channelName // ); // const result = await streamChannel.join(options); // } catch (status) { // console.error('join channel failed: ', status); // } // } async loginRTM() { try { await this.rtmClient?.login({ token: this.options.token, }); this.options.connectState = true; // 登录成功 let userMateData = await this.getUserMetadata(this.userId); if (!userMateData?.nickname?.value || !userMateData?.avatar?.value) { let user = Parse.User.current(); const metadata = [ { key: 'nickname', value: user?.get('nickname') || user?.get('name') || user?.id, }, { key: 'avatar', value: user?.get('avatar') ?? 'https://file-cloud.fmode.cn/DXNgcD6zo6/20221202/j6p8kb034039.png', }, ]; const options = { userId: this.userId, addTimeStamp: true, addUserId: true, }; let result = await this.rtmClient?.storage.setUserMetadata( metadata, options ); console.log(JSON.stringify(result)); } } catch (status) { console.log(status); } } logOutRTM() { this.rtmClient?.logout().then(() => { console.log('logout success'); this.options.connectState = false; }); } /* 订阅消息 */ subscribeMessage(channelName: string, param?: any, deadline?: number): any { if (this.channelNameList[channelName]) { return }; return new Promise((resolve, reject) => { const options = { withMessage: param?.message ?? false, // message 事件 withPresence: param?.presence ?? false, // presence 事件 beQuiet: false, // quiet 事件 withMetadata: false, // metadata 事件 withLock: false, // lock 事件 }; this.rtmClient ?.subscribe(channelName, options) .then((res: any) => { console.log('subscribeMessage', res); //订阅成功 this.channelNameList[channelName] = true; if (!this.messageMapList[channelName]) { this.messageMapList[channelName] = []; if (channelName.indexOf('-') > -1 || channelName == 'global_room') { this.getHistoryMessage(channelName, deadline); } } this.pageFun?.(); resolve(true); }) .catch((err: any) => { console.error('subscribeMessageErr', err); resolve(false); }); }); } async getHistoryMessage(channelName: string, deadline?: number) { let query = new Parse.Query('MessageLog'); query.equalTo('channel', channelName); query.descending('createdAt'); query.skip(this.messageMapList[channelName].length); query.limit(50); query.select('content', 'from_id'); deadline && query.greaterThanOrEqualTo('createdAt', new Date(deadline)); let msgList = await query.find(); console.log('历史消息:', msgList); msgList.forEach((item: any) => { let is_self = item?.get('from_id') == this.userId; let data: any = item?.get('content'); data['is_self'] = is_self; data['istoday'] = true; data['timestamp'] = new Date(data.timestamp); this.messageMapList[channelName].unshift(data); }); // this.messageMapList[channelName].unshift(...msgList); } /* 取消订阅 */ unsubscribeMessage(channelName: string) { this.rtmClient ?.unsubscribe(channelName) .then((res: any) => { console.log('unsubscribeMessage', res); //订阅成功 this.channelNameList[channelName] = false; }) .catch((err: any) => { console.error('unsubscribeMessage', err); }); } async showMessage(param: any) { let userData = await this.getUserMetadata(param.publisher); let is_self = param.publisher == this.userId; // console.log(userData); let message = JSON.parse(param.message); if (!this.messageMapList[param.channelName]) this.messageMapList[param.channelName] = []; if (is_self) { this.saveMeesage({ from_id: this.userId, from_username: Parse.User.current()?.get('nickname') ?? '未知用户', target_id: param.channelName, // target_name: userData?.nickname?.value ?? '未知用户', channel: param.channelName, content: { avatar: userData?.avatar?.value ?? 'https://file-cloud.fmode.cn/DXNgcD6zo6/20221202/j6p8kb034039.png', msg_type: 1, name: userData?.nickname?.value ?? '未知用户', content: message?.text ?? '', publisher: param.publisher, timestamp: param.timestamp, }, }); } let data: any = { is_self: is_self, avatar: userData?.avatar?.value ?? 'https://file-cloud.fmode.cn/DXNgcD6zo6/20221202/j6p8kb034039.png', msg_type: 1, name: userData?.nickname?.value ?? '未知用户', content: message?.text ?? '', publisher: param.publisher, timestamp: new Date(param.timestamp), istoday: true, }; this.messageMapList[param.channelName].push(data); this.pageFun?.(); } async saveMeesage(parse: { from_id: string; from_username: string; target_id: string; // target_name: string; channel: string; content: Object; }) { console.log(parse); let obj = Parse.Object.extend('MessageLog'); let message = new obj(); message.set('from_id', parse.from_id); message.set('from_username', parse.from_username); message.set('target_id', parse.target_id); // message.set('target_name', parse.target_name); message.set('channel', parse.channel); message.set('content', parse.content); message.set('company', { __type: 'Pointer', className: 'Company', objectId: this.company, }); await message.save(); } async getUserMetadata(uid: string) { try { const result = await this.rtmClient?.storage.getUserMetadata({ userId: uid, }); return result.metadata; } catch (status) { console.log(JSON.stringify(status)); } } async publishMessage( message: string, channelName: string, channelType?: string ) { const payload = { type: 'text', text: message }; const publishMessage = JSON.stringify(payload); const publishOptions = { channelType: channelType ?? 'MESSAGE' }; try { const result = await this.rtmClient?.publish( channelName, publishMessage, publishOptions ); console.log('发出消息:', result); } catch (status) { console.log('发出消息失败:', status); } } delMsg(channelName: string, index?: number, limit?: number) { this.messageMapList[channelName].splice( index ?? 0, limit ?? this.messageMapList[channelName].length ); console.log(this.messageMapList[channelName]); } }