1import { BroadcastableDiffuseElement, query } from "@common/element.js";
2import { signal, untracked } from "@common/signal.js";
3
4/**
5 * @import {Track} from "@definitions/types.d.ts"
6 * @import {ProxiedActions} from "@common/worker.d.ts"
7 * @import {InputElement} from "@components/input/types.d.ts"
8 * @import {OutputElement} from "@components/output/types.d.ts"
9 *
10 * @import {Actions} from "./types.d.ts"
11 */
12
13////////////////////////////////////////////
14// ELEMENT
15////////////////////////////////////////////
16
17/**
18 * Processes inputs into tracks whenever
19 * the already existing tracks are loaded
20 * from the assigned output element.
21 */
22class ProcessTracksOrchestrator extends BroadcastableDiffuseElement {
23 static NAME = "diffuse/orchestrator/process-tracks";
24 static WORKER_URL = "components/orchestrator/process-tracks/worker.js";
25
26 /** @type {ProxiedActions<Actions>} */
27 #proxy;
28
29 constructor() {
30 super();
31 this.#proxy = this.workerProxy({
32 forceNew: {
33 dependencies: { input: true },
34 },
35 });
36 }
37
38 // SIGNALS
39
40 #isProcessing = signal(false);
41
42 // STATE
43
44 isProcessing = this.#isProcessing.get;
45
46 // LIFECYCLE
47
48 /**
49 * @override
50 */
51 async connectedCallback() {
52 // Broadcast if needed
53 if (this.hasAttribute("group")) {
54 const actions = this.broadcast(this.nameWithGroup, {
55 process: { strategy: "leaderOnly", fn: this.process },
56 });
57
58 if (actions) this.process = actions.process;
59 }
60
61 // Super
62 super.connectedCallback();
63
64 /** @type {InputElement} */
65 const input = query(this, "input-selector");
66
67 /** @type {OutputElement<Track[]>} */
68 const output = query(this, "output-selector");
69
70 /** @type {import("@components/processor/metadata/element.js").CLASS} */
71 const metadataProcessor = query(this, "metadata-processor-selector");
72
73 // Assign to self
74 this.input = input;
75 this.output = output;
76 this.metadataProcessor = metadataProcessor;
77
78 // Wait until defined
79 await customElements.whenDefined(output.localName);
80
81 // Process whenever tracks are initially loaded
82 if (this.hasAttribute("process-when-ready")) {
83 this.effect(() => {
84 const state = output.tracks.state();
85 if (state !== "loaded") return;
86
87 const skip = /** @type {any} */ (import.meta).env
88 ?.DISABLE_AUTOMATIC_TRACKS_PROCESSING ?? false;
89 if (skip) return;
90
91 untracked(() => this.process());
92 });
93 }
94 }
95
96 // WORKERS
97
98 /**
99 * @override
100 */
101 dependencies() {
102 if (!this.input) throw new Error("Input element not defined yet");
103 if (!this.metadataProcessor) {
104 throw new Error("Metadata processor element not defined yet");
105 }
106
107 return {
108 input: this.input,
109 metadataProcessor: this.metadataProcessor,
110 };
111 }
112
113 // ACTIONS
114
115 async process() {
116 if (!this.output) return;
117 if (this.#isProcessing.value) return;
118
119 // Start
120 this.#isProcessing.value = true;
121 console.log("🪵 Processing initiated");
122
123 const cachedTracks = this.output.tracks.collection();
124 const result = await this.#proxy.process(cachedTracks);
125
126 // Save if collection changed
127 if (result) await this.output.tracks.save(result);
128
129 // Fin
130 console.log("🪵 Processing completed");
131 this.#isProcessing.value = false;
132 }
133}
134
135export default ProcessTracksOrchestrator;
136
137////////////////////////////////////////////
138// REGISTER
139////////////////////////////////////////////
140
141export const CLASS = ProcessTracksOrchestrator;
142export const NAME = "do-process-tracks";
143
144customElements.define(NAME, ProcessTracksOrchestrator);