修复视频通话振铃阶段本地小窗预览提前可见且可拖动

本地小窗预览(.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,且用真实鼠标拖拽(非合成事件)验证了拖动/边界收紧都正常。
This commit is contained in:
Your Name
2026-09-06 15:55:48 +08:00
parent dbf47847fe
commit 9eb9df649d
+9 -5
View File
@@ -163,11 +163,15 @@ class CallPanel extends React.PureComponent {
// Fullscreen connected video call: let the user drag their own small self-view preview // 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 "双方视频 // out of the way instead of it being stuck wherever it defaults to (spec 8.9 "双方视频
// 通话中": "小窗应支持拖动且不能遮挡关键按钮"). Only relevant when the box actually exists // 通话中": "小窗应支持拖动且不能遮挡关键按钮"). 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 // .call-party.self[disabled]) and only when the drag actually starts on that box, not on
// the peer's video/avatar card behind it. // the peer's video/avatar card behind it. The `this.state.remoteStream` check mirrors
if (!this.props.minimized && !this.state.audioOnly && this.selfBoxRef.current && // render()'s own `connected` gate: without it, a pointerdown during the pre-connect ringing
event.target.closest('.call-party.self')) { // 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(); event.stopPropagation();
const el = this.selfBoxRef.current; const el = this.selfBoxRef.current;
// Pointer events are captured on the outer container (same element the listeners below // Pointer events are captured on the outer container (same element the listeners below
@@ -956,7 +960,7 @@ class CallPanel extends React.PureComponent {
<div className="call-duration">{formatElapsedTime(this.state.elapsedSeconds)}</div>} <div className="call-duration">{formatElapsedTime(this.state.elapsedSeconds)}</div>}
<div id="video-container-panel" className="draggable"> <div id="video-container-panel" className="draggable">
<div className="call-party self" ref={this.selfBoxRef} <div className="call-party self" ref={this.selfBoxRef}
disabled={this.state.audioOnly || this.props.minimized}> disabled={this.state.audioOnly || this.props.minimized || !connected}>
<video ref={this.localRef} autoPlay muted playsInline /> <video ref={this.localRef} autoPlay muted playsInline />
<div className="caller-name inactive"> <div className="caller-name inactive">
<FormattedMessage id="calls_you_label" <FormattedMessage id="calls_you_label"