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:
@@ -4,7 +4,7 @@ import { WorkAdventureDesktopApi } from "@wa-preload-app";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
WAD: WorkAdventureDesktopApi;
|
||||
WAD?: WorkAdventureDesktopApi;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
import { actionsMenuStore } from "../Stores/ActionsMenuStore";
|
||||
import ActionsMenu from "./ActionsMenu/ActionsMenu.svelte";
|
||||
import Lazy from "./Lazy.svelte";
|
||||
import { showDesktopCapturerSourcePicker } from "../Stores/ScreenSharingStore";
|
||||
|
||||
let mainLayout: HTMLDivElement;
|
||||
|
||||
@@ -119,6 +120,11 @@
|
||||
|
||||
<Lazy when={$emoteMenuStore} component={() => import("./EmoteMenu/EmoteMenu.svelte")} />
|
||||
|
||||
<Lazy
|
||||
when={$showDesktopCapturerSourcePicker}
|
||||
component={() => import("./Video/DesktopCapturerSourcePicker.svelte")}
|
||||
/>
|
||||
|
||||
{#if hasEmbedScreen}
|
||||
<EmbedScreensContainer />
|
||||
{/if}
|
||||
|
||||
@@ -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 type { LocalStreamStoreValue } from "./MediaStore";
|
||||
import { myCameraVisibilityStore } from "./MyCameraStoreVisibility";
|
||||
import type { DesktopCapturerSource } from "@wa-preload-app";
|
||||
|
||||
declare const navigator: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@@ -91,6 +92,25 @@ export const screenSharingConstraintsStore = derived(
|
||||
} 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)
|
||||
*/
|
||||
@@ -110,7 +130,9 @@ export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstra
|
||||
}
|
||||
|
||||
let currentStreamPromise: Promise<MediaStream>;
|
||||
if (navigator.getDisplayMedia) {
|
||||
if (window.WAD?.getDesktopCapturerSources) {
|
||||
currentStreamPromise = getDesktopCapturerSources();
|
||||
} else if (navigator.getDisplayMedia) {
|
||||
currentStreamPromise = navigator.getDisplayMedia({ constraints });
|
||||
} else if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
|
||||
currentStreamPromise = navigator.mediaDevices.getDisplayMedia({ constraints });
|
||||
@@ -200,3 +222,7 @@ export const screenSharingLocalMedia = readable<ScreenSharingLocalMedia | null>(
|
||||
unsubscribe();
|
||||
};
|
||||
});
|
||||
|
||||
export const showDesktopCapturerSourcePicker = writable(false);
|
||||
|
||||
export let desktopCapturerSourcePromiseResolve: ((source: DesktopCapturerSource | null) => void) | undefined;
|
||||
|
||||
Reference in New Issue
Block a user