I have a NEXT JS app that uses the following VideoPlayer
component:
import React, { useCallback, useEffect, useState, useContext } from "react";
import { useSocket, useSocketUpdate } from "../Store";
import { useRouter } from "next/router";
import styles from "../styles/CallPage.module.css";
const VideoPlayer = (props: any) => {
const {
isAdmin,
myVideo,
userVideo
}: any = useSocket();
const router = useRouter();
useEffect(() => {
if (router.isReady && myVideo.current && userVideo.current) {
myVideo?.current.load();
userVideo?.current.load();
}
}, []);
return (
<div className={styles.grid_container}>
{stream && (
<div>
<video
className={styles.video}
playsInline
muted
ref={myVideo}
autoPlay
/>
<div className={styles.overlay}>
<h2 className={styles.overlay_h2}>OVERLAY</h2>
</div>
</div>
)}
{callAccepted && !callEnded ? (
<div>
<video
className={styles.video}
playsInline
ref={userVideo}
autoPlay
/>
<div className={styles.overlay}>
<h2 className={styles.overlay_h2}>OVERLAY2</h2>
</div>
</div>
) : (
isGuest &&
!callAccepted && (
<div className={styles.grid_container}>
<div className={styles.no_match_content}>
<h2 className={styles.h2_no_match}>
Please wait until the host of this room let you in.
</h2>
<div className={styles.btn_no_match}></div>
</div>
</div>
)
)}
</div>
);
};
export default VideoPlayer;
The myVideo ref is being retrieved by the useSocket hook which comes from the following Store declaration:
import React, {
createContext,
useState,
useRef,
useEffect,
useContext
} from "react";
import { io } from "socket.io-client";
import Peer from "simple-peer";
import { useRouter } from "next/router";
const SocketContext = createContext({});
const SocketUpdateContext = createContext({});
const socket = io(`${process.env.NEXT_PUBLIC_SERVER_URL}`);
function useSocket() {
const useSocket = useContext(SocketContext);
if (useSocket === undefined) {
throw new Error("useSocket must be used within a SocketProvider");
}
return useSocket;
}
function useSocketUpdate() {
const useSocketUpdate = useContext(SocketUpdateContext);
if (useSocketUpdate === undefined) {
throw new Error("useSocketUpdate must be used within a SocketProvider");
}
return useSocketUpdate;
}
const ContextProvider = ({ children }: any) => {
const router = useRouter();
const [callAccepted, setCallAccepted] = useState(false);
const [callEnded, setCallEnded] = useState(false);
const [stream, setStream] = useState<MediaStream>();
const [name, setName] = useState("");
const [call, setCall] = useState<any>({});
const [me, setMe] = useState("");
const [isAdmin, setAdmin] = useState(false);
const [isGuest, setGuest] = useState(false);
const [idToCall, setIdToCall] = useState("");
const myVideo = useRef<HTMLVideoElement>(null!);
const userVideo = useRef<HTMLVideoElement>(null!);
const connectionRef = useRef<Peer.Instance>(null!);
useEffect(() => {
navigator.mediaDevices
.getUserMedia({ video: true, audio: true })
.then((currentStream) => {
setStream(currentStream);
if (myVideo.current) {
myVideo.current.srcObject = currentStream;
}
});
socket.on("me", (id) => {
setMe(id);
});
socket.on("callUser", ({ from, name: callerName, signal }) => {
setCall({ isReceivingCall: true, from, name: callerName, signal });
});
}, []);
const answerCall = () => {
setCallAccepted(true);
const peer = new Peer({ initiator: false, trickle: false, stream });
peer.on("signal", (data) => {
socket.emit("answerCall", { signal: data, to: call.from });
});
peer.on("stream", (currentStream) => {
if (userVideo.current) userVideo.current.srcObject = currentStream;
});
peer.signal(call.signal);
connectionRef.current = peer;
};
const callUser = (id: String) => {
// setAdmin(false);
const peer = new Peer({ initiator: true, trickle: false, stream });
peer.on("signal", async (data) => {
socket.emit("callUser", {
userToCall: id,
signalData: data,
from: me,
name
});
});
peer.on("stream", (currentStream) => {
if (userVideo.current) userVideo.current.srcObject = currentStream;
});
socket.on("callAccepted", (signal) => {
setCallAccepted(true);
peer.signal(signal);
});
if (connectionRef.current) connectionRef.current = peer;
};
const leaveCall = () => {
setCallEnded(true);
setAdmin(false);
if (connectionRef.current) connectionRef.current.destroy();
// if (typeof window !== "undefined") window.location.reload();
router.push("/home");
};
return (
<SocketContext.Provider
value={{
call,
callAccepted,
myVideo,
userVideo,
stream,
name,
callEnded,
me,
isAdmin,
router,
isGuest,
user,
isAuthenticated,
isUnauthenticated,
isAuthenticating,
authError,
idToCall
}}
>
<SocketUpdateContext.Provider
value={{
callUser,
leaveCall,
answerCall,
setName,
setAdmin,
setGuest,
logout,
authenticate,
signup,
login,
setIdToCall
}}
>
{children}
</SocketUpdateContext.Provider>
</SocketContext.Provider>
);
};
export { ContextProvider, SocketContext, useSocket, useSocketUpdate };
And finally Im rendering the VideoPlayer component in this CallPage:
import { useEffect, useReducer, useState, useContext } from "react";
import styles from "../styles/CallPage.module.css";
import Peer from "simple-peer";
import VideoPlayer from "../components/VideoPlayer";
import Page from "../components/UI/Page";
import Link from "next/link";
import Head from "next/head";
import { useSocket, useSocketUpdate } from "../Store";
const initialState = [];
const CallPage = () => {
const { isAdmin, myVideo, isAuthenticated, isUnauthenticated, user }: any =
useSocket();
if (isUnauthenticated) {
return (
<Page>
<div className={styles.no_match_content}>
<h2 className={styles.h2_no_match}>
Please login first to access a call.
</h2>
<div className={styles.btn_no_match}>
<Link passHref href="/login">
Login
</Link>
</div>
</div>
</Page>
);
}
let alertTimeout = null;
const [messageList, messageListReducer] = useReducer(
MessageListReducer,
initialState
);
useEffect(() => {
if (isAdmin) {
setMeetInfoPopup(true);
}
}, []);
return (
<div className={styles.page_container}>
<VideoPlayer className={styles.video_container} />
)}
</div>
);
};
export default CallPage;
When I click on my home to take me to CallPage, by doing this:
<Link
passHref
href="/callpage"
>
<Button
loadingText="Loading"
colorScheme="teal"
variant="outline"
spinnerPlacement="start"
className={styles.btn}
onClick={handleAdmin}
>
New Meeting
</Button>
</Link>
The router takes me and everything is rendered but the VideoRef. If I refresh the page it then loads. Do you have any idea why this happens? The devtools throws an error that says that
function components cannot be given refs
even though Im using such ref in a DOM element as you can see. So I have already ruled out using forwardref. I would really appreciate any help. Im clueless here.