Skip to main content

Send presentation

In the previous lesson we learnt how to receive a presentation from other participants and display it in our interface. Now we will do the reverse. We will add a new button to the interface that, when clicked, will ask the user what they want to share (tab, window or whole screen) and after that, remote users will see that content.

info

Screen sharing from a mobile browser to other devices is not permitted by mobile browsers. Mobile browsers don't support this feature due to security restrictions. If you want to use this feature in a mobile, you will need to create a native application using our Mobile SDKs.

However, a mobile web application is still able to receive screen shares from other devices. The limitation only prevents mobile browsers from sending screen shares.

You can download the starter code from Step.07-Exercise-Send-presentation.

App component

We will start by making some modifications in the src/App.tsx file.

First of all, we will add a new state variable to indicate if the user is sharing their screen or not:

const [screenShared, setScreenShared] = useState(false);

We will also add a new reference to the presentation stream. It will be used by a callback function to get the current stream that is being shared:

const presentationStreamRef = useRef<MediaStream>();

Then we will define the function that will be called once the user click on the share screen button to activate or deactivate this feature:

const handleScreenShare = async (): Promise<void> => {
if (screenShared) {
presentationStream?.getTracks().forEach(track => {
track.stop();
});
setPresentationStream(undefined);
infinityClient.stopPresenting();
} else {
const stream = await navigator.mediaDevices.getDisplayMedia();
stream.getVideoTracks()[0].onended = () => {
setScreenShared(false);
setPresentationStream(undefined);
infinityClient.stopPresenting();
};
infinityClient.present(stream);
setPresentationStream(stream);
}
setScreenShared(!screenShared);
};

In this function the first thing that we will do is to check the value of screenShared, and depending of its value we will activate the feature or not. However, independently of the value of screenShared, we will change the state this value to the opposite calling setScreenShared(!screenShared):

  • screenShared === false: In this case, the first thing that we will do is to stop all the tracks in the same way that we do with video mute. Then we set the state variable to undefined to remove the video from the interface, and finally, we call infinityClient.stopPresenting() to stop sending the stream to Pexip Infinity.

  • screenShared === true: If this condition is met, means that we will enable this feature. The first step is to get a MediaStream with the presentation. We will use the browser API getDisplayMedia() that will show a native browser message asking the user to choose what they want to share:

    Choose Screen

    Once we have this stream, we attach a callback function to onended. This function will be called when the user clicks on the native browser message "Stop sharing":

    Stop Sharing

    The callback function will change the value of the state variable screenShared to false and do the same as in the first case. We stop all the tracks, set the state variable presentationStream to undefined, and call infinityClient.stopPresenting().

    Then we call infinityClient.present(stream) and, at this point, the stream will be sent to Pexip Infinity and to other participants. The last call to setPresentationStream(stream) will make our stream visible in our interface.

Other task that we will do is to modify the handleDisconnect function and stop the presentation stream in case the user clicks on the hang up button:

const handleDisconnect = () => {
presentationStream?.getTracks().forEach((track) => {
track.stop()
})
setPresentationStream(undefined)
...
};

Now we will use the useEffect hook to update the value of the presentationStreamRef reference each time the presentationStream changes:

useEffect(() => {
presentationStreamRef.current = presentationStream;
}, [presentationStream]);

Next, we will need to make a small modification to the onRemotePresentationStream callback function. We will check if the user is sharing their screen and, in that case, we will stop the presentation stream:

callSignals.onRemotePresentationStream.add(stream => {
presentationStreamRef.current?.getTracks().forEach(track => {
track.stop();
});
setScreenShared(false);
setPresentationStream(stream);
});

We will subscribe to a new signal, onPresentationConnectionChange, and in case we don't have any participant sharing their screen, we will set the value of the state variable presentationStream to undefined:

callSignals.onPresentationConnectionChange.add(event => {
if (event.recv === 'disconnected' && event.send !== 'connected') {
setPresentationStream(undefined);
}
});

Now it's the turn to pass screenShared and handleScreenShare to the Conference component:

<Conference
localVideoStream={processedStream}
remoteStream={remoteStream}
presentationStream={presentationStream}
screenShared={screenShared}
devices={devices}
settings={{
audioInput,
audioOutput,
videoInput,
effect
}}
onAudioMute={handleAudioMute}
onVideoMute={handleVideoMute}
onScreenShare={handleScreenShare}
onSettingsChange={handleSettingsChange}
onDisconnect={handleDisconnect}
/>

Conference component

We have to include some modifications in the conference component. So the first step is to open src/components/Conference/Conference.tsx.

We will start by including screenShared and onScreenShare in the list of props:

interface ConferenceProps {
localVideoStream: MediaStream | undefined;
remoteStream: MediaStream | undefined;
presentationStream: MediaStream | undefined;
screenShared: boolean;
devices: MediaDeviceInfoLike[];
settings: Settings;
onAudioMute: (mute: boolean) => Promise<void>;
onVideoMute: (mute: boolean) => Promise<void>;
onScreenShare: (share: boolean, onEnded: () => void) => void;
onSettingsChange: (settings: Settings) => Promise<void>;
onDisconnect: () => Promise<void>;
}

Now we add the same screenShared and onScreenShare when we define the Toolbar component:

<Toolbar
className="toolbar"
settingsOpened={settingsOpened}
screenShared={props.screenShared}
onAudioMute={props.onAudioMute}
onVideoMute={props.onVideoMute}
onScreenShare={async () => {
await props.onScreenShare()
setPresentationInMain(false)
}}
onOpenSettings={() => {
setSettingsOpened(true)
}}
onDisconnect={props.onDisconnect}
/>

In this case, instead of calling directly to onScreenShare, we set the value of presentationInMain to false. This way, we will show the video with the remote participants in the main region and the PiP with the presentation on the top-left corner. After that, we call props.onScreenShare().

Toolbar component

Next we will modify the file src/components/Conference/Toolbar/Toolbar.tsx and create the button that the user will use to start or stop sharing their screen.

The first step is to add screenShared and onScreenShare to the list of props:

interface ToolbarProps {
className: string;
settingsOpened: boolean;
screenShared: boolean;
onAudioMute: (mute: boolean) => Promise<void>;
onVideoMute: (mute: boolean) => Promise<void>;
onScreenShare: () => Promise<void>;
onOpenSettings: () => void;
onDisconnect: () => Promise<void>;
}

After that, we will define the function that will be triggered each time the user clicks on the screen share button. This function will call the function defined in props and change the button state:

const handleScreenShare = async (): Promise<void> => {
await props.onScreenShare();
};

The final step is to return the button itself with the rest of the components from the toolbar:

<Tooltip text={`${props.screenShared ? 'Stop' : 'Start'} screen sharing`}>
<Button
onClick={() => {
handleScreenShare().catch(console.error)
}}
variant="translucent"
modifier="square"
isActive={!props.screenShared}
colorScheme="light"
>
<Icon
source={
props.screenShared
? IconTypes.IconPresentationOff
: IconTypes.IconPresentationOn
}
/>
</Button>
</Tooltip>

Run the app

Now we have everything in place to test our new functionality. The user only has to press the screen share button and the rest of the users will start seeing the shared content.

You can compare your code with the solution in Step.07-Solution-Send-presentation. You can also check the differences with the previous lesson in the git diff.

Send Presentation