Skip to main content

How to Share Screen Presentations

In this tutorial a participant shares their screen, an application window or a browser tab, and it fills the whole stream for everyone else in the session. When they are done, the stream returns to the normal layout and the presenter's camera comes back. You need two pieces for that, and this tutorial combines them.

info

Presentation mode decides whose video source fills the stream. It does not decide what that source shows. While a presentation runs, all other participants see only the presenter's video source, so when the presenter's browser sends a screen capture instead of the camera, everyone sees the screen. The Eyeson JS SDK makes that switch.

Prerequisites​

The presenter takes part through a browser application built with the Eyeson JS SDK, because the screen switch happens on their device. Once the session is set up, the presentation flow works with a participant's access key alone. None of its steps needs your API key, so the presenter's browser can send the presentation requests itself.

  • A running session. Each participant gets their own access_key from the Start Meeting request
  • A browser application with the Eyeson JS SDK, installed with npm i --save @eyeson/js
  • A browser that supports screen capture. Step 2 checks this
  • A secure page. Screen capture and camera access only work over HTTPS or on http://localhost, not from a page opened as a file

Set up the session​

Start the meeting with options[sfu_mode]=disabled. Without it, a meeting with two participants runs in SFU mode, and nobody sees themselves in the stream. A presentation still switches to SFU mode while it runs, and the stream returns to the composited layout afterwards.

curl -X POST \
-H "Authorization: $API_KEY" \
-d "user[name]=Presenter" \
-d "options[sfu_mode]=disabled" \
"https://api.eyeson.team/rooms"

The response contains the presenter's access_key. A second participant joins with the guest link from links.guest_join, or your application registers them with POST /guests/GUEST_TOKEN, which returns their own access_key. For a room you reuse every time, create a Permalink with the same options instead.

note

Room options are fixed when a meeting starts, so a meeting that is already running keeps its options until it ends. The same applies to a permalink: its options are set when you create it.

warning

The presentation itself needs only access keys. Your API key is needed to start the session, and for server-side features such as the Meeting Observer or force-stopping a meeting. Keep these on your backend, so the key never reaches the browser.

Step 1. Connect presenter​

The presenter joins the session from your browser application with their own access key. Start with camera and microphone, as in any session. The accept event delivers the composited stream, which you attach to a video element so the presenter sees what everyone sees.

import eyeson from "@eyeson/js";

eyeson.onEvent((event) => {
if (event.type === "accept") {
// Some iOS devices need the autoplay attribute on the video element.
const video = document.querySelector("video");
video.srcObject = event.remoteStream;
video.play();
}
});

eyeson.start("ACCESS_KEY", { audio: true, video: true });

Keep the access key at hand. The same key starts and ends the presentation in Step 3 and Step 4.

tip

For a quick test without a bundler, load the SDK as a module from a CDN in a plain HTML page:

<script type="module">
import eyeson, { FeatureDetector, StreamHelpers } from "https://cdn.jsdelivr.net/npm/@eyeson/js@2.2.1/+esm";
</script>
warning

If the camera cannot be opened, for example because another application uses it, joining fails with an error event and no accept follows. Handle it as described in Handle errors, or the application waits forever.

Step 2. Share the screen​

The start_stream command with screen: true replaces the presenter's camera with a screen capture. The browser then shows its own picker, where the presenter chooses the screen, a window or a tab. surface is optional: where the browser supports it, it pre-selects one of these. FeatureDetector tells you what the current browser can do.

import eyeson, { FeatureDetector } from "@eyeson/js";

function shareScreen() {
if (!FeatureDetector.canScreenCapture()) {
alert("This browser cannot share the screen.");
return;
}
eyeson.send({
type: "start_stream",
audio: true,
screen: true,
// Pre-select browser tabs where supported, otherwise show the browser's default picker.
surface: FeatureDetector.canChooseDisplaySurface() ? "browser" : null,
});
}

eyeson.onEvent((event) => {
if (event.type === "screen_capture_error") {
const message =
event.name === "error_Screen_NotAllowedError"
? "Allow screen access in your browser."
: "Unable to start screen capture";
alert(message);
}
});

Call shareScreen() from a button. Use "monitor" for the entire screen or "window" for an application window instead of "browser", or leave surface out. Enabling the screen turns the camera off, because the two are mutually exclusive. If the presenter allows audio in the browser's picker, the screen's audio is sent along with the microphone. While the screen is shared, don't send change_stream with video: true, because that replaces the screen with the camera again.

Step 3. Start presentation​

Send a POST to /rooms/ACCESS_KEY/presentation with the presenter's access key, which also makes that participant the presenter. The request has no body. The stream switches to SFU mode, the presenter's video source fills the whole stream, and all other participants see only that source while the presentation runs.

export ACCESS_KEY=...
curl -i -X POST "https://api.eyeson.team/rooms/$ACCESS_KEY/presentation"
warning

Send this request only once the screen capture is live, which the SDK reports with a stream_update event after start_stream. If the presenter cancels the browser's picker, screen_capture_error arrives instead, and you should not start the presentation, or everyone would see the camera.

A successful start returns 201 Created. The response has no documented body. Only one presentation can run at a time, see Handle errors.

To show who is presenting, and to disable your start button while someone else presents, listen for presentation_update. In the browser, use the SDK's event, which needs no API key. On your backend, the Meeting Observer sends the same event, and it needs your API key. presentation holds the running presentation, or null when it ends.

eyeson.onEvent((event) => {
if (event.type === "presentation_update") {
const isRunning = Boolean(event.presentation);
document.querySelector("#start-presentation").disabled = isRunning;
}
});

Step 4. End presentation​

Ending takes two actions in the reverse order. Send a DELETE to the same path to end presentation mode, then switch the presenter's browser back to the camera. Any participant can end the presentation with their own access key, so a moderator can stop it too.

export ACCESS_KEY=...
curl -i -X DELETE "https://api.eyeson.team/rooms/$ACCESS_KEY/presentation"

A successful end returns 200 OK. Then bring the camera back in the presenter's browser. Restore the presenter's previous camera and microphone state rather than switching both on, so that a muted presenter stays muted.

Two more cases end a presentation without the presenter's stop button:

  • The presenter stops sharing with the browser's own "Stop sharing" button. The SDK then sends screen_video_ended, and your application should end the presentation and restore the camera the same way.
  • Another participant ends the presentation. The presenter's browser then receives presentation_update with presentation: null while it still sends the screen. Restore the camera without sending a second DELETE.
import eyeson from "@eyeson/js";

const accessKey = "ACCESS_KEY";
let presenting = true;
let micOn = true;
let camOn = true;

function restoreCamera() {
eyeson.send({ type: "start_stream", audio: micOn, video: camOn });
}

async function stopPresenting() {
presenting = false;
const res = await fetch(`https://api.eyeson.team/rooms/${accessKey}/presentation`, {
method: "DELETE",
});
if (!res.ok) console.warn(`Ending the presentation failed with ${res.status}`);
restoreCamera();
}

eyeson.onEvent((event) => {
if (event.type === "screen_video_ended" && presenting) stopPresenting();

// Someone else ended the presentation.
if (event.type === "presentation_update" && !event.presentation && presenting) {
presenting = false;
restoreCamera();
}
});

Step 5. Verify presentation​

Test with two participants, because the result is only visible to the others. Join the second participant with the guest link from Set up the session. After the session is set up, the presentation flow works with access keys, so no backend or API key is involved.

note

To test on one computer, use two profiles of the same browser. Two different browsers usually cannot use the same camera at once.

Check three things in this order.

  1. The requests succeed. The start in Step 3 returns 201 Created, and the end in Step 4 returns 200 OK.
  2. The others see the screen. While the presentation runs, the second participant sees only the presenter's shared screen, filling the whole stream, and hears the presenter.
  3. Presentation mode is active. Send the start request from Step 3 once more with the second participant's access key. It returns 409 Conflict, because only one presentation can run at a time. After Step 4, the same request succeeds, which shows the stream is free again.

After Step 4, both participants see the normal layout again and the presenter's camera is back.

success

A participant can now share a screen, window or tab, it fills the stream for everyone, and the session returns to normal when they stop, whichever way they stop.

Handle errors​

Most errors come from two places: the presentation endpoint and the browser's screen capture. A conflict on start is part of normal use when two people want to present, so your interface should expect it. Joining can also fail when the camera or microphone cannot be opened, which the SDK reports with an error event. The table lists every case the documentation names.

WhereSignalMeaningWhat to do
Start request409 ConflictAnother participant is already presentingEnd the current presentation with Step 4, then start again
Start or end request404 Not FoundThe path is wrong, for example a typo such as /presentatioCheck the path against /rooms/ACCESS_KEY/presentation
Start or end request401 UnauthorizedThe access key is wrongCheck the participant's access key
Browserscreen_capture_error with name error_Screen_NotAllowedErrorThe presenter or the browser denied screen accessAsk the presenter to allow screen access
Browserscreen_video_endedThe presenter stopped sharing from the browserEnd the presentation and restore the camera, as in Step 4
Browserpresentation_update with presentation: null while presentingAnother participant ended the presentationRestore the camera, as in Step 4
Joiningerror with name not_readable or devicesThe camera is used by another application, or is missing. A virtual camera such as OBS can be listed first and fail when its application isn't runningJoin without video, or let the user choose another camera
Joiningerror with name permissionThe user or the browser denied camera or microphone accessAsk the user to allow access in the browser
Joiningerror with name meeting_locked or session_in_useThe meeting is locked, or the user already joined in another tabShow a message to the user
Browserdisconnect from the presenterThe presenter left without ending the presentationEnd the presentation from another participant, as in Step 4

Next Steps​

Screen presentations combine a browser feature with a layout change. The pages below go deeper into each part and into what else the presenter's stream can do.