feat: allow auto streaming of camera devices (#469)
* feat: allow auto streaming of camera devices * Make My Edits * Merge StartStream + Respect Sound & Preview Settings * Proper About * Redo Logger Naming * Remove Extra Space --------- Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com>
This commit is contained in:
parent
23f49ddcce
commit
3e340f824f
4 changed files with 125 additions and 31 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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: () => (
|
||||
<>
|
||||
<HeadingSecondary>For Linux</HeadingSecondary>
|
||||
|
|
@ -51,8 +66,19 @@ export default definePlugin({
|
|||
<br />
|
||||
For X11 it may or may not work :shrug:
|
||||
</Paragraph>
|
||||
<br />
|
||||
<HeadingSecondary>Video Devices</HeadingSecondary>
|
||||
<Paragraph>
|
||||
Supports cameras and capture cards (like Elgato HD60X) when enabled in settings
|
||||
</Paragraph>
|
||||
<br />
|
||||
<HeadingSecondary>Regarding Sound & Preview Settings</HeadingSecondary>
|
||||
<Paragraph>
|
||||
We use the settings set and used by discord to decide if stream preview and sound should be enabled or not
|
||||
</Paragraph>
|
||||
</>
|
||||
),
|
||||
|
||||
flux: {
|
||||
async VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
|
||||
const myId = UserStore.getCurrentUser().id;
|
||||
|
|
|
|||
|
|
@ -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<any[]>([]);
|
||||
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 <Paragraph>Loading media sources...</Paragraph>;
|
||||
if (!streamMediaSelection.length) return <Paragraph>No Media found.</Paragraph>;
|
||||
|
|
|
|||
|
|
@ -1174,6 +1174,10 @@ export const EquicordDevs = Object.freeze({
|
|||
name: "ZcraftElite",
|
||||
id: 926788037785047050n
|
||||
},
|
||||
mart: {
|
||||
name: "mja00",
|
||||
id: 108698087769260032n
|
||||
},
|
||||
} satisfies Record<string, Dev>);
|
||||
|
||||
// iife so #__PURE__ works correctly
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue