【m】增加多用户连接
This commit is contained in:
@@ -174,6 +174,10 @@ class UIRenderer {
|
||||
// 通话结束 - 渲染通话结束界面
|
||||
this.renderCallEnded();
|
||||
break;
|
||||
case 'PARTICIPANT_LEFT':
|
||||
// participant离开 - 更新UI但房间仍然存在
|
||||
this.renderParticipantLeft(changes.connectionId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -892,6 +896,19 @@ class UIRenderer {
|
||||
window.location.href = './endcall/endcall.html';
|
||||
}
|
||||
|
||||
// 渲染participant离开(host端,房间仍然存在)
|
||||
renderParticipantLeft(connectionId) {
|
||||
console.log(`Participant left: ${connectionId}, updating UI`);
|
||||
// 更新远程用户状态显示为离线
|
||||
if (this.elements.remoteNetworkIndicator) {
|
||||
this.elements.remoteNetworkIndicator.className = 'w-2 h-2 bg-gray-500 rounded-full';
|
||||
}
|
||||
// 显示断开连接的遮罩层(如果存在)
|
||||
if (this.elements.disconnectedOverlay) {
|
||||
this.elements.disconnectedOverlay.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText(status) {
|
||||
const statusMap = {
|
||||
|
||||
@@ -8,15 +8,9 @@ import { RenderStreaming } from "../../module/renderstreaming.js"; // WebRTC连
|
||||
import { getServerConfig, getRTCConfiguration } from "../js/config.js";//服务器配置和RTC配置
|
||||
import { showNotification, generateId } from './utils.js'; // 导入通知函数
|
||||
import chatMessage from './chatmessage.js';
|
||||
// 默认视频流尺寸
|
||||
const defaultStreamWidth = 1280;
|
||||
const defaultStreamHeight = 720;
|
||||
|
||||
class CallStateManager {
|
||||
constructor() {
|
||||
const renderstreaming = null; // WebRTC连接管理实例
|
||||
const useWebSocket = null; // 是否使用WebSocket信令
|
||||
const connectionId = null; // 连接ID
|
||||
// 核心状态
|
||||
this.state = {
|
||||
id: generateId(),
|
||||
@@ -332,9 +326,23 @@ class CallStateManager {
|
||||
]
|
||||
};
|
||||
this.renderstreaming = new RenderStreaming(signaling, config); // 创建WebRTC连接管理实例
|
||||
|
||||
this.renderstreaming.onNewPeer = (connectionId) => {
|
||||
console.log(`New peer created for ${connectionId}, adding local tracks`);
|
||||
if (this.state.localStream) {
|
||||
const tracks = this.state.localStream.getTracks();
|
||||
for (const track of tracks) {
|
||||
this.renderstreaming.addTransceiver(track, { direction: 'sendonly' });
|
||||
}
|
||||
this.setCodecPreferences();
|
||||
}
|
||||
};
|
||||
// 连接建立回调
|
||||
this.renderstreaming.onConnect = () => {
|
||||
this.renderstreaming.onConnect = (connectionId, data) => {
|
||||
// 保存角色信息(host/participant)
|
||||
if (data && data.role) {
|
||||
this.role = data.role;
|
||||
console.log(`Connected as ${this.role}`);
|
||||
}
|
||||
// 连接建立后,更新状态为ongoing
|
||||
this.state.session.status = 'ongoing';
|
||||
this.notify({ type: 'CALL_STATUS_CHANGE', status: 'ongoing' });
|
||||
@@ -347,21 +355,39 @@ class CallStateManager {
|
||||
});
|
||||
|
||||
if (this.state.localStream) {
|
||||
const tracks = this.state.localStream.getTracks(); // 获取本地媒体轨道
|
||||
for (const track of tracks) {
|
||||
this.renderstreaming.addTransceiver(track, { direction: 'sendonly' }); // 添加发送轨道
|
||||
}
|
||||
this.setCodecPreferences(); // 设置编解码器偏好
|
||||
this.showStatsMessage(); // 显示统计信息
|
||||
// const tracks = this.state.localStream.getTracks();
|
||||
// for (const track of tracks) {
|
||||
// this.renderstreaming.addTransceiver(track, { direction: 'sendonly' });
|
||||
// }
|
||||
// this.setCodecPreferences();
|
||||
this.showStatsMessage();
|
||||
} else {
|
||||
console.error('Local stream is not available');
|
||||
showNotification('本地视频流不可用', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
// 连接断开回调
|
||||
// 连接断开回调(收到服务器的 disconnect 消息,通常是 host 离开导致房间关闭)
|
||||
this.renderstreaming.onDisconnect = () => {
|
||||
this.hangUp(); // 挂断连接
|
||||
console.log('Received disconnect from server, host left or room closed');
|
||||
this.hangUp(); // 房间已关闭,挂断连接
|
||||
};
|
||||
|
||||
// participant离开回调(host收到,房间仍然存在)
|
||||
this.renderstreaming.onParticipantLeft = (connectionId) => {
|
||||
console.log(`Participant left: ${connectionId}, room still active`);
|
||||
// 更新远程用户状态,但不关闭房间
|
||||
this.updateRemoteUserStatus('offline');
|
||||
this.updateRemoteUserNetworkQuality('no_signal');
|
||||
showNotification('对方已离开通话', 'warning');
|
||||
// 清理远端流,重置Peer连接为新participant加入做准备
|
||||
if (this.state.remoteStream) {
|
||||
this.state.remoteStream.getTracks().forEach(track => track.stop());
|
||||
this.state.remoteStream = null;
|
||||
}
|
||||
this.notify({ type: 'REMOTE_STREAM_OBTAINED', stream: null });
|
||||
// 通知UI更新
|
||||
this.notify({ type: 'PARTICIPANT_LEFT', connectionId: connectionId });
|
||||
};
|
||||
|
||||
// 轨道事件回调
|
||||
@@ -473,6 +499,8 @@ class CallStateManager {
|
||||
|
||||
/**
|
||||
* 挂断WebRTC连接
|
||||
* Host挂断:房间删除,通知所有participants
|
||||
* Participant挂断:仅自己离开,房间保留
|
||||
* @async
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@@ -484,11 +512,17 @@ class CallStateManager {
|
||||
clearInterval(this.durationInterval);
|
||||
this.durationInterval = null;
|
||||
}
|
||||
console.log(`Disconnect peer on ${this.connectionId}.`);
|
||||
|
||||
const isHost = this.role === 'host';
|
||||
console.log(`Disconnect peer on ${this.connectionId}. Role: ${this.role}`);
|
||||
|
||||
// 删除连接并停止WebRTC
|
||||
if (this.renderstreaming) {
|
||||
try {
|
||||
// 发送断开连接信令给服务器
|
||||
// 服务器会根据角色决定:
|
||||
// - host断开:通知所有participants,删除房间
|
||||
// - participant断开:仅通知host,保留房间
|
||||
await this.renderstreaming.deleteConnection();
|
||||
await this.renderstreaming.stop();
|
||||
} catch (error) {
|
||||
@@ -501,8 +535,9 @@ class CallStateManager {
|
||||
this.updateRemoteUserStatus('offline');
|
||||
this.updateRemoteUserNetworkQuality('no_signal');
|
||||
this.connectionId = null;
|
||||
this.role = null;
|
||||
this.state.session.status = 'ended';
|
||||
this.notify({ type: 'CALL_ENDED' });
|
||||
this.notify({ type: 'CALL_ENDED', reason: isHost ? 'host_hangup' : 'participant_hangup' });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -570,18 +605,14 @@ class CallStateManager {
|
||||
this.state.session.remoteUser.networkQuality = networkQuality;
|
||||
this.notify({ type: 'REMOTE_MEDIA_CHANGE', localUser: this.state.session.localUser, remoteUser: this.state.session.remoteUser });
|
||||
}
|
||||
// 结束通话
|
||||
endCall() {
|
||||
if (this.durationInterval) {
|
||||
clearInterval(this.durationInterval);
|
||||
this.durationInterval = null;
|
||||
}
|
||||
this.state.session.status = 'ended';
|
||||
this.notify({ type: 'CALL_ENDED' });
|
||||
|
||||
// 发送结束通话请求到服务器
|
||||
// [API_CALL: POST /api/call/:callId/leave]
|
||||
// [WEBSOCKET_EMIT: leave-call]
|
||||
// 结束通话(用户主动点击挂断按钮)
|
||||
async endCall() {
|
||||
console.log(`endCall called. Role: ${this.role}`);
|
||||
// 调用 hangUp() 正确关闭 WebRTC 连接并发送断开信令
|
||||
// hangUp 内部会根据角色区分:
|
||||
// - host: 通知所有participants,删除房间
|
||||
// - participant: 仅自己离开,房间保留
|
||||
await this.hangUp();
|
||||
}
|
||||
|
||||
// 加入通话
|
||||
|
||||
@@ -42,7 +42,7 @@ export default class Peer extends EventTarget {
|
||||
|
||||
this.pc.oniceconnectionstatechange = () => {
|
||||
_this.log(`iceConnectionState changed:${_this.pc.iceConnectionState}`);
|
||||
if (_this.pc.iceConnectionState === 'disconnected') {
|
||||
if (_this.pc.iceConnectionState === 'failed') {
|
||||
this.dispatchEvent(new Event('disconnect'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -23,6 +23,8 @@ export class RenderStreaming {
|
||||
this.onTrackEvent = function (data) { Logger.log(`OnTrack event peer with data:${data}`); };
|
||||
this.onAddChannel = function (data) { Logger.log(`onAddChannel event peer with data:${data}`); };
|
||||
this.onMessage = function (data) { Logger.log(`On message: ${data}`); };
|
||||
this.onParticipantLeft = function (connectionId) { Logger.log(`Participant left on ${connectionId}.`); };
|
||||
this.onNewPeer = function (connectionId) { Logger.log(`New peer created for ${connectionId}.`); };
|
||||
this._config = config;
|
||||
this._signaling = signaling;
|
||||
this._signaling.addEventListener('connect', this._onConnect.bind(this));
|
||||
@@ -31,13 +33,14 @@ export class RenderStreaming {
|
||||
this._signaling.addEventListener('answer', this._onAnswer.bind(this));
|
||||
this._signaling.addEventListener('candidate', this._onIceCandidate.bind(this));
|
||||
this._signaling.addEventListener('on-message', this._onMessage.bind(this));
|
||||
this._signaling.addEventListener('participant-left', this._onParticipantLeft.bind(this));
|
||||
}
|
||||
|
||||
async _onConnect(e) {
|
||||
const data = e.detail;
|
||||
if (this._connectionId == data.connectionId) {
|
||||
this._preparePeerConnection(this._connectionId, data.polite);
|
||||
this.onConnect(data.connectionId);
|
||||
this.onConnect(data.connectionId, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +57,12 @@ export class RenderStreaming {
|
||||
|
||||
async _onOffer(e) {
|
||||
const offer = e.detail;
|
||||
// 如果已有Peer但ICE连接已断开,需要重建Peer
|
||||
if (this._peer && this._peer.pc && this._peer.pc.iceConnectionState === 'disconnected') {
|
||||
Logger.log('ICE disconnected, resetting PeerConnection for new offer');
|
||||
this._peer.close();
|
||||
this._peer = null;
|
||||
}
|
||||
if (!this._peer) {
|
||||
this._preparePeerConnection(offer.connectionId, offer.polite);
|
||||
}
|
||||
@@ -91,6 +100,12 @@ export class RenderStreaming {
|
||||
const data = e.detail;
|
||||
this.onMessage(data);
|
||||
}
|
||||
|
||||
async _onParticipantLeft(e) {
|
||||
const data = e.detail;
|
||||
Logger.log(`Participant left: ${data.connectionId}`);
|
||||
this.onParticipantLeft(data.connectionId);
|
||||
}
|
||||
/**
|
||||
* if not set argument, a generated uuid is used.
|
||||
* @param {string | null} connectionId
|
||||
@@ -114,9 +129,9 @@ export class RenderStreaming {
|
||||
|
||||
// Create peerConnection with proxy server and set up handlers
|
||||
this._peer = new Peer(connectionId, polite, this._config);
|
||||
this._peer.addEventListener('disconnect', () => {
|
||||
this.onDisconnect(`Receive disconnect message from peer. connectionId:${connectionId}`);
|
||||
});
|
||||
// this._peer.addEventListener('disconnect', () => {
|
||||
// this.onDisconnect(`Receive disconnect message from peer. connectionId:${connectionId}`);
|
||||
// });
|
||||
this._peer.addEventListener('trackevent', (e) => {
|
||||
const data = e.detail;
|
||||
this.onTrackEvent(data);
|
||||
@@ -145,6 +160,7 @@ export class RenderStreaming {
|
||||
const candidate = e.detail;
|
||||
this._signaling.sendCandidate(candidate.connectionId, candidate.candidate, candidate.sdpMid, candidate.sdpMLineIndex);
|
||||
});
|
||||
this.onNewPeer(connectionId);
|
||||
return this._peer;
|
||||
}
|
||||
|
||||
|
||||
@@ -193,6 +193,9 @@ export class WebSocketSignaling extends EventTarget {
|
||||
case "on-message":
|
||||
this.dispatchEvent(new CustomEvent('on-message', { detail: msg.data }));
|
||||
break;
|
||||
case "participant-left":
|
||||
this.dispatchEvent(new CustomEvent('participant-left', { detail: msg }));
|
||||
break;
|
||||
case "broadcast":
|
||||
// 处理服务器广播的消息
|
||||
this.dispatchEvent(new CustomEvent('on-message', { detail: msg.message }));
|
||||
|
||||
Reference in New Issue
Block a user