From 8bffb1246f847bab620a1edd183f39805f95d178 Mon Sep 17 00:00:00 2001 From: Maximilian Martin Date: Wed, 19 Aug 2026 18:36:41 +0000 Subject: [PATCH 1/2] feat: split default camera and microphone settings into separate toggles, Assisted-by: Copilot:gpt-5.6-luna Signed-off-by: Maximilian Martin --- docs/capabilities.md | 3 +- docs/settings.md | 8 ++-- lib/Capabilities.php | 6 ++- lib/Config.php | 25 +++++++++++- lib/ConfigLexicon.php | 3 +- lib/Controller/RoomController.php | 7 +++- lib/ResponseDefinitions.php | 3 +- .../BeforePreferenceSetEventListener.php | 4 +- lib/Settings/UserPreference.php | 3 ++ openapi-administration.json | 11 ++++-- openapi-backend-recording.json | 11 ++++-- openapi-backend-signaling.json | 11 ++++-- openapi-backend-sipbridge.json | 11 ++++-- openapi-bots.json | 11 ++++-- openapi-federation.json | 11 ++++-- openapi-full.json | 11 ++++-- openapi.json | 11 ++++-- src/__mocks__/capabilities.ts | 6 ++- src/components/CallView/CallView.vue | 10 +++-- .../MediaSettings/MediaSettings.vue | 23 ++++++----- .../SettingsDialog/SettingsDialog.vue | 39 +++++++++++++------ src/composables/useJoinCall.ts | 8 ++-- src/services/settingsService.ts | 14 +++++-- src/stores/__tests__/settings.spec.js | 3 +- src/stores/settings.ts | 29 ++++++++++---- src/types/openapi/openapi-administration.ts | 6 ++- .../openapi/openapi-backend-recording.ts | 6 ++- .../openapi/openapi-backend-signaling.ts | 6 ++- .../openapi/openapi-backend-sipbridge.ts | 6 ++- src/types/openapi/openapi-bots.ts | 6 ++- src/types/openapi/openapi-federation.ts | 6 ++- src/types/openapi/openapi-full.ts | 6 ++- src/types/openapi/openapi.ts | 6 ++- tests/php/CapabilitiesTest.php | 6 ++- 34 files changed, 237 insertions(+), 99 deletions(-) diff --git a/docs/capabilities.md b/docs/capabilities.md index 56da9f0c0ee..242d87f7d35 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -163,7 +163,8 @@ * `chat-summary-api` (local) - Whether the endpoint to get summarized chat messages in a conversation is available * `email-csv-import` - Whether the endpoint to import a CSV email list as participants exists * `config => chat => summary-threshold` (local) - Number of unread messages that should exist to show a "Generate summary" option -* `config => call => start-without-media` (local) - Boolean, whether media should be disabled when starting or joining a conversation +* `config => call => start-without-audio` (local) - Boolean, whether audio should be disabled when starting or joining a conversation +* `config => call => start-without-video` (local) - Boolean, whether video 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 2760be3abab..6e07c6fa3d4 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -21,7 +21,7 @@ ## User settings -**Note:** Settings from `calls_start_without_media` onwards can not be set via above API. +**Note:** Settings from `calls_start_without_audio` 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 | @@ -30,7 +30,8 @@ Instead, the server API `POST /ocs/v2.php/apps/provisioning_api/api/v1/config/us | `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'` | +| `calls_start_without_audio` | `config => call => start-without-audio` | `''` falling back to app config with the same name | `'yes'` and `'no'` | +| `calls_start_without_video` | `config => call => start-without-video` | `''` falling back to app config with the same name | `'yes'` and `'no'` | | `blur_virtual_background` | `config => call => blur-virtual-background` | `'no'` | `'yes'` and `'no'` | | `conversations_list_style` | `config => conversations => list-style` | `''` falling back to app config with the same name | One of the constants from the [constants list](constants.md#conversation-list-style) | | `chat_style` | `config => chat => chat-style` | `''` falling back to app config with the same name | One of the constants from the [constants list](constants.md#chat-style) | @@ -117,7 +118,8 @@ Legend: | `hide_signaling_warning` | string
`yes` or `no` | `no` | No | 🖌️ | Flag that allows to suppress the warning that an HPB should be configured | | `conversations_list_style` | string
`two-lines` or `compact` | `two-lines` | No | | Default conversation list style when not overwritten by the user | | `chat_style` | string
`split` or `unified` | `split` | No | | Default chat style when not overwritten by the user | -| `calls_start_without_media` | bool | `false` | No | | Whether participants start with enabled or disabled audio and video by default | +| `calls_start_without_audio` | bool | `false` | No | | Whether participants start with audio disabled by default | +| `calls_start_without_video` | bool | `false` | No | | Whether participants start with video disabled by default | | `breakout_rooms` | bool | `true` | Yes | | Whether or not breakout rooms are allowed (Will only prevent creating new breakout rooms. Existing conversations are not modified.) | | `call_recording` | string
`yes` or `no` | `yes` | Yes | | Enable call recording | | `call_recording_summary` | string
`yes` or `no` | `yes` | No | 🖌️ | Whether call recordings should automatically be summarized when a transcription and summary provider is enabled. | diff --git a/lib/Capabilities.php b/lib/Capabilities.php index 2a713b41549..27c85b7be35 100644 --- a/lib/Capabilities.php +++ b/lib/Capabilities.php @@ -189,7 +189,8 @@ class Capabilities implements IPublicCapability { 'predefined-backgrounds', 'predefined-backgrounds-v2', 'can-upload-background', - 'start-without-media', + 'start-without-audio', + 'start-without-video', 'blur-virtual-background', 'live-transcription-target-language-id', 'play-sounds', @@ -290,7 +291,8 @@ public function getCapabilities(): array { 'sip-dialout-enabled' => $this->talkConfig->isSIPDialOutEnabled(), 'default-phone-region' => $this->serverConfig->getSystemValueString('default_phone_region'), 'can-enable-sip' => false, - 'start-without-media' => $this->talkConfig->getCallsStartWithoutMedia($user?->getUID()), + 'start-without-audio' => $this->talkConfig->getCallsStartWithoutAudio($user?->getUID()), + 'start-without-video' => $this->talkConfig->getCallsStartWithoutVideo($user?->getUID()), 'max-duration' => $this->appConfig->getAppValueInt('max_call_duration'), 'blur-virtual-background' => $this->talkConfig->getBlurVirtualBackground($user?->getUID()), 'end-to-end-encryption' => $this->talkConfig->isCallEndToEndEncryptionEnabled(), diff --git a/lib/Config.php b/lib/Config.php index e02cd88fdc0..62a97fe39d9 100644 --- a/lib/Config.php +++ b/lib/Config.php @@ -60,6 +60,9 @@ class Config { public const string EXTERNAL_CALL_SERVICE_AUTH_USER = 'external_call_service_auth_user'; public const string EXTERNAL_CALL_SERVICE_AUTH_PASSWORD = 'external_call_service_auth_password'; public const string EXTERNAL_CALL_SERVICE_IFRAME_FIELD = 'external_call_service_iframe_field'; + public const string CALLS_START_WITHOUT_AUDIO = 'calls_start_without_audio'; + public const string CALLS_START_WITHOUT_VIDEO = 'calls_start_without_video'; + /** @deprecated Kept as fallback for existing installations. */ public const string CALLS_START_WITHOUT_MEDIA = 'calls_start_without_media'; public const string INACTIVITY_LOCK_AFTER_DAYS = 'inactivity_lock_after_days'; public const string INACTIVITY_ENABLE_LOBBY = 'inactivity_enable_lobby'; @@ -872,12 +875,30 @@ public function getGridVideosLimitEnforced(): bool { * @param ?string $userId * @return bool */ - public function getCallsStartWithoutMedia(?string $userId): bool { + public function getCallsStartWithoutAudio(?string $userId): bool { + return $this->getCallsStartWithoutMediaType($userId, self::CALLS_START_WITHOUT_AUDIO); + } + + public function getCallsStartWithoutVideo(?string $userId): bool { + return $this->getCallsStartWithoutMediaType($userId, self::CALLS_START_WITHOUT_VIDEO); + } + + private function getCallsStartWithoutMediaType(?string $userId, string $key): bool { if ($userId !== null) { - $userSetting = $this->config->getUserValue($userId, 'spreed', UserPreference::CALLS_START_WITHOUT_MEDIA); + $userSetting = $this->config->getUserValue($userId, 'spreed', $key); if ($userSetting === 'yes' || $userSetting === 'no') { return $userSetting === 'yes'; } + + // Legacy setting for backward compatibility + $legacyUserSetting = $this->config->getUserValue($userId, 'spreed', UserPreference::CALLS_START_WITHOUT_MEDIA); + if ($legacyUserSetting === 'yes' || $legacyUserSetting === 'no') { + return $legacyUserSetting === 'yes'; + } + } + + if ($this->appConfig->getAppValue($key) !== '') { + return $this->appConfig->getAppValueBool($key); } return $this->appConfig->getAppValueBool(self::CALLS_START_WITHOUT_MEDIA); diff --git a/lib/ConfigLexicon.php b/lib/ConfigLexicon.php index e452e683930..0db0c131e1e 100644 --- a/lib/ConfigLexicon.php +++ b/lib/ConfigLexicon.php @@ -49,7 +49,8 @@ public function getAppConfigs(): array { new Entry(Config::EXTERNAL_CALL_SERVICE_AUTH_PASSWORD, ValueType::STRING, '', definition: 'HTTP Basic Auth password used when Talk calls the external service'), new Entry(Config::EXTERNAL_CALL_SERVICE_FRAME_ORIGINS, ValueType::ARRAY, [], definition: 'JSON array of scheme+host(+port) origins that may be loaded in the iframe.' . PHP_EOL . 'Added to `Content-Security-Policy: frame-src` and the `Permissions-Policy` for camera/microphone'), new Entry(Config::EXTERNAL_CALL_SERVICE_IFRAME_FIELD, ValueType::STRING, '', definition: 'JSON field name in the external service response that contains the iframe URL'), - new Entry(Config::CALLS_START_WITHOUT_MEDIA, ValueType::BOOL, false, definition: 'Whether participants start with enabled or disabled audio and video by default'), + new Entry(Config::CALLS_START_WITHOUT_AUDIO, ValueType::BOOL, false, definition: 'Whether participants start with audio disabled by default'), + new Entry(Config::CALLS_START_WITHOUT_VIDEO, ValueType::BOOL, false, definition: 'Whether participants start with video disabled by default'), new Entry(Config::INACTIVITY_LOCK_AFTER_DAYS, ValueType::INT, 0, definition: 'A duration (in days) after which rooms are locked. Calculated from the last activity in the room,'), new Entry(Config::INACTIVITY_ENABLE_LOBBY, ValueType::BOOL, false, definition: 'Additionally enable the lobby for inactive rooms so they can only be read by moderators.'), new Entry(Config::EXPERIMENTS_USERS, ValueType::INT, 0, definition: 'Bit flag of experiments that should be enabled for logged-in users on this server' . PHP_EOL . 'See https://github.com/nextcloud/spreed/blob/main/docs/settings.md#experiments'), diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index 64db32b159a..6611726819f 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -3373,8 +3373,11 @@ public function getCapabilities(): DataResponse { if (isset($data['config']['chat']['typing-privacy'])) { $data['config']['chat']['typing-privacy'] = $this->talkConfig->getUserTypingPrivacy($this->userId); } - if (isset($data['config']['call']['start-without-media'])) { - $data['config']['call']['start-without-media'] = $this->talkConfig->getCallsStartWithoutMedia($this->userId); + if (isset($data['config']['call']['start-without-audio'])) { + $data['config']['call']['start-without-audio'] = $this->talkConfig->getCallsStartWithoutAudio($this->userId); + } + if (isset($data['config']['call']['start-without-video'])) { + $data['config']['call']['start-without-video'] = $this->talkConfig->getCallsStartWithoutVideo($this->userId); } if (isset($data['config']['call']['blur-virtual-background'])) { $data['config']['call']['blur-virtual-background'] = $this->talkConfig->getBlurVirtualBackground($this->userId); diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index 06ef67e8b49..9a7e79145d2 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -792,7 +792,8 @@ * // Whether the user can enable SIP for conversations * can-enable-sip: bool, * // Whether calls start without media by default - * start-without-media: bool, + * start-without-audio: bool, + * start-without-video: bool, * // Maximum duration of a call in seconds, `0` means unlimited * max-duration: int, * // Whether the blur virtual background is available diff --git a/lib/Settings/BeforePreferenceSetEventListener.php b/lib/Settings/BeforePreferenceSetEventListener.php index 8c3d6aafb8d..183102fc3e0 100644 --- a/lib/Settings/BeforePreferenceSetEventListener.php +++ b/lib/Settings/BeforePreferenceSetEventListener.php @@ -60,7 +60,9 @@ public function validatePreference(string $userId, string $key, string|int|null } // "boolean" yes/no - if ($key === UserPreference::CALLS_START_WITHOUT_MEDIA + if ($key === UserPreference::CALLS_START_WITHOUT_AUDIO + || $key === UserPreference::CALLS_START_WITHOUT_VIDEO + || $key === UserPreference::CALLS_START_WITHOUT_MEDIA || $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 13cba0c6f70..64c2b3322ec 100644 --- a/lib/Settings/UserPreference.php +++ b/lib/Settings/UserPreference.php @@ -11,6 +11,9 @@ class UserPreference { public const ATTACHMENT_FOLDER = 'attachment_folder'; public const BLUR_VIRTUAL_BACKGROUND = 'blur_virtual_background'; + public const CALLS_START_WITHOUT_AUDIO = 'calls_start_without_audio'; + public const CALLS_START_WITHOUT_VIDEO = 'calls_start_without_video'; + /** @deprecated Kept as fallback for existing installations. */ public const CALLS_START_WITHOUT_MEDIA = 'calls_start_without_media'; public const CONVERSATIONS_LIST_STYLE = 'conversations_list_style'; public const CHAT_STYLE = 'chat_style'; diff --git a/openapi-administration.json b/openapi-administration.json index 4fb779e0312..6f15f95d1ba 100644 --- a/openapi-administration.json +++ b/openapi-administration.json @@ -174,7 +174,8 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", - "start-without-media", + "start-without-audio", + "start-without-video", "max-duration", "blur-virtual-background", "end-to-end-encryption", @@ -244,9 +245,13 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, - "start-without-media": { + "start-without-audio": { "type": "boolean", - "description": "Whether calls start without media by default" + "description": "Whether calls start without audio by default" + }, + "start-without-video": { + "type": "boolean", + "description": "Whether calls start without video by default" }, "max-duration": { "type": "integer", diff --git a/openapi-backend-recording.json b/openapi-backend-recording.json index d2e137d96b2..1bfed4e7fce 100644 --- a/openapi-backend-recording.json +++ b/openapi-backend-recording.json @@ -97,7 +97,8 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", - "start-without-media", + "start-without-audio", + "start-without-video", "max-duration", "blur-virtual-background", "end-to-end-encryption", @@ -167,9 +168,13 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, - "start-without-media": { + "start-without-audio": { "type": "boolean", - "description": "Whether calls start without media by default" + "description": "Whether calls start without audio by default" + }, + "start-without-video": { + "type": "boolean", + "description": "Whether calls start without video by default" }, "max-duration": { "type": "integer", diff --git a/openapi-backend-signaling.json b/openapi-backend-signaling.json index 207a06650c8..4a4204d0f36 100644 --- a/openapi-backend-signaling.json +++ b/openapi-backend-signaling.json @@ -97,7 +97,8 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", - "start-without-media", + "start-without-audio", + "start-without-video", "max-duration", "blur-virtual-background", "end-to-end-encryption", @@ -167,9 +168,13 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, - "start-without-media": { + "start-without-audio": { "type": "boolean", - "description": "Whether calls start without media by default" + "description": "Whether calls start without audio by default" + }, + "start-without-video": { + "type": "boolean", + "description": "Whether calls start without video by default" }, "max-duration": { "type": "integer", diff --git a/openapi-backend-sipbridge.json b/openapi-backend-sipbridge.json index e14c21d174d..9320c6cd61c 100644 --- a/openapi-backend-sipbridge.json +++ b/openapi-backend-sipbridge.json @@ -148,7 +148,8 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", - "start-without-media", + "start-without-audio", + "start-without-video", "max-duration", "blur-virtual-background", "end-to-end-encryption", @@ -218,9 +219,13 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, - "start-without-media": { + "start-without-audio": { "type": "boolean", - "description": "Whether calls start without media by default" + "description": "Whether calls start without audio by default" + }, + "start-without-video": { + "type": "boolean", + "description": "Whether calls start without video by default" }, "max-duration": { "type": "integer", diff --git a/openapi-bots.json b/openapi-bots.json index e17b992f0cd..e80a760e3e2 100644 --- a/openapi-bots.json +++ b/openapi-bots.json @@ -110,7 +110,8 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", - "start-without-media", + "start-without-audio", + "start-without-video", "max-duration", "blur-virtual-background", "end-to-end-encryption", @@ -180,9 +181,13 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, - "start-without-media": { + "start-without-audio": { "type": "boolean", - "description": "Whether calls start without media by default" + "description": "Whether calls start without audio by default" + }, + "start-without-video": { + "type": "boolean", + "description": "Whether calls start without video by default" }, "max-duration": { "type": "integer", diff --git a/openapi-federation.json b/openapi-federation.json index 83f8c612c5a..094a6ea3ba6 100644 --- a/openapi-federation.json +++ b/openapi-federation.json @@ -148,7 +148,8 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", - "start-without-media", + "start-without-audio", + "start-without-video", "max-duration", "blur-virtual-background", "end-to-end-encryption", @@ -218,9 +219,13 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, - "start-without-media": { + "start-without-audio": { "type": "boolean", - "description": "Whether calls start without media by default" + "description": "Whether calls start without audio by default" + }, + "start-without-video": { + "type": "boolean", + "description": "Whether calls start without video by default" }, "max-duration": { "type": "integer", diff --git a/openapi-full.json b/openapi-full.json index 4c7c3394d86..96c3735ac35 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -344,7 +344,8 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", - "start-without-media", + "start-without-audio", + "start-without-video", "max-duration", "blur-virtual-background", "end-to-end-encryption", @@ -414,9 +415,13 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, - "start-without-media": { + "start-without-audio": { "type": "boolean", - "description": "Whether calls start without media by default" + "description": "Whether calls start without audio by default" + }, + "start-without-video": { + "type": "boolean", + "description": "Whether calls start without video by default" }, "max-duration": { "type": "integer", diff --git a/openapi.json b/openapi.json index c357132b4ba..d9c0b74f6c0 100644 --- a/openapi.json +++ b/openapi.json @@ -284,7 +284,8 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", - "start-without-media", + "start-without-audio", + "start-without-video", "max-duration", "blur-virtual-background", "end-to-end-encryption", @@ -354,9 +355,13 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, - "start-without-media": { + "start-without-audio": { "type": "boolean", - "description": "Whether calls start without media by default" + "description": "Whether calls start without audio by default" + }, + "start-without-video": { + "type": "boolean", + "description": "Whether calls start without video by default" }, "max-duration": { "type": "integer", diff --git a/src/__mocks__/capabilities.ts b/src/__mocks__/capabilities.ts index bf37420d04d..a16e23db4b7 100644 --- a/src/__mocks__/capabilities.ts +++ b/src/__mocks__/capabilities.ts @@ -160,7 +160,8 @@ export const mockedCapabilities: Capabilities = { 'sip-dialout-enabled': true, 'default-phone-region': '', 'can-enable-sip': true, - 'start-without-media': false, + 'start-without-audio': false, + 'start-without-video': false, 'max-duration': 0, 'blur-virtual-background': false, 'end-to-end-encryption': false, @@ -230,7 +231,8 @@ export const mockedCapabilities: Capabilities = { 'predefined-backgrounds', 'predefined-backgrounds-v2', 'can-upload-background', - 'start-without-media', + 'start-without-audio', + 'start-without-video', 'blur-virtual-background', ], chat: [ diff --git a/src/components/CallView/CallView.vue b/src/components/CallView/CallView.vue index fc1e52809c7..24f78b5546e 100644 --- a/src/components/CallView/CallView.vue +++ b/src/components/CallView/CallView.vue @@ -253,9 +253,13 @@ export default { provide('CallView:screenshotModeEnabled', screenshotMode) const settingsStore = useSettingsStore() // If media settings was not used, we check the global config of default devices state here - if (!settingsStore.showMediaSettings && settingsStore.startWithoutMedia) { - localMediaModel.disableAudio() - localMediaModel.disableVideo() + if (!settingsStore.showMediaSettings) { + if (settingsStore.startWithoutAudio) { + localMediaModel.disableAudio() + } + if (settingsStore.startWithoutVideo) { + localMediaModel.disableVideo() + } } const participantActivityStore = useParticipantActivityStore() diff --git a/src/components/MediaSettings/MediaSettings.vue b/src/components/MediaSettings/MediaSettings.vue index 25095f2c6f7..4ee784b0fff 100644 --- a/src/components/MediaSettings/MediaSettings.vue +++ b/src/components/MediaSettings/MediaSettings.vue @@ -294,7 +294,8 @@ import { useSettingsStore } from '../../stores/settings.ts' import { isClassifiedConversation } from '../../utils/conversation.ts' import { localMediaModel } from '../../utils/webrtc/index.js' -const supportStartWithoutMedia = getTalkConfig('local', 'call', 'start-without-media') !== undefined +const supportStartWithoutAudio = getTalkConfig('local', 'call', 'start-without-audio') !== undefined +const supportStartWithoutVideo = getTalkConfig('local', 'call', 'start-without-video') !== undefined const supportDefaultBlurVirtualBackground = getTalkConfig('local', 'call', 'blur-virtual-background') !== undefined export default { @@ -408,7 +409,8 @@ export default { virtualBackground, tabs, dialogHeaderId, - supportStartWithoutMedia, + supportStartWithoutAudio, + supportStartWithoutVideo, supportDefaultBlurVirtualBackground, actorStore: useActorStore(), token: useGetToken(), @@ -566,10 +568,6 @@ export default { return this.$store.getters.connectionFailed(this.token) }, - startWithoutMediaEnabled() { - return this.settingsStore.startWithoutMedia - }, - audioStreamErrorMessage() { if (!this.audioStreamError) { return null @@ -640,15 +638,16 @@ export default { this.registerVideoElement(this.video) }) - if (this.settingsStore.startWithoutMedia) { - // Disable audio + if (this.settingsStore.startWithoutAudio) { this.audioOn = false BrowserStorage.setItem('audioDisabled_' + this.token, 'true') - // Disable video + } else { + this.audioOn = !BrowserStorage.getItem('audioDisabled_' + this.token) + } + if (this.settingsStore.startWithoutVideo) { this.videoOn = false BrowserStorage.setItem('videoDisabled_' + this.token, 'true') } else { - this.audioOn = !BrowserStorage.getItem('audioDisabled_' + this.token) this.videoOn = !BrowserStorage.getItem('videoDisabled_' + this.token) } this.notifyCall = BrowserStorage.getItem('silentCall_' + this.token) !== 'true' @@ -672,14 +671,14 @@ export default { audioInputId(audioInputId) { if (this.tabContent === 'devices' && audioInputId && !this.audioOn - && !this.settingsStore.startWithoutMedia) { + && !this.settingsStore.startWithoutAudio) { this.toggleAudio() } }, videoInputId(videoInputId) { if (this.tabContent === 'devices' && videoInputId && !this.videoOn - && !this.settingsStore.startWithoutMedia) { + && !this.settingsStore.startWithoutVideo) { this.toggleVideo() } }, diff --git a/src/components/SettingsDialog/SettingsDialog.vue b/src/components/SettingsDialog/SettingsDialog.vue index a21defe4da7..b6e20318d84 100644 --- a/src/components/SettingsDialog/SettingsDialog.vue +++ b/src/components/SettingsDialog/SettingsDialog.vue @@ -23,11 +23,17 @@ :name="t('spreed', 'Devices')"> + @update:modelValue="toggleStartWithoutAudio" /> + > = {} if (!settingsStore.showMediaSettings || directCall) { - // Join calls with video off if device preview skipped or bypassed - options.videoOn = false - if (settingsStore.startWithoutMedia) { - // Option: 'Turn camera and microphone off by default' + if (directCall || settingsStore.startWithoutVideo) { + options.videoOn = false + } + if (settingsStore.startWithoutAudio) { options.audioOn = false } } diff --git a/src/services/settingsService.ts b/src/services/settingsService.ts index 08804dbf7f0..ac6e8e54365 100644 --- a/src/services/settingsService.ts +++ b/src/services/settingsService.ts @@ -88,8 +88,15 @@ async function setPlaySounds(hasUserAccount: boolean, value: 'yes' | 'no') { * * @param value */ -async function setStartWithoutMedia(value: boolean) { - return setUserConfig('spreed', 'calls_start_without_media', value ? 'yes' : 'no') +async function setStartWithoutAudio(value: boolean) { + return setUserConfig('spreed', 'calls_start_without_audio', value ? 'yes' : 'no') +} + +/** + * @param value whether calls should start without video + */ +async function setStartWithoutVideo(value: boolean) { + return setUserConfig('spreed', 'calls_start_without_video', value ? 'yes' : 'no') } /** @@ -174,7 +181,8 @@ export { setPlaySounds, setReadStatusPrivacy, setSIPSettings, - setStartWithoutMedia, + setStartWithoutAudio, + setStartWithoutVideo, setTypingStatusPrivacy, setUserConfig, } diff --git a/src/stores/__tests__/settings.spec.js b/src/stores/__tests__/settings.spec.js index 4f2507195cc..1dfacc758f5 100644 --- a/src/stores/__tests__/settings.spec.js +++ b/src/stores/__tests__/settings.spec.js @@ -49,7 +49,8 @@ describe('settingsStore', () => { settingsStore.readStatusPrivacy = PRIVACY.PUBLIC settingsStore.typingStatusPrivacy = PRIVACY.PUBLIC settingsStore.showMediaSettings = true - settingsStore.startWithoutMedia = false + settingsStore.startWithoutAudio = false + settingsStore.startWithoutVideo = false settingsStore.blurVirtualBackgroundEnabled = false settingsStore.conversationsListStyle = CONVERSATION.LIST_STYLE.TWO_LINES settingsStore.attachmentFolder = '/Talk' diff --git a/src/stores/settings.ts b/src/stores/settings.ts index 7346ec38a98..e8a534a879a 100644 --- a/src/stores/settings.ts +++ b/src/stores/settings.ts @@ -23,7 +23,8 @@ import { setConversationsSortOrder, setLiveTranscriptionTargetLanguageId, setReadStatusPrivacy, - setStartWithoutMedia, + setStartWithoutAudio, + setStartWithoutVideo, setTypingStatusPrivacy, } from '../services/settingsService.ts' import { isSafari } from '../utils/browserCheck.ts' @@ -51,7 +52,8 @@ export const useSettingsStore = defineStore('settings', () => { const noiseSuppressionWithModel = ref<'none' | 'rnnoise' | (string & {})>(BrowserStorage.getItem('noiseSuppressionWithModel') ?? 'none') const echoCancellation = ref(BrowserStorage.getItem('echoCancellation') !== 'false') const autoGainControl = ref(BrowserStorage.getItem('autoGainControl') !== 'false' && !isSafari) - const startWithoutMedia = ref(getTalkConfig('local', 'call', 'start-without-media')) + const startWithoutAudio = ref(getTalkConfig('local', 'call', 'start-without-audio')) + const startWithoutVideo = ref(getTalkConfig('local', 'call', 'start-without-video')) const blurVirtualBackgroundEnabled = ref(getTalkConfig('local', 'call', 'blur-virtual-background')) const conversationsListStyle = ref(getTalkConfig('local', 'conversations', 'list-style')) const chatStyle = ref(supportChatStyle ? (getTalkConfig('local', 'chat', 'style') ?? CHAT_STYLE.SPLIT) : CHAT_STYLE.UNIFIED) @@ -171,9 +173,20 @@ export const useSettingsStore = defineStore('settings', () => { * * @param value - new selected state */ - async function updateStartWithoutMedia(value: boolean) { - await setStartWithoutMedia(value) - startWithoutMedia.value = value + /** + * @param value whether calls should start without audio + */ + async function updateStartWithoutAudio(value: boolean) { + await setStartWithoutAudio(value) + startWithoutAudio.value = value + } + + /** + * @param value whether calls should start without video + */ + async function updateStartWithoutVideo(value: boolean) { + await setStartWithoutVideo(value) + startWithoutVideo.value = value } /** @@ -258,7 +271,8 @@ export const useSettingsStore = defineStore('settings', () => { noiseSuppressionWithModel, echoCancellation, autoGainControl, - startWithoutMedia, + startWithoutAudio, + startWithoutVideo, blurVirtualBackgroundEnabled, conversationsListStyle, attachmentFolder, @@ -281,7 +295,8 @@ export const useSettingsStore = defineStore('settings', () => { setEchoCancellation, setAutoGainControl, setBlurVirtualBackgroundEnabled, - updateStartWithoutMedia, + updateStartWithoutAudio, + updateStartWithoutVideo, updateConversationsListStyle, updateAttachmentFolder, updateChatStyle, diff --git a/src/types/openapi/openapi-administration.ts b/src/types/openapi/openapi-administration.ts index defe828ef5b..e94d2d9a9f3 100644 --- a/src/types/openapi/openapi-administration.ts +++ b/src/types/openapi/openapi-administration.ts @@ -270,8 +270,10 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; - /** @description Whether calls start without media by default */ - "start-without-media": boolean; + /** @description Whether calls start without audio by default */ + "start-without-audio": boolean; + /** @description Whether calls start without video by default */ + "start-without-video": boolean; /** * Format: int64 * @description Maximum duration of a call in seconds, `0` means unlimited diff --git a/src/types/openapi/openapi-backend-recording.ts b/src/types/openapi/openapi-backend-recording.ts index efee1369da2..cad80d264fa 100644 --- a/src/types/openapi/openapi-backend-recording.ts +++ b/src/types/openapi/openapi-backend-recording.ts @@ -112,8 +112,10 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; - /** @description Whether calls start without media by default */ - "start-without-media": boolean; + /** @description Whether calls start without audio by default */ + "start-without-audio": boolean; + /** @description Whether calls start without video by default */ + "start-without-video": boolean; /** * Format: int64 * @description Maximum duration of a call in seconds, `0` means unlimited diff --git a/src/types/openapi/openapi-backend-signaling.ts b/src/types/openapi/openapi-backend-signaling.ts index 0b77d959f06..0621401c9d0 100644 --- a/src/types/openapi/openapi-backend-signaling.ts +++ b/src/types/openapi/openapi-backend-signaling.ts @@ -70,8 +70,10 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; - /** @description Whether calls start without media by default */ - "start-without-media": boolean; + /** @description Whether calls start without audio by default */ + "start-without-audio": boolean; + /** @description Whether calls start without video by default */ + "start-without-video": boolean; /** * Format: int64 * @description Maximum duration of a call in seconds, `0` means unlimited diff --git a/src/types/openapi/openapi-backend-sipbridge.ts b/src/types/openapi/openapi-backend-sipbridge.ts index 7a480cd5f69..dc3cd49f203 100644 --- a/src/types/openapi/openapi-backend-sipbridge.ts +++ b/src/types/openapi/openapi-backend-sipbridge.ts @@ -195,8 +195,10 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; - /** @description Whether calls start without media by default */ - "start-without-media": boolean; + /** @description Whether calls start without audio by default */ + "start-without-audio": boolean; + /** @description Whether calls start without video by default */ + "start-without-video": boolean; /** * Format: int64 * @description Maximum duration of a call in seconds, `0` means unlimited diff --git a/src/types/openapi/openapi-bots.ts b/src/types/openapi/openapi-bots.ts index fdcce298815..e3998cc53aa 100644 --- a/src/types/openapi/openapi-bots.ts +++ b/src/types/openapi/openapi-bots.ts @@ -116,8 +116,10 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; - /** @description Whether calls start without media by default */ - "start-without-media": boolean; + /** @description Whether calls start without audio by default */ + "start-without-audio": boolean; + /** @description Whether calls start without video by default */ + "start-without-video": boolean; /** * Format: int64 * @description Maximum duration of a call in seconds, `0` means unlimited diff --git a/src/types/openapi/openapi-federation.ts b/src/types/openapi/openapi-federation.ts index a596dd3809f..cb496cc41d0 100644 --- a/src/types/openapi/openapi-federation.ts +++ b/src/types/openapi/openapi-federation.ts @@ -206,8 +206,10 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; - /** @description Whether calls start without media by default */ - "start-without-media": boolean; + /** @description Whether calls start without audio by default */ + "start-without-audio": boolean; + /** @description Whether calls start without video by default */ + "start-without-video": boolean; /** * Format: int64 * @description Maximum duration of a call in seconds, `0` means unlimited diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index 74493fc5c85..c320baf181f 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -2819,8 +2819,10 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; - /** @description Whether calls start without media by default */ - "start-without-media": boolean; + /** @description Whether calls start without audio by default */ + "start-without-audio": boolean; + /** @description Whether calls start without video by default */ + "start-without-video": boolean; /** * Format: int64 * @description Maximum duration of a call in seconds, `0` means unlimited diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index 1e548991f2d..7ee6b9e79cb 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -2229,8 +2229,10 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; - /** @description Whether calls start without media by default */ - "start-without-media": boolean; + /** @description Whether calls start without audio by default */ + "start-without-audio": boolean; + /** @description Whether calls start without video by default */ + "start-without-video": boolean; /** * Format: int64 * @description Maximum duration of a call in seconds, `0` means unlimited diff --git a/tests/php/CapabilitiesTest.php b/tests/php/CapabilitiesTest.php index 6af769c1f2f..348fcfc55ae 100644 --- a/tests/php/CapabilitiesTest.php +++ b/tests/php/CapabilitiesTest.php @@ -183,7 +183,8 @@ public function testGetCapabilitiesGuest(): void { 'sip-dialout-enabled' => false, 'default-phone-region' => '', 'can-enable-sip' => false, - 'start-without-media' => false, + 'start-without-audio' => false, + 'start-without-video' => false, 'max-duration' => 0, 'blur-virtual-background' => false, 'end-to-end-encryption' => false, @@ -412,7 +413,8 @@ public function testGetCapabilitiesUserAllowed(bool $isNotAllowed, bool $canCrea 'sip-dialout-enabled' => false, 'default-phone-region' => 'DE', 'can-enable-sip' => false, - 'start-without-media' => false, + 'start-without-audio' => false, + 'start-without-video' => false, 'max-duration' => 0, 'blur-virtual-background' => false, 'end-to-end-encryption' => false, From ad59addd610121cc43aba154804cbba79817ed6c Mon Sep 17 00:00:00 2001 From: Maximilian Martin Date: Fri, 21 Aug 2026 10:00:40 +0000 Subject: [PATCH 2/2] add forward and backward compatibility, Assisted-by: Copilot:gpt-5.6-luna Signed-off-by: Maximilian Martin --- docs/capabilities.md | 5 ++- lib/Capabilities.php | 2 + lib/Config.php | 38 +++++++++++++++- lib/ConfigLexicon.php | 1 + lib/Controller/RoomController.php | 3 ++ lib/Controller/SettingsController.php | 1 + lib/ResponseDefinitions.php | 1 + .../BeforePreferenceSetEventListener.php | 44 +++++++++++++++++++ openapi-administration.json | 6 +++ openapi-backend-recording.json | 6 +++ openapi-backend-signaling.json | 6 +++ openapi-backend-sipbridge.json | 6 +++ openapi-bots.json | 6 +++ openapi-federation.json | 6 +++ openapi-full.json | 6 +++ openapi.json | 6 +++ src/__mocks__/capabilities.ts | 2 + .../SettingsDialog/SettingsDialog.vue | 23 +++++++++- src/services/settingsService.ts | 8 ++++ src/stores/settings.ts | 16 ++++++- src/types/openapi/openapi-administration.ts | 2 + .../openapi/openapi-backend-recording.ts | 2 + .../openapi/openapi-backend-signaling.ts | 2 + .../openapi/openapi-backend-sipbridge.ts | 2 + src/types/openapi/openapi-bots.ts | 2 + src/types/openapi/openapi-federation.ts | 2 + src/types/openapi/openapi-full.ts | 2 + src/types/openapi/openapi.ts | 2 + tests/php/CapabilitiesTest.php | 2 + 29 files changed, 203 insertions(+), 7 deletions(-) diff --git a/docs/capabilities.md b/docs/capabilities.md index 242d87f7d35..29f82aba65b 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -163,8 +163,7 @@ * `chat-summary-api` (local) - Whether the endpoint to get summarized chat messages in a conversation is available * `email-csv-import` - Whether the endpoint to import a CSV email list as participants exists * `config => chat => summary-threshold` (local) - Number of unread messages that should exist to show a "Generate summary" option -* `config => call => start-without-audio` (local) - Boolean, whether audio should be disabled when starting or joining a conversation -* `config => call => start-without-video` (local) - Boolean, whether video should be disabled when starting or joining a conversation +* `config => call => start-without-media` (local, deprecated) - 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 @@ -238,3 +237,5 @@ * `config => call => external-call-service` (local) - The target URL for an external call service if one is configured * `bot-features-api` (local) - Whether bots can fetch their own enabled features using their shared secret * `promote-demote-owner` - Whether owners can promote other participants to owner and demote other owners again, by sending the `participantType` parameter when promoting or demoting a participant +* `config => call => start-without-audio` (local) - Boolean, whether audio should be disabled when starting or joining a conversation +* `config => call => start-without-video` (local) - Boolean, whether video should be disabled when starting or joining a conversation diff --git a/lib/Capabilities.php b/lib/Capabilities.php index 27c85b7be35..2d6bf9f4275 100644 --- a/lib/Capabilities.php +++ b/lib/Capabilities.php @@ -189,6 +189,7 @@ class Capabilities implements IPublicCapability { 'predefined-backgrounds', 'predefined-backgrounds-v2', 'can-upload-background', + 'start-without-media', 'start-without-audio', 'start-without-video', 'blur-virtual-background', @@ -291,6 +292,7 @@ public function getCapabilities(): array { 'sip-dialout-enabled' => $this->talkConfig->isSIPDialOutEnabled(), 'default-phone-region' => $this->serverConfig->getSystemValueString('default_phone_region'), 'can-enable-sip' => false, + 'start-without-media' => $this->talkConfig->getCallsStartWithoutMedia($user?->getUID()), 'start-without-audio' => $this->talkConfig->getCallsStartWithoutAudio($user?->getUID()), 'start-without-video' => $this->talkConfig->getCallsStartWithoutVideo($user?->getUID()), 'max-duration' => $this->appConfig->getAppValueInt('max_call_duration'), diff --git a/lib/Config.php b/lib/Config.php index 62a97fe39d9..7cdc01eaf63 100644 --- a/lib/Config.php +++ b/lib/Config.php @@ -883,6 +883,41 @@ public function getCallsStartWithoutVideo(?string $userId): bool { return $this->getCallsStartWithoutMediaType($userId, self::CALLS_START_WITHOUT_VIDEO); } + /** @deprecated Use getCallsStartWithoutAudio() and getCallsStartWithoutVideo(). */ + public function getCallsStartWithoutMedia(?string $userId): bool { + if ($userId !== null) { + $userSetting = $this->config->getUserValue($userId, 'spreed', UserPreference::CALLS_START_WITHOUT_MEDIA); + if ($userSetting === 'yes' || $userSetting === 'no') { + return $userSetting === 'yes'; + } + + // If the legacy setting is not set, check the new settings for audio and video separately. + $audioSetting = $this->config->getUserValue($userId, 'spreed', UserPreference::CALLS_START_WITHOUT_AUDIO); + $videoSetting = $this->config->getUserValue($userId, 'spreed', UserPreference::CALLS_START_WITHOUT_VIDEO); + if ($audioSetting === 'yes' || $videoSetting === 'yes') { + return true; + } + if ($audioSetting === 'no' && $videoSetting === 'no') { + return false; + } + } + + $legacyAppSetting = $this->appConfig->getAppValue(self::CALLS_START_WITHOUT_MEDIA); + if ($legacyAppSetting !== '') { + return $legacyAppSetting === 'yes' || $legacyAppSetting === '1'; + } + + // If the legacy setting is not set, check the new settings for audio and video separately. + $audioAppSetting = $this->appConfig->getAppValue(self::CALLS_START_WITHOUT_AUDIO); + $videoAppSetting = $this->appConfig->getAppValue(self::CALLS_START_WITHOUT_VIDEO); + if ($audioAppSetting !== '' || $videoAppSetting !== '') { + return $audioAppSetting === 'yes' || $audioAppSetting === '1' + || $videoAppSetting === 'yes' || $videoAppSetting === '1'; + } + + return false; + } + private function getCallsStartWithoutMediaType(?string $userId, string $key): bool { if ($userId !== null) { $userSetting = $this->config->getUserValue($userId, 'spreed', $key); @@ -890,7 +925,7 @@ private function getCallsStartWithoutMediaType(?string $userId, string $key): bo return $userSetting === 'yes'; } - // Legacy setting for backward compatibility + // If the new setting is not set, check the legacy setting. $legacyUserSetting = $this->config->getUserValue($userId, 'spreed', UserPreference::CALLS_START_WITHOUT_MEDIA); if ($legacyUserSetting === 'yes' || $legacyUserSetting === 'no') { return $legacyUserSetting === 'yes'; @@ -901,6 +936,7 @@ private function getCallsStartWithoutMediaType(?string $userId, string $key): bo return $this->appConfig->getAppValueBool($key); } + // If the new setting is not set, check the legacy setting. return $this->appConfig->getAppValueBool(self::CALLS_START_WITHOUT_MEDIA); } diff --git a/lib/ConfigLexicon.php b/lib/ConfigLexicon.php index 0db0c131e1e..0540a95de31 100644 --- a/lib/ConfigLexicon.php +++ b/lib/ConfigLexicon.php @@ -49,6 +49,7 @@ public function getAppConfigs(): array { new Entry(Config::EXTERNAL_CALL_SERVICE_AUTH_PASSWORD, ValueType::STRING, '', definition: 'HTTP Basic Auth password used when Talk calls the external service'), new Entry(Config::EXTERNAL_CALL_SERVICE_FRAME_ORIGINS, ValueType::ARRAY, [], definition: 'JSON array of scheme+host(+port) origins that may be loaded in the iframe.' . PHP_EOL . 'Added to `Content-Security-Policy: frame-src` and the `Permissions-Policy` for camera/microphone'), new Entry(Config::EXTERNAL_CALL_SERVICE_IFRAME_FIELD, ValueType::STRING, '', definition: 'JSON field name in the external service response that contains the iframe URL'), + new Entry(Config::CALLS_START_WITHOUT_MEDIA, ValueType::BOOL, false, definition: 'Deprecated: whether participants start with audio and video disabled by default', deprecated: true), new Entry(Config::CALLS_START_WITHOUT_AUDIO, ValueType::BOOL, false, definition: 'Whether participants start with audio disabled by default'), new Entry(Config::CALLS_START_WITHOUT_VIDEO, ValueType::BOOL, false, definition: 'Whether participants start with video disabled by default'), new Entry(Config::INACTIVITY_LOCK_AFTER_DAYS, ValueType::INT, 0, definition: 'A duration (in days) after which rooms are locked. Calculated from the last activity in the room,'), diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php index 6611726819f..03adf9ada8a 100644 --- a/lib/Controller/RoomController.php +++ b/lib/Controller/RoomController.php @@ -3376,6 +3376,9 @@ public function getCapabilities(): DataResponse { if (isset($data['config']['call']['start-without-audio'])) { $data['config']['call']['start-without-audio'] = $this->talkConfig->getCallsStartWithoutAudio($this->userId); } + if (isset($data['config']['call']['start-without-media'])) { + $data['config']['call']['start-without-media'] = $this->talkConfig->getCallsStartWithoutMedia($this->userId); + } if (isset($data['config']['call']['start-without-video'])) { $data['config']['call']['start-without-video'] = $this->talkConfig->getCallsStartWithoutVideo($this->userId); } diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index 5d2e7d6b2ce..4b02ea95cf5 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -53,6 +53,7 @@ public function setUserSetting(string $key, string|int|null $value): DataRespons } $this->config->setUserValue($this->userId, 'spreed', $key, $value); + $this->preferenceListener->synchronizeMediaPreferences($this->userId, $key, (string)$value); return new DataResponse(null); } diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index 9a7e79145d2..0b95d03360d 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -792,6 +792,7 @@ * // Whether the user can enable SIP for conversations * can-enable-sip: bool, * // Whether calls start without media by default + * start-without-media: bool, * start-without-audio: bool, * start-without-video: bool, * // Maximum duration of a call in seconds, `0` means unlimited diff --git a/lib/Settings/BeforePreferenceSetEventListener.php b/lib/Settings/BeforePreferenceSetEventListener.php index 183102fc3e0..9bdc24ef4fe 100644 --- a/lib/Settings/BeforePreferenceSetEventListener.php +++ b/lib/Settings/BeforePreferenceSetEventListener.php @@ -10,6 +10,7 @@ use OCA\Files_Sharing\SharedStorage; use OCA\Talk\AppInfo\Application; +use OCA\Talk\Config; use OCA\Talk\Model\Attendee; use OCA\Talk\Participant; use OCA\Talk\Service\ParticipantService; @@ -20,6 +21,7 @@ use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; +use OCP\IConfig; use Psr\Log\LoggerInterface; /** @@ -29,6 +31,7 @@ class BeforePreferenceSetEventListener implements IEventListener { public function __construct( private readonly IRootFolder $rootFolder, private readonly ParticipantService $participantService, + private readonly IConfig $config, private readonly LoggerInterface $logger, ) { } @@ -49,6 +52,9 @@ public function handle(Event $event): void { $event->getConfigKey(), $event->getConfigValue(), )); + if ($event->isValid()) { + $this->synchronizeMediaPreferences($event->getUserId(), $event->getConfigKey(), $event->getConfigValue()); + } } /** @@ -107,6 +113,44 @@ public function validatePreference(string $userId, string $key, string|int|null return false; } + /** + * Synchronizes media preferences based on the updated setting. + * For compatibility reasons, the legacy preference `calls_start_without_media`is updated + * based on the values of `calls_start_without_audio` and `calls_start_without_video`. + * + * @param string $userId The user ID + * @param string $key The preference key + * @param string $value The new preference value + * @return void + */ + public function synchronizeMediaPreferences(string $userId, string $key, string $value): void { + // If the legacy preference is updated, we need to update the new preferences as well + if ($key === UserPreference::CALLS_START_WITHOUT_MEDIA) { + $this->config->setUserValue($userId, Application::APP_ID, UserPreference::CALLS_START_WITHOUT_AUDIO, $value); + $this->config->setUserValue($userId, Application::APP_ID, UserPreference::CALLS_START_WITHOUT_VIDEO, $value); + return; + } + + if ($key !== UserPreference::CALLS_START_WITHOUT_AUDIO && $key !== UserPreference::CALLS_START_WITHOUT_VIDEO) { + return; + } + + // Update the legacy preference based on the new preferences + // If either of the new preferences is set to 'yes', the legacy preference should be 'yes' + $otherKey = $key === UserPreference::CALLS_START_WITHOUT_AUDIO + ? UserPreference::CALLS_START_WITHOUT_VIDEO + : UserPreference::CALLS_START_WITHOUT_AUDIO; + $otherValue = $this->config->getUserValue($userId, Application::APP_ID, $otherKey); + if ($otherValue !== 'yes' && $otherValue !== 'no') { + $otherValue = $this->config->getUserValue($userId, Application::APP_ID, UserPreference::CALLS_START_WITHOUT_MEDIA); + } + if ($otherValue !== 'yes' && $otherValue !== 'no') { + $otherValue = $this->config->getAppValue(Application::APP_ID, Config::CALLS_START_WITHOUT_MEDIA, 'no'); + } + $legacyValue = ($value === 'yes' || $otherValue === 'yes') ? 'yes' : 'no'; + $this->config->setUserValue($userId, Application::APP_ID, UserPreference::CALLS_START_WITHOUT_MEDIA, $legacyValue); + } + protected function validateAttachmentFolder(string $userId, string $value): bool { try { $userFolder = $this->rootFolder->getUserFolder($userId); diff --git a/openapi-administration.json b/openapi-administration.json index 6f15f95d1ba..ae6e3307332 100644 --- a/openapi-administration.json +++ b/openapi-administration.json @@ -174,6 +174,7 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", + "start-without-media", "start-without-audio", "start-without-video", "max-duration", @@ -245,6 +246,11 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, + "start-without-media": { + "type": "boolean", + "deprecated": true, + "description": "Whether calls start without media by default" + }, "start-without-audio": { "type": "boolean", "description": "Whether calls start without audio by default" diff --git a/openapi-backend-recording.json b/openapi-backend-recording.json index 1bfed4e7fce..bf9313381a7 100644 --- a/openapi-backend-recording.json +++ b/openapi-backend-recording.json @@ -97,6 +97,7 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", + "start-without-media", "start-without-audio", "start-without-video", "max-duration", @@ -168,6 +169,11 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, + "start-without-media": { + "type": "boolean", + "deprecated": true, + "description": "Whether calls start without media by default" + }, "start-without-audio": { "type": "boolean", "description": "Whether calls start without audio by default" diff --git a/openapi-backend-signaling.json b/openapi-backend-signaling.json index 4a4204d0f36..b1c43267c21 100644 --- a/openapi-backend-signaling.json +++ b/openapi-backend-signaling.json @@ -97,6 +97,7 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", + "start-without-media", "start-without-audio", "start-without-video", "max-duration", @@ -168,6 +169,11 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, + "start-without-media": { + "type": "boolean", + "deprecated": true, + "description": "Whether calls start without media by default" + }, "start-without-audio": { "type": "boolean", "description": "Whether calls start without audio by default" diff --git a/openapi-backend-sipbridge.json b/openapi-backend-sipbridge.json index 9320c6cd61c..f57ab086604 100644 --- a/openapi-backend-sipbridge.json +++ b/openapi-backend-sipbridge.json @@ -148,6 +148,7 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", + "start-without-media", "start-without-audio", "start-without-video", "max-duration", @@ -219,6 +220,11 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, + "start-without-media": { + "type": "boolean", + "deprecated": true, + "description": "Whether calls start without media by default" + }, "start-without-audio": { "type": "boolean", "description": "Whether calls start without audio by default" diff --git a/openapi-bots.json b/openapi-bots.json index e80a760e3e2..b4aee3d757d 100644 --- a/openapi-bots.json +++ b/openapi-bots.json @@ -110,6 +110,7 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", + "start-without-media", "start-without-audio", "start-without-video", "max-duration", @@ -181,6 +182,11 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, + "start-without-media": { + "type": "boolean", + "deprecated": true, + "description": "Whether calls start without media by default" + }, "start-without-audio": { "type": "boolean", "description": "Whether calls start without audio by default" diff --git a/openapi-federation.json b/openapi-federation.json index 094a6ea3ba6..55b0ec3f5b0 100644 --- a/openapi-federation.json +++ b/openapi-federation.json @@ -148,6 +148,7 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", + "start-without-media", "start-without-audio", "start-without-video", "max-duration", @@ -219,6 +220,11 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, + "start-without-media": { + "type": "boolean", + "deprecated": true, + "description": "Whether calls start without media by default" + }, "start-without-audio": { "type": "boolean", "description": "Whether calls start without audio by default" diff --git a/openapi-full.json b/openapi-full.json index 96c3735ac35..7941a490ddb 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -344,6 +344,7 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", + "start-without-media", "start-without-audio", "start-without-video", "max-duration", @@ -415,6 +416,11 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, + "start-without-media": { + "type": "boolean", + "deprecated": true, + "description": "Whether calls start without media by default" + }, "start-without-audio": { "type": "boolean", "description": "Whether calls start without audio by default" diff --git a/openapi.json b/openapi.json index d9c0b74f6c0..65e8140759c 100644 --- a/openapi.json +++ b/openapi.json @@ -284,6 +284,7 @@ "sip-dialout-enabled", "default-phone-region", "can-enable-sip", + "start-without-media", "start-without-audio", "start-without-video", "max-duration", @@ -355,6 +356,11 @@ "type": "boolean", "description": "Whether the user can enable SIP for conversations" }, + "start-without-media": { + "type": "boolean", + "deprecated": true, + "description": "Whether calls start without media by default" + }, "start-without-audio": { "type": "boolean", "description": "Whether calls start without audio by default" diff --git a/src/__mocks__/capabilities.ts b/src/__mocks__/capabilities.ts index a16e23db4b7..66bc509ce74 100644 --- a/src/__mocks__/capabilities.ts +++ b/src/__mocks__/capabilities.ts @@ -160,6 +160,7 @@ export const mockedCapabilities: Capabilities = { 'sip-dialout-enabled': true, 'default-phone-region': '', 'can-enable-sip': true, + 'start-without-media': false, 'start-without-audio': false, 'start-without-video': false, 'max-duration': 0, @@ -231,6 +232,7 @@ export const mockedCapabilities: Capabilities = { 'predefined-backgrounds', 'predefined-backgrounds-v2', 'can-upload-background', + 'start-without-media', 'start-without-audio', 'start-without-video', 'blur-virtual-background', diff --git a/src/components/SettingsDialog/SettingsDialog.vue b/src/components/SettingsDialog/SettingsDialog.vue index b6e20318d84..7442eb75baa 100644 --- a/src/components/SettingsDialog/SettingsDialog.vue +++ b/src/components/SettingsDialog/SettingsDialog.vue @@ -23,17 +23,23 @@ :name="t('spreed', 'Devices')"> + { const noiseSuppressionWithModel = ref<'none' | 'rnnoise' | (string & {})>(BrowserStorage.getItem('noiseSuppressionWithModel') ?? 'none') const echoCancellation = ref(BrowserStorage.getItem('echoCancellation') !== 'false') const autoGainControl = ref(BrowserStorage.getItem('autoGainControl') !== 'false' && !isSafari) - const startWithoutAudio = ref(getTalkConfig('local', 'call', 'start-without-audio')) - const startWithoutVideo = ref(getTalkConfig('local', 'call', 'start-without-video')) + const startWithoutAudio = ref(getTalkConfig('local', 'call', 'start-without-audio') ?? legacyStartWithoutMedia) + const startWithoutVideo = ref(getTalkConfig('local', 'call', 'start-without-video') ?? legacyStartWithoutMedia) const blurVirtualBackgroundEnabled = ref(getTalkConfig('local', 'call', 'blur-virtual-background')) const conversationsListStyle = ref(getTalkConfig('local', 'conversations', 'list-style')) const chatStyle = ref(supportChatStyle ? (getTalkConfig('local', 'chat', 'style') ?? CHAT_STYLE.SPLIT) : CHAT_STYLE.UNIFIED) @@ -181,6 +183,15 @@ export const useSettingsStore = defineStore('settings', () => { startWithoutAudio.value = value } + /** + * @param value whether calls should start without audio and video + */ + async function updateStartWithoutMedia(value: boolean) { + await setStartWithoutMedia(value) + startWithoutAudio.value = value + startWithoutVideo.value = value + } + /** * @param value whether calls should start without video */ @@ -296,6 +307,7 @@ export const useSettingsStore = defineStore('settings', () => { setAutoGainControl, setBlurVirtualBackgroundEnabled, updateStartWithoutAudio, + updateStartWithoutMedia, updateStartWithoutVideo, updateConversationsListStyle, updateAttachmentFolder, diff --git a/src/types/openapi/openapi-administration.ts b/src/types/openapi/openapi-administration.ts index e94d2d9a9f3..02ddb88e8f4 100644 --- a/src/types/openapi/openapi-administration.ts +++ b/src/types/openapi/openapi-administration.ts @@ -270,6 +270,8 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; + /** @deprecated Whether calls start without media by default */ + "start-without-media": boolean; /** @description Whether calls start without audio by default */ "start-without-audio": boolean; /** @description Whether calls start without video by default */ diff --git a/src/types/openapi/openapi-backend-recording.ts b/src/types/openapi/openapi-backend-recording.ts index cad80d264fa..2afd1a37b7e 100644 --- a/src/types/openapi/openapi-backend-recording.ts +++ b/src/types/openapi/openapi-backend-recording.ts @@ -112,6 +112,8 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; + /** @deprecated Whether calls start without media by default */ + "start-without-media": boolean; /** @description Whether calls start without audio by default */ "start-without-audio": boolean; /** @description Whether calls start without video by default */ diff --git a/src/types/openapi/openapi-backend-signaling.ts b/src/types/openapi/openapi-backend-signaling.ts index 0621401c9d0..d023f3c059b 100644 --- a/src/types/openapi/openapi-backend-signaling.ts +++ b/src/types/openapi/openapi-backend-signaling.ts @@ -70,6 +70,8 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; + /** @deprecated Whether calls start without media by default */ + "start-without-media": boolean; /** @description Whether calls start without audio by default */ "start-without-audio": boolean; /** @description Whether calls start without video by default */ diff --git a/src/types/openapi/openapi-backend-sipbridge.ts b/src/types/openapi/openapi-backend-sipbridge.ts index dc3cd49f203..9b4f10e4d5c 100644 --- a/src/types/openapi/openapi-backend-sipbridge.ts +++ b/src/types/openapi/openapi-backend-sipbridge.ts @@ -195,6 +195,8 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; + /** @deprecated Whether calls start without media by default */ + "start-without-media": boolean; /** @description Whether calls start without audio by default */ "start-without-audio": boolean; /** @description Whether calls start without video by default */ diff --git a/src/types/openapi/openapi-bots.ts b/src/types/openapi/openapi-bots.ts index e3998cc53aa..2bedd316193 100644 --- a/src/types/openapi/openapi-bots.ts +++ b/src/types/openapi/openapi-bots.ts @@ -116,6 +116,8 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; + /** @deprecated Whether calls start without media by default */ + "start-without-media": boolean; /** @description Whether calls start without audio by default */ "start-without-audio": boolean; /** @description Whether calls start without video by default */ diff --git a/src/types/openapi/openapi-federation.ts b/src/types/openapi/openapi-federation.ts index cb496cc41d0..674af6a3adf 100644 --- a/src/types/openapi/openapi-federation.ts +++ b/src/types/openapi/openapi-federation.ts @@ -206,6 +206,8 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; + /** @deprecated Whether calls start without media by default */ + "start-without-media": boolean; /** @description Whether calls start without audio by default */ "start-without-audio": boolean; /** @description Whether calls start without video by default */ diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index c320baf181f..22df6025a1a 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -2819,6 +2819,8 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; + /** @deprecated Whether calls start without media by default */ + "start-without-media": boolean; /** @description Whether calls start without audio by default */ "start-without-audio": boolean; /** @description Whether calls start without video by default */ diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index 7ee6b9e79cb..e124ed5d30a 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -2229,6 +2229,8 @@ export type components = { "default-phone-region": string; /** @description Whether the user can enable SIP for conversations */ "can-enable-sip": boolean; + /** @deprecated Whether calls start without media by default */ + "start-without-media": boolean; /** @description Whether calls start without audio by default */ "start-without-audio": boolean; /** @description Whether calls start without video by default */ diff --git a/tests/php/CapabilitiesTest.php b/tests/php/CapabilitiesTest.php index 348fcfc55ae..083c157e3e6 100644 --- a/tests/php/CapabilitiesTest.php +++ b/tests/php/CapabilitiesTest.php @@ -183,6 +183,7 @@ public function testGetCapabilitiesGuest(): void { 'sip-dialout-enabled' => false, 'default-phone-region' => '', 'can-enable-sip' => false, + 'start-without-media' => false, 'start-without-audio' => false, 'start-without-video' => false, 'max-duration' => 0, @@ -413,6 +414,7 @@ public function testGetCapabilitiesUserAllowed(bool $isNotAllowed, bool $canCrea 'sip-dialout-enabled' => false, 'default-phone-region' => 'DE', 'can-enable-sip' => false, + 'start-without-media' => false, 'start-without-audio' => false, 'start-without-video' => false, 'max-duration' => 0,