UNPKG

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
"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 });