diff --git a/frontend/src/components/lib/JVirtual/JVirtual.vue b/frontend/src/components/lib/JVirtual/JVirtual.vue index 7b400a22b52..f156f22d34f 100644 --- a/frontend/src/components/lib/JVirtual/JVirtual.vue +++ b/frontend/src/components/lib/JVirtual/JVirtual.vue @@ -40,7 +40,7 @@ import { type StyleValue, useTemplateRef } from 'vue'; -import { wrap } from 'comlink'; +import { releaseProxy, wrap } from 'comlink'; import { getBufferMeta, getContentSize, @@ -356,7 +356,10 @@ watch([bufferLength, resizeMeasurement, itemsLength, bufferOffset], (val, oldVal }); onBeforeUnmount(destroyEventListeners); -onBeforeUnmount(() => workerInstance.terminate()); +onBeforeUnmount(() => { + worker[releaseProxy](); + workerInstance.terminate(); +}); onBeforeUnmount(() => cache.clear()); diff --git a/frontend/src/plugins/workers/generic.worker.ts b/frontend/src/plugins/workers/generic.worker.ts index 7d7f4958159..cd12902d840 100644 --- a/frontend/src/plugins/workers/generic.worker.ts +++ b/frontend/src/plugins/workers/generic.worker.ts @@ -14,6 +14,7 @@ class GenericWorker { */ public shuffle(array: T[]) { for (let i = array.length - 1; i > 0; i--) { + // eslint-disable-next-line sonarjs/pseudo-random const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; diff --git a/frontend/src/plugins/workers/index.ts b/frontend/src/plugins/workers/index.ts index 46f868b40e2..480d49c765c 100644 --- a/frontend/src/plugins/workers/index.ts +++ b/frontend/src/plugins/workers/index.ts @@ -1,4 +1,5 @@ -import { wrap } from 'comlink'; +import { toRaw } from 'vue'; +import { releaseProxy, wrap } from 'comlink'; import type { IBlurhashDecoder } from './blurhash-decoder.worker'; import BlurhashDecoder from './blurhash-decoder.worker?worker'; import type { ICanvasDrawer } from './canvas-drawer.worker'; @@ -27,7 +28,34 @@ export const blurhashDecoder = wrap(new BlurhashDecoder()); export const canvasDrawer = wrap(new CanvasDrawer()); /** - * A worker for running any non-specific function that could be expensive and take some time to complete, - * blocking the main thread + * The generic worker is a class that must implement all functions that could take some time to complete + * and could potentially block the main thread. + * + * With his function, we instantiate the worker, run the function and then cleanup it immediately. + * + * The function is also prepared for receiving Vue reactive objects, + * converting them beforehand. */ -export const genericWorker = wrap(new GenericWorker()); +export function runGenericWorkerFunc< + T extends keyof IGenericWorker, + P extends Parameters = Parameters, + R extends ReturnType = ReturnType +>(key: T) { + return async (...args: P): Promise => { + const workerInstance = new GenericWorker(); + const genericWorker = wrap(workerInstance); + + try { + /** + * The `toRaw` function is used to convert a reactive object into a plain object + * before passing to the worker + */ + return await genericWorker[key](...args.map(arg => toRaw(arg))); + } catch { + return undefined; + } finally { + genericWorker[releaseProxy](); + workerInstance.terminate(); + } + }; +} diff --git a/frontend/src/store/playback-manager.ts b/frontend/src/store/playback-manager.ts index db4605f1d47..23404681e88 100644 --- a/frontend/src/store/playback-manager.ts +++ b/frontend/src/store/playback-manager.ts @@ -35,7 +35,7 @@ import playbackProfile from '@/utils/playback-profiles'; import { msToTicks } from '@/utils/time'; import { mediaControls, mediaElementRef } from '@/store'; import { CommonStore } from '@/store/super/common-store'; -import { shuffle } from '@/utils/data-manipulation'; +import { runGenericWorkerFunc } from '@/plugins/workers'; /** * == INTERFACES AND TYPES == @@ -614,7 +614,7 @@ class PlaybackManagerStore extends CommonStore { this._state.currentItemIndex = startFromIndex; if (startShuffled) { - void this.toggleShuffle(false); + await this.toggleShuffle(false); } this.currentTime = startFromTime; @@ -779,7 +779,7 @@ class PlaybackManagerStore extends CommonStore { this._state.originalQueue = []; this._state.isShuffling = false; } else { - const queue = await shuffle(this._state.queue); + const queue = await runGenericWorkerFunc('shuffle')(this._state.queue) ?? this._state.originalQueue; this._state.originalQueue = this._state.queue; diff --git a/frontend/src/store/player-element.ts b/frontend/src/store/player-element.ts index 43e1bae30da..06274d4647b 100644 --- a/frontend/src/store/player-element.ts +++ b/frontend/src/store/player-element.ts @@ -17,7 +17,7 @@ import { CommonStore } from '@/store/super/common-store'; import { router } from '@/plugins/router'; import { remote } from '@/plugins/remote'; import type { ParsedSubtitleTrack } from '@/plugins/workers/generic/subtitles'; -import { genericWorker } from '@/plugins/workers'; +import { runGenericWorkerFunc } from '@/plugins/workers'; import { subtitleSettings } from '@/store/client-settings/subtitle-settings'; /** @@ -202,7 +202,7 @@ class PlayerElementStore extends CommonStore { * otherwise show default subtitle track */ if (this._useCustomSubtitleTrack) { - const data = await genericWorker.parseVttFile(subtitleTrack.src); + const data = await runGenericWorkerFunc('parseVttFile')(subtitleTrack.src); this.currentExternalSubtitleTrack.parsed = data; } else { diff --git a/frontend/src/utils/data-manipulation.ts b/frontend/src/utils/data-manipulation.ts index 4fd8670eec6..121379e3822 100644 --- a/frontend/src/utils/data-manipulation.ts +++ b/frontend/src/utils/data-manipulation.ts @@ -1,6 +1,4 @@ import { defu } from 'defu'; -import { toRaw } from 'vue'; -import { genericWorker } from '@/plugins/workers'; /** * Merge 2 objects, excluding the keys from the destination that are not present in source @@ -70,13 +68,3 @@ export function pick(object: T, keys: (keyof T)[]): Partial return res; } - -/** - * Shuffles an array in a WebWorker using the Durstenfeld shuffle algorithm, an - * optimized version of Fisher-Yates shuffle. - * - * It's also prepared for the case when the array is reactive thorugh Vue's `ref` or `reactive`. - */ -export async function shuffle(array: T[]): Promise { - return await genericWorker.shuffle(toRaw(array)) as T[]; -}