react-video-call
Version:
ReactVideoCall is a simple yet powerful WebRTC-based video call component for React. It uses Firebase for signaling and supports both desktop and mobile views with minimal setup.
246 lines • 11.3 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { FirebaseWrapper } from "./firebaseLib";
export class WebRTCManager {
constructor(firebaseConfig, localVidEle, remortVidEle, RTCConfiguration) {
this.ref = new Date().toISOString();
this.fireBaseDb = new FirebaseWrapper(firebaseConfig);
this.localVidEle = localVidEle;
this.remortVidEle = remortVidEle;
this.peerConnection = new RTCPeerConnection(RTCConfiguration);
this.peerConnection.ontrack = (event) => {
this.onRemortTrack(event);
};
this.peerConnection.onconnectionstatechange = (event) => {
console.debug("connectionState", this.peerConnection.connectionState);
if (this.onStateChange) {
this.onStateChange(this.peerConnection.connectionState, event);
}
};
const videoElement = localVidEle.current;
let isDragging = false;
let offsetX, offsetY;
// Handle start of dragging (both mouse and touch)
function startDrag(e) {
isDragging = true;
let clientX;
let clientY;
if (e instanceof TouchEvent) {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
}
else {
clientX = e.clientX;
clientY = e.clientY;
}
offsetX = clientX - videoElement.offsetLeft;
offsetY = clientY - videoElement.offsetTop;
videoElement.style.cursor = 'grabbing';
}
// Handle dragging (both mouse and touch)
function drag(e) {
if (!isDragging)
return;
let clientX;
let clientY;
if (e instanceof TouchEvent) {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
}
else {
clientX = e.clientX;
clientY = e.clientY;
}
// Calculate the new position
let newX = clientX - offsetX;
let newY = clientY - offsetY;
// Keep the video within the container boundaries
if (!videoElement.parentElement)
return;
const rect = videoElement.parentElement.getBoundingClientRect();
const videoRect = videoElement.getBoundingClientRect();
if (newX < 0)
newX = 0;
if (newY < 0)
newY = 0;
if (newX + videoRect.width > rect.width)
newX = rect.width - videoRect.width;
if (newY + videoRect.height > rect.height)
newY = rect.height - videoRect.height;
// Update the position
videoElement.style.left = `${newX}px`;
videoElement.style.top = `${newY}px`;
}
// Handle end of dragging (both mouse and touch)
function endDrag() {
isDragging = false;
videoElement.style.cursor = 'grab';
}
// Mouse events
videoElement.addEventListener('mousedown', startDrag);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', endDrag);
// Touch events
videoElement.addEventListener('touchstart', startDrag);
document.addEventListener('touchmove', drag);
document.addEventListener('touchend', endDrag);
}
joinOrStartRoom(name) {
return __awaiter(this, void 0, void 0, function* () {
this.peerConnection.onicecandidate = (event) => __awaiter(this, void 0, void 0, function* () {
//Event that fires off when a new answer ICE candidate is created
if (event.candidate) {
yield this.fireBaseDb.updateRoom(this.roomRole === "owner" ? "offer" : "answer", JSON.stringify(this.peerConnection.localDescription));
}
});
this.roomUnSub = yield this.fireBaseDb.joinRoom(name, (data) => __awaiter(this, void 0, void 0, function* () {
var _a;
if (!this.roomRole && (!data || (!data.create && !data.offer))) {
console.debug("As Owner creating offer.");
this.roomRole = "owner";
const offer = yield this.peerConnection.createOffer();
yield this.peerConnection.setLocalDescription(offer);
yield this.fireBaseDb.createRoom(name, this.ref);
}
else if ((this.roomRole === "owner" && data && (data === null || data === void 0 ? void 0 : data.create) && ((_a = data === null || data === void 0 ? void 0 : data.create) === null || _a === void 0 ? void 0 : _a.ref) !== this.ref) ||
(!this.roomRole && (data && data.offer && !data.completed2))) {
if (this.roomRole === "owner")
console.debug("Got racecondition and moving to joiner");
console.debug("Joiner creating answer");
this.roomRole = "joiner";
yield this.peerConnection.setRemoteDescription(JSON.parse(data.offer));
const answer = yield this.peerConnection.createAnswer();
yield this.peerConnection.setLocalDescription(answer);
yield this.fireBaseDb.updateRoom("completed2", true);
}
else if (this.roomRole === "owner" && data && data.answer && !data.completed1) {
console.debug("OWNER GOT Answer", this.ref);
this.peerConnection.setRemoteDescription(JSON.parse(data.answer));
yield this.fireBaseDb.updateRoom("completed1", true);
}
}));
});
}
onRemortTrack(event) {
if (event.track.kind === "video") {
return;
}
console.log("onRemortTrack");
if (this.remoteStream) {
this.remoteStream.getTracks().forEach(track => {
track.stop();
});
}
const newstrema = new MediaStream();
event.streams[0].getTracks().forEach((track) => {
newstrema.addTrack(track);
});
this.remoteStream = newstrema;
this.remortVidEle.current.srcObject = this.remoteStream;
// this.remortVidEle.current.volume = 0;
this.remortVidEle.current.play();
}
getDevices() {
return __awaiter(this, void 0, void 0, function* () {
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
console.debug("Media devices not supported in this browser.");
return { audioDevices: [], videoDevices: [] };
}
yield navigator.mediaDevices.getUserMedia({ video: true, audio: true });
const deviceInfos = yield navigator.mediaDevices.enumerateDevices();
const audioDevices = [], videoDevices = [];
for (const deviceInfo of deviceInfos) {
if (deviceInfo.kind === "audioinput") {
audioDevices.push({
value: deviceInfo.deviceId,
label: deviceInfo.label,
data: deviceInfo
});
}
else if (deviceInfo.kind === "videoinput") {
videoDevices.push({
value: deviceInfo.deviceId,
label: deviceInfo.label,
data: deviceInfo
});
}
}
return { audioDevices, videoDevices };
});
}
selectDevice(camera, mic) {
return __awaiter(this, void 0, void 0, function* () {
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
console.debug("Media devices not supported in this browser.");
return;
}
const constraints = {
audio: mic ? { deviceId: mic ? { exact: mic } : undefined } : true,
video: camera ? { deviceId: camera ? { exact: camera } : undefined, facingMode: "user" } : true
};
navigator.mediaDevices.getUserMedia(constraints).
then((stream) => {
console.debug("Setting local Devices.");
if (this.localStream) {
this.localStream.getTracks().forEach(track => {
track.stop();
});
}
this.localStream = stream;
if (this.localStream) {
this.localStream.getTracks().forEach((track) => {
if (this.localStream) {
console.debug("added local track");
this.peerConnection.addTrack(track, this.localStream);
}
});
this.localVidEle.current.srcObject = this.localStream;
this.localVidEle.current.volume = 0;
this.localVidEle.current.play();
this.localVidEle.current.style.transform = "scaleX(" + "-1" + ")";
}
}).catch((err) => {
console.debug("Could not load stream", err);
});
});
}
disconnectAll() {
if (this.roomUnSub) {
this.roomUnSub();
}
this.peerConnection.close();
if (this.localStream) {
// Stop all media tracks
this.localStream.getTracks().forEach((track) => track.stop());
this.localStream = undefined;
}
if (this.remoteStream) {
// Stop all media tracks
this.remoteStream.getTracks().forEach((track) => track.stop());
this.remoteStream = undefined;
}
if (this.peerConnection) {
this.peerConnection.onicecandidate = null;
this.peerConnection.ontrack = null;
this.peerConnection.oniceconnectionstatechange = null;
this.peerConnection.onnegotiationneeded = null;
this.peerConnection.onsignalingstatechange = null;
this.peerConnection.onconnectionstatechange = null;
}
this.fireBaseDb.leaveRoom();
console.debug('Connection Closed...');
}
muteMic(isMiute) {
var _a;
(_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().forEach(track => track.enabled = !isMiute);
console.debug("Microphone is muted");
}
}
//# sourceMappingURL=webrtcManager.js.map