fix: patch getDisplayMedia in desktop app

Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
Alexander Onnikov 2025-05-06 22:09:57 +07:00
parent d158fa5a21
commit 351843da15
No known key found for this signature in database
GPG Key ID: 3320C3B3324E934C
5 changed files with 59 additions and 50 deletions

View File

@ -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)

View File

@ -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<MediaStream> {
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<MediaStream>((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<MediaStream> => {
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<MediaStream>((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 {

View File

@ -196,3 +196,14 @@ export async function getMicrophoneStream (
return null
}
}
export async function getDisplayMedia (constraints: MediaStreamConstraints): Promise<MediaStream> {
if (
navigator?.mediaDevices?.getDisplayMedia !== undefined &&
typeof navigator.mediaDevices.getDisplayMedia === 'function'
) {
return await navigator.mediaDevices.getDisplayMedia(constraints)
}
throw new Error('getDisplayMedia not supported')
}

View File

@ -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<void>
let displayStream: MediaStream
try {
displayStream = await navigator.mediaDevices.getDisplayMedia({
displayStream = await getDisplayMedia({
video: {
frameRate: { ideal: fps ?? 30 }
}

View File

@ -21,11 +21,6 @@ import { type UploadHandler } from '@hcengineering/uploader'
*/
export const recorderId = 'recorder' as Plugin
/**
* @public
*/
export type GetMediaStream = (options?: DisplayMediaStreamOptions) => Promise<MediaStream>
/**
* @public
*/
@ -34,8 +29,7 @@ const recordPlugin = plugin(recorderId, {
Record: '' as Asset
},
metadata: {
StreamUrl: '' as Metadata<string>,
GetCustomMediaStream: '' as Metadata<GetMediaStream>
StreamUrl: '' as Metadata<string>
},
space: {
Drive: '' as Ref<Drive>