webrtc2-peer
Version:
WebRTC2 Peer - Low-level WebRTC peer connection management for cross-platform real-time communication with signaling, ICE handling, and media streaming
352 lines (346 loc) • 9.65 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
MediaManager: () => MediaManager,
PeerConnection: () => PeerConnection,
SignalingClient: () => SignalingClient,
VERSION: () => VERSION
});
module.exports = __toCommonJS(index_exports);
// src/MediaManager.ts
var MediaManager = class {
constructor() {
this.devices = [];
}
async getDevices() {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
this.devices = devices.map((device) => ({
deviceId: device.deviceId,
kind: device.kind,
label: device.label,
groupId: device.groupId
}));
return this.devices;
} catch (error) {
throw new Error(`Failed to get media devices: ${error}`);
}
}
async getUserMedia(constraints = { video: true, audio: true }) {
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
this.currentStream = stream;
return stream;
} catch (error) {
throw new Error(`Failed to get user media: ${error}`);
}
}
async getDisplayMedia(constraints) {
try {
const stream = await navigator.mediaDevices.getDisplayMedia(constraints);
return stream;
} catch (error) {
throw new Error(`Failed to get display media: ${error}`);
}
}
async switchCamera(deviceId) {
if (!this.currentStream) {
throw new Error("No active stream to switch camera");
}
const videoTrack = this.currentStream.getVideoTracks()[0];
if (!videoTrack) {
throw new Error("No video track found");
}
const constraints = {
deviceId: deviceId ? { exact: deviceId } : void 0
};
try {
const newStream = await navigator.mediaDevices.getUserMedia({
video: constraints,
audio: this.hasAudio()
});
const newVideoTrack = newStream.getVideoTracks()[0];
if (newVideoTrack) {
this.currentStream.removeTrack(videoTrack);
this.currentStream.addTrack(newVideoTrack);
videoTrack.stop();
}
return this.currentStream;
} catch (error) {
throw new Error(`Failed to switch camera: ${error}`);
}
}
muteAudio(muted = true) {
if (!this.currentStream) return;
this.currentStream.getAudioTracks().forEach((track) => {
track.enabled = !muted;
});
}
muteVideo(muted = true) {
if (!this.currentStream) return;
this.currentStream.getVideoTracks().forEach((track) => {
track.enabled = !muted;
});
}
stopStream(stream) {
const targetStream = stream || this.currentStream;
if (!targetStream) return;
targetStream.getTracks().forEach((track) => {
track.stop();
});
if (targetStream === this.currentStream) {
this.currentStream = void 0;
}
}
getCurrentStream() {
return this.currentStream;
}
hasVideo() {
return (this.currentStream?.getVideoTracks().length || 0) > 0;
}
hasAudio() {
return (this.currentStream?.getAudioTracks().length || 0) > 0;
}
isAudioMuted() {
const audioTrack = this.currentStream?.getAudioTracks()[0];
return audioTrack ? !audioTrack.enabled : true;
}
isVideoMuted() {
const videoTrack = this.currentStream?.getVideoTracks()[0];
return videoTrack ? !videoTrack.enabled : true;
}
getVideoDevices() {
return this.devices.filter((device) => device.kind === "videoinput");
}
getAudioDevices() {
return this.devices.filter((device) => device.kind === "audioinput");
}
async checkPermissions() {
try {
const [camera, microphone] = await Promise.all([
navigator.permissions.query({ name: "camera" }),
navigator.permissions.query({ name: "microphone" })
]);
return {
camera: camera.state,
microphone: microphone.state
};
} catch (error) {
throw new Error(`Failed to check permissions: ${error}`);
}
}
};
// src/PeerConnection.ts
var import_eventemitter3 = require("eventemitter3");
var PeerConnection = class extends import_eventemitter3.EventEmitter {
constructor(config) {
super();
this.pc = new RTCPeerConnection(config || {
iceServers: [
{ urls: "stun:stun.l.google.com:19302" }
]
});
this.setupEventHandlers();
}
setupEventHandlers() {
this.pc.onconnectionstatechange = () => {
this.emit("connection-state-change", this.pc.connectionState);
};
this.pc.onicecandidate = (event) => {
if (event.candidate) {
this.emit("ice-candidate", event.candidate);
}
};
this.pc.ontrack = (event) => {
const [stream] = event.streams;
this.remoteStream = stream;
this.emit("remote-stream", stream);
};
this.pc.ondatachannel = (event) => {
this.emit("data-channel", event.channel);
};
}
async createOffer() {
const offer = await this.pc.createOffer();
await this.pc.setLocalDescription(offer);
return offer;
}
async createAnswer() {
const answer = await this.pc.createAnswer();
await this.pc.setLocalDescription(answer);
return answer;
}
async setRemoteDescription(description) {
await this.pc.setRemoteDescription(description);
}
async addIceCandidate(candidate) {
await this.pc.addIceCandidate(candidate);
}
addLocalStream(stream) {
this.localStream = stream;
stream.getTracks().forEach((track) => {
this.pc.addTrack(track, stream);
});
}
createDataChannel(label, options) {
return this.pc.createDataChannel(label, options);
}
getConnectionState() {
return this.pc.connectionState;
}
getLocalStream() {
return this.localStream;
}
getRemoteStream() {
return this.remoteStream;
}
close() {
this.pc.close();
this.removeAllListeners();
}
};
// src/SignalingClient.ts
var import_eventemitter32 = require("eventemitter3");
var SignalingClient = class extends import_eventemitter32.EventEmitter {
constructor(url, options) {
super();
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 1e3;
this.isConnecting = false;
this.url = url;
if (options?.maxReconnectAttempts) {
this.maxReconnectAttempts = options.maxReconnectAttempts;
}
if (options?.reconnectDelay) {
this.reconnectDelay = options.reconnectDelay;
}
}
connect() {
return new Promise((resolve, reject) => {
if (this.isConnecting || this.isConnected()) {
resolve();
return;
}
this.isConnecting = true;
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
this.isConnecting = false;
this.reconnectAttempts = 0;
this.emit("connected");
resolve();
};
this.ws.onclose = () => {
this.isConnecting = false;
this.emit("disconnected");
this.attemptReconnect();
};
this.ws.onerror = (event) => {
this.isConnecting = false;
const error = new Error("WebSocket connection error");
this.emit("error", error);
reject(error);
};
this.ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
this.emit("message", message);
} catch (error) {
this.emit("error", new Error("Failed to parse message"));
}
};
});
}
attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
this.emit("error", new Error("Max reconnection attempts reached"));
return;
}
this.reconnectAttempts++;
setTimeout(() => {
this.connect().catch(() => {
});
}, this.reconnectDelay * this.reconnectAttempts);
}
send(message) {
if (!this.isConnected()) {
throw new Error("WebSocket is not connected");
}
this.ws.send(JSON.stringify(message));
}
joinRoom(roomId, userId) {
this.send({
type: "join-room",
roomId,
userId
});
}
leaveRoom(roomId, userId) {
this.send({
type: "leave-room",
roomId,
userId
});
}
sendOffer(offer, roomId, userId) {
this.send({
type: "offer",
data: offer,
roomId,
userId
});
}
sendAnswer(answer, roomId, userId) {
this.send({
type: "answer",
data: answer,
roomId,
userId
});
}
sendIceCandidate(candidate, roomId, userId) {
this.send({
type: "ice-candidate",
data: candidate,
roomId,
userId
});
}
isConnected() {
return this.ws?.readyState === WebSocket.OPEN;
}
disconnect() {
if (this.ws) {
this.ws.close();
this.ws = void 0;
}
this.removeAllListeners();
}
};
// src/index.ts
var VERSION = "1.0.0";
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
MediaManager,
PeerConnection,
SignalingClient,
VERSION
});