Screensharing rework

Split Linux screensharing audio tracks, Rework screensharing functionality and layout
This will need some refactoring soon
This commit is contained in:
2026-03-08 06:33:27 +01:00
parent d20509566d
commit 7a4c4ede8c
42 changed files with 4998 additions and 475 deletions

View File

@@ -189,6 +189,49 @@
class="w-full h-1.5 bg-secondary rounded-lg appearance-none cursor-pointer accent-primary"
/>
</div>
<div>
<label
for="screen-share-quality-select"
class="block text-xs font-medium text-muted-foreground mb-1"
>Screen share quality</label
>
<select
(change)="onScreenShareQualityChange($event)"
id="screen-share-quality-select"
class="w-full px-3 py-2 bg-secondary rounded-lg border border-border text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-primary"
>
@for (option of screenShareQualityOptions; track option.id) {
<option
[value]="option.id"
[selected]="screenShareQuality() === option.id"
>
{{ option.label }}
</option>
}
</select>
<p class="text-[10px] text-muted-foreground/60 mt-1">
{{ selectedScreenShareQualityDescription() }}
</p>
</div>
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-foreground">Ask before screen sharing</p>
<p class="text-xs text-muted-foreground">Let the user confirm quality before each new screen share</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
[checked]="askScreenShareQuality()"
(change)="onAskScreenShareQualityChange($event)"
id="ask-screen-share-quality-toggle"
aria-label="Toggle screen share quality prompt"
class="sr-only peer"
/>
<div
class="w-10 h-5 bg-secondary rounded-full peer peer-checked:bg-primary peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all"
></div>
</label>
</div>
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-foreground">Noise reduction</p>
@@ -211,7 +254,7 @@
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-foreground">Screen share system audio</p>
<p class="text-xs text-muted-foreground">Include system audio when sharing screen</p>
<p class="text-xs text-muted-foreground">Share other computer audio while filtering MeToYou audio when supported</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input
@@ -227,6 +270,58 @@
></div>
</label>
</div>
<p class="text-[10px] text-muted-foreground/60 -mt-1">
Your microphone stays on the normal voice channel. The shared screen audio should only contain desktop sound.
</p>
</div>
</section>
@if (isElectron) {
<section>
<div class="flex items-center gap-2 mb-3">
<ng-icon
name="lucideCpu"
class="w-5 h-5 text-muted-foreground"
/>
<h4 class="text-sm font-semibold text-foreground">Desktop Performance</h4>
</div>
<div class="space-y-3">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-foreground">Hardware acceleration</p>
<p class="text-xs text-muted-foreground">Use GPU acceleration for rendering and WebRTC when available</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
[checked]="hardwareAcceleration()"
(change)="onHardwareAccelerationChange($event)"
id="hardware-acceleration-toggle"
aria-label="Toggle hardware acceleration"
class="sr-only peer"
/>
<div
class="w-10 h-5 bg-secondary rounded-full peer peer-checked:bg-primary peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all"
></div>
</label>
</div>
@if (hardwareAccelerationRestartRequired()) {
<div class="rounded-lg border border-primary/30 bg-primary/10 p-3 flex items-center justify-between gap-3">
<div>
<p class="text-sm font-medium text-foreground">Restart required</p>
<p class="text-xs text-muted-foreground">Restart MeToYou to apply the new hardware acceleration setting.</p>
</div>
<button
type="button"
(click)="restartDesktopApp()"
class="px-3 py-1.5 rounded-lg bg-primary text-primary-foreground text-xs font-medium hover:bg-primary/90 transition-colors"
>
Restart app
</button>
</div>
}
</div>
</section>
}
</div>

View File

@@ -2,6 +2,7 @@
import {
Component,
inject,
computed,
signal
} from '@angular/core';
import { CommonModule } from '@angular/common';
@@ -10,19 +11,41 @@ import { NgIcon, provideIcons } from '@ng-icons/core';
import {
lucideMic,
lucideHeadphones,
lucideAudioLines
lucideAudioLines,
lucideMonitor,
lucideCpu
} from '@ng-icons/lucide';
import { WebRTCService } from '../../../../core/services/webrtc.service';
import { VoicePlaybackService } from '../../../voice/voice-controls/services/voice-playback.service';
import { NotificationAudioService, AppSound } from '../../../../core/services/notification-audio.service';
import { STORAGE_KEY_VOICE_SETTINGS } from '../../../../core/constants';
import { PlatformService } from '../../../../core/services/platform.service';
import {
loadVoiceSettingsFromStorage,
saveVoiceSettingsToStorage
} from '../../../../core/services/voice-settings.storage';
import {
SCREEN_SHARE_QUALITY_OPTIONS,
ScreenShareQuality
} from '../../../../core/services/webrtc';
interface AudioDevice {
deviceId: string;
label: string;
}
interface DesktopSettingsSnapshot {
hardwareAcceleration: boolean;
runtimeHardwareAcceleration: boolean;
restartRequired: boolean;
}
interface DesktopSettingsElectronApi {
getDesktopSettings?: () => Promise<DesktopSettingsSnapshot>;
setDesktopSettings?: (patch: { hardwareAcceleration?: boolean }) => Promise<DesktopSettingsSnapshot>;
relaunchApp?: () => Promise<boolean>;
}
@Component({
selector: 'app-voice-settings',
standalone: true,
@@ -35,7 +58,9 @@ interface AudioDevice {
provideIcons({
lucideMic,
lucideHeadphones,
lucideAudioLines
lucideAudioLines,
lucideMonitor,
lucideCpu
})
],
templateUrl: './voice-settings.component.html'
@@ -43,7 +68,10 @@ interface AudioDevice {
export class VoiceSettingsComponent {
private webrtcService = inject(WebRTCService);
private voicePlayback = inject(VoicePlaybackService);
private platform = inject(PlatformService);
readonly audioService = inject(NotificationAudioService);
readonly isElectron = this.platform.isElectron;
readonly screenShareQualityOptions = SCREEN_SHARE_QUALITY_OPTIONS;
inputDevices = signal<AudioDevice[]>([]);
outputDevices = signal<AudioDevice[]>([]);
@@ -55,10 +83,19 @@ export class VoiceSettingsComponent {
latencyProfile = signal<'low' | 'balanced' | 'high'>('balanced');
includeSystemAudio = signal(false);
noiseReduction = signal(true);
screenShareQuality = signal<ScreenShareQuality>('balanced');
askScreenShareQuality = signal(true);
hardwareAcceleration = signal(true);
hardwareAccelerationRestartRequired = signal(false);
readonly selectedScreenShareQualityDescription = computed(() => this.screenShareQualityOptions.find((option) => option.id === this.screenShareQuality())?.description ?? '');
constructor() {
this.loadVoiceSettings();
this.loadAudioDevices();
if (this.isElectron) {
void this.loadDesktopSettings();
}
}
async loadAudioDevices(): Promise<void> {
@@ -85,38 +122,18 @@ export class VoiceSettingsComponent {
}
loadVoiceSettings(): void {
try {
const raw = localStorage.getItem(STORAGE_KEY_VOICE_SETTINGS);
const settings = loadVoiceSettingsFromStorage();
if (!raw)
return;
const settings = JSON.parse(raw);
if (settings.inputDevice)
this.selectedInputDevice.set(settings.inputDevice);
if (settings.outputDevice)
this.selectedOutputDevice.set(settings.outputDevice);
if (typeof settings.inputVolume === 'number')
this.inputVolume.set(settings.inputVolume);
if (typeof settings.outputVolume === 'number')
this.outputVolume.set(settings.outputVolume);
if (typeof settings.audioBitrate === 'number')
this.audioBitrate.set(settings.audioBitrate);
if (settings.latencyProfile)
this.latencyProfile.set(settings.latencyProfile);
if (typeof settings.includeSystemAudio === 'boolean')
this.includeSystemAudio.set(settings.includeSystemAudio);
if (typeof settings.noiseReduction === 'boolean')
this.noiseReduction.set(settings.noiseReduction);
} catch {}
this.selectedInputDevice.set(settings.inputDevice);
this.selectedOutputDevice.set(settings.outputDevice);
this.inputVolume.set(settings.inputVolume);
this.outputVolume.set(settings.outputVolume);
this.audioBitrate.set(settings.audioBitrate);
this.latencyProfile.set(settings.latencyProfile);
this.includeSystemAudio.set(settings.includeSystemAudio);
this.noiseReduction.set(settings.noiseReduction);
this.screenShareQuality.set(settings.screenShareQuality);
this.askScreenShareQuality.set(settings.askScreenShareQuality);
if (this.noiseReduction() !== this.webrtcService.isNoiseReductionEnabled()) {
this.webrtcService.toggleNoiseReduction(this.noiseReduction());
@@ -129,21 +146,18 @@ export class VoiceSettingsComponent {
}
saveVoiceSettings(): void {
try {
localStorage.setItem(
STORAGE_KEY_VOICE_SETTINGS,
JSON.stringify({
inputDevice: this.selectedInputDevice(),
outputDevice: this.selectedOutputDevice(),
inputVolume: this.inputVolume(),
outputVolume: this.outputVolume(),
audioBitrate: this.audioBitrate(),
latencyProfile: this.latencyProfile(),
includeSystemAudio: this.includeSystemAudio(),
noiseReduction: this.noiseReduction()
})
);
} catch {}
saveVoiceSettingsToStorage({
inputDevice: this.selectedInputDevice(),
outputDevice: this.selectedOutputDevice(),
inputVolume: this.inputVolume(),
outputVolume: this.outputVolume(),
audioBitrate: this.audioBitrate(),
latencyProfile: this.latencyProfile(),
includeSystemAudio: this.includeSystemAudio(),
noiseReduction: this.noiseReduction(),
screenShareQuality: this.screenShareQuality(),
askScreenShareQuality: this.askScreenShareQuality()
});
}
onInputDeviceChange(event: Event): void {
@@ -202,6 +216,20 @@ export class VoiceSettingsComponent {
this.saveVoiceSettings();
}
onScreenShareQualityChange(event: Event): void {
const select = event.target as HTMLSelectElement;
this.screenShareQuality.set(select.value as ScreenShareQuality);
this.saveVoiceSettings();
}
onAskScreenShareQualityChange(event: Event): void {
const input = event.target as HTMLInputElement;
this.askScreenShareQuality.set(!!input.checked);
this.saveVoiceSettings();
}
async onNoiseReductionChange(): Promise<void> {
this.noiseReduction.update((currentValue) => !currentValue);
await this.webrtcService.toggleNoiseReduction(this.noiseReduction());
@@ -217,4 +245,56 @@ export class VoiceSettingsComponent {
previewNotificationSound(): void {
this.audioService.play(AppSound.Notification);
}
async onHardwareAccelerationChange(event: Event): Promise<void> {
const input = event.target as HTMLInputElement;
const enabled = !!input.checked;
const api = this.getElectronApi();
if (!api?.setDesktopSettings) {
this.hardwareAcceleration.set(enabled);
return;
}
try {
const snapshot = await api.setDesktopSettings({ hardwareAcceleration: enabled });
this.applyDesktopSettings(snapshot);
} catch {
input.checked = this.hardwareAcceleration();
}
}
async restartDesktopApp(): Promise<void> {
const api = this.getElectronApi();
if (api?.relaunchApp) {
await api.relaunchApp();
}
}
private async loadDesktopSettings(): Promise<void> {
const api = this.getElectronApi();
if (!api?.getDesktopSettings) {
return;
}
try {
const snapshot = await api.getDesktopSettings();
this.applyDesktopSettings(snapshot);
} catch {}
}
private applyDesktopSettings(snapshot: DesktopSettingsSnapshot): void {
this.hardwareAcceleration.set(snapshot.hardwareAcceleration);
this.hardwareAccelerationRestartRequired.set(snapshot.restartRequired);
}
private getElectronApi(): DesktopSettingsElectronApi | null {
return typeof window !== 'undefined'
? (window as any).electronAPI as DesktopSettingsElectronApi
: null;
}
}