拆分媒体
This commit is contained in:
@@ -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 ===');
|
||||
|
||||
Reference in New Issue
Block a user