先记录两端 OS 版本、CPU 架构、应用版本/提交 SHA、连接方式、IP/端口、分辨率/FPS/码率和完整复现步骤。日志对外发送前删除私网 IP、设备名、UUID、SDP/ICE 和屏幕内容。
CI 构建没有配置 Developer ID 正式签名和 Apple 公证。确认文件来自可信工作流和正确提交后,优先使用 Finder 右键应用 →“打开”,或在“系统设置 → 隐私与安全性”中使用针对该应用的单次“仍要打开”。
不要长期关闭 Gatekeeper,也不要对来源不明的应用清除隔离属性。正式用户分发前必须完成签名和公证。
CI portable EXE 没有 Authenticode 签名。只在核对可信 Actions 运行和文件哈希后用于内部验收。正式发布前应签名,不能把关闭 SmartScreen 作为安装步骤。
在仓库根目录确认 Node/npm 版本并做干净锁定安装:
node --version
npm --version
npm ci要求 Node 22+、npm 10+。不要复制其他平台的 node_modules。
主端没有独立创建按钮。请先选择子端并点击“扩展到 …”;应用收到 welcome 的真实设备 UUID 后,才会在默认“创建扩展屏”模式启动 helper。若这一步提示找不到 helper:
npm run build:native
ls -l native/macos/.build/lanextend-vdisplay构建需要 Xcode Command Line Tools、macOS SDK 和 Clang:
xcode-select -p
xcrun --sdk macosx --show-sdk-path
xcrun --sdk macosx --find clangnative/macos/.build/lanextend-vdisplay --probeavailable: true:只代表类和 selector 在当前进程可见,继续做创建测试;available: false/api_unavailable:当前 macOS 与 helper 不兼容,不要反复重试或关闭系统保护;记录 OS build 和missing字段,提交兼容性问题。
项目支持基线是 macOS 14+,但每个系统版本仍需真机回归。App Store 版本不是可行分发路径。
虚拟显示对象由 helper 进程持有;helper 退出、崩溃或被系统终止时,显示器会消失。检查启动终端的 stderr/JSONL 输出和系统崩溃报告。确认没有脚本清理进程,也没有重复启动另一个主端实例。
GUI 接受偶数逻辑宽 800–7680、偶数逻辑高 600–4320、15–60 FPS。HiDPI 会创建宽高各 2× 的物理 framebuffer,因此逻辑宽/高各自不能超过 3840。先恢复 1920×1080、30 FPS、关闭 HiDPI,再逐项增加。
- 确认已经选中子端、保持“创建扩展屏”并点击“扩展到 …”;收到 welcome 前不会创建显示器。
- 打开“系统设置 → 显示器”,确认系统确实列出虚拟显示器。默认模式会自动按 display ID/名称匹配,不需要手选。
- 检查屏幕录制权限;修改后完全退出并重开应用。
- 点击“断开扩展屏”清理后重新连接,让应用重新创建并轮询捕获源。
- 如果 GUI 已切到“已有显示器”兼容模式,刷新列表并手选来源;不依赖上次保存的 source ID,系统重启或显示器重建后 ID 可能改变。
如果系统显示器列表也没有该显示器,回到 helper 排障,而不是继续排查 WebRTC。
按顺序检查:
- 子端 GUI 是否显示正在监听,端口是否合法。
ipconfig中 Windows 和 Mac 是否在预期 IPv4 子网。- Windows 网络配置文件是否为“专用”,防火墙是否允许子端程序的 UDP 广播。
- Wi‑Fi 是否启用 AP/client isolation;企业无线网和访客网通常会阻止客户端互见。
- 两端是否经过不同 VLAN、VPN 或虚拟网卡;广播默认不能跨三层路由。
- 暂停 VPN/虚拟网卡后重试,避免广播从错误接口发出。
- 若 IP 可路由但广播受限,使用手动私有 IPv4 + 端口。
在线设备约 6 秒收不到广播会变离线;这不是立即断开会话的判据。
MVP 只接受:10/8、172.16/12、192.168/16、127/8、169.254/16 IPv4 和 1–65535 端口。
不支持:
- IPv6;
hostname.local或 DNS 名称;- 公网 IPv4;
- 带
http://、ws://或路径的完整 URL。
应只填写例如 192.168.1.25 和 47772。
子端已有活动 WebSocket 会话。到当前主端正常断开,或在 Windows 子端确认会话后断开。不要通过杀进程绕过会话归属,除非应用已无响应。
双端协议版本或消息实现不一致,或者同网有非 LanExtend 客户端连接。确认两端来自同一提交/版本,检查 protocol: 1,不要把完整 SDP/ICE 发布到公开 issue。
- 子端未启动或端口已修改;
- Windows 防火墙阻止 TCP;
- 连接了错误 IP/网卡;
- 信令服务启动失败。
Windows 可检查监听端口:
Get-NetTCPConnection -State Listen | Where-Object LocalPort -eq 47772端口被另一个进程占用,或启动了两个子端实例。关闭冲突程序,或在子端设置一个未占用的高位端口后让服务重启。同步更新主端记忆/手动端口和防火墙规则。
- Mac 屏幕录制权限是否为已授权;修改后重开应用。
- 默认“创建扩展屏”是否成功自动匹配新 display ID;若处于“已有显示器”兼容模式,确认手选的是仍存在的来源而不是旧 source ID。
- 把一个颜色明显、无敏感内容的窗口拖到虚拟显示器,确认不是空桌面。
- 断开后用 1920×1080、30 FPS、8 Mbps、关闭 HiDPI 重建。
- 检查两端 WebRTC 状态是否到
connected;若停在connecting,重点排查防火墙/ICE。 - Windows 更新显卡驱动并重启;Electron/Chromium 会自行选择编解码路径,但 MVP 没有硬件解码保证。
如果主端预览/缩略图也黑,问题在权限/捕获;如果主端可见而子端黑,问题更可能在 WebRTC、解码或视频元素渲染。
MVP 没有 STUN/TURN,只面向可直接互通的局域网。检查:
- 两端主机防火墙是否阻止 WebRTC 动态 UDP;
- VPN、企业 EDR 或安全软件是否过滤 ICE/DTLS;
- 是否处于仅能访问网关、不能客户端互访的无线网络;
- 双端系统时间是否严重异常(证书/DTLS 行为可能受影响);
- 两端是否为相同协议版本。
成功建立 WebSocket 不代表 WebRTC 媒体一定可达。
一次只调整一个变量:
- 降为 1920×1080、30 FPS、8 Mbps、关闭 HiDPI。
- 改用有线网络,或靠近 5/6 GHz AP;避免 2.4 GHz 拥塞。
- 关闭 Mac/Windows 上的高负载编码、游戏、虚拟机和屏幕录制软件。
- 若卡顿但带宽充足,降低 FPS;若稳定但块状模糊,逐步提高码率。
- 若 CPU/GPU 饱和,降低分辨率;HiDPI 可能让物理像素量增加四倍。
- 连续测试至少 10 分钟,记录任务管理器/活动监视器和网络丢包,不使用单次主观感受做结论。
码率是发送目标,实际 WebRTC 会受带宽估计和编码器约束影响;GUI 数值不保证线上恒定码率。
- 使用子端顶部按钮或画面悬浮按钮退出/进入全屏;Esc 也会退出全屏。
- 多显示器 Windows 上,先把子端窗口移到目标物理屏,再进入全屏。
- 若应用在断开后仍阻止显示休眠,正常退出子端并记录复现;会话关闭处理应停止 blocker。
- 若窗口在睡眠/唤醒后黑屏,断开并重新建立 WebRTC;MVP 尚未声明完整的睡眠恢复保证。
重新发现相同设备 UUID 后,在线记录应覆盖记忆中的旧 IP。若广播不可用,忘记旧设备并用新私有 IP 手动添加。
克隆 Windows 用户数据目录可能复制子端 UUID,导致主端把两台机器合并。退出被克隆的子端,备份并重命名其 settings.json,让它生成新 UUID,再重新命名和连接。
完全退出应用,备份后重命名 Electron userData 目录中的 settings.json。不要直接删除整个 userData 目录,里面可能有 Chromium/Electron 其他状态。
npm ci失败:确认package.json与package-lock.json同步、registry 可用。- Mac 原生构建失败:查看
xcrun/SDK/Clang 错误和 Objective-C-Werror输出。 - electron-builder 找不到 helper:确认
npm run dist:mac先运行 native build,且.build/lanextend-vdisplay存在。 - 上传提示无匹配文件:检查
release/与artifactName,不要用if-no-files-found: ignore隐藏打包失败。 - Windows 构建意外尝试 Mac helper:
build-native.mjs在非 macOS 应明确打印 skip 并成功退出。
CI 通过后仍需双机手工验收;CI 失败时不要发布上一次残留的 release/ 文件。