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:
parent
2760bfc836
commit
cb03aac4cf
5 changed files with 5 additions and 137 deletions
|
@ -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 => {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in a new issue