From 9eb9df649d2021a77c07208b077a5f604b4d9409 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 6 Sep 2026 15:55:48 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A7=86=E9=A2=91=E9=80=9A?= =?UTF-8?q?=E8=AF=9D=E6=8C=AF=E9=93=83=E9=98=B6=E6=AE=B5=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E5=B0=8F=E7=AA=97=E9=A2=84=E8=A7=88=E6=8F=90=E5=89=8D=E5=8F=AF?= =?UTF-8?q?=E8=A7=81=E4=B8=94=E5=8F=AF=E6=8B=96=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本地小窗预览(.call-party.self)此前只由 audioOnly/minimized 控制显隐, 没有校验连接是否真正建立(this.state.remoteStream),导致视频通话振铃 阶段(对方尚未接听、只有本地摄像头画面)小窗就已经以 display:block 可见, 且 dragStart 里对应的分支同样没有这层校验,振铃阶段就能拖动这个本应还 不存在的预览框。 render() 里 .call-party.self 的 disabled 属性补上 !connected 判断, dragStart 里进入 'self' 拖动分支的条件补上 this.state.remoteStream 校验, 两处保持一致:只有远端媒体真正到达(连接建立)后小窗才出现并可拖动。 验证:用 canvas.captureStream()/AudioContext 构造的合法 MediaStreamTrack 配合 monkey-patch getUserMedia,绕开已知后端通话邀请 400 缺陷直接驱动 CallPanel 组件状态。振铃阶段(waitingForPeer=true, remoteStream 未设置) 下 .call-party.self 计算样式 display:none、无 [disabled] 属性对应的 cursor:grab;调用 handleTrackEvent 模拟远端媒体到达后,display:block、 cursor:grab,且用真实鼠标拖拽(非合成事件)验证了拖动/边界收紧都正常。 --- src/widgets/call-panel.jsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/widgets/call-panel.jsx b/src/widgets/call-panel.jsx index 83717ba2..a8788adc 100644 --- a/src/widgets/call-panel.jsx +++ b/src/widgets/call-panel.jsx @@ -163,11 +163,15 @@ class CallPanel extends React.PureComponent { // Fullscreen connected video call: let the user drag their own small self-view preview // out of the way instead of it being stuck wherever it defaults to (spec 8.9 "双方视频 // 通话中": "小窗应支持拖动且不能遮挡关键按钮"). Only relevant when the box actually exists - // (base.css hides it entirely for audio-only calls / while minimized via + // (base.css hides it entirely for audio-only calls / while minimized, and now also while + // still ringing -- see the render()'s `disabled` prop just below -- via // .call-party.self[disabled]) and only when the drag actually starts on that box, not on - // the peer's video/avatar card behind it. - if (!this.props.minimized && !this.state.audioOnly && this.selfBoxRef.current && - event.target.closest('.call-party.self')) { + // the peer's video/avatar card behind it. The `this.state.remoteStream` check mirrors + // render()'s own `connected` gate: without it, a pointerdown during the pre-connect ringing + // phase (box still hidden by CSS, but not yet [disabled] before this fix) would already set + // dragTarget='self' and start moving an invisible box. + if (!this.props.minimized && !this.state.audioOnly && this.state.remoteStream && + this.selfBoxRef.current && event.target.closest('.call-party.self')) { event.stopPropagation(); const el = this.selfBoxRef.current; // Pointer events are captured on the outer container (same element the listeners below @@ -956,7 +960,7 @@ class CallPanel extends React.PureComponent {
{formatElapsedTime(this.state.elapsedSeconds)}
}
+ disabled={this.state.audioOnly || this.props.minimized || !connected}>