fix webRTC
This commit is contained in:
@@ -1,25 +1,262 @@
|
||||
// // src/services/webrtc.js - WebRTC utility functions
|
||||
// export const webrtcService = {
|
||||
// /**
|
||||
// * Get STUN/TURN server configuration
|
||||
// */
|
||||
// getIceServerConfig() {
|
||||
// return {
|
||||
// iceServers: [
|
||||
// { urls: 'stun:stun.l.google.com:19302' },
|
||||
// { urls: 'stun:stun1.l.google.com:19302' },
|
||||
// { urls: 'stun:stun2.l.google.com:19302' },
|
||||
// // Add TURN servers for production
|
||||
// // {
|
||||
// // urls: 'turn:your-turn-server.com:3478',
|
||||
// // username: 'username',
|
||||
// // credential: 'password'
|
||||
// // }
|
||||
// ],
|
||||
// iceCandidatePoolSize: 10,
|
||||
// }
|
||||
// },
|
||||
|
||||
// /**
|
||||
// * Test WebRTC support
|
||||
// */
|
||||
// isWebRTCSupported() {
|
||||
// return !!(
|
||||
// window.RTCPeerConnection ||
|
||||
// window.webkitRTCPeerConnection ||
|
||||
// window.mozRTCPeerConnection
|
||||
// )
|
||||
// },
|
||||
|
||||
// /**
|
||||
// * Get WebRTC statistics
|
||||
// */
|
||||
// async getConnectionStats(peerConnection) {
|
||||
// if (!peerConnection) return null
|
||||
|
||||
// try {
|
||||
// const stats = await peerConnection.getStats()
|
||||
// const result = {
|
||||
// video: {},
|
||||
// audio: {},
|
||||
// connection: {},
|
||||
// }
|
||||
|
||||
// stats.forEach((report) => {
|
||||
// if (report.type === 'inbound-rtp' && report.mediaType === 'video') {
|
||||
// result.video.inbound = {
|
||||
// bytesReceived: report.bytesReceived,
|
||||
// packetsReceived: report.packetsReceived,
|
||||
// packetsLost: report.packetsLost,
|
||||
// frameWidth: report.frameWidth,
|
||||
// frameHeight: report.frameHeight,
|
||||
// framesPerSecond: report.framesPerSecond,
|
||||
// }
|
||||
// } else if (report.type === 'outbound-rtp' && report.mediaType === 'video') {
|
||||
// result.video.outbound = {
|
||||
// bytesSent: report.bytesSent,
|
||||
// packetsSent: report.packetsSent,
|
||||
// frameWidth: report.frameWidth,
|
||||
// frameHeight: report.frameHeight,
|
||||
// framesPerSecond: report.framesPerSecond,
|
||||
// }
|
||||
// } else if (report.type === 'candidate-pair' && report.state === 'succeeded') {
|
||||
// result.connection = {
|
||||
// currentRoundTripTime: report.currentRoundTripTime,
|
||||
// availableOutgoingBitrate: report.availableOutgoingBitrate,
|
||||
// bytesReceived: report.bytesReceived,
|
||||
// bytesSent: report.bytesSent,
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
|
||||
// return result
|
||||
// } catch (error) {
|
||||
// console.error('Failed to get connection stats:', error)
|
||||
// return null
|
||||
// }
|
||||
// },
|
||||
|
||||
// /**
|
||||
// * Monitor connection quality
|
||||
// */
|
||||
// createQualityMonitor(peerConnection, callback, interval = 5000) {
|
||||
// if (!peerConnection || typeof callback !== 'function') {
|
||||
// return null
|
||||
// }
|
||||
|
||||
// const monitor = setInterval(async () => {
|
||||
// try {
|
||||
// const stats = await this.getConnectionStats(peerConnection)
|
||||
// if (stats) {
|
||||
// const quality = this.calculateQuality(stats)
|
||||
// callback(quality, stats)
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error('Quality monitoring error:', error)
|
||||
// }
|
||||
// }, interval)
|
||||
|
||||
// return monitor
|
||||
// },
|
||||
|
||||
// /**
|
||||
// * Calculate connection quality score (0-100)
|
||||
// */
|
||||
// calculateQuality(stats) {
|
||||
// let score = 100
|
||||
|
||||
// // Reduce score based on packet loss
|
||||
// if (stats.video.inbound?.packetsLost && stats.video.inbound?.packetsReceived) {
|
||||
// const lossRate = stats.video.inbound.packetsLost / stats.video.inbound.packetsReceived
|
||||
// score -= lossRate * 50 // Up to 50 points for packet loss
|
||||
// }
|
||||
|
||||
// // Reduce score based on round trip time
|
||||
// if (stats.connection?.currentRoundTripTime) {
|
||||
// const rtt = stats.connection.currentRoundTripTime * 1000 // Convert to ms
|
||||
// if (rtt > 150) {
|
||||
// score -= Math.min(30, (rtt - 150) / 10) // Up to 30 points for high latency
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Reduce score based on low frame rate
|
||||
// if (stats.video.inbound?.framesPerSecond) {
|
||||
// const fps = stats.video.inbound.framesPerSecond
|
||||
// if (fps < 15) {
|
||||
// score -= (15 - fps) * 2 // Up to 30 points for low FPS
|
||||
// }
|
||||
// }
|
||||
|
||||
// return Math.max(0, Math.min(100, Math.round(score)))
|
||||
// },
|
||||
// }
|
||||
|
||||
|
||||
// src/services/webrtc.js - WebRTC utility functions
|
||||
export const webrtcService = {
|
||||
/**
|
||||
* Get STUN/TURN server configuration
|
||||
* Get STUN/TURN server configuration (оптимизировано)
|
||||
*/
|
||||
getIceServerConfig() {
|
||||
return {
|
||||
iceServers: [
|
||||
{ urls: 'stun:stun.l.google.com:19302' },
|
||||
{ urls: 'stun:stun1.l.google.com:19302' },
|
||||
{ urls: 'stun:stun2.l.google.com:19302' },
|
||||
// Add TURN servers for production
|
||||
// Добавьте TURN сервер только при необходимости
|
||||
// {
|
||||
// urls: 'turn:your-turn-server.com:3478',
|
||||
// username: 'username',
|
||||
// credential: 'password'
|
||||
// }
|
||||
],
|
||||
iceCandidatePoolSize: 10,
|
||||
iceCandidatePoolSize: 0, // Уменьшено для экономии ресурсов
|
||||
bundlePolicy: 'max-bundle', // Объединение каналов
|
||||
rtcpMuxPolicy: 'require' // Объединение RTCP и RTP
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get optimized media constraints
|
||||
*/
|
||||
getOptimizedConstraints() {
|
||||
return {
|
||||
video: {
|
||||
width: { ideal: 640, max: 1280 },
|
||||
height: { ideal: 480, max: 720 },
|
||||
frameRate: { ideal: 15, max: 30 }
|
||||
},
|
||||
audio: {
|
||||
sampleRate: 10000,
|
||||
channelCount: 1,
|
||||
echoCancellation: true,
|
||||
noiseSuppression: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply optimized parameters to video sender
|
||||
*/
|
||||
optimizeVideoSender(sender, quality = 'medium') {
|
||||
if (!sender || sender.track?.kind !== 'video') return;
|
||||
|
||||
const presets = {
|
||||
low: {
|
||||
maxBitrate: 150_000,
|
||||
scaleResolutionDownBy: 4.0,
|
||||
maxFramerate: 10
|
||||
},
|
||||
medium: {
|
||||
maxBitrate: 400_000,
|
||||
scaleResolutionDownBy: 2.0,
|
||||
maxFramerate: 15
|
||||
},
|
||||
high: {
|
||||
maxBitrate: 1_000_000,
|
||||
scaleResolutionDownBy: 1.0,
|
||||
maxFramerate: 30
|
||||
}
|
||||
};
|
||||
|
||||
const params = sender.getParameters();
|
||||
params.encodings = [presets[quality] || presets.medium];
|
||||
sender.setParameters(params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply simulcast for better quality adaptation
|
||||
*/
|
||||
enableSimulcast(sender) {
|
||||
if (!sender || sender.track?.kind !== 'video') return;
|
||||
|
||||
const params = sender.getParameters();
|
||||
params.encodings = [
|
||||
{ rid: 'low', active: true, maxBitrate: 150_000, scaleResolutionDownBy: 4.0, maxFramerate: 10 },
|
||||
{ rid: 'medium', active: true, maxBitrate: 400_000, scaleResolutionDownBy: 2.0, maxFramerate: 15 },
|
||||
{ rid: 'high', active: true, maxBitrate: 1_000_000, scaleResolutionDownBy: 1.0, maxFramerate: 30 }
|
||||
];
|
||||
sender.setParameters(params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Prefer specific codec in SDP
|
||||
*/
|
||||
preferCodec(sdp, codec) {
|
||||
const codecInfo = this.extractCodecInfo(sdp, codec);
|
||||
if (!codecInfo) return sdp;
|
||||
|
||||
const { payload, rtpmap } = codecInfo;
|
||||
const lines = sdp.split('\r\n');
|
||||
const mLineIndex = lines.findIndex(line => line.startsWith('m=video'));
|
||||
|
||||
if (mLineIndex === -1) return sdp;
|
||||
|
||||
// Изменяем порядок кодеков в m-line
|
||||
const mLineParts = lines[mLineIndex].split(' ');
|
||||
const payloads = mLineParts.slice(3);
|
||||
const newPayloads = [payload, ...payloads.filter(p => p !== payload)];
|
||||
lines[mLineIndex] = [...mLineParts.slice(0, 3), ...newPayloads].join(' ');
|
||||
|
||||
return lines.join('\r\n');
|
||||
},
|
||||
|
||||
/**
|
||||
* Extract codec information from SDP
|
||||
*/
|
||||
extractCodecInfo(sdp, codecName) {
|
||||
const lines = sdp.split('\r\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i].includes(`a=rtpmap:`) && lines[i].toLowerCase().includes(codecName.toLowerCase())) {
|
||||
const payload = lines[i].split(' ')[0].split(':')[1];
|
||||
return { payload, rtpmap: lines[i] };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Test WebRTC support
|
||||
*/
|
||||
@@ -32,7 +269,7 @@ export const webrtcService = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Get WebRTC statistics
|
||||
* Get WebRTC statistics (оптимизировано)
|
||||
*/
|
||||
async getConnectionStats(peerConnection) {
|
||||
if (!peerConnection) return null
|
||||
@@ -48,27 +285,27 @@ export const webrtcService = {
|
||||
stats.forEach((report) => {
|
||||
if (report.type === 'inbound-rtp' && report.mediaType === 'video') {
|
||||
result.video.inbound = {
|
||||
bytesReceived: report.bytesReceived,
|
||||
packetsReceived: report.packetsReceived,
|
||||
packetsLost: report.packetsLost,
|
||||
frameWidth: report.frameWidth,
|
||||
frameHeight: report.frameHeight,
|
||||
framesPerSecond: report.framesPerSecond,
|
||||
bytesReceived: report.bytesReceived || 0,
|
||||
packetsReceived: report.packetsReceived || 0,
|
||||
packetsLost: report.packetsLost || 0,
|
||||
frameWidth: report.frameWidth || 0,
|
||||
frameHeight: report.frameHeight || 0,
|
||||
framesPerSecond: report.framesPerSecond || 0,
|
||||
}
|
||||
} else if (report.type === 'outbound-rtp' && report.mediaType === 'video') {
|
||||
result.video.outbound = {
|
||||
bytesSent: report.bytesSent,
|
||||
packetsSent: report.packetsSent,
|
||||
frameWidth: report.frameWidth,
|
||||
frameHeight: report.frameHeight,
|
||||
framesPerSecond: report.framesPerSecond,
|
||||
bytesSent: report.bytesSent || 0,
|
||||
packetsSent: report.packetsSent || 0,
|
||||
frameWidth: report.frameWidth || 0,
|
||||
frameHeight: report.frameHeight || 0,
|
||||
framesPerSecond: report.framesPerSecond || 0,
|
||||
}
|
||||
} else if (report.type === 'candidate-pair' && report.state === 'succeeded') {
|
||||
result.connection = {
|
||||
currentRoundTripTime: report.currentRoundTripTime,
|
||||
availableOutgoingBitrate: report.availableOutgoingBitrate,
|
||||
bytesReceived: report.bytesReceived,
|
||||
bytesSent: report.bytesSent,
|
||||
currentRoundTripTime: report.currentRoundTripTime || 0,
|
||||
availableOutgoingBitrate: report.availableOutgoingBitrate || 0,
|
||||
bytesReceived: report.bytesReceived || 0,
|
||||
bytesSent: report.bytesSent || 0,
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -81,9 +318,9 @@ export const webrtcService = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Monitor connection quality
|
||||
* Monitor connection quality (увеличен интервал)
|
||||
*/
|
||||
createQualityMonitor(peerConnection, callback, interval = 5000) {
|
||||
createQualityMonitor(peerConnection, callback, interval = 10000) {
|
||||
if (!peerConnection || typeof callback !== 'function') {
|
||||
return null
|
||||
}
|
||||
@@ -111,8 +348,8 @@ export const webrtcService = {
|
||||
|
||||
// Reduce score based on packet loss
|
||||
if (stats.video.inbound?.packetsLost && stats.video.inbound?.packetsReceived) {
|
||||
const lossRate = stats.video.inbound.packetsLost / stats.video.inbound.packetsReceived
|
||||
score -= lossRate * 50 // Up to 50 points for packet loss
|
||||
const lossRate = stats.video.inbound.packetsLost / Math.max(1, stats.video.inbound.packetsReceived)
|
||||
score -= Math.min(50, lossRate * 100) // Up to 50 points for packet loss
|
||||
}
|
||||
|
||||
// Reduce score based on round trip time
|
||||
@@ -126,11 +363,40 @@ export const webrtcService = {
|
||||
// Reduce score based on low frame rate
|
||||
if (stats.video.inbound?.framesPerSecond) {
|
||||
const fps = stats.video.inbound.framesPerSecond
|
||||
if (fps < 15) {
|
||||
score -= (15 - fps) * 2 // Up to 30 points for low FPS
|
||||
if (fps < 10) {
|
||||
score -= (10 - fps) * 3 // Up to 30 points for very low FPS
|
||||
} else if (fps < 15) {
|
||||
score -= (15 - fps) * 2
|
||||
}
|
||||
}
|
||||
|
||||
return Math.max(0, Math.min(100, Math.round(score)))
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Adaptive quality control based on network conditions
|
||||
*/
|
||||
async adaptQuality(peerConnection, qualityCallback) {
|
||||
if (!peerConnection) return;
|
||||
|
||||
const senders = peerConnection.getSenders();
|
||||
const videoSender = senders.find(sender => sender.track?.kind === 'video');
|
||||
|
||||
if (!videoSender) return;
|
||||
|
||||
const stats = await this.getConnectionStats(peerConnection);
|
||||
if (!stats) return;
|
||||
|
||||
const quality = this.calculateQuality(stats);
|
||||
qualityCallback(quality);
|
||||
|
||||
// Адаптивное управление качеством
|
||||
if (quality < 30) {
|
||||
this.optimizeVideoSender(videoSender, 'low');
|
||||
} else if (quality < 60) {
|
||||
this.optimizeVideoSender(videoSender, 'medium');
|
||||
} else {
|
||||
this.optimizeVideoSender(videoSender, 'high');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user