消息模块开发完成
This commit is contained in:
@@ -161,6 +161,16 @@ function escapeHtml(value) {
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function getCurrentUserId() {
|
||||
try {
|
||||
const settings = JSON.parse(localStorage.getItem('userSettings') || '{}');
|
||||
return settings.userId || settings.id || '';
|
||||
} catch (error) {
|
||||
console.error('Error parsing current user settings:', error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示全部在线WebSocket用户
|
||||
* @param {Array} users - 在线用户列表
|
||||
@@ -212,6 +222,7 @@ function displayOnlineUsers(users) {
|
||||
const userName = user.name || user.userId || '匿名用户';
|
||||
const avatar = user.avatar || '/images/p2.png';
|
||||
const roleLabel = user.role === 'host' ? '房主' : (user.role === 'participant' ? '成员' : '大厅');
|
||||
const isSelf = Boolean(user.userId) && user.userId === getCurrentUserId();
|
||||
const userItem = document.createElement('div');
|
||||
userItem.className = 'flex items-center justify-between rounded-lg bg-black/20 px-3 py-2';
|
||||
userItem.innerHTML = `
|
||||
@@ -222,7 +233,10 @@ function displayOnlineUsers(users) {
|
||||
<div class="text-xs text-gray-400 truncate">${escapeHtml(user.userId || user.socketId || user.participantId || '未设置ID')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-xs px-2 py-1 rounded-full ${user.role === 'host' ? 'bg-indigo-500/20 text-indigo-300' : (user.role === 'participant' ? 'bg-white/10 text-gray-300' : 'bg-emerald-500/20 text-emerald-300')}">${roleLabel}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs px-2 py-1 rounded-full ${user.role === 'host' ? 'bg-indigo-500/20 text-indigo-300' : (user.role === 'participant' ? 'bg-white/10 text-gray-300' : 'bg-emerald-500/20 text-emerald-300')}">${roleLabel}</span>
|
||||
${isSelf ? '<span class="text-xs text-gray-500">自己</span>' : ''}
|
||||
</div>
|
||||
`;
|
||||
roomList.appendChild(userItem);
|
||||
});
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
let connectionId = "";
|
||||
// 当前视图状态:'connect' 或 'call'(可用于未来扩展)
|
||||
let currentView = 'connect';
|
||||
let pendingIncomingInvite = null;
|
||||
let inviteHandlersBound = false;
|
||||
|
||||
function getInvitePayloadFromUrl() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
@@ -39,6 +41,7 @@ function showCallRequestDialog(caller = {}) {
|
||||
const callerName = caller.name || '邀请方';
|
||||
const callerAvatar = caller.avatar || '/images/p2.png';
|
||||
const targetConnectionId = caller.connectionId || '';
|
||||
pendingIncomingInvite = caller;
|
||||
|
||||
if (document.getElementById('callRequestName')) {
|
||||
document.getElementById('callRequestName').textContent = callerName;
|
||||
@@ -60,6 +63,44 @@ function showCallRequestDialog(caller = {}) {
|
||||
dialog.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function getCurrentUserProfile() {
|
||||
try {
|
||||
const settings = JSON.parse(localStorage.getItem('userSettings') || '{}');
|
||||
return {
|
||||
userId: settings.userId || settings.id || '',
|
||||
name: settings.name || '我',
|
||||
avatar: settings.avatar || '/images/p1.png'
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error parsing user settings:', error);
|
||||
return {
|
||||
userId: '',
|
||||
name: '我',
|
||||
avatar: '/images/p1.png'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function bindInviteSignalHandlers() {
|
||||
if (inviteHandlersBound) {
|
||||
return;
|
||||
}
|
||||
|
||||
store.onSocketEvent('invite-call', (payload) => {
|
||||
pendingIncomingInvite = {
|
||||
connectionId: payload.connectionId,
|
||||
inviterSocketId: payload.inviterSocketId,
|
||||
inviterUserId: payload.inviterUserId,
|
||||
name: payload.inviterName || '邀请方',
|
||||
avatar: payload.inviterAvatar || '/images/p2.png'
|
||||
};
|
||||
showCallRequestDialog(pendingIncomingInvite);
|
||||
showNotification(`${pendingIncomingInvite.name} 邀请你加入通话`);
|
||||
});
|
||||
|
||||
inviteHandlersBound = true;
|
||||
}
|
||||
|
||||
function bindInviteDialogEvents() {
|
||||
window.showCallRequest = function (caller) {
|
||||
showCallRequestDialog(caller);
|
||||
@@ -70,6 +111,18 @@ function bindInviteDialogEvents() {
|
||||
if (dialog) {
|
||||
dialog.classList.add('hidden');
|
||||
}
|
||||
if (pendingIncomingInvite) {
|
||||
try {
|
||||
store.sendInviteRejected({
|
||||
connectionId: pendingIncomingInvite.connectionId || '',
|
||||
targetSocketId: pendingIncomingInvite.inviterSocketId || '',
|
||||
targetUserId: pendingIncomingInvite.inviterUserId || ''
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error rejecting invite:', error);
|
||||
}
|
||||
}
|
||||
pendingIncomingInvite = null;
|
||||
showNotification('已拒绝通话请求');
|
||||
};
|
||||
|
||||
@@ -80,6 +133,7 @@ function bindInviteDialogEvents() {
|
||||
}
|
||||
|
||||
const targetConnectionId =
|
||||
(pendingIncomingInvite && pendingIncomingInvite.connectionId) ||
|
||||
connectionId ||
|
||||
localStorage.getItem('connectionId') ||
|
||||
new URLSearchParams(window.location.search).get('connectionId');
|
||||
@@ -91,7 +145,23 @@ function bindInviteDialogEvents() {
|
||||
|
||||
connectionId = targetConnectionId;
|
||||
localStorage.setItem('connectionId', targetConnectionId);
|
||||
|
||||
if (pendingIncomingInvite) {
|
||||
try {
|
||||
store.sendInviteAccepted({
|
||||
connectionId: targetConnectionId,
|
||||
targetSocketId: pendingIncomingInvite.inviterSocketId || '',
|
||||
targetUserId: pendingIncomingInvite.inviterUserId || ''
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error accepting invite:', error);
|
||||
showNotification('接受邀请失败,请稍后重试', 'error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
showNotification('已接受通话请求');
|
||||
pendingIncomingInvite = null;
|
||||
|
||||
if (currentView !== 'call') {
|
||||
await switchToCallView(targetConnectionId);
|
||||
@@ -139,9 +209,11 @@ async function switchToCallView(connectionId) {
|
||||
bindCallViewDomEvents();
|
||||
|
||||
console.log('Video call app initialized successfully');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error initializing app:', error);
|
||||
showNotification('初始化失败,请刷新页面重试', 'error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,14 +249,14 @@ function bindCallViewDomEvents() {
|
||||
};
|
||||
|
||||
// 切换麦克风
|
||||
window.toggleMute = function (button) {
|
||||
window.toggleMute = function () {
|
||||
const state = store.getState();
|
||||
const currentState = state.session.localUser.mediaState.audio;
|
||||
store.updateLocalMedia('audio', !currentState);
|
||||
};
|
||||
|
||||
// 切换视频
|
||||
window.toggleVideo = function (button) {
|
||||
window.toggleVideo = function () {
|
||||
const state = store.getState();
|
||||
const currentState = state.session.localUser.mediaState.video;
|
||||
store.updateLocalMedia('video', !currentState);
|
||||
@@ -196,7 +268,7 @@ function bindCallViewDomEvents() {
|
||||
};
|
||||
|
||||
// 切换录屏
|
||||
window.toggleRecording = function (button) {
|
||||
window.toggleRecording = function () {
|
||||
const state = store.getState();
|
||||
const currentState = state.session.localUser.mediaState.recording || false;
|
||||
store.updateLocalMedia('recording', !currentState);
|
||||
@@ -304,6 +376,7 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
|
||||
// 初始化WebSocket连接(在connect视图就建立WebSocket)
|
||||
await initWebSocket();
|
||||
bindInviteSignalHandlers();
|
||||
|
||||
// 绑定connect视图事件(加入通话、创建通话等)
|
||||
bindConnectViewEvents(handleJoinCall, handleCreateCall);
|
||||
|
||||
@@ -56,6 +56,8 @@ class CallStateManager {
|
||||
|
||||
// 监听器数组
|
||||
this.listeners = [];
|
||||
this.socketEventHandlers = {};
|
||||
this._socketInviteBound = false;
|
||||
}
|
||||
|
||||
// 订阅状态变化
|
||||
@@ -339,10 +341,68 @@ class CallStateManager {
|
||||
// 创建信令实例
|
||||
this._signaling = this.useWebSocket ? new WebSocketSignaling() : new Signaling();
|
||||
await this._signaling.start();
|
||||
this._bindSocketInviteEvents(this._signaling);
|
||||
console.log('Signaling connected (WebSocket only, no room yet)');
|
||||
return this._signaling;
|
||||
}
|
||||
|
||||
_bindSocketInviteEvents(signaling) {
|
||||
if (!signaling || this._socketInviteBound || typeof signaling.addEventListener !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
['invite-call', 'invite-accepted', 'invite-rejected', 'invite-failed'].forEach((eventName) => {
|
||||
signaling.addEventListener(eventName, (event) => {
|
||||
const handler = this.socketEventHandlers[eventName];
|
||||
if (typeof handler === 'function') {
|
||||
handler(event.detail);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this._socketInviteBound = true;
|
||||
}
|
||||
|
||||
onSocketEvent(eventName, handler) {
|
||||
this.socketEventHandlers[eventName] = handler;
|
||||
}
|
||||
|
||||
getActiveSignaling() {
|
||||
if (this._signaling) {
|
||||
return this._signaling;
|
||||
}
|
||||
|
||||
if (this.renderstreaming && this.renderstreaming._signaling) {
|
||||
return this.renderstreaming._signaling;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
sendInviteCall(payload) {
|
||||
const signaling = this.getActiveSignaling();
|
||||
if (!signaling || typeof signaling.sendInviteCall !== 'function') {
|
||||
throw new Error('Invite signaling is not ready');
|
||||
}
|
||||
signaling.sendInviteCall(payload);
|
||||
}
|
||||
|
||||
sendInviteAccepted(payload) {
|
||||
const signaling = this.getActiveSignaling();
|
||||
if (!signaling || typeof signaling.sendInviteAccepted !== 'function') {
|
||||
throw new Error('Invite signaling is not ready');
|
||||
}
|
||||
signaling.sendInviteAccepted(payload);
|
||||
}
|
||||
|
||||
sendInviteRejected(payload) {
|
||||
const signaling = this.getActiveSignaling();
|
||||
if (!signaling || typeof signaling.sendInviteRejected !== 'function') {
|
||||
throw new Error('Invite signaling is not ready');
|
||||
}
|
||||
signaling.sendInviteRejected(payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在仅建立WebSocket连接时同步当前用户信息
|
||||
* @param {{ id?: string, name?: string, avatar?: string } | null} userInfo - 用户信息
|
||||
|
||||
@@ -221,6 +221,18 @@ export class WebSocketSignaling extends EventTarget {
|
||||
case "broadcast":
|
||||
this.dispatchEvent(new CustomEvent('on-message', { detail: msg.message }));
|
||||
break;
|
||||
case "invite-call":
|
||||
this.dispatchEvent(new CustomEvent('invite-call', { detail: msg.data }));
|
||||
break;
|
||||
case "invite-accepted":
|
||||
this.dispatchEvent(new CustomEvent('invite-accepted', { detail: msg.data }));
|
||||
break;
|
||||
case "invite-rejected":
|
||||
this.dispatchEvent(new CustomEvent('invite-rejected', { detail: msg.data }));
|
||||
break;
|
||||
case "invite-failed":
|
||||
this.dispatchEvent(new CustomEvent('invite-failed', { detail: msg.data }));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -288,4 +300,22 @@ export class WebSocketSignaling extends EventTarget {
|
||||
Logger.log(sendJson);
|
||||
this.websocket.send(sendJson);
|
||||
}
|
||||
|
||||
sendInviteCall(payload) {
|
||||
const sendJson = JSON.stringify({ type: 'invite-call', data: payload });
|
||||
Logger.log(sendJson);
|
||||
this.websocket.send(sendJson);
|
||||
}
|
||||
|
||||
sendInviteAccepted(payload) {
|
||||
const sendJson = JSON.stringify({ type: 'invite-accepted', data: payload });
|
||||
Logger.log(sendJson);
|
||||
this.websocket.send(sendJson);
|
||||
}
|
||||
|
||||
sendInviteRejected(payload) {
|
||||
const sendJson = JSON.stringify({ type: 'invite-rejected', data: payload });
|
||||
Logger.log(sendJson);
|
||||
this.websocket.send(sendJson);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user