拆分媒体

This commit is contained in:
2026-05-24 01:01:28 +08:00
parent 44f4b30313
commit 0d8a567c95
3 changed files with 193 additions and 160 deletions

View File

@@ -16,36 +16,16 @@ import {
removeParticipant,
upsertParticipant
} from './participants.js';
const AUDIO_CONFIG = {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
};
const VAD_CONFIG = {
threshold: 15,
debounceTime: 500,
fftSize: 256
};
const MEDIA_CONSTRAINTS = {
video: {
width: { ideal: 1920, max: 1920 },
height: { ideal: 1080, max: 1080 },
frameRate: { ideal: 30, max: 30 }
},
audio: AUDIO_CONFIG
};
const VIDEO_ONLY_CONSTRAINT = {
video: {
width: { ideal: 1920, max: 1920 },
height: { ideal: 1080, max: 1080 },
frameRate: { ideal: 30, max: 30 }
},
audio: false
};
import {
AUDIO_CONFIG,
VAD_CONFIG,
VIDEO_ONLY_CONSTRAINT,
buildVideoConstraints,
getAdaptiveVideoBitrate,
getResolutionLabel,
getTargetResolutionBitrate
} from './media-config.js';
import { getNetworkQualityFromSummary, summarizeInboundStats } from './webrtc-stats.js';
class CallStateManager {
constructor() {
@@ -138,13 +118,7 @@ class CallStateManager {
// 请求摄像头权限并获取媒体流,启用回声消除
// 使用保存的分辨率(如有),否则使用默认约束
const videoConstraints = this._savedResolution
? {
width: { ideal: this._savedResolution.width, max: this._savedResolution.width },
height: { ideal: this._savedResolution.height, max: this._savedResolution.height },
frameRate: { ideal: 30, max: 30 }
}
: MEDIA_CONSTRAINTS.video;
const videoConstraints = buildVideoConstraints(this._savedResolution);
const stream = await navigator.mediaDevices.getUserMedia({
video: videoConstraints,
audio: AUDIO_CONFIG
@@ -1064,23 +1038,7 @@ class CallStateManager {
const settings = videoTrack ? videoTrack.getSettings() : {};
const height = settings.height || 1080;
const bitrateMap = {
270: 1000000,
480: 1500000,
720: 2500000,
1080: 4000000,
1440: 6000000
};
// 找到最接近的分辨率对应的比特率
let maxBitrate = 4000000;
const heights = Object.keys(bitrateMap).map(Number).sort((a, b) => a - b);
for (const h of heights) {
if (height <= h) {
maxBitrate = bitrateMap[h];
break;
}
maxBitrate = bitrateMap[h];
}
const maxBitrate = getAdaptiveVideoBitrate(height);
params.encodings[0].maxBitrate = maxBitrate;
params.encodings[0].scaleResolutionDownBy = 1.0;
@@ -1121,7 +1079,7 @@ class CallStateManager {
}
const track = videoTracks[0];
const label = height >= 1440 ? '2K 1440p' : height >= 1080 ? '1080p 超清' : height >= 720 ? '720p 高清' : '480p 流畅';
const label = getResolutionLabel(height);
try {
// 使用 applyConstraints 在不重新获取流的情况下调整分辨率
@@ -1135,13 +1093,7 @@ class CallStateManager {
// 根据分辨率调整编码比特率
// 480p: ~1Mbps, 720p: ~2.5Mbps, 1080p: ~4Mbps, 2K: ~6Mbps
const bitrateMap = {
270: 1000000, // 480p
720: 2500000, // 720p
1080: 4000000, // 1080p
1440: 6000000 // 2K
};
const maxBitrate = bitrateMap[height] || 2500000;
const maxBitrate = getTargetResolutionBitrate(height);
this._applyMaxBitrate(maxBitrate);
// 保存当前分辨率设置到本地存储
@@ -1258,62 +1210,8 @@ class CallStateManager {
return;
}
let totalPacketsLost = 0;
let totalPacketsReceived = 0;
let inboundRTPCount = 0;
let jitter = 0;
let roundTripTime = 0;
// 分析统计信息
stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.mediaType === 'video') {
inboundRTPCount++;
// 计算丢包率
if (report.packetsLost !== undefined && report.packetsReceived !== undefined) {
totalPacketsLost += report.packetsLost;
totalPacketsReceived += report.packetsReceived;
}
// 获取抖动
if (report.jitter !== undefined) {
jitter = Math.max(jitter, report.jitter);
}
// 获取往返时间
if (report.roundTripTime !== undefined) {
roundTripTime = Math.max(roundTripTime, report.roundTripTime);
}
}
});
// 计算网络质量指标
let quality = 'excellent';
if (inboundRTPCount > 0) {
// 基于丢包率判断
const packetLossRate = totalPacketsReceived > 0 ? (totalPacketsLost / (totalPacketsLost + totalPacketsReceived)) : 0;
// 基于抖动判断
const jitterMs = jitter * 1000;
// 基于往返时间判断
const rttMs = roundTripTime * 1000;
// 综合评估网络质量
if (packetLossRate > 0.05 || jitterMs > 100 || rttMs > 300) {
quality = 'poor';
} else if (packetLossRate > 0.02 || jitterMs > 50 || rttMs > 150) {
quality = 'fair';
} else if (packetLossRate > 0.01 || jitterMs > 30 || rttMs > 100) {
quality = 'good';
} else {
quality = 'excellent';
}
} else {
// 没有收到任何RTP包设置为无信号状态
quality = 'no_signal';
}
const summary = summarizeInboundStats(stats);
const quality = getNetworkQualityFromSummary(summary);
// 更新网络质量状态
if (this.state.session.remoteUser.networkQuality !== quality) {
@@ -1455,48 +1353,7 @@ class CallStateManager {
return;
}
let statsSummary = {
video: {
packetsLost: 0,
packetsReceived: 0,
bytesReceived: 0,
jitter: 0,
roundTripTime: 0,
fps: 0,
bitrate: 0
},
audio: {
packetsLost: 0,
packetsReceived: 0,
bytesReceived: 0,
jitter: 0
}
};
// 分析统计信息
stats.forEach(report => {
if (report.type === 'inbound-rtp') {
if (report.mediaType === 'video') {
statsSummary.video.packetsLost = report.packetsLost || 0;
statsSummary.video.packetsReceived = report.packetsReceived || 0;
statsSummary.video.bytesReceived = report.bytesReceived || 0;
statsSummary.video.jitter = report.jitter || 0;
statsSummary.video.roundTripTime = report.roundTripTime || 0;
statsSummary.video.fps = report.framesPerSecond || 0;
// 计算视频比特率 (kbps)
if (report.bytesReceived && report.timestamp) {
const duration = report.timestamp / 1000; // 转换为秒
statsSummary.video.bitrate = duration > 0 ? Math.round((report.bytesReceived * 8) / (duration * 1000)) : 0;
}
} else if (report.mediaType === 'audio') {
statsSummary.audio.packetsLost = report.packetsLost || 0;
statsSummary.audio.packetsReceived = report.packetsReceived || 0;
statsSummary.audio.bytesReceived = report.bytesReceived || 0;
statsSummary.audio.jitter = report.jitter || 0;
}
}
});
const statsSummary = summarizeInboundStats(stats);
// 输出详细统计信息
console.log('=== WebRTC Statistics ===');