401 lines
12 KiB
JavaScript
401 lines
12 KiB
JavaScript
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<string, {stream:MediaStream?,id:string,name:string}>}
|
||
*/
|
||
this.recaivingStream = new Map();
|
||
/**
|
||
* @type {Map<string, {stream:MediaStream?,id:string,name:string}>}
|
||
*/
|
||
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
|
||
})
|
||
} |