|
|
|
|
<template>
|
|
|
|
|
<el-dialog
|
|
|
|
|
title="设置"
|
|
|
|
|
:visible.sync="visible"
|
|
|
|
|
width="600px"
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
@close="handleClose"
|
|
|
|
|
>
|
|
|
|
|
<el-tabs v-model="activeTab" :tab-position="'left'" class="settings-tabs">
|
|
|
|
|
<!-- 视频设置 -->
|
|
|
|
|
<el-tab-pane name="video">
|
|
|
|
|
<span slot="label"><i class="el-icon-video-camera"></i> 视频</span>
|
|
|
|
|
<el-form :model="videoConfig" label-width="140px" class="settings-form">
|
|
|
|
|
<!-- 摄像头选择:使用外部传入的摄像头列表 -->
|
|
|
|
|
<el-form-item label="摄像头/工作站">
|
|
|
|
|
<el-select
|
|
|
|
|
v-model="videoConfig.camera"
|
|
|
|
|
placeholder="请选择"
|
|
|
|
|
style="width: 240px"
|
|
|
|
|
@change="getCameraResolutions"
|
|
|
|
|
>
|
|
|
|
|
<el-option
|
|
|
|
|
v-for="cam in effectiveCameraList"
|
|
|
|
|
:key="cam.id"
|
|
|
|
|
:label="cam.name"
|
|
|
|
|
:value="cam.id"
|
|
|
|
|
/>
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="采集卡">
|
|
|
|
|
<el-select
|
|
|
|
|
v-model="videoConfig.captureCard"
|
|
|
|
|
placeholder="请选择"
|
|
|
|
|
style="width: 240px"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="网络调控策略">
|
|
|
|
|
<el-select
|
|
|
|
|
v-model="videoConfig.networkStrategy"
|
|
|
|
|
placeholder="请选择"
|
|
|
|
|
style="width: 240px"
|
|
|
|
|
>
|
|
|
|
|
<el-option label="弱网下流畅度优先" value="smooth" />
|
|
|
|
|
<el-option label="清晰度优先" value="quality" />
|
|
|
|
|
<el-option label="平衡模式" value="balance" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<!-- 摄像头分辨率 -->
|
|
|
|
|
<el-form-item label="摄像头分辨率">
|
|
|
|
|
<el-select
|
|
|
|
|
v-model="videoConfig.cameraResolution"
|
|
|
|
|
placeholder="请选择"
|
|
|
|
|
style="width: 240px"
|
|
|
|
|
>
|
|
|
|
|
<el-option
|
|
|
|
|
v-for="res in supportedResolutions"
|
|
|
|
|
:key="res.label"
|
|
|
|
|
:label="res.label"
|
|
|
|
|
:value="res.label"
|
|
|
|
|
/>
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="采集卡分辨率">
|
|
|
|
|
<el-select
|
|
|
|
|
v-model="videoConfig.captureResolution"
|
|
|
|
|
placeholder="请选择"
|
|
|
|
|
style="width: 240px"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="采集卡帧率">
|
|
|
|
|
<el-select
|
|
|
|
|
v-model="videoConfig.captureFps"
|
|
|
|
|
placeholder="请选择"
|
|
|
|
|
style="width: 240px"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="采集卡码率(2-8M)">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="videoConfig.captureBitrate"
|
|
|
|
|
style="width: 240px"
|
|
|
|
|
/>
|
|
|
|
|
<i class="el-icon-edit edit-icon" style="margin-left: 10px"></i>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="视频镜像">
|
|
|
|
|
<el-radio-group v-model="videoConfig.mirror">
|
|
|
|
|
<el-radio :label="true">是</el-radio>
|
|
|
|
|
<el-radio :label="false">否</el-radio>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
|
|
|
|
|
<!-- 音频设置 -->
|
|
|
|
|
<el-tab-pane name="audio">
|
|
|
|
|
<span slot="label"><i class="el-icon-microphone"></i> 音频</span>
|
|
|
|
|
<el-form :model="audioConfig" label-width="140px" class="settings-form">
|
|
|
|
|
<el-form-item label="麦克风">
|
|
|
|
|
<el-select
|
|
|
|
|
v-model="audioConfig.mic"
|
|
|
|
|
placeholder="请选择麦克风"
|
|
|
|
|
style="width: 240px"
|
|
|
|
|
@change="startMicTest"
|
|
|
|
|
>
|
|
|
|
|
<el-option
|
|
|
|
|
v-for="mic in effectiveMicList"
|
|
|
|
|
:key="mic.id"
|
|
|
|
|
:label="mic.name"
|
|
|
|
|
:value="mic.id"
|
|
|
|
|
/>
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<!-- 麦克风音量实时测试条 -->
|
|
|
|
|
<el-form-item label="麦克风测试">
|
|
|
|
|
<el-slider
|
|
|
|
|
v-model="micTestPercent"
|
|
|
|
|
class="progress-slider"
|
|
|
|
|
disabled
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<!-- 扬声器选择 -->
|
|
|
|
|
<el-form-item label="扬声器">
|
|
|
|
|
<el-select
|
|
|
|
|
v-model="audioConfig.speaker"
|
|
|
|
|
placeholder="请选择扬声器"
|
|
|
|
|
style="width: 240px"
|
|
|
|
|
@change="playSpeakerTest"
|
|
|
|
|
>
|
|
|
|
|
<el-option
|
|
|
|
|
v-for="spk in effectiveSpeakerList"
|
|
|
|
|
:key="spk.id"
|
|
|
|
|
:label="spk.name"
|
|
|
|
|
:value="spk.id"
|
|
|
|
|
/>
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<!-- 扬声器音量实时测试条 -->
|
|
|
|
|
<el-form-item label="扬声器测试">
|
|
|
|
|
<el-slider
|
|
|
|
|
v-model="speakerTestPercent"
|
|
|
|
|
class="progress-slider"
|
|
|
|
|
disabled
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
</el-tab-pane>
|
|
|
|
|
</el-tabs>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
export default {
|
|
|
|
|
name: "SettingsDialog",
|
|
|
|
|
props: {
|
|
|
|
|
visible: {
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false,
|
|
|
|
|
},
|
|
|
|
|
videoConfig: {
|
|
|
|
|
type: Object,
|
|
|
|
|
required: true,
|
|
|
|
|
},
|
|
|
|
|
audioConfig: {
|
|
|
|
|
type: Object,
|
|
|
|
|
required: true,
|
|
|
|
|
},
|
|
|
|
|
cameraList: {
|
|
|
|
|
type: Array,
|
|
|
|
|
default: () => [],
|
|
|
|
|
},
|
|
|
|
|
micList: {
|
|
|
|
|
type: Array,
|
|
|
|
|
default: () => [],
|
|
|
|
|
},
|
|
|
|
|
speakerList: {
|
|
|
|
|
type: Array,
|
|
|
|
|
default: () => [],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
activeTab: "video",
|
|
|
|
|
micTestPercent: 0,
|
|
|
|
|
speakerTestPercent: 0,
|
|
|
|
|
videoDevices: [],
|
|
|
|
|
audioInputDevices: [],
|
|
|
|
|
audioOutputDevices: [],
|
|
|
|
|
supportedResolutions: [],
|
|
|
|
|
micAudioContext: null,
|
|
|
|
|
micAnalyser: null,
|
|
|
|
|
micAnimation: null,
|
|
|
|
|
speakerAudio: null,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
effectiveCameraList() {
|
|
|
|
|
if (this.cameraList && this.cameraList.length) {
|
|
|
|
|
return this.cameraList;
|
|
|
|
|
}
|
|
|
|
|
return this.videoDevices.map(d => ({ id: d.deviceId, name: d.label || '未知摄像头' }));
|
|
|
|
|
},
|
|
|
|
|
effectiveMicList() {
|
|
|
|
|
if (this.micList && this.micList.length) {
|
|
|
|
|
return this.micList;
|
|
|
|
|
}
|
|
|
|
|
return this.audioInputDevices.map(d => ({ id: d.deviceId, name: d.label || '未知麦克风' }));
|
|
|
|
|
},
|
|
|
|
|
effectiveSpeakerList() {
|
|
|
|
|
if (this.speakerList && this.speakerList.length) {
|
|
|
|
|
return this.speakerList;
|
|
|
|
|
}
|
|
|
|
|
return this.audioOutputDevices.map(d => ({ id: d.deviceId, name: d.label || '未知扬声器' }));
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
watch: {
|
|
|
|
|
visible(val) {
|
|
|
|
|
if (val) {
|
|
|
|
|
this.getDevices();
|
|
|
|
|
this.micTestPercent = 10;
|
|
|
|
|
this.speakerTestPercent = 10;
|
|
|
|
|
} else {
|
|
|
|
|
this.stopAllTest();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
async getDevices() {
|
|
|
|
|
if (this.cameraList && this.cameraList.length) {
|
|
|
|
|
this.getCameraResolutions();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
|
|
|
|
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
|
|
|
this.videoDevices = devices.filter((d) => d.kind === "videoinput");
|
|
|
|
|
this.audioInputDevices = devices.filter((d) => d.kind === "audioinput");
|
|
|
|
|
this.audioOutputDevices = devices.filter((d) => d.kind === "audiooutput");
|
|
|
|
|
|
|
|
|
|
if (this.videoDevices.length && !this.videoConfig.camera) {
|
|
|
|
|
this.videoConfig.camera = this.videoDevices[0].deviceId;
|
|
|
|
|
this.getCameraResolutions();
|
|
|
|
|
}
|
|
|
|
|
if (this.audioInputDevices.length && !this.audioConfig.mic) {
|
|
|
|
|
this.audioConfig.mic = this.audioInputDevices[0].deviceId;
|
|
|
|
|
this.startMicTest();
|
|
|
|
|
}
|
|
|
|
|
if (this.audioOutputDevices.length && !this.audioConfig.speaker) {
|
|
|
|
|
this.audioConfig.speaker = this.audioOutputDevices[0].deviceId;
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("获取设备失败", err);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
async getCameraResolutions() {
|
|
|
|
|
if (!this.videoConfig.camera) return;
|
|
|
|
|
try {
|
|
|
|
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
|
|
|
video: { deviceId: this.videoConfig.camera },
|
|
|
|
|
});
|
|
|
|
|
const track = stream.getVideoTracks()[0];
|
|
|
|
|
const caps = track.getCapabilities ? track.getCapabilities() : { width: { max: 1920 } };
|
|
|
|
|
const list = [
|
|
|
|
|
{ w: 3840, h: 2160, label: "4K" },
|
|
|
|
|
{ w: 1920, h: 1080, label: "1080P" },
|
|
|
|
|
{ w: 1280, h: 720, label: "720P" },
|
|
|
|
|
{ w: 640, h: 480, label: "480P" },
|
|
|
|
|
].filter((item) => (caps.width && caps.width.max >= item.w) || true);
|
|
|
|
|
this.supportedResolutions = list;
|
|
|
|
|
if (list.length && !this.videoConfig.cameraResolution) {
|
|
|
|
|
this.videoConfig.cameraResolution = list[0].label;
|
|
|
|
|
}
|
|
|
|
|
stream.getTracks().forEach((t) => t.stop());
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.supportedResolutions = [
|
|
|
|
|
{ w: 1920, h: 1080, label: "1080P" },
|
|
|
|
|
{ w: 1280, h: 720, label: "720P" },
|
|
|
|
|
{ w: 640, h: 480, label: "480P" },
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
async startMicTest() {
|
|
|
|
|
this.stopMicTest();
|
|
|
|
|
try {
|
|
|
|
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
|
|
|
audio: { deviceId: this.audioConfig.mic },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.micAudioContext = new AudioContext();
|
|
|
|
|
this.micAnalyser = this.micAudioContext.createAnalyser();
|
|
|
|
|
this.micAnalyser.fftSize = 256;
|
|
|
|
|
const source = this.micAudioContext.createMediaStreamSource(stream);
|
|
|
|
|
source.connect(this.micAnalyser);
|
|
|
|
|
|
|
|
|
|
const dataArray = new Uint8Array(this.micAnalyser.frequencyBinCount);
|
|
|
|
|
const update = () => {
|
|
|
|
|
if (!this.micAnalyser) return;
|
|
|
|
|
this.micAnalyser.getByteFrequencyData(dataArray);
|
|
|
|
|
const sum = dataArray.reduce((a, b) => a + b, 0);
|
|
|
|
|
const vol = Math.min(100, sum / 50);
|
|
|
|
|
this.micTestPercent = vol;
|
|
|
|
|
this.micAnimation = requestAnimationFrame(update);
|
|
|
|
|
};
|
|
|
|
|
update();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.log("麦克风测试失败");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
stopMicTest() {
|
|
|
|
|
if (this.micAnimation) {
|
|
|
|
|
cancelAnimationFrame(this.micAnimation);
|
|
|
|
|
this.micAnimation = null;
|
|
|
|
|
}
|
|
|
|
|
if (this.micAudioContext) {
|
|
|
|
|
this.micAudioContext.close();
|
|
|
|
|
this.micAudioContext = null;
|
|
|
|
|
this.micAnalyser = null;
|
|
|
|
|
}
|
|
|
|
|
this.micTestPercent = 10;
|
|
|
|
|
},
|
|
|
|
|
async playSpeakerTest() {
|
|
|
|
|
try {
|
|
|
|
|
if (this.speakerAudio) {
|
|
|
|
|
this.speakerAudio.pause();
|
|
|
|
|
this.speakerAudio = null;
|
|
|
|
|
}
|
|
|
|
|
this.speakerTestPercent = 10;
|
|
|
|
|
|
|
|
|
|
const audio = new Audio(
|
|
|
|
|
"data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJmWl5hQVjMpOkhUXMDc1d3d1dPQ1NDR0dLR0dDQ0M/Pz8vLy8rKysnJyeHh4d3d3dHR0dDQ0M/Pz8vLy8rKysnJyeHh4d3d3dHR0dHR0dHR0dHR0dHR0dHQ="
|
|
|
|
|
);
|
|
|
|
|
audio.muted = false;
|
|
|
|
|
|
|
|
|
|
if (this.audioConfig.speaker && audio.setSinkId) {
|
|
|
|
|
await audio.setSinkId(this.audioConfig.speaker);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
audio.onplaying = () => {
|
|
|
|
|
this.speakerTestPercent = 80;
|
|
|
|
|
setTimeout(() => (this.speakerTestPercent = 40), 300);
|
|
|
|
|
setTimeout(() => (this.speakerTestPercent = 70), 600);
|
|
|
|
|
setTimeout(() => (this.speakerTestPercent = 30), 900);
|
|
|
|
|
setTimeout(() => (this.speakerTestPercent = 10), 1200);
|
|
|
|
|
};
|
|
|
|
|
audio.play();
|
|
|
|
|
this.speakerAudio = audio;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.log("扬声器测试失败");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
stopAllTest() {
|
|
|
|
|
this.stopMicTest();
|
|
|
|
|
if (this.speakerAudio) {
|
|
|
|
|
this.speakerAudio.pause();
|
|
|
|
|
this.speakerAudio = null;
|
|
|
|
|
}
|
|
|
|
|
this.speakerTestPercent = 10;
|
|
|
|
|
},
|
|
|
|
|
handleClose() {
|
|
|
|
|
this.$emit("update:visible", false);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.settings-form {
|
|
|
|
|
padding: 10px;
|
|
|
|
|
}
|
|
|
|
|
.edit-icon {
|
|
|
|
|
margin-left: 5px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
.progress-slider {
|
|
|
|
|
width: 240px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|