MWSE/script/webrtc.js

401 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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ıı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ıı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
})
}