Screensharing rework
Split Linux screensharing audio tracks, Rework screensharing functionality and layout This will need some refactoring soon
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user