dpg.broadcast-webrtc
Version:
Modern Broadcast client service library
227 lines (197 loc) • 7.12 kB
text/typescript
import * as IO from "socket.io-client";
import type {
Socket as SocketType,
SocketOptions,
ManagerOptions,
} from "socket.io-client";
interface SocketData {
readonly socket: SocketType;
readonly routes?: {
[eventName: string]: (...any) => any | Promise<any>;
};
}
interface SocketDataOwner extends SocketData {
children_db?: { [socketId: string]: RTCPeerConnection };
}
interface SocketConfig extends Partial<IO.ManagerOptions & IO.SocketOptions> {
cid: string;
}
class PeerConnectionVisitor implements SocketData {
private peer: RTCPeerConnection;
private answer: RTCSessionDescriptionInit;
readonly routes: SocketData["routes"] = {
"peerconnection/visitor/offer": async (
socketId: SocketType["id"],
offer: RTCSessionDescriptionInit,
) => {
console.log("GET OFFER", {
socketId,
kek: this.socket.id,
bool: socketId == this.socket.id
})
if (socketId == this.socket.id) {
await this.initOffer(offer);
await this.initAnswer();
}
},
};
readonly socket: SocketType;
protected async initOffer(offer: RTCSessionDescriptionInit) {
await this.peer.setRemoteDescription(offer);
this.peer.restartIce();
console.log("[PeerConnection] Status:", {
signal: this.peer.signalingState,
connection: this.peer.connectionState
})
}
protected async initAnswer() {
this.answer = await this.peer.createAnswer();
await this.peer.setLocalDescription(this.answer);
this.socket.emit("peerconnection/visitor/answer", {
answer: this.answer,
});
this.peer.restartIce();
console.log("[PeerConnection] Status:", {
signal: this.peer.signalingState,
connection: this.peer.connectionState
})
}
onTrack(callback: any) {
this.peer.ontrack = ev => {
callback(ev.streams[0])
}
}
constructor(
rtcConfig: RTCConfiguration,
socketConfig: SocketConfig | SocketType,
) {
this.peer = new RTCPeerConnection(rtcConfig);
this.socket = !!(socketConfig as SocketType)?.io
? (socketConfig as SocketType)
: IO.connect(
socketConfig as Partial<
IO.ManagerOptions & IO.SocketOptions
>,
);
Object.keys(this.routes).forEach((eventKey) =>
this.socket.on(eventKey, this.routes[eventKey]),
);
this.peer.onicecandidate = (ev) =>
ev?.candidate &&
this.socket.emit("peerconnection/visitor/candidate", {
candidate: ev?.candidate,
});
// this.peer.addEventListener("")
this.peer.ondatachannel = (ev) =>
console.log("[PeerConnection] Channel open.");
this.peer.onicecandidateerror = ev => {
console.log("[PeerConnection] Error from Ice Candidate.", ev);
}
}
}
class PeerConnectionOwner implements SocketDataOwner {
private peer: RTCPeerConnection;
private readonly RTCConfiguration: RTCConfiguration;
private readonly Stream: MediaStream;
readonly socket: SocketType;
readonly routes: SocketDataOwner["routes"] = {
"peerconnection/owner/join": (socketId: SocketType["id"]) => {
this.join(socketId);
},
"peerconnection/owner/answer": (
socketId: SocketType["id"],
answer: RTCSessionDescriptionInit,
) => {
this.setAnswer(socketId, answer);
},
"peerconnection/owner/candidate": (
socketId: SocketType["id"],
candidate: RTCIceCandidate,
) => {
this.setCandidate(socketId, candidate);
},
};
private children: SocketDataOwner["children_db"] = {};
private async join(socketId: SocketType["id"]) {
const peer = (this.children[socketId] = new RTCPeerConnection(
this.RTCConfiguration,
));
peer.createDataChannel("broadcast");
this.Stream.getTracks().forEach((track) =>
peer.addTrack(track, this.Stream),
);
const offer = await peer.createOffer({iceRestart: true});
await peer.setLocalDescription(offer);
this.socket.emit("peerconnection/owner/offer", socketId, offer);
peer.restartIce();
console.log("[PeerConnection] Status:", {
signal: peer.signalingState,
connection: peer.connectionState
})
}
private setAnswer(
socketId: SocketType["id"],
answer: RTCSessionDescriptionInit,
) {
this.children[socketId]
.setRemoteDescription(answer)
.then(candidate => console.log("[PeerConnection] Add answer"))
.catch(candidate => console.log("[PeerConnection] Error answer", candidate));
}
private setCandidate(
socketId: SocketType["id"],
candidate: RTCIceCandidate,
) {
this.children[socketId].addIceCandidate(
new RTCIceCandidate(candidate),
)
.then(candidate => console.log("[PeerConnection] Add candidate"))
.catch(candidate => console.log("[PeerConnection] Error candidate", candidate));
}
constructor(
rtcConfig: RTCConfiguration,
socketConfig: SocketConfig | SocketType,
stream: MediaStream,
) {
this.peer = new RTCPeerConnection(rtcConfig);
this.socket = !!(socketConfig as SocketType)?.io
? (socketConfig as SocketType)
: IO.connect(
socketConfig as Partial<
IO.ManagerOptions & IO.SocketOptions
>,
);
Object.keys(this.routes).forEach((eventKey) =>
this.socket.on(eventKey, this.routes[eventKey]),
);
this.RTCConfiguration = rtcConfig;
this.Stream = stream;
}
}
function init(
config: RTCConfiguration,
socketConfig: SocketConfig | SocketType,
): PeerConnectionVisitor;
function init(
config: RTCConfiguration,
socketConfig: SocketConfig | SocketType,
owner: false,
stream?: undefined,
): PeerConnectionVisitor;
function init(
config: RTCConfiguration,
socketConfig: SocketConfig | SocketType,
owner: true,
stream: MediaStream | any,
): PeerConnectionOwner;
function init(
config: RTCConfiguration,
socketConfig: SocketConfig | SocketType,
owner?: boolean,
stream?: MediaStream | any,
) {
if (owner) return new PeerConnectionOwner(config, socketConfig, stream);
else return new PeerConnectionVisitor(config, socketConfig);
}
export { PeerConnectionVisitor, PeerConnectionOwner };
export default init;