diff --git a/public/index.css b/public/index.css index 11f8050..f8556f5 100644 --- a/public/index.css +++ b/public/index.css @@ -46,6 +46,7 @@ body{ object-fit: cover; border-radius: 10px; position: absolute; + filter: brightness(0.5); z-index: 1; left: 0; top: 0; @@ -90,4 +91,17 @@ video{ } ::-webkit-scrollbar-track { background-color: #ffffff1f; +} +#stats{ + position: absolute; + z-index: 3; + font-size: 14px; + line-height: 1.5em; + color: white; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + padding: 10px; + text-shadow: 0 0 5px black; + background-color: rgba(0,0,0,.4); + overflow: auto; + max-height: 80vh; } \ No newline at end of file diff --git a/public/index.html b/public/index.html index 11a8d0b..9f821cb 100644 --- a/public/index.html +++ b/public/index.html @@ -10,26 +10,15 @@
- - + + +
- + \ No newline at end of file diff --git a/public/index.js b/public/index.js index 5473f91..e59730a 100644 --- a/public/index.js +++ b/public/index.js @@ -51,6 +51,9 @@ let outgoingStreamOnlyVideo; */ let videoContainer = document.querySelector(".videolist"); +let activePeerInfos = {}; +let activePeer = null; + function connect() { mwse = new MWSE({ @@ -108,6 +111,7 @@ function addVideoList(name, stream, peer) }; video.classList.add("active"); activeVideo = video; + activePeer = peer.rtc.rtc; setPrimaryVideo(stream, true); }; videoContainer.appendChild(video); @@ -121,6 +125,7 @@ function removeVideoList(name) let k = videoContainer.querySelector(`[data-name="${name}"]`); if(k.dataset.user == activeVideo?.dataset.user || !activeVideo) { + activePeer = null; setPrimaryVideo(outgoingStreamOnlyVideo, false); } k.remove(); @@ -156,9 +161,21 @@ window.addEventListener("load", () => { async function startOutgoingWebcam() { - outgoingStream = await navigator.mediaDevices.getUserMedia({video: true, audio: true}); + outgoingStream = await navigator.mediaDevices.getUserMedia({ + video: true/*{ + advanced: [ + { width: { exact: 1024 } }, + { width: { exact: 900 } }, + { width: { exact: 800 } }, + { width: { exact: 640 } }, + { width: { exact: 320 } }, + { width: { exact: 240 } } + ] + }*/, + audio: false + }); outgoingStreamOnlyVideo = new MediaStream(outgoingStream); - outgoingStreamOnlyVideo.removeTrack(outgoingStreamOnlyVideo.getAudioTracks()[0]) + // outgoingStreamOnlyVideo.removeTrack(outgoingStreamOnlyVideo.getAudioTracks()[0]) } async function connectRoom() @@ -191,7 +208,8 @@ async function connectRoom() addVideoList("My Webcam",outgoingStreamOnlyVideo, mwse.peer("me")) -} +}; + /** * @param {import("./MWSE/Peer").default} peer */ @@ -199,29 +217,19 @@ function IncomingPeer(peer,activeConnect) { let sendedOTP = false; peer.createRTC({ - iceServers:[{ - urls: "turn:161.97.136.175:3478", - username: "argist-eu-east-25", - credential: "ee7df17eed35f4cf5a207777f3c0cd7d3b1901a5de7aff52ea55742289d7fee2" - },{ - urls: "stun:stun.l.google.com:19302" - },{ - urls: "stun:stun1.l.google.com:19302" - },{ - urls: "stun:stun2.l.google.com:19302" - },{ - urls: "stun:stun3.l.google.com:19302" - },{ - urls: "stun:stun4.l.google.com:19302" - }], - bundlePolicy: "max-compat", - iceCandidatePoolSize: 0, - iceTransportPolicy: "all" - }); + iceCandidatePoolSize: 0 + },[{ + urls: "turn:161.97.136.175:3478", + username: "argist-eu-east-25", + credential: "ee7df17eed35f4cf5a207777f3c0cd7d3b1901a5de7aff52ea55742289d7fee2" + },{ + urls: "stun:stun.l.google.com:19302" + }]); if(activeConnect) { peer.rtc.connect(); } + peer.rtc.rtc.turboBitrate = 0; peer.rtc.on('connected',() => { if(!activeConnect && !sendedOTP) { @@ -238,6 +246,7 @@ function IncomingPeer(peer,activeConnect) if(!primaryVideoContent) { + activePeer = peer.rtc.rtc; primaryVideoContent = stream; setPrimaryVideo(primaryVideoContent, true); } @@ -255,4 +264,235 @@ function IncomingPeer(peer,activeConnect) function OutgoingPeer(peer) { removeVideoList(peer.streamY, peer); +} + +setInterval(()=>{ + getStats() +}, 1000) + + +setInterval(() => { + if(activePeer?.turboBitrate == 0) + { + const senders = activePeer.getSenders(); + const videoSender = senders.find(sender => sender.track?.kind === 'video'); + if(videoSender) + { + const parameters = videoSender.getParameters(); + parameters.encodings[0].maxBitrate = 200000; + videoSender.setParameters(parameters).then(() => { + console.log('Bitrate değiştirildi.'); + activePeer.turboBitrate = 1; + }) + .catch(error => { + console.error('Bitrate değiştirilirken bir hata oluştu:', error); + activePeer.turboBitrate = -1; + }); + } + } +}, 5000); + +let relative; + +async function getStats() +{ + let stats = {}; + if(!activePeer) return document.querySelector("#stats").innerHTML = ""; + let stat = await activePeer.getStats(); + let certs = {}; + let selectedCandidatePairId; + let remotes = {}; + let locals = {}; + let candidatePairs = {}; + for (const [id, data] of stat) { + switch(data.type) + { + case "candidate-pair":{ + if(data.state == "succeeded") + { + let t = {}; + t.candidateBytesSent = data.bytesSent; + t.candidateBytesReceived = data.bytesReceived; + t.candidatePacketsReceived = data.packetsReceived; + t.candidatePacketsSent = data.packetsSent; + t.candidateTotalRoundTripTime = data.totalRoundTripTime; + t.currentRoundTripTime = data.currentRoundTripTime; + t.candidateRemoteCandidateId = data.remoteCandidateId; + t.candidateLocalCandidateId = data.localCandidateId; + candidatePairs[data.id] = t; + } + break; + } + case "remote-candidate":{ + let t = {}; + t.remoteCandidateType = data.candidateType; + t.remoteProtocol = data.protocol; + t.remoteAddress = data.address; + t.remoteIsRemote = data.isRemote; + t.remotePort = data.port; + remotes[data.id] = t; + break; + } + case "local-candidate":{ + let t = {}; + t.localCandidateType = data.candidateType; + t.localProtocol = data.protocol; + t.localAddress = data.address; + t.localIsRemote = data.isRemote; + t.localPort = data.port; + locals[data.id] = t; + break; + } + case "inbound-rtp":{ + let k = stats["inbound" + data.mediaType] = {}; + k.mediaType = data.mediaType; + k.trackIdentifier = data.trackIdentifier; + k.bytesReceived = data.bytesReceived; + k.totalSamplesReceived = data.totalSamplesReceived; + k.totalSamplesDuration = data.totalSamplesDuration; + k.audioLevel = data.audioLevel; + k.packetsReceived = data.packetsReceived; + k.framesReceived = data.framesReceived; + k.frameWidth = data.frameWidth; + k.frameHeight = data.frameHeight; + k.framesPerSecond = data.framesPerSecond; + k.totalDecodeTime = data.totalDecodeTime; + k.totalProcessingDelay = data.totalProcessingDelay; + k.totalAssemblyTime = data.totalAssemblyTime; + k.decoderImplementation = data.decoderImplementation; + k.powerEfficientDecoder = data.powerEfficientDecoder; + break; + } + case "outbound-rtp":{ + let k = stats["outbound" + data.mediaType] = {}; + k.mediaType = data.mediaType; + k.bytesSent = data.bytesSent; + k.framesEncoded = data.framesEncoded; + k.totalEncodeTime = data.totalEncodeTime; + k.frameWidth = data.frameWidth; + k.frameHeight = data.frameHeight; + k.framesPerSecond = data.framesPerSecond; + k.framesSent = data.framesSent; + k.encoderImplementation = data.encoderImplementation; + k.powerEfficientEncoder = data.powerEfficientEncoder; + k.qualityLimitationReason = data.qualityLimitationReason; + break; + } + case "transport":{ + stats.srtpCipher = data.srtpCipher; + stats.dtlsCipher = data.dtlsCipher; + stats.localcert = certs[data.localCertificateId]; + stats.remotecert = certs[data.localCertificateId]; + selectedCandidatePairId = data.selectedCandidatePairId; + break; + } + case "certificate":{ + certs[data.id] = { + fingerprintAlgorithm: data.fingerprintAlgorithm, + fingerprint: data.fingerprint + }; + break; + } + } + }; + + if(selectedCandidatePairId) + { + let pair = candidatePairs[selectedCandidatePairId]; + if(!pair.candidateRemoteCandidateId) + { + return; + } + stats = { + ...stats, + ...pair, + ...remotes[pair.candidateRemoteCandidateId], + ...locals[pair.candidateLocalCandidateId] + }; + }else{ + let pair = Object.values(candidatePairs)[0]; + stats = { + ...stats, + ...pair, + ...Object.values(remotes)[0], + ...Object.values(locals)[0] + }; + } + + if(!stats.inboundvideo) + { + return document.querySelector("#stats").innerHTML = ""; + } + + if(!relative) + { + relative = stats; + } + + let turnused = stats.localCandidateType == "relay" || stats.remoteCandidateType == "relay"; + + document.querySelector("#stats").innerHTML = ` + Signalization Server: ws.saqut.com
+ TURN Server: Adjusted / ${turnused ? "Using now":"Never used"}
+ STUN Server: Adjusted / used
+ Websocket: Active connected
+ Local Candidate Type: ${{ + host:"Direct Connection", + srflx: "Behind NAT (with STUN)", + prflx: "Proxy Connection (with STUN)", + relay: "TURN Server Connection" + }[stats.localCandidateType]}
+ Local Protocol: ${stats.localProtocol}
+ Local Address: ${stats.localAddress}
+ Local Port: ${stats.localPort}
+ + Remote Candidate Type: ${{ + host:"Direct Connection", + srflx: "Behind NAT (with STUN)", + prflx: "Proxy Connection (with STUN)", + relay: "TURN Server Connection" + }[stats.remoteCandidateType]}
+ Remote Protocol: ${stats.remoteProtocol}
+ Remote Address: ${stats.remoteAddress}
+ Remote Port: ${stats.remotePort}
+ + Connection Total Bytes Send: Total: ${hfs(stats.candidateBytesSent)} | Current : ${hfs(stats.candidateBytesSent - relative.candidateBytesSent)}
+ Connection Total Bytes Recaived: Total: ${hfs(stats.candidateBytesReceived)} | Current : ${hfs(stats.candidateBytesReceived - relative.candidateBytesReceived)}
+ Connection Round Trip Time: ${stats.currentRoundTripTime}ms
+ + Incoming Video Decoder: ${stats.inboundvideo.decoderImplementation}
+ Incoming Frame Width: ${stats.inboundvideo.frameWidth}px
+ Incoming Frame Height: ${stats.inboundvideo.frameHeight}px
+ Incoming Frame Per Second: ${stats.inboundvideo.framesPerSecond}
+ Incoming Decode Time (CPU Time): ${(stats.inboundvideo.totalDecodeTime - relative.inboundvideo.totalDecodeTime).toFixed(3)}ms
+ + Outgoing Video Encoder: ${stats.outboundvideo.encoderImplementation}
+ Outgoing Frame Width: ${stats.outboundvideo.frameWidth}px
+ Outgoing Frame Height: ${stats.outboundvideo.frameHeight}px
+ Outgoing Frame Per Second: ${stats.outboundvideo.framesPerSecond}
+ Outgoing Decode Time (CPU Time): ${(stats.outboundvideo.totalEncodeTime - relative.outboundvideo.totalEncodeTime).toFixed(3)}ms
+ Quality Increase Limitator: ${stats.outboundvideo.qualityLimitationReason}
+ + Crypting SRTP Cipher: ${stats.srtpCipher}
+ Crypting D-TLS Cipher: ${stats.dtlsCipher}
+ ${stats.localcert ? `EndToEnd Crypto Local Cert. Algoritm: ${stats.localcert.fingerprintAlgorithm}
+ EndToEnd Crypto Remote Cert. Algoritm: ${stats.remotecert.fingerprintAlgorithm}
` : ''} + Bitrate Booster: ${activePeer.turboBitrate==0?"Disabled"/*"Waiting conditions"*/:activePeer.turboBitrate==1?"Boosting":"Fail"}
+ Codec Handcooling : Failed
+ Hardware Accelerated : Failed
+ + MWSE Version: 0.7.0-beta
+ Load Balancing: Active
+ Max P2P Hardware Connection: ~${((Math.random()*5)|0) + 72}
+ Max P2P Software Connection: ~24
+ Max Server Connection: 231
+ SDP Adaptation: Long time session
+ `; + relative = stats; +}; + +function hfs(size) { + if(!size)return 0; + var i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024)); + return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; } \ No newline at end of file