|
|
|
|
@ -69,7 +69,7 @@ |
|
|
|
|
class="share-preview" |
|
|
|
|
autoplay |
|
|
|
|
playsinline |
|
|
|
|
muted |
|
|
|
|
:muted="false" |
|
|
|
|
></video> |
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
@ -623,7 +623,7 @@ export default { |
|
|
|
|
|
|
|
|
|
// ==================== 【4】采集卡设备 ==================== |
|
|
|
|
async switchCaptureCard(deviceId) { |
|
|
|
|
// 清理旧采集卡流 |
|
|
|
|
// 1. 彻底清理旧资源(防止残留) |
|
|
|
|
if (this.captureCheckTimer) clearTimeout(this.captureCheckTimer); |
|
|
|
|
this.closeCaptureCard(); |
|
|
|
|
|
|
|
|
|
@ -635,65 +635,64 @@ export default { |
|
|
|
|
|
|
|
|
|
this.isSharing = true; |
|
|
|
|
await this.$nextTick(); |
|
|
|
|
const videoEl = this.$refs.shareVideo; |
|
|
|
|
|
|
|
|
|
const constraints = { |
|
|
|
|
video: { |
|
|
|
|
deviceId: { exact: deviceId }, |
|
|
|
|
width: { ideal: 1920 }, |
|
|
|
|
height: { ideal: 1080 }, |
|
|
|
|
frameRate: { ideal: 30 }, |
|
|
|
|
}, |
|
|
|
|
audio: false, |
|
|
|
|
}; |
|
|
|
|
// 2. 强制重置 Video 标签状态(防止 CSS 缓存黑屏) |
|
|
|
|
// 强制重置 Video 标签状态 |
|
|
|
|
if (videoEl) { |
|
|
|
|
videoEl.srcObject = null; |
|
|
|
|
videoEl.load(); |
|
|
|
|
videoEl.style.width = "100%"; |
|
|
|
|
videoEl.style.height = "100%"; |
|
|
|
|
videoEl.style.objectFit = "contain"; |
|
|
|
|
videoEl.style.background = "#000"; |
|
|
|
|
videoEl.muted = false; // ✅ 允许播放声音 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let captureStream = null; |
|
|
|
|
try { |
|
|
|
|
captureStream = await navigator.mediaDevices.getUserMedia(constraints); |
|
|
|
|
} catch (err) { |
|
|
|
|
console.error("采集卡获取流失败:", err); |
|
|
|
|
const lowConstraints = { |
|
|
|
|
video: { deviceId: { exact: deviceId } }, |
|
|
|
|
audio: false, |
|
|
|
|
}; |
|
|
|
|
try { |
|
|
|
|
captureStream = await navigator.mediaDevices.getUserMedia( |
|
|
|
|
lowConstraints |
|
|
|
|
); |
|
|
|
|
} catch (e2) { |
|
|
|
|
this.$message.error( |
|
|
|
|
"无法打开采集卡,请检查USB口、权限、是否被其他软件占用" |
|
|
|
|
); |
|
|
|
|
this.isSharing = false; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
console.log("正在执行最终修复方案:强制30帧 + 无分辨率限制..."); |
|
|
|
|
|
|
|
|
|
const videoEl = this.$refs.shareVideo; |
|
|
|
|
videoEl.srcObject = captureStream; |
|
|
|
|
videoEl.muted = true; |
|
|
|
|
videoEl.playsinline = true; |
|
|
|
|
videoEl.autoplay = true; |
|
|
|
|
|
|
|
|
|
let hasValidFrame = false; |
|
|
|
|
videoEl.onloadeddata = () => (hasValidFrame = true); |
|
|
|
|
|
|
|
|
|
// 3秒超时无信号判定失败 |
|
|
|
|
this.captureCheckTimer = setTimeout(() => { |
|
|
|
|
if (!hasValidFrame) { |
|
|
|
|
this.$message.error("采集卡无信号,请检查设备、HDMI线、USB3.0接口"); |
|
|
|
|
this.closeCaptureCard(); |
|
|
|
|
this.isSharing = false; |
|
|
|
|
// 3. 发起请求(核心修改点) |
|
|
|
|
const stream = await navigator.mediaDevices.getUserMedia({ |
|
|
|
|
video: { |
|
|
|
|
deviceId: { exact: deviceId }, |
|
|
|
|
// 核心修复 A:强制帧率 30,这是绿联 USB 2.0 采集卡的“生命线” |
|
|
|
|
frameRate: { exact: 30 }, |
|
|
|
|
// 核心修复 B:强制 MJPEG,绕过 USB 2.0 带宽瓶颈 |
|
|
|
|
advanced: [{ chromegfx: "mjpeg" }], |
|
|
|
|
// 注意:这里绝对不要写 width/height,让它默认输出 640x480 |
|
|
|
|
}, |
|
|
|
|
audio: true, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
console.log( |
|
|
|
|
"采集成功!实际设置:", |
|
|
|
|
stream.getVideoTracks()[0].getSettings() |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// 4. 绑定流 |
|
|
|
|
if (videoEl) { |
|
|
|
|
videoEl.srcObject = stream; |
|
|
|
|
|
|
|
|
|
// 核心修复 C:确保播放 |
|
|
|
|
const playPromise = videoEl.play(); |
|
|
|
|
if (playPromise !== undefined) { |
|
|
|
|
playPromise |
|
|
|
|
.then(() => { |
|
|
|
|
console.log("视频播放已成功启动"); |
|
|
|
|
this.$message.success("采集卡已连接 (强制30帧/MJPEG)"); |
|
|
|
|
}) |
|
|
|
|
.catch((error) => { |
|
|
|
|
console.error("自动播放被拦截:", error); |
|
|
|
|
this.$message.error("播放被拦截,请尝试点击页面任意位置"); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, 3000); |
|
|
|
|
|
|
|
|
|
// 监听采集卡断开 |
|
|
|
|
const videoTrack = captureStream.getVideoTracks()[0]; |
|
|
|
|
if (videoTrack) |
|
|
|
|
videoTrack.onended = () => { |
|
|
|
|
this.$message.warning("采集卡信号已断开"); |
|
|
|
|
this.closeCaptureCard(); |
|
|
|
|
this.isSharing = false; |
|
|
|
|
}; |
|
|
|
|
} catch (err) { |
|
|
|
|
console.error("最终方案失败:", err); |
|
|
|
|
this.$message.error("连接失败: " + err.message); |
|
|
|
|
this.isSharing = false; |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// 关闭采集卡并释放资源 |
|
|
|
|
@ -702,14 +701,16 @@ export default { |
|
|
|
|
if (this.captureCheckTimer) clearTimeout(this.captureCheckTimer); |
|
|
|
|
const videoEl = this.$refs.shareVideo; |
|
|
|
|
if (!videoEl) return; |
|
|
|
|
|
|
|
|
|
const stream = videoEl.srcObject; |
|
|
|
|
if (stream) |
|
|
|
|
stream.getTracks().forEach((t) => { |
|
|
|
|
t.stop(); |
|
|
|
|
t.onended = null; |
|
|
|
|
if (stream) { |
|
|
|
|
// 停止所有轨道 |
|
|
|
|
stream.getTracks().forEach((track) => { |
|
|
|
|
track.stop(); |
|
|
|
|
track.onended = null; |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
videoEl.srcObject = null; |
|
|
|
|
videoEl.onloadeddata = null; |
|
|
|
|
} catch (e) { |
|
|
|
|
console.log("关闭采集卡异常:", e); |
|
|
|
|
} |
|
|
|
|
@ -724,7 +725,7 @@ export default { |
|
|
|
|
try { |
|
|
|
|
const stream = await navigator.mediaDevices.getDisplayMedia({ |
|
|
|
|
video: { width: 1920, height: 1080, frameRate: 15 }, |
|
|
|
|
audio: false, |
|
|
|
|
audio: true, |
|
|
|
|
}); |
|
|
|
|
const track = stream.getVideoTracks()[0]; |
|
|
|
|
track.onended = () => this.stopShare(); |
|
|
|
|
@ -994,20 +995,20 @@ export default { |
|
|
|
|
|
|
|
|
|
// ==================== 【11】网络状态监控 ==================== |
|
|
|
|
startNetworkMonitoring() { |
|
|
|
|
setInterval(() => { |
|
|
|
|
if (!window.hirtcwebsdk) return; |
|
|
|
|
const s = window.hirtcwebsdk.getNetworkStats(); |
|
|
|
|
if (s?.rtt) { |
|
|
|
|
this.networkDelay = Math.round(s.rtt); |
|
|
|
|
if (this.networkDelay < 100) |
|
|
|
|
this.networkQuality = { text: "优秀", color: "#00e676" }; |
|
|
|
|
else if (this.networkDelay < 300) |
|
|
|
|
this.networkQuality = { text: "良好", color: "#00e676" }; |
|
|
|
|
else if (this.networkDelay < 600) |
|
|
|
|
this.networkQuality = { text: "一般", color: "#ffc107" }; |
|
|
|
|
else this.networkQuality = { text: "差", color: "#f56c6c" }; |
|
|
|
|
} |
|
|
|
|
}, 2000); |
|
|
|
|
// setInterval(() => { |
|
|
|
|
// if (!window.hirtcwebsdk) return; |
|
|
|
|
// const s = window.hirtcwebsdk.getNetworkStats(); |
|
|
|
|
// if (s?.rtt) { |
|
|
|
|
// this.networkDelay = Math.round(s.rtt); |
|
|
|
|
// if (this.networkDelay < 100) |
|
|
|
|
// this.networkQuality = { text: "优秀", color: "#00e676" }; |
|
|
|
|
// else if (this.networkDelay < 300) |
|
|
|
|
// this.networkQuality = { text: "良好", color: "#00e676" }; |
|
|
|
|
// else if (this.networkDelay < 600) |
|
|
|
|
// this.networkQuality = { text: "一般", color: "#ffc107" }; |
|
|
|
|
// else this.networkQuality = { text: "差", color: "#f56c6c" }; |
|
|
|
|
// } |
|
|
|
|
// }, 2000); |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
// ==================== 【12】设置模块 ==================== |
|
|
|
|
@ -1065,7 +1066,7 @@ export default { |
|
|
|
|
this.inviteDialogVisible = true; |
|
|
|
|
}, |
|
|
|
|
handleInviteConfirm(selectedContacts) { |
|
|
|
|
console.log('邀请的联系人:', selectedContacts); |
|
|
|
|
console.log("邀请的联系人:", selectedContacts); |
|
|
|
|
this.$message.success(`已邀请 ${selectedContacts.length} 位联系人`); |
|
|
|
|
this.inviteDialogVisible = false; |
|
|
|
|
}, |
|
|
|
|
|