summaryrefslogtreecommitdiff
path: root/apps/web/src/core/managers/media-manager.ts
diff options
context:
space:
mode:
authorMaze <167211895+mazeincoding@users.noreply.github.com>2026-01-31 00:20:04 +0100
committerGitHub <noreply@github.com>2026-01-31 00:20:04 +0100
commit7bf0984698e43b9aa2dd879f65951318ed410390 (patch)
treeccf2cd590a47d550152afc07129cb55c71a9ec35 /apps/web/src/core/managers/media-manager.ts
parent0173db99448d1c4c6415dcf15272e17551b48b04 (diff)
downloadopencut-7bf0984698e43b9aa2dd879f65951318ed410390.tar.gz
codebase overhaul (#697)
Diffstat (limited to 'apps/web/src/core/managers/media-manager.ts')
-rw-r--r--apps/web/src/core/managers/media-manager.ts162
1 files changed, 162 insertions, 0 deletions
diff --git a/apps/web/src/core/managers/media-manager.ts b/apps/web/src/core/managers/media-manager.ts
new file mode 100644
index 00000000..7b9db0d5
--- /dev/null
+++ b/apps/web/src/core/managers/media-manager.ts
@@ -0,0 +1,162 @@
+import type { EditorCore } from "@/core";
+import type { MediaAsset } from "@/types/assets";
+import { storageService } from "@/services/storage/storage-service";
+import { generateUUID } from "@/utils/id";
+import { videoCache } from "@/services/media/video-cache";
+import { hasMediaId } from "@/lib/timeline/element-utils";
+
+export class MediaManager {
+ private assets: MediaAsset[] = [];
+ private isLoading = false;
+ private listeners = new Set<() => void>();
+
+ constructor(private editor: EditorCore) {}
+
+ async addMediaAsset({
+ projectId,
+ asset,
+ }: {
+ projectId: string;
+ asset: Omit<MediaAsset, "id">;
+ }): Promise<void> {
+ const newAsset: MediaAsset = {
+ ...asset,
+ id: generateUUID(),
+ };
+
+ this.assets = [...this.assets, newAsset];
+ this.notify();
+
+ try {
+ await storageService.saveMediaAsset({ projectId, mediaAsset: newAsset });
+ } catch (error) {
+ console.error("Failed to save media asset:", error);
+ this.assets = this.assets.filter((asset) => asset.id !== newAsset.id);
+ this.notify();
+ }
+ }
+
+ async removeMediaAsset({
+ projectId,
+ id,
+ }: {
+ projectId: string;
+ id: string;
+ }): Promise<void> {
+ const asset = this.assets.find((asset) => asset.id === id);
+
+ videoCache.clearVideo({ mediaId: id });
+
+ if (asset?.url) {
+ URL.revokeObjectURL(asset.url);
+ if (asset.thumbnailUrl) {
+ URL.revokeObjectURL(asset.thumbnailUrl);
+ }
+ }
+
+ this.assets = this.assets.filter((asset) => asset.id !== id);
+ this.notify();
+
+ const tracks = this.editor.timeline.getTracks();
+ const elementsToRemove: Array<{ trackId: string; elementId: string }> = [];
+
+ for (const track of tracks) {
+ for (const element of track.elements) {
+ if (hasMediaId(element) && element.mediaId === id) {
+ elementsToRemove.push({ trackId: track.id, elementId: element.id });
+ }
+ }
+ }
+
+ if (elementsToRemove.length > 0) {
+ this.editor.timeline.deleteElements({ elements: elementsToRemove });
+ }
+
+ try {
+ await storageService.deleteMediaAsset({ projectId, id });
+ } catch (error) {
+ console.error("Failed to delete media asset:", error);
+ }
+ }
+
+ async loadProjectMedia({ projectId }: { projectId: string }): Promise<void> {
+ this.isLoading = true;
+ this.notify();
+
+ try {
+ const mediaAssets = await storageService.loadAllMediaAssets({
+ projectId,
+ });
+ this.assets = mediaAssets;
+ this.notify();
+ } catch (error) {
+ console.error("Failed to load media assets:", error);
+ } finally {
+ this.isLoading = false;
+ this.notify();
+ }
+ }
+
+ async clearProjectMedia({ projectId }: { projectId: string }): Promise<void> {
+ this.assets.forEach((asset) => {
+ if (asset.url) {
+ URL.revokeObjectURL(asset.url);
+ }
+ if (asset.thumbnailUrl) {
+ URL.revokeObjectURL(asset.thumbnailUrl);
+ }
+ });
+
+ const mediaIds = this.assets.map((asset) => asset.id);
+ this.assets = [];
+ this.notify();
+
+ try {
+ await Promise.all(
+ mediaIds.map((id) =>
+ storageService.deleteMediaAsset({ projectId, id }),
+ ),
+ );
+ } catch (error) {
+ console.error("Failed to clear media assets from storage:", error);
+ }
+ }
+
+ clearAllAssets(): void {
+ videoCache.clearAll();
+
+ this.assets.forEach((asset) => {
+ if (asset.url) {
+ URL.revokeObjectURL(asset.url);
+ }
+ if (asset.thumbnailUrl) {
+ URL.revokeObjectURL(asset.thumbnailUrl);
+ }
+ });
+
+ this.assets = [];
+ this.notify();
+ }
+
+ getAssets(): MediaAsset[] {
+ return this.assets;
+ }
+
+ setAssets({ assets }: { assets: MediaAsset[] }): void {
+ this.assets = assets;
+ this.notify();
+ }
+
+ isLoadingMedia(): boolean {
+ return this.isLoading;
+ }
+
+ subscribe(listener: () => void): () => void {
+ this.listeners.add(listener);
+ return () => this.listeners.delete(listener);
+ }
+
+ private notify(): void {
+ this.listeners.forEach((fn) => fn());
+ }
+}