From 06c4dd225e90a03b5756d841c6f445febaf396da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Thu, 17 Dec 2020 22:06:19 +0100 Subject: [PATCH 1/2] Use a LRU cache for the blurred background images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The blurred background images were cached directly in an array, so the cache could grow without limits. Although it should be a rare case, this could lead to a high memory consumption if the user slowly resizes during a long time the browser window. Therefore the cache now uses a least recently used cache limited to just five elements (so it can store the blurred backgrounds when the sidebars are open or closed, plus having a bit of room). "node-lru-cache" was selected as the library to use due to being small, widely used and, apparently, matching the expected behaviour ("quick-lru", which is smaller and also widely used, although not so much, unfortunately does not seem to honour the "maximum size" parameter). Signed-off-by: Daniel Calviño Sánchez --- package-lock.json | 51 +++++++++++++++---- package.json | 1 + .../CallView/shared/VideoBackground.vue | 10 ++-- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 26f7c0ceeac..5c9fd29923a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4789,11 +4789,27 @@ "vue-template-es2015-compiler": "^1.9.0" }, "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true } } }, @@ -8944,6 +8960,24 @@ "lru-cache": "^4.1.5", "semver": "^5.6.0", "sigmund": "^1.0.1" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } } }, "ee-first": { @@ -13797,13 +13831,11 @@ "dev": true }, "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "yallist": "^4.0.0" } }, "make-dir": { @@ -22100,10 +22132,9 @@ "dev": true }, "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yaml": { "version": "1.10.0", diff --git a/package.json b/package.json index af2d39a3909..553d39e5e47 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "escape-html": "^1.0.3", "hark": "^1.2.3", "lodash": "^4.17.20", + "lru-cache": "^6.0.0", "mockconsole": "0.0.1", "nextcloud-vue-collections": "^0.9.0", "sdp-transform": "^2.14.1", diff --git a/src/components/CallView/shared/VideoBackground.vue b/src/components/CallView/shared/VideoBackground.vue index e75325c66c6..734a3113448 100644 --- a/src/components/CallView/shared/VideoBackground.vue +++ b/src/components/CallView/shared/VideoBackground.vue @@ -51,6 +51,8 @@ import { getBuilder } from '@nextcloud/browser-storage' import browserCheck from '../../../mixins/browserCheck' import blur from '../../../utils/imageBlurrer' +const LRU = require('lru-cache') + const browserStorage = getBuilder('nextcloud').persist().build() // note: this info is shared with the Avatar component @@ -97,7 +99,7 @@ export default { useCssBlurFilter: true, blur: 0, blurredBackgroundImage: null, - blurredBackgroundImageCache: {}, + blurredBackgroundImageCache: new LRU({ max: 5 }), blurredBackgroundImageSource: null, pendingGenerateBlurredBackgroundImageCount: 0, isDestroyed: false, @@ -254,8 +256,8 @@ export default { } const cacheId = this.backgroundImageUrl + '-' + width + '-' + height + '-' + this.backgroundBlur - if (this.blurredBackgroundImageCache[cacheId]) { - this.blurredBackgroundImage = this.blurredBackgroundImageCache[cacheId] + if (this.blurredBackgroundImageCache.get(cacheId)) { + this.blurredBackgroundImage = this.blurredBackgroundImageCache.get(cacheId) return } @@ -274,7 +276,7 @@ export default { } this.blurredBackgroundImage = image - this.blurredBackgroundImageCache[cacheId] = this.blurredBackgroundImage + this.blurredBackgroundImageCache.set(cacheId, this.blurredBackgroundImage) const generateBlurredBackgroundImageCalledAgain = this.pendingGenerateBlurredBackgroundImageCount > 1 From 208b820e67e90e1f60e2bd02f00a5e2a0c8347a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= Date: Tue, 5 Jan 2021 17:09:26 +0100 Subject: [PATCH 2/2] Use a shared cache between different VideoBackground instances MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before the cache for blurred video backgrounds was local to each VideoBackground instance. During a call different VideoBackground instances can be mounted and destroyed for the same participant (for example, when switching between speaker mode and grid view, or when a promoted speaker changes), so the cache was lost and the blurred background had to be recalculated every time that happened. Now the cache is global and shared between VideoBackground instances, so previously cached backgrounds can be used by new VideoBackground instances. The global cache is lazily initialized when needed and cleared each time a call ends. Signed-off-by: Daniel Calviño Sánchez --- .../CallView/shared/VideoBackground.vue | 14 ++++++++++--- src/store/callViewStore.js | 20 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/components/CallView/shared/VideoBackground.vue b/src/components/CallView/shared/VideoBackground.vue index 734a3113448..410a0167397 100644 --- a/src/components/CallView/shared/VideoBackground.vue +++ b/src/components/CallView/shared/VideoBackground.vue @@ -51,8 +51,6 @@ import { getBuilder } from '@nextcloud/browser-storage' import browserCheck from '../../../mixins/browserCheck' import blur from '../../../utils/imageBlurrer' -const LRU = require('lru-cache') - const browserStorage = getBuilder('nextcloud').persist().build() // note: this info is shared with the Avatar component @@ -99,7 +97,6 @@ export default { useCssBlurFilter: true, blur: 0, blurredBackgroundImage: null, - blurredBackgroundImageCache: new LRU({ max: 5 }), blurredBackgroundImageSource: null, pendingGenerateBlurredBackgroundImageCount: 0, isDestroyed: false, @@ -152,6 +149,17 @@ export default { return this.backgroundBlur }, + blurredBackgroundImageCache() { + if (!this.user) { + return null + } + + if (!this.$store.getters.getBlurredBackgroundImageCache(this.user)) { + this.$store.dispatch('initializeBlurredBackgroundImageCache', this.user) + } + + return this.$store.getters.getBlurredBackgroundImageCache(this.user) + }, }, watch: { diff --git a/src/store/callViewStore.js b/src/store/callViewStore.js index 2518c71094d..8353f6bd41e 100644 --- a/src/store/callViewStore.js +++ b/src/store/callViewStore.js @@ -26,6 +26,8 @@ import { CONVERSATION, } from '../constants' +const LRU = require('lru-cache') + const state = { isGrid: false, isStripeOpen: true, @@ -35,6 +37,7 @@ const state = { selectedVideoPeerId: null, videoBackgroundBlur: 1, participantRaisedHands: {}, + blurredBackgroundImageCache: {}, } const getters = { @@ -54,6 +57,7 @@ const getters = { return (width * height * state.videoBackgroundBlur) / 1000 }, isParticipantRaisedHand: (state) => (peerId) => !!state.participantRaisedHands[peerId], + getBlurredBackgroundImageCache: (state) => (videoBackgroundId) => state.blurredBackgroundImageCache[videoBackgroundId], } const mutations = { @@ -86,6 +90,16 @@ const mutations = { clearParticipantHandRaised(state) { state.participantRaisedHands = {} }, + initializeBlurredBackgroundImageCache(state, videoBackgroundId) { + if (state.blurredBackgroundImageCache[videoBackgroundId]) { + return + } + + Vue.set(state.blurredBackgroundImageCache, videoBackgroundId, new LRU({ max: 10 })) + }, + clearBlurredBackgroundImageCache(state) { + state.blurredBackgroundImageCache = {} + }, } const actions = { @@ -116,6 +130,8 @@ const actions = { leaveCall(context) { // clear raised hands as they were specific to the call context.commit('clearParticipantHandRaised') + + context.commit('clearBlurredBackgroundImageCache') }, /** @@ -197,6 +213,10 @@ const actions = { }) context.commit('presentationStarted', false) }, + + initializeBlurredBackgroundImageCache(context, videoBackgroundId) { + context.commit('initializeBlurredBackgroundImageCache', videoBackgroundId) + }, } export default { state, mutations, getters, actions }