import {
StreamVideo,
StreamCall,
SpeakerLayout,
CallControls,
useCall,
StreamTheme,
} from "@stream-io/video-react-sdk";
import "@stream-io/video-react-sdk/dist/css/styles.css";
export default function App() {
// create client and call
return (
<StreamTheme>
<StreamVideo client={client}>
<StreamCall call={call}>
<MyCallUI />
</StreamCall>
</StreamVideo>
</StreamTheme>
);
}
const MyCallUI = () => {
const call = useCall();
return (
<div>
<MyPermissionRequestNotifications />
<SpeakerLayout />
<CallControls />
<MyPermissionRequests />
</div>
);
};
const MyPermissionRequests = () => {};
const MyPermissionRequestNotifications = () => {};Permission Requests
Build custom UI for permission requests - allowing participants to request audio/video/screen-share permissions, and moderators to accept/reject them.
Best Practices
- Use
useRequestPermissionhook for sending requests - it handles permission checking and request state. - Check
canRequestPermissionbefore showing request buttons. - Listen to
call.permission_requestevent to receive incoming requests. - Use
call.grantPermissions()to accept,call.revokePermissions()to reject. - Only users with
UPDATE_CALL_PERMISSIONScapability can approve/reject requests.
Start point
Let's start with a simple call UI where we create an audio_room call.
Permission requests are only relevant for call types that have roles where a participant can't publish their audio/video/screen-share by default.
This guide shows how to implement PermissionRequests to send permission requests and PermissionRequestNotifications to receive them.
To follow along with the guide, don't forget to turn off backstage mode so others can join:
let call: Call;
await call.goLive();Request permission
In this step we will implement the MyPermissionRequests and the MyPermissionRequestButton components to send permission requests.
For readability the code snippet only contains the MyPermissionRequests and the MyPermissionRequestButton implementation, the full example is available at the end of the guide.
import {
useRequestPermission,
useCallStateHooks,
OwnCapability,
} from "@stream-io/video-react-sdk";
const MyPermissionRequestButton = ({ children, capability }) => {
const {
requestPermission,
hasPermission,
canRequestPermission,
isAwaitingPermission,
} = useRequestPermission(capability);
if (hasPermission || !canRequestPermission) return null;
return (
<button onClick={requestPermission} disabled={isAwaitingPermission}>
{children}
</button>
);
};
const MyPermissionRequests = () => {
const call = useCall();
const { useHasPermissions } = useCallStateHooks();
const canSendAudio = useHasPermissions(OwnCapability.SEND_AUDIO);
const canSendVideo = useHasPermissions(OwnCapability.SEND_VIDEO);
const canShareScreen = useHasPermissions(OwnCapability.SCREENSHARE);
if (!call) return null;
return (
<div>
<MyPermissionRequestButton capability={OwnCapability.SEND_AUDIO}>
Request audio permission
</MyPermissionRequestButton>
<MyPermissionRequestButton capability={OwnCapability.SEND_VIDEO}>
Request video permission
</MyPermissionRequestButton>
<MyPermissionRequestButton capability={OwnCapability.SCREENSHARE}>
Request screen share permission
</MyPermissionRequestButton>
{canSendAudio ? "Allowed to send audio" : "Not allowed to send audio"}
{canSendVideo ? "Allowed to send video" : "Not allowed to send video"}
{canShareScreen
? "Allowed to share screen"
: "Not allowed to share screen"}
</div>
);
};Let's unpack the above code snippet:
- We only display the request button if the user is allowed to request the specific capability and doesn't already have that permission. For more information, check out the permissions guide.
- To send the request, we are using the
requestPermissionfunction (wrapper aroundrequestPermissionsmethod of thecallinstance) which comes from theuseRequestPermissionhook. - We use the
useHasPermissionshook to be notified when the request was approved. Alternatively you can subscribe to thecall.permissions_updatedevent.
Receive permission requests
In this step we will implement the MyPermissionRequestNotifications component to receive permission requests.
For readability, the code snippet only contains the MyPermissionRequestNotifications implementation, the full example is available at the end of the guide.
const MyPermissionRequestNotifications = () => {
const call = useCall();
const { useLocalParticipant, useHasPermissions } = useCallStateHooks();
const localParticipant = useLocalParticipant();
const canUpdateCallPermissions = useHasPermissions(
OwnCapability.UPDATE_CALL_PERMISSIONS,
);
const [permissionRequests, setPermissionRequests] = useState([]);
useEffect(() => {
if (!call || !canUpdateCallPermissions) return;
return call.on("call.permission_request", (event) => {
// ignore own requests
if (event.user.id !== localParticipant?.userId) {
setPermissionRequests((requests) => [...requests, event]);
}
});
}, [call, canUpdateCallPermissions, localParticipant]);
if (!call || permissionRequests.length === 0) return null;
const answerRequest = async (answer, request) => {
if (answer === "accept") {
await call.grantPermissions(request.user.id, request.permissions);
} else {
await call.revokePermissions(request.user.id, request.permissions);
}
setPermissionRequests((requests) => requests.filter((r) => r !== request));
};
return (
<div>
{permissionRequests.map((request) => (
<div>
New request from {request.user.id} to publish {request.permissions}
<button onClick={() => answerRequest("accept", request)}>
Accept
</button>
<button onClick={() => answerRequest("reject", request)}>
Reject
</button>
</div>
))}
</div>
);
};Let's unpack the above code snippet:
- We only show the permission requests to users that have the necessary capability to accept/reject:
call.hasPermission(OwnCapability.UPDATE_CALL_PERMISSIONS) - We subscribe to listen to
'call.permission_request'WS event to update the list of requests - A permission request can be accepted with the
grantPermissionsmethod of theCallinstance - A permission request can be rejected with the
revokePermissionsmethod of theCallinstance. This method can also be used to revoke permissions later in the call.
Finished example
Here is the full example:
import { useEffect, useState } from "react";
import {
CallControls,
CallParticipantsView,
OwnCapability,
PermissionRequestEvent,
StreamCall,
StreamTheme,
StreamVideo,
StreamVideoEvent,
useCall,
useCallStateHooks,
useRequestPermission,
} from "@stream-io/video-react-sdk";
import "@stream-io/video-react-sdk/dist/css/styles.css";
export default function App() {
// create client and call
return (
<StreamTheme>
<StreamVideo client={client}>
<StreamCall call={call}>
<MyCallUI />
</StreamCall>
</StreamVideo>
</StreamTheme>
);
}
const MyCallUI = () => {
const call = useCall();
return (
<div>
<MyPermissionRequestNotifications />
<CallParticipantsView call={call} />
<CallControls />
<MyPermissionRequests />
</div>
);
};
const MyPermissionRequestButton = ({ children, capability }) => {
const {
requestPermission,
hasPermission,
canRequestPermission,
isAwaitingPermission,
} = useRequestPermission(capability);
if (hasPermission || !canRequestPermission) return null;
return (
<button disabled={isAwaitingPermission} onClick={requestPermission}>
{children}
</button>
);
};
const MyPermissionRequests = () => {
const call = useCall();
const { useHasPermissions } = useCallStateHooks();
const canSendAudio = useHasPermissions(OwnCapability.SEND_AUDIO);
const canSendVideo = useHasPermissions(OwnCapability.SEND_VIDEO);
const canShareScreen = useHasPermissions(OwnCapability.SCREENSHARE);
if (!call) return null;
return (
<div>
<MyPermissionRequestButton capability={OwnCapability.SEND_AUDIO}>
Request audio permission
</MyPermissionRequestButton>
<MyPermissionRequestButton capability={OwnCapability.SEND_VIDEO}>
Request video permission
</MyPermissionRequestButton>
<MyPermissionRequestButton capability={OwnCapability.SCREENSHARE}>
Request screen share permission
</MyPermissionRequestButton>
{canSendAudio ? "Allowed to send audio" : "Not allowed to send audio"}
{canSendVideo ? "Allowed to send video" : "Not allowed to send video"}
{canShareScreen
? "Allowed to share screen"
: "Not allowed to share screen"}
</div>
);
};
const MyPermissionRequestNotifications = () => {
const call = useCall();
const { useLocalParticipant, useHasPermissions } = useCallStateHooks();
const localParticipant = useLocalParticipant();
const canUpdateCallPermissions = useHasPermissions(
OwnCapability.UPDATE_CALL_PERMISSIONS,
);
const [permissionRequests, setPermissionRequests] = useState([]);
useEffect(() => {
if (!call || !canUpdateCallPermissions) return;
return call.on("call.permission_request", (event) => {
// ignore own requests
if (event.user.id !== localParticipant?.userId) {
setPermissionRequests((requests) => [...requests, event]);
}
});
}, [call, canUpdateCallPermissions, localParticipant]);
if (!call || permissionRequests.length === 0) return null;
const answerRequest = async (answer, request) => {
if (answer === "accept") {
await call.grantPermissions(request.user.id, request.permissions);
} else {
await call.revokePermissions(request.user.id, request.permissions);
}
setPermissionRequests((requests) => requests.filter((r) => r !== request));
};
return (
<div>
{permissionRequests.map((request) => (
<div>
New request from {request.user.id} to publish {request.permissions}
<button onClick={() => answerRequest("accept", request)}>
Accept
</button>
<button onClick={() => answerRequest("reject", request)}>
Reject
</button>
</div>
))}
</div>
);
};