An open API service indexing awesome lists of open source software.

https://github.com/mobinx/easymeet-js

EasyMeetjs is a robust and versatile TypeScript library that provides a solid foundation for building WebRTC-based applications. It simplifies the complexities of WebRTC, enabling developers to easily incorporate real-time communication features into their projects.From simple audio video calling to real time peer to peer file transfer , everything
https://github.com/mobinx/easymeet-js

data meeting react realtime screensharing streaming-video webrtc zoom

Last synced: 6 months ago
JSON representation

EasyMeetjs is a robust and versatile TypeScript library that provides a solid foundation for building WebRTC-based applications. It simplifies the complexities of WebRTC, enabling developers to easily incorporate real-time communication features into their projects.From simple audio video calling to real time peer to peer file transfer , everything

Awesome Lists containing this project

README

          

# EasyMeetjs: The easiest yet flexible and robust webrtc libraty for react and javascript with typescript support

**EasyMeetjs** is a robust and versatile TypeScript library that provides a solid foundation for building WebRTC-based applications. It simplifies the complexities of WebRTC, enabling developers to easily incorporate real-time communication features into their projects.From simple audio video calling to real time peer to peer file transfer , everything can be done in just 40 lines! 😉😉

## Demo app implemented using this library
[MeetUp -> https://meetupx.vercel.app/](https://meetupx.vercel.app/)

## Key Features:

* **Peer-to-Peer Connection Management:** Seamlessly handles the creation, management, and closure of WebRTC peer connections, facilitating direct communication between users.
* **Media Handling (Audio/Video/Screen Sharing):** Supports capturing, sending, and receiving audio, video, and screen share streams, enriching the communication experience.
* **Data Channels:** Enables the exchange of arbitrary data between peers, extending the possibilities beyond just audio and video.
* **File Transfer:** Implements a file transfer mechanism leveraging data channels, allowing users to share files directly.
* **State Management and Event Handling:** Maintains and updates the state of peers, media streams, and file transfers, providing callbacks to react to changes.
* **Well-Structured and Comprehensive:** Encapsulates the complexities of WebRTC, offering a convenient interface for developers to build upon.

## Installation:

```bash
npm install @mobinx/easymeet
```

## Usage: (React Next.js - using meterd iceServers and ably as socket server)

Meet.tsx
```typescript
"use client"
import Ably from "ably";
import { useEasyMeet } from "@mobinx/easymeet/react";
import { useEffect, useRef, useState } from "react";
import { FileState } from "@mobinx/easymeet";

const ably = new Ably.Realtime({ key: 'your-ably-api-key', clientId: Math.random().toString(36).substring(7) })
ably.connection.once('connected').then(() => {
console.log('Connected to Ably!');
})
const channel = ably.channels.get('quickstart');
channel.presence.enter("mobin");

async function sendmsg(msg: any, to: any) {
await channel.publish("greeting", {
data: msg,
clientId: ably.auth.clientId,
to: to,
});
console.log("message sent: ", msg);
}

const VIdeo = ({ stream }: { stream: MediaStream }) => {
let viref = useRef(null);
let [isPlay, setIsPlay] = useState(false);
useEffect(() => {
if (viref.current) {
viref.current.srcObject = stream;
viref.current?.play();
viref.current.onplaying = () => {
console.log("playing");
setIsPlay(true);
};
viref.current.onpause = () => {
console.log("pause");
setIsPlay(false);
viref.current?.play();
};

if (viref.current.paused) {
viref.current.play();
}
}
});

return (

);
};

const AUdeo = ({ stream }: { stream: MediaStream }) => {
let viref = useRef(null);
let [isPlay, setIsPlay] = useState(false);
useEffect(() => {
if (viref.current) {
viref.current.srcObject = stream;
viref.current?.play();
viref.current.onplaying = () => {
console.log("playing");
setIsPlay(true);
};
viref.current.onpause = () => {
console.log("pause");
setIsPlay(false);
viref.current?.play();
};

if (viref.current.paused) {
viref.current.play();
}
}
});

return (

);
};

export default function Meet({ iceServers }: { iceServers: any }) {
const isInit = useRef(null);
const {
isSystemReady,
joinExistingPeer,
joinNewPeer,
leavePeer,
sendFile,
fileSharingCompleted,
fileSharingState,
onSocketMessage,
sendDataChannelMsg,
newDataChannelMsg,
toggleAudio,
toggleCamera,
toggleScreenShare,
isAudioOn,
isVideoOn,
isScreenShareOn,
audioStream,
videoStream,
screenShareStream,
peers,
} = useEasyMeet(ably.auth.clientId, iceServers, sendmsg);
const [myMsg, setMyMsg] = useState("");
const [allMsg, setAllMsg] = useState<{ from: string; msg: string }[]>([]);
const [fileProgress, setFileProgress] = useState<
{
id: string;
progress: number;
url?: string | null;
fileState?: FileState;
}[]
>([]);
useEffect(() => {
console.log(peers);
}, [peers]);
useEffect(() => {
if (fileSharingState) {
setFileProgress((prev) => {
let tempArray = [];
prev.map((item) => {
if (item.id != fileSharingState.fileId) {
tempArray.push(item);
}
});
tempArray.push({
id: fileSharingState.fileId,
progress: fileSharingState.progress,
fileState: fileSharingState,
});
return tempArray;
});
}
}, [fileSharingState]);
useEffect(() => {
console.log(fileSharingCompleted);
if (fileSharingCompleted) {
setFileProgress((prev) => {
let tempArray = [];
prev.map((item) => {
if (item.id != fileSharingCompleted.file.fileId) {
tempArray.push(item);
}
});
tempArray.push({
id: fileSharingCompleted.file.fileId,
progress: 100,
url: fileSharingCompleted.objectUrl,
fileState: fileSharingCompleted.file,
});
return tempArray;
});
}
}, [fileSharingCompleted]);
useEffect(() => {
if (newDataChannelMsg) {
setAllMsg((prev) => prev.concat([newDataChannelMsg]));
}
}, [newDataChannelMsg]);
useEffect(() => {
async function init() {
if (!isInit.current) {
if (isSystemReady) {
console.log("isSystemReady");
await channel.subscribe("greeting", async (message) => {
if (message.clientId === ably.auth.clientId) {
return;
}
if (message.data.to === ably.auth.clientId) {
console.log("message received from: " + message.clientId);
await onSocketMessage(message.data.data, message.clientId!, null);
}
});
channel.presence.subscribe("enter", async function (member) {
if (member.clientId === ably.auth.clientId) {
return;
}
console.log("informAboutNewConnection", member);
joinNewPeer(member.clientId);
});

channel.presence.subscribe("leave", async function (member) {
if (member.clientId === ably.auth.clientId) {
return;
}
console.log("leave", member);
leavePeer(member.clientId);
});
channel.presence.get().then((other_users: any) => {
console.log("userconnected", other_users);
if (other_users) {
for (var i = 0; i < other_users.length; i++) {
if (other_users[i].clientId !== ably.auth.clientId)
joinExistingPeer(other_users[i].clientId, false);
}
}
});

isInit.current = true;
}
}
}

init();
}, [
isSystemReady,
joinExistingPeer,
joinNewPeer,
leavePeer,
onSocketMessage,
]);

return (


my id: {ably.auth.clientId}
setMyMsg(e.target.value)}
className="bg-gray-200"
/>
{
sendDataChannelMsg(myMsg, "all");
setAllMsg((prev) =>
prev.concat([{ from: ably.auth.clientId, msg: myMsg }])
);
setMyMsg("");
}}
>
send

{
const file = e.target.files?.[0];
if (file) {
peers.forEach((peer) => {
sendFile(peer.socketId, file);
});
}
}}
/>
await toggleAudio()}>
{isAudioOn ? "mute" : "unmute"}

await toggleCamera()}>
{isVideoOn ? "camera off" : "camera on"}

await toggleScreenShare()}>
{isScreenShareOn ? "stop screen share" : "start screen share"}


{isVideoOn && }
{isScreenShareOn && }



{allMsg.map((msg, key) => (

{msg.from} : {msg.msg}

))}





{fileProgress.map((item, key) => {
return (

id: {item.fileState?.fileName}

{item.url && (

download

)}

);
})}


{peers.map((peer, key) => (

Peer Id: {peer.socketId}
{peer.isScreenShareOn && }
{peer.isVideoOn && }
{peer.isAudioOn && }

))}


);
}

```

page.tsx

```typescript
import Image from "next/image";
import dynamic from "next/dynamic";

const Meet = dynamic(() => import("./Meet"), { ssr: false });

export default async function Home() {
const response = await fetch("your-metered-api-key");
const iceServers = await response.json();
console.log(iceServers);
return (




);
}
```

## Usage: (Html with Jquery , Meterd and ably)
index.html
```html


Multi Conn App







$(function () {

const urlParams = new URLSearchParams(window.location.search);

var meeting_id = urlParams.get('mid');

// if (!meeting_id) {
// var murl = window.location.origin + "?mid=" + (new Date()).getTime();
// $('#meetingid').attr('href',murl).text(murl);
// $("#meetingContainer").hide();
// $("#meetingbox").show();
// return;
// }

// var user_id = urlParams.get('uid');
// if (!user_id) {
// user_id = window.prompt('Enter your nick!');
// }

// if (!user_id || !meeting_id) {
// alert('user id or meeting id missing');
// return;
// }
$("#meetingContainer").show();
$("#meetingbox").hide();

// MyApp._init(user_id,meeting_id);

});




Send File


Send


















UnMute
Start Camera
Screen Share





```
app.js
```javascript
(async () => {

const response = await fetch("https://virsys.metered.live/api/v1/turn/credentials?apiKey=ca9f4e60bf446fc29401ccb1fa904d110708");
const iceServers = await response.json();
let isWrtcInit = false;
const ably = new Ably.Realtime({ key: 'YSXfdw.ksCpsA:Bf6jKYu4LPPpMfiFkSMJrZ4q4ArLDkuBf7bJCPxKQUo', clientId: Math.random().toString(36).substring(7) });
ably.connection.once('connected').then(async () => {
console.log('Connected to Ably!');
})
const myid = ably.auth.clientId;
console.log('myid: ', myid);
const channel = ably.channels.get('quickstart');
let easymeet = new EasyMeet.WebrtcBase(ably.auth.clientId, iceServers, sendmsg,);
document.title = myid;

async function sendmsg(msg, to) {
await channel.publish('greeting', { data: msg, clientId: myid, to: to });
console.log('message sent: ', msg);
}

await channel.subscribe('greeting', async (message) => {

// clientid == sender from
// id == receiver (to)
if (message.clientId === myid) {
//checking i am not worikng on my own msg
return;
} else {

if (message.data.to === myid) {
//checking if the msg is for me
console.log('message received from: ' + message.clientId);

console.log(message);
await easymeet.onSocketMessage(message.data.data, message.clientId);

}

}

});

let _localVideoPlayer = document.getElementById('localVideoCtr');
let localScreenVideoCtr = document.getElementById('localScreenVideoCtr');

easymeet.onFileStateChange((fileState) => {

console.log(fileState);
if (document.getElementById('fileprogress' + fileState.fileId) == null) {
let progress = document.createElement('progress');
progress.id = 'fileprogress' + fileState.fileId;
progress.value = parseInt(fileState.progress);
progress.max = 100;
$("#fileprogress").append(progress);
}
else {
document.getElementById('fileprogress' + fileState.fileId).value = parseInt(fileState.progress);
}

// $("#fileprogress").append(`

${(fileState.progress) + "% " + parseInt(fileState.transferSpeed) + "kb/s" }
`)
})
easymeet.onFileTransferCompleted((fileState, objectURl) => {
console.log(fileState, objectURl);
$("#fileprogress").append(`
Completed
`)
document.getElementById('fileprogress' + fileState.fileId).value = 100;

// // $("#fileprogress").append(`

${JSON.stringify(fileState)}
`)
$("#fileprogress").append(`${objectURl}`)
})

$("#btnsendfile").on('click', async function () {
let file = document.getElementById('fileinput').files[0];
console.log(file);
(easymeet.getAllPeerDetails()).forEach(element => {
console.log(element.socketId);
easymeet.sendFile(element.socketId, file);
});

});

easymeet.onCameraVideoStateChange((state, stream) => {
if (state) {
_localVideoPlayer.srcObject = stream;
}
else {
_localVideoPlayer.srcObject = null;
}

})
easymeet.onScreenShareVideoStateChange((state, stream) => {
if (state) {
localScreenVideoCtr.srcObject = stream;
}
else {
localScreenVideoCtr.srcObject = null;
}

})

$("#btnMuteUnmute").on('click', async function () {
await easymeet.toggleAudio()

});
$("#btnStartStopCam").on('click', async function () {
await easymeet.toggleCamera();
});

$("#btnStartStopScreenshare").on('click', async function () {
await easymeet.toggleScreenShare();
})

easymeet.onDataChannelMsg((from, msg) => {
console.log("onDataChannelMsg", from, msg);
$("#messages").append("

  • " + from + ": " + msg + "
  • ");
    })

    easymeet.onPeerStateChange((peerstate) => {
    if (peerstate) {
    console.log("peerstate", peerstate);
    for (let peerz in peerstate) {
    let pr = peerstate[peerz];
    let remoteElm = document.getElementById(peerstate[peerz].socketId);
    if (!remoteElm) {
    AddNewUser(peerstate[peerz].socketId, peerstate[peerz].socketId);
    }
    let video = remoteElm.querySelector('.video'), audio = remoteElm.querySelector('audio'), screen = remoteElm.querySelector('.screen');
    if (pr.isAudioOn) {
    if (audio) {
    audio.srcObject = peerstate[peerz].audioStream;
    audio.play();
    }
    }
    else {
    if (audio) {
    audio.srcObject = null;
    }
    }
    if (pr.isVideoOn) {
    if (video) {
    video.srcObject = peerstate[peerz].videoStream;
    }
    }
    else {
    if (video) {
    video.srcObject = null;
    }
    }

    if (pr.isScreenShareOn) {
    if (screen) {
    screen.srcObject = peerstate[peerz].screenShareStream;
    }
    }
    else {
    if (screen) {
    screen.srcObject = null;
    }
    }

    }
    }
    })

    channel.presence.subscribe('enter', async function (member) {
    if (member.clientId === myid) {
    return;
    }
    console.log("informAboutNewConnection", member);
    AddNewUser(member.clientId, member.clientId);
    easymeet.createConnection(member.clientId, true);
    });

    channel.presence.subscribe('leave', async function (member) {
    if (member.clientId === myid) {
    return;
    }
    $('#' + member.clientId).remove();
    easymeet.closeConnection(member.clientId);
    });
    channel.presence.get(function (err, other_users) {
    console.log("userconnected", other_users);
    $('#divUsers .other').remove();
    if (other_users) {
    for (var i = 0; i < other_users.length; i++) {
    AddNewUser(other_users[i].clientId, other_users[i].clientId);
    easymeet.createConnection(other_users[i].clientId, false);
    }
    }
    $(".toolbox").show();
    $('#messages').show();
    $('#divUsers').show();
    });

    $('#btnResetMeeting').on('click', function () {
    socket.emit('reset');
    });

    $('#btnsend').on('click', function () {
    //_hub.server.sendMessage($('#msgbox').val());
    easymeet.sendDataChannelMsg("all", $('#msgbox').val());

    });

    $('#divUsers').on('dblclick', 'video', function () {
    this.requestFullscreen();
    });

    function AddNewUser(other_user_id, connId) {
    var $newDiv = $('#otherTemplate').clone();
    $newDiv = $newDiv.attr('id', connId).addClass('other');
    $newDiv.find('h2').text(other_user_id);
    $newDiv.find('video').attr('id', 'v_' + connId);
    $newDiv.find('audio').attr('id', 'a_' + connId);
    $newDiv.show();
    $('#divUsers').append($newDiv);
    }
    channel.presence.enter("mobin");
    })();

    ```

    ## Public API for core library (vanilla js)
    **Public Methods:**

    This table provides a detailed description of all public methods available in the `WebrtcBase` class from @mobinx/easymeet:

    like
    ```javascript
    let webrtc = new EasyMeet.WebrtcBase("you-socket-id", iceServers, sendmsg /*the function used by easymeet system for sending msg to other over socket, takes msg and to ,two perameter , see usages example avobe */);

    ```

    | Method | Description | Usage Example |
    |-------------------------------------------|-------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
    | `createConnection(connid, polite, extraInfo)` | Establishes a WebRTC connection with a peer. | `webrtc.createConnection('peer-id', false, { username: 'Alice' });` |
    | `closeConnection(connid)` | Closes the connection with a specific peer. | `webrtc.closeConnection('peer-id');` |
    | `onSocketMessage(message, from_connid, extraInfo)` | Handles incoming signaling messages from a peer. | `webrtc.onSocketMessage(JSON.stringify({ offer: offer }), 'peer-id', { username: 'Bob' });` |
    | `onPeerStateChange(fn)` | Registers a callback to be notified of peer state changes (e.g., audio/video on/off). | `webrtc.onPeerStateChange((peerStates) => { console.log('Peer states updated:', peerStates); });` |
    | `getAllPeerDetails()` | Returns an array of details for all connected peers. | `const peerDetails = webrtc.getAllPeerDetails(); console.log('Connected peers:', peerDetails);` |
    | `getPeerDetailsById(connid)` | Returns the details of a specific peer by their connection ID. | `const peerDetails = webrtc.getPeerDetailsById('peer-id'); console.log('Peer details:', peerDetails);` |
    | `startCamera(cameraConfig)` | Starts the user's camera with optional configuration (resolution, audio). | `webrtc.startCamera({ video: { width: 1280, height: 720 }, audio: true });` |
    | `stopCamera()` | Stops the user's camera. | `webrtc.stopCamera();` |
    | `toggleCamera()` | Toggles the camera on or off. | `webrtc.toggleCamera();` |
    | `startScreenShare(screenConfig)` | Starts screen sharing with optional configuration. | `webrtc.startScreenShare();` |
    | `stopScreenShare()` | Stops screen sharing. | `webrtc.stopScreenShare();` |
    | `toggleScreenShare()` | Toggles screen sharing on or off. | `webrtc.toggleScreenShare();` |
    | `startAudio()` | Starts the user's microphone. | `webrtc.startAudio();` |
    | `stopAudio()` | Stops the user's microphone. | `webrtc.stopAudio();` |
    | `toggleAudio()` | Toggles the microphone on or off. | `webrtc.toggleAudio();` |
    | `isLocalAudioOn()` | Returns `true` if the local audio is on, `false` otherwise. | `const audioOn = webrtc.isLocalAudioOn(); console.log('Local audio is on:', audioOn);` |
    | `isLocalVideoOn()` | Returns `true` if the local video is on, `false` otherwise. | `const videoOn = webrtc.isLocalVideoOn(); console.log('Local video is on:', videoOn);` |
    | `isLocalScreenShareOn()` | Returns `true` if local screen sharing is on, `false` otherwise. | `const screenSharingOn = webrtc.isLocalScreenShareOn(); console.log('Local screen sharing is on:', screenSharingOn);` |
    | `onCameraVideoStateChange(fn)` | Registers a callback for camera video state changes. | `webrtc.onCameraVideoStateChange((state, stream) => { console.log('Camera state changed:', state, stream); });` |
    | `onScreenShareVideoStateChange(fn)` | Registers a callback for screen share video state changes. | `webrtc.onScreenShareVideoStateChange((state, stream) => { console.log('Screen share state changed:', state, stream); });` |
    | `onAudioStateChange(fn)` | Registers a callback for audio state changes. | `webrtc.onAudioStateChange((state, stream) => { console.log('Audio state changed:', state, stream); });` |
    | `sendDataChannelMsg(conId, msg)` | Sends a message over the data channel to a specific peer or all peers (if `conId` is "all"). | `webrtc.sendDataChannelMsg('peer-id', { message: 'This is a data channel message!' });` |
    | `onDataChannelMsg(fn)` | Registers a callback function to handle incoming data channel messages. | `webrtc.onDataChannelMsg((connId, message) => { console.log('Data channel message from', connId, ':', message); });` |
    | `sendFile(to, file)` | Sends a file to a specific peer. | `webrtc.sendFile('peer-id', fileInput.files[0]);` |
    | `onFileSendingReq(fn)` | Registers a callback to handle file sending requests (allows confirmation before accepting a file). | `webrtc.onFileSendingReq((filename, connId) => { return confirm(`Accept file ${filename} from ${connId}?`); });` |
    | `onFileStateChange(fn)` | Registers a callback to track file transfer progress. | `webrtc.onFileStateChange((fileState) => { console.log('File transfer progress:', fileState.progress); });` |
    | `onFileTransferCompleted(fn)` | Registers a callback to handle completed file transfers. | `webrtc.onFileTransferCompleted((fileState, objectUrl) => { console.log('File transfer completed:', fileState, objectUrl); });` |
    | `onError(fn)` | Registers a callback function to handle errors. | `webrtc.onError((error) => { console.error('WebRTC Error:', error); });` |

    ## Public Method of React Hook (useEasyMeet)

    You can use those from
    @mobinx/easymeet/react
    ```javascript
    import {useEasyMeet} from "@mobinx/easymeet/react"
    let {startCamera, isVideoOn , isAudioOn , peers, fileSharingState ...and more} = useEasyMeet("you-socket-id", iceServers, sendmsg /*the function used by easymeet system for sending msg to other over socket, takes msg and to ,two perameter , see usages example avobe */)
    ```

    | Method/State | Description | Usage Example |
    |----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
    | `webRTCBaseRef` | A ref object containing the underlying `WebrtcBase` instance. | `webRTCBaseRef.current?.sendDataChannelMsg('peer-id', 'Hello!');` (Access the `WebrtcBase` instance directly if needed) |
    | `error` | An object containing error information (if any). | `if (error) { console.error('WebRTC Error:', error.message); }` |
    | `onSocketMessage(message, from_connid, extraInfo)` | Handles incoming signaling messages from a peer. | `onSocketMessage(JSON.stringify({ offer: offer }), 'peer-id', { username: 'Bob' });` |
    | `startCamera(cameraConfig)` | Starts the user's camera with optional configuration. | `startCamera({ video: { width: 1280, height: 720 }, audio: true });` |
    | `stopCamera()` | Stops the user's camera. | `stopCamera();` |
    | `startScreenShare(screenConfig)` | Starts screen sharing with optional configuration. | `startScreenShare();` |
    | `stopScreenShare()` | Stops screen sharing. | `stopScreenShare();` |
    | `toggleCamera()` | Toggles the camera on or off. | `toggleCamera();` |
    | `toggleScreenShare()` | Toggles screen sharing on or off. | `toggleScreenShare();` |
    | `startAudio()` | Starts the user's microphone. | `startAudio();` |
    | `stopAudio()` | Stops the user's microphone. | `stopAudio();` |
    | `toggleAudio()` | Toggles the microphone on or off. | `toggleAudio();` |
    | `isLocalAudioOn()` | Returns `true` if the local audio is on, `false` otherwise. | `const audioOn = isLocalAudioOn();` |
    | `isLocalVideoOn()` | Returns `true` if the local video is on, `false` otherwise. | `const videoOn = isLocalVideoOn();` |
    | `isLocalScreenShareOn()` | Returns `true` if local screen sharing is on, `false` otherwise. | `const screenSharingOn = isLocalScreenShareOn();` |
    | `joinExistingPeer(peerID, extraData)` | Joins an existing peer connection. | `joinExistingPeer('peer-id', { username: 'Alice' });` |
    | `joinNewPeer(peerID, extraData)` | Initiates a new peer connection. | `joinNewPeer('peer-id', { username: 'Bob' });` |
    | `leavePeer(peerID)` | Leaves a peer connection. | `leavePeer('peer-id');` |
    | `isAudioOn` | Indicates whether the remote peer's audio is on. | `const peerAudioOn = peers.find(peer => peer.socketId === 'peer-id')?.isAudioOn;` |
    | `isVideoOn` | Indicates whether the remote peer's video is on. | `const peerVideoOn = peers.find(peer => peer.socketId === 'peer-id')?.isVideoOn;` |
    | `isScreenShareOn` | Indicates whether the remote peer is screen sharing. | `const peerScreenSharingOn = peers.find(peer => peer.socketId === 'peer-id')?.isScreenShareOn;` |
    | `audioStream` | The remote peer's audio stream. | `` |
    | `videoStream` | The remote peer's video stream. | `` |
    | `screenShareStream` | The remote peer's screen share stream. | `` |
    | `newDataChannelMsg` | The latest data channel message received. | `useEffect(() => { if (newDataChannelMsg) { console.log('New data channel message:', newDataChannelMsg.msg, 'from:', newDataChannelMsg.from); } }, [newDataChannelMsg]);` |
    | `fileSharingCompleted` | Details of a completed file transfer. | `useEffect(() => { if (fileSharingCompleted) { console.log('File transfer completed:', fileSharingCompleted.file, 'URL:', fileSharingCompleted.objectUrl); } }, [fileSharingCompleted]);` |
    | `fileSharingState` | The current state of an ongoing file transfer. | `useEffect(() => { if (fileSharingState) { console.log('File transfer progress:', fileSharingState.progress); } }, [fileSharingState]);` |
    | `isSystemReady` | Indicates whether the WebRTC system is initialized and ready. | `if (isSystemReady) { // Perform WebRTC operations }` |
    | `peers` | An array of connected peer states. | `peers.map(peer =>

    Peer: {peer.socketId}, Audio: {peer.isAudioOn}, Video: {peer.isVideoOn}
    )` |
    | `sendDataChannelMsg(msg, toID)` | Sends a data channel message to a specific peer. | `sendDataChannelMsg('Hello from data channel!', 'peer-id');` |
    | `sendFile(to, file)` | Sends a file to a specific peer. | `const fileInput = useRef(null); // ... ... sendFile('peer-id', fileInput.current.files[0]);` |

    ## Contributing:

    Contributions are welcome! Please open an issue or submit a pull request if you have any suggestions, bug fixes, or new features.

    ## License:

    This project is licensed under the [MIT License](LICENSE).

    ## Acknowledgements:

    * [WebRTC](https://webrtc.org/) - The underlying technology powering this library.

    ## Contact:

    For any inquiries or support, please contact MobinX at [mobin0219@gmail.com](mobin0219@gmail.com).