拆分part
This commit is contained in:
77
client/public/participants.js
Normal file
77
client/public/participants.js
Normal file
@@ -0,0 +1,77 @@
|
||||
export const DEFAULT_PARTICIPANT_NAME = '参与者';
|
||||
export const DEFAULT_PARTICIPANT_AVATAR = '/images/p2.png';
|
||||
|
||||
const DEFAULT_MEDIA_STATE = Object.freeze({
|
||||
audio: false,
|
||||
video: true,
|
||||
isSpeaking: false
|
||||
});
|
||||
|
||||
function createParticipantRecord(current = {}, patch = {}) {
|
||||
return {
|
||||
id: '',
|
||||
name: DEFAULT_PARTICIPANT_NAME,
|
||||
avatar: DEFAULT_PARTICIPANT_AVATAR,
|
||||
mediaState: { ...DEFAULT_MEDIA_STATE },
|
||||
status: 'online',
|
||||
...current,
|
||||
...patch,
|
||||
mediaState: {
|
||||
...DEFAULT_MEDIA_STATE,
|
||||
...(current.mediaState || {}),
|
||||
...(patch.mediaState || {})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function upsertParticipant(participants, participantId, patch = {}) {
|
||||
if (!participantId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
participants[participantId] = createParticipantRecord(participants[participantId], patch);
|
||||
return participants[participantId];
|
||||
}
|
||||
|
||||
export function removeParticipant(participants, participantId) {
|
||||
if (!participantId || !participants[participantId]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
delete participants[participantId];
|
||||
return true;
|
||||
}
|
||||
|
||||
export function omitParticipant(participants, excludedParticipantId) {
|
||||
const filtered = {};
|
||||
|
||||
for (const [participantId, participant] of Object.entries(participants || {})) {
|
||||
if (participantId !== excludedParticipantId) {
|
||||
filtered[participantId] = participant;
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
export function buildParticipantsSyncData(localUser, participants) {
|
||||
const memberList = {
|
||||
host: {
|
||||
id: localUser.id,
|
||||
name: localUser.name,
|
||||
avatar: localUser.avatar,
|
||||
mediaState: { ...localUser.mediaState },
|
||||
status: 'online',
|
||||
role: 'host'
|
||||
}
|
||||
};
|
||||
|
||||
for (const [participantId, participant] of Object.entries(participants || {})) {
|
||||
memberList[participantId] = {
|
||||
...createParticipantRecord(participant),
|
||||
role: 'participant'
|
||||
};
|
||||
}
|
||||
|
||||
return memberList;
|
||||
}
|
||||
@@ -8,6 +8,14 @@ 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';
|
||||
import {
|
||||
DEFAULT_PARTICIPANT_AVATAR,
|
||||
DEFAULT_PARTICIPANT_NAME,
|
||||
buildParticipantsSyncData,
|
||||
omitParticipant,
|
||||
removeParticipant,
|
||||
upsertParticipant
|
||||
} from './participants.js';
|
||||
|
||||
const AUDIO_CONFIG = {
|
||||
echoCancellation: true,
|
||||
@@ -574,16 +582,8 @@ class CallStateManager {
|
||||
// participant加入回调(host收到,新participant加入房间)
|
||||
this.renderstreaming.onParticipantJoined = (participantId) => {
|
||||
console.log(`Participant joined: ${participantId}`);
|
||||
if (!this.state.participants[participantId]) {
|
||||
this.state.participants[participantId] = {
|
||||
id: '',
|
||||
name: '参与者',
|
||||
avatar: '/images/p2.png',
|
||||
mediaState: { audio: false, video: true, isSpeaking: false },
|
||||
status: 'online'
|
||||
};
|
||||
}
|
||||
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||
this._upsertParticipant(participantId);
|
||||
this._notifyParticipantsUpdate();
|
||||
this.broadcastParticipantsList();
|
||||
};
|
||||
|
||||
@@ -603,256 +603,20 @@ class CallStateManager {
|
||||
this.state.remoteStream = null;
|
||||
}
|
||||
// 清理该 participant 的用户信息
|
||||
delete this.state.participants[participantId];
|
||||
this._removeParticipant(participantId);
|
||||
// 通知UI更新,用participantId作为connectionId传给renderer
|
||||
this.notify({ type: 'PARTICIPANT_LEFT', connectionId: participantId });
|
||||
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||
this._notifyParticipantsUpdate();
|
||||
this.broadcastParticipantsList();
|
||||
};
|
||||
|
||||
// 轨道事件回调
|
||||
this.renderstreaming.onTrackEvent = (data) => {
|
||||
const direction = data.transceiver.direction;
|
||||
if (direction == "sendrecv" || direction == "recvonly") {
|
||||
// 使用participantId区分不同participant的流
|
||||
const trackParticipantId = data.participantId || this.connectionId;
|
||||
const isHost = this.role === 'host';
|
||||
|
||||
let targetStream = null;
|
||||
if (isHost) {
|
||||
// Host端: 按 participantId 管理多路远端流
|
||||
if (!this.state.remoteStreams[trackParticipantId]) {
|
||||
this.state.remoteStreams[trackParticipantId] = new MediaStream();
|
||||
}
|
||||
targetStream = this.state.remoteStreams[trackParticipantId];
|
||||
} else {
|
||||
// Participant端: 使用单一远端流
|
||||
if (this.state.remoteStream == null) {
|
||||
this.state.remoteStream = new MediaStream();
|
||||
}
|
||||
targetStream = this.state.remoteStream;
|
||||
}
|
||||
|
||||
// 检查是否已经有相同类型的轨道
|
||||
const existingTracks = targetStream.getTracks().filter(track => track.kind === data.track.kind);
|
||||
existingTracks.forEach(track => {
|
||||
targetStream.removeTrack(track);
|
||||
console.log('Removed old track:', track.kind);
|
||||
});
|
||||
|
||||
targetStream.addTrack(data.track);
|
||||
console.log('Added new track:', data.track.kind, 'for participant:', trackParticipantId);
|
||||
|
||||
// Host端兜底:确保participants中有该participant条目
|
||||
if (isHost && !this.state.participants[trackParticipantId]) {
|
||||
this.state.participants[trackParticipantId] = {
|
||||
id: '',
|
||||
name: '参与者',
|
||||
avatar: '/images/p2.png',
|
||||
mediaState: { audio: false, video: true, isSpeaking: false },
|
||||
status: 'online'
|
||||
};
|
||||
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||
this.broadcastParticipantsList();
|
||||
}
|
||||
|
||||
// 通知UI远程流已更新
|
||||
// 关键优化:如果是音频轨道先到达且流中尚无视频轨道,
|
||||
// 延迟通知UI等待视频轨道到达,避免音频先触发的UI更新导致黑屏
|
||||
const notifyStreamUpdate = () => {
|
||||
this.notify({
|
||||
type: 'REMOTE_STREAM_OBTAINED',
|
||||
stream: targetStream,
|
||||
connectionId: trackParticipantId,
|
||||
isHost: isHost
|
||||
});
|
||||
console.log('Notified UI about remote stream update');
|
||||
};
|
||||
|
||||
if (data.track.kind === 'audio' && targetStream.getVideoTracks().length === 0) {
|
||||
// 音频先到,视频尚未到达:延迟200ms通知,给视频轨道到达的机会
|
||||
console.log('Audio track arrived first, delaying stream notification for video track...');
|
||||
setTimeout(() => {
|
||||
const nowHasVideo = targetStream.getVideoTracks().length > 0;
|
||||
console.log(`After delay, stream has video: ${nowHasVideo}`);
|
||||
notifyStreamUpdate();
|
||||
}, 200);
|
||||
} else {
|
||||
// 视频轨道到达,或音频视频同时存在:立即通知
|
||||
notifyStreamUpdate();
|
||||
}
|
||||
// 只有当收到远程流时才更新远程用户状态为在线
|
||||
if (this.state.session.remoteUser.status !== 'online') {
|
||||
this.updateRemoteUserStatus('online');
|
||||
// 更新远程用户网络质量为好
|
||||
this.updateRemoteUserNetworkQuality('good');
|
||||
|
||||
this.sendMessage('user-info', {
|
||||
id: this.state.session.localUser.id,
|
||||
name: this.state.session.localUser.name,
|
||||
avatar: this.state.session.localUser.avatar
|
||||
});
|
||||
// 启动通话时长计时器(避免重复启动)
|
||||
if (!this.durationInterval) {
|
||||
this.durationInterval = setInterval(() => {
|
||||
this.state.session.duration++;
|
||||
this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration });
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
// 如果是音频轨道,启动远程音频活动检测
|
||||
if (data.track.kind === 'audio') {
|
||||
this.startActivityDetection(this.state.remoteStream, { isLocal: false });
|
||||
}
|
||||
} else if (direction == "sendonly") {
|
||||
// 本地发送轨道,启动本地音频活动检测
|
||||
if (data.track.kind === 'audio') {
|
||||
this.startActivityDetection(this.state.localStream, { isLocal: true });
|
||||
}
|
||||
}
|
||||
this._handleTrackEvent(data);
|
||||
};
|
||||
|
||||
this.renderstreaming.onMessage = (data) => {
|
||||
console.log('收到消息:', data);
|
||||
if (data.type === 'chat-message') {
|
||||
const chatPayload = data.data || data.message;
|
||||
if (!chatPayload) {
|
||||
return;
|
||||
}
|
||||
|
||||
chatMessage.handleChatMessage(chatPayload);
|
||||
if (data.participantId && this.role === 'host' && this.state.participants[data.participantId]) {
|
||||
this.state.participants[data.participantId].id = chatPayload.senderId;
|
||||
if (chatPayload.senderName) {
|
||||
this.state.participants[data.participantId].name = chatPayload.senderName;
|
||||
}
|
||||
if (chatPayload.senderAvatar) {
|
||||
this.state.participants[data.participantId].avatar = chatPayload.senderAvatar;
|
||||
}
|
||||
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||
this.broadcastParticipantsList();
|
||||
}
|
||||
if (!this.role || this.role !== 'host') {
|
||||
if (data.participantId && this.state.participants[data.participantId]) {
|
||||
if (chatPayload.senderName) {
|
||||
this.state.participants[data.participantId].name = chatPayload.senderName;
|
||||
}
|
||||
if (chatPayload.senderAvatar) {
|
||||
this.state.participants[data.participantId].avatar = chatPayload.senderAvatar;
|
||||
}
|
||||
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||
} else if (chatPayload.senderId !== this.state.session.localUser.id) {
|
||||
this.state.session.remoteUser = {
|
||||
...this.state.session.remoteUser,
|
||||
id: chatPayload.senderId,
|
||||
name: chatPayload.senderName,
|
||||
avatar: chatPayload.senderAvatar
|
||||
};
|
||||
this.notify({ type: 'REMOTE_MEDIA_CHANGE', mediaState: this.state.session.remoteUser.mediaState });
|
||||
}
|
||||
}
|
||||
} else if (data.type === 'media-state-changed') {
|
||||
// 处理媒体状态变化
|
||||
console.log('收到媒体状态变化:', data.data, 'from participant:', data.participantId);
|
||||
if (this.role === 'host') {
|
||||
// Host端:按participantId同步更新participants中对应participant的mediaState
|
||||
if (data.participantId && this.state.participants[data.participantId]) {
|
||||
this.state.participants[data.participantId].mediaState = {
|
||||
...this.state.participants[data.participantId].mediaState,
|
||||
...data.data
|
||||
};
|
||||
}
|
||||
// 更新远端媒体状态
|
||||
this.updateRemoteMedia(data.data, data.participantId);
|
||||
// 通知UI更新participants
|
||||
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||
// Host端广播最新成员列表(含媒体状态)给所有Participant
|
||||
this.broadcastParticipantsList();
|
||||
} else {
|
||||
// Participant端:根据消息来源更新对应条目
|
||||
// Host的participantId在participants-sync中也会同步,所以不能仅靠participants中有无该key判断
|
||||
// 自身发出的消息回声(participantId === selfParticipantId)可以忽略
|
||||
// 来自其他Participant:participantId存在且在participants中,且不是自身
|
||||
// 来自Host:participantId存在但不是自身(Host不在selfParticipantId中)
|
||||
if (data.participantId && data.participantId !== this.selfParticipantId && this.state.participants[data.participantId]) {
|
||||
// 来自其他Participant的媒体状态变化:仅更新participants中对应条目
|
||||
// 不调用updateRemoteMedia,因为Participant端没有其他Participant的视频流
|
||||
this.state.participants[data.participantId].mediaState = {
|
||||
...this.state.participants[data.participantId].mediaState,
|
||||
...data.data
|
||||
};
|
||||
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||
} else if (data.participantId === this.selfParticipantId) {
|
||||
// 自身消息回声,忽略
|
||||
} else {
|
||||
// 来自Host的媒体状态变化(Host的participantId不匹配participants中任何条目,或无participantId):
|
||||
// 更新remoteUser(Host的视频流是本端远端画面)
|
||||
console.log('Received media-state-changed from Host, updating remoteUser:', data.data);
|
||||
this.updateRemoteMedia(data.data, data.participantId);
|
||||
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||
}
|
||||
}
|
||||
} else if (data.type === 'user-info') {
|
||||
// 处理用户信息更新
|
||||
console.log('收到用户信息:', data.data, 'from participant:', data.participantId);
|
||||
if (data.data) {
|
||||
if (data.participantId && this.role === 'host') {
|
||||
// Host端:按participantId存储到participants Map
|
||||
if (!this.state.participants[data.participantId]) {
|
||||
this.state.participants[data.participantId] = {
|
||||
id: '',
|
||||
name: '参与者',
|
||||
avatar: '/images/p2.png',
|
||||
mediaState: { audio: false, video: true, isSpeaking: false },
|
||||
status: 'online'
|
||||
};
|
||||
}
|
||||
this.state.participants[data.participantId].id = data.data.id || '';
|
||||
this.state.participants[data.participantId].name = data.data.name || '参与者';
|
||||
this.state.participants[data.participantId].avatar = data.data.avatar || '/images/p2.png';
|
||||
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||
this.broadcastParticipantsList();
|
||||
} else {
|
||||
// Participant端:更新单一remoteUser(Host的信息)
|
||||
this.state.session.remoteUser = {
|
||||
...this.state.session.remoteUser,
|
||||
id: data.data.id || this.state.session.remoteUser.id,
|
||||
name: data.data.name || this.state.session.remoteUser.name,
|
||||
avatar: data.data.avatar || this.state.session.remoteUser.avatar
|
||||
};
|
||||
this.notify({ type: 'REMOTE_MEDIA_CHANGE', mediaState: this.state.session.remoteUser.mediaState });
|
||||
}
|
||||
}
|
||||
} else if (data.type === 'participants-sync') {
|
||||
// Participant端:接收Host广播的完整成员列表
|
||||
if (this.role !== 'host' && data.data) {
|
||||
console.log('收到成员列表同步:', data.data);
|
||||
// 过滤掉自身条目,避免在列表中重复显示(自身已作为localUser显示)
|
||||
const filtered = {};
|
||||
for (const [pid, pInfo] of Object.entries(data.data)) {
|
||||
if (pid !== this.selfParticipantId) {
|
||||
filtered[pid] = pInfo;
|
||||
}
|
||||
}
|
||||
this.state.participants = filtered;
|
||||
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||
|
||||
// 同步通话时长:仅首次同步,将Host的时长作为基准
|
||||
if (!this.durationSynced && typeof data.callDuration === 'number') {
|
||||
this.state.session.duration = data.callDuration;
|
||||
this.durationSynced = true;
|
||||
// 如果计时器尚未启动(远程流还未到达),先启动计时器
|
||||
if (!this.durationInterval) {
|
||||
this.durationInterval = setInterval(() => {
|
||||
this.state.session.duration++;
|
||||
this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration });
|
||||
}, 1000);
|
||||
}
|
||||
this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration });
|
||||
console.log(`通话时长已同步,当前时长: ${data.callDuration}秒`);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._handleRenderStreamingMessage(data);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -924,6 +688,256 @@ class CallStateManager {
|
||||
this.notify({ type: 'CALL_ENDED', reason: isHost ? 'host_hangup' : 'participant_hangup' });
|
||||
}
|
||||
|
||||
_handleTrackEvent(data) {
|
||||
const direction = data.transceiver.direction;
|
||||
|
||||
if (direction === 'sendrecv' || direction === 'recvonly') {
|
||||
this._handleIncomingTrack(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (direction === 'sendonly' && data.track.kind === 'audio') {
|
||||
this.startActivityDetection(this.state.localStream, { isLocal: true });
|
||||
}
|
||||
}
|
||||
|
||||
_handleIncomingTrack(data) {
|
||||
const trackParticipantId = data.participantId || this.connectionId;
|
||||
const isHost = this.role === 'host';
|
||||
const targetStream = this._getOrCreateRemoteStream(trackParticipantId, isHost);
|
||||
|
||||
this._replaceTrackOfSameKind(targetStream, data.track);
|
||||
console.log('Added new track:', data.track.kind, 'for participant:', trackParticipantId);
|
||||
|
||||
if (isHost && !this.state.participants[trackParticipantId]) {
|
||||
this._upsertParticipant(trackParticipantId);
|
||||
this._notifyParticipantsUpdate();
|
||||
this.broadcastParticipantsList();
|
||||
}
|
||||
|
||||
this._notifyRemoteStreamUpdate(targetStream, trackParticipantId, isHost, data.track.kind);
|
||||
|
||||
if (this.state.session.remoteUser.status !== 'online') {
|
||||
this.updateRemoteUserStatus('online');
|
||||
this.updateRemoteUserNetworkQuality('good');
|
||||
this.sendMessage('user-info', {
|
||||
id: this.state.session.localUser.id,
|
||||
name: this.state.session.localUser.name,
|
||||
avatar: this.state.session.localUser.avatar
|
||||
});
|
||||
this._startDurationTimer();
|
||||
}
|
||||
|
||||
if (data.track.kind === 'audio') {
|
||||
this.startActivityDetection(this.state.remoteStream, { isLocal: false });
|
||||
}
|
||||
}
|
||||
|
||||
_getOrCreateRemoteStream(trackParticipantId, isHost) {
|
||||
if (isHost) {
|
||||
if (!this.state.remoteStreams[trackParticipantId]) {
|
||||
this.state.remoteStreams[trackParticipantId] = new MediaStream();
|
||||
}
|
||||
return this.state.remoteStreams[trackParticipantId];
|
||||
}
|
||||
|
||||
if (this.state.remoteStream == null) {
|
||||
this.state.remoteStream = new MediaStream();
|
||||
}
|
||||
|
||||
return this.state.remoteStream;
|
||||
}
|
||||
|
||||
_replaceTrackOfSameKind(targetStream, track) {
|
||||
const existingTracks = targetStream.getTracks().filter(existingTrack => existingTrack.kind === track.kind);
|
||||
existingTracks.forEach(existingTrack => {
|
||||
targetStream.removeTrack(existingTrack);
|
||||
console.log('Removed old track:', existingTrack.kind);
|
||||
});
|
||||
|
||||
targetStream.addTrack(track);
|
||||
}
|
||||
|
||||
_notifyRemoteStreamUpdate(targetStream, trackParticipantId, isHost, trackKind) {
|
||||
const notifyStreamUpdate = () => {
|
||||
this.notify({
|
||||
type: 'REMOTE_STREAM_OBTAINED',
|
||||
stream: targetStream,
|
||||
connectionId: trackParticipantId,
|
||||
isHost
|
||||
});
|
||||
console.log('Notified UI about remote stream update');
|
||||
};
|
||||
|
||||
if (trackKind === 'audio' && targetStream.getVideoTracks().length === 0) {
|
||||
console.log('Audio track arrived first, delaying stream notification for video track...');
|
||||
setTimeout(() => {
|
||||
const nowHasVideo = targetStream.getVideoTracks().length > 0;
|
||||
console.log(`After delay, stream has video: ${nowHasVideo}`);
|
||||
notifyStreamUpdate();
|
||||
}, 200);
|
||||
return;
|
||||
}
|
||||
|
||||
notifyStreamUpdate();
|
||||
}
|
||||
|
||||
_handleRenderStreamingMessage(data) {
|
||||
console.log('收到消息:', data);
|
||||
|
||||
switch (data.type) {
|
||||
case 'chat-message':
|
||||
this._handleChatMessage(data);
|
||||
break;
|
||||
case 'media-state-changed':
|
||||
this._handleMediaStateChangedMessage(data);
|
||||
break;
|
||||
case 'user-info':
|
||||
this._handleUserInfoMessage(data);
|
||||
break;
|
||||
case 'participants-sync':
|
||||
this._handleParticipantsSyncMessage(data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_handleChatMessage(data) {
|
||||
const chatPayload = data.data || data.message;
|
||||
if (!chatPayload) {
|
||||
return;
|
||||
}
|
||||
|
||||
chatMessage.handleChatMessage(chatPayload);
|
||||
|
||||
if (data.participantId && this.role === 'host' && this.state.participants[data.participantId]) {
|
||||
this._upsertParticipant(data.participantId, {
|
||||
id: chatPayload.senderId,
|
||||
...(chatPayload.senderName ? { name: chatPayload.senderName } : {}),
|
||||
...(chatPayload.senderAvatar ? { avatar: chatPayload.senderAvatar } : {})
|
||||
});
|
||||
this._notifyParticipantsUpdate();
|
||||
this.broadcastParticipantsList();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.role || this.role !== 'host') {
|
||||
if (data.participantId && this.state.participants[data.participantId]) {
|
||||
this._upsertParticipant(data.participantId, {
|
||||
...(chatPayload.senderName ? { name: chatPayload.senderName } : {}),
|
||||
...(chatPayload.senderAvatar ? { avatar: chatPayload.senderAvatar } : {})
|
||||
});
|
||||
this._notifyParticipantsUpdate();
|
||||
} else if (chatPayload.senderId !== this.state.session.localUser.id) {
|
||||
this._updateRemoteUserProfile({
|
||||
id: chatPayload.senderId,
|
||||
name: chatPayload.senderName,
|
||||
avatar: chatPayload.senderAvatar
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleMediaStateChangedMessage(data) {
|
||||
console.log('收到媒体状态变化:', data.data, 'from participant:', data.participantId);
|
||||
|
||||
if (this.role === 'host') {
|
||||
if (data.participantId && this.state.participants[data.participantId]) {
|
||||
this._upsertParticipant(data.participantId, {
|
||||
mediaState: data.data
|
||||
});
|
||||
}
|
||||
|
||||
this.updateRemoteMedia(data.data, data.participantId);
|
||||
this._notifyParticipantsUpdate();
|
||||
this.broadcastParticipantsList();
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.participantId && data.participantId !== this.selfParticipantId && this.state.participants[data.participantId]) {
|
||||
this._upsertParticipant(data.participantId, {
|
||||
mediaState: data.data
|
||||
});
|
||||
this._notifyParticipantsUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.participantId === this.selfParticipantId) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Received media-state-changed from Host, updating remoteUser:', data.data);
|
||||
this.updateRemoteMedia(data.data, data.participantId);
|
||||
this._notifyParticipantsUpdate();
|
||||
}
|
||||
|
||||
_handleUserInfoMessage(data) {
|
||||
console.log('收到用户信息:', data.data, 'from participant:', data.participantId);
|
||||
if (!data.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.participantId && this.role === 'host') {
|
||||
this._upsertParticipant(data.participantId, {
|
||||
id: data.data.id || '',
|
||||
name: data.data.name || DEFAULT_PARTICIPANT_NAME,
|
||||
avatar: data.data.avatar || DEFAULT_PARTICIPANT_AVATAR
|
||||
});
|
||||
this._notifyParticipantsUpdate();
|
||||
this.broadcastParticipantsList();
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateRemoteUserProfile({
|
||||
id: data.data.id || this.state.session.remoteUser.id,
|
||||
name: data.data.name || this.state.session.remoteUser.name,
|
||||
avatar: data.data.avatar || this.state.session.remoteUser.avatar
|
||||
});
|
||||
}
|
||||
|
||||
_handleParticipantsSyncMessage(data) {
|
||||
if (this.role === 'host' || !data.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('收到成员列表同步:', data.data);
|
||||
this.state.participants = omitParticipant(data.data, this.selfParticipantId);
|
||||
this._notifyParticipantsUpdate();
|
||||
this._syncCallDuration(data.callDuration);
|
||||
}
|
||||
|
||||
_updateRemoteUserProfile(profile) {
|
||||
this.state.session.remoteUser = {
|
||||
...this.state.session.remoteUser,
|
||||
...profile
|
||||
};
|
||||
this.notify({ type: 'REMOTE_MEDIA_CHANGE', mediaState: this.state.session.remoteUser.mediaState });
|
||||
}
|
||||
|
||||
_syncCallDuration(callDuration) {
|
||||
if (this.durationSynced || typeof callDuration !== 'number') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.session.duration = callDuration;
|
||||
this.durationSynced = true;
|
||||
this._startDurationTimer();
|
||||
this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration });
|
||||
console.log(`通话时长已同步,当前时长: ${callDuration}秒`);
|
||||
}
|
||||
|
||||
_startDurationTimer() {
|
||||
if (this.durationInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.durationInterval = setInterval(() => {
|
||||
this.state.session.duration++;
|
||||
this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration });
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* @param {string} type - 消息类型
|
||||
@@ -938,6 +952,21 @@ class CallStateManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Participant state helpers
|
||||
*/
|
||||
_notifyParticipantsUpdate() {
|
||||
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||
}
|
||||
|
||||
_upsertParticipant(participantId, patch = {}) {
|
||||
return upsertParticipant(this.state.participants, participantId, patch);
|
||||
}
|
||||
|
||||
_removeParticipant(participantId) {
|
||||
return removeParticipant(this.state.participants, participantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Host端广播完整成员列表给所有Participant
|
||||
* 包含Host自身信息 + 所有Participant信息
|
||||
@@ -946,25 +975,7 @@ class CallStateManager {
|
||||
broadcastParticipantsList() {
|
||||
if (this.role !== 'host' || !this.renderstreaming) return;
|
||||
|
||||
const memberList = {};
|
||||
|
||||
// 添加Host自身信息
|
||||
memberList['host'] = {
|
||||
id: this.state.session.localUser.id,
|
||||
name: this.state.session.localUser.name,
|
||||
avatar: this.state.session.localUser.avatar,
|
||||
mediaState: { ...this.state.session.localUser.mediaState },
|
||||
status: 'online',
|
||||
role: 'host'
|
||||
};
|
||||
|
||||
// 添加所有Participant信息
|
||||
for (const [pid, pInfo] of Object.entries(this.state.participants)) {
|
||||
memberList[pid] = {
|
||||
...pInfo,
|
||||
role: 'participant'
|
||||
};
|
||||
}
|
||||
const memberList = buildParticipantsSyncData(this.state.session.localUser, this.state.participants);
|
||||
|
||||
this.renderstreaming.sendMessage({
|
||||
type: 'participants-sync',
|
||||
|
||||
Reference in New Issue
Block a user