From a23d107343891d7577ab8a4c8ed467f6033eb276 Mon Sep 17 00:00:00 2001 From: automated-signal <37887102+automated-signal@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:48:18 -0500 Subject: [PATCH] Add minimum frame rate constraint for screen share --- package-lock.json | 8 ++--- package.json | 2 +- ts/util/desktopCapturer.ts | 66 +++++++++++++++++++++++++++++--------- 3 files changed, 56 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7381829ecaf7..f1252896ef1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@formatjs/icu-messageformat-parser": "2.3.0", "@formatjs/intl-localematcher": "0.2.32", "@indutny/dicer": "0.3.2", - "@indutny/mac-screen-share": "1.0.11", + "@indutny/mac-screen-share": "1.0.13", "@indutny/range-finder": "1.3.4", "@indutny/simple-windows-notifications": "2.0.7", "@indutny/sneequals": "4.0.0", @@ -4086,9 +4086,9 @@ } }, "node_modules/@indutny/mac-screen-share": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@indutny/mac-screen-share/-/mac-screen-share-1.0.11.tgz", - "integrity": "sha512-0UHiWyUBYn3VjjwpaS0nD6bppunyaTdFGXU84uGmzlV1R9RYcXQOF4EDCWQLpMlw4TieLOBvsh10gLEP116MPw==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@indutny/mac-screen-share/-/mac-screen-share-1.0.13.tgz", + "integrity": "sha512-fCOYWGaIuZAwfk/eUUjqPKge4+PKKCwTsuH9SSoaBZMP8IsWQtyyrTVpeuq84oDren4b9EUgRGgh2t950sQA0w==", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index bd310c94a34e..9c87128ec54b 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "@formatjs/icu-messageformat-parser": "2.3.0", "@formatjs/intl-localematcher": "0.2.32", "@indutny/dicer": "0.3.2", - "@indutny/mac-screen-share": "1.0.11", + "@indutny/mac-screen-share": "1.0.13", "@indutny/range-finder": "1.3.4", "@indutny/simple-windows-notifications": "2.0.7", "@indutny/sneequals": "4.0.0", diff --git a/ts/util/desktopCapturer.ts b/ts/util/desktopCapturer.ts index 7fe8c346ecbd..0c24289e8b1e 100644 --- a/ts/util/desktopCapturer.ts +++ b/ts/util/desktopCapturer.ts @@ -18,6 +18,8 @@ import { strictAssert } from './assert'; import { explodePromise } from './explodePromise'; import { isNotNil } from './isNotNil'; import { drop } from './drop'; +import { SECOND } from './durations'; +import { isOlderThan } from './timestamp'; // Chrome-only API for now, thus a declaration: declare class MediaStreamTrackGenerator extends MediaStreamTrack { @@ -176,19 +178,26 @@ export class DesktopCapturer { liveCapturers.add(this); try { const stream = await navigator.mediaDevices.getDisplayMedia({ - video: { - width: { - max: REQUESTED_SCREEN_SHARE_WIDTH, - ideal: REQUESTED_SCREEN_SHARE_WIDTH, - }, - height: { - max: REQUESTED_SCREEN_SHARE_HEIGHT, - ideal: REQUESTED_SCREEN_SHARE_HEIGHT, - }, - frameRate: { - max: REQUESTED_SCREEN_SHARE_FRAMERATE, - ideal: REQUESTED_SCREEN_SHARE_FRAMERATE, - }, + video: true, + }); + + const videoTrack = stream.getVideoTracks()[0]; + strictAssert(videoTrack, 'videoTrack does not exist'); + + // Apply constraints and ensure that there is at least 1 frame per second. + await videoTrack.applyConstraints({ + width: { + max: REQUESTED_SCREEN_SHARE_WIDTH, + ideal: REQUESTED_SCREEN_SHARE_WIDTH, + }, + height: { + max: REQUESTED_SCREEN_SHARE_HEIGHT, + ideal: REQUESTED_SCREEN_SHARE_HEIGHT, + }, + frameRate: { + min: 1, + max: REQUESTED_SCREEN_SHARE_FRAMERATE, + ideal: REQUESTED_SCREEN_SHARE_FRAMERATE, }, }); @@ -222,6 +231,20 @@ export class DesktopCapturer { let isRunning = false; + let lastFrame: VideoFrame | undefined; + let lastFrameSentAt = 0; + + let frameRepeater: NodeJS.Timeout | undefined; + + const cleanup = () => { + lastFrame?.close(); + if (frameRepeater != null) { + clearInterval(frameRepeater); + } + frameRepeater = undefined; + lastFrame = undefined; + }; + const stream = new macScreenShare.Stream({ width: REQUESTED_SCREEN_SHARE_WIDTH, height: REQUESTED_SCREEN_SHARE_HEIGHT, @@ -230,6 +253,17 @@ export class DesktopCapturer { onStart: () => { isRunning = true; + // Repeat last frame every second to match "min" constraint above. + frameRepeater = setInterval(() => { + if (isRunning && track.readyState !== 'ended' && lastFrame != null) { + if (isOlderThan(lastFrameSentAt, SECOND)) { + drop(writer.write(lastFrame.clone())); + } + } else { + cleanup(); + } + }, SECOND); + this.options.onMediaStream(mediaStream); }, onStop() { @@ -253,13 +287,15 @@ export class DesktopCapturer { return; } - const videoFrame = new VideoFrame(frame, { + lastFrame?.close(); + lastFrameSentAt = Date.now(); + lastFrame = new VideoFrame(frame, { format: 'NV12', codedWidth: width, codedHeight: height, timestamp: 0, }); - drop(writer.write(videoFrame)); + drop(writer.write(lastFrame.clone())); }, });