import React, { useEffect, useRef, useState } from “react”;
import {
View,
Text,
TouchableOpacity,
StyleSheet,
Platform,
PermissionsAndroid,
ActivityIndicator,
Alert,
} from “react-native”;
import {
EventType,
useZoom,
ZoomVideoSdkUser,
ZoomView,
VideoAspect,
} from “@zoom/react-native-videosdk”;
import Ionicons from “react-native-vector-icons/Ionicons”;
import { generateVideoSDKSignature } from “./generateVideoSDKSignature”;
const SDK_KEY = “”;
const SDK_SECRET = “”;
const SESSION_NAME = “84512927821”;
const USER_NAME = “Test Koti”;
const CallPeer = () => {
const zoom = useZoom();
const listeners = useRef<any>();
const [localUser, setLocalUser] = useState<ZoomVideoSdkUser | null>(null);
const [remoteUsers, setRemoteUsers] = useState<ZoomVideoSdkUser>();
const [isInSession, setIsInSession] = useState(false);
const [isJoining, setIsJoining] = useState(false);
const [micOn, setMicOn] = useState(true);
const [videoOn, setVideoOn] = useState(false);
//
Request permissions
useEffect(() => {
if (Platform.OS === “android”) {
PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.CAMERA,
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
\]);
}
}, );
//
Refresh user lists
const refreshUsers = async () => {
try {
const meRaw = await zoom.session.getMySelf();
const me = new ZoomVideoSdkUser(meRaw);
const remoteRaw = await zoom.session.getRemoteUsers();
const remote = remoteRaw.map((r: any) => new ZoomVideoSdkUser(r));
setLocalUser(me);
setRemoteUsers(remote);
console.log(“
refreshUsers ->”, {
local: { name: me.userName, video: me.videoStatus?.isOn },
remote: remote.map((u) => ({
name: u.userName,
videoOn: u.videoStatus?.isOn,
})),
});
} catch (e) {
console.log(“refreshUsers error:”, e);
}
};
//
Setup Zoom event listeners
const setupListeners = () => {
if (!zoom) return;
listeners.current.forEach((l) => l.remove());
listeners.current = ;
const onSessionJoin = zoom.addListener(EventType.onSessionJoin, async () => {
console.log(“
Joined session”);
setIsInSession(true);
await refreshUsers();
try {
await zoom.audioHelper.startAudio();
const me = await zoom.session.getMySelf();
await zoom.audioHelper.unmuteAudio(me.userId);
console.log(“
Audio started”);
} catch (e) {
console.log(“Audio start error:”, e);
}
try {
await zoom.videoHelper.startVideo();
setVideoOn(true);
console.log(“
Local video started”);
} catch (e) {
console.log(“Video start error:”, e);
}
setIsJoining(false);
});
const onUserJoin = zoom.addListener(EventType.onUserJoin, async () => {
console.log(“
User joined”);
await refreshUsers();
});
const onUserLeave = zoom.addListener(EventType.onUserLeave, async () => {
console.log(“
User left”);
await refreshUsers();
});
//
Handle remote user video status change
const onUserVideoStatusChanged = zoom.addListener(
EventType.onUserVideoStatusChanged,
async ({ changedUsers }) => {
console.log(“
Video status changed:”, changedUsers);
for (const changedUser of changedUsers) {
const user = new ZoomVideoSdkUser(changedUser);
console.log(
`🎥 ${user.userName}: video ${user.videoStatus?.isOn ? “ON” : “OFF”}`
);
try {
if (user.videoStatus?.isOn) {
await zoom.videoHelper.subscribe(user.userId);
} else {
await zoom.videoHelper.unsubscribe(user.userId);
}
} catch (err) {
console.log(“Subscription error:”, err);
}
}
// Refresh after change
await refreshUsers();
}
);
const onSessionLeave = zoom.addListener(EventType.onSessionLeave, () => {
console.log(“
Session ended”);
setIsInSession(false);
setLocalUser(null);
setRemoteUsers();
setVideoOn(false);
setMicOn(true);
});
listeners.current.push(
onSessionJoin,
onUserJoin,
onUserLeave,
onUserVideoStatusChanged,
onSessionLeave
);
};
useEffect(() => {
setupListeners();
return () => listeners.current.forEach((l) => l.remove());
}, [zoom]);
//
Manage remote subscriptions
useEffect(() => {
if (!zoom || remoteUsers.length === 0) return;
const manageSubscriptions = async () => {
for (const user of remoteUsers) {
try {
if (user.videoStatus?.isOn) {
await zoom.videoHelper.subscribe(user.userId);
} else {
await zoom.videoHelper.unsubscribe(user.userId);
}
} catch (err) {
console.log(“Subscription error:”, err);
}
}
};
manageSubscriptions();
}, [remoteUsers, zoom]);
//
Join session
const handleJoin = async () => {
try {
setIsJoining(true);
const token = await generateVideoSDKSignature(
SDK_KEY,
SDK_SECRET,
SESSION_NAME,
USER_NAME,
0
);
await zoom.joinSession({
sessionName: SESSION_NAME,
userName: USER_NAME,
token,
sessionIdleTimeoutMins: 2,
audioOptions: { connect: true, mute: false,autoAdjustSpeakerVolume:true },
videoOptions: { localVideoOn: true },
});
} catch (e: any) {
console.log(“Join error:”, e);
Alert.alert(“Join Error”, e.message || “Failed to join session”);
setIsJoining(false);
}
};
//
Leave session
const leaveSession = async () => {
try {
await zoom.leaveSession(false);
setIsInSession(false);
setLocalUser(null);
setRemoteUsers();
} catch (e: any) {
Alert.alert(“Leave Error”, e.message || “Failed to leave session”);
}
};
//
Toggle mic
const toggleMic = async () => {
try {
const me = await zoom.session.getMySelf();
await zoom.audioHelper.startAudio();
if (micOn) {
await zoom.audioHelper.muteAudio(me.userId);
setMicOn(false);
} else {
await zoom.audioHelper.unmuteAudio(me.userId);
setMicOn(true);
}
} catch (e) {
console.log(“Mic toggle error:”, e);
}
};
//
Toggle video
const toggleVideo = async () => {
try {
if (videoOn) {
await zoom.videoHelper.stopVideo();
setVideoOn(false);
} else {
await zoom.videoHelper.startVideo();
setVideoOn(true);
}
await refreshUsers();
} catch (e) {
console.log(“Video toggle error:”, e);
}
};
//
Render video & placeholder
const renderVideo = (user: ZoomVideoSdkUser) => (
<ZoomView
key={`${user.userId}-${user.videoStatus?.isOn ? “on” : “off”}`}
userId={user.userId}
fullScreen={false}
videoAspect={VideoAspect.PanAndScan}
style={styles.videoView}
/>
);
const renderPlaceholder = (user: ZoomVideoSdkUser) => (
{user.userName?.[0]?.toUpperCase() || “U”}
<Text style={{ color: “#ccc”, marginTop: 4 }}>Video Off
);
if (isJoining)
return (
Joining session…
);
if (!isInSession)
return (
Session: {SESSION_NAME}
Join Session
);
return (
{/* Remote Users */}
{remoteUsers.length > 0 ? (
remoteUsers.map((user) => (
<View
key={`${user.userId}-${user.videoStatus?.isOn}`}
style={{ flex: 1, width: “100%”, height: “100%” }}
>
{user.videoStatus?.isOn
? renderVideo(user)
: renderPlaceholder(user)}
))
) : (
Waiting for remote user…
)}
{/* Local Self-view */}
{localUser && (
{videoOn ? renderVideo(localUser) : renderPlaceholder(localUser)}
)}
{/* Controls */}
<Ionicons name={micOn ? “mic” : “mic-off”} size={26} color=“#fff” />
<Ionicons
name={videoOn ? “videocam” : “videocam-off”}
size={26}
color=“#fff”
/>
<TouchableOpacity
style={[styles.controlBtn, { backgroundColor: “#d32f2f” }]}
onPress={leaveSession}
>
);
};
export default CallPeer;
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: “#000” },
center: { flex: 1, justifyContent: “center”, alignItems: “center” },
title: { color: “#fff”, fontSize: 18, marginBottom: 20 },
joinBtn: {
backgroundColor: “#d32f2f”,
paddingVertical: 14,
paddingHorizontal: 32,
borderRadius: 8,
},
btnText: { color: “#fff”, fontWeight: “600”, fontSize: 16 },
remoteContainer: { flex: 1, justifyContent: “center”, alignItems: “center” },
videoView: { width: “100%”, height: “100%”, backgroundColor: “#111” },
selfViewBox: {
position: “absolute”,
bottom: 120,
right: 20,
width: 120,
height: 160,
borderRadius: 12,
overflow: “hidden”,
borderWidth: 2,
borderColor: “#444”,
},
placeholder: {
flex: 1,
justifyContent: “center”,
alignItems: “center”,
backgroundColor: “#333”,
},
placeholderText: { color: “#fff”, fontSize: 18, marginTop: 8 },
waitingText: { color: “#ccc”, fontSize: 16 },
controls: {
flexDirection: “row”,
justifyContent: “space-evenly”,
alignItems: “center”,
paddingVertical: 16,
backgroundColor: “#1c1c1c”,
},
controlBtn: {
backgroundColor: “#1976d2”,
padding: 16,
borderRadius: 50,
},
loaderText: { color: “#fff”, marginTop: 10 },
});