fix: Web端投屏画面缩小闪烁问题
- canvas尺寸锁定策略:首次有效帧锁定canvas尺寸,后续帧统一drawImage缩放绘制 - 消除不同采集模式(MediaProjection/无障碍截图)帧尺寸不一致导致的canvas反复resize - 设备切换或断开时重置锁定尺寸,下次连接重新锁定 - 移除renderLatestFrame对screenDisplay.fitMode的无效依赖
This commit is contained in:
@@ -38,10 +38,9 @@ const DeviceScreen: React.FC<DeviceScreenProps> = ({ deviceId, onScreenSizeChang
|
||||
const isRenderingRef = useRef(false)
|
||||
const imageSizeRef = useRef<{ width: number, height: number } | null>(null)
|
||||
|
||||
// ✅ 尺寸稳定性:防止偶发异常帧导致canvas闪烁
|
||||
const pendingSizeRef = useRef<{ width: number, height: number } | null>(null)
|
||||
const pendingSizeCountRef = useRef(0)
|
||||
const SIZE_STABLE_THRESHOLD = 3 // 连续3帧相同尺寸才更新
|
||||
// canvas锁定尺寸:首次有效帧确定canvas尺寸后不再变化,
|
||||
// 避免不同采集模式(MediaProjection/无障碍截图)帧尺寸不一致导致闪烁
|
||||
const lockedCanvasSizeRef = useRef<{ width: number, height: number } | null>(null)
|
||||
|
||||
// ✅ 添加控制权状态跟踪,避免重复申请
|
||||
const [isControlRequested, setIsControlRequested] = useState(false)
|
||||
@@ -172,6 +171,8 @@ const DeviceScreen: React.FC<DeviceScreenProps> = ({ deviceId, onScreenSizeChang
|
||||
rafIdRef.current = 0
|
||||
}
|
||||
isRenderingRef.current = false
|
||||
// 设备切换或断开时重置锁定尺寸,下次连接重新锁定
|
||||
lockedCanvasSizeRef.current = null
|
||||
}
|
||||
}, [webSocket, deviceId])
|
||||
|
||||
@@ -303,52 +304,33 @@ const DeviceScreen: React.FC<DeviceScreenProps> = ({ deviceId, onScreenSizeChang
|
||||
return
|
||||
}
|
||||
|
||||
// canvas尺寸匹配bitmap,只在尺寸真正变化时才设置
|
||||
// 注意:设置canvas.width/height会清空画布内容,所以必须紧接着绘制新帧
|
||||
const needsResize = canvas.width !== bitmap.width || canvas.height !== bitmap.height
|
||||
if (needsResize) {
|
||||
// canvas尺寸锁定策略:
|
||||
// 首次有效帧确定canvas尺寸后锁定,后续帧直接绘制到固定尺寸canvas上
|
||||
// 避免不同采集模式帧尺寸不一致导致canvas反复resize闪烁
|
||||
const locked = lockedCanvasSizeRef.current
|
||||
if (!locked) {
|
||||
// 首次帧:锁定canvas尺寸
|
||||
lockedCanvasSizeRef.current = { width: bitmap.width, height: bitmap.height }
|
||||
canvas.width = bitmap.width
|
||||
canvas.height = bitmap.height
|
||||
} else if (canvas.width !== locked.width || canvas.height !== locked.height) {
|
||||
// canvas被外部重置了,恢复锁定尺寸
|
||||
canvas.width = locked.width
|
||||
canvas.height = locked.height
|
||||
}
|
||||
|
||||
switch (screenDisplay.fitMode) {
|
||||
case 'fit':
|
||||
drawFitMode(ctx, bitmap, canvas)
|
||||
break
|
||||
case 'fill':
|
||||
drawFillMode(ctx, bitmap, canvas)
|
||||
break
|
||||
case 'stretch':
|
||||
drawStretchMode(ctx, bitmap, canvas)
|
||||
break
|
||||
case 'original':
|
||||
drawOriginalMode(ctx, bitmap, canvas)
|
||||
break
|
||||
}
|
||||
// 始终将bitmap绘制到整个canvas区域,浏览器自动缩放
|
||||
// 这样不同尺寸的帧都能正确显示,不会闪烁
|
||||
ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height)
|
||||
|
||||
const bw = bitmap.width
|
||||
const bh = bitmap.height
|
||||
// 使用锁定的canvas尺寸上报,保持稳定
|
||||
const reportSize = lockedCanvasSizeRef.current
|
||||
if (reportSize) {
|
||||
const prevSize = imageSizeRef.current
|
||||
if (!prevSize || prevSize.width !== bw || prevSize.height !== bh) {
|
||||
// 尺寸稳定性检查:只有连续多帧相同尺寸才更新,防止偶发异常帧闪烁
|
||||
const pending = pendingSizeRef.current
|
||||
if (pending && pending.width === bw && pending.height === bh) {
|
||||
pendingSizeCountRef.current++
|
||||
} else {
|
||||
pendingSizeRef.current = { width: bw, height: bh }
|
||||
pendingSizeCountRef.current = 1
|
||||
if (!prevSize || prevSize.width !== reportSize.width || prevSize.height !== reportSize.height) {
|
||||
imageSizeRef.current = { width: reportSize.width, height: reportSize.height }
|
||||
setImageSize({ width: reportSize.width, height: reportSize.height })
|
||||
}
|
||||
|
||||
if (pendingSizeCountRef.current >= SIZE_STABLE_THRESHOLD || !prevSize) {
|
||||
imageSizeRef.current = { width: bw, height: bh }
|
||||
setImageSize({ width: bw, height: bh })
|
||||
pendingSizeRef.current = null
|
||||
pendingSizeCountRef.current = 0
|
||||
}
|
||||
} else {
|
||||
// 尺寸未变,重置pending
|
||||
pendingSizeRef.current = null
|
||||
pendingSizeCountRef.current = 0
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
@@ -373,7 +355,7 @@ const DeviceScreen: React.FC<DeviceScreenProps> = ({ deviceId, onScreenSizeChang
|
||||
}
|
||||
|
||||
doRender()
|
||||
}, [screenDisplay.fitMode])
|
||||
}, [])
|
||||
|
||||
const drawFitMode = (ctx: CanvasRenderingContext2D, img: HTMLImageElement | ImageBitmap, canvas: HTMLCanvasElement) => {
|
||||
const scale = Math.min(canvas.width / img.width, canvas.height / img.height)
|
||||
|
||||
Reference in New Issue
Block a user