Why is there an Agora video call issue : "Can't publish stream, haven't joined yet!" while it's in "await"?

Viewed 16

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 |

0 Answers
Related