So here is my code: VideoCall.jsx:
import React, { useState, useEffect } from "react";
import { config, useClient, useMicrophoneAndCameraTracks, channelName} from "./settings.js";
import { Grid } from "@material-ui/core";
import Video from "./Video.jsx";
import Controls from "./Controls";
export default function VideoCall(props) {
const { setInCall } = props;
const [users, setUsers] = useState([]);
const [start, setStart] = useState(false);
const client = useClient();
const { ready, tracks } = useMicrophoneAndCameraTracks();
useEffect(() => {
let init = async (name) => {
client.on("user-published", async (user, mediaType) => {
await client.subscribe(user, mediaType);
if (mediaType === "video") {
setUsers((prevUsers) => {
return [...prevUsers, user];
});
}
if (mediaType === "audio") {
user.audioTrack.play();
}
});
client.on("user-unpublished", (user, mediaType) => {
if (mediaType === "audio") {
if (user.audioTrack) user.audioTrack.stop();
}
if (mediaType === "video") {
setUsers((prevUsers) => {
return prevUsers.filter((User) => User.uid !== user.uid);
});
}
});
client.on("user-left", (user) => {
setUsers((prevUsers) => {
return prevUsers.filter((User) => User.uid !== user.uid);
});
});
try {
await client.join(config.appId, name, config.token, null);
} catch (error) {
console.log("error");
}
debugger;
if (tracks) await client.publish([tracks[0], tracks[1]]);
setStart(true);
};
if (ready && tracks) {
try {
init(channelName);
} catch (error) {
console.log(error);
}
}
}, [channelName, client, ready, tracks]);
return (
<Grid container direction="column" style={{ height: "100%" }}>
<Grid item style={{ height: "5%" }}>
{ready && tracks && (
<Controls tracks={tracks} setStart={setStart} setInCall={setInCall} />
)}
</Grid>
<Grid item style={{ height: "95%" }}>
{start && tracks && <Video tracks={tracks} users={users} />}
</Grid>
</Grid>
);
}
Video.jsx:
import { AgoraVideoPlayer } from "agora-rtc-react";
import { Grid } from "@material-ui/core";
import React, { useState, useEffect } from "react";
export default function Video(props) {
const { users, tracks } = props;
const [gridSpacing, setGridSpacing] = useState(12);
useEffect(() => {
setGridSpacing(Math.max(Math.floor(12 / (users.length + 1)), 4));
}, [users, tracks]);
return (
<Grid container style={{ height: "100%" }}>
<Grid item xs={gridSpacing}>
<AgoraVideoPlayer
videoTrack={tracks[1]}
style={{ height: "100%", width: "100%" }}
/>
</Grid>
{users.length > 0 &&
users.map((user) => {
if (user.videoTrack) {
return (
<Grid item xs={gridSpacing}>
<AgoraVideoPlayer
videoTrack={user.videoTrack}
key={user.uid}
style={{ height: "100%", width: "100%" }}
/>
</Grid>
);
} else return null;
})}
</Grid>
);
}
Controls.jsx:
import React, { useState } from "react";
import { useClient } from "./settings";
import { Grid, Button } from "@material-ui/core";
import ExitToAppIcon from "@material-ui/icons/ExitToApp";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faMicrophone, faMicrophoneSlash, faVideoSlash, faVideo } from "@fortawesome/free-solid-svg-icons";
import { useTranslation } from 'react-i18next';
export default function Controls(props) {
const {t} = useTranslation();
const client = useClient();
const { tracks, setStart, setInCall } = props;
const [trackState, setTrackState] = useState({ video: true, audio: true });
const mute = async (type) => {
if (type === "audio") {
await tracks[0].setEnabled(!trackState.audio);
setTrackState((ps) => {
return { ...ps, audio: !ps.audio };
});
} else if (type === "video") {
await tracks[1].setEnabled(!trackState.video);
setTrackState((ps) => {
return { ...ps, video: !ps.video };
});
}
};
const leaveChannel = async () => {
await client.leave();
client.removeAllListeners();
tracks[0].close();
tracks[1].close();
setStart(false);
setInCall(false);
};
return (
<Grid container spacing={2} alignItems="center">
<Grid item>
<Button variant="contained" color={trackState.audio ? "primary" : "secondary"} onClick={() => mute("audio")} >
{trackState.audio ? <FontAwesomeIcon icon={faMicrophone} /> : <FontAwesomeIcon icon={faMicrophoneSlash} />}
</Button>
</Grid>
<Grid item>
<Button variant="contained" color={trackState.video ? "primary" : "secondary"} onClick={() => mute("video")} >
{trackState.video ? <FontAwesomeIcon icon={faVideo} /> : <FontAwesomeIcon icon={faVideoSlash} />}
</Button>
</Grid>
<Grid item>
<Button variant="contained" color="default" onClick={() => leaveChannel()} >
{t('agora.leave')}
<ExitToAppIcon />
</Button>
</Grid>
</Grid>
);
}
settings.js:
import {createClient, createMicrophoneAndCameraTracks} from 'agora-rtc-react';
export const config = {mode: 'rtc', codec: "vp8", appId: process.env.AGORA_APP_ID, token : process.env.AGORA_TOKEN};
export const useClient = createClient(config);
export const useMicrophoneAndCameraTracks = createMicrophoneAndCameraTracks();
export const channelName = process.env.AGORA_CHANNEL_NAME;
AgoraDemo.jsx:
import React, {useState} from "react";
import {Form, Modal, Button,Row, Col} from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import VideoCall from "./agora/VideoCall";
const AgoraDemo = ({subjects}) => {
const {t} = useTranslation();
const [subjectSelected, setSubjectSelected] = useState(null);
const [inCall, setInCall] = useState(false);
return (
<Modal.Dialog size='xl'>
<Modal.Header style={{ display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
<Modal.Title>
{t('agora.headline')}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="App" style={{ height: "100%" }}>
{subjectSelected && inCall ? (
<VideoCall setInCall={setInCall} />
) : (
<Button onClick={() => setInCall(true)}>
{t('agora.join call')}
</Button>
)}
</div>
</Modal.Body>
<Modal.Footer/>
</Modal.Dialog>
)
}
export default AgoraDemo;
I don't know much plain javascript so I don't understand why this await does not pass first before proceding to the next line of code and how I should fix it. This is the error:
× Unhandled Rejection (AgoraRTCException): AgoraRTCError INVALID_OPERATION: Can't publish stream, haven't joined yet! ▶ 2 stack frames were collapsed. async init C:/Users/User/Desktop/Agora/src/agora/VideoCall.jsx:53 50 | 51 | debugger; 52 |
53 | if (tracks) await client.publish([tracks[0], tracks[1]]); | ^ 54 | setStart(true); 55 | }; 56 |