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
Permission requests allow call participants to request permission to publish audio/video/screen-share. Participants with elevated permissions can accept or reject these requests. This guide shows you how you can build a UI for sending and receiving these requests.
The ToggleVideoPublishingButton
, ToggleAudioPublishingButton
and ScreenShareButton
components can send permission requests, you only need to follow this guide if you want to build your own UI for this.
The PermissionRequests
component can receive permission requests, you only need to follow this guide if you want to build your own UI for this.
Prerequisites
Before we can join a call, we need to connect to Stream’s edge infrastructure. To do that, we follow these steps:
- Register for a Stream account and obtain our API key and secret.
- Install the Stream React Video SDK:
npm install @stream-io/video-react-sdk
yarn add @stream-io/video-react-sdk
- Initialize the SDK by passing in your API key, token and user information
- Create and join a call
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.
In this guide, we will implement the PermissionRequests
component to send permission requests and the 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 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>
);
};
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
requestPermission
function (wrapper aroundrequestPermissions
method of theCall
instance) which comes from theuseRequestPermission
hook. - We use the
useHasPermissions
hook to be notified when the request was approved. Alternatively you can subscribe to thecall.permissions_updated
event.
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;
const unsubscribe = call.on(
'call.permission_request',
(event: StreamVideoEvent) => {
// ignore own requests
if (event.user.id !== localParticipant?.userId) {
setPermissionRequests((requests) => [
...requests,
event as PermissionRequestEvent,
]);
}
},
);
return () => {
unsubscribe();
};
}, [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
grantPermissions
method of theCall
instance - A permission request can be rejected with the
revokePermissions
method of theCall
instance. This method can also be used to revoke permissions later in the call.
Finished example
Here is the full example:
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';
import { useEffect, useState } from 'react';
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;
const unsubscribe = call.on(
'call.permission_request',
(event: StreamVideoEvent) => {
// ignore own requests
if (event.user.id !== localParticipant?.userId) {
setPermissionRequests((requests) => [
...requests,
event as PermissionRequestEvent,
]);
}
},
);
return () => {
unsubscribe();
};
}, [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>
);
};