Reputation: 11
I'm looking to implement a feature in my application that lets users make audio group calls over the internet using socket.io, WebRTC and a Node.js server. I am currently using this library called simple-peer to connect between multiple peers and the connection from the client part (a react webpage in the browser) to the server (a node.js server running socket.io and express) is working fine as expeted, but transmitting audio seems to be failing for some reason.
For starters, i'm using node version 18.17.1, npm version 9.6.7 and the version of simple-peer and socket.io-client versions specified in my package.json are:
"socket.io-client": "^4.8.1",
"simple-peer": "^9.11.1"
Ive imported the two libaries into my App.js class file (im using Component instead of function) like so:
import io from 'socket.io-client';
import SimplePeer from "simple-peer";
...
class App extends Component {...
Then in my state, ive declared three state variables like so:
state={
myId:'',
roomId:'',
peers:[]
}
The roomId variable is used when establishing a new room thats meant to contain all the connected users subscribed or listening in to that specific room and peers is meant to contain instantiated SimplePeer objects. Then, i have a constructor to hold some refs:
constructor(props) {
super(props);
this.localStream = React.createRef();
this.peersRef = []
}
Then in my componentDidMount(), i initialize the socket.io-client connection to the server. Im using localhost for now:
componentDidMount(){
this.socket = io('http://localhost:3002', {
transports: ['websocket']
});
var me = this
this.socket.on('connect', () => me.setState({myId: me.socket.id}));
}
This basically establishes a connection to the server, then sets myId. Then in my render function, i have the following:
render(){
return (
<div>
<h2>My ID: {this.state.myId}</h2>
<input
type="text"
placeholder="Enter ID to call"
value={this.state.roomId}
onChange={(e) => this.setRoomId(e.target.value)}
/>
<button onClick={this.initializeMedia}>Initialize Microphone</button>
<button onClick={this.joinMultiCall}>Join Call</button>
<audio ref={this.localStream} autoPlay muted />
{this.state.peers.map((peerObj) => (
<Audio key={peerObj.peerId} peer={peerObj.peer} />
))}
</div>
);
}
setRoomId(value){
this.setState({roomId: value})
}
initializeMedia = async () => {
this.stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this.localStream.current.srcObject = this.stream;
};
An input for setting the roomId to connect to, a button for requesting a connection to the mic, a button for joining a room, a muted audio thats connected to the stream from my microphone, and a list of peers rendered inside a custom Audio function. Then in the joinMultiCall function, i have the following code:
joinMultiCall = async () => {
this.socket.emit("join-room", this.state.roomId);
this.socket.on("user-joined", (userId) => {
console.log(`User ${userId} joined`); // <------This fires successfully.
const peer = this.createPeer(userId, this.socket.id, this.stream);
const peer_obj = { peerId: userId, peer: peer }
this.peersRef.push(peer_obj);
var clone = this.state.peers.slice()
clone.push(peer_obj)
this.setState({peers: clone})
})
this.socket.on("signal", ({ from, data }) => {
const peerObj = this.peersRef.find((p) => p.peerId === from);
if (peerObj) {
peerObj.peer.signal(data);
}
});
this.socket.on("user-left", (userId) => {
console.log(`User ${userId} left`); //<------- this fires successfully
this.peersRef = this.peersRef.filter((p) => p.peerId !== userId);
var clone = this.state.peers.slice()
clone = clone.filter((p) => p.peerId !== userId);
this.setState({peers: clone})
});
}
// Create a new peer for an incoming user
createPeer = (userToSignal, callerId, localStream) => {
const peer = new SimplePeer({
initiator: location.hash === "#1",
trickle: false,
stream: localStream
});
// peer.addStream(localStream)
peer.on('error', err => {
console.error('Peer error:', err);
});
peer.on("signal", (signal) => {
console.log('signal received,', signal) //<----------This fires too.
this.socket.emit("signal", { to: userToSignal, data: signal });
});
return peer;
};
The socket emits the 'join-room' event successfully because its logged in the console. Also, a signal object is received after a peer object is created. Then when a peer is created, an Audio element is rendered which is supposed to listen for audio streams:
const Audio = ({ peer }) => {
const ref = useRef();
useEffect(() => {
peer.on("stream", (stream) => {
console.log('stream received, ', stream) //<-------- does not fire
ref.current.srcObject = stream;
});
}, [peer]);
return <audio ref={ref} autoPlay controls />;
};
class App extends Component {
...
The 'stream' event is not firing here for some reason, and its what is supposed to be streaming all the data to the <audio/> object. My server hosting the socket.io connection:
const express = require('express');
const { createServer } = require('http');
const { Server } = require('socket.io');
const app = express();
app.use(cors());
app.use(express.json({ limit: "10gb" }));
const server = createServer(app);
const io = new Server(server);
io.on("connection", (socket) => {
console.log("User connected:", socket.id); //<------ logs in the terminal
// Join a room
socket.on("join-room", (roomId) => {
console.log(`${socket.id} joined room: ${roomId}`); //<------ logs in the terminal
socket.join(roomId);
socket.to(roomId).emit("user-joined", socket.id);
});
// Relay signaling data
socket.on("signal", ({ to, data }) => {
io.to(to).emit("signal", { from: socket.id, data });
});
// Notify users when someone leaves
socket.on("disconnect", () => {
console.log("User disconnected:", socket.id); //<------ logs in the terminal
io.emit("user-left", socket.id);
});
});
const IO_PORT = 3002;
server.listen(IO_PORT)
Upvotes: 1
Views: 107