@videosdk.live/react-native-sdk
Version:
<h1 align="center"> <img src="https://static.videosdk.live/videosdk_logo_website_black.png"/><br/> <p align="center"> Video SDK React Native App <br/> <a href="https://videosdk.live/">videosdk.live</a> </p> </h1>
439 lines (409 loc) • 11 kB
JavaScript
import React, { useEffect, useRef, useState } from "react";
import { PermissionsAndroid, Platform } from "react-native";
import useFile from "./upload/useFile";
import useMediaDevice from "./useMediaDevice";
import {
mediaDevices,
registerGlobals,
RTCView,
MediaStream,
MediaStreamTrack,
permissions,
VideoProcessor,
SystemAudioShare
} from "@videosdk.live/react-native-webrtc";
import {
useMeeting as OldUseMeeting,
MeetingProvider as OldMeetingProvider,
useParticipant as OldUseParticipant,
MeetingConsumer,
usePubSub,
useConnection,
Constants,
createCameraVideoTrack,
createMicrophoneAudioTrack,
createScreenShareVideoTrack,
useTranscription,
useCharacter,
useWhiteboard,
} from "@videosdk.live/react-sdk";
import ReactNativeForegroundService from "@videosdk.live/react-native-foreground-service";
import InCallManager from "@videosdk.live/react-native-incallmanager";
import { version } from "./package.json";
import E2EEManager from "./e2ee/e2eeManager";
import useKeyProvider from "./e2ee/useKeyProvider";
let notificationObj = null;
let e2eeManager = null;
function generateTaskName(length) {
let result = "";
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
/**@deprecated */
const getAudioDeviceList = async () => {
const devices = await InCallManager.getAudioDeviceList();
if (devices.length > 0) {
return devices.includes("WIRED_HEADSET") || devices.includes("BLUETOOTH")
? devices.filter((d) => d !== "EARPIECE")
: devices;
} else {
return null;
}
};
const switchAudioDevice = async (device) => {
if (Platform.OS == "android") {
await InCallManager.switchAudioDevice(device);
} else {
switch (device) {
case "EARPIECE":
InCallManager.setForceSpeakerphoneOn(false);
break;
case "SPEAKER_PHONE":
InCallManager.setForceSpeakerphoneOn(true);
break;
case "BLUETOOTH":
InCallManager.setForceSpeakerphoneOn(false);
break;
case "WIRED_HEADSET":
InCallManager.setForceSpeakerphoneOn(false);
break;
default:
InCallManager.setForceSpeakerphoneOn(true);
}
}
};
const applyVideoProcessor = (name) => {
VideoProcessor.applyVideoProcessor(name);
};
const removeVideoProcessor = () => {
VideoProcessor.removeVideoProcessor();
};
const foregroundServiceInitialize = async () => {
const { title, message } = notificationObj;
const id = Math.floor(Math.random() * 1000);
await ReactNativeForegroundService.start({
id,
title: title ? title : "Provide notification title",
message: message ? message : "Provide notification message",
});
};
const foregroundServiceTerminate = async () => {
if (ReactNativeForegroundService.is_running()) {
await ReactNativeForegroundService.stopAll();
}
};
const initialize = ({ notification }) => {
notificationObj = notification;
InCallManager.start({ media: "video" });
};
const terminate = () => {
VideoProcessor.removeVideoProcessor();
InCallManager.stop();
e2eeManager = null;
foregroundServiceTerminate();
};
const register = async () => {
registerGlobals();
navigator.mediaDevices.getDisplayMedia =
mediaDevices.getDisplayMedia.bind(mediaDevices);
};
function MeetingProvider(props) {
const [deviceInfoConfig, setDeviceInfoConfig] = useState(null);
const { keyProvider } = props;
if (keyProvider) {
e2eeManager = new E2EEManager(keyProvider);
}
useEffect(() => {
Platform.OS === "android" && requestBluetoothConnectPermission();
InCallManager.getDeviceInfo().then((deviceData) => {
const deviceInfo = {
...props,
config: { ...props.config, deviceInfo: true },
deviceInfo: {
sdkType: "react-native",
sdkVersion: version,
platform: Platform.OS,
deviceUserAgent: deviceData,
browserUserAgent: "null",
},
};
delete deviceInfo.keyProvider;
setDeviceInfoConfig(deviceInfo);
});
}, []);
useEffect(() => {
return () => {
terminate();
};
}, []);
const requestBluetoothConnectPermission = async () => {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
{
title:
"This Permission will hep you to communicate over Bluetooth Peripherals",
buttonNegative: "Cancel",
buttonPositive: "OK",
}
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
console.log("Bluetooth Connect Permission Granted");
} else {
console.log("Bluetooth Connect Permission Denied");
}
} catch (err) {
console.warn(err);
}
};
return deviceInfoConfig ? (
<OldMeetingProvider {...deviceInfoConfig}>
<MeetingConsumer
onMeetingJoined={() => {
if (props.config.hasOwnProperty("notification")) {
initialize(props.config);
} else {
initialize({
notification: {
title: "Notification title",
message: "Notification message",
},
});
}
}}
onMeetingLeft={terminate}
>
{({}) => {
return props.children;
}}
</MeetingConsumer>
</OldMeetingProvider>
) : (
<></>
);
}
function useParticipant(
participantId,
{
onStreamEnabled,
onStreamDisabled,
onMediaStatusChanged,
onVideoQualityChanged,
onE2EEStateChanged = () => {}, // default no-op function
} = {}
) {
const data = OldUseParticipant(participantId, {
onStreamEnabled,
onStreamDisabled,
onMediaStatusChanged,
onVideoQualityChanged,
onProducerAdded: (data) => {
handleOnProducerAdded(data);
},
onProducerRemoved: (data) => {
handleOnProducerRemoved(data);
},
onConsumerAdded: (data) => {
handleOnConsumerAdded(data);
},
onConsumerRemoved: (data) => {
handleOnConsumerRemoved(data);
},
});
const dataRef = useRef(data);
useEffect(() => {
dataRef.current = data;
}, [data]);
async function handleOnProducerAdded(data) {
if (!e2eeManager) return;
if (data.rtpSender._id && data.rtpSender._peerConnectionId) {
let e2eCallback = (state) => {
let stateinfo = {
state: state,
kind: data.rtpSender._track.kind,
};
if (onE2EEStateChanged) {
onE2EEStateChanged(stateinfo);
}
};
e2eeManager.setupE2EESender(data, e2eCallback);
}
}
async function handleOnProducerRemoved(data) {
if (!e2eeManager) return;
e2eeManager.closeCryptor(data);
}
async function handleOnConsumerAdded(data) {
if (!e2eeManager) return;
if (data.rtpReceiver._id && data.rtpReceiver._peerConnectionId) {
let e2eCallback = (state) => {
let stateinfo = {
state: state,
kind: data.rtpReceiver._track.kind,
};
onE2EEStateChanged(stateinfo);
};
e2eeManager.setupE2EEReceiver(data, e2eCallback);
}
}
async function handleOnConsumerRemoved(data) {
if (!e2eeManager) return;
e2eeManager.closeCryptor(data);
}
const captureImage = async ({ height, width } = {}) => {
try {
if (dataRef.current.isLocal) {
if (dataRef.current.webcamStream == null) {
throw new Error("Camera must be on to capture an image");
}
const base64 = await new MediaStream([
dataRef.current.webcamStream?.track,
]).getBitMap({ height, width });
return base64;
} else {
throw new Error("You can only capture a image of LocalParticipant");
}
} catch (err) {
console.error("err on image capture", err);
}
};
return {
...data,
captureImage,
};
}
function useMeeting({
onParticipantJoined,
onParticipantLeft,
onSpeakerChanged,
onPresenterChanged,
onMainParticipantChanged,
onEntryRequested,
onEntryResponded,
onRecordingStarted,
onRecordingStopped,
onChatMessage,
onMeetingJoined,
onMeetingLeft,
onLiveStreamStarted,
onLiveStreamStopped,
onVideoStateChanged,
onVideoSeeked,
onWebcamRequested,
onMicRequested,
onPinStateChanged,
onConnectionOpen,
onConnetionClose,
onSwitchMeeting,
onError,
onHlsStarted,
onHlsStopped,
onHlsStateChanged,
onPausedAllStreams,
onResumedAllStreams,
onRecordingStateChanged,
onLivestreamStateChanged,
onMeetingStateChanged,
onParticipantModeChanged,
} = {}) {
const meeting = OldUseMeeting({
onParticipantJoined,
onParticipantLeft,
onSpeakerChanged,
onPresenterChanged,
onMainParticipantChanged,
onEntryRequested,
onEntryResponded,
onRecordingStarted,
onRecordingStopped,
onChatMessage,
onMeetingJoined,
onMeetingLeft,
onLiveStreamStarted,
onLiveStreamStopped,
onVideoStateChanged,
onVideoSeeked,
onWebcamRequested,
onMicRequested,
onPinStateChanged,
onConnectionOpen,
onConnetionClose,
onSwitchMeeting,
onError,
onHlsStarted,
onHlsStopped,
onHlsStateChanged,
onPausedAllStreams,
onResumedAllStreams,
onRecordingStateChanged,
onLivestreamStateChanged,
onMeetingStateChanged,
onParticipantModeChanged,
});
const enableScreenShare = (customScreenShareTrack = undefined) => {
if (Platform.OS === "android") {
if (!ReactNativeForegroundService.is_running()) {
ReactNativeForegroundService.register({
taskName: generateTaskName(10),
});
foregroundServiceInitialize();
}
meeting.enableScreenShare(customScreenShareTrack);
} else {
meeting.enableScreenShare(customScreenShareTrack);
}
};
const disableScreenShare = () => {
foregroundServiceTerminate();
meeting.disableScreenShare();
SystemAudioShare.stopSystemAudioShare()
};
const toggleScreenShare = (customScreenShareTrack = undefined) => {
if (meeting.localScreenShareOn) {
disableScreenShare();
} else {
enableScreenShare(customScreenShareTrack);
}
};
return {
...meeting,
e2eeEnabled: e2eeManager || false,
enableScreenShare,
disableScreenShare,
toggleScreenShare,
};
}
export {
register,
usePubSub,
useConnection,
useWhiteboard,
Constants,
MeetingConsumer,
MeetingProvider,
useParticipant,
useFile,
RTCView,
MediaStream,
MediaStreamTrack,
permissions,
mediaDevices,
ReactNativeForegroundService,
createCameraVideoTrack,
createMicrophoneAudioTrack,
createScreenShareVideoTrack,
getAudioDeviceList,
switchAudioDevice,
useMediaDevice,
useTranscription,
useMeeting,
useCharacter,
applyVideoProcessor,
removeVideoProcessor,
useKeyProvider,
};