diff --git a/src/room/useGroupCall.ts b/src/room/useGroupCall.ts index 87bbdb2..895ef3e 100644 --- a/src/room/useGroupCall.ts +++ b/src/room/useGroupCall.ts @@ -27,10 +27,11 @@ import { MatrixCall } from "matrix-js-sdk/src/webrtc/call"; import { CallFeed } from "matrix-js-sdk/src/webrtc/callFeed"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { useTranslation } from "react-i18next"; +import { IWidgetApiRequest } from "matrix-widget-api"; import { usePageUnload } from "./usePageUnload"; import { TranslatedError, translatedError } from "../TranslatedError"; -import { ElementWidgetActions, widget } from "../widget"; +import { ElementWidgetActions, ScreenshareStartData, widget } from "../widget"; export interface UseGroupCallReturnType { state: GroupCallState; @@ -302,36 +303,82 @@ export function useGroupCall(groupCall: GroupCall): UseGroupCallReturnType { groupCall.setMicrophoneMuted(!groupCall.isMicrophoneMuted()); }, [groupCall]); - const toggleScreensharing = useCallback(() => { - updateState({ requestingScreenshare: true }); + const toggleScreensharing = useCallback(async () => { + if (!groupCall.isScreensharing()) { + // toggling on + updateState({ requestingScreenshare: true }); - if (groupCall.isScreensharing()) { - groupCall.setScreensharingEnabled(false).then(() => { + try { + await groupCall.setScreensharingEnabled(true, { + audio: true, + throwOnFail: true, + }); updateState({ requestingScreenshare: false }); - }); - } else { - widget.api.transport - .send(ElementWidgetActions.Screenshare, {}) - .then( - (reply: { desktopCapturerSourceId: string; failed?: boolean }) => { - if (reply.failed) { - updateState({ requestingScreenshare: false }); - return; - } - - groupCall - .setScreensharingEnabled(true, { - audio: !reply.desktopCapturerSourceId, - desktopCapturerSourceId: reply.desktopCapturerSourceId, - }) - .then(() => { - updateState({ requestingScreenshare: false }); - }); + } catch (e) { + // this will fail in Electron because getDisplayMedia just throws a permission + // error, so if we have a widget API, try requesting via that. + if (widget) { + const reply = await widget.api.transport.send( + ElementWidgetActions.ScreenshareRequest, + {} + ); + if (!reply.pending) { + updateState({ requestingScreenshare: false }); } - ); + } + } + } else { + // toggling off + groupCall.setScreensharingEnabled(false); } }, [groupCall]); + const onScreenshareStart = useCallback( + async (ev: CustomEvent) => { + updateState({ requestingScreenshare: false }); + await groupCall.setScreensharingEnabled(true, { + desktopCapturerSourceId: ev.detail.data + .desktopCapturerSourceId as string, + audio: !ev.detail.data.desktopCapturerSourceId, + }); + await widget.api.transport.reply(ev.detail, {}); + }, + [groupCall] + ); + + const onScreenshareStop = useCallback( + async (ev: CustomEvent) => { + updateState({ requestingScreenshare: false }); + await groupCall.setScreensharingEnabled(false); + await widget.api.transport.reply(ev.detail, {}); + }, + [groupCall] + ); + + useEffect(() => { + if (widget) { + widget.lazyActions.on( + ElementWidgetActions.ScreenshareStart, + onScreenshareStart + ); + widget.lazyActions.on( + ElementWidgetActions.ScreenshareStop, + onScreenshareStop + ); + + return () => { + widget.lazyActions.off( + ElementWidgetActions.ScreenshareStart, + onScreenshareStart + ); + widget.lazyActions.off( + ElementWidgetActions.ScreenshareStop, + onScreenshareStop + ); + }; + } + }, [onScreenshareStart, onScreenshareStop]); + const { t } = useTranslation(); useEffect(() => { diff --git a/src/widget.ts b/src/widget.ts index aa0be83..66ac546 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -30,7 +30,21 @@ export enum ElementWidgetActions { HangupCall = "im.vector.hangup", TileLayout = "io.element.tile_layout", SpotlightLayout = "io.element.spotlight_layout", - Screenshare = "io.element.screenshare", + + // Element Call -> host requesting to start a screenshare + // (ie. expects a ScreenshareStart once the user has picked a source) + // Element Call -> host requesting to start a screenshare + // (ie. expects a ScreenshareStart once the user has picked a source) + // replies with { pending } where pending is true if the host has asked + // the user to choose a window and false if not (ie. if the host isn't + // running within Electron) + ScreenshareRequest = "io.element.screenshare_request", + // host -> Element Call telling EC to start screen sharing with + // the given source + ScreenshareStart = "io.element.screenshare_start", + // host -> Element Call telling EC to stop screen sharing, or that + // the user cancelled when selecting a source after a ScreenshareRequest + ScreenshareStop = "io.element.screenshare_stop", } export interface JoinCallData { @@ -38,6 +52,10 @@ export interface JoinCallData { videoInput: string | null; } +export interface ScreenshareStartData { + desktopCapturerSourceId: string; +} + interface WidgetHelpers { api: WidgetApi; lazyActions: LazyEventEmitter; @@ -69,6 +87,8 @@ export const widget: WidgetHelpers | null = (() => { ElementWidgetActions.HangupCall, ElementWidgetActions.TileLayout, ElementWidgetActions.SpotlightLayout, + ElementWidgetActions.ScreenshareStart, + ElementWidgetActions.ScreenshareStop, ].forEach((action) => { api.on(`action:${action}`, (ev: CustomEvent) => { ev.preventDefault();