diff --git a/docs/capabilities.md b/docs/capabilities.md index f8bebf371cd..a23dbef4c40 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -162,3 +162,4 @@ * `download-call-participants` - Whether the endpoints for moderators to download the call participants is available * `config => call => start-without-media` (local) - Boolean, whether media should be disabled when starting or joining a conversation * `config => call => max-duration` - Integer, maximum call duration in seconds. Please note that this should only be used with system cron and with a reasonable high value, due to the expended duration until the background job ran. +* `config => call => blur-virtual-background` (local) - Boolean, whether blur background is set by default when joining a conversation diff --git a/docs/settings.md b/docs/settings.md index 6093c41af7e..ad2543d8412 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -24,13 +24,14 @@ **Note:** Settings from `calls_start_without_media` onwards can not be set via above API. Instead, the server API `POST /ocs/v2.php/apps/provisioning_api/api/v1/config/users/{appId}/{configKey}` needs to be used. -| Key | Capability | Default | Valid values | -|-----------------------------|-----------------------------------------|----------------------------------------------------|----------------------------------------------------------------------------------------------------------| -| `attachment_folder` | `config => attachments => folder` | Value of app config `default_attachment_folder` | Path owned by the user to store uploads and received shares. It is created if it does not exist. | -| `read_status_privacy` | `config => chat => read-privacy` | `0` | One of the read-status constants from the [constants list](constants.md#participant-read-status-privacy) | -| `typing_privacy` | `config => chat => typing-privacy` | `0` | One of the typing privacy constants from the [constants list](constants.md#participant-typing-privacy) | -| `play_sounds` | | `'yes'` | `'yes'` and `'no'` | -| `calls_start_without_media` | `config => call => start-without-media` | `''` falling back to app config with the same name | `'yes'` and `'no'` | +| Key | Capability | Default | Valid values | +|-----------------------------|---------------------------------------------|----------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| `attachment_folder` | `config => attachments => folder` | Value of app config `default_attachment_folder` | Path owned by the user to store uploads and received shares. It is created if it does not exist. | +| `read_status_privacy` | `config => chat => read-privacy` | `0` | One of the read-status constants from the [constants list](constants.md#participant-read-status-privacy) | +| `typing_privacy` | `config => chat => typing-privacy` | `0` | One of the typing privacy constants from the [constants list](constants.md#participant-typing-privacy) | +| `play_sounds` | | `'yes'` | `'yes'` and `'no'` | +| `calls_start_without_media` | `config => call => start-without-media` | `''` falling back to app config with the same name | `'yes'` and `'no'` | +| `blur_virtual_background` | `config => call => blur-virtual-background` | `'no'` | `'yes'` and `'no'` | ## Set SIP settings diff --git a/lib/Capabilities.php b/lib/Capabilities.php index cb30c4bb443..4d777717ef0 100644 --- a/lib/Capabilities.php +++ b/lib/Capabilities.php @@ -130,6 +130,7 @@ class Capabilities implements IPublicCapability { 'predefined-backgrounds', 'can-upload-background', 'start-without-media', + 'blur-virtual-background', ], 'chat' => [ 'read-privacy', @@ -201,6 +202,7 @@ public function getCapabilities(): array { 'can-enable-sip' => false, 'start-without-media' => $this->talkConfig->getCallsStartWithoutMedia($user?->getUID()), 'max-duration' => $this->appConfig->getAppValueInt('max_call_duration'), + 'blur-virtual-background' => $this->talkConfig->getBlurVirtualBackground($user?->getUID()), ], 'chat' => [ 'max-length' => ChatManager::MAX_CHAT_LENGTH, @@ -250,6 +252,7 @@ public function getCapabilities(): array { $capabilities['config']['attachments']['folder'] = $this->talkConfig->getAttachmentFolder($user->getUID()); $capabilities['config']['chat']['read-privacy'] = $this->talkConfig->getUserReadPrivacy($user->getUID()); $capabilities['config']['chat']['typing-privacy'] = $this->talkConfig->getUserTypingPrivacy($user->getUID()); + $capabilities['config']['call']['blur-virtual-background'] = $this->talkConfig->getBlurVirtualBackground($user->getUID()); } $pubKey = $this->talkConfig->getSignalingTokenPublicKey(); diff --git a/lib/Config.php b/lib/Config.php index 2019731d976..87f26c52cd5 100644 --- a/lib/Config.php +++ b/lib/Config.php @@ -680,4 +680,18 @@ public function getCallsStartWithoutMedia(?string $userId): bool { return $this->appConfig->getAppValueBool('calls_start_without_media'); } + + /** + * User setting for blur background + * + * @param ?string $userId + * @return bool + */ + public function getBlurVirtualBackground(?string $userId): bool { + if ($userId !== null) { + $userSetting = $this->config->getUserValue($userId, 'spreed', UserPreference::BLUR_VIRTUAL_BACKGROUND); + return $userSetting === 'yes'; + } + return false; + } } diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index 17821e88409..4efb1c853ff 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -2458,6 +2458,9 @@ public function getCapabilities(): DataResponse { if (isset($data['config']['call']['start-without-media'])) { $data['config']['call']['start-without-media'] = $this->talkConfig->getCallsStartWithoutMedia($this->userId); } + if (isset($data['config']['call']['blur-virtual-background'])) { + $data['config']['call']['blur-virtual-background'] = $this->talkConfig->getBlurVirtualBackground($this->userId); + } if ($response->getHeaders()['X-Nextcloud-Talk-Hash']) { $headers['X-Nextcloud-Talk-Proxy-Hash'] = $response->getHeaders()['X-Nextcloud-Talk-Hash']; diff --git a/lib/Federation/Proxy/TalkV1/ProxyRequest.php b/lib/Federation/Proxy/TalkV1/ProxyRequest.php index 04305ca24d1..ea5d606bc2f 100644 --- a/lib/Federation/Proxy/TalkV1/ProxyRequest.php +++ b/lib/Federation/Proxy/TalkV1/ProxyRequest.php @@ -42,6 +42,9 @@ public function overwrittenRemoteTalkHash(string $hash): string { 'read-privacy', 'typing-privacy', ], + 'call' => [ + 'blur-virtual-background', + ] ], ] ])); diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index 9722340e49d..3fef42ab4db 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -348,6 +348,7 @@ * can-enable-sip: bool, * start-without-media: bool, * max-duration: int, + * blur-virtual-background: bool, * }, * chat: array{ * max-length: int, diff --git a/lib/Settings/BeforePreferenceSetEventListener.php b/lib/Settings/BeforePreferenceSetEventListener.php index 00f41f19f6a..1fe87fdb1d5 100644 --- a/lib/Settings/BeforePreferenceSetEventListener.php +++ b/lib/Settings/BeforePreferenceSetEventListener.php @@ -60,7 +60,8 @@ public function validatePreference(string $userId, string $key, string|int|null // "boolean" yes/no if ($key === UserPreference::CALLS_START_WITHOUT_MEDIA - || $key === UserPreference::PLAY_SOUNDS) { + || $key === UserPreference::PLAY_SOUNDS + || $key === UserPreference::BLUR_VIRTUAL_BACKGROUND) { return $value === 'yes' || $value === 'no'; } diff --git a/lib/Settings/UserPreference.php b/lib/Settings/UserPreference.php index f360172f20e..6c729ae0f11 100644 --- a/lib/Settings/UserPreference.php +++ b/lib/Settings/UserPreference.php @@ -9,6 +9,7 @@ namespace OCA\Talk\Settings; class UserPreference { + public const BLUR_VIRTUAL_BACKGROUND = 'blur_virtual_background'; public const CALLS_START_WITHOUT_MEDIA = 'calls_start_without_media'; public const PLAY_SOUNDS = 'play_sounds'; public const TYPING_PRIVACY = 'typing_privacy'; diff --git a/openapi-administration.json b/openapi-administration.json index fa4e80bed78..84f73eb4e61 100644 --- a/openapi-administration.json +++ b/openapi-administration.json @@ -149,7 +149,8 @@ "sip-dialout-enabled", "can-enable-sip", "start-without-media", - "max-duration" + "max-duration", + "blur-virtual-background" ], "properties": { "enabled": { @@ -195,6 +196,9 @@ "max-duration": { "type": "integer", "format": "int64" + }, + "blur-virtual-background": { + "type": "boolean" } } }, diff --git a/openapi-backend-recording.json b/openapi-backend-recording.json index f6b1d452627..11f4b758125 100644 --- a/openapi-backend-recording.json +++ b/openapi-backend-recording.json @@ -82,7 +82,8 @@ "sip-dialout-enabled", "can-enable-sip", "start-without-media", - "max-duration" + "max-duration", + "blur-virtual-background" ], "properties": { "enabled": { @@ -128,6 +129,9 @@ "max-duration": { "type": "integer", "format": "int64" + }, + "blur-virtual-background": { + "type": "boolean" } } }, diff --git a/openapi-backend-signaling.json b/openapi-backend-signaling.json index ad42624f290..2d74e247e13 100644 --- a/openapi-backend-signaling.json +++ b/openapi-backend-signaling.json @@ -82,7 +82,8 @@ "sip-dialout-enabled", "can-enable-sip", "start-without-media", - "max-duration" + "max-duration", + "blur-virtual-background" ], "properties": { "enabled": { @@ -128,6 +129,9 @@ "max-duration": { "type": "integer", "format": "int64" + }, + "blur-virtual-background": { + "type": "boolean" } } }, diff --git a/openapi-backend-sipbridge.json b/openapi-backend-sipbridge.json index 5df0b9eeb46..aecba7b3a81 100644 --- a/openapi-backend-sipbridge.json +++ b/openapi-backend-sipbridge.json @@ -125,7 +125,8 @@ "sip-dialout-enabled", "can-enable-sip", "start-without-media", - "max-duration" + "max-duration", + "blur-virtual-background" ], "properties": { "enabled": { @@ -171,6 +172,9 @@ "max-duration": { "type": "integer", "format": "int64" + }, + "blur-virtual-background": { + "type": "boolean" } } }, diff --git a/openapi-bots.json b/openapi-bots.json index 8f24560fe4b..45a49772408 100644 --- a/openapi-bots.json +++ b/openapi-bots.json @@ -82,7 +82,8 @@ "sip-dialout-enabled", "can-enable-sip", "start-without-media", - "max-duration" + "max-duration", + "blur-virtual-background" ], "properties": { "enabled": { @@ -128,6 +129,9 @@ "max-duration": { "type": "integer", "format": "int64" + }, + "blur-virtual-background": { + "type": "boolean" } } }, diff --git a/openapi-federation.json b/openapi-federation.json index 1b2274d22c7..99cc3664bef 100644 --- a/openapi-federation.json +++ b/openapi-federation.json @@ -125,7 +125,8 @@ "sip-dialout-enabled", "can-enable-sip", "start-without-media", - "max-duration" + "max-duration", + "blur-virtual-background" ], "properties": { "enabled": { @@ -171,6 +172,9 @@ "max-duration": { "type": "integer", "format": "int64" + }, + "blur-virtual-background": { + "type": "boolean" } } }, diff --git a/openapi-full.json b/openapi-full.json index ee2f367ec0e..202b530c72d 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -301,7 +301,8 @@ "sip-dialout-enabled", "can-enable-sip", "start-without-media", - "max-duration" + "max-duration", + "blur-virtual-background" ], "properties": { "enabled": { @@ -347,6 +348,9 @@ "max-duration": { "type": "integer", "format": "int64" + }, + "blur-virtual-background": { + "type": "boolean" } } }, diff --git a/openapi.json b/openapi.json index cdfc0dadfab..bccf2ab6a57 100644 --- a/openapi.json +++ b/openapi.json @@ -242,7 +242,8 @@ "sip-dialout-enabled", "can-enable-sip", "start-without-media", - "max-duration" + "max-duration", + "blur-virtual-background" ], "properties": { "enabled": { @@ -288,6 +289,9 @@ "max-duration": { "type": "integer", "format": "int64" + }, + "blur-virtual-background": { + "type": "boolean" } } }, diff --git a/src/__mocks__/capabilities.ts b/src/__mocks__/capabilities.ts index 4064044190f..4a21c8c7f8d 100644 --- a/src/__mocks__/capabilities.ts +++ b/src/__mocks__/capabilities.ts @@ -119,6 +119,7 @@ export const mockedCapabilities: Capabilities = { 'can-enable-sip': true, 'start-without-media': false, 'max-duration': 0, + 'blur-virtual-background': false, }, chat: { 'max-length': 32000, @@ -151,6 +152,7 @@ export const mockedCapabilities: Capabilities = { 'predefined-backgrounds', 'can-upload-background', 'start-without-media', + 'blur-virtual-background', ], chat: [ 'read-privacy', diff --git a/src/components/MediaSettings/MediaSettings.vue b/src/components/MediaSettings/MediaSettings.vue index eb2fd8af0f9..f0f981279e0 100644 --- a/src/components/MediaSettings/MediaSettings.vue +++ b/src/components/MediaSettings/MediaSettings.vue @@ -93,6 +93,7 @@ @@ -321,6 +322,7 @@ export default { isRecordingFromStart: false, isPublicShareAuthSidebar: false, isMirrored: false, + skipBlurVirtualBackground: false, } }, @@ -352,6 +354,10 @@ export default { return this.settingsStore.getShowMediaSettings(this.token) }, + blurVirtualBackgroundEnabled() { + return this.settingsStore.blurVirtualBackgroundEnabled + }, + showVideo() { return this.videoPreviewAvailable && this.videoOn }, @@ -423,6 +429,10 @@ export default { return this.updatedBackground || this.audioDeviceStateChanged || this.videoDeviceStateChanged }, + + connectionFailed() { + return this.$store.getters.connectionFailed(this.token) + }, }, watch: { @@ -439,6 +449,9 @@ export default { } else if (BrowserStorage.getItem('virtualBackgroundType_' + this.token) === VIRTUAL_BACKGROUND.BACKGROUND_TYPE.IMAGE) { this.setVirtualBackgroundImage(BrowserStorage.getItem('virtualBackgroundUrl_' + this.token)) } + } else if (this.blurVirtualBackgroundEnabled && !this.skipBlurVirtualBackground) { + // Fall back to global blur background setting + this.blurVirtualBackground() } else { this.clearVirtualBackground() } @@ -464,6 +477,25 @@ export default { isRecordingFromStart(value) { this.setRecordingConsentGiven(value) }, + + isInCall(value) { + if (value) { + const virtualBackgroundEnabled = BrowserStorage.getItem('virtualBackgroundEnabled_' + this.token) === 'true' + // Apply global blur background setting + if (this.blurVirtualBackgroundEnabled && !this.skipBlurVirtualBackground && !virtualBackgroundEnabled) { + this.blurBackground(true) + } + } else { + // Reset the flag for the next call + this.skipBlurVirtualBackground = false + } + }, + + connectionFailed(value) { + if (value) { + this.skipBlurVirtualBackground = false + } + }, }, beforeMount() { @@ -554,10 +586,15 @@ export default { }, handleUpdateBackground(background) { + // Default global blur background setting was changed by user + if (this.blurVirtualBackgroundEnabled && background !== 'blur') { + this.skipBlurVirtualBackground = true + } + // Apply the new background if (background === 'none') { this.clearBackground() } else if (background === 'blur') { - this.blurBackground() + this.blurBackground(this.blurVirtualBackgroundEnabled) } else { this.setBackgroundImage(background) } @@ -605,12 +642,15 @@ export default { /** * Blurs the background of the participants in current or future call + * + * @param {boolean} globalBlurVirtualBackground - Whether the global blur background setting is enabled (in Talk settings) */ - blurBackground() { + blurBackground(globalBlurVirtualBackground = false) { if (this.isInCall) { localMediaModel.enableVirtualBackground() - localMediaModel.setVirtualBackgroundBlur(VIRTUAL_BACKGROUND.BLUR_STRENGTH.DEFAULT) - } else { + localMediaModel.setVirtualBackgroundBlur(VIRTUAL_BACKGROUND.BLUR_STRENGTH.DEFAULT, globalBlurVirtualBackground) + } else if (!globalBlurVirtualBackground) { + this.skipBlurVirtualBackground = true BrowserStorage.setItem('virtualBackgroundEnabled_' + this.token, 'true') BrowserStorage.setItem('virtualBackgroundType_' + this.token, VIRTUAL_BACKGROUND.BACKGROUND_TYPE.BLUR) BrowserStorage.setItem('virtualBackgroundBlurStrength_' + this.token, VIRTUAL_BACKGROUND.BLUR_STRENGTH.DEFAULT) diff --git a/src/components/MediaSettings/VideoBackgroundEditor.vue b/src/components/MediaSettings/VideoBackgroundEditor.vue index ec3db3e9f35..b6123e2e338 100644 --- a/src/components/MediaSettings/VideoBackgroundEditor.vue +++ b/src/components/MediaSettings/VideoBackgroundEditor.vue @@ -87,6 +87,7 @@ import { VIRTUAL_BACKGROUND } from '../../constants.js' import BrowserStorage from '../../services/BrowserStorage.js' import { getTalkConfig } from '../../services/CapabilitiesManager.ts' import { getDavClient } from '../../services/DavClient.js' +import { useSettingsStore } from '../../stores/settings.js' import { findUniquePath } from '../../utils/fileUpload.js' const predefinedBackgroundLabels = { @@ -117,6 +118,11 @@ export default { type: String, required: true, }, + + skipBlurVirtualBackground: { + type: Boolean, + default: false, + }, }, emits: ['update-background'], @@ -125,6 +131,7 @@ export default { return { canUploadBackgrounds: getTalkConfig('local', 'call', 'can-upload-background'), predefinedBackgrounds: getTalkConfig('local', 'call', 'predefined-backgrounds'), + settingsStore: useSettingsStore(), } }, @@ -263,6 +270,8 @@ export default { } else { this.selectedBackground = 'none' } + } else if (this.settingsStore.blurVirtualBackgroundEnabled && !this.skipBlurVirtualBackground) { + this.selectedBackground = 'blur' } else { this.selectedBackground = 'none' } diff --git a/src/components/SettingsDialog/MediaDevicesPreview.vue b/src/components/SettingsDialog/MediaDevicesPreview.vue index d672aa1b14b..f694253e3a9 100644 --- a/src/components/SettingsDialog/MediaDevicesPreview.vue +++ b/src/components/SettingsDialog/MediaDevicesPreview.vue @@ -63,6 +63,12 @@ disablePictureInPicture="true" tabindex="-1" /> + + {{ t('spreed', 'Enable blur background by default for all conversation') }} + @@ -75,10 +81,17 @@ import VideoOff from 'vue-material-design-icons/VideoOff.vue' import { t } from '@nextcloud/l10n' +import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' + import MediaDevicesSelector from '../MediaSettings/MediaDevicesSelector.vue' import VolumeIndicator from '../UIShared/VolumeIndicator.vue' import { useDevices } from '../../composables/useDevices.js' +import { VIRTUAL_BACKGROUND } from '../../constants.js' +import { getTalkConfig } from '../../services/CapabilitiesManager.ts' +import { useSettingsStore } from '../../stores/settings.js' + +const supportDefaultBlurVirtualBackground = getTalkConfig('local', 'call', 'blur-virtual-background') !== undefined export default { @@ -88,6 +101,7 @@ export default { AlertCircle, MediaDevicesSelector, MicrophoneOff, + NcCheckboxRadioSwitch, VideoOff, VolumeIndicator, }, @@ -108,6 +122,7 @@ export default { audioStreamError, videoStream, videoStreamError, + virtualBackground, } = useDevices(video, true) return { @@ -125,6 +140,9 @@ export default { audioStreamError, videoStream, videoStreamError, + settingsStore: useSettingsStore(), + virtualBackground, + supportDefaultBlurVirtualBackground, } }, @@ -180,6 +198,23 @@ export default { return t('spreed', 'Error while accessing camera') }, + + blurVirtualBackgroundEnabled() { + return this.settingsStore.blurVirtualBackgroundEnabled + }, + }, + + mounted() { + if (this.blurVirtualBackgroundEnabled) { + // wait for the virtual background to be ready + this.$nextTick(() => { + this.virtualBackground.setEnabled(true) + this.virtualBackground.setVirtualBackground({ + backgroundType: VIRTUAL_BACKGROUND.BACKGROUND_TYPE.BLUR, + blurValue: VIRTUAL_BACKGROUND.BLUR_STRENGTH.DEFAULT, + }) + }) + } }, methods: { @@ -194,6 +229,23 @@ export default { this.videoInputId = videoInputId this.updatePreferences('videoinput') }, + + async setBlurVirtualBackgroundEnabled(value) { + try { + await this.settingsStore.setBlurVirtualBackgroundEnabled(value) + if (value) { + this.virtualBackground.setEnabled(true) + this.virtualBackground.setVirtualBackground({ + backgroundType: VIRTUAL_BACKGROUND.BACKGROUND_TYPE.BLUR, + blurValue: VIRTUAL_BACKGROUND.BLUR_STRENGTH.DEFAULT, + }) + } else { + this.virtualBackground.setEnabled(false) + } + } catch (error) { + console.error('Failed to set blur background enabled:', error) + } + }, }, } diff --git a/src/services/settingsService.js b/src/services/settingsService.js index 684e8a8babe..b0f92149f16 100644 --- a/src/services/settingsService.js +++ b/src/services/settingsService.js @@ -78,6 +78,10 @@ const setStartWithoutMedia = async function(value) { await setUserConfig('spreed', 'calls_start_without_media', value ? 'yes' : 'no') } +const setBlurVirtualBackground = async function(value) { + await setUserConfig('spreed', 'blur_virtual_background', value ? 'yes' : 'no') +} + /** * Set user config using provisioning API * @@ -93,6 +97,7 @@ const setUserConfig = async function(appId, configKey, configValue) { export { setAttachmentFolder, + setBlurVirtualBackground, setReadStatusPrivacy, setTypingStatusPrivacy, setSIPSettings, diff --git a/src/stores/__tests__/settings.spec.js b/src/stores/__tests__/settings.spec.js index baa9cbb8ba2..f0a2eee7c68 100644 --- a/src/stores/__tests__/settings.spec.js +++ b/src/stores/__tests__/settings.spec.js @@ -71,8 +71,9 @@ describe('settingsStore', () => { // Assert expect(results).toEqual([true, false]) // It's always called at least once : BrowserStorage.getItem('cachedConversations') - // +1 - expect(BrowserStorage.getItem).toHaveBeenCalledTimes(1) + // Whenever capabilitiesManager.ts is imported + // +2 + expect(BrowserStorage.getItem).toHaveBeenCalledTimes(2) }) it('shows correct values received from BrowserStorage', () => { @@ -89,11 +90,11 @@ describe('settingsStore', () => { // Assert expect(results).toEqual([true, true, false]) - // It's always called at least once : BrowserStorage.getItem('cachedConversations') - expect(BrowserStorage.getItem).toHaveBeenCalledTimes(4) // 1 + 3 - expect(BrowserStorage.getItem).toHaveBeenNthCalledWith(2, 'showMediaSettings_token-1') - expect(BrowserStorage.getItem).toHaveBeenNthCalledWith(3, 'showMediaSettings_token-2') - expect(BrowserStorage.getItem).toHaveBeenNthCalledWith(4, 'showMediaSettings_token-3') + // It's always called at least once : BrowserStorage.getItem('cachedConversations') (+2) + expect(BrowserStorage.getItem).toHaveBeenCalledTimes(5) // 2 + 3 + expect(BrowserStorage.getItem).toHaveBeenNthCalledWith(3, 'showMediaSettings_token-1') + expect(BrowserStorage.getItem).toHaveBeenNthCalledWith(4, 'showMediaSettings_token-2') + expect(BrowserStorage.getItem).toHaveBeenNthCalledWith(5, 'showMediaSettings_token-3') }) it('updates values correctly', async () => { @@ -109,8 +110,8 @@ describe('settingsStore', () => { // Assert expect(results).toEqual([false, true]) - // It's always called at least once : BrowserStorage.getItem('cachedConversations') - expect(BrowserStorage.getItem).toHaveBeenCalledTimes(1) + // It's always called at least once : BrowserStorage.getItem('cachedConversations') (+2) + expect(BrowserStorage.getItem).toHaveBeenCalledTimes(2) expect(BrowserStorage.setItem).toHaveBeenCalledTimes(2) expect(BrowserStorage.setItem).toHaveBeenNthCalledWith(1, 'showMediaSettings_token-1', 'false') expect(BrowserStorage.setItem).toHaveBeenNthCalledWith(2, 'showMediaSettings_token-2', 'true') diff --git a/src/stores/settings.js b/src/stores/settings.js index 5eb93f533ef..9c3e37aaf63 100644 --- a/src/stores/settings.js +++ b/src/stores/settings.js @@ -14,7 +14,8 @@ import { getTalkConfig } from '../services/CapabilitiesManager.ts' import { setReadStatusPrivacy, setTypingStatusPrivacy, - setStartWithoutMedia + setStartWithoutMedia, + setBlurVirtualBackground, } from '../services/settingsService.js' /** @@ -40,6 +41,7 @@ export const useSettingsStore = defineStore('settings', { typingStatusPrivacy: loadState('spreed', 'typing_privacy', PRIVACY.PRIVATE), showMediaSettings: {}, startWithoutMedia: getTalkConfig('local', 'call', 'start-without-media'), + blurVirtualBackgroundEnabled: getTalkConfig('local', 'call', 'blur-virtual-background'), }), getters: { @@ -103,6 +105,11 @@ export const useSettingsStore = defineStore('settings', { Vue.set(this.showMediaSettings, token, value) }, + async setBlurVirtualBackgroundEnabled(value) { + await setBlurVirtualBackground(value) + this.blurVirtualBackgroundEnabled = value + }, + async setStartWithoutMedia(value) { await setStartWithoutMedia(value) this.startWithoutMedia = value diff --git a/src/types/openapi/openapi-administration.ts b/src/types/openapi/openapi-administration.ts index e960be1a078..6a5c7e4be1f 100644 --- a/src/types/openapi/openapi-administration.ts +++ b/src/types/openapi/openapi-administration.ts @@ -231,6 +231,7 @@ export type components = { "start-without-media": boolean; /** Format: int64 */ "max-duration": number; + "blur-virtual-background": boolean; }; chat: { /** Format: int64 */ diff --git a/src/types/openapi/openapi-backend-recording.ts b/src/types/openapi/openapi-backend-recording.ts index 63f3a3b8602..7d5ede90d3f 100644 --- a/src/types/openapi/openapi-backend-recording.ts +++ b/src/types/openapi/openapi-backend-recording.ts @@ -65,6 +65,7 @@ export type components = { "start-without-media": boolean; /** Format: int64 */ "max-duration": number; + "blur-virtual-background": boolean; }; chat: { /** Format: int64 */ diff --git a/src/types/openapi/openapi-backend-signaling.ts b/src/types/openapi/openapi-backend-signaling.ts index 54bf7d5d98b..e632042a376 100644 --- a/src/types/openapi/openapi-backend-signaling.ts +++ b/src/types/openapi/openapi-backend-signaling.ts @@ -51,6 +51,7 @@ export type components = { "start-without-media": boolean; /** Format: int64 */ "max-duration": number; + "blur-virtual-background": boolean; }; chat: { /** Format: int64 */ diff --git a/src/types/openapi/openapi-backend-sipbridge.ts b/src/types/openapi/openapi-backend-sipbridge.ts index 84e84856e36..040cd5a9ede 100644 --- a/src/types/openapi/openapi-backend-sipbridge.ts +++ b/src/types/openapi/openapi-backend-sipbridge.ts @@ -146,6 +146,7 @@ export type components = { "start-without-media": boolean; /** Format: int64 */ "max-duration": number; + "blur-virtual-background": boolean; }; chat: { /** Format: int64 */ diff --git a/src/types/openapi/openapi-bots.ts b/src/types/openapi/openapi-bots.ts index 5e29b8e7795..5698171ec98 100644 --- a/src/types/openapi/openapi-bots.ts +++ b/src/types/openapi/openapi-bots.ts @@ -69,6 +69,7 @@ export type components = { "start-without-media": boolean; /** Format: int64 */ "max-duration": number; + "blur-virtual-background": boolean; }; chat: { /** Format: int64 */ diff --git a/src/types/openapi/openapi-federation.ts b/src/types/openapi/openapi-federation.ts index b498f99f0f7..f03075498c0 100644 --- a/src/types/openapi/openapi-federation.ts +++ b/src/types/openapi/openapi-federation.ts @@ -177,6 +177,7 @@ export type components = { "start-without-media": boolean; /** Format: int64 */ "max-duration": number; + "blur-virtual-background": boolean; }; chat: { /** Format: int64 */ diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index d097abeb12c..113cbf01f16 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -1927,6 +1927,7 @@ export type components = { "start-without-media": boolean; /** Format: int64 */ "max-duration": number; + "blur-virtual-background": boolean; }; chat: { /** Format: int64 */ diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index 303985fa07d..49c8a6fe7fd 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -1424,6 +1424,7 @@ export type components = { "start-without-media": boolean; /** Format: int64 */ "max-duration": number; + "blur-virtual-background": boolean; }; chat: { /** Format: int64 */ diff --git a/src/utils/webrtc/models/LocalMediaModel.js b/src/utils/webrtc/models/LocalMediaModel.js index c8121182164..00c1a756e99 100644 --- a/src/utils/webrtc/models/LocalMediaModel.js +++ b/src/utils/webrtc/models/LocalMediaModel.js @@ -387,7 +387,7 @@ LocalMediaModel.prototype = { this._webRtc.enableVirtualBackground() }, - setVirtualBackgroundBlur(blurStrength) { + setVirtualBackgroundBlur(blurStrength, globalBlurVirtualBackground = false) { if (!this._webRtc) { throw new Error('WebRtc not initialized yet') } @@ -396,9 +396,11 @@ LocalMediaModel.prototype = { blurStrength = VIRTUAL_BACKGROUND.BLUR_STRENGTH.DEFAULT } - BrowserStorage.setItem('virtualBackgroundType_' + this.get('token'), VIRTUAL_BACKGROUND.BACKGROUND_TYPE.BLUR) - BrowserStorage.setItem('virtualBackgroundBlurStrength_' + this.get('token'), blurStrength) - BrowserStorage.removeItem('virtualBackgroundUrl_' + this.get('token')) + if (!globalBlurVirtualBackground) { + BrowserStorage.setItem('virtualBackgroundType_' + this.get('token'), VIRTUAL_BACKGROUND.BACKGROUND_TYPE.BLUR) + BrowserStorage.setItem('virtualBackgroundBlurStrength_' + this.get('token'), blurStrength) + BrowserStorage.removeItem('virtualBackgroundUrl_' + this.get('token')) + } this._webRtc.setVirtualBackground({ backgroundType: VIRTUAL_BACKGROUND.BACKGROUND_TYPE.BLUR, diff --git a/tests/php/CapabilitiesTest.php b/tests/php/CapabilitiesTest.php index f18ab3d3c55..9569ec7f184 100644 --- a/tests/php/CapabilitiesTest.php +++ b/tests/php/CapabilitiesTest.php @@ -121,6 +121,7 @@ public function testGetCapabilitiesGuest(): void { 'can-enable-sip' => false, 'start-without-media' => false, 'max-duration' => 0, + 'blur-virtual-background' => false, 'predefined-backgrounds' => [ '1_office.jpg', '2_home.jpg', @@ -256,6 +257,7 @@ public function testGetCapabilitiesUserAllowed(bool $isNotAllowed, bool $canCrea 'can-enable-sip' => false, 'start-without-media' => false, 'max-duration' => 0, + 'blur-virtual-background' => false, 'predefined-backgrounds' => [ '1_office.jpg', '2_home.jpg',