diff --git a/desktop/src/main/start.ts b/desktop/src/main/start.ts index 620b651a0f..13a6fa7edd 100644 --- a/desktop/src/main/start.ts +++ b/desktop/src/main/start.ts @@ -115,6 +115,7 @@ function hookOpenWindow (window: BrowserWindow): void { devTools: true, sandbox: false, partition: sessionPartition, + nodeIntegration: true, preload: path.join(app.getAppPath(), 'dist', 'main', 'preload.js'), additionalArguments: [ `--open=${encodeURI( @@ -192,6 +193,7 @@ const createWindow = async (): Promise => { webPreferences: { devTools: true, sandbox: false, + nodeIntegration: true, backgroundThrottling: false, partition: sessionPartition, preload: path.join(app.getAppPath(), 'dist', 'main', 'preload.js') diff --git a/desktop/src/ui/index.ts b/desktop/src/ui/index.ts index 499a86d5f5..abe70b8b99 100644 --- a/desktop/src/ui/index.ts +++ b/desktop/src/ui/index.ts @@ -19,10 +19,11 @@ import { workbenchId, logOut } from '@hcengineering/workbench' import { isOwnerOrMaintainer } from '@hcengineering/core' import { configurePlatform } from './platform' -import { defineScreenShare } from './screenShare' +import { defineScreenShare, defineScreenRecorder } from './screenShare' import { IPCMainExposed } from './types' defineScreenShare() +defineScreenRecorder() void configurePlatform().then(() => { createApp(document.body) diff --git a/desktop/src/ui/screenShare.ts b/desktop/src/ui/screenShare.ts index 4516519906..7a2c3a705d 100644 --- a/desktop/src/ui/screenShare.ts +++ b/desktop/src/ui/screenShare.ts @@ -5,6 +5,54 @@ 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') + } + + 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) +} export function defineScreenShare (): void { setCustomCreateScreenTracks(async function electronCreateScreenTracks (options?: ScreenShareCaptureOptions) { diff --git a/plugins/recorder-resources/src/components/Panel.svelte b/plugins/recorder-resources/src/components/Panel.svelte index f1a53d3902..7ce58c1b80 100644 --- a/plugins/recorder-resources/src/components/Panel.svelte +++ b/plugins/recorder-resources/src/components/Panel.svelte @@ -91,8 +91,10 @@ if (state === RecordingState.Inactive) { try { await createScreenRecorder() - } catch { + } catch (err) { + console.log('cant create screen recorder', err) distpacher('close', true) + return } await showCountdown() recorder?.start() diff --git a/plugins/recorder-resources/src/index.ts b/plugins/recorder-resources/src/index.ts index e97f3c2a80..37c3ec93a3 100644 --- a/plugins/recorder-resources/src/index.ts +++ b/plugins/recorder-resources/src/index.ts @@ -16,6 +16,8 @@ import { type Resources } from '@hcengineering/platform' import { record } from './recording' +export { ScreenRecorder } from './screen-recorder' + export default async (): Promise => ({ function: { Record: record diff --git a/plugins/recorder-resources/src/screen-recorder.ts b/plugins/recorder-resources/src/screen-recorder.ts index dc4f7b640f..8b699310ec 100644 --- a/plugins/recorder-resources/src/screen-recorder.ts +++ b/plugins/recorder-resources/src/screen-recorder.ts @@ -13,8 +13,10 @@ // limitations under the License. // +import plugin from '@hcengineering/recorder' import { Recorder } from './recorder' import { TusUploader, type Uploader, type Options } from './uploader' +import { getMetadata } from '@hcengineering/platform' export class ScreenRecorder { private readonly recorder: Recorder @@ -29,11 +31,20 @@ export class ScreenRecorder { let width = 0 let height = 0 const combinedStream = new MediaStream() - const displayStream = await navigator.mediaDevices.getDisplayMedia({ - video: { frameRate: opts.fps ?? 30 }, - audio: true + const getMediaStream = + getMetadata(plugin.metadata.GetCustomMediaStream) ?? + (async (op) => await navigator.mediaDevices.getDisplayMedia(op)) + const displayStream = await getMediaStream({ + video: { frameRate: opts.fps ?? 30 } }) - const microphoneStream = await navigator.mediaDevices.getUserMedia({ audio: true }) + try { + const microphoneStream = await navigator.mediaDevices.getUserMedia({ audio: true }) + microphoneStream.getAudioTracks().forEach((track) => { + combinedStream.addTrack(track) + }) + } catch (err) { + console.warn('microphone is disabled', err) + } displayStream.getVideoTracks().forEach((track) => { combinedStream.addTrack(track) width = Math.max(track.getSettings().width ?? width, width) @@ -42,9 +53,6 @@ export class ScreenRecorder { displayStream.getAudioTracks().forEach((track) => { combinedStream.addTrack(track) }) - microphoneStream.getAudioTracks().forEach((track) => { - combinedStream.addTrack(track) - }) const recorder = new Recorder(combinedStream) const uploader = new TusUploader(recorder.asStream(), { ...opts, metadata: { resolution: width + ':' + height } }) diff --git a/plugins/recorder/src/index.ts b/plugins/recorder/src/index.ts index b17942d4e2..21793e5258 100644 --- a/plugins/recorder/src/index.ts +++ b/plugins/recorder/src/index.ts @@ -19,6 +19,11 @@ import { type UploadHandler } from '@hcengineering/uploader' */ export const recorderId = 'recorder' as Plugin +/** + * @public + */ +export type GetMediaStream = (options?: DisplayMediaStreamOptions) => Promise + /** * @public */ @@ -27,7 +32,8 @@ const recordPlugin = plugin(recorderId, { Record: '' as Asset }, metadata: { - StreamUrl: '' as Metadata + StreamUrl: '' as Metadata, + GetCustomMediaStream: '' as Metadata }, string: { Pause: '' as IntlString,