Switch to the new session API for screen-sharing (#11266)

See https://github.com/electron/electron/pull/30702 - this has the benefit of the js-sdk and LiveKit not having to add custom logic for Electron

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner 2023-07-14 22:51:24 +02:00 committed by GitHub
parent 2760bfc836
commit cb03aac4cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 5 additions and 137 deletions

View file

@ -79,10 +79,10 @@ export class ExistingSource extends React.Component<ExistingSourceIProps> {
export interface PickerIState { export interface PickerIState {
selectedTab: Tabs; selectedTab: Tabs;
sources: Array<DesktopCapturerSource>; sources: Array<DesktopCapturerSource>;
selectedSource: DesktopCapturerSource | null; selectedSource?: DesktopCapturerSource;
} }
export interface PickerIProps { export interface PickerIProps {
onFinished(sourceId?: string): void; onFinished(source?: DesktopCapturerSource): void;
} }
type TabId = "screen" | "window"; type TabId = "screen" | "window";
@ -96,7 +96,6 @@ export default class DesktopCapturerSourcePicker extends React.Component<PickerI
this.state = { this.state = {
selectedTab: Tabs.Screens, selectedTab: Tabs.Screens,
sources: [], sources: [],
selectedSource: null,
}; };
} }
@ -125,11 +124,11 @@ export default class DesktopCapturerSourcePicker extends React.Component<PickerI
}; };
private onShare = (): void => { private onShare = (): void => {
this.props.onFinished(this.state.selectedSource?.id); this.props.onFinished(this.state.selectedSource);
}; };
private onTabChange = (): void => { private onTabChange = (): void => {
this.setState({ selectedSource: null }); this.setState({ selectedSource: undefined });
}; };
private onCloseClick = (): void => { private onCloseClick = (): void => {

View file

@ -30,12 +30,9 @@ import VideoFeed from "./VideoFeed";
import RoomAvatar from "../avatars/RoomAvatar"; import RoomAvatar from "../avatars/RoomAvatar";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import { avatarUrlForMember } from "../../../Avatar"; import { avatarUrlForMember } from "../../../Avatar";
import DesktopCapturerSourcePicker from "../elements/DesktopCapturerSourcePicker";
import Modal from "../../../Modal";
import LegacyCallViewSidebar from "./LegacyCallViewSidebar"; import LegacyCallViewSidebar from "./LegacyCallViewSidebar";
import LegacyCallViewHeader from "./LegacyCallView/LegacyCallViewHeader"; import LegacyCallViewHeader from "./LegacyCallView/LegacyCallViewHeader";
import LegacyCallViewButtons from "./LegacyCallView/LegacyCallViewButtons"; import LegacyCallViewButtons from "./LegacyCallView/LegacyCallViewButtons";
import PlatformPeg from "../../../PlatformPeg";
import { ActionPayload } from "../../../dispatcher/payloads"; import { ActionPayload } from "../../../dispatcher/payloads";
import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
@ -288,19 +285,9 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
let isScreensharing; let isScreensharing;
if (this.state.screensharing) { if (this.state.screensharing) {
isScreensharing = await this.props.call.setScreensharingEnabled(false); isScreensharing = await this.props.call.setScreensharingEnabled(false);
} else {
if (PlatformPeg.get()?.supportsDesktopCapturer()) {
const { finished } = Modal.createDialog(DesktopCapturerSourcePicker);
const [source] = await finished;
if (!source) return;
isScreensharing = await this.props.call.setScreensharingEnabled(true, {
desktopCapturerSourceId: source,
});
} else { } else {
isScreensharing = await this.props.call.setScreensharingEnabled(true); isScreensharing = await this.props.call.setScreensharingEnabled(true);
} }
}
this.setState({ this.setState({
sidebarShown: true, sidebarShown: true,

View file

@ -48,10 +48,7 @@ import { ElementWidgetActions } from "../stores/widgets/ElementWidgetActions";
import WidgetStore from "../stores/WidgetStore"; import WidgetStore from "../stores/WidgetStore";
import { WidgetMessagingStore, WidgetMessagingStoreEvent } from "../stores/widgets/WidgetMessagingStore"; import { WidgetMessagingStore, WidgetMessagingStoreEvent } from "../stores/widgets/WidgetMessagingStore";
import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../stores/ActiveWidgetStore"; import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../stores/ActiveWidgetStore";
import PlatformPeg from "../PlatformPeg";
import { getCurrentLanguage } from "../languageHandler"; import { getCurrentLanguage } from "../languageHandler";
import DesktopCapturerSourcePicker from "../components/views/elements/DesktopCapturerSourcePicker";
import Modal from "../Modal";
import { FontWatcher } from "../settings/watchers/FontWatcher"; import { FontWatcher } from "../settings/watchers/FontWatcher";
import { PosthogAnalytics } from "../PosthogAnalytics"; import { PosthogAnalytics } from "../PosthogAnalytics";
@ -762,7 +759,6 @@ export class ElementCall extends Call {
this.messaging!.on(`action:${ElementWidgetActions.HangupCall}`, this.onHangup); this.messaging!.on(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
this.messaging!.on(`action:${ElementWidgetActions.TileLayout}`, this.onTileLayout); this.messaging!.on(`action:${ElementWidgetActions.TileLayout}`, this.onTileLayout);
this.messaging!.on(`action:${ElementWidgetActions.SpotlightLayout}`, this.onSpotlightLayout); this.messaging!.on(`action:${ElementWidgetActions.SpotlightLayout}`, this.onSpotlightLayout);
this.messaging!.on(`action:${ElementWidgetActions.ScreenshareRequest}`, this.onScreenshareRequest);
} }
protected async performDisconnection(): Promise<void> { protected async performDisconnection(): Promise<void> {
@ -777,7 +773,6 @@ export class ElementCall extends Call {
this.messaging!.off(`action:${ElementWidgetActions.HangupCall}`, this.onHangup); this.messaging!.off(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
this.messaging!.off(`action:${ElementWidgetActions.TileLayout}`, this.onTileLayout); this.messaging!.off(`action:${ElementWidgetActions.TileLayout}`, this.onTileLayout);
this.messaging!.off(`action:${ElementWidgetActions.SpotlightLayout}`, this.onSpotlightLayout); this.messaging!.off(`action:${ElementWidgetActions.SpotlightLayout}`, this.onSpotlightLayout);
this.messaging!.off(`action:${ElementWidgetActions.ScreenshareRequest}`, this.onScreenshareRequest);
super.setDisconnected(); super.setDisconnected();
this.groupCall.enteredViaAnotherSession = false; this.groupCall.enteredViaAnotherSession = false;
} }
@ -878,25 +873,4 @@ export class ElementCall extends Call {
this.layout = Layout.Spotlight; this.layout = Layout.Spotlight;
await this.messaging!.transport.reply(ev.detail, {}); // ack await this.messaging!.transport.reply(ev.detail, {}); // ack
}; };
private onScreenshareRequest = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
ev.preventDefault();
if (PlatformPeg.get()?.supportsDesktopCapturer()) {
await this.messaging!.transport.reply(ev.detail, { pending: true });
const { finished } = Modal.createDialog(DesktopCapturerSourcePicker);
const [source] = await finished;
if (source) {
await this.messaging!.transport.send(ElementWidgetActions.ScreenshareStart, {
desktopCapturerSourceId: source,
});
} else {
await this.messaging!.transport.send(ElementWidgetActions.ScreenshareStop, {});
}
} else {
await this.messaging!.transport.reply(ev.detail, { pending: false });
}
};
} }

View file

@ -27,19 +27,6 @@ export enum ElementWidgetActions {
UnmuteVideo = "io.element.unmute_video", UnmuteVideo = "io.element.unmute_video",
StartLiveStream = "im.vector.start_live_stream", StartLiveStream = "im.vector.start_live_stream",
// 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",
// Actions for switching layouts // Actions for switching layouts
TileLayout = "io.element.tile_layout", TileLayout = "io.element.tile_layout",
SpotlightLayout = "io.element.spotlight_layout", SpotlightLayout = "io.element.spotlight_layout",

View file

@ -39,8 +39,6 @@ import { WidgetMessagingStore } from "../../src/stores/widgets/WidgetMessagingSt
import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../../src/stores/ActiveWidgetStore"; import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../../src/stores/ActiveWidgetStore";
import { ElementWidgetActions } from "../../src/stores/widgets/ElementWidgetActions"; import { ElementWidgetActions } from "../../src/stores/widgets/ElementWidgetActions";
import SettingsStore from "../../src/settings/SettingsStore"; import SettingsStore from "../../src/settings/SettingsStore";
import Modal, { IHandle } from "../../src/Modal";
import PlatformPeg from "../../src/PlatformPeg";
import { PosthogAnalytics } from "../../src/PosthogAnalytics"; import { PosthogAnalytics } from "../../src/PosthogAnalytics";
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({ jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
@ -947,83 +945,6 @@ describe("ElementCall", () => {
call.off(CallEvent.Layout, onLayout); call.off(CallEvent.Layout, onLayout);
}); });
describe("screensharing", () => {
it("passes source id if we can get it", async () => {
const sourceId = "source_id";
jest.spyOn(Modal, "createDialog").mockReturnValue({
finished: new Promise((r) => r([sourceId])),
} as IHandle<any>);
jest.spyOn(PlatformPeg.get()!, "supportsDesktopCapturer").mockReturnValue(true);
await call.connect();
messaging.emit(
`action:${ElementWidgetActions.ScreenshareRequest}`,
new CustomEvent("widgetapirequest", { detail: {} }),
);
await waitFor(() => {
expect(messaging!.transport.reply).toHaveBeenCalledWith(
expect.objectContaining({}),
expect.objectContaining({ pending: true }),
);
});
await waitFor(() => {
expect(messaging!.transport.send).toHaveBeenCalledWith(
"io.element.screenshare_start",
expect.objectContaining({ desktopCapturerSourceId: sourceId }),
);
});
});
it("sends ScreenshareStop if we couldn't get a source id", async () => {
jest.spyOn(Modal, "createDialog").mockReturnValue({
finished: new Promise((r) => r([null])),
} as IHandle<any>);
jest.spyOn(PlatformPeg.get()!, "supportsDesktopCapturer").mockReturnValue(true);
await call.connect();
messaging.emit(
`action:${ElementWidgetActions.ScreenshareRequest}`,
new CustomEvent("widgetapirequest", { detail: {} }),
);
await waitFor(() => {
expect(messaging!.transport.reply).toHaveBeenCalledWith(
expect.objectContaining({}),
expect.objectContaining({ pending: true }),
);
});
await waitFor(() => {
expect(messaging!.transport.send).toHaveBeenCalledWith(
"io.element.screenshare_stop",
expect.objectContaining({}),
);
});
});
it("replies with pending: false if we don't support desktop capturer", async () => {
jest.spyOn(PlatformPeg.get()!, "supportsDesktopCapturer").mockReturnValue(false);
await call.connect();
messaging.emit(
`action:${ElementWidgetActions.ScreenshareRequest}`,
new CustomEvent("widgetapirequest", { detail: {} }),
);
await waitFor(() => {
expect(messaging!.transport.reply).toHaveBeenCalledWith(
expect.objectContaining({}),
expect.objectContaining({ pending: false }),
);
});
});
});
it("ends the call immediately if we're the last participant to leave", async () => { it("ends the call immediately if we're the last participant to leave", async () => {
await call.connect(); await call.connect();
const onDestroy = jest.fn(); const onDestroy = jest.fn();