Issue creating session immediately after leaving another session

Zoom Video SDK - 1.11.0
React

Hey,
I’m trying to use zoom video sdk to facilitate calls between two clients. I have the call working fine, but when I end the call and go to start a new one, with a new session name, I get an error that the meeting is closed, as if it is trying to join the original meeting.

{type: ‘IMPROPER_MEETING_STATE’, reason: ‘closed’}

How To Reproduce

  1. Create meeting using zoom video SDK
            await client.init('en-US', 'Global', { patchJsMedia: true });
            await client.join(sessionId, jwt, username);
  1. Have both clients leave the session
            await client.leave();
  1. Generate a new JWT and sessionId
    (this is done in my backend)
  2. Try and join the new session
            await client.init('en-US', 'Global', { patchJsMedia: true });
            await client.join(sessionId, jwt, username);

This causes the error above as if I was trying to join the session that has been closed due to everyone leaving

1 Like

I faced the same issue. The support team’s help is needed here.

We have implemented the webinar functionality in which users can enter and leave the room.
After the user has left the room and tries to join it again, we get an error when enabling their camera:

await stream.startVideo(option);
    "type": "IMPROPER_MEETING_STATE",
    "reason": "closed"
main.d8214fab.js:1 Uncaught TypeError: Cannot read properties of undefined (reading 'style')

Zoom Video SDK - 1.11.0
React 17.0.2

Hey @hazzal @sergey77

Thanks for your feedback.

{type: ‘IMPROPER_MEETING_STATE’, reason: ‘closed’}

This error is not caused by the client.join method. It may be triggered by the call of other methods, such as invoking the stream.stopAudio method for cleanup after the session.

Can you share the problematic session ID with us to help with troubleshooting?

Thanks
Vic

Hey @vic.yang

So the first session I am joining and leaving is

sessionId:  "cpJYlK39Ra6IF7eQ7AnSTQ=="
topic:  "6a074761-a1e2-43a0-ad07-af2e2b436c15"

After both clients leave that session and I try and rejoin and get

sessionId: ""
topic: undefined

Calling getMediaStream() was what was causing the improper_meeting_state error so youre right about that thanks!

Then if i generate a new JWT with a different tpc, I get the same undefined topic and “” sessionId and cant seem to use the client again, even if I destroy the client and re-initialize it I cant use it until i do a hard refresh of the page.

The new session I tried to join had tpc - d7d228fa-ebe9-4a64-8259-6f9fcd083117

Regards
Harry

Hi @vic.yang

Any updates on this? I’m trying to get this resolved as soon as possible so I can finish off the integration with zoom.

Regards Harry

Hey @hazzal

Thank you for providing detailed context regarding the issue.

We checked the error and found that getMediaStream() does not produce this error.

Additionally, we tried rejoining the session multiple times right after leaving, as you mentioned, but we did not encounter any related issues. Could you please check again to see if there are any other error messages in the console that might have been overlooked? For example, errors caused by an incorrect JWT token leading to a failed join session?

Thanks
Vic

Hey @vic.yang

I have no issue rejoining the same session if it hasn’t been ended, the issue I’m encountering comes when I end the session and then try to create a new session with a new JWT.

I want to be able to create a new session with a new JWT without having to refresh my browser, but right now that’s the only way I can manage to. Otherwise it just returns with the undefined that I showed above.

Regards Harry

Video SDK version: 1.11.0
JsMedia version: 15.0.5858
SharedArrayBuffer: false

In my case, this bug is easy to reproduce.
For this, you just need to get out of the room and then get into the same room.

I think the issue is related to this code:

zoomSession.attachVideo

        const zoomSession = zmClient.getMediaStream();

        zmClient.getAllUser().forEach(async (user) => {
            if (user.bVideoOn) {
                await zoomSession.detachVideo(user.userId);
                const userVideo = await zoomSession.attachVideo(user.userId, isMobileScreenSize() ? Video_360P : Video_720P);
                const targetElement = document.getElementById(`${PARTICIPANT_VIDEO_ID}_${user.userId}`)?.querySelector("video-player-container");
                while (targetElement?.firstChild) {
                    targetElement?.firstChild?.remove();
                }
                targetElement?.appendChild(userVideo);
            }
        });

After which, the error in the file occurs:
Screenshot 2024-05-21 at 10.07.07

index.umd.js:1 Uncaught TypeError: Cannot read properties of undefined (reading 'style')

Then, there is the issue with:

stream.startVideo
    "type": "IMPROPER_MEETING_STATE",
    "reason": "closed"

The only spike that helped was reloading the browser page before getting back into the room. However, this way doesn’t work for me since it makes the user experience not smooth.

Hey @hazzal

Yes, we tested it exactly as you described: leaving a session and then joining a new session with a new JWT. We did not encounter any issues.

Following is the pseudocode for this process:

await client.leave();
await client.init('en-US', 'Global', { patchJsMedia: true });
await client.join(`[new topic]`, `[new token]`, `[name]`, `[passcode]`);
console.log(client.getSessionInfo());

Please correct me if anything differs from your process.

Thanks
Vic

An update:
In my case, I managed to resolve the issue with:

    "type": "IMPROPER_MEETING_STATE",
    "reason": "closed"

It occurred when I tried to call startVideo before getting an event
zmClient.on(“connection-change”, onConnectionChange); // ConnectionState.Connected.

However, I still can’t fix the error when calling:

zoomSession.attachVideo
index.umd.js:1 Uncaught TypeError: Cannot read properties of undefined (reading 'style')

Screenshot 2024-05-21 at 10.07.07

Hey @sergey77

  index.umd.js:1 Uncaught TypeError: Cannot read properties of undefined (reading 'style')

This is a known console error, but it does not affect functionality.

stream.startVideo

"type": "IMPROPER_MEETING_STATE",
"reason": "closed"

This error is likely caused by calling startVideo before the user has joined the session. Could you add a conditional check before calling this method?

if (client.getSessionInfo()?.isInMeeting) {
  await stream.startVideo();
}

Thanks
Vic

1 Like

Thanks for the support.
The issue has been resolved for me.

I really hope Zoom will manage to fix the issue in the nearest time:

index.umd.js:1 Uncaught TypeError: Cannot read properties of undefined (reading 'style')

Screenshot 2024-05-21 at 10.07.07

@vic.yang

Hey @vic.yang

In your testing did you join with two clients or just one? The issue i’m encountering happens after I join on two clients and leave on both, then I cant rejoin the original meeting or any others.

            const zoomJwtResponse = await consultationService.getZoomJwt(consultationId);

            if (!zoomJwtResponse || !zoomJwtResponse.session || !zoomJwtResponse.jwt) {
                console.error("Invalid Zoom JWT response", zoomJwtResponse);
                return;
            }

            await client.init('en-US', 'Global', { patchJsMedia: true, leaveOnPageUnload: true });
            await client.join(zoomJwtResponse.session, zoomJwtResponse.jwt, username);
            const zoomStream = await client.getMediaStream();

Then I have a listener for the ‘user-removed’ event

            client.on('user-removed', async () => {
                await client.leave();
            });

So that when either person leaves they both leave, after one client leaves I recreate the session on the backend (session being the topic used to create the JWT) and then when i call

    const joinClient = useCallback(async () => {
        try {
            const remoteViewElement = canvasElementRef.current;
            const zoomJwtResponse = await consultationService.getZoomJwt(consultationId);
            await client.init('en-US', 'Global', { patchJsMedia: true, leaveOnPageUnload: true });
            await client.join(zoomJwtResponse.session, zoomJwtResponse.jwt, username);
            const zoomStream = await client.getMediaStream();
            setStream(zoomStream);
            client.getAllUser().forEach(async (user: Participant) => {
                if (user.bVideoOn && remoteViewElement && stream) {
                    await stream.renderVideo(remoteViewElement, user.userId, CANVAS_WIDTH, CANVAS_HEIGHT, undefined, undefined, 4);
                    setOtherTrackStatus(TrackStatus.On);
                }
            });
        } catch (error) {
            console.error("Error joining Zoom session:", error);
        }
    }, [client, consultationId, consultationService, username, stream]);

I get that error I mentioned earlier, and if call client.getSessionInfo();

I get this

{isInMeeting: false, password: '', topic: undefined, userName: '', userId: NaN, …}
isInMeeting: false,
password: ""
sessionId: ""
topic: undefined
userId: NaN
userName: ""

I get the same result if I try and join the original meeting after leaving with both clients.

Hey @hazzal

Thank you for providing the code snippet related to this issue.

Regarding the requirement:

So that when either person leaves they both leave, after one client leaves

It would be better to use client.leave(true) method to end the session on the host side.

As for the timing of joining a new session, there are some risks in the code you mentioned above. I’m not sure if the joinClient method is being executed after the client.leave() promise is resolved.

It’s best to ensure this call order or listen for the connection-change event and proceed with the next steps when the payload.state is ConnectionState.Closed.

Thanks
Vic

Hey @vic.yang

I was trying to use client.leave(true), but strangely even though when making the jwt I have both as hosts, I wasn’t able to pass true in due to permissions. Regarding the promise potentially not havint resolved yet before rejoining, I am waiting upwards of 5 minutes before trying to create a new session and still encountering the same issues :frowning:

Regards Harry

Hey @hazzal

There can only be one host in a session, so you need to add an isHost condition check when calling client.leave(true).

if (client.isHost()) {
  client.leave(true);
}

As for the join issue, could you provide a more complete example? You can use an online editor like JSFiddle or CodePen, or contact our ISV representative to provide your example.

Thanks
Vic

Hey @vic.yang

I still get the same issue after leaving the session properly like that, and trying to join a new session with a new session ID, I get this after logging out the session info.

Regards Harry

Hey @hazzal

Could you share the detailed code snippet of useZoom.tsx with us for troubleshooting purpose?

Thanks
Vic

Hey @sergey77

I really hope Zoom will manage to fix the issue in the nearest time:

Uncaught TypeError: Cannot read properties of undefined (reading 'style')

We have just released version 1.11.5 of the Video SDK, and this error has been fixed in the latest version.

Thanks
Vic

1 Like

Hey @vic.yang

Can do, theres a decent amount of redudant code in here right now from testing stuff, but here it is.

import ZoomSdk, { MobileVideoFacingMode, Participant } from '@zoom/videosdk';
import { Stream } from '@zoom/videosdk/dist/types/media';
import { useConsultationService } from '~/common/services/consultation.service';
import { createTelehealthCallSession } from '~/common/api/TelehealthCallSession';

const CANVAS_WIDTH = 1280;
const CANVAS_HEIGHT = CANVAS_WIDTH * 9 / 16;

export enum TrackStatus {
    Unknown = "Unknown",
    Enabled = "Enabled",
    Disabled = "Disabled",
    On = "On",
    Off = "Off"
}

interface UseZoomProps {
    username: string;
    isPatient: boolean;
    consultationId: number;
}

interface UseZoomReturn {
    canvasElementRef: React.RefObject<HTMLCanvasElement>;
    selfViewElementRef: React.RefObject<HTMLVideoElement>;
    trackStatus: TrackStatus;
    otherTrackStatus: TrackStatus;
    audioTrackStatus: TrackStatus;
    toggleVideo: () => Promise<void>;
    toggleCameraDirection: () => Promise<void>;
    toggleAudio: () => Promise<void>;
    leaveClient: () => Promise<void>;
    joinClient: () => Promise<void>;
    createNewSession: () => Promise<void>;
    destroyClient: () => void;
}

export const useZoom = ({ username, isPatient, consultationId }: UseZoomProps): UseZoomReturn => {
    const streamRef = useRef<typeof Stream | undefined>(undefined);
    const [trackStatus, setTrackStatus] = useState<TrackStatus>(TrackStatus.Unknown);
    const [otherTrackStatus, setOtherTrackStatus] = useState<TrackStatus>(TrackStatus.Unknown);
    const [audioTrackStatus, setAudioTrackStatus] = useState<TrackStatus>(TrackStatus.Unknown);
    const [videoTrackFacingDirection, setVideoTrackFacingDirection] = useState<MobileVideoFacingMode>(MobileVideoFacingMode.User);
    const client = ZoomSdk.createClient();
    const consultationService = useConsultationService();
    const canvasElementRef = useRef<HTMLCanvasElement>(null);
    const selfViewElementRef = useRef<HTMLVideoElement>(null);

    const renderVideo = useCallback(async (elementRef: HTMLCanvasElement | HTMLVideoElement, userId: number) => {
        if(streamRef.current) {
            await streamRef.current.renderVideo(elementRef, userId, CANVAS_WIDTH, CANVAS_HEIGHT, undefined, undefined, 4);
        }
    }, []);

    const initialiseCall = useCallback(async () => {
        try {
            const remoteViewElement = canvasElementRef.current;
            const selfViewElement = selfViewElementRef.current;
            const zoomJwtResponse = await consultationService.getZoomJwt(consultationId);

            if (!zoomJwtResponse || !zoomJwtResponse.session || !zoomJwtResponse.jwt) {
                console.error("Invalid Zoom JWT response", zoomJwtResponse);
                return;
            }

            await client.init('en-US', 'Global', { patchJsMedia: true, leaveOnPageUnload: true });
            await client.join(zoomJwtResponse.session, zoomJwtResponse.jwt, username);
            console.log('session', client.getSessionInfo());

            const zoomStream = await client.getMediaStream();
            console.log(zoomStream)
            streamRef.current = zoomStream;

            client.getAllUser().forEach(async (user: Participant) => {
                if (user.bVideoOn && remoteViewElement) {
                    await renderVideo(remoteViewElement, user.userId);
                    setOtherTrackStatus(TrackStatus.On);
                }
            });

            client.on('peer-video-state-change', async (payload: { action: string; userId: any; }) => {
                if (payload.action === 'Start' && remoteViewElement) {
                    setOtherTrackStatus(TrackStatus.On);
                    await renderVideo(remoteViewElement, payload.userId);
                } else if (payload.action === 'Stop' && remoteViewElement) {
                    await zoomStream.stopRenderVideo(remoteViewElement, payload.userId);
                    setOtherTrackStatus(TrackStatus.Off);
                }
            });

            client.on('user-removed', async () => {
                await client.leave();
            });

            await zoomStream.startAudio();
            setAudioTrackStatus(TrackStatus.On);

            if (isPatient && selfViewElement) {
                await zoomStream.startVideo({ hd: true });
                await renderVideo(selfViewElement, client.getCurrentUserInfo().userId);
                setTrackStatus(TrackStatus.On);
            }
        } catch (error) {
            console.error("Error joining Zoom session:", error);
        }
    }, [client, consultationId, consultationService, isPatient, renderVideo, username]);

    useEffect(() => {
        void initialiseCall();
    }, [initialiseCall]);

    const toggleVideo = useCallback(async () => {
        const selfViewElement = selfViewElementRef.current;
        if (trackStatus === TrackStatus.Off || trackStatus === TrackStatus.Unknown) {
            if (streamRef.current && selfViewElement) {
                await streamRef.current.startVideo({ hd: true });
                await renderVideo(selfViewElement, client.getCurrentUserInfo().userId);
                setTrackStatus(TrackStatus.On);
            }
        } else {
            if (streamRef.current && selfViewElement) {
                setTrackStatus(TrackStatus.Off);
                await streamRef.current.stopVideo();
                await streamRef.current.stopRenderVideo(selfViewElement, client.getCurrentUserInfo().userId);
            }
        }
    }, [client, renderVideo, trackStatus]);

    const toggleCameraDirection = useCallback(async () => {
        if (streamRef.current && trackStatus === TrackStatus.On) {
            if (videoTrackFacingDirection === MobileVideoFacingMode.User) {
                setVideoTrackFacingDirection(MobileVideoFacingMode.Environment);
                await streamRef.current.switchCamera(MobileVideoFacingMode.Environment);
            } else {
                setVideoTrackFacingDirection(MobileVideoFacingMode.User);
                await streamRef.current.switchCamera(MobileVideoFacingMode.User);
            }
        }
    }, [trackStatus, videoTrackFacingDirection]);

    const toggleAudio = useCallback(async () => {
        if (audioTrackStatus === TrackStatus.Off) {
            if (streamRef.current) {
                await streamRef.current.unmuteAudio();
                setAudioTrackStatus(TrackStatus.On);
            }
        } else {
            if (streamRef.current) {
                await streamRef.current.muteAudio();
                setAudioTrackStatus(TrackStatus.Off);
            }
        }
    }, [audioTrackStatus]);

    const leaveClient = useCallback(async () => {
        try {
            console.log("Leaving Zoom session");
            await client.leave();
            // ZoomSdk.destroyClient();
        } catch (error) {
            console.error("Error leaving Zoom session:", error);
        }
    }, [client]);

    const joinClient = useCallback(async () => {
        try {
            const remoteViewElement = canvasElementRef.current;
            const zoomJwtResponse = await consultationService.getZoomJwt(consultationId);
            console.log('joining zoom session with', zoomJwtResponse)
            await client.init('en-US', 'Global', { patchJsMedia: true, leaveOnPageUnload: true });
            await client.join(zoomJwtResponse.session, zoomJwtResponse.jwt, username);
            console.log('session', client.getSessionInfo())
            const zoomStream = await client.getMediaStream();
            streamRef.current = zoomStream;
            await streamRef.current.startVideo();
            client.getAllUser().forEach(async (user: Participant) => {
                if (user.bVideoOn && remoteViewElement && streamRef.current) {
                    await renderVideo(remoteViewElement, user.userId);
                    setOtherTrackStatus(TrackStatus.On);
                }
            });
        } catch (error) {
            console.error("Error joining Zoom session:", error);
        }
    }, [client, consultationId, consultationService, renderVideo, username]);

    const createNewSession = useCallback(async () => {
        await createTelehealthCallSession(consultationId);
    }, [consultationId]);

    const destroyClient = useCallback(() => {
        ZoomSdk.destroyClient();
    }, []);

    return {
        canvasElementRef,
        selfViewElementRef,
        trackStatus,
        otherTrackStatus,
        audioTrackStatus,
        toggleVideo,
        toggleCameraDirection,
        toggleAudio,
        leaveClient,
        joinClient,
        createNewSession,
        destroyClient,
    };
};