function WebRTC() { this.id = null; this.active = false; this.connectionStatus = ""; this.iceStatus = ""; this.gatheringStatus = ""; this.signalingStatus = ""; this.rtc = new RTCPeerConnection({ iceCandidatePoolSize: 0, iceTransportPolicy:"all", rtcpMuxPolicy:"require", iceServers:[{ 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" }] }); /** * @type {Map} */ this.recaivingStream = new Map(); /** * @type {Map} */ this.sendingStream = new Map(); this.rtc.addEventListener("connectionstatechange",(...args)=>{ this.eventConnectionState(...args); }) this.rtc.addEventListener("icecandidate",(...args)=>{ this.eventIcecandidate(...args); }) this.rtc.addEventListener("iceconnectionstatechange",(...args)=>{ this.eventICEConnectionState(...args); }) this.rtc.addEventListener("icegatheringstatechange",(...args)=>{ this.eventICEGatherinState(...args); }) this.rtc.addEventListener("negotiationneeded",(...args)=>{ this.eventNogationNeeded(...args); }) this.rtc.addEventListener("signalingstatechange",(...args)=>{ this.eventSignalingState(...args); }) this.rtc.addEventListener("track",(...args)=>{ this.eventTrack(...args); }) this.rtc.addEventListener("datachannel",(...args)=>{ this.eventDatachannel(...args); }) let events = {}; /** * @param {Function} callback */ this.addEventListener = function(event,callback){ (events[event] || (events[event]=[])).push(callback); }; this.on = this.addEventListener; this.dispatch = async (event,...args) => { if(events[event]) for (const callback of events[event]) { await callback(...args) } } this.emit = this.dispatch; /** * @type {RTCDataChannel} */ this.channel = null; this.on('input',async (data)=>{ switch(data.type) { case "icecandidate":{ await this.rtc.addIceCandidate(new RTCIceCandidate(data.value)); break; } case "offer":{ await this.rtc.setRemoteDescription(new RTCSessionDescription(data.value)); let answer = await this.rtc.createAnswer({ offerToReceiveAudio: true, offerToReceiveVideo: true }) await this.rtc.setLocalDescription(answer); this.send({ type: 'answer', value: answer }); break; } case "answer":{ await this.rtc.setRemoteDescription(new RTCSessionDescription(data.value)) break; } case "streamInfo":{ let {id,value} = data; if(!this.recaivingStream.has(id)) { this.recaivingStream.set(id,{ stream: null }); }; Object.assign(this.recaivingStream.get(id), value); this.send({ type:'streamAccept', id }) break; } case "streamRemoved":{ let {id} = data; this.emit('stream:stopped', this.recaivingStream.get(id)); this.sendingStream.delete(id); break; } case "streamAccept":{ let {id} = data; let {stream} = this.sendingStream.get(id); let senders = []; for (const track of stream.getTracks()) { senders.push(this.rtc.addTrack(track, stream)); }; stream.senders = senders; break; } case "message":{ this.emit('message', data.payload); break; } } }) }; WebRTC.channels = new Map(); WebRTC.prototype.connect = function(object){ if(!this.channel) { this.createDefaultDataChannel(); } }; WebRTC.prototype.sendMessage = function(object){ this.send({ type:'message', payload: object }); }; WebRTC.prototype.createDefaultDataChannel = function(){ let dt = this.rtc.createDataChannel(":default:",{ ordered: true }); dt.addEventListener("open",()=>{ this.channel = dt; console.log(...rtcLabel, this.id, dt.label + ' veri kanalı açıldı'); WebRTC.channels.set(this.id, this); }); dt.addEventListener("message",({data})=>{ let pack = JSON.parse(data); console.log(...rtcLabel, this.id, dt.label + ' P2P Pack ', pack); this.emit('input', pack); }) dt.addEventListener("close",()=>{ this.channel = null; console.log(...rtcLabel, this.id, dt.label + ' veri kanalı kapandı'); }) }; WebRTC.prototype.destroy = function(){ this.active = false; if(this.channel) { this.channel.close(); this.channel = null; } if(this.rtc) { this.rtc.close(); this.rtc = null; }; this.emit('disconnected'); WebRTC.channels.delete(this.id); } /** * * @param {RTCDataChannelEvent} event */ WebRTC.prototype.eventDatachannel = function(event){ console.log(...rtcLabel, this.id, event.channel.label + ' veri kanalı açıldı'); if(event.channel.label == ':default:'){ WebRTC.channels.set(this.id, this); this.channel = event.channel } event.channel.addEventListener("message",({data})=>{ let pack = JSON.parse(data); console.log(...rtcLabel, this.id, event.channel.label + ' P2P Pack ', pack); this.emit('input', pack); }) event.channel.addEventListener("close",()=>{ this.channel = null; WebRTC.channels.delete(this.id); WebRTC.requireGC = true; console.log(...rtcLabel, this.id, event.channel.label + ' veri kanalı kapandı'); }) }; WebRTC.requireGC = false; setInterval(()=>{ if(WebRTC.requireGC == false) return; let img = document.createElement("img"); img.src = window.URL.createObjectURL(new Blob([new ArrayBuffer(5e+7)])); img.onerror = function() { window.URL.revokeObjectURL(this.src); img = null; console.log("WebRTC Pool connections garbage connection microtask completed") }; WebRTC.requireGC = false; }, 3000) WebRTC.prototype.send = function(object){ if(this.channel?.readyState == "open") { this.channel.send(JSON.stringify(object)); }else{ this.emit('output', object); } }; let rtcLabel = ["%cRTC","color:red;font-weight:bold"]; WebRTC.prototype.eventConnectionState = function(){ this.connectionStatus = this.rtc.connectionState; if(this.connectionStatus == 'connected') { if(this.active == false) { this.emit('connected'); this.active = true; } }; if(this.connectionStatus == 'failed' || this.connectionStatus == "disconnected" || this.connectionStatus == "closed") { if(this.active) { this.destroy(); } } console.log(...rtcLabel, this.id, "connectionStatus", this.connectionStatus) }; /** * * @param {RTCPeerConnectionIceEvent} event */ WebRTC.prototype.eventIcecandidate = function(event){ console.log(...rtcLabel, this.id, 'ice created'); if(event.candidate) { this.send({ type:'icecandidate', value: event.candidate }) } }; WebRTC.prototype.eventICEConnectionState = function(){ this.iceStatus = this.rtc.iceConnectionState; console.log(...rtcLabel, this.id, "iceStatus",this.iceStatus) }; WebRTC.prototype.eventICEGatherinState = function(){ this.gatheringStatus = this.rtc.iceGatheringState; console.log(...rtcLabel, this.id, "gatheringStatus",this.gatheringStatus) }; WebRTC.prototype.eventNogationNeeded = async function(){ console.log(...rtcLabel, this.id, "requested nogation"); let offer = await this.rtc.createOffer({ iceRestart: true, offerToReceiveAudio: true, offerToReceiveVideo: true }); await this.rtc.setLocalDescription(offer); this.send({ type: 'offer', value: offer }); }; WebRTC.prototype.eventSignalingState = function(){ this.signalingStatus = this.rtc.signalingState; console.log(...rtcLabel, this.id, "signalingStatus",this.signalingStatus) }; /** * @param {RTCTrackEvent} event */ WebRTC.prototype.eventTrack = function(event){ if(event.streams.length) { for (const stream of event.streams) { if(this.recaivingStream.get(stream.id).stream == null) { this.recaivingStream.get(stream.id).stream = stream; this.emit('stream:added', this.recaivingStream.get(stream.id)); }else{ this.recaivingStream.get(stream.id).stream = stream; } } } }; /** * @param {MediaStream} stream * @param {string} name * @param {any} info */ WebRTC.prototype.sendStream = function(stream,name,info){ this.send({ type: 'streamInfo', id: stream.id, value: { ...info, name: name } }); this.sendingStream.set(stream.id,{ ...info, name: name, stream }); }; /** * @param {MediaStream} stream * @param {string} name * @param {any} info */ WebRTC.prototype.stopStream = function(_stream){ if(this.connectionStatus != 'connected'){ return } if(this.sendingStream.has(_stream.id)) { let {stream} = this.sendingStream.get(_stream.id); for (const track of stream.getTracks()) { for (const RTCPSender of this.rtc.getSenders()) { if(RTCPSender.track?.id == track.id) { this.rtc.removeTrack(RTCPSender); } } } this.send({ type: 'streamRemoved', id: stream.id }); this.sendingStream.delete(_stream.id) } }; /** * @param {string} name * @param {any} info */ WebRTC.prototype.stopAllStreams = function(){ if(this.connectionStatus != 'connected'){ return } for (const [id, {stream}] of this.sendingStream) { for (const track of stream.getTracks()) { for (const RTCPSender of this.rtc.getSenders()) { if(RTCPSender.track?.id == track.id) { this.rtc.removeTrack(RTCPSender); } } } this.send({ type: 'streamRemoved', id: stream.id }); }; this.sendingStream.clear(); }; WebRTC.getCamera = async (options) => { return navigator.mediaDevices.getUserMedia({ video: options || { frameRate: 10, width: 640, height: 480 } }) } WebRTC.getMicrophone = async (options) => { return navigator.mediaDevices.getUserMedia({ audio: options || { channelCount: 1, sampleRate: 16000, sampleSize: 16, volume: 1 } }) } WebRTC.getDisplay = async (videoOptions) => { return navigator.mediaDevices.getDisplayMedia({ video: videoOptions || true }) }