I am working on react and laravel project and I want to implement the ability of to users to call each other and joint in a video call. I have implemented all of the code as instructed in their documentation and I have used Laravel back-end to generate the token. Apparently everything works and I get no errors. However, my camera opens but no local stream (video) is displayed on the screen. I suspect is something wrong with my react code:
import React, { useEffect, useState, useRef } from "react";
import { connect } from "react-redux";
import Echo from "laravel-echo";
import axios from "axios";
import AgoraRTC from "agora-rtc-sdk";
import styles from "./css/settingspace.module.css";
const AGORA_ID = "my app id copyed from agora website";
const VideoCallPage = (props) => {
const [agoraConnection, setAgoraConnection] = useState({
callPlaced: false,
mutedAudio: false,
mutedVideo: false,
userOnlineChannel: null,
onlineUsers: [],
incomingCall: false,
incomingCaller: "",
agoraChannel: null,
});
const client = useRef(null);
const localStream = useRef(null);
useEffect(() => {
const initUserOnlineChannel = () => {
console.log("Initiate User Online Channel");
setAgoraConnection((prevAgoraConnection) => ({
...prevAgoraConnection,
userOnlineChannel: window.Echo.join("agora-online-channel"),
}));
console.log("Initiate User Online Channel - Finished");
};
const initUserOnlineListeners = () => {
console.log("Initiate User Online Listeners");
agoraConnection.userOnlineChannel.here((users) => {
setAgoraConnection((prevAgoraConnection) => ({
...prevAgoraConnection,
onlineUsers: users,
}));
});
agoraConnection.userOnlineChannel.joining((user) => {
// check user availability
const joiningUserIndex = agoraConnection.onlineUsers.findIndex(
(data) => data.id === user.id
);
if (joiningUserIndex < 0) {
setAgoraConnection((prevAgoraConnection) => ({
...prevAgoraConnection,
onlineUsers: [...agoraConnection.onlineUsers, user],
}));
}
});
agoraConnection.userOnlineChannel.leaving((user) => {
const leavingUserIndex = agoraConnection.onlineUsers.findIndex(
(data) => data.id === user.id
);
setAgoraConnection((prevAgoraConnection) => ({
...prevAgoraConnection,
onlineUsers: prevAgoraConnection.onlineUsers.splice(
leavingUserIndex,
1
),
}));
});
// listen to incoming call
agoraConnection.userOnlineChannel.listen("MakeAgoraCall", ({ data }) => {
console.log("Incoming call");
if (parseInt(data.userToCall) === parseInt(props.user.id)) {
const callerIndex = agoraConnection.onlineUsers.findIndex(
(user) => user.id === data.from
);
// the channel that was sent over to the user being called is what
// the receiver will use to join the call when accepting the call.
setAgoraConnection((prevAgoraConnection) => ({
...prevAgoraConnection,
incomingCaller: agoraConnection.onlineUsers[callerIndex]["name"],
incomingCall: true,
agoraChannel: data.channelName,
}));
}
});
};
initUserOnlineChannel();
if (agoraConnection.userOnlineChannel) {
initUserOnlineListeners();
}
}, [
agoraConnection.onlineUsers,
agoraConnection.userOnlineChannel,
props.user.id,
]);
const placeCall = async (id, calleeId) => {
try {
// channelName = the caller's and the callee's id. you can use anything. tho.
const channelName = `${props.user.id}_${calleeId}`;
const tokenRes = await generateToken(channelName);
// Broadcasts a call event to the callee and also gets back the token
await axios.post("api/agora/call-user", {
user_to_call: id,
channel_name: channelName,
});
initializeAgora();
joinRoom(tokenRes.data, channelName);
} catch (error) {
console.log(error);
}
};
const acceptCall = async () => {
initializeAgora();
const tokenRes = await generateToken(agoraConnection.agoraChannel);
joinRoom(tokenRes.data, agoraConnection.agoraChannel);
setAgoraConnection({
...agoraConnection,
incomingCall: false,
callPlaced: true,
});
};
const declineCall = () => {
// You can send a request to the caller to
// alert them of rejected call
setAgoraConnection({
incomingCall: false,
});
};
const generateToken = (channelName) => {
return axios.post("api/agora/token", {
channelName,
});
};
const initializeAgora = () => {
client.current = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
client.current.init(
AGORA_ID,
() => {
console.log("AgoraRTC client initialized");
},
(err) => {
console.log("AgoraRTC client init failed", err);
}
);
};
const joinRoom = async (token, channel) => {
client.current.join(
token,
channel,
props.user.id,
(uid) => {
console.log("User " + uid + " join channel successfully");
setAgoraConnection({ ...agoraConnection, callPlaced: true });
createLocalStream();
initializedAgoraListeners();
},
(err) => {
console.log("Join channel failed", err);
}
);
};
const initializedAgoraListeners = () => {
// Register event listeners
client.current.on("stream-published", function (evt) {
console.log("Publish local stream successfully");
console.log(evt);
});
//subscribe remote stream
client.current.on("stream-added", ({ stream }) => {
console.log("New stream added: " + stream.getId());
client.current.subscribe(stream, function (err) {
console.log("Subscribe stream failed", err);
});
});
client.current.on("stream-subscribed", (evt) => {
// Attach remote stream to the remote-video div
evt.stream.play("remote-video");
client.current.publish(evt.stream);
});
client.current.on("stream-removed", ({ stream }) => {
console.log(String(stream.getId()));
stream.close();
});
client.current.on("peer-online", (evt) => {
console.log("peer-online", evt.uid);
});
client.current.on("peer-leave", (evt) => {
var uid = evt.uid;
var reason = evt.reason;
console.log("remote user left ", uid, "reason: ", reason);
});
client.current.on("stream-unpublished", (evt) => {
console.log(evt);
});
};
const createLocalStream = () => {
localStream.current = AgoraRTC.createStream({
audio: true,
video: true,
});
// Initialize the local stream
localStream.current.init(
() => {
// Play the local stream
localStream.current.play("me");
console.log("Local stream played");
// Publish the local stream
client.current.publish(localStream.current, (err) => {
console.log("publish local stream", err);
});
},
(err) => {
console.log(err);
}
);
};
const endCall = () => {
localStream.current.close();
client.current.leave(
() => {
console.log("Leave channel successfully");
setAgoraConnection({
...agoraConnection,
callPlaced: false,
});
},
(err) => {
console.log("Leave channel failed");
}
);
};
const handleAudioToggle = () => {
if (agoraConnection.mutedAudio) {
localStream.current.unmuteAudio();
setAgoraConnection({
...agoraConnection,
mutedAudio: false,
});
} else {
localStream.current.muteAudio();
setAgoraConnection({
...agoraConnection,
mutedAudio: true,
});
}
};
const handleVideoToggle = () => {
if (agoraConnection.mutedVideo) {
localStream.current.unmuteVideo();
setAgoraConnection({
...agoraConnection,
mutedVideo: false,
});
} else {
localStream.current.muteVideo();
setAgoraConnection({
...agoraConnection,
mutedVideo: true,
});
}
};
return (
<div className="mt-5">
<div>
<button
type="button"
className="btn btn-primary"
onClick={() => placeCall(4, 4)}
>
Call
</button>
</div>
{agoraConnection.incomingCall && (
<div className="row my-5">
<div className="col-12">
<p>
Incoming Call From{" "}
<strong>{agoraConnection.incomingCaller}</strong>
</p>
<div className="btn-group" role="group">
<button
type="button"
className="btn btn-danger"
data-dismiss="modal"
onClick={() => declineCall()}
>
Decline
</button>
<button
type="button"
className="btn btn-success ml-5"
onClick={() => acceptCall()}
>
Accept
</button>
</div>
</div>
</div>
)}
{agoraConnection.callPlaced && (
<section id="video-container">
<div id="me"></div>
<div id="remote-video"></div>
<div className="action-btns">
<button
type="button"
className="btn btn-info"
onClick={() => handleAudioToggle()}
>
{agoraConnection.mutedAudio ? "Unmute" : "Mute"}
</button>
<button
type="button"
className="btn btn-primary mx-4"
onClick={() => handleVideoToggle()}
>
{agoraConnection.mutedVideo ? "ShowVideo" : "HideVideo"}
</button>
<button
type="button"
className="btn btn-danger"
onClick={() => endCall()}
>
EndCall
</button>
</div>
</section>
)}
</div>
);
};
const mapStateToProps = (state) => {
return {
user: state.auth,
};
};
export default connect(mapStateToProps)(VideoCallPage);
After I place a call like I said my web camera starts the place where my local stream should be displayed is shown but without the actual stream:
I really have no other ideas how to fix this.
in the most cases AgoraRTC insert the html video element inside two divs (first div is the trackid, second div is the video element wrapper), you need to style this elements cu'z they have width 0px & heigth 0px, check in the inspector, you can also use the AgoraVideoPlayer component imported from agora-rtc and need style this too