feat(calls): Allow to provide branded talk-backgrounds via themes

Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
Joas Schilling 2025-04-25 16:36:57 +02:00
parent 246e68104e
commit b1c444896a
No known key found for this signature in database
GPG key ID: F72FA5B49FFA96B0
7 changed files with 104 additions and 35 deletions

View file

@ -182,3 +182,4 @@
## 21.1
* `conversation-creation-all` - Whether the conversation creation endpoint allows to specify all attributes of a conversation
* `important-conversations` (local) - Whether important conversations are supported
* `config => call => predefined-backgrounds-v2` (local) - Whether virtual backgrounds should be read from the theming directory

View file

@ -122,3 +122,6 @@ Legend:
| `call_end_to_end_encryption` | string<br>`1` or `0` | `0` | No | πŸ–ŒοΈ | Whether clients should end-to-end encrypt streams in calls (Only supported with High-performance backend) |
| `inactivity_lock_after_days` | int | `0` | No | | A duration (in days) after which rooms are locked. Calculated from the last activity in the room. |
| `inactivity_enable_lobby` | string<br>`1` or `0` | `0` | No | | Additionally enable the lobby for inactive rooms so they can only be read by moderators. |
| `backgrounds_branded_for_guests` | string<br>`1` or `0` | `0` | No | | Whether guests are allowed to use the virtual backgrounds provided via `themes/talk-backgrounds/` |
| `backgrounds_default_for_users` | string<br>`1` or `0` | `1` | No | | Whether users are allowed to use the default virtual backgrounds provided by the releases |
| `backgrounds_upload_users` | string<br>`1` or `0` | `1` | No | | Whether users are allowed to upload custom virtual backgrounds and choose from their Nextcloud Files |

View file

@ -149,6 +149,7 @@ class Capabilities implements IPublicCapability {
],
'call' => [
'predefined-backgrounds',
'predefined-backgrounds-v2',
'can-upload-background',
'start-without-media',
'blur-virtual-background',
@ -223,6 +224,7 @@ class Capabilities implements IPublicCapability {
'recording-consent' => $this->talkConfig->recordingConsentRequired(),
'supported-reactions' => ['❀️', 'πŸŽ‰', 'πŸ‘', 'πŸ‘‹', 'πŸ‘', 'πŸ‘Ž', 'πŸ”₯', 'πŸ˜‚', '🀩', 'πŸ€”', '😲', 'πŸ˜₯'],
// 'predefined-backgrounds' => list<string>,
// 'predefined-backgrounds-v2' => list<string>,
'can-upload-background' => false,
'sip-enabled' => $this->talkConfig->isSIPConfigured(),
'sip-dialout-enabled' => $this->talkConfig->isSIPDialOutEnabled(),
@ -293,46 +295,34 @@ class Capabilities implements IPublicCapability {
$capabilities['config']['signaling']['hello-v2-token-key'] = $pubKey;
}
/** @var ?list<string> $predefinedBackgrounds */
$predefinedBackgrounds = null;
$cachedPredefinedBackgrounds = $this->talkCache->get('predefined_backgrounds');
if ($cachedPredefinedBackgrounds !== null) {
// Try using cached value
/** @var list<string>|null $predefinedBackgrounds */
$predefinedBackgrounds = json_decode($cachedPredefinedBackgrounds, true);
$includeBrandedBackgrounds = $user instanceof IUser || $this->appConfig->getAppValueBool('backgrounds_branded_for_guests');
$includeDefaultBackgrounds = !$user instanceof IUser || $this->appConfig->getAppValueBool('backgrounds_default_for_users', true);
$predefinedBackgrounds = [];
$defaultBackgrounds = $this->getBackgroundsFromDirectory(__DIR__ . '/../img/backgrounds', '_default');
if ($includeBrandedBackgrounds) {
$predefinedBackgrounds = $this->getBackgroundsFromDirectory(\OC::$SERVERROOT . '/themes/talk-backgrounds', '_branded');
$predefinedBackgrounds = array_map(static fn ($fileName) => '/themes/talk-backgrounds/' . $fileName, $predefinedBackgrounds);
}
if (!is_array($predefinedBackgrounds)) {
// Cache was empty or invalid, regenerate
/** @var list<string> $predefinedBackgrounds */
$predefinedBackgrounds = [];
if (file_exists(__DIR__ . '/../img/backgrounds')) {
$directoryIterator = new \DirectoryIterator(__DIR__ . '/../img/backgrounds');
foreach ($directoryIterator as $file) {
if (!$file->isFile()) {
continue;
}
if ($file->isDot()) {
continue;
}
if ($file->getFilename() === 'COPYING') {
continue;
}
$predefinedBackgrounds[] = $file->getFilename();
}
sort($predefinedBackgrounds);
}
$this->talkCache->set('predefined_backgrounds', json_encode($predefinedBackgrounds), 300);
if ($includeDefaultBackgrounds) {
$spreedWebPath = $this->appManager->getAppWebPath('spreed');
$prefixedDefaultBackgrounds = array_map(static fn ($fileName) => $spreedWebPath . '/img/backgrounds/' . $fileName, $defaultBackgrounds);
$predefinedBackgrounds = array_merge($predefinedBackgrounds, $prefixedDefaultBackgrounds);
}
$capabilities['config']['call']['predefined-backgrounds'] = $predefinedBackgrounds;
$capabilities['config']['call']['predefined-backgrounds'] = $defaultBackgrounds;
$capabilities['config']['call']['predefined-backgrounds-v2'] = array_values($predefinedBackgrounds);
if ($user instanceof IUser) {
$quota = $user->getQuota();
if ($quota !== 'none') {
$quota = Util::computerFileSize($quota);
$userAllowedToUpload = $this->appConfig->getAppValueBool('backgrounds_upload_users', true);
if ($userAllowedToUpload) {
$quota = $user->getQuota();
if ($quota !== 'none') {
$quota = Util::computerFileSize($quota);
}
$capabilities['config']['call']['can-upload-background'] = $quota === 'none' || $quota > 0;
}
$capabilities['config']['call']['can-upload-background'] = $quota === 'none' || $quota > 0;
$capabilities['config']['call']['can-enable-sip'] = $this->talkConfig->canUserEnableSIP($user);
}
@ -352,4 +342,43 @@ class Capabilities implements IPublicCapability {
'spreed' => $capabilities,
];
}
/**
* @return list<string>
*/
protected function getBackgroundsFromDirectory(string $directory, string $cacheSuffix): array {
$cacheKey = 'predefined_backgrounds' . $cacheSuffix;
/** @var ?list<string> $predefinedBackgrounds */
$predefinedBackgrounds = null;
$cachedPredefinedBackgrounds = $this->talkCache->get($cacheKey);
if ($cachedPredefinedBackgrounds !== null) {
// Try using cached value
/** @var list<string>|null $predefinedBackgrounds */
$predefinedBackgrounds = json_decode($cachedPredefinedBackgrounds, true);
}
if (!is_array($predefinedBackgrounds)) {
if (file_exists($directory) && is_dir($directory)) {
$directoryIterator = new \DirectoryIterator($directory);
foreach ($directoryIterator as $file) {
if (!$file->isFile()) {
continue;
}
if ($file->isDot()) {
continue;
}
if ($file->getFilename() === 'COPYING') {
continue;
}
$predefinedBackgrounds[] = $file->getFilename();
}
sort($predefinedBackgrounds);
}
$this->talkCache->set($cacheKey, json_encode($predefinedBackgrounds), 300);
}
return $predefinedBackgrounds ?? [];
}
}

View file

@ -354,7 +354,10 @@ namespace OCA\Talk;
* recording: bool,
* recording-consent: int,
* supported-reactions: list<string>,
* // List of file names relative to the spreed/img/backgrounds/ web path, e.g. `2_home.jpg`
* predefined-backgrounds: list<string>,
* // List of file paths relative to the server web root with leading slash, e.g. `/apps/spreed/img/backgrounds/2_home.jpg`
* predefined-backgrounds-v2: list<string>,
* can-upload-background: bool,
* sip-enabled: bool,
* sip-dialout-enabled: bool,

View file

@ -125,7 +125,8 @@ export const mockedCapabilities: Capabilities = {
recording: true,
'recording-consent': 0,
'supported-reactions': ['❀️', 'πŸŽ‰', 'πŸ‘', 'πŸ‘', 'πŸ‘Ž', 'πŸ˜‚', '🀩', 'πŸ€”', '😲', 'πŸ˜₯'],
'predefined-backgrounds': ['1_office', '2_home', '3_abstract'],
'predefined-backgrounds': ['1_office.jpg', '2_home.jpg', '3_abstract.jpg'],
'predefined-backgrounds-v2': ['/apps/spreed/img/backgrounds/1_office.jpg', '/apps/spreed/img/backgrounds/2_home.jpg', '/apps/spreed/img/backgrounds/3_abstract.jpg'],
'can-upload-background': true,
'sip-enabled': true,
'sip-dialout-enabled': true,

View file

@ -131,6 +131,7 @@ export default {
return {
canUploadBackgrounds: getTalkConfig('local', 'call', 'can-upload-background'),
predefinedBackgrounds: getTalkConfig('local', 'call', 'predefined-backgrounds'),
predefinedBackgroundsV2: getTalkConfig('local', 'call', 'predefined-backgrounds-v2'),
settingsStore: useSettingsStore(),
}
},
@ -150,6 +151,10 @@ export default {
},
predefinedBackgroundsURLs() {
if (this.predefinedBackgroundsV2) {
return this.predefinedBackgroundsV2
}
return this.predefinedBackgrounds.map(fileName => {
return imagePath('spreed', 'backgrounds/' + fileName)
})

View file

@ -147,6 +147,16 @@ class CapabilitiesTest extends TestCase {
'7_library.jpg',
'8_space_station.jpg',
],
'predefined-backgrounds-v2' => [
'/img/backgrounds/1_office.jpg',
'/img/backgrounds/2_home.jpg',
'/img/backgrounds/3_abstract.jpg',
'/img/backgrounds/4_beach.jpg',
'/img/backgrounds/5_park.jpg',
'/img/backgrounds/6_theater.jpg',
'/img/backgrounds/7_library.jpg',
'/img/backgrounds/8_space_station.jpg',
],
],
'chat' => [
'max-length' => 32000,
@ -246,6 +256,13 @@ class CapabilitiesTest extends TestCase {
['core', 'backgroundjobs_mode', 'ajax', 'cron'],
]);
$this->appConfig->expects($this->any())
->method('getAppValueBool')
->willReturnMap([
['backgrounds_default_for_users', true, true],
['backgrounds_upload_users', true, true],
]);
$this->assertInstanceOf(IPublicCapability::class, $capabilities);
$data = $capabilities->getCapabilities();
$this->assertSame([
@ -287,6 +304,16 @@ class CapabilitiesTest extends TestCase {
'7_library.jpg',
'8_space_station.jpg',
],
'predefined-backgrounds-v2' => [
'/img/backgrounds/1_office.jpg',
'/img/backgrounds/2_home.jpg',
'/img/backgrounds/3_abstract.jpg',
'/img/backgrounds/4_beach.jpg',
'/img/backgrounds/5_park.jpg',
'/img/backgrounds/6_theater.jpg',
'/img/backgrounds/7_library.jpg',
'/img/backgrounds/8_space_station.jpg',
],
],
'chat' => [
'max-length' => 32000,