From 18bc2ed4a6dec3acbb196ca054ea83f9813f5bfa Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Wed, 11 Oct 2023 01:17:39 +0800 Subject: [PATCH] feat: add getDisplayMedia support for desktop #158 --- .../src/main/lib/capturer-source-picker.html | 138 ++++++++++++++++++ client/desktop/src/main/main.ts | 82 ++++++++++- client/desktop/src/main/preload.ts | 12 +- client/desktop/src/main/util.ts | 4 + client/desktop/src/renderer/preload.d.ts | 3 + .../com.msgbyte.env.electron/src/index.tsx | 20 +++ 6 files changed, 256 insertions(+), 3 deletions(-) create mode 100644 client/desktop/src/main/lib/capturer-source-picker.html diff --git a/client/desktop/src/main/lib/capturer-source-picker.html b/client/desktop/src/main/lib/capturer-source-picker.html new file mode 100644 index 00000000000..66cd673dbd8 --- /dev/null +++ b/client/desktop/src/main/lib/capturer-source-picker.html @@ -0,0 +1,138 @@ + + + + + + Capturer Picker + + + + + + diff --git a/client/desktop/src/main/main.ts b/client/desktop/src/main/main.ts index b0f44100f62..8a7f3d0cfdb 100644 --- a/client/desktop/src/main/main.ts +++ b/client/desktop/src/main/main.ts @@ -9,11 +9,18 @@ * `./src/main.js` using webpack. This gives us some performance wins. */ import path from 'path'; -import { app, BrowserWindow, shell, ipcMain } from 'electron'; +import { + app, + BrowserWindow, + shell, + ipcMain, + desktopCapturer, + DesktopCapturerSource, +} from 'electron'; import { autoUpdater } from 'electron-updater'; import log from 'electron-log'; import MenuBuilder from './menu'; -import { resolveHtmlPath } from './util'; +import { CONSTANT, resolveHtmlPath } from './util'; import windowStateKeeper from 'electron-window-state'; import is from 'electron-is'; import { initScreenshots } from './screenshots'; @@ -42,6 +49,22 @@ ipcMain.on('ipc-example', async (event, arg) => { event.reply('ipc-example', msgTemplate('pong')); }); +ipcMain.handle(CONSTANT.DESKTOP_CAPTURER_GET_SOURCES, async (event, opts) => { + const sources = await desktopCapturer.getSources({ + types: ['window', 'screen'], + }); + + return new Promise((resolve) => { + createCapturerSourcePicker(sources, (source) => { + if (source) { + resolve(source); + } else { + resolve(null); + } + }); + }); +}); + if (process.env.NODE_ENV === 'production') { const sourceMapSupport = require('source-map-support'); sourceMapSupport.install(); @@ -76,6 +99,7 @@ const installExtensions = async () => { const webPreferences: Electron.WebPreferences = { nodeIntegration: false, contextIsolation: true, + devTools: true, webSecurity: false, // skip same-origin allowRunningInsecureContent: true, // allow visit http page in https preload: app.isPackaged @@ -85,6 +109,7 @@ const webPreferences: Electron.WebPreferences = { let welcomeWindow: BrowserWindow | null = null; let mainWindow: BrowserWindow | null = null; +let capturerSourcePickerWindow: BrowserWindow | null = null; const createWelcomeWindow = async () => { // 创建一个新的浏览器窗口 @@ -259,6 +284,59 @@ const createMainWindow = async (url: string) => { } }; +const createCapturerSourcePicker = async ( + sources: DesktopCapturerSource[], + onSelected: (source: DesktopCapturerSource | null) => void +) => { + // 创建一个新的浏览器窗口 + capturerSourcePickerWindow = new BrowserWindow({ + width: 800, + height: 600, + alwaysOnTop: true, + parent: mainWindow ?? undefined, + modal: true, + autoHideMenuBar: true, + minimizable: false, + maximizable: false, + webPreferences, + }); + + // 加载欢迎窗口的HTML文件 + capturerSourcePickerWindow.webContents.loadFile( + require.resolve('./lib/capturer-source-picker.html') + ); + + capturerSourcePickerWindow.webContents.on('did-finish-load', () => { + if (capturerSourcePickerWindow) { + capturerSourcePickerWindow.webContents.send( + 'SEND_SCREEN_SHARE_SOURCES', + sources.map((s) => ({ + ...s, + thumbnail: s.thumbnail.toDataURL(), + })) + ); + } + }); + + // 监听从渲染进程发送的选择捕获源事件 + capturerSourcePickerWindow.webContents.on( + 'ipc-message', + (e, channel, data) => { + if (channel === 'selectCapturerSource') { + onSelected(data); + if (capturerSourcePickerWindow) { + capturerSourcePickerWindow.close(); + } + } + } + ); + + capturerSourcePickerWindow.on('closed', () => { + onSelected(null); + capturerSourcePickerWindow = null; + }); +}; + /** * Add event listeners... */ diff --git a/client/desktop/src/main/preload.ts b/client/desktop/src/main/preload.ts index 188c401af92..64af11c754a 100644 --- a/client/desktop/src/main/preload.ts +++ b/client/desktop/src/main/preload.ts @@ -4,12 +4,14 @@ import { IpcRendererEvent, webFrame, } from 'electron'; +import { CONSTANT } from './util'; export type Channels = | 'ipc-example' | 'webview-message' | 'close' - | 'selectServer'; + | 'selectServer' + | 'selectCapturerSource'; contextBridge.exposeInMainWorld('electron', { ipcRenderer: { @@ -26,6 +28,14 @@ contextBridge.exposeInMainWorld('electron', { once(channel: Channels, func: (...args: unknown[]) => void) { ipcRenderer.once(channel, (_event, ...args) => func(...args)); }, + getDesktopCapturerSource: + async (): Promise => { + const source = await ipcRenderer.invoke( + CONSTANT.DESKTOP_CAPTURER_GET_SOURCES + ); + + return source; + }, }, }); diff --git a/client/desktop/src/main/util.ts b/client/desktop/src/main/util.ts index ddea9da5352..50dd8d3bdbc 100644 --- a/client/desktop/src/main/util.ts +++ b/client/desktop/src/main/util.ts @@ -16,3 +16,7 @@ export function resolveHtmlPath(htmlFileName: string) { export function getDefaultLoggerPath(): string { return log.transports.file.getFile().path; } + +export const CONSTANT = { + DESKTOP_CAPTURER_GET_SOURCES: 'DESKTOP_CAPTURER_GET_SOURCES', +}; diff --git a/client/desktop/src/renderer/preload.d.ts b/client/desktop/src/renderer/preload.d.ts index e61057df528..7a0edf62129 100644 --- a/client/desktop/src/renderer/preload.d.ts +++ b/client/desktop/src/renderer/preload.d.ts @@ -10,6 +10,9 @@ declare global { func: (...args: unknown[]) => void ): (() => void) | undefined; once(channel: string, func: (...args: unknown[]) => void): void; + getDesktopCapturerSource: () => Promise< + Electron.DesktopCapturerSource[] + >; }; }; } diff --git a/client/web/plugins/com.msgbyte.env.electron/src/index.tsx b/client/web/plugins/com.msgbyte.env.electron/src/index.tsx index 202e5e4a164..287e9f3377b 100644 --- a/client/web/plugins/com.msgbyte.env.electron/src/index.tsx +++ b/client/web/plugins/com.msgbyte.env.electron/src/index.tsx @@ -42,3 +42,23 @@ forwardSharedEvent('receiveUnmutedMessage'); setTimeout(() => { checkUpdate(); }, 1000); + +navigator.mediaDevices.getDisplayMedia = async ( + options: DisplayMediaStreamOptions +) => { + const source = await ( + window as any + ).electron.ipcRenderer.getDesktopCapturerSource(); + + const stream = await window.navigator.mediaDevices.getUserMedia({ + // audio: options.audio, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: source.id, + }, + } as any, + }); + + return stream; +};