1e62e45
commit 1e62e45
1 file changed
+192 -7
Showing 1 changed file+192-7
▼packages/worker/src/index.ts
+192-7
| @@ -1,42 +1,227 @@ | ||
1 | 1 | export interface WorkerMessage<T = unknown> { |
2 | - type: 'demux' | 'decode' | 'packet' | 'frame' | 'error'; | |
2 | + type: 'demux' | 'decode' | 'packet' | 'frame' | 'error' | 'ready' | 'stats'; | |
3 | 3 | payload: T; |
4 | + id?: string; | |
4 | 5 | } |
5 | 6 | |
7 | +export interface WorkerStatistics { | |
8 | + packetsSent: number; | |
9 | + framesReceived: number; | |
10 | + errors: number; | |
11 | + startTime: number; | |
12 | +} | |
13 | + | |
14 | +export type WorkerState = 'idle' | 'connected' | 'error'; | |
15 | + | |
6 | 16 | export class WorkerPipelineHost { |
7 | 17 | private worker?: Worker; |
18 | + private state: WorkerState = 'idle'; | |
19 | + private stats: WorkerStatistics = { | |
20 | + packetsSent: 0, | |
21 | + framesReceived: 0, | |
22 | + errors: 0, | |
23 | + startTime: Date.now() | |
24 | + }; | |
25 | + private handlers: Map<string, Set<(payload: unknown) => void>> = new Map(); | |
26 | + private errorHandler?: (error: { type: string; message: string; stack?: string }) => void; | |
27 | + private stateHandler?: (state: WorkerState) => void; | |
28 | + private workerScriptUrl?: string; | |
8 | 29 | |
9 | 30 | constructor(workerScriptUrl?: string) { |
31 | + this.workerScriptUrl = workerScriptUrl; | |
10 | 32 | if (typeof Worker !== 'undefined' && workerScriptUrl) { |
11 | - this.worker = new Worker(workerScriptUrl, { type: 'module' }); | |
33 | + this.connect(); | |
34 | + } | |
35 | + } | |
36 | + | |
37 | + private connect(): void { | |
38 | + if (!this.workerScriptUrl) return; | |
39 | + | |
40 | + try { | |
41 | + this.worker = new Worker(this.workerScriptUrl, { type: 'module' }); | |
42 | + | |
43 | + this.worker.onmessage = (event: MessageEvent<WorkerMessage>) => { | |
44 | + const { type, payload, id } = event.data; | |
45 | + | |
46 | + if (type === 'frame') { | |
47 | + this.stats.framesReceived++; | |
48 | + } else if (type === 'error') { | |
49 | + this.stats.errors++; | |
50 | + if (this.errorHandler) { | |
51 | + this.errorHandler(payload as { type: string; message: string; stack?: string }); | |
52 | + } | |
53 | + } else if (type === 'ready') { | |
54 | + this.state = 'connected'; | |
55 | + if (this.stateHandler) { | |
56 | + this.stateHandler(this.state); | |
57 | + } | |
58 | + } else if (type === 'stats') { | |
59 | + // Handle stats updates | |
60 | + } | |
61 | + | |
62 | + // Call registered handlers | |
63 | + const typeHandlers = this.handlers.get(type); | |
64 | + if (typeHandlers) { | |
65 | + for (const handler of typeHandlers) { | |
66 | + handler(payload); | |
67 | + } | |
68 | + } | |
69 | + }; | |
70 | + | |
71 | + this.worker.onerror = (event) => { | |
72 | + this.state = 'error'; | |
73 | + this.stats.errors++; | |
74 | + | |
75 | + if (this.errorHandler) { | |
76 | + this.errorHandler({ | |
77 | + type: 'worker-error', | |
78 | + message: event.message || 'Unknown worker error', | |
79 | + stack: undefined | |
80 | + }); | |
81 | + } | |
82 | + | |
83 | + if (this.stateHandler) { | |
84 | + this.stateHandler(this.state); | |
85 | + } | |
86 | + }; | |
87 | + | |
88 | + this.state = 'connected'; | |
89 | + } catch (error) { | |
90 | + this.state = 'error'; | |
91 | + if (this.stateHandler) { | |
92 | + this.stateHandler(this.state); | |
93 | + } | |
12 | 94 | } |
13 | 95 | } |
14 | 96 | |
15 | 97 | public sendPacket(packetData: Uint8Array, transfer: boolean = true): void { |
16 | - if (!this.worker) return; | |
98 | + if (!this.worker || this.state !== 'connected') return; | |
99 | + | |
17 | 100 | const message: WorkerMessage<{ data: Uint8Array }> = { |
18 | 101 | type: 'packet', |
19 | 102 | payload: { data: packetData } |
20 | 103 | }; |
104 | + | |
21 | 105 | if (transfer && packetData.buffer instanceof ArrayBuffer) { |
22 | 106 | this.worker.postMessage(message, [packetData.buffer]); |
23 | 107 | } else { |
24 | 108 | this.worker.postMessage(message); |
25 | 109 | } |
110 | + | |
111 | + this.stats.packetsSent++; | |
112 | + } | |
113 | + | |
114 | + public sendDemuxCommand(sourceUrl: string, format?: string): void { | |
115 | + if (!this.worker || this.state !== 'connected') return; | |
116 | + | |
117 | + const message: WorkerMessage<{ sourceUrl: string; format?: string }> = { | |
118 | + type: 'demux', | |
119 | + payload: { sourceUrl, format } | |
120 | + }; | |
121 | + | |
122 | + this.worker.postMessage(message); | |
123 | + } | |
124 | + | |
125 | + public sendDecodeCommand(trackId: string, codec: string): void { | |
126 | + if (!this.worker || this.state !== 'connected') return; | |
127 | + | |
128 | + const message: WorkerMessage<{ trackId: string; codec: string }> = { | |
129 | + type: 'decode', | |
130 | + payload: { trackId, codec } | |
131 | + }; | |
132 | + | |
133 | + this.worker.postMessage(message); | |
26 | 134 | } |
27 | 135 | |
28 | 136 | public onFrame(handler: (frame: unknown) => void): void { |
29 | - if (!this.worker) return; | |
30 | - this.worker.onmessage = (event: MessageEvent<WorkerMessage>) => { | |
31 | - if (event.data.type === 'frame') { | |
32 | - handler(event.data.payload); | |
137 | + this.on('frame', handler); | |
138 | + } | |
139 | + | |
140 | + public on(type: string, handler: (payload: unknown) => void): () => void { | |
141 | + if (!this.handlers.has(type)) { | |
142 | + this.handlers.set(type, new Set()); | |
143 | + } | |
144 | + this.handlers.get(type)!.add(handler); | |
145 | + | |
146 | + return () => { | |
147 | + const handlers = this.handlers.get(type); | |
148 | + if (handlers) { | |
149 | + handlers.delete(handler); | |
33 | 150 | } |
34 | 151 | }; |
35 | 152 | } |
36 | 153 | |
154 | + public onError(handler: (error: { type: string; message: string; stack?: string }) => void): void { | |
155 | + this.errorHandler = handler; | |
156 | + } | |
157 | + | |
158 | + public onStateChange(handler: (state: WorkerState) => void): void { | |
159 | + this.stateHandler = handler; | |
160 | + } | |
161 | + | |
162 | + public getState(): WorkerState { | |
163 | + return this.state; | |
164 | + } | |
165 | + | |
166 | + public getStatistics(): WorkerStatistics { | |
167 | + return { ...this.stats }; | |
168 | + } | |
169 | + | |
170 | + public reconnect(): void { | |
171 | + this.terminate(); | |
172 | + this.state = 'idle'; | |
173 | + this.connect(); | |
174 | + } | |
175 | + | |
37 | 176 | public terminate(): void { |
38 | 177 | if (this.worker) { |
39 | 178 | this.worker.terminate(); |
179 | + this.worker = undefined; | |
180 | + this.state = 'idle'; | |
40 | 181 | } |
41 | 182 | } |
42 | 183 | } |
184 | + | |
185 | +// Worker-side consumer code | |
186 | +export function createWorkerHandler(): (event: MessageEvent<WorkerMessage>) => void { | |
187 | + return (event: MessageEvent<WorkerMessage>) => { | |
188 | + const { type, payload } = event.data; | |
189 | + | |
190 | + switch (type) { | |
191 | + case 'demux': | |
192 | + handleDemux(payload as { sourceUrl: string; format?: string }); | |
193 | + break; | |
194 | + case 'decode': | |
195 | + handleDecode(payload as { trackId: string; codec: string }); | |
196 | + break; | |
197 | + case 'packet': | |
198 | + handlePacket(payload as { data: Uint8Array }); | |
199 | + break; | |
200 | + default: | |
201 | + console.warn(`Unknown message type: ${type}`); | |
202 | + } | |
203 | + }; | |
204 | +} | |
205 | + | |
206 | +function handleDemux(payload: { sourceUrl: string; format?: string }): void { | |
207 | + // TODO: Implement demux handling | |
208 | + // This would use ContainerRegistry to demux the source | |
209 | + console.log('Demux command received:', payload); | |
210 | +} | |
211 | + | |
212 | +function handleDecode(payload: { trackId: string; codec: string }): void { | |
213 | + // TODO: Implement decode handling | |
214 | + // This would use CodecNegotiator and WebCodecsVideoDecoder | |
215 | + console.log('Decode command received:', payload); | |
216 | +} | |
217 | + | |
218 | +function handlePacket(payload: { data: Uint8Array }): void { | |
219 | + // TODO: Implement packet handling | |
220 | + // This would process the packet data | |
221 | + console.log('Packet received:', payload.data.length, 'bytes'); | |
222 | +} | |
223 | + | |
224 | +// Setup worker self message handler | |
225 | +if (typeof self !== 'undefined' && typeof (self as any).onmessage !== 'undefined') { | |
226 | + (self as any).onmessage = createWorkerHandler(); | |
227 | +} | |
43 | 228 | |