优化完成

This commit is contained in:
2026-04-25 21:09:45 +08:00
parent bcd55f9dac
commit d48ce78c03
10 changed files with 707 additions and 569 deletions

View File

@@ -9,6 +9,28 @@ import { getServerConfig, getRTCConfiguration } from "../js/config.js";//服务
import { showNotification, generateId } from './utils.js'; // 导入通知函数
import chatMessage from './chatmessage.js';
const AUDIO_CONFIG = {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
};
const VAD_CONFIG = {
threshold: 15,
debounceTime: 500,
fftSize: 256
};
const MEDIA_CONSTRAINTS = {
video: true,
audio: AUDIO_CONFIG
};
const VIDEO_ONLY_CONSTRAINT = {
video: true,
audio: false
};
class CallStateManager {
constructor() {
// 核心状态
@@ -91,14 +113,7 @@ class CallStateManager {
}
// 请求摄像头权限并获取媒体流,启用回声消除
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
});
const stream = await navigator.mediaDevices.getUserMedia(MEDIA_CONSTRAINTS);
console.log('Stream obtained successfully:', stream);
console.log('Video tracks:', stream.getVideoTracks());
@@ -120,7 +135,7 @@ class CallStateManager {
this.emitMediaStateChange();
// 启动本地音频活动检测
this.startLocalActivityDetection();
this.startActivityDetection(this.state.localStream, { isLocal: true });
} catch (error) {
console.error('Error getting local stream:', error);
// 如果获取视频失败,保持视频关闭状态
@@ -140,7 +155,7 @@ class CallStateManager {
if (mediaType === 'video' && value) {
try {
// 只获取新的视频轨道,不干扰正在工作的音频
const newVideoStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
const newVideoStream = await navigator.mediaDevices.getUserMedia(VIDEO_ONLY_CONSTRAINT);
const newVideoTrack = newVideoStream.getVideoTracks()[0];
if (!newVideoTrack) {
@@ -232,7 +247,7 @@ class CallStateManager {
this.notify({ type: 'LOCAL_STREAM_OBTAINED', stream: this.state.localStream });
this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'video', value: true });
this.emitMediaStateChange();
this.startLocalActivityDetection();
this.startActivityDetection(this.state.localStream, { isLocal: true });
} catch (error) {
console.error('Error reopening video:', error);
@@ -280,12 +295,12 @@ class CallStateManager {
}
/**
* 设置WebRTC连接
* 创建信令和RTC实例
* @async
* @param {string} connectionId - 连接ID
* @returns {Promise<void>}
*/
async setUp(connectionId) {
//TODO
async _createSignalingAndRTC(connectionId) {
this.connectionId = connectionId; // 获取连接ID
// 设置状态为连接中
this.state.session.status = 'connecting';
@@ -326,6 +341,23 @@ class CallStateManager {
]
};
this.renderstreaming = new RenderStreaming(signaling, config);
}
/**
* 设置WebRTC连接
* @async
* @returns {Promise<void>}
*/
async setUp(connectionId) {
await this._createSignalingAndRTC(connectionId);
this._registerCallbacks();
await this._startConnection(connectionId);
}
/**
* 注册所有WebRTC回调
*/
_registerCallbacks() {
this.renderstreaming.onNewPeer = (participantId) => {
console.log(`New peer created for ${participantId}, adding local tracks`);
if (this.state.localStream) {
@@ -336,6 +368,7 @@ class CallStateManager {
this.setCodecPreferences(participantId);
}
};
// 连接建立回调
this.renderstreaming.onConnect = (connectionId, data) => {
// 保存角色信息host/participant
@@ -515,24 +548,26 @@ class CallStateManager {
name: this.state.session.localUser.name,
avatar: this.state.session.localUser.avatar
});
// 启动通话时长计时器
this.durationInterval = setInterval(() => {
this.state.session.duration++;
this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration });
}, 1000);
// 启动通话时长计时器(避免重复启动)
if (!this.durationInterval) {
this.durationInterval = setInterval(() => {
this.state.session.duration++;
this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration });
}, 1000);
}
}
// 如果是音频轨道,启动远程音频活动检测
if (data.track.kind === 'audio') {
this.startRemoteActivityDetection();
this.startActivityDetection(this.state.remoteStream, { isLocal: false });
}
} else if (direction == "sendonly") {
// 本地发送轨道,启动本地音频活动检测
if (data.track.kind === 'audio') {
this.startLocalActivityDetection();
this.startActivityDetection(this.state.localStream, { isLocal: true });
}
}
};
// 初始化 RenderStreaming 实例后
this.renderstreaming.onMessage = (data) => {
console.log('收到消息:', data);
if (data.type === 'chat-message') {
@@ -657,26 +692,44 @@ class CallStateManager {
}
this.state.participants = filtered;
this.notify({ type: 'PARTICIPANTS_UPDATE', participants: this.state.participants });
// 同步通话时长仅首次同步将Host的时长作为基准
if (!this.durationSynced && typeof data.callDuration === 'number') {
this.state.session.duration = data.callDuration;
this.durationSynced = true;
// 如果计时器尚未启动(远程流还未到达),先启动计时器
if (!this.durationInterval) {
this.durationInterval = setInterval(() => {
this.state.session.duration++;
this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration });
}, 1000);
}
this.notify({ type: 'DURATION_UPDATE', duration: this.state.session.duration });
console.log(`通话时长已同步,当前时长: ${data.callDuration}`);
}
}
}
};
}
/**
* 启动WebRTC连接和检测
* @async
* @param {string} connectionId - 连接ID
* @returns {Promise<void>}
*/
async _startConnection(connectionId) {
// 启动WebRTC连接
await this.renderstreaming.start();
await this.renderstreaming.createConnection(connectionId);
// 启动网络质量检测
this.startNetworkQualityDetection();
// 启动本地音频活动检测
this.startLocalActivityDetection();
// 启动远端音频活动检测
this.startRemoteActivityDetection();
//模拟远端活动 (开发测试用)
//this.simulateRemoteActivity();
this.startActivityDetection(this.state.localStream, { isLocal: true });
//启动远端音频活动检测
this.startActivityDetection(this.state.remoteStream, { isLocal: false });
}
/**
@@ -694,6 +747,8 @@ class CallStateManager {
clearInterval(this.durationInterval);
this.durationInterval = null;
}
// 重置通话时长同步标志
this.durationSynced = false;
const isHost = this.role === 'host';
console.log(`Disconnect peer on ${this.connectionId}. Role: ${this.role}`);
@@ -769,7 +824,8 @@ class CallStateManager {
this.renderstreaming.sendMessage({
type: 'participants-sync',
data: memberList
data: memberList,
callDuration: this.state.session.duration
});
console.log('Broadcast participants list:', Object.keys(memberList));
}
@@ -853,27 +909,6 @@ class CallStateManager {
await this.init();
}
// 模拟远端活动 (开发测试用)
simulateRemoteActivity() {
setInterval(() => {
if (Math.random() > 0.7) {
const isSpeaking = Math.random() > 0.5;
this.updateRemoteMedia({ isSpeaking });
}
}, 800);
}
simulateNetworkChange() {
// 模拟网络质量变化
const qualities = ['good', 'fair', 'excellent', 'poor', 'no_signal'];
setInterval(() => {
if (Math.random() > 0.8) {
const quality = qualities[Math.floor(Math.random() * qualities.length)];
this.state.session.remoteUser.networkQuality = quality;
this.notify({ type: 'NETWORK_CHANGE', quality });
}
}, 5000);
}
// 真实网络质量检测
async detectNetworkQuality() {
if (!this.renderstreaming) {
@@ -953,181 +988,81 @@ class CallStateManager {
console.error('Error detecting network quality:', error);
}
}
// 真实音频活动检测 - 远端
startRemoteActivityDetection() {
// 检查是否有远端音频流
if (!this.state.remoteStream) {
// 音频活动检测
startActivityDetection(stream, { isLocal = false } = {}) {
if (!stream) {
return;
}
// 获取音频轨道
const audioTracks = this.state.remoteStream.getAudioTracks();
const audioTracks = stream.getAudioTracks();
if (audioTracks.length === 0) {
return;
}
try {
// 创建音频上下文
const { threshold, debounceTime, fftSize } = VAD_CONFIG;
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
// 创建媒体流源
const source = audioContext.createMediaStreamSource(this.state.remoteStream);
// 创建音频分析器
const source = audioContext.createMediaStreamSource(stream);
const analyser = audioContext.createAnalyser();
analyser.fftSize = 256;
analyser.fftSize = fftSize;
// 连接音频节点
source.connect(analyser);
// 创建数据缓冲区
const dataArray = new Uint8Array(analyser.frequencyBinCount);
// 检测参数
const threshold = 15; // 音频电平阈值
const debounceTime = 500; // 防抖时间
let isSpeaking = false;
let lastActivityTime = 0;
// 音频活动检测循环
const detectActivity = () => {
if (!this.state.remoteStream || !this.renderstreaming) {
if (!stream || !this.renderstreaming) {
return;
}
// 获取时域数据
analyser.getByteTimeDomainData(dataArray);
// 计算音频电平
let sum = 0;
for (let i = 0; i < dataArray.length; i++) {
// 转换为振幅 (0-255 → -128-127)
const amplitude = dataArray[i] - 128;
sum += amplitude * amplitude;
}
const rms = Math.sqrt(sum / dataArray.length);
const level = rms / 128; // 归一化到 0-1
const level = rms / 128;
// 检测说话状态
const currentTime = Date.now();
if (level > threshold / 100) {
// 检测到说话
lastActivityTime = currentTime;
if (!isSpeaking) {
isSpeaking = true;
this.updateRemoteMedia({ isSpeaking: true });
if (isLocal) {
this.state.session.localUser.mediaState.isSpeaking = true;
this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'isSpeaking', value: true });
this.emitMediaStateChange();
} else {
this.updateRemoteMedia({ isSpeaking: true });
}
}
} else if (isSpeaking && currentTime - lastActivityTime > debounceTime) {
// 停止说话
isSpeaking = false;
this.updateRemoteMedia({ isSpeaking: false });
}
// 继续检测
if (this.state.session.status === 'ongoing') {
requestAnimationFrame(detectActivity);
}
};
// 开始检测
detectActivity();
console.log('Remote activity detection started');
} catch (error) {
console.error('Error starting remote activity detection:', error);
}
}
// 真实音频活动检测 - 本地
startLocalActivityDetection() {
// 检查是否有本地音频流
if (!this.state.localStream) {
return;
}
// 获取音频轨道
const audioTracks = this.state.localStream.getAudioTracks();
if (audioTracks.length === 0) {
return;
}
try {
// 创建音频上下文
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
// 创建媒体流源
const source = audioContext.createMediaStreamSource(this.state.localStream);
// 创建音频分析器
const analyser = audioContext.createAnalyser();
analyser.fftSize = 256;
// 连接音频节点
source.connect(analyser);
// 创建数据缓冲区
const dataArray = new Uint8Array(analyser.frequencyBinCount);
// 检测参数
const threshold = 15; // 音频电平阈值
const debounceTime = 500; // 防抖时间
let isSpeaking = false;
let lastActivityTime = 0;
// 音频活动检测循环
const detectActivity = () => {
if (!this.state.localStream || !this.renderstreaming) {
return;
}
// 获取时域数据
analyser.getByteTimeDomainData(dataArray);
// 计算音频电平
let sum = 0;
for (let i = 0; i < dataArray.length; i++) {
// 转换为振幅 (0-255 → -128-127)
const amplitude = dataArray[i] - 128;
sum += amplitude * amplitude;
}
const rms = Math.sqrt(sum / dataArray.length);
const level = rms / 128; // 归一化到 0-1
// 检测说话状态
const currentTime = Date.now();
if (level > threshold / 100) {
// 检测到说话
lastActivityTime = currentTime;
if (!isSpeaking) {
isSpeaking = true;
this.state.session.localUser.mediaState.isSpeaking = true;
this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'isSpeaking', value: true });
// 发送媒体状态到服务器
if (isLocal) {
this.state.session.localUser.mediaState.isSpeaking = false;
this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'isSpeaking', value: false });
this.emitMediaStateChange();
} else {
this.updateRemoteMedia({ isSpeaking: false });
}
} else if (isSpeaking && currentTime - lastActivityTime > debounceTime) {
// 停止说话
isSpeaking = false;
this.state.session.localUser.mediaState.isSpeaking = false;
this.notify({ type: 'LOCAL_MEDIA_CHANGE', mediaType: 'isSpeaking', value: false });
// 发送媒体状态到服务器
this.emitMediaStateChange();
}
// 继续检测
if (this.state.session.status === 'ongoing') {
requestAnimationFrame(detectActivity);
}
};
// 开始检测
detectActivity();
console.log('Local activity detection started');
console.log(`${isLocal ? 'Local' : 'Remote'} activity detection started`);
} catch (error) {
console.error('Error starting local activity detection:', error);
console.error(`Error starting ${isLocal ? 'local' : 'remote'} activity detection:`, error);
}
}
// 启动网络质量检测
@@ -1135,7 +1070,6 @@ class CallStateManager {
// 每3秒检测一次网络质量
this.networkQualityInterval = setInterval(() => {
this.detectNetworkQuality();
//this.simulateNetworkChange();
}, 3000);
}