Enable screensharing in desktop app (#2042)
* enable screensharing in desktop app * add source picker * update dependencies * improve source picker * improve source picker * update thumbnails * decrease thumbnail size * revert unnecessary changes in screen sharing store * fix types and eslint * fix prettier * remove unused import
This commit is contained in:
parent
2412f4076f
commit
1c9caa690a
@ -31,13 +31,13 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/auto-launch": "^5.0.2",
|
"@types/auto-launch": "^5.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
||||||
"@typescript-eslint/parser": "^2.26.0",
|
"@typescript-eslint/parser": "^5.18.0",
|
||||||
"electron": "^17.0.1",
|
"electron": "^18.0.3",
|
||||||
"electron-builder": "^22.14.13",
|
"electron-builder": "^22.14.13",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^8.12.0",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.6.2",
|
||||||
"tsup": "^5.11.13",
|
"tsup": "^5.12.4",
|
||||||
"typescript": "^3.8.3"
|
"typescript": "^4.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import { setLogLevel } from "./log";
|
|||||||
import "./serve"; // prepare custom url scheme
|
import "./serve"; // prepare custom url scheme
|
||||||
import { loadShortcuts } from "./shortcuts";
|
import { loadShortcuts } from "./shortcuts";
|
||||||
|
|
||||||
function init() {
|
async function init() {
|
||||||
const appLock = app.requestSingleInstanceLock();
|
const appLock = app.requestSingleInstanceLock();
|
||||||
|
|
||||||
if (!appLock) {
|
if (!appLock) {
|
||||||
@ -21,7 +21,7 @@ function init() {
|
|||||||
|
|
||||||
app.on("second-instance", () => {
|
app.on("second-instance", () => {
|
||||||
// re-create window if closed
|
// re-create window if closed
|
||||||
createWindow();
|
void createWindow();
|
||||||
|
|
||||||
const mainWindow = getWindow();
|
const mainWindow = getWindow();
|
||||||
|
|
||||||
@ -36,15 +36,15 @@ function init() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// This method will be called when Electron has finished loading
|
// This method will be called when Electron has finished loading
|
||||||
app.whenReady().then(async () => {
|
await app.whenReady().then(async () => {
|
||||||
await settings.init();
|
await settings.init();
|
||||||
|
|
||||||
setLogLevel(settings.get("log_level") || "info");
|
setLogLevel(settings.get("log_level") || "info");
|
||||||
|
|
||||||
autoUpdater.init();
|
await autoUpdater.init();
|
||||||
|
|
||||||
// enable auto launch
|
// enable auto launch
|
||||||
updateAutoLaunch();
|
await updateAutoLaunch();
|
||||||
|
|
||||||
// load ipc handler
|
// load ipc handler
|
||||||
ipc();
|
ipc();
|
||||||
@ -72,7 +72,7 @@ function init() {
|
|||||||
// On macOS it's common to re-create a window in the app when the
|
// On macOS it's common to re-create a window in the app when the
|
||||||
// dock icon is clicked and there are no other windows open.
|
// dock icon is clicked and there are no other windows open.
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
createWindow();
|
void createWindow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -38,32 +38,35 @@ export async function manualRequestUpdateCheck() {
|
|||||||
isManualRequestedUpdate = false;
|
isManualRequestedUpdate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
async function init() {
|
||||||
autoUpdater.logger = log;
|
autoUpdater.logger = log;
|
||||||
|
|
||||||
autoUpdater.on("update-downloaded", ({ releaseNotes, releaseName }) => {
|
autoUpdater.on(
|
||||||
(async () => {
|
"update-downloaded",
|
||||||
const dialogOpts = {
|
({ releaseNotes, releaseName }: { releaseNotes: string; releaseName: string }) => {
|
||||||
type: "question",
|
void (async () => {
|
||||||
buttons: ["Install and Restart", "Install Later"],
|
const dialogOpts = {
|
||||||
defaultId: 0,
|
type: "question",
|
||||||
title: "WorkAdventure - Update",
|
buttons: ["Install and Restart", "Install Later"],
|
||||||
message: process.platform === "win32" ? releaseNotes : releaseName,
|
defaultId: 0,
|
||||||
detail: "A new version has been downloaded. Restart the application to apply the updates.",
|
title: "WorkAdventure - Update",
|
||||||
};
|
message: process.platform === "win32" ? releaseNotes : releaseName,
|
||||||
|
detail: "A new version has been downloaded. Restart the application to apply the updates.",
|
||||||
|
};
|
||||||
|
|
||||||
const { response } = await dialog.showMessageBox(dialogOpts);
|
const { response } = await dialog.showMessageBox(dialogOpts);
|
||||||
if (response === 0) {
|
if (response === 0) {
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
|
|
||||||
autoUpdater.quitAndInstall();
|
autoUpdater.quitAndInstall();
|
||||||
|
|
||||||
// Force app to quit. This is just a workaround, ideally autoUpdater.quitAndInstall() should relaunch the app.
|
// Force app to quit. This is just a workaround, ideally autoUpdater.quitAndInstall() should relaunch the app.
|
||||||
// app.confirmedExitPrompt = true;
|
// app.confirmedExitPrompt = true;
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (process.platform === "linux" && !process.env.APPIMAGE) {
|
if (process.platform === "linux" && !process.env.APPIMAGE) {
|
||||||
autoUpdater.autoDownload = false;
|
autoUpdater.autoDownload = false;
|
||||||
@ -85,7 +88,7 @@ function init() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
checkForUpdates();
|
await checkForUpdates();
|
||||||
|
|
||||||
// run update check every hour again
|
// run update check every hour again
|
||||||
setInterval(() => checkForUpdates, 1000 * 60 * 1);
|
setInterval(() => checkForUpdates, 1000 * 60 * 1);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ipcMain, app } from "electron";
|
import { ipcMain, app, desktopCapturer } from "electron";
|
||||||
import electronIsDev from "electron-is-dev";
|
import electronIsDev from "electron-is-dev";
|
||||||
import { createAndShowNotification } from "./notification";
|
import { createAndShowNotification } from "./notification";
|
||||||
import { Server } from "./preload-local-app/types";
|
import { Server } from "./preload-local-app/types";
|
||||||
@ -30,10 +30,18 @@ export default () => {
|
|||||||
ipcMain.handle("get-version", () => (electronIsDev ? "dev" : app.getVersion()));
|
ipcMain.handle("get-version", () => (electronIsDev ? "dev" : app.getVersion()));
|
||||||
|
|
||||||
// app ipc
|
// app ipc
|
||||||
ipcMain.on("app:notify", (event, txt) => {
|
ipcMain.on("app:notify", (event, txt: string) => {
|
||||||
createAndShowNotification({ body: txt });
|
createAndShowNotification({ body: txt });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("app:getDesktopCapturerSources", async (event, options: Electron.SourcesOptions) => {
|
||||||
|
return (await desktopCapturer.getSources(options)).map((source) => ({
|
||||||
|
id: source.id,
|
||||||
|
name: source.name,
|
||||||
|
thumbnailURL: source.thumbnail.toDataURL(),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
// local-app ipc
|
// local-app ipc
|
||||||
ipcMain.handle("local-app:showLocalApp", () => {
|
ipcMain.handle("local-app:showLocalApp", () => {
|
||||||
hideAppView();
|
hideAppView();
|
||||||
@ -43,7 +51,7 @@ export default () => {
|
|||||||
return settings.get("servers");
|
return settings.get("servers");
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle("local-app:selectServer", (event, serverId: string) => {
|
ipcMain.handle("local-app:selectServer", async (event, serverId: string) => {
|
||||||
const servers = settings.get("servers") || [];
|
const servers = settings.get("servers") || [];
|
||||||
const selectedServer = servers.find((s) => s._id === serverId);
|
const selectedServer = servers.find((s) => s._id === serverId);
|
||||||
|
|
||||||
@ -51,7 +59,7 @@ export default () => {
|
|||||||
return new Error("Server not found");
|
return new Error("Server not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
showAppView(selectedServer.url);
|
await showAppView(selectedServer.url);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ function onError(e: Error) {
|
|||||||
function onRejection(reason: Error) {
|
function onRejection(reason: Error) {
|
||||||
if (reason instanceof Error) {
|
if (reason instanceof Error) {
|
||||||
let _reason = reason;
|
let _reason = reason;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const errPrototype = Object.getPrototypeOf(reason);
|
const errPrototype = Object.getPrototypeOf(reason);
|
||||||
const nameProperty = Object.getOwnPropertyDescriptor(errPrototype, "name");
|
const nameProperty = Object.getOwnPropertyDescriptor(errPrototype, "name");
|
||||||
|
|
||||||
|
@ -2,4 +2,4 @@ import app from "./app";
|
|||||||
import log from "./log";
|
import log from "./log";
|
||||||
|
|
||||||
log.init();
|
log.init();
|
||||||
app.init();
|
void app.init();
|
||||||
|
@ -8,6 +8,7 @@ const api: WorkAdventureDesktopApi = {
|
|||||||
notify: (txt) => ipcRenderer.send("app:notify", txt),
|
notify: (txt) => ipcRenderer.send("app:notify", txt),
|
||||||
onMuteToggle: (callback) => ipcRenderer.on("app:on-mute-toggle", callback),
|
onMuteToggle: (callback) => ipcRenderer.on("app:on-mute-toggle", callback),
|
||||||
onCameraToggle: (callback) => ipcRenderer.on("app:on-camera-toggle", callback),
|
onCameraToggle: (callback) => ipcRenderer.on("app:on-camera-toggle", callback),
|
||||||
|
getDesktopCapturerSources: (options) => ipcRenderer.invoke("app:getDesktopCapturerSources", options),
|
||||||
};
|
};
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("WAD", api);
|
contextBridge.exposeInMainWorld("WAD", api);
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
// copy of Electron.SourcesOptions to avoid Electron dependency in front
|
||||||
|
export interface SourcesOptions {
|
||||||
|
types: string[];
|
||||||
|
thumbnailSize?: { height: number; width: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DesktopCapturerSource {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
thumbnailURL: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type WorkAdventureDesktopApi = {
|
export type WorkAdventureDesktopApi = {
|
||||||
desktop: boolean;
|
desktop: boolean;
|
||||||
isDevelopment: () => Promise<boolean>;
|
isDevelopment: () => Promise<boolean>;
|
||||||
@ -5,4 +17,5 @@ export type WorkAdventureDesktopApi = {
|
|||||||
notify: (txt: string) => void;
|
notify: (txt: string) => void;
|
||||||
onMuteToggle: (callback: () => void) => void;
|
onMuteToggle: (callback: () => void) => void;
|
||||||
onCameraToggle: (callback: () => void) => void;
|
onCameraToggle: (callback: () => void) => void;
|
||||||
|
getDesktopCapturerSources: (options: SourcesOptions) => Promise<DesktopCapturerSource[]>;
|
||||||
};
|
};
|
||||||
|
@ -36,14 +36,14 @@ export function createTray() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Check for updates",
|
label: "Check for updates",
|
||||||
async click() {
|
click() {
|
||||||
await autoUpdater.manualRequestUpdateCheck();
|
void autoUpdater.manualRequestUpdateCheck();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Open Logs",
|
label: "Open Logs",
|
||||||
click() {
|
click() {
|
||||||
log.openLog();
|
void log.openLog();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -115,7 +115,7 @@ export async function createWindow() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showAppView(url?: string) {
|
export async function showAppView(url?: string) {
|
||||||
if (!appView) {
|
if (!appView) {
|
||||||
throw new Error("App view not found");
|
throw new Error("App view not found");
|
||||||
}
|
}
|
||||||
@ -130,7 +130,7 @@ export function showAppView(url?: string) {
|
|||||||
mainWindow.addBrowserView(appView);
|
mainWindow.addBrowserView(appView);
|
||||||
|
|
||||||
if (url && url !== appViewUrl) {
|
if (url && url !== appViewUrl) {
|
||||||
appView.webContents.loadURL(url);
|
await appView.webContents.loadURL(url);
|
||||||
appViewUrl = url;
|
appViewUrl = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@ import { WorkAdventureDesktopApi } from "@wa-preload-app";
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
WAD: WorkAdventureDesktopApi;
|
WAD?: WorkAdventureDesktopApi;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
import { actionsMenuStore } from "../Stores/ActionsMenuStore";
|
import { actionsMenuStore } from "../Stores/ActionsMenuStore";
|
||||||
import ActionsMenu from "./ActionsMenu/ActionsMenu.svelte";
|
import ActionsMenu from "./ActionsMenu/ActionsMenu.svelte";
|
||||||
import Lazy from "./Lazy.svelte";
|
import Lazy from "./Lazy.svelte";
|
||||||
|
import { showDesktopCapturerSourcePicker } from "../Stores/ScreenSharingStore";
|
||||||
|
|
||||||
let mainLayout: HTMLDivElement;
|
let mainLayout: HTMLDivElement;
|
||||||
|
|
||||||
@ -119,6 +120,11 @@
|
|||||||
|
|
||||||
<Lazy when={$emoteMenuStore} component={() => import("./EmoteMenu/EmoteMenu.svelte")} />
|
<Lazy when={$emoteMenuStore} component={() => import("./EmoteMenu/EmoteMenu.svelte")} />
|
||||||
|
|
||||||
|
<Lazy
|
||||||
|
when={$showDesktopCapturerSourcePicker}
|
||||||
|
component={() => import("./Video/DesktopCapturerSourcePicker.svelte")}
|
||||||
|
/>
|
||||||
|
|
||||||
{#if hasEmbedScreen}
|
{#if hasEmbedScreen}
|
||||||
<EmbedScreensContainer />
|
<EmbedScreensContainer />
|
||||||
{/if}
|
{/if}
|
||||||
|
171
front/src/Components/Video/DesktopCapturerSourcePicker.svelte
Normal file
171
front/src/Components/Video/DesktopCapturerSourcePicker.svelte
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fly } from "svelte/transition";
|
||||||
|
import {
|
||||||
|
desktopCapturerSourcePromiseResolve,
|
||||||
|
showDesktopCapturerSourcePicker,
|
||||||
|
} from "../../Stores/ScreenSharingStore";
|
||||||
|
import { onDestroy, onMount } from "svelte";
|
||||||
|
import type { DesktopCapturerSource } from "@wa-preload-app";
|
||||||
|
|
||||||
|
let desktopCapturerSources: DesktopCapturerSource[] = [];
|
||||||
|
let interval: ReturnType<typeof setInterval>;
|
||||||
|
|
||||||
|
async function getDesktopCapturerSources() {
|
||||||
|
if (!window.WAD) {
|
||||||
|
throw new Error("This component can only be used in the desktop app");
|
||||||
|
}
|
||||||
|
desktopCapturerSources = await window.WAD.getDesktopCapturerSources({
|
||||||
|
thumbnailSize: {
|
||||||
|
height: 144,
|
||||||
|
width: 256,
|
||||||
|
},
|
||||||
|
types: ["screen", "window"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await getDesktopCapturerSources();
|
||||||
|
interval = setInterval(() => {
|
||||||
|
void getDesktopCapturerSources();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
clearInterval(interval);
|
||||||
|
});
|
||||||
|
|
||||||
|
function selectDesktopCapturerSource(source: DesktopCapturerSource) {
|
||||||
|
if (!desktopCapturerSourcePromiseResolve) {
|
||||||
|
throw new Error("desktopCapturerSourcePromiseResolve is not defined");
|
||||||
|
}
|
||||||
|
desktopCapturerSourcePromiseResolve(source);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel() {
|
||||||
|
if (!desktopCapturerSourcePromiseResolve) {
|
||||||
|
throw new Error("desktopCapturerSourcePromiseResolve is not defined");
|
||||||
|
}
|
||||||
|
desktopCapturerSourcePromiseResolve(null);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
$showDesktopCapturerSourcePicker = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="source-picker nes-container is-rounded" transition:fly={{ y: -50, duration: 500 }}>
|
||||||
|
<button type="button" class="nes-btn is-error close" on:click={cancel}>×</button>
|
||||||
|
<h2>Select a Screen or Window to share!</h2>
|
||||||
|
<section class="streams">
|
||||||
|
{#each desktopCapturerSources as source}
|
||||||
|
<div
|
||||||
|
class="media-box nes-container is-rounded clickable"
|
||||||
|
on:click|preventDefault={() => selectDesktopCapturerSource(source)}
|
||||||
|
>
|
||||||
|
<img src={source.thumbnailURL} alt={source.name} />
|
||||||
|
<div class="container">
|
||||||
|
{source.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.source-picker {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: auto;
|
||||||
|
background: #eceeee;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 4%;
|
||||||
|
height: 80vh;
|
||||||
|
width: 80vw;
|
||||||
|
max-width: 1024px;
|
||||||
|
z-index: 900;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #333333;
|
||||||
|
color: whitesmoke;
|
||||||
|
|
||||||
|
.nes-btn.is-error.close {
|
||||||
|
position: absolute;
|
||||||
|
top: -20px;
|
||||||
|
right: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
}
|
||||||
|
|
||||||
|
section.streams {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
overflow-y: auto;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: flex-start;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-box {
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
width: calc(100% / 3 - 20px);
|
||||||
|
max-width: 256px;
|
||||||
|
padding-bottom: calc(min((100% / 3 - 20px), 256px) * (144px / 256px));
|
||||||
|
max-height: 144px;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #000;
|
||||||
|
background-clip: padding-box;
|
||||||
|
|
||||||
|
&.clickable * {
|
||||||
|
cursor: url("../../../style/images/cursor_pointer.png"), pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.nes-container.is-rounded {
|
||||||
|
border-image-outset: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.container {
|
||||||
|
position: absolute;
|
||||||
|
width: 90%;
|
||||||
|
height: auto;
|
||||||
|
left: 5%;
|
||||||
|
top: calc(100% - 28px);
|
||||||
|
text-align: center;
|
||||||
|
padding: 2px 36px;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 2px;
|
||||||
|
background-color: white;
|
||||||
|
color: #333333;
|
||||||
|
border: solid 3px black;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -2,6 +2,7 @@ import { derived, Readable, readable, writable } from "svelte/store";
|
|||||||
import { peerStore } from "./PeerStore";
|
import { peerStore } from "./PeerStore";
|
||||||
import type { LocalStreamStoreValue } from "./MediaStore";
|
import type { LocalStreamStoreValue } from "./MediaStore";
|
||||||
import { myCameraVisibilityStore } from "./MyCameraStoreVisibility";
|
import { myCameraVisibilityStore } from "./MyCameraStoreVisibility";
|
||||||
|
import type { DesktopCapturerSource } from "@wa-preload-app";
|
||||||
|
|
||||||
declare const navigator: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
declare const navigator: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
@ -91,6 +92,25 @@ export const screenSharingConstraintsStore = derived(
|
|||||||
} as MediaStreamConstraints
|
} as MediaStreamConstraints
|
||||||
);
|
);
|
||||||
|
|
||||||
|
async function getDesktopCapturerSources() {
|
||||||
|
showDesktopCapturerSourcePicker.set(true);
|
||||||
|
const source = await new Promise<DesktopCapturerSource | null>((resolve) => {
|
||||||
|
desktopCapturerSourcePromiseResolve = resolve;
|
||||||
|
});
|
||||||
|
if (source === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return navigator.mediaDevices.getUserMedia({
|
||||||
|
audio: false,
|
||||||
|
video: {
|
||||||
|
mandatory: {
|
||||||
|
chromeMediaSource: "desktop",
|
||||||
|
chromeMediaSourceId: source.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A store containing the MediaStream object for ScreenSharing (or null if nothing requested, or Error if an error occurred)
|
* A store containing the MediaStream object for ScreenSharing (or null if nothing requested, or Error if an error occurred)
|
||||||
*/
|
*/
|
||||||
@ -110,7 +130,9 @@ export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstra
|
|||||||
}
|
}
|
||||||
|
|
||||||
let currentStreamPromise: Promise<MediaStream>;
|
let currentStreamPromise: Promise<MediaStream>;
|
||||||
if (navigator.getDisplayMedia) {
|
if (window.WAD?.getDesktopCapturerSources) {
|
||||||
|
currentStreamPromise = getDesktopCapturerSources();
|
||||||
|
} else if (navigator.getDisplayMedia) {
|
||||||
currentStreamPromise = navigator.getDisplayMedia({ constraints });
|
currentStreamPromise = navigator.getDisplayMedia({ constraints });
|
||||||
} else if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
|
} else if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
|
||||||
currentStreamPromise = navigator.mediaDevices.getDisplayMedia({ constraints });
|
currentStreamPromise = navigator.mediaDevices.getDisplayMedia({ constraints });
|
||||||
@ -200,3 +222,7 @@ export const screenSharingLocalMedia = readable<ScreenSharingLocalMedia | null>(
|
|||||||
unsubscribe();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const showDesktopCapturerSourcePicker = writable(false);
|
||||||
|
|
||||||
|
export let desktopCapturerSourcePromiseResolve: ((source: DesktopCapturerSource | null) => void) | undefined;
|
||||||
|
Loading…
Reference in New Issue
Block a user