Skip to content

Commit

Permalink
Improve default screen sharing FPS, limiting capturing surface
Browse files Browse the repository at this point in the history
  • Loading branch information
davidzhao committed Dec 22, 2023
1 parent 761711a commit 0b80cd4
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 12 deletions.
2 changes: 2 additions & 0 deletions example/sample.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
RoomConnectOptions,
RoomEvent,
RoomOptions,
ScreenSharePresets,
Track,
TrackPublication,
VideoCaptureOptions,
Expand Down Expand Up @@ -95,6 +96,7 @@ const appActions = {
dtx: true,
red: true,
forceStereo: false,
screenShareEncoding: ScreenSharePresets.h1080fps30.encoding,
},
videoCaptureDefaults: {
resolution: VideoPresets.h720.resolution,
Expand Down
24 changes: 22 additions & 2 deletions src/room/participant/LocalParticipant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,24 @@ import type {
TrackPublishOptions,
VideoCaptureOptions,
} from '../track/options';
import { VideoPresets, isBackupCodec } from '../track/options';
import { ScreenSharePresets, VideoPresets, isBackupCodec } from '../track/options';
import {
constraintsForOptions,
mergeDefaultOptions,
mimeTypeToVideoCodecString,
screenCaptureToDisplayMediaStreamOptions,
} from '../track/utils';
import type { DataPublishOptions } from '../types';
import { Future, isFireFox, isSVCCodec, isSafari, isWeb, supportsAV1, supportsVP9 } from '../utils';
import {
Future,
isFireFox,
isSVCCodec,
isSafari,
isSafari17,
isWeb,
supportsAV1,
supportsVP9,
} from '../utils';
import Participant from './Participant';
import type { ParticipantTrackPermission } from './ParticipantTrackPermission';
import { trackPermissionToProto } from './ParticipantTrackPermission';
Expand Down Expand Up @@ -448,6 +457,13 @@ export default class LocalParticipant extends Participant {
throw new DeviceUnsupportedError('getDisplayMedia not supported');
}

if (options.resolution === undefined && !isSafari17()) {
// we need to constrain the dimensions, otherwise it could lead to low bitrate
// due to encoding a huge video. Encoding such large surfaces is really expensive
// unfortunately Safari 17 has a but and cannot be constrained by default
options.resolution = ScreenSharePresets.h1080fps30.resolution;
}

const constraints = screenCaptureToDisplayMediaStreamOptions(options);
const stream: MediaStream = await navigator.mediaDevices.getDisplayMedia(constraints);

Expand All @@ -457,6 +473,10 @@ export default class LocalParticipant extends Participant {
}
const screenVideo = new LocalVideoTrack(tracks[0], undefined, false);
screenVideo.source = Track.Source.ScreenShare;
if (options.contentHint) {
screenVideo.mediaStreamTrack.contentHint = options.contentHint;
}

const localTracks: Array<LocalTrack> = [screenVideo];
if (stream.getAudioTracks().length > 0) {
this.emit(ParticipantEvent.AudioStreamAcquired);
Expand Down
5 changes: 3 additions & 2 deletions src/room/participant/publishUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const defaultSimulcastPresets43 = [VideoPresets43.h180, VideoPresets43.h3

/* @internal */
export const computeDefaultScreenShareSimulcastPresets = (fromPreset: VideoPreset) => {
const layers = [{ scaleResolutionDownBy: 2, fps: 3 }];
const layers = [{ scaleResolutionDownBy: 2, fps: fromPreset.encoding.maxFramerate }];
return layers.map(
(t) =>
new VideoPreset(
Expand All @@ -54,7 +54,8 @@ export const computeDefaultScreenShareSimulcastPresets = (fromPreset: VideoPrese
150_000,
Math.floor(
fromPreset.encoding.maxBitrate /
(t.scaleResolutionDownBy ** 2 * ((fromPreset.encoding.maxFramerate ?? 30) / t.fps)),
(t.scaleResolutionDownBy ** 2 *
((fromPreset.encoding.maxFramerate ?? 30) / (t.fps ?? 30))),
),
),
t.fps,
Expand Down
7 changes: 4 additions & 3 deletions src/room/track/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ import DeviceManager from '../DeviceManager';
import { audioDefaults, videoDefaults } from '../defaults';
import { DeviceUnsupportedError, TrackInvalidError } from '../errors';
import { mediaTrackToLocalTrack } from '../participant/publishUtils';
import { isSafari17 } from '../utils';
import LocalAudioTrack from './LocalAudioTrack';
import type LocalTrack from './LocalTrack';
import LocalVideoTrack from './LocalVideoTrack';
import { Track } from './Track';
import { ScreenSharePresets } from './options';
import type {
AudioCaptureOptions,
CreateLocalTracksOptions,
ScreenShareCaptureOptions,
VideoCaptureOptions,
} from './options';
import { ScreenSharePresets } from './options';
import {
constraintsForOptions,
mergeDefaultOptions,
Expand Down Expand Up @@ -116,8 +117,8 @@ export async function createLocalScreenTracks(
if (options === undefined) {
options = {};
}
if (options.resolution === undefined) {
options.resolution = ScreenSharePresets.h1080fps15.resolution;
if (options.resolution === undefined && !isSafari17()) {
options.resolution = ScreenSharePresets.h1080fps30.resolution;
}

if (navigator.mediaDevices.getDisplayMedia === undefined) {
Expand Down
17 changes: 12 additions & 5 deletions src/room/track/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,10 @@ export interface ScreenShareCaptureOptions {
video?: true | { displaySurface?: 'window' | 'browser' | 'monitor' };

/**
* capture resolution, defaults to screen resolution
* NOTE: In Safari 17, specifying any resolution at all would lead to a low-resolution
* capture. https://bugs.webkit.org/show_bug.cgi?id=263015
* capture resolution, defaults to 1080 for all browsers other than Safari
* On Safari 17, default resolution is not capped, due to a bug, specifying
* any resolution at all would lead to a low-resolution capture.
* https://bugs.webkit.org/show_bug.cgi?id=263015
*/
resolution?: VideoResolution;

Expand All @@ -187,6 +188,9 @@ export interface ScreenShareCaptureOptions {
/** specifies whether the browser should include the system audio among the possible audio sources offered to the user */
systemAudio?: 'include' | 'exclude';

/** specify the type of content, see: https://www.w3.org/TR/mst-content-hint/#video-content-hints */
contentHint?: 'detail' | 'text' | 'motion';

/**
* Experimental option to control whether the audio playing in a tab will continue to be played out of a user's
* local speakers when the tab is captured.
Expand Down Expand Up @@ -367,9 +371,12 @@ export const VideoPresets43 = {

export const ScreenSharePresets = {
h360fps3: new VideoPreset(640, 360, 200_000, 3, 'medium'),
h720fps5: new VideoPreset(1280, 720, 400_000, 5, 'medium'),
h360fps15: new VideoPreset(640, 360, 400_000, 15, 'medium'),
h540fps15: new VideoPreset(960, 540, 600_000, 15, 'medium'),
h540fps30: new VideoPreset(960, 540, 800_000, 15, 'medium'),
h720fps5: new VideoPreset(1280, 720, 800_000, 5, 'medium'),
h720fps15: new VideoPreset(1280, 720, 1_500_000, 15, 'medium'),
h720fps30: new VideoPreset(1280, 720, 2_000_000, 30, 'medium'),
h1080fps15: new VideoPreset(1920, 1080, 2_500_000, 15, 'medium'),
h1080fps30: new VideoPreset(1920, 1080, 4_000_000, 30, 'medium'),
h1080fps30: new VideoPreset(1920, 1080, 5_000_000, 30, 'medium'),
} as const;
5 changes: 5 additions & 0 deletions src/room/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ export function isSafari(): boolean {
return getBrowser()?.name === 'Safari';
}

export function isSafari17(): boolean {
const b = getBrowser();
return b?.name === 'Safari' && b.version.startsWith('17.');
}

export function isMobile(): boolean {
if (!isWeb()) return false;
return /Tablet|iPad|Mobile|Android|BlackBerry/.test(navigator.userAgent);
Expand Down

0 comments on commit 0b80cd4

Please sign in to comment.