Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/components/CallView/shared/LocalAudioControlButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,23 @@
{{ device.label }}
</NcActionButton>
</template>

<NcActionSeparator />
<NcActionButton
v-if="isAudioAllowed"
key="advanced-settings"
class="audio-selector__action"
close-after-click
@click="openAdvancedSettings">
{{ t('spreed', 'Microphone settings') }}
</NcActionButton>
<NcActionButton
key="media-settings"
class="audio-selector__action"
close-after-click
@click="emit('talk:media-settings:show')">
{{ t('spreed', 'Check devices') }}
</NcActionButton>
</NcActions>
</div>
</template>
Expand All @@ -87,6 +104,7 @@
import { emit } from '@nextcloud/event-bus'
import { t } from '@nextcloud/l10n'
import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
import { onBeforeUnmount, ref, watch } from 'vue'
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
import NcActionCaption from '@nextcloud/vue/components/NcActionCaption'
Expand All @@ -95,6 +113,7 @@ import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcPopover from '@nextcloud/vue/components/NcPopover'
import IconChevronUp from 'vue-material-design-icons/ChevronUp.vue'
import AdvancedAudioDialog from '../../MediaSettings/AdvancedAudioDialog.vue'
import VolumeIndicator from '../../UIShared/VolumeIndicator.vue'
import { useDevices } from '../../../composables/useDevices.js'
import { PARTICIPANT } from '../../../constants.ts'
Expand Down Expand Up @@ -301,6 +320,8 @@ export default {

methods: {
t,
emit,

toggleAudio() {
if (!this.isAudioAllowed || !this.isAudioAvailable) {
emit('talk:media-settings:show')
Expand All @@ -327,6 +348,12 @@ export default {
this.audioOutputId = audioOutputId
this.updatePreferences('audiooutput')
},

async openAdvancedSettings() {
if (await spawnDialog(AdvancedAudioDialog)) {
this.resumeAudioAfterChange = true
}
},
},
}
</script>
Expand Down
13 changes: 13 additions & 0 deletions src/components/CallView/shared/LocalVideoControlButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@
@click="handleVideoInputIdChange(device.deviceId)">
{{ device.label }}
</NcActionButton>

<NcActionSeparator />
<NcActionButton
key="media-settings"
class="video-selector__action"
close-after-click
@click="emit('talk:media-settings:show')">
{{ t('spreed', 'Check devices') }}
</NcActionButton>
</NcActions>
</div>
</template>
Expand All @@ -53,6 +62,7 @@ import { ref } from 'vue'
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
import NcActionCaption from '@nextcloud/vue/components/NcActionCaption'
import NcActions from '@nextcloud/vue/components/NcActions'
import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator'
import NcButton from '@nextcloud/vue/components/NcButton'
import IconChevronUp from 'vue-material-design-icons/ChevronUp.vue'
import IconVideo from 'vue-material-design-icons/Video.vue' // Filled for better indication
Expand All @@ -67,6 +77,7 @@ export default {
NcActions,
NcActionButton,
NcActionCaption,
NcActionSeparator,
NcButton,
IconChevronUp,
IconVideo,
Expand Down Expand Up @@ -221,6 +232,8 @@ export default {
methods: {
t,
emit,
toggleVideo() {
if (!this.isVideoAllowed || !this.isVideoAvailable) {
emit('talk:media-settings:show')
Expand Down
97 changes: 97 additions & 0 deletions src/components/MediaSettings/AdvancedAudioDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<script setup lang="ts">
import { t } from '@nextcloud/l10n'
import NcDialog from '@nextcloud/vue/components/NcDialog'
import NcFormBox from '@nextcloud/vue/components/NcFormBox'
import NcFormBoxSwitch from '@nextcloud/vue/components/NcFormBoxSwitch'
import { useDevices } from '../../composables/useDevices.js'
import { useSettingsStore } from '../../stores/settings.ts'

const props = defineProps<{
container?: string
}>()

const emit = defineEmits<{
close: [value?: unknown]
}>()

// TRANSLATORS Microphone setting to reduce background noises for better voice quality
const noiseSuppressionLabel = t('spreed', 'Enable noise suppression')
const noiseSuppressionDescription = t('spreed', 'Reduce background noises for better voice quality')

// TRANSLATORS Microphone setting to minimize echo effect from own surrounding
const echoCancellationLabel = t('spreed', 'Enable echo cancellation')
const echoCancellationDescription = t('spreed', 'Minimize echo effect from own surrounding')

// TRANSLATORS Microphone setting to dynamically adjust microphone volume for consistent level
const autoGainControlLabel = t('spreed', 'Enable auto gain control')
const autoGainControlDescription = t('spreed', 'Dynamically adjust microphone volume for consistent level')

const settingsStore = useSettingsStore()

const { audioPreviewAvailable, updateAudioStream } = useDevices()

const originalState = {
noiseSuppression: settingsStore.noiseSuppression,
echoCancellation: settingsStore.echoCancellation,
autoGainControl: settingsStore.autoGainControl,
} as const

/**
* Emit result, if any (for spawnDialog callback)
*
* @param result callback result
*/
function onClosing(result?: unknown) {
if (!result) {
// Revert changes
settingsStore.setNoiseSuppression(originalState.noiseSuppression)
settingsStore.setEchoCancellation(originalState.echoCancellation)
settingsStore.setAutoGainControl(originalState.autoGainControl)
} else if (audioPreviewAvailable.value && (
originalState.noiseSuppression !== settingsStore.noiseSuppression
|| originalState.echoCancellation !== settingsStore.echoCancellation
|| originalState.autoGainControl !== settingsStore.autoGainControl
)) {
// Apply changes to audio stream
updateAudioStream(true)
}

emit('close', result)
}
</script>

<template>
<NcDialog
:name="t('spreed', 'Microphone settings')"
:container="container"
size="normal"
:buttons="[
{ label: t('spreed', 'Dismiss'), variant: 'tertiary', callback: () => undefined },
{ label: t('spreed', 'Done'), variant: 'primary', callback: () => true },
]"
close-on-click-outside
@closing="onClosing">
<NcFormBox>
<NcFormBoxSwitch
:model-value="settingsStore.noiseSuppression"
:label="noiseSuppressionLabel"
:description="noiseSuppressionDescription"
@update:model-value="settingsStore.setNoiseSuppression" />
<NcFormBoxSwitch
:model-value="settingsStore.echoCancellation"
:label="echoCancellationLabel"
:description="echoCancellationDescription"
@update:model-value="settingsStore.setEchoCancellation" />
<NcFormBoxSwitch
:model-value="settingsStore.autoGainControl"
:label="autoGainControlLabel"
:description="autoGainControlDescription"
@update:model-value="settingsStore.setAutoGainControl" />
</NcFormBox>
</NcDialog>
</template>
32 changes: 20 additions & 12 deletions src/components/MediaSettings/MediaSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,15 @@
<MediaDevicesSpeakerTest :disabled="!!audioStreamError" />
</template>
</MediaDevicesSelector>
<NcButton
variant="tertiary"
wide
@click="openAdvancedSettings">
<template #icon>
<IconTune :size="20" />
</template>
{{ t('spreed', 'Microphone settings') }}
</NcButton>
</template>

<template #tab-panel:backgrounds>
Expand Down Expand Up @@ -229,10 +238,10 @@
</template>

<script>
import { showError, showSuccess } from '@nextcloud/dialogs'
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
import { t } from '@nextcloud/l10n'
import { useIsMobile } from '@nextcloud/vue/composables/useIsMobile'
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
import { computed, h, markRaw, ref, useId } from 'vue'
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
import NcActions from '@nextcloud/vue/components/NcActions'
Expand All @@ -244,13 +253,15 @@ import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
import NcPopover from '@nextcloud/vue/components/NcPopover'
import IconCogOutline from 'vue-material-design-icons/CogOutline.vue'
import IconReflectHorizontal from 'vue-material-design-icons/ReflectHorizontal.vue'
import IconTune from 'vue-material-design-icons/Tune.vue'
import IconVideo from 'vue-material-design-icons/Video.vue' // Filled for better indication
import IconVideoOffOutline from 'vue-material-design-icons/VideoOffOutline.vue'
import AvatarWrapper from '../AvatarWrapper/AvatarWrapper.vue'
import VideoBackground from '../CallView/shared/VideoBackground.vue'
import SetGuestUsername from '../SetGuestUsername.vue'
import CallButton from '../TopBar/CallButton.vue'
import VolumeIndicator from '../UIShared/VolumeIndicator.vue'
import AdvancedAudioDialog from './AdvancedAudioDialog.vue'
import MediaDevicesSelector from './MediaDevicesSelector.vue'
import MediaDevicesSpeakerTest from './MediaDevicesSpeakerTest.vue'
import MediaSettingsTabs from './MediaSettingsTabs.vue'
Expand Down Expand Up @@ -294,6 +305,7 @@ export default {
SetGuestUsername,
// Icons
IconReflectHorizontal,
IconTune,
IconVideo,
IconVideoOffOutline,
},
Expand Down Expand Up @@ -331,6 +343,7 @@ export default {
audioOutputId,
videoInputId,
audioOutputSupported,
updateAudioStream,
subscribeToDevices,
unsubscribeFromDevices,
audioStreamError,
Expand Down Expand Up @@ -371,6 +384,7 @@ export default {
audioOutputId,
videoInputId,
audioOutputSupported,
updateAudioStream,
subscribeToDevices,
unsubscribeFromDevices,
registerVideoElement,
Expand Down Expand Up @@ -889,16 +903,10 @@ export default {
this.updatePreferences('videoinput')
},

async toggleStartWithoutMedia(value) {
this.mediaLoading = true
try {
await this.settingsStore.updateStartWithoutMedia(value)
showSuccess(t('spreed', 'Your default media state has been saved'))
} catch (exception) {
showError(t('spreed', 'Error while setting default media state'))
} finally {
this.mediaLoading = false
}
async openAdvancedSettings() {
await spawnDialog(AdvancedAudioDialog, {
container: '.media-settings__settings',
})
},

async setBlurVirtualBackgroundEnabled(value) {
Expand Down
17 changes: 17 additions & 0 deletions src/components/SettingsDialog/SettingsDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@
:label="t('spreed', 'Skip device preview before joining a call')"
:description="t('spreed', 'Always shown if recording consent is required')"
@update:model-value="setHideMediaSettings" />
<NcFormBoxButton
:label="t('spreed', 'Microphone settings')"
@click="openAdvancedSettings">
<template #icon>
<IconTune :size="20" />
</template>
</NcFormBoxButton>
</NcFormBox>

<NcButton
Expand Down Expand Up @@ -157,6 +164,7 @@ import { getFilePickerBuilder } from '@nextcloud/dialogs'
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
import { t } from '@nextcloud/l10n'
import { generateUrl } from '@nextcloud/router'
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
import NcAppSettingsDialog from '@nextcloud/vue/components/NcAppSettingsDialog'
import NcAppSettingsSection from '@nextcloud/vue/components/NcAppSettingsSection'
import NcAppSettingsShortcutsSection from '@nextcloud/vue/components/NcAppSettingsShortcutsSection'
Expand All @@ -169,6 +177,8 @@ import NcHotkeyList from '@nextcloud/vue/components/NcHotkeyList'
import NcKbd from '@nextcloud/vue/components/NcKbd'
import IconFolderOpenOutline from 'vue-material-design-icons/FolderOpenOutline.vue'
import IconMicrophoneOutline from 'vue-material-design-icons/MicrophoneOutline.vue'
import IconTune from 'vue-material-design-icons/Tune.vue'
import AdvancedAudioDialog from '../MediaSettings/AdvancedAudioDialog.vue'
import { CHAT_STYLE, CONVERSATION, PRIVACY } from '../../constants.ts'
import { getTalkConfig } from '../../services/CapabilitiesManager.ts'
import { useCustomSettings } from '../../services/SettingsAPI.ts'
Expand All @@ -189,6 +199,7 @@ export default {
name: 'SettingsDialog',

components: {
IconTune,
IconFolderOpenOutline,
IconMicrophoneOutline,
NcAppSettingsDialog,
Expand Down Expand Up @@ -401,6 +412,12 @@ export default {
this.settingsStore.setShowMediaSettings(!newValue)
},

async openAdvancedSettings() {
await spawnDialog(AdvancedAudioDialog, {
container: '#devices',
})
},

async setBlurVirtualBackgroundEnabled(value) {
try {
await this.settingsStore.setBlurVirtualBackgroundEnabled(value)
Expand Down
7 changes: 5 additions & 2 deletions src/composables/useDevices.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,12 +314,14 @@ export const useDevices = createSharedComposable(function() {

/**
* Update audio stream
*
* @param {boolean} force whether to force the update (e.g. due to constraints change)
*/
function updateAudioStream() {
function updateAudioStream(force = false) {
if (!mediaDevicesManager.isSupported()) {
return
}
if (audioStreamInputId.value && audioStreamInputId.value === audioInputId.value) {
if (!force && audioStreamInputId.value && audioStreamInputId.value === audioInputId.value) {
return
}
if (pendingGetUserMediaAudioCount) {
Expand Down Expand Up @@ -466,6 +468,7 @@ export const useDevices = createSharedComposable(function() {
audioOutputId,
videoInputId,
audioOutputSupported,
updateAudioStream,
subscribeToDevices,
unsubscribeFromDevices,
// MediaDevicesPreview only
Expand Down
Loading