|
|
@@ -76,655 +76,692 @@
|
|
|
|
|
|
|
|
|
<script>
|
|
|
- import { v4 as uuidv4 } from 'uuid';
|
|
|
- import { mapGetters, mapState } from 'vuex';
|
|
|
- import { formatDuration } from '@/utils/formatDuration';
|
|
|
- import { CALL_TYPE } from '@/constant/call';
|
|
|
- import { getOpenIM } from '@/utils/openIM';
|
|
|
- import Trtc,{createLocalAudioTrack,createLocalVideoTrack} from '@/utils/trtc';
|
|
|
- import beCalledSound from '@/assets/audio/beCalled.mp3';
|
|
|
- export default {
|
|
|
- name: 'CallLayer',
|
|
|
- data() {
|
|
|
- return {
|
|
|
- acceptToken: '',
|
|
|
- acceptURL: '',
|
|
|
- acceptRoomID: '',
|
|
|
- faceImages:"",
|
|
|
- nickName:"",
|
|
|
- callType: '',
|
|
|
- inviteID: '',
|
|
|
- inviteData: {},
|
|
|
- sponsor: '',
|
|
|
- callingUserList: [],
|
|
|
- isCamOn: true,
|
|
|
- isMicOn: true,
|
|
|
- start: 0,
|
|
|
- duration: 0,
|
|
|
- dialling: false,
|
|
|
- calling: false,
|
|
|
- isDialled: false,
|
|
|
- invitedUserInfo: [],
|
|
|
- room: null,
|
|
|
- localTracks: [],
|
|
|
- remoteUsers: [],
|
|
|
- OpenIM: null,
|
|
|
- durationTimer: null,
|
|
|
- defaultAvatar: 'https://imgcache.qq.com/open/qcloud/video/act/webim-avatar/avatar-3.png',
|
|
|
- isInvitedMicOn: true,
|
|
|
- ringtone: null,
|
|
|
- ringtoneType: '',
|
|
|
- };
|
|
|
+import { v4 as uuidv4 } from 'uuid';
|
|
|
+import { mapGetters, mapState } from 'vuex';
|
|
|
+import { formatDuration } from '@/utils/formatDuration';
|
|
|
+import { CALL_TYPE } from '@/constant/call';
|
|
|
+import { getOpenIM } from '@/utils/openIM';
|
|
|
+import Trtc,{createLocalAudioTrack,createLocalVideoTrack} from '@/utils/trtc';
|
|
|
+import beCalledSound from '@/assets/audio/beCalled.mp3';
|
|
|
+export default {
|
|
|
+ name: 'CallLayer',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ acceptToken: '',
|
|
|
+ acceptURL: '',
|
|
|
+ acceptRoomID: '',
|
|
|
+ faceImages:"",
|
|
|
+ nickName:"",
|
|
|
+ callType: '',
|
|
|
+ inviteID: '',
|
|
|
+ inviteData: {},
|
|
|
+ sponsor: '',
|
|
|
+ callingUserList: [],
|
|
|
+ isCamOn: true,
|
|
|
+ isMicOn: true,
|
|
|
+ start: 0,
|
|
|
+ duration: 0,
|
|
|
+ dialling: false,
|
|
|
+ calling: false,
|
|
|
+ isDialled: false,
|
|
|
+ invitedUserInfo: [],
|
|
|
+ room: null,
|
|
|
+ localTracks: [],
|
|
|
+ remoteUsers: [],
|
|
|
+ OpenIM: null,
|
|
|
+ durationTimer: null,
|
|
|
+ defaultAvatar: 'https://imgcache.qq.com/open/qcloud/video/act/webim-avatar/avatar-3.png',
|
|
|
+ isInvitedMicOn: true,
|
|
|
+ ringtone: null,
|
|
|
+ ringtoneType: '',
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ CALL_TYPE() {
|
|
|
+ return CALL_TYPE;
|
|
|
},
|
|
|
- computed: {
|
|
|
- CALL_TYPE() {
|
|
|
- return CALL_TYPE;
|
|
|
- },
|
|
|
- ...mapGetters(['toAccount', 'currentConversationType']),
|
|
|
- ...mapState({
|
|
|
- currentConversation: state => state.conversation.currentConversation,
|
|
|
- currentUserProfile: state => state.imuser.currentUserProfile,
|
|
|
- callingInfo: state => state.conversation.callingInfo,
|
|
|
- userID: state => state.imuser.userID,
|
|
|
- }),
|
|
|
- formatDurationStr() {
|
|
|
- return formatDuration(this.duration);
|
|
|
- },
|
|
|
- myAvatar() {
|
|
|
- console.log("我的相关信息",this.currentUserProfile)
|
|
|
- return this.currentUserProfile.faceURL || this.defaultAvatar
|
|
|
- },
|
|
|
- myNick() {
|
|
|
- return this.currentUserProfile.nickname || this.userID
|
|
|
- }
|
|
|
+ ...mapGetters(['toAccount', 'currentConversationType']),
|
|
|
+ ...mapState({
|
|
|
+ currentConversation: state => state.conversation.currentConversation,
|
|
|
+ currentUserProfile: state => state.imuser.currentUserProfile,
|
|
|
+ callingInfo: state => state.conversation.callingInfo,
|
|
|
+ userID: state => state.imuser.userID,
|
|
|
+ }),
|
|
|
+ formatDurationStr() {
|
|
|
+ return formatDuration(this.duration);
|
|
|
},
|
|
|
- created() {
|
|
|
- this.OpenIM = getOpenIM();
|
|
|
- //document.addEventListener('click', this.initAudio, { once: true })
|
|
|
- this.setupEventListeners();
|
|
|
+ myAvatar() {
|
|
|
+ console.log("我的相关信息",this.currentUserProfile)
|
|
|
+ return this.currentUserProfile.faceURL || this.defaultAvatar
|
|
|
},
|
|
|
- beforeDestroy() {
|
|
|
- this.cleanup();
|
|
|
+ myNick() {
|
|
|
+ return this.currentUserProfile.nickname || this.userID
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ this.OpenIM = getOpenIM();
|
|
|
+ //document.addEventListener('click', this.initAudio, { once: true })
|
|
|
+ this.setupEventListeners();
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ this.cleanup();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+
|
|
|
+ micHandler() { // 是否打开麦克风
|
|
|
+ if (this.isMicOn) {
|
|
|
+ this.toggleMic()
|
|
|
+ this.isMicOn = false
|
|
|
+ } else {
|
|
|
+ this.toggleMic()
|
|
|
+ this.isMicOn = true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 初始化事件监听
|
|
|
+ setupEventListeners() {
|
|
|
+ this.OpenIM.on('OnReceiveNewInvitation', this.handleNewInvitation);
|
|
|
+ this.OpenIM.on('OnInviteeAccepted', this.handleInviteeAccepted);
|
|
|
+ this.OpenIM.on('OnInviteeRejected', this.handleInviteeRejected);
|
|
|
+ this.OpenIM.on('OnInvitationTimeout', this.handleInvitationTimeout);
|
|
|
+ this.OpenIM.on('OnHangUp', this.handleHangUp);
|
|
|
+
|
|
|
+ this.$bus.$on('video-call', () => this.startCall('video'));
|
|
|
+ this.$bus.$on('audio-call', () => this.startCall('audio'));
|
|
|
},
|
|
|
- methods: {
|
|
|
|
|
|
- micHandler() { // 是否打开麦克风
|
|
|
- if (this.isMicOn) {
|
|
|
- this.toggleMic()
|
|
|
- this.isMicOn = false
|
|
|
- } else {
|
|
|
- this.toggleMic()
|
|
|
- this.isMicOn = true
|
|
|
- }
|
|
|
- },
|
|
|
- // 初始化事件监听
|
|
|
- setupEventListeners() {
|
|
|
- this.OpenIM.on('OnReceiveNewInvitation', this.handleNewInvitation);
|
|
|
- this.OpenIM.on('OnInviteeAccepted', this.handleInviteeAccepted);
|
|
|
- this.OpenIM.on('OnInviteeRejected', this.handleInviteeRejected);
|
|
|
- this.OpenIM.on('OnInvitationTimeout', this.handleInvitationTimeout);
|
|
|
- this.OpenIM.on('OnHangUp', this.handleHangUp);
|
|
|
-
|
|
|
- this.$bus.$on('video-call', () => this.startCall('video'));
|
|
|
- this.$bus.$on('audio-call', () => this.startCall('audio'));
|
|
|
- },
|
|
|
-
|
|
|
- // 清理资源
|
|
|
- cleanup() {
|
|
|
- //this.stopRingtone();
|
|
|
- clearInterval(this.durationTimer);
|
|
|
+ // 清理资源
|
|
|
+ cleanup() {
|
|
|
+ //this.stopRingtone();
|
|
|
+ clearInterval(this.durationTimer);
|
|
|
|
|
|
- if (this.room) {
|
|
|
- this.room.disconnect();
|
|
|
- this.room = null;
|
|
|
- }
|
|
|
+ if (this.room) {
|
|
|
+ this.room.disconnect();
|
|
|
+ this.room = null;
|
|
|
+ }
|
|
|
|
|
|
- this.OpenIM.off('OnReceiveNewInvitation', this.handleNewInvitation);
|
|
|
- this.OpenIM.off('OnInviteeAccepted', this.handleInviteeAccepted);
|
|
|
- this.OpenIM.off('OnInviteeRejected', this.handleInviteeRejected);
|
|
|
- this.OpenIM.off('OnInvitationTimeout', this.handleInvitationTimeout);
|
|
|
- this.OpenIM.off('OnHangUp', this.handleHangUp);
|
|
|
-
|
|
|
- this.$bus.$off('video-call');
|
|
|
- this.$bus.$off('audio-call');
|
|
|
- },
|
|
|
-
|
|
|
- // 播放铃声
|
|
|
- initAudio(type = 'callee') {
|
|
|
- try {
|
|
|
- const audio = new Audio(beCalledSound);
|
|
|
- audio.play().catch(err => {
|
|
|
- console.warn("播放声音失败", err);
|
|
|
- });
|
|
|
- this.ringtone = new Audio(beCalledSound);
|
|
|
- this.ringtone.loop = true;
|
|
|
- this.ringtone.preload = 'auto';
|
|
|
- this.ringtoneType = type;
|
|
|
+ this.OpenIM.off('OnReceiveNewInvitation', this.handleNewInvitation);
|
|
|
+ this.OpenIM.off('OnInviteeAccepted', this.handleInviteeAccepted);
|
|
|
+ this.OpenIM.off('OnInviteeRejected', this.handleInviteeRejected);
|
|
|
+ this.OpenIM.off('OnInvitationTimeout', this.handleInvitationTimeout);
|
|
|
+ this.OpenIM.off('OnHangUp', this.handleHangUp);
|
|
|
|
|
|
- this.ringtone.addEventListener('error', (e) => {
|
|
|
- console.error('音频加载错误:', e);
|
|
|
- });
|
|
|
+ this.$bus.$off('video-call');
|
|
|
+ this.$bus.$off('audio-call');
|
|
|
+ },
|
|
|
|
|
|
- this.ringtone.play().catch((err) => {
|
|
|
- console.warn('播放铃声失败,可能未解锁音频权限:', err);
|
|
|
- });
|
|
|
- } catch (e) {
|
|
|
- console.error('音频初始化失败:', e);
|
|
|
- }
|
|
|
- },
|
|
|
- playRingtone() {
|
|
|
- if (!this.ringtone) {
|
|
|
- this.initAudio()
|
|
|
- }
|
|
|
+ // 播放铃声
|
|
|
+ initAudio(type = 'callee') {
|
|
|
+ try {
|
|
|
+ const audio = new Audio(beCalledSound);
|
|
|
+ audio.play().catch(err => {
|
|
|
+ console.warn("播放声音失败", err);
|
|
|
+ });
|
|
|
+ this.ringtone = new Audio(beCalledSound);
|
|
|
+ this.ringtone.loop = true;
|
|
|
+ this.ringtone.preload = 'auto';
|
|
|
+ this.ringtoneType = type;
|
|
|
|
|
|
- this.ringtone.loop = true
|
|
|
- this.ringtone.play().catch(e => {
|
|
|
- console.error('播放被阻止:', e)
|
|
|
- // 可以在这里显示"点击允许音频"的提示
|
|
|
- })
|
|
|
- },
|
|
|
- stopRingtone() {
|
|
|
- if (this.ringtone) {
|
|
|
- this.ringtone.pause();
|
|
|
- this.ringtone.currentTime = 0;
|
|
|
- this.ringtone = null;
|
|
|
- this.ringtoneType = '';
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- // 处理新邀请
|
|
|
- async handleNewInvitation(data) {
|
|
|
- console.log("返回的参数data", data)
|
|
|
- this.inviteID = data.data.inviteID
|
|
|
- this.inviteData = data.data.invitation
|
|
|
- this.callType = data.data.invitation.mediaType
|
|
|
- this.sponsor = data.data.participant.userInfo.nickname
|
|
|
- this.isDialled = true
|
|
|
-
|
|
|
- // ✅ 提前预热权限(耗时 1-2 秒,提前做)
|
|
|
- this.prewarmMediaPermission()
|
|
|
-
|
|
|
- // ✅ 提前执行 signalingAccept,拿到 token/url(非真正入会)
|
|
|
- try {
|
|
|
- const res = await this.OpenIM.signalingAccept({
|
|
|
- opUserID: this.userID,
|
|
|
- invitation: this.inviteData
|
|
|
- });
|
|
|
- this.acceptToken = res.data.token
|
|
|
- this.acceptURL = res.data.liveURL
|
|
|
- this.acceptRoomID = this.inviteData.roomID
|
|
|
- } catch (err) {
|
|
|
- console.error("提前 accept 接口失败", err)
|
|
|
- }
|
|
|
+ this.ringtone.addEventListener('error', (e) => {
|
|
|
+ console.error('音频加载错误:', e);
|
|
|
+ });
|
|
|
|
|
|
- document.addEventListener('click', () => {
|
|
|
- this.initAudio('callee');
|
|
|
- }, { once: true })
|
|
|
+ this.ringtone.play().catch((err) => {
|
|
|
+ console.warn('播放铃声失败,可能未解锁音频权限:', err);
|
|
|
+ });
|
|
|
+ } catch (e) {
|
|
|
+ console.error('音频初始化失败:', e);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ playRingtone() {
|
|
|
+ if (!this.ringtone) {
|
|
|
+ this.initAudio()
|
|
|
+ }
|
|
|
|
|
|
- this.updateInvitedUserInfo(data.data.invitation.inviteeUserIDList)
|
|
|
- },
|
|
|
+ this.ringtone.loop = true
|
|
|
+ this.ringtone.play().catch(e => {
|
|
|
+ console.error('播放被阻止:', e)
|
|
|
+ // 可以在这里显示"点击允许音频"的提示
|
|
|
+ })
|
|
|
+ },
|
|
|
+ stopRingtone() {
|
|
|
+ if (this.ringtone) {
|
|
|
+ this.ringtone.pause();
|
|
|
+ this.ringtone.currentTime = 0;
|
|
|
+ this.ringtone = null;
|
|
|
+ this.ringtoneType = '';
|
|
|
+ }
|
|
|
+ },
|
|
|
|
|
|
- // 更新被邀请用户信息
|
|
|
- async updateInvitedUserInfo(userIDs) {
|
|
|
- const users = await Promise.all(
|
|
|
- userIDs.map(userID => this.OpenIM.getUsersInfo([userID]))
|
|
|
- );
|
|
|
- console.log("查询的用户信息",users)
|
|
|
- this.invitedUserInfo = users.map(user => ({
|
|
|
- userID: user.userID,
|
|
|
- nickname: user.nickname,
|
|
|
- avatar: user.faceURL,
|
|
|
- isMicOn: true
|
|
|
- }));
|
|
|
- },
|
|
|
-
|
|
|
- // 发起通话
|
|
|
- async startCall(type) {
|
|
|
- console.log("发起通话",this.toAccount)
|
|
|
- if (this.calling || this.dialling) return;
|
|
|
-
|
|
|
- try {
|
|
|
- this.callType = type;
|
|
|
- console.log("通话方式",this.callType)
|
|
|
- this.dialling = true;
|
|
|
- const roomId = uuidv4();
|
|
|
-
|
|
|
- // 1. 先发送邀请,等待被叫方响应(这里需要监听被叫方的接受事件)
|
|
|
- const { token, liveURL, roomID, inviteID } = await this.sendCallInvitation(roomId, type);
|
|
|
- this.initAudio('caller');
|
|
|
- this.inviteID = inviteID; // 保存 inviteID,等待对方接听后再 joinRoom
|
|
|
- this.roomToken = token;
|
|
|
- this.roomURL = liveURL;
|
|
|
- this.roomID = roomID;
|
|
|
- console.log("生成的token和url",this.roomToken, this.roomURL)
|
|
|
- } catch (error) {
|
|
|
- console.error('发起通话失败:', error);
|
|
|
- this.resetCallState();
|
|
|
- }
|
|
|
- },
|
|
|
- // 被叫方接受邀请后,主叫方和被叫方都调用
|
|
|
- /*async joinRoom(roomID,roomToken,roomURL) {
|
|
|
- console.log(roomID)
|
|
|
- const callData = {
|
|
|
- // 移除外层 operationID(SDK 会自动添加)
|
|
|
- invitation: {
|
|
|
- inviterUserID: this.userID,
|
|
|
- inviteeUserIDList: ["D79"],
|
|
|
- groupID: '',
|
|
|
- roomID: roomID,
|
|
|
- timeout: 60,
|
|
|
- mediaType: this.callType,
|
|
|
- sessionType: 1,
|
|
|
- platformID: 5
|
|
|
- },
|
|
|
- offlinePushInfo: {
|
|
|
- title: 'You have a call invitation',
|
|
|
- desc: 'You have a call invitation',
|
|
|
- ex: '',
|
|
|
- iOSPushSound: '',
|
|
|
- iOSBadgeCount: true
|
|
|
- }
|
|
|
- };
|
|
|
- const res = await this.OpenIM.signalingAccept(callData);
|
|
|
- console.log(res)
|
|
|
- if (res.errCode !== 0) {
|
|
|
- console.error("获取房间 token 失败:", res.errMsg);
|
|
|
- return;
|
|
|
- }
|
|
|
- this.room = await Trtc.joinRoom(roomToken, roomURL, this.userID, roomID, {
|
|
|
- video: this.callType === 'video',
|
|
|
- audio: true
|
|
|
+ // 处理新邀请
|
|
|
+ async handleNewInvitation(data) {
|
|
|
+ console.log("返回的参数data", data)
|
|
|
+ this.inviteID = data.data.inviteID
|
|
|
+ this.inviteData = data.data.invitation
|
|
|
+ this.callType = data.data.invitation.mediaType
|
|
|
+ this.sponsor = data.data.participant.userInfo.nickname
|
|
|
+ this.isDialled = true
|
|
|
+
|
|
|
+ // 提前预热权限(耗时 1-2 秒,提前做)
|
|
|
+ this.prewarmMediaPermission()
|
|
|
+
|
|
|
+ // 提前执行 signalingAccept,拿到 token/url(非真正入会)
|
|
|
+ try {
|
|
|
+ const res = await this.OpenIM.signalingAccept({
|
|
|
+ opUserID: this.userID,
|
|
|
+ invitation: this.inviteData
|
|
|
});
|
|
|
+ this.acceptToken = res.data.token
|
|
|
+ this.acceptURL = res.data.liveURL
|
|
|
+ this.acceptRoomID = this.inviteData.roomID
|
|
|
+ } catch (err) {
|
|
|
+ console.error("提前 accept 接口失败", err)
|
|
|
+ }
|
|
|
|
|
|
- this.setupRoomListeners();
|
|
|
- if (this.callType === 'video'){
|
|
|
- await this.publishLocalVideo();
|
|
|
- await this.publishLocalAudio();
|
|
|
- }
|
|
|
- },*/
|
|
|
-
|
|
|
- // 发送通话邀请
|
|
|
- async sendCallInvitation(roomId, type) {
|
|
|
- console.log(" this.callingInfo.memberList", this.userID)
|
|
|
- const callData = {
|
|
|
- invitation: {
|
|
|
- inviterUserID: this.userID,
|
|
|
- inviteeUserIDList: [this.toAccount],
|
|
|
- groupID: '',
|
|
|
- roomID: roomId,
|
|
|
- timeout: 60,
|
|
|
- mediaType: this.callType,
|
|
|
- sessionType: 1,
|
|
|
- platformID: 5
|
|
|
- },
|
|
|
- offlinePushInfo: {
|
|
|
- title: '来电提示',
|
|
|
- desc: this.callType === 'video'? '视频通话':'语音通话',
|
|
|
- ex: '',
|
|
|
- iOSPushSound: '',
|
|
|
- iOSBadgeCount: true
|
|
|
- }
|
|
|
- };
|
|
|
- console.log("callData",callData)
|
|
|
- const res = await this.OpenIM.signalingInvite(callData);
|
|
|
- this.inviteID = res.data.inviteID;
|
|
|
- console.log("sendCallInvitation返回值",res)
|
|
|
- return {
|
|
|
- token: res.data.token,
|
|
|
- liveURL: res.data.liveURL,
|
|
|
- roomID: res.data.roomID,
|
|
|
- inviteID: res.data.inviteID,
|
|
|
- };
|
|
|
- },
|
|
|
- // 设置房间事件监听
|
|
|
- setupRoomListeners() {
|
|
|
- console.log("触发setupRoomListeners")
|
|
|
- /*this.room.on('participantConnected', participant => {
|
|
|
- console.log('participantConnected:', participant.identity);
|
|
|
- if (!this.callingUserList.includes(participant.identity)) {
|
|
|
- this.callingUserList = [...this.callingUserList, participant.identity];
|
|
|
- this.updateParticipantInfo(participant.identity);
|
|
|
- }
|
|
|
- });
|
|
|
+ document.addEventListener('click', () => {
|
|
|
+ this.initAudio('callee');
|
|
|
+ }, { once: true })
|
|
|
|
|
|
- this.room.on('trackSubscribed', (track, participant) => {
|
|
|
- console.log('trackSubscribed:', track.kind, participant.identity);
|
|
|
- this.attachRemoteTrack(track, participant);
|
|
|
- });*/
|
|
|
+ this.updateInvitedUserInfo(data.data.invitation.inviteeUserIDList)
|
|
|
+ },
|
|
|
|
|
|
- this.room.on('trackPublished', publication => {
|
|
|
- console.log('trackPublished:', publication.trackKind);
|
|
|
- });
|
|
|
- },
|
|
|
- async prewarmMediaPermission() {
|
|
|
- try {
|
|
|
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: this.callType === 'video' });
|
|
|
- stream.getTracks().forEach(track => track.stop()); // 不播放,只为触发授权
|
|
|
- console.log("预热媒体权限成功");
|
|
|
- } catch (err) {
|
|
|
- console.warn("权限预热失败:", err);
|
|
|
+ // 更新被邀请用户信息
|
|
|
+ async updateInvitedUserInfo(userIDs) {
|
|
|
+ const users = await Promise.all(
|
|
|
+ userIDs.map(userID => this.OpenIM.getUsersInfo([userID]))
|
|
|
+ );
|
|
|
+ console.log("查询的用户信息",users)
|
|
|
+ this.invitedUserInfo = users.map(user => ({
|
|
|
+ userID: user.userID,
|
|
|
+ nickname: user.nickname,
|
|
|
+ avatar: user.faceURL,
|
|
|
+ isMicOn: true
|
|
|
+ }));
|
|
|
+ },
|
|
|
+
|
|
|
+ // 发起通话
|
|
|
+ async startCall(type) {
|
|
|
+ console.log("发起通话",this.toAccount)
|
|
|
+ if (this.calling || this.dialling) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.callType = type;
|
|
|
+ console.log("通话方式",this.callType)
|
|
|
+ this.dialling = true;
|
|
|
+ const roomId = uuidv4();
|
|
|
+
|
|
|
+ // 1. 先发送邀请,等待被叫方响应(这里需要监听被叫方的接受事件)
|
|
|
+ const { token, liveURL, roomID, inviteID } = await this.sendCallInvitation(roomId, type);
|
|
|
+ this.initAudio('caller');
|
|
|
+ this.inviteID = inviteID; // 保存 inviteID,等待对方接听后再 joinRoom
|
|
|
+ this.roomToken = token;
|
|
|
+ this.roomURL = liveURL;
|
|
|
+ this.roomID = roomID;
|
|
|
+ console.log("生成的token和url",this.roomToken, this.roomURL)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('发起通话失败:', error);
|
|
|
+ this.resetCallState();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 被叫方接受邀请后,主叫方和被叫方都调用
|
|
|
+ /*async joinRoom(roomID,roomToken,roomURL) {
|
|
|
+ console.log(roomID)
|
|
|
+ const callData = {
|
|
|
+ // 移除外层 operationID(SDK 会自动添加)
|
|
|
+ invitation: {
|
|
|
+ inviterUserID: this.userID,
|
|
|
+ inviteeUserIDList: ["D79"],
|
|
|
+ groupID: '',
|
|
|
+ roomID: roomID,
|
|
|
+ timeout: 60,
|
|
|
+ mediaType: this.callType,
|
|
|
+ sessionType: 1,
|
|
|
+ platformID: 5
|
|
|
+ },
|
|
|
+ offlinePushInfo: {
|
|
|
+ title: 'You have a call invitation',
|
|
|
+ desc: 'You have a call invitation',
|
|
|
+ ex: '',
|
|
|
+ iOSPushSound: '',
|
|
|
+ iOSBadgeCount: true
|
|
|
}
|
|
|
- },
|
|
|
- async onAcceptClick() {
|
|
|
- try {
|
|
|
- await this.prewarmMediaPermission(); // 权限预热(防止卡住)
|
|
|
- await this.accept(); // 原来的接听逻辑
|
|
|
- } catch (e) {
|
|
|
- console.error("接听过程异常:", e);
|
|
|
+ };
|
|
|
+ const res = await this.OpenIM.signalingAccept(callData);
|
|
|
+ console.log(res)
|
|
|
+ if (res.errCode !== 0) {
|
|
|
+ console.error("获取房间 token 失败:", res.errMsg);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.room = await Trtc.joinRoom(roomToken, roomURL, this.userID, roomID, {
|
|
|
+ video: this.callType === 'video',
|
|
|
+ audio: true
|
|
|
+ });
|
|
|
+
|
|
|
+ this.setupRoomListeners();
|
|
|
+ if (this.callType === 'video'){
|
|
|
+ await this.publishLocalVideo();
|
|
|
+ await this.publishLocalAudio();
|
|
|
+ }
|
|
|
+ },*/
|
|
|
+
|
|
|
+ // 发送通话邀请
|
|
|
+ async sendCallInvitation(roomId, type) {
|
|
|
+ console.log(" this.callingInfo.memberList", this.userID)
|
|
|
+ const callData = {
|
|
|
+ invitation: {
|
|
|
+ inviterUserID: this.userID,
|
|
|
+ inviteeUserIDList: [this.toAccount],
|
|
|
+ groupID: '',
|
|
|
+ roomID: roomId,
|
|
|
+ timeout: 60,
|
|
|
+ mediaType: this.callType,
|
|
|
+ sessionType: 1,
|
|
|
+ platformID: 5
|
|
|
+ },
|
|
|
+ offlinePushInfo: {
|
|
|
+ title: '来电提示',
|
|
|
+ desc: this.callType === 'video'? '视频通话':'语音通话',
|
|
|
+ ex: '',
|
|
|
+ iOSPushSound: '',
|
|
|
+ iOSBadgeCount: true
|
|
|
}
|
|
|
- },
|
|
|
- // 发布本地视频
|
|
|
- async publishLocalVideo() {
|
|
|
- const devices = await navigator.mediaDevices.enumerateDevices();
|
|
|
- const hasCam = devices.some(d => d.kind === 'videoinput');
|
|
|
- if (!hasCam) {
|
|
|
- console.warn('无可用摄像头,跳过视频轨道发布');
|
|
|
- return;
|
|
|
+ };
|
|
|
+ console.log("callData",callData)
|
|
|
+ const res = await this.OpenIM.signalingInvite(callData);
|
|
|
+ this.inviteID = res.data.inviteID;
|
|
|
+ console.log("sendCallInvitation返回值",res)
|
|
|
+ return {
|
|
|
+ token: res.data.token,
|
|
|
+ liveURL: res.data.liveURL,
|
|
|
+ roomID: res.data.roomID,
|
|
|
+ inviteID: res.data.inviteID,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ // 设置房间事件监听
|
|
|
+ setupRoomListeners() {
|
|
|
+ console.log("触发setupRoomListeners")
|
|
|
+ /*this.room.on('participantConnected', participant => {
|
|
|
+ console.log('participantConnected:', participant.identity);
|
|
|
+ if (!this.callingUserList.includes(participant.identity)) {
|
|
|
+ this.callingUserList = [...this.callingUserList, participant.identity];
|
|
|
+ this.updateParticipantInfo(participant.identity);
|
|
|
}
|
|
|
+ });
|
|
|
+
|
|
|
+ this.room.on('trackSubscribed', (track, participant) => {
|
|
|
+ console.log('trackSubscribed:', track.kind, participant.identity);
|
|
|
+ this.attachRemoteTrack(track, participant);
|
|
|
+ });*/
|
|
|
+
|
|
|
+ this.room.on('trackPublished', publication => {
|
|
|
+ console.log('trackPublished:', publication.trackKind);
|
|
|
+ });
|
|
|
+ },
|
|
|
+ async prewarmMediaPermission() {
|
|
|
+ try {
|
|
|
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: this.callType === 'video' });
|
|
|
+ stream.getTracks().forEach(track => track.stop()); // 不播放,只为触发授权
|
|
|
+ console.log("预热媒体权限成功");
|
|
|
+ } catch (err) {
|
|
|
+ console.warn("权限预热失败:", err);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async onAcceptClick() {
|
|
|
+ try {
|
|
|
+ await this.prewarmMediaPermission(); // 权限预热(防止卡住)
|
|
|
+ await this.accept(); // 原来的接听逻辑
|
|
|
+ } catch (e) {
|
|
|
+ console.error("接听过程异常:", e);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 发布本地视频
|
|
|
+ async publishLocalVideo() {
|
|
|
+ const localContainer = document.getElementById('local');
|
|
|
+ if (!localContainer) return;
|
|
|
+
|
|
|
+ // 确保容器有尺寸
|
|
|
+ localContainer.style.width = '320px';
|
|
|
+ localContainer.style.height = '240px';
|
|
|
+ localContainer.style.backgroundColor = 'black';
|
|
|
+ localContainer.innerHTML = '';
|
|
|
+
|
|
|
+ const devices = await navigator.mediaDevices.enumerateDevices();
|
|
|
+ const hasCam = devices.some(d => d.kind === 'videoinput');
|
|
|
+
|
|
|
+ if (!hasCam) {
|
|
|
+ console.warn('无可用摄像头,显示蓝色占位');
|
|
|
+ this.showBluePlaceholder(localContainer, '无摄像头');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
const videoTrack = await createLocalVideoTrack();
|
|
|
await this.room.localParticipant.publishTrack(videoTrack);
|
|
|
this.localTracks.push(videoTrack);
|
|
|
|
|
|
// 挂载本地视频轨道
|
|
|
- const localContainer = document.getElementById('local');
|
|
|
- if (localContainer) {
|
|
|
- // 清空容器
|
|
|
- localContainer.innerHTML = '';
|
|
|
- const videoEl = document.createElement('video');
|
|
|
- videoEl.autoplay = true;
|
|
|
- videoEl.muted = true; // 本地视频静音避免回声
|
|
|
- videoEl.playsInline = true;
|
|
|
- videoEl.style.width = '100%';
|
|
|
- videoEl.style.height = '100%';
|
|
|
- localContainer.appendChild(videoEl);
|
|
|
- videoTrack.attach(videoEl);
|
|
|
- }
|
|
|
- },
|
|
|
+ const videoEl = document.createElement('video');
|
|
|
+ videoEl.autoplay = true;
|
|
|
+ videoEl.muted = true;
|
|
|
+ videoEl.playsInline = true;
|
|
|
+ videoEl.style.width = '100%';
|
|
|
+ videoEl.style.height = '100%';
|
|
|
+ localContainer.appendChild(videoEl);
|
|
|
+ videoTrack.attach(videoEl);
|
|
|
+ } catch (err) {
|
|
|
+ console.error('本地视频初始化失败:', err);
|
|
|
+ //this.showBluePlaceholder(localContainer, '摄像头不可用');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 渲染蓝色占位
|
|
|
+ showBluePlaceholder(container, text = '') {
|
|
|
+ const placeholder = document.createElement('div');
|
|
|
+ placeholder.style.width = '100%';
|
|
|
+ placeholder.style.height = '100%';
|
|
|
+ placeholder.style.backgroundColor = 'blue';
|
|
|
+ placeholder.style.display = 'flex';
|
|
|
+ placeholder.style.alignItems = 'center';
|
|
|
+ placeholder.style.justifyContent = 'center';
|
|
|
+ placeholder.style.color = '#fff';
|
|
|
+ placeholder.style.fontSize = '18px';
|
|
|
+ placeholder.innerText = text;
|
|
|
+ container.innerHTML = ''; // 清空容器
|
|
|
+ container.appendChild(placeholder);
|
|
|
+ },
|
|
|
|
|
|
- async publishLocalAudio() {
|
|
|
- const devices = await navigator.mediaDevices.enumerateDevices();
|
|
|
- const hasMic = devices.some(d => d.kind === 'audioinput');
|
|
|
- if (!hasMic) {
|
|
|
- console.warn('无可用麦克风,跳过音频轨道发布');
|
|
|
- return;
|
|
|
- }
|
|
|
- const audioTrack = await createLocalAudioTrack();
|
|
|
- await this.room.localParticipant.publishTrack(audioTrack);
|
|
|
- this.localTracks.push(audioTrack);
|
|
|
- },
|
|
|
-
|
|
|
- // 接听通话
|
|
|
- async accept() {
|
|
|
- this.stopRingtone()
|
|
|
- try {
|
|
|
- console.time("joinRoom");
|
|
|
-
|
|
|
- this.room = await Trtc.joinRoom(
|
|
|
- this.acceptToken,
|
|
|
- this.acceptURL,
|
|
|
- this.userID,
|
|
|
- (track, participant) => {
|
|
|
- this.updateParticipantInfo(participant.identity)
|
|
|
- this.attachRemoteTrack(track, participant)
|
|
|
- }
|
|
|
- )
|
|
|
-
|
|
|
- console.timeEnd("joinRoom");
|
|
|
- console.log("Room 对象:", this.room);
|
|
|
-
|
|
|
- this.setupRoomListeners();
|
|
|
- this.isDialled = false;
|
|
|
- this.calling = true;
|
|
|
- this.startCallTimer();
|
|
|
- } catch (error) {
|
|
|
- console.error('接听失败:', error);
|
|
|
- this.resetCallState();
|
|
|
+ async publishLocalAudio() {
|
|
|
+ const devices = await navigator.mediaDevices.enumerateDevices();
|
|
|
+ const hasMic = devices.some(d => d.kind === 'audioinput');
|
|
|
+ if (!hasMic) {
|
|
|
+ console.warn('无可用麦克风,跳过音频轨道发布');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const audioTrack = await createLocalAudioTrack();
|
|
|
+ await this.room.localParticipant.publishTrack(audioTrack);
|
|
|
+ this.localTracks.push(audioTrack);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 接听通话
|
|
|
+ async accept() {
|
|
|
+ this.stopRingtone()
|
|
|
+ try {
|
|
|
+ console.time("joinRoom");
|
|
|
+
|
|
|
+ this.room = await Trtc.joinRoom(
|
|
|
+ this.acceptToken,
|
|
|
+ this.acceptURL,
|
|
|
+ this.userID,
|
|
|
+ (track, participant) => {
|
|
|
+ this.updateParticipantInfo(participant.identity)
|
|
|
+ this.attachRemoteTrack(track, participant)
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ console.timeEnd("joinRoom");
|
|
|
+ console.log("Room 对象:", this.room);
|
|
|
+
|
|
|
+ this.setupRoomListeners();
|
|
|
+ this.isDialled = false;
|
|
|
+ this.calling = true;
|
|
|
+ this.startCallTimer();
|
|
|
+ // 接听后挂载本地音视频
|
|
|
+ if (this.callType === 'video') {
|
|
|
+ await this.publishLocalVideo()
|
|
|
}
|
|
|
- },
|
|
|
-
|
|
|
- // 拒绝通话
|
|
|
- async reject() {
|
|
|
- this.stopRingtone();
|
|
|
- try {
|
|
|
- console.log('接收到的邀请数据:', this.inviteData);
|
|
|
- //this.stopRingtone();
|
|
|
- await this.OpenIM.signalingReject({
|
|
|
+ await this.publishLocalAudio()
|
|
|
+ } catch (error) {
|
|
|
+ console.error('接听失败:', error);
|
|
|
+ this.resetCallState();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 拒绝通话
|
|
|
+ async reject() {
|
|
|
+ this.stopRingtone();
|
|
|
+ try {
|
|
|
+ console.log('接收到的邀请数据:', this.inviteData);
|
|
|
+ //this.stopRingtone();
|
|
|
+ await this.OpenIM.signalingReject({
|
|
|
+ opUserID: this.userID, // 当前用户ID
|
|
|
+ invitation: this.inviteData // 完整 invitation 对象
|
|
|
+ });
|
|
|
+ this.resetCallState();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('拒绝失败:', error);
|
|
|
+ //message.error(t('toast.rejectFailed'));
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 挂断通话
|
|
|
+ async hangUp() {
|
|
|
+ console.log("接听还是拨打",this.inviteData)
|
|
|
+ console.log("接听还是拨打",this.dialling,this.calling)
|
|
|
+ this.stopRingtone();
|
|
|
+ try {
|
|
|
+ if (this.dialling) {
|
|
|
+ await this.OpenIM.signalingCancel({
|
|
|
+ opUserID: this.userID, // 当前用户ID
|
|
|
+ invitation: this.inviteData // 完整 invitation 对象
|
|
|
+ });
|
|
|
+ } else if (this.calling) {
|
|
|
+ await this.OpenIM.signalingHungUp({
|
|
|
opUserID: this.userID, // 当前用户ID
|
|
|
invitation: this.inviteData // 完整 invitation 对象
|
|
|
});
|
|
|
- this.resetCallState();
|
|
|
- } catch (error) {
|
|
|
- console.error('拒绝失败:', error);
|
|
|
- //message.error(t('toast.rejectFailed'));
|
|
|
}
|
|
|
- },
|
|
|
-
|
|
|
- // 挂断通话
|
|
|
- async hangUp() {
|
|
|
- console.log("接听还是拨打",this.inviteData)
|
|
|
- console.log("接听还是拨打",this.dialling,this.calling)
|
|
|
- this.stopRingtone();
|
|
|
- try {
|
|
|
- if (this.dialling) {
|
|
|
- await this.OpenIM.signalingCancel({
|
|
|
- opUserID: this.userID, // 当前用户ID
|
|
|
- invitation: this.inviteData // 完整 invitation 对象
|
|
|
- });
|
|
|
- } else if (this.calling) {
|
|
|
- await this.OpenIM.signalingHungUp({
|
|
|
- opUserID: this.userID, // 当前用户ID
|
|
|
- invitation: this.inviteData // 完整 invitation 对象
|
|
|
- });
|
|
|
- }
|
|
|
|
|
|
- // 离开房间
|
|
|
- if (Trtc) {
|
|
|
- await Trtc.leaveRoom();
|
|
|
- await this.sleep(500)
|
|
|
- }
|
|
|
+ // 离开房间
|
|
|
+ if (Trtc) {
|
|
|
+ await Trtc.leaveRoom();
|
|
|
+ await this.sleep(500)
|
|
|
+ }
|
|
|
|
|
|
- this.resetCallState();
|
|
|
+ this.resetCallState();
|
|
|
|
|
|
- } catch (error) {
|
|
|
- console.error('挂断失败:', error);
|
|
|
- //message.error(t('toast.hangUpFailed'));
|
|
|
+ } catch (error) {
|
|
|
+ console.error('挂断失败:', error);
|
|
|
+ //message.error(t('toast.hangUpFailed'));
|
|
|
+ }
|
|
|
+ },
|
|
|
+ sleep(ms) {
|
|
|
+ return new Promise(resolve => setTimeout(resolve, ms))
|
|
|
+ },
|
|
|
+ // 防抖处理
|
|
|
+ handleDebounce(func, wait) {
|
|
|
+ if (this.timeout) clearTimeout(this.timeout)
|
|
|
+ this.timeout = setTimeout(() => {
|
|
|
+ func()
|
|
|
+ }, wait)
|
|
|
+ },
|
|
|
+ // 开始通话计时
|
|
|
+ startCallTimer() {
|
|
|
+ this.start = Date.now();
|
|
|
+ this.durationTimer = setInterval(() => {
|
|
|
+ this.duration = Math.floor((Date.now() - this.start) / 1000);
|
|
|
+ }, 1000);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 重置通话状态
|
|
|
+ resetCallState() {
|
|
|
+ clearInterval(this.durationTimer);
|
|
|
+ this.duration = 0;
|
|
|
+ this.dialling = false;
|
|
|
+ this.calling = false;
|
|
|
+ this.isDialled = false;
|
|
|
+ this.localTracks = [];
|
|
|
+ this.remoteUsers = [];
|
|
|
+ this.callingUserList = [];
|
|
|
+ this.inviteID = '';
|
|
|
+ this.inviteData = {};
|
|
|
+ this.roomToken = '';
|
|
|
+ this.roomURL = '';
|
|
|
+ this.roomID = '';
|
|
|
+ this.room = null;
|
|
|
+ this.acceptToken = '';
|
|
|
+ this.acceptURL = '';
|
|
|
+ this.acceptRoomID = '';
|
|
|
+ },
|
|
|
+
|
|
|
+ // 切换摄像头状态
|
|
|
+ async toggleCamera() {
|
|
|
+ try {
|
|
|
+ this.isCamOn = !this.isCamOn;
|
|
|
+ const videoTrack = this.localTracks.find(t => t.kind === 'video');
|
|
|
+ if (videoTrack) {
|
|
|
+ this.isCamOn ? await videoTrack.unmute() : await videoTrack.mute();
|
|
|
}
|
|
|
- },
|
|
|
- sleep(ms) {
|
|
|
- return new Promise(resolve => setTimeout(resolve, ms))
|
|
|
- },
|
|
|
- // 防抖处理
|
|
|
- handleDebounce(func, wait) {
|
|
|
- if (this.timeout) clearTimeout(this.timeout)
|
|
|
- this.timeout = setTimeout(() => {
|
|
|
- func()
|
|
|
- }, wait)
|
|
|
- },
|
|
|
- // 开始通话计时
|
|
|
- startCallTimer() {
|
|
|
- this.start = Date.now();
|
|
|
- this.durationTimer = setInterval(() => {
|
|
|
- this.duration = Math.floor((Date.now() - this.start) / 1000);
|
|
|
- }, 1000);
|
|
|
- },
|
|
|
-
|
|
|
- // 重置通话状态
|
|
|
- resetCallState() {
|
|
|
- clearInterval(this.durationTimer);
|
|
|
- this.duration = 0;
|
|
|
- this.dialling = false;
|
|
|
- this.calling = false;
|
|
|
- this.isDialled = false;
|
|
|
- this.localTracks = [];
|
|
|
- this.remoteUsers = [];
|
|
|
- this.callingUserList = [];
|
|
|
- this.inviteID = '';
|
|
|
- this.inviteData = {};
|
|
|
- this.roomToken = '';
|
|
|
- this.roomURL = '';
|
|
|
- this.roomID = '';
|
|
|
- this.room = null;
|
|
|
- this.acceptToken = '';
|
|
|
- this.acceptURL = '';
|
|
|
- this.acceptRoomID = '';
|
|
|
- },
|
|
|
-
|
|
|
- // 切换摄像头状态
|
|
|
- async toggleCamera() {
|
|
|
- try {
|
|
|
- this.isCamOn = !this.isCamOn;
|
|
|
- const videoTrack = this.localTracks.find(t => t.kind === 'video');
|
|
|
- if (videoTrack) {
|
|
|
- this.isCamOn ? await videoTrack.unmute() : await videoTrack.mute();
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('切换摄像头失败:', error);
|
|
|
- this.isCamOn = !this.isCamOn;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('切换摄像头失败:', error);
|
|
|
+ this.isCamOn = !this.isCamOn;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 切换麦克风状态
|
|
|
+ async toggleMic() {
|
|
|
+ try {
|
|
|
+ this.isMicOn = !this.isMicOn;
|
|
|
+ const audioTrack = this.localTracks.find(t => t.kind === 'audio');
|
|
|
+ if (audioTrack) {
|
|
|
+ this.isMicOn ? await audioTrack.unmute() : await audioTrack.mute();
|
|
|
}
|
|
|
- },
|
|
|
-
|
|
|
- // 切换麦克风状态
|
|
|
- async toggleMic() {
|
|
|
- try {
|
|
|
- this.isMicOn = !this.isMicOn;
|
|
|
- const audioTrack = this.localTracks.find(t => t.kind === 'audio');
|
|
|
- if (audioTrack) {
|
|
|
- this.isMicOn ? await audioTrack.unmute() : await audioTrack.mute();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('切换麦克风失败:', error);
|
|
|
+ this.isMicOn = !this.isMicOn;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理参与者接受邀请
|
|
|
+ async handleInviteeAccepted(data) {
|
|
|
+ if (data.inviteID !== this.inviteID) return
|
|
|
+ this.stopRingtone()
|
|
|
+ this.inviteData = data.data.invitation
|
|
|
+ try {
|
|
|
+ // joinRoom,发布本地轨道 & 挂载远端轨道都在这里完成
|
|
|
+ this.room = await Trtc.joinRoom(
|
|
|
+ this.roomToken,
|
|
|
+ this.roomURL,
|
|
|
+ this.userID,
|
|
|
+ (track, participant) => {
|
|
|
+ this.updateParticipantInfo(participant.identity)
|
|
|
+ this.attachRemoteTrack(track, participant)
|
|
|
}
|
|
|
- } catch (error) {
|
|
|
- console.error('切换麦克风失败:', error);
|
|
|
- this.isMicOn = !this.isMicOn;
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- // 处理参与者接受邀请
|
|
|
- async handleInviteeAccepted(data) {
|
|
|
- if (data.inviteID !== this.inviteID) return
|
|
|
- this.stopRingtone()
|
|
|
- this.inviteData = data.data.invitation
|
|
|
- try {
|
|
|
- // joinRoom,发布本地轨道 & 挂载远端轨道都在这里完成
|
|
|
- this.room = await Trtc.joinRoom(
|
|
|
- this.roomToken,
|
|
|
- this.roomURL,
|
|
|
- this.userID,
|
|
|
- (track, participant) => {
|
|
|
- this.updateParticipantInfo(participant.identity)
|
|
|
- this.attachRemoteTrack(track, participant)
|
|
|
- }
|
|
|
- )
|
|
|
- console.log("Room 对象:", this.room);
|
|
|
- this.setupRoomListeners();
|
|
|
- this.calling = true
|
|
|
- this.dialling = false
|
|
|
- this.isDialled = false
|
|
|
- this.startCallTimer()
|
|
|
- } catch (err) {
|
|
|
- console.error('主叫方 joinRoom 出错', err)
|
|
|
+ )
|
|
|
+ console.log("Room 对象:", this.room);
|
|
|
+ this.setupRoomListeners();
|
|
|
+ this.calling = true
|
|
|
+ this.dialling = false
|
|
|
+ this.isDialled = false
|
|
|
+ this.startCallTimer()
|
|
|
+ // 加上本地轨道挂载
|
|
|
+ if (this.callType === 'video') {
|
|
|
+ await this.publishLocalVideo()
|
|
|
}
|
|
|
- },
|
|
|
+ await this.publishLocalAudio()
|
|
|
+ } catch (err) {
|
|
|
+ console.error('主叫方 joinRoom 出错', err)
|
|
|
+ }
|
|
|
+ },
|
|
|
|
|
|
|
|
|
- // 处理参与者拒绝邀请
|
|
|
- handleInviteeRejected(data) {
|
|
|
- console.log(123456789)
|
|
|
- this.stopRingtone();
|
|
|
- if (data.inviteID === this.inviteID) {
|
|
|
- this.invitedUserInfo = this.invitedUserInfo.filter(
|
|
|
- user => user.userID !== data.data.invitation.inviteeUserID
|
|
|
- );
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- // 处理邀请超时
|
|
|
- handleInvitationTimeout(data) {
|
|
|
- this.stopRingtone();
|
|
|
- if (data.inviteID === this.inviteID) {
|
|
|
- //message.warning(t('toast.callTimeout'));
|
|
|
- this.resetCallState();
|
|
|
- }
|
|
|
- },
|
|
|
+ // 处理参与者拒绝邀请
|
|
|
+ handleInviteeRejected(data) {
|
|
|
+ console.log(123456789)
|
|
|
+ this.stopRingtone();
|
|
|
+ if (data.inviteID === this.inviteID) {
|
|
|
+ this.invitedUserInfo = this.invitedUserInfo.filter(
|
|
|
+ user => user.userID !== data.data.invitation.inviteeUserID
|
|
|
+ );
|
|
|
+ }
|
|
|
+ },
|
|
|
|
|
|
- // 处理挂断
|
|
|
- handleHangUp(data) {
|
|
|
+ // 处理邀请超时
|
|
|
+ handleInvitationTimeout(data) {
|
|
|
+ this.stopRingtone();
|
|
|
+ if (data.inviteID === this.inviteID) {
|
|
|
+ //message.warning(t('toast.callTimeout'));
|
|
|
+ this.resetCallState();
|
|
|
+ }
|
|
|
+ },
|
|
|
|
|
|
- this.stopRingtone();
|
|
|
- if (data.inviteID === this.inviteID) {
|
|
|
- //message.info(t('toast.callEnded'));
|
|
|
- this.resetCallState();
|
|
|
- }
|
|
|
- },
|
|
|
+ // 处理挂断
|
|
|
+ handleHangUp(data) {
|
|
|
|
|
|
- // 更新参与者信息
|
|
|
- updateParticipantInfo(userID) {
|
|
|
- if (!this.callingUserList.includes(userID)) {
|
|
|
- this.callingUserList.push(userID); // ✅ 先 push
|
|
|
- }
|
|
|
- this.OpenIM.getUsersInfo([userID])
|
|
|
- .then(({ data }) => {
|
|
|
- console.log("查询用户信息",data)
|
|
|
- this.faceImages = data[0].faceURL
|
|
|
- this.nickName = data[0].nickname
|
|
|
- console.log("更新参与者信息", this.invitedUserInfo)
|
|
|
- const user = this.invitedUserInfo.find(u => u.userID === userID);
|
|
|
- console.log(this.faceImages)
|
|
|
- console.log(this.nickName)
|
|
|
- if (!user) {
|
|
|
- this.invitedUserInfo.push({
|
|
|
- userID,
|
|
|
- nickname: this.nickName,
|
|
|
- avatar: this.faceImages,
|
|
|
- isMicOn: true
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- // data: 查询到的用户信息列表
|
|
|
- })
|
|
|
- .catch(({ errCode, errMsg }) => {
|
|
|
- // 调用失败attachRemoteTrack
|
|
|
- });
|
|
|
+ this.stopRingtone();
|
|
|
+ if (data.inviteID === this.inviteID) {
|
|
|
+ //message.info(t('toast.callEnded'));
|
|
|
+ this.resetCallState();
|
|
|
+ }
|
|
|
+ },
|
|
|
|
|
|
- },
|
|
|
+ // 更新参与者信息
|
|
|
+ updateParticipantInfo(userID) {
|
|
|
+ if (!this.callingUserList.includes(userID)) {
|
|
|
+ this.callingUserList.push(userID); // ✅ 先 push
|
|
|
+ }
|
|
|
+ this.OpenIM.getUsersInfo([userID])
|
|
|
+ .then(({ data }) => {
|
|
|
+ console.log("查询用户信息",data)
|
|
|
+ this.faceImages = data[0].faceURL
|
|
|
+ this.nickName = data[0].nickname
|
|
|
+ console.log("更新参与者信息", this.invitedUserInfo)
|
|
|
+ const user = this.invitedUserInfo.find(u => u.userID === userID);
|
|
|
+ console.log(this.faceImages)
|
|
|
+ console.log(this.nickName)
|
|
|
+ if (!user) {
|
|
|
+ this.invitedUserInfo.push({
|
|
|
+ userID,
|
|
|
+ nickname: this.nickName,
|
|
|
+ avatar: this.faceImages,
|
|
|
+ isMicOn: true
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- // 附加远端轨道
|
|
|
- attachRemoteTrack(track, participant, retryCount = 0) {
|
|
|
- const kind = track.kind;
|
|
|
- const uid = participant.identity;
|
|
|
+ // data: 查询到的用户信息列表
|
|
|
+ })
|
|
|
+ .catch(({ errCode, errMsg }) => {
|
|
|
+ // 调用失败attachRemoteTrack
|
|
|
+ });
|
|
|
|
|
|
- if (kind === 'audio') {
|
|
|
- const audioEl = document.createElement('audio');
|
|
|
- audioEl.autoplay = true;
|
|
|
- audioEl.playsInline = true;
|
|
|
- track.attach(audioEl);
|
|
|
- document.body.appendChild(audioEl); // 直接挂到 body,避免容器找不到
|
|
|
- return;
|
|
|
- }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 附加远端轨道
|
|
|
+ attachRemoteTrack(track, participant, retryCount = 0) {
|
|
|
+ const kind = track.kind;
|
|
|
+ const uid = participant.identity;
|
|
|
+
|
|
|
+ if (kind === 'audio') {
|
|
|
+ const audioEl = document.createElement('audio');
|
|
|
+ audioEl.autoplay = true;
|
|
|
+ audioEl.playsInline = true;
|
|
|
+ track.attach(audioEl);
|
|
|
+ document.body.appendChild(audioEl); // 直接挂到 body,避免容器找不到
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- if (kind === 'video') {
|
|
|
- const containerId = `video-${uid}`;
|
|
|
- let container = document.getElementById(containerId);
|
|
|
+ if (kind === 'video') {
|
|
|
+ const containerId = `video-${uid}`;
|
|
|
+ let container = document.getElementById(containerId);
|
|
|
|
|
|
- if (!container) {
|
|
|
- if (retryCount < 20) {
|
|
|
- setTimeout(() => this.attachRemoteTrack(track, participant, retryCount + 1), 200);
|
|
|
- return;
|
|
|
- }
|
|
|
- console.warn(`找不到容器: ${containerId}`);
|
|
|
+ if (!container) {
|
|
|
+ if (retryCount < 20) {
|
|
|
+ setTimeout(() => this.attachRemoteTrack(track, participant, retryCount + 1), 200);
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- container.innerHTML = '';
|
|
|
- const videoEl = document.createElement('video');
|
|
|
- videoEl.autoplay = true;
|
|
|
- videoEl.playsInline = true;
|
|
|
- videoEl.style.width = '100%';
|
|
|
- videoEl.style.height = '100%';
|
|
|
- container.appendChild(videoEl);
|
|
|
- track.attach(videoEl);
|
|
|
+ console.warn(`找不到容器: ${containerId}`);
|
|
|
+ return;
|
|
|
}
|
|
|
+
|
|
|
+ container.innerHTML = '';
|
|
|
+ const videoEl = document.createElement('video');
|
|
|
+ videoEl.autoplay = true;
|
|
|
+ videoEl.playsInline = true;
|
|
|
+ videoEl.style.width = '100%';
|
|
|
+ videoEl.style.height = '100%';
|
|
|
+ container.appendChild(videoEl);
|
|
|
+ track.attach(videoEl);
|
|
|
}
|
|
|
}
|
|
|
- };
|
|
|
+ }
|
|
|
+};
|
|
|
</script>
|
|
|
|
|
|
<style lang="stylus" scoped>
|