From 351843da15e8c93bc1a7b401ab0ba2e6106afb97 Mon Sep 17 00:00:00 2001 From: Alexander Onnikov Date: Tue, 6 May 2025 22:09:57 +0700 Subject: [PATCH] fix: patch getDisplayMedia in desktop app Signed-off-by: Alexander Onnikov --- desktop/src/ui/index.ts | 4 +- desktop/src/ui/screenShare.ts | 83 +++++++++++---------- plugins/media/src/utils.ts | 11 +++ plugins/recorder-resources/src/recording.ts | 3 +- plugins/recorder/src/index.ts | 8 +- 5 files changed, 59 insertions(+), 50 deletions(-) diff --git a/desktop/src/ui/index.ts b/desktop/src/ui/index.ts index abe70b8b99..25befc08ea 100644 --- a/desktop/src/ui/index.ts +++ b/desktop/src/ui/index.ts @@ -19,11 +19,11 @@ import { workbenchId, logOut } from '@hcengineering/workbench' import { isOwnerOrMaintainer } from '@hcengineering/core' import { configurePlatform } from './platform' -import { defineScreenShare, defineScreenRecorder } from './screenShare' +import { defineScreenShare, defineGetDisplayMedia } from './screenShare' import { IPCMainExposed } from './types' defineScreenShare() -defineScreenRecorder() +defineGetDisplayMedia() void configurePlatform().then(() => { createApp(document.body) diff --git a/desktop/src/ui/screenShare.ts b/desktop/src/ui/screenShare.ts index 7a2c3a705d..6048ce96c8 100644 --- a/desktop/src/ui/screenShare.ts +++ b/desktop/src/ui/screenShare.ts @@ -5,53 +5,56 @@ import { showPopup } from '@hcengineering/ui' import { Track, LocalTrack, LocalAudioTrack, LocalVideoTrack, ParticipantEvent, TrackInvalidError, ScreenShareCaptureOptions, DeviceUnsupportedError, ScreenSharePresets } from 'livekit-client' import { IPCMainExposed } from './types' -import { setMetadata } from '@hcengineering/platform' -import recordPlugin from '@hcengineering/recorder' -export async function getMediaStream (opts?: DisplayMediaStreamOptions): Promise { - if (opts === undefined) { - throw new Error('opts must be provided') - } - const ipcMain = (window as any).electron as IPCMainExposed - const sources = await ipcMain.getScreenSources() - - const hasAccess = await ipcMain.getScreenAccess() - if (!hasAccess) { - log.error('No screen access granted') - throw new Error('No screen access granted') +export function defineGetDisplayMedia (): void { + if (navigator?.mediaDevices === undefined) { + console.warn('mediaDevices API not available') + return } if (navigator.mediaDevices.getDisplayMedia === undefined) { throw new DeviceUnsupportedError('getDisplayMedia not supported') } - return await new Promise((resolve, reject) => { - showPopup( - love.component.SelectScreenSourcePopup, - { - sources - }, - 'top', - () => { - reject(new Error('No source selected')) - }, - (val) => { - if (val != null) { - opts.video = { - mandatory: { - ...(typeof opts.video === 'boolean' ? {} : opts.video), - chromeMediaSource: 'desktop', - chromeMediaSourceId: val - } - } as any - resolve(window.navigator.mediaDevices.getUserMedia(opts)) - } - } - ) - }) -} -export function defineScreenRecorder (): void { - setMetadata(recordPlugin.metadata.GetCustomMediaStream, getMediaStream) + navigator.mediaDevices.getDisplayMedia = async (opts?: DisplayMediaStreamOptions): Promise => { + if (opts === undefined) { + throw new Error('opts must be provided') + } + + const ipcMain = (window as any).electron as IPCMainExposed + const sources = await ipcMain.getScreenSources() + + const hasAccess = await ipcMain.getScreenAccess() + if (!hasAccess) { + log.error('No screen access granted') + throw new Error('No screen access granted') + } + + return await new Promise((resolve, reject) => { + showPopup( + love.component.SelectScreenSourcePopup, + { + sources + }, + 'top', + () => { + reject(new Error('No source selected')) + }, + (val) => { + if (val != null) { + opts.video = { + mandatory: { + ...(typeof opts.video === 'boolean' ? {} : opts.video), + chromeMediaSource: 'desktop', + chromeMediaSourceId: val + } + } as any + resolve(window.navigator.mediaDevices.getUserMedia(opts)) + } + } + ) + }) + } } export function defineScreenShare (): void { diff --git a/plugins/media/src/utils.ts b/plugins/media/src/utils.ts index 4cdcfb9690..41e1a18477 100644 --- a/plugins/media/src/utils.ts +++ b/plugins/media/src/utils.ts @@ -196,3 +196,14 @@ export async function getMicrophoneStream ( return null } } + +export async function getDisplayMedia (constraints: MediaStreamConstraints): Promise { + if ( + navigator?.mediaDevices?.getDisplayMedia !== undefined && + typeof navigator.mediaDevices.getDisplayMedia === 'function' + ) { + return await navigator.mediaDevices.getDisplayMedia(constraints) + } + + throw new Error('getDisplayMedia not supported') +} diff --git a/plugins/recorder-resources/src/recording.ts b/plugins/recorder-resources/src/recording.ts index 9ac6935fcd..a51d2dbbc9 100644 --- a/plugins/recorder-resources/src/recording.ts +++ b/plugins/recorder-resources/src/recording.ts @@ -13,6 +13,7 @@ // limitations under the License. // +import { getDisplayMedia } from '@hcengineering/media' import { getMetadata } from '@hcengineering/platform' import presentation from '@hcengineering/presentation' import { showPopup } from '@hcengineering/ui' @@ -69,7 +70,7 @@ export async function startRecording (options: RecordingOptions): Promise let displayStream: MediaStream try { - displayStream = await navigator.mediaDevices.getDisplayMedia({ + displayStream = await getDisplayMedia({ video: { frameRate: { ideal: fps ?? 30 } } diff --git a/plugins/recorder/src/index.ts b/plugins/recorder/src/index.ts index f300d7b733..7e44c9da73 100644 --- a/plugins/recorder/src/index.ts +++ b/plugins/recorder/src/index.ts @@ -21,11 +21,6 @@ import { type UploadHandler } from '@hcengineering/uploader' */ export const recorderId = 'recorder' as Plugin -/** - * @public - */ -export type GetMediaStream = (options?: DisplayMediaStreamOptions) => Promise - /** * @public */ @@ -34,8 +29,7 @@ const recordPlugin = plugin(recorderId, { Record: '' as Asset }, metadata: { - StreamUrl: '' as Metadata, - GetCustomMediaStream: '' as Metadata + StreamUrl: '' as Metadata }, space: { Drive: '' as Ref