Compare commits
2 Commits
87c4a56306
...
d7264b9102
| Author | SHA1 | Date | |
|---|---|---|---|
| d7264b9102 | |||
| 53166b648f |
@@ -169,7 +169,7 @@
|
|||||||
|
|
||||||
<!-- 远端未连接时的占位背景 -->
|
<!-- 远端未连接时的占位背景 -->
|
||||||
<div id="remoteVideoPlaceholder"
|
<div id="remoteVideoPlaceholder"
|
||||||
class="absolute inset-0 flex items-center justify-center bg-gradient-to-br from-indigo-900/80 to-purple-900/80">
|
class="absolute inset-0 flex items-center justify-center bg-gradient-to-br from-indigo-900 to-purple-900 z-10">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div
|
<div
|
||||||
class="w-32 h-32 rounded-full bg-indigo-700/50 flex items-center justify-center mx-auto mb-4">
|
class="w-32 h-32 rounded-full bg-indigo-700/50 flex items-center justify-center mx-auto mb-4">
|
||||||
|
|||||||
@@ -290,6 +290,15 @@ class UIRenderer {
|
|||||||
const shouldShowPlaceholder = !remoteUser.mediaState.video;
|
const shouldShowPlaceholder = !remoteUser.mediaState.video;
|
||||||
toggleElement(this.elements.remoteVideoPlaceholder, shouldShowPlaceholder);
|
toggleElement(this.elements.remoteVideoPlaceholder, shouldShowPlaceholder);
|
||||||
|
|
||||||
|
// 当远程视频关闭时,隐藏视频元素本身,避免冻结画面透过占位符
|
||||||
|
if (this.elements.remoteVideo) {
|
||||||
|
if (shouldShowPlaceholder) {
|
||||||
|
this.elements.remoteVideo.style.opacity = '0';
|
||||||
|
} else {
|
||||||
|
this.elements.remoteVideo.style.opacity = '1';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 更新占位符文本内容
|
// 更新占位符文本内容
|
||||||
if (shouldShowPlaceholder) {
|
if (shouldShowPlaceholder) {
|
||||||
const placeholderContent = this.elements.remoteVideoPlaceholder.querySelector('.text-center');
|
const placeholderContent = this.elements.remoteVideoPlaceholder.querySelector('.text-center');
|
||||||
|
|||||||
@@ -653,7 +653,11 @@ class CallStateManager {
|
|||||||
this.broadcastParticipantsList();
|
this.broadcastParticipantsList();
|
||||||
} else {
|
} else {
|
||||||
// Participant端:根据消息来源更新对应条目
|
// Participant端:根据消息来源更新对应条目
|
||||||
if (data.participantId && this.state.participants[data.participantId]) {
|
// 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中对应条目
|
// 来自其他Participant的媒体状态变化:仅更新participants中对应条目
|
||||||
// 不调用updateRemoteMedia,因为Participant端没有其他Participant的视频流
|
// 不调用updateRemoteMedia,因为Participant端没有其他Participant的视频流
|
||||||
this.state.participants[data.participantId].mediaState = {
|
this.state.participants[data.participantId].mediaState = {
|
||||||
@@ -661,15 +665,12 @@ class CallStateManager {
|
|||||||
...data.data
|
...data.data
|
||||||
};
|
};
|
||||||
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||||
} else if (!data.participantId) {
|
} else if (data.participantId === this.selfParticipantId) {
|
||||||
// 来自Host的媒体状态变化(无participantId):
|
// 自身消息回声,忽略
|
||||||
// 更新participants中Host条目 + 更新remoteUser(Host的视频流是本端远端画面)
|
} else {
|
||||||
if (this.state.participants['host']) {
|
// 来自Host的媒体状态变化(Host的participantId不匹配participants中任何条目,或无participantId):
|
||||||
this.state.participants['host'].mediaState = {
|
// 更新remoteUser(Host的视频流是本端远端画面)
|
||||||
...this.state.participants['host'].mediaState,
|
console.log('Received media-state-changed from Host, updating remoteUser:', data.data);
|
||||||
...data.data
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this.updateRemoteMedia(data.data, data.participantId);
|
this.updateRemoteMedia(data.data, data.participantId);
|
||||||
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,10 +75,11 @@ export class RenderStreaming {
|
|||||||
|
|
||||||
if (this._isHost) {
|
if (this._isHost) {
|
||||||
// host端:为该participant创建或复用peer
|
// host端:为该participant创建或复用peer
|
||||||
|
// host端始终使用polite=false(impolite),确保perfect negotiation中host的offer优先
|
||||||
let peer = this._peers.get(participantId);
|
let peer = this._peers.get(participantId);
|
||||||
if (!peer || (peer.pc && peer.pc.iceConnectionState === 'disconnected')) {
|
if (!peer || (peer.pc && peer.pc.iceConnectionState === 'disconnected')) {
|
||||||
if (peer) peer.close();
|
if (peer) peer.close();
|
||||||
peer = this._preparePeerConnection(this._connectionId, offer.polite, participantId);
|
peer = this._preparePeerConnection(this._connectionId, false, participantId);
|
||||||
}
|
}
|
||||||
const desc = new RTCSessionDescription({ sdp: offer.sdp, type: "offer" });
|
const desc = new RTCSessionDescription({ sdp: offer.sdp, type: "offer" });
|
||||||
try {
|
try {
|
||||||
@@ -87,13 +88,13 @@ export class RenderStreaming {
|
|||||||
Logger.warn(`Error on GotDescription for participant ${participantId}: ${error}`);
|
Logger.warn(`Error on GotDescription for participant ${participantId}: ${error}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// participant端:使用单一peer
|
// participant端:使用单一peer,始终使用polite=true
|
||||||
if (this._peer && this._peer.pc && this._peer.pc.iceConnectionState === 'disconnected') {
|
if (this._peer && this._peer.pc && this._peer.pc.iceConnectionState === 'disconnected') {
|
||||||
this._peer.close();
|
this._peer.close();
|
||||||
this._peer = null;
|
this._peer = null;
|
||||||
}
|
}
|
||||||
if (!this._peer) {
|
if (!this._peer) {
|
||||||
this._preparePeerConnection(offer.connectionId, offer.polite, null);
|
this._preparePeerConnection(offer.connectionId, true, null);
|
||||||
}
|
}
|
||||||
const desc = new RTCSessionDescription({ sdp: offer.sdp, type: "offer" });
|
const desc = new RTCSessionDescription({ sdp: offer.sdp, type: "offer" });
|
||||||
try {
|
try {
|
||||||
@@ -171,10 +172,9 @@ export class RenderStreaming {
|
|||||||
const participantId = data.participantId;
|
const participantId = data.participantId;
|
||||||
Logger.log(`Participant joined: ${participantId}`);
|
Logger.log(`Participant joined: ${participantId}`);
|
||||||
|
|
||||||
// host端:为新participant创建peer
|
// host端:不在此处创建peer,等待participant的offer到达后在_onOffer中创建
|
||||||
if (this._isHost && !this._peers.has(participantId)) {
|
// 这样避免host和participant同时发offer导致的glare冲突
|
||||||
this._preparePeerConnection(this._connectionId, false, participantId);
|
// _onOffer会在收到participant的offer时自动创建peer(如果不存在)
|
||||||
}
|
|
||||||
|
|
||||||
this.onParticipantJoined(participantId);
|
this.onParticipantJoined(participantId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -239,7 +239,8 @@ function onOffer(ws: WebSocket, message: any): void {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// participant发送offer给host,携带该participant的participantId
|
// participant发送offer给host,携带该participant的participantId
|
||||||
newOffer.polite = true;
|
// host端应为impolite(polite=false),确保perfect negotiation中host优先
|
||||||
|
newOffer.polite = false;
|
||||||
group.host.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer, participantId: senderParticipantId }));
|
group.host.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer, participantId: senderParticipantId }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user