WebRTC and WebSocket merged

This commit is contained in:
Abdussamed 2023-04-18 23:19:53 +03:00
parent d3142c3ee4
commit 7dd4797f00
7 changed files with 563 additions and 17 deletions

View File

@ -1,34 +1,79 @@
## Kurulum
Proje ortamına kurulumu
### Proje ortamına kurulumu
```html
<script src="https://ws.saqut.com/script"></script>
```
Geliştirme ortamına kurulumu
### Geliştirme ortamına kurulumu
```javascript
const wsjs = new MWSE({
endpoint: "https://ws.saqut.com/" // Bağlanılacak sunucuyu belirtir
endpoint: "https://ws.saqut.com/" // MSWS kurulu sunucu adresi
});
wsjs.scope(async () => {
// Bağlantı sağlandığında burası tetiklenir
})
```
Kendi bağlantı kimliğini öğrenme
### Kendi bağlantı kimliğini öğrenme
```
```javascript
wsjs.scope(async () => {
let me = wsjs.peer('me'); // kendimizden bahsederken 'me' anahtar kelimesini kullanırız
console.log(me.socketId); // her peerin socketId'si olması gerekir
let me = wsjs.peer('me'); // Kendi bağlantınız üzerinde işlem yaparken `me` olarak bahsedersiniz
console.log(me.socketId); // Her eşin tekil bir socketIdsi vardır
})
```
### Sanal Adres ayırma / yeniden ayırma / kaldırma
```javascript
wsjs.scope(async () => {
let me = wsjs.peer('me');
/**
* Sanal adresler size veri gönderilmek istendiğinde veya etkileşime
* geçilmesi istendiğinde ona socketId gibi bir UUID yerine sizi temsil eden daha kısa
* ip adresi, sayı veya kısa bir kod ile aynı şeyleri yapmanıza olanak tanır.
* Aynı anda hem sanal ip adres, sayı ve kısa koduna sahip olabilirsiniz
* ancak aynı türden temsil koduna (mesela kısa koddan) birden fazla sahip olamazsınız
* Yeni bir bağlantı daha açmanız gerekir
**/
// Bağlantınıze özel sanal tekil ip adresi kaynağı ayırın
let ipadress = await me.virtualPressure.allocAPIPAddress();
// Bağlantınıze özel sanal tekil numara kaynağı ayırın
let numberaddress = await me.virtualPressure.allocAPNumber();
// Bağlantınıze özel sanal kod kaynağı ayırın
let shortcodeadress = await me.virtualPressure.allocAPShortCode();
// Bütün bu kaynakları yenileriyle değiştirmek için
// her birinin ayrı ayrı yeniden alma işlevleri vardır
// Bir adresi yenilediğinizde artık eski adres kullanılmaz olur
me.virtualPressure.reallocAPIPAddress();
me.virtualPressure.reallocAPNumber();
me.virtualPressure.reallocAPShortCode();
// Bütün bu kaynakları kaldırmak için her birinin ayrı ayrı
// bırakma işlevi vardır
// Bir adresi kullanmadığınızda artık bu adreslerden size
// ulaşılamaz olursunuz
await me.virtualPressure.releaseAPIPAddress();
await me.virtualPressure.releaseAPNumber();
await me.virtualPressure.releaseAPShortCode();
await me.virtualPressure.queryAPIPAddress();
await me.virtualPressure.queryAPNumber();
await me.virtualPressure.queryAPShortCode();
})
```
Farklı bir eş ile iletişime geçme
```
```javascript
wsjs.scope(async () => {
let peer = wsjs.peer('325a8f7f-eaaf-4c21-855e-9e965c0d5ac9') // Diğer eşin socketId'sini belirtiyoruz

View File

@ -1,5 +1,6 @@
import EventTarget from "./EventTarget";
import { PeerInfo } from "./PeerInfo";
import WebRTC from "./WebRTC";
import MWSE from "./index";
interface IPeerOptions{
@ -15,12 +16,44 @@ export default class Peer extends EventTarget
public selfSocket : boolean = false;
public active : boolean = false;
public info : PeerInfo;
public rtc? : WebRTC;
public peerConnection : boolean = false;
public primaryChannel : "websocket" | "datachannel" = "datachannel";
constructor(wsts:MWSE){
super();
this.mwse = wsts;
this.info = new PeerInfo(this);
}
setPeerOptions(options: string | IPeerOptions){
public createRTC() : WebRTC
{
this.rtc = new WebRTC();
this.rtc.on("connected", () => {
this.peerConnection = true;
});
this.rtc.on('disconnected', () => {
this.peerConnection = false;
})
this.rtc.on("output",(payload:object) => {
this.send({
type: 'rtc:session',
payload: payload
})
});
this.on('message',(data:{type?:string,payload?:any}) => {
if(data.type == 'rtc:session')
{
if(this.rtc)
{
this.rtc.emit("input", data.payload)
}
}else if(data.type == 'rtc:message')
{
this.emit("message", data.payload)
}
});
return this.rtc;
}
public setPeerOptions(options: string | IPeerOptions){
if(typeof options == "string")
{
this.setSocketId(options)
@ -28,7 +61,7 @@ export default class Peer extends EventTarget
this.options = options;
}
}
setSocketId(uuid: string){
public setSocketId(uuid: string){
this.socketId = uuid;
}
async metadata() : Promise<any>
@ -77,11 +110,37 @@ export default class Peer extends EventTarget
});
}
async send(pack: any){
await this.mwse.EventPooling.request({
type:'pack/to',
pack,
to: this.socketId
});
let isOpenedP2P = this.peerConnection;
let isOpenedServer = this.mwse.server.connected;
let sendChannel : "websocket" | "datachannel";
if(isOpenedP2P && isOpenedServer)
{
if(this.primaryChannel == "websocket")
{
sendChannel = "websocket"
}else
{
sendChannel = "datachannel"
}
}else if(isOpenedServer){
sendChannel = "websocket"
}else{
sendChannel = "datachannel"
}
if(sendChannel == "websocket")
{
await this.mwse.EventPooling.request({
type:'pack/to',
pack,
to: this.socketId
});
}else{
this.rtc?.send({
type: 'rtc:message',
payload: pack
})
}
}
async forget(){
this.mwse.peers.delete(this.socketId as string);

378
frontend/WebRTC.ts Normal file
View File

@ -0,0 +1,378 @@
interface TransferStreamInfo
{
senders : RTCRtpSender[];
stream:MediaStream | undefined;
id:string;
name:string;
}
export default class WebRTC
{
public static channels : Map<any,any> = new Map();
public static requireGC : boolean = false;
public id : any;
public active : boolean = false;
public connectionStatus : "closed" | "connected" | "connecting" | "disconnected" | "failed" | "new" = "new";
public iceStatus : "checking" | "closed" | "completed" | "connected" | "disconnected" | "failed" | "new" = "new";
public gatheringStatus : "complete" | "gathering" | "new" = "new";
public signalingStatus : "" | "closed" | "have-local-offer" | "have-local-pranswer" | "have-remote-offer" | "have-remote-pranswer" | "stable" = ""
public rtc! : RTCPeerConnection;
public recaivingStream : Map<string, TransferStreamInfo> = new Map();
public sendingStream : Map<string, TransferStreamInfo> = new Map();
public events : { [eventname:string]: Function[] } = {};
public channel : RTCDataChannel | undefined;
public WebRTC()
{
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"
}]
});
this.rtc.addEventListener("connectionstatechange",()=>{
this.eventConnectionState();
})
this.rtc.addEventListener("icecandidate",(...args)=>{
this.eventIcecandidate(...args);
})
this.rtc.addEventListener("iceconnectionstatechange",()=>{
this.eventICEConnectionState();
})
this.rtc.addEventListener("icegatheringstatechange",()=>{
this.eventICEGatherinState();
})
this.rtc.addEventListener("negotiationneeded",()=>{
this.eventNogationNeeded();
})
this.rtc.addEventListener("signalingstatechange",()=>{
this.eventSignalingState();
})
this.rtc.addEventListener("track",(...args)=>{
this.eventTrack(...args);
})
this.rtc.addEventListener("datachannel",(...args)=>{
this.eventDatachannel(...args);
})
this.on('input',async (data:{[key:string]:any})=>{
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;
let streamInfo = this.recaivingStream.get(id);
if(!streamInfo)
{
this.recaivingStream.set(id,value as TransferStreamInfo);
}else{
this.recaivingStream.set(id,{
...streamInfo,
...value
} as TransferStreamInfo);
}
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) as {stream:MediaStream};
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;
}
}
})
}
public addEventListener(event:string,callback: Function){
(this.events[event] || (this.events[event]=[])).push(callback);
};
public on(event:string,callback: Function){
this.addEventListener(event, callback)
};
public async dispatch(event:string,...args:any[]) : Promise<any> {
if(this.events[event])
{
for (const callback of this.events[event])
{
await callback(...args)
}
}
}
public async emit(event:string,...args:any[]) : Promise<any> {
await this.dispatch(event, ...args)
}
public connect()
{
if(!this.channel)
{
this.createDefaultDataChannel();
}
}
public sendMessage(data: any)
{
this.send({
type: 'message',
payload: data
});
}
public createDefaultDataChannel()
{
let dt = this.rtc.createDataChannel(':default:',{
ordered: true
});
dt.addEventListener("open",()=>{
this.channel = dt;
WebRTC.channels.set(this.id, this);
});
dt.addEventListener("message",({data})=>{
let pack = JSON.parse(data);
this.emit('input', pack);
})
dt.addEventListener("close",()=>{
this.channel = undefined;
})
}
public destroy()
{
this.active = false;
if(this.channel)
{
this.channel.close();
this.channel = undefined;
}
if(this.rtc)
{
this.rtc.close();
// this.rtc = undefined;
};
this.emit('disconnected');
WebRTC.channels.delete(this.id);
}
public eventDatachannel(event: RTCDataChannelEvent)
{
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);
this.emit('input', pack);
})
event.channel.addEventListener("close",()=>{
this.channel = undefined;
WebRTC.channels.delete(this.id);
WebRTC.requireGC = true;
})
}
public send(data:object)
{
if(this.channel?.readyState == "open")
{
this.channel.send(JSON.stringify(data));
}else{
this.emit('output', data);
}
}
public eventConnectionState()
{
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();
}
}
}
public eventIcecandidate(event: RTCPeerConnectionIceEvent)
{
if(event.candidate)
{
this.send({
type:'icecandidate',
value: event.candidate
})
}
}
public eventICEConnectionState()
{
this.iceStatus = this.rtc.iceConnectionState;
}
public eventICEGatherinState()
{
this.gatheringStatus = this.rtc.iceGatheringState;
}
public async eventNogationNeeded()
{
let offer = await this.rtc.createOffer({
iceRestart: true,
offerToReceiveAudio: true,
offerToReceiveVideo: true
});
await this.rtc.setLocalDescription(offer);
this.send({
type: 'offer',
value: offer
});
}
public eventSignalingState()
{
this.signalingStatus = this.rtc.signalingState;
}
public eventTrack(event: RTCTrackEvent)
{
if(event.streams.length)
{
for (const stream of event.streams) {
if((this.recaivingStream.get(stream.id) as {stream : MediaStream | undefined}).stream == null)
{
(
this.recaivingStream.get(stream.id) as {stream : MediaStream | undefined}
).stream = stream;
this.emit('stream:added', this.recaivingStream.get(stream.id));
}else{
(
this.recaivingStream.get(stream.id) as {stream : MediaStream | undefined}
).stream = stream;
}
}
}
}
public sendStream(stream:MediaStream,name:string,info:{[key:string]:any}){
this.send({
type: 'streamInfo',
id: stream.id,
value: {
...info,
name: name
}
});
this.sendingStream.set(stream.id,{
...info,
id:stream.id,
name: name,
stream
} as TransferStreamInfo);
};
public stopStream(_stream:MediaStream){
if(this.connectionStatus != 'connected'){
return
}
if(this.sendingStream.has(_stream.id))
{
let {stream} = this.sendingStream.get(_stream.id) as {stream:MediaStream};
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)
}
}
public stopAllStreams()
{
if(this.connectionStatus != 'connected'){
return
}
for (const [id, {stream}] of this.sendingStream) {
if(stream == undefined)
{
continue;
}
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.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);
};
WebRTC.requireGC = false;
}, 3000)
declare global {
interface MediaStream {
senders : RTCRtpSender[];
}
}

View File

@ -5,7 +5,9 @@ import { IPPressure } from "./IPPressure";
import Peer from "./Peer";
import Room, { IRoomOptions } from "./Room";
import WSTSProtocol, { Message } from "./WSTSProtocol";
import WebRTC from "./WebRTC";
export default class MWSE extends EventTarget {
public static rtc : WebRTC;
public server! : Connection;
public WSTSProtocol! : WSTSProtocol;
public EventPooling! : EventPool;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

62
test.html Normal file
View File

@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="http://127.0.0.1:7707/script"></script>
<script>
const wsjs = new MWSE({
endpoint: "ws://127.0.0.1:7707"
});
wsjs.scope(async () => {
let me = wsjs.peer('me');
await me.info.set("name","Abdussamed");
await me.info.set("surname","ULUTAŞ");
await me.info.set("age","25");
await me.info.set("date",1);
let t = 2;
setInterval(()=>{
me.info.set("date", t);
t++;
},2000)
let room = wsjs.room({
name: "saqut.com",
description: "saqut.com try",
joinType: "free",
ifexistsJoin: true,
accessType: "private",
notifyActionInvite: false,
notifyActionJoined: true,
notifyActionEjected: true
});
await room.createRoom();
let peers = await room.fetchPeers();
for (const peer of peers) {
await peer.info.fetch();
console.log("Peer info fetched",peer.socketId,peer.info.get());
peer.on('info', (name, value) => {
console.log("Peer info changed", peer.socketId, name, value, peer.info.get());
})
}
room.on('join', async peer => {
await peer.info.fetch();
console.log("Peer info fetched",peer.socketId,peer.info.get());
peer.on('info', (name, value) => {
console.log("Peer info changed", peer.socketId, name, value, peer.info.get());
})
});
});
</script>
</body>
</html>