diff --git a/packages/discord-types/src/stores/MediaEngineStore.d.ts b/packages/discord-types/src/stores/MediaEngineStore.d.ts index d44f5aa58..4ef81639d 100644 --- a/packages/discord-types/src/stores/MediaEngineStore.d.ts +++ b/packages/discord-types/src/stores/MediaEngineStore.d.ts @@ -1,10 +1,37 @@ import { FluxStore } from ".."; export class MediaEngineStore extends FluxStore { - isSelfMute(): boolean; - isSelfDeaf(): boolean; + isAdvancedVoiceActivitySupported(): boolean; + isAecDumpSupported(): boolean; + isAnyLocalVideoAutoDisabled(): boolean; + isAutomaticGainControlSupported(): boolean; + isDeaf(): boolean; + isEnableHardwareMuteNotice(): boolean; + isEnabled(): boolean; + isExperimentalEncodersSupported(): boolean; + isHardwareMute(): boolean; + isInputProfileCustom(): boolean; + isInteractionRequired(): boolean; + isLocalMute(): boolean; isLocalMute(userId): boolean; + isLocalVideoAutoDisabled(userId): boolean; isLocalVideoDisabled(userId): boolean; + isLocalVideoDisabled(): boolean; + isMediaFilterSettingLoading(): boolean; + isMute(): boolean; + isNativeAudioPermissionReady(): boolean; + isNoiseCancellationError(): boolean; + isNoiseCancellationSupported(): boolean; + isNoiseSuppressionSupported(): boolean; + isScreenSharing(): boolean; + isSelfDeaf(): boolean; + isSelfMute(): boolean; + isSelfMutedTemporarily(): boolean; + isSimulcastSupported(): boolean; + isSoundSharing(): boolean; + isSupported(): boolean; + isVideoAvailable(): boolean; + isVideoEnabled(): boolean; setAv1Enabled(AV1: boolean): void; setH265Enabled(H265: boolean): void; diff --git a/src/equicordplugins/instantScreenshare/index.tsx b/src/equicordplugins/instantScreenshare/index.tsx index 980973b9c..8a148c4ac 100644 --- a/src/equicordplugins/instantScreenshare/index.tsx +++ b/src/equicordplugins/instantScreenshare/index.tsx @@ -4,45 +4,60 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { getUserSettingLazy } from "@api/UserSettings"; import { HeadingSecondary } from "@components/Heading"; import { Paragraph } from "@components/Paragraph"; -import { Devs } from "@utils/constants"; +import { Devs, EquicordDevs } from "@utils/constants"; import definePlugin from "@utils/types"; import { VoiceState } from "@vencord/discord-types"; -import { findByCodeLazy } from "@webpack"; -import { ChannelStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common"; +import { findByCodeLazy, findStoreLazy } from "@webpack"; +import { ChannelStore, MediaEngineStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore, VoiceActions } from "@webpack/common"; import { getCurrentMedia, settings } from "./utils"; -const startStream = findByCodeLazy('type:"STREAM_START"'); - let hasStreamed; +const startStream = findByCodeLazy('type:"STREAM_START"'); +const StreamPreviewSettings = getUserSettingLazy("voiceAndVideo", "disableStreamPreviews")!; +const ApplicationStreamingSettingsStore = findStoreLazy("ApplicationStreamingSettingsStore"); async function autoStartStream() { const selected = SelectedChannelStore.getVoiceChannelId(); if (!selected) return; - const channel = ChannelStore.getChannel(selected); - if (channel.type === 13 || !PermissionStore.can(PermissionsBits.STREAM, channel)) return; + const channel = ChannelStore.getChannel(selected); + const isGuildChannel = !channel.isDM() && !channel.isGroupDM(); + + if (channel.type === 13 || isGuildChannel && !PermissionStore.can(PermissionsBits.STREAM, channel)) return; + + if (settings.store.autoDeafen && !MediaEngineStore.isSelfDeaf()) { + VoiceActions.toggleSelfDeaf(); + } else if (settings.store.autoMute && !MediaEngineStore.isSelfMute()) { + VoiceActions.toggleSelfMute(); + } const streamMedia = await getCurrentMedia(); + const preview = StreamPreviewSettings.getSetting(); + const { soundshareEnabled } = ApplicationStreamingSettingsStore.getState(); + let sourceId = streamMedia.id; + if (streamMedia.type === "video_device") sourceId = `camera:${streamMedia.id}`; - startStream(channel.guild_id, selected, { + startStream(channel.guild_id ?? null, selected, { "pid": null, - "sourceId": streamMedia.id, + "sourceId": sourceId, "sourceName": streamMedia.name, - "audioSourceId": null, - "sound": true, - "previewDisabled": false + "audioSourceId": streamMedia.name, + "sound": soundshareEnabled, + "previewDisabled": preview }); } export default definePlugin({ name: "InstantScreenshare", - description: "Instantly screenshare when joining a voice channel", - authors: [Devs.HAHALOSAH, Devs.thororen], + description: "Instantly screenshare when joining a voice channel with support for desktop sources, windows, and video input devices (cameras, capture cards)", + authors: [Devs.HAHALOSAH, Devs.thororen, EquicordDevs.mart], getCurrentMedia, settings, + settingsAboutComponent: () => ( <> For Linux @@ -51,8 +66,19 @@ export default definePlugin({
For X11 it may or may not work :shrug: +
+ Video Devices + + Supports cameras and capture cards (like Elgato HD60X) when enabled in settings + +
+ Regarding Sound & Preview Settings + + We use the settings set and used by discord to decide if stream preview and sound should be enabled or not + ), + flux: { async VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) { const myId = UserStore.getCurrentUser().id; diff --git a/src/equicordplugins/instantScreenshare/utils.tsx b/src/equicordplugins/instantScreenshare/utils.tsx index 853b1c14d..b73bc5ce7 100644 --- a/src/equicordplugins/instantScreenshare/utils.tsx +++ b/src/equicordplugins/instantScreenshare/utils.tsx @@ -9,7 +9,7 @@ import { Heading } from "@components/Heading"; import { Paragraph } from "@components/Paragraph"; import { Logger } from "@utils/Logger"; import { OptionType } from "@utils/types"; -import { findByCodeLazy } from "@webpack"; +import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { MediaEngineStore, SearchableSelect, useEffect, useState } from "@webpack/common"; interface PickerProps { @@ -18,26 +18,52 @@ interface PickerProps { } const getDesktopSources = findByCodeLazy("desktop sources"); - +const configModule = findByPropsLazy("getOutputVolume"); +const log = new Logger("InstantScreenShare"); export const settings = definePluginSettings({ streamMedia: { type: OptionType.COMPONENT, component: SettingSection, }, + includeVideoDevices: { + type: OptionType.BOOLEAN, + description: "Include video input devices (cameras, capture cards) in the source list", + default: false, + }, + autoMute: { + type: OptionType.BOOLEAN, + description: "Automatically mute your microphone when joining a voice channel", + default: false, + }, + autoDeafen: { + type: OptionType.BOOLEAN, + description: "Automatically deafen when joining a voice channel (also mutes you)", + default: false, + }, }); export async function getCurrentMedia() { const media = MediaEngineStore.getMediaEngine(); - const sources = [ - ...(await getDesktopSources(media, ["screen"], null) ?? []), - ...(await getDesktopSources(media, ["window", "application"], null) ?? []) - ]; - const streamMedia = sources.find(screen => screen.id === settings.store.streamMedia); - console.log(sources); + const sources = await getDesktopSources(media, ["screen", "window"], null) ?? []; + if (settings.store.includeVideoDevices) { + try { + const videoDevices = Object.values(configModule.getVideoDevices() || {}); + const videoSources = videoDevices.map((device: any) => ({ + id: device.id, + name: device.name, + type: "video_device" + })); + sources.push(...videoSources); + } catch (e) { + new log.warn("Failed to get video devices:", e); + } + } + + const streamMedia = sources.find(screen => screen.id === settings.store.streamMedia); if (streamMedia) return streamMedia; - new Logger("InstantScreenShare").error(`Stream Media "${settings.store.streamMedia}" not found. Resetting to default.`); + log.error(`Stream Media "${settings.store.streamMedia}" not found. Resetting to default.`); settings.store.streamMedia = sources[0]; return sources[0]; @@ -63,7 +89,7 @@ function StreamSimplePicker({ streamMediaSelection, streamMedia }: PickerProps) } function ScreenSetting() { - const { streamMedia } = settings.use(["streamMedia"]); + const { streamMedia, includeVideoDevices } = settings.use(["streamMedia", "includeVideoDevices"]); const media = MediaEngineStore.getMediaEngine(); const [streamMediaSelection, setStreamMediaSelection] = useState([]); const [loading, setLoading] = useState(true); @@ -72,10 +98,21 @@ function ScreenSetting() { let active = true; async function fetchMedia() { setLoading(true); - const sources = [ - ...(await getDesktopSources(media, ["screen"], null) ?? []), - ...(await getDesktopSources(media, ["window", "application"], null) ?? []) - ]; + const sources = await getDesktopSources(media, ["screen", "window"], null) ?? []; + + if (includeVideoDevices) { + try { + const videoDevices = Object.values(configModule.getVideoDevices() || {}); + const videoSources = videoDevices.map((device: any) => ({ + id: device.id, + name: device.name, + type: "video_device" + })); + sources.push(...videoSources); + } catch (e) { + log.warn("Failed to get video devices:", e); + } + } if (active) { setStreamMediaSelection(sources); @@ -84,7 +121,7 @@ function ScreenSetting() { } fetchMedia(); return () => { active = false; }; - }, []); + }, [includeVideoDevices]); if (loading) return Loading media sources...; if (!streamMediaSelection.length) return No Media found.; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 29995ce27..d52c83bc3 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1174,6 +1174,10 @@ export const EquicordDevs = Object.freeze({ name: "ZcraftElite", id: 926788037785047050n }, + mart: { + name: "mja00", + id: 108698087769260032n + }, } satisfies Record); // iife so #__PURE__ works correctly