@design.estate/dees-wcctools 2.0.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/npmextra.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "gitzone": {
2
+ "@git.zone/cli": {
3
3
  "projectType": "wcc",
4
4
  "module": {
5
5
  "githost": "code.foss.global",
@@ -21,13 +21,19 @@
21
21
  "element testing",
22
22
  "page development"
23
23
  ]
24
+ },
25
+ "release": {
26
+ "registries": [
27
+ "https://verdaccio.lossless.digital",
28
+ "https://registry.npmjs.org"
29
+ ],
30
+ "accessLevel": "public"
24
31
  }
25
32
  },
26
- "npmci": {
27
- "npmGlobalTools": [],
28
- "npmAccessLevel": "public"
29
- },
30
- "tsdoc": {
33
+ "@git.zone/tsdoc": {
31
34
  "legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
35
+ },
36
+ "@ship.zone/szci": {
37
+ "npmGlobalTools": []
32
38
  }
33
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@design.estate/dees-wcctools",
3
- "version": "2.0.1",
3
+ "version": "3.0.0",
4
4
  "private": false,
5
5
  "description": "A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.",
6
6
  "exports": {
@@ -8,6 +8,12 @@
8
8
  "./demotools": "./dist_ts_demotools/index.js"
9
9
  },
10
10
  "type": "module",
11
+ "scripts": {
12
+ "test": "(npm run build)",
13
+ "build": "(tsbuild tsfolders --allowimplicitany && tsbundle element)",
14
+ "watch": "tswatch element",
15
+ "buildDocs": "tsdoc"
16
+ },
11
17
  "author": "Lossless GmbH",
12
18
  "license": "UNLICENSED",
13
19
  "dependencies": {
@@ -18,13 +24,13 @@
18
24
  },
19
25
  "devDependencies": {
20
26
  "@api.global/typedserver": "^7.11.1",
21
- "@git.zone/tsbuild": "^3.1.2",
27
+ "@git.zone/tsbuild": "^4.0.2",
22
28
  "@git.zone/tsbundle": "^2.6.3",
23
- "@git.zone/tsrun": "^2.0.0",
29
+ "@git.zone/tsrun": "^2.0.1",
24
30
  "@git.zone/tstest": "^3.1.3",
25
- "@git.zone/tswatch": "^2.3.12",
31
+ "@git.zone/tswatch": "^2.3.13",
26
32
  "@push.rocks/projectinfo": "^5.0.2",
27
- "@types/node": "^25.0.0"
33
+ "@types/node": "^25.0.3"
28
34
  },
29
35
  "files": [
30
36
  "ts/**/*",
@@ -53,10 +59,5 @@
53
59
  "element testing",
54
60
  "page development"
55
61
  ],
56
- "scripts": {
57
- "test": "(npm run build)",
58
- "build": "(tsbuild tsfolders --allowimplicitany && tsbundle element)",
59
- "watch": "tswatch element",
60
- "buildDocs": "tsdoc"
61
- }
62
- }
62
+ "packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
63
+ }
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@design.estate/dees-wcctools',
6
- version: '2.0.1',
6
+ version: '3.0.0',
7
7
  description: 'A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.'
8
8
  }
@@ -34,8 +34,10 @@ export class WccDashboard extends DeesElement {
34
34
  @property()
35
35
  accessor selectedTheme: TTheme = 'dark';
36
36
 
37
- @property()
38
- accessor isFullscreen: boolean = false;
37
+ // Derived from selectedViewport - no need for separate property
38
+ public get isNative(): boolean {
39
+ return this.selectedViewport === 'native';
40
+ }
39
41
 
40
42
  @property()
41
43
  accessor pages: Record<string, TTemplateFactory> = {};
@@ -84,7 +86,7 @@ export class WccDashboard extends DeesElement {
84
86
  <wcc-sidebar
85
87
  .dashboardRef=${this}
86
88
  .selectedItem=${this.selectedItem}
87
- .isFullscreen=${this.isFullscreen}
89
+ .isNative=${this.isNative}
88
90
  @selectedType=${(eventArg) => {
89
91
  this.selectedType = eventArg.detail;
90
92
  }}
@@ -101,7 +103,7 @@ export class WccDashboard extends DeesElement {
101
103
  .selectedItem=${this.selectedItem}
102
104
  .selectedViewport=${this.selectedViewport}
103
105
  .selectedTheme=${this.selectedTheme}
104
- .isFullscreen=${this.isFullscreen}
106
+ .isNative=${this.isNative}
105
107
  @selectedViewport=${(eventArg) => {
106
108
  this.selectedViewport = eventArg.detail;
107
109
  this.scheduleUpdate();
@@ -116,11 +118,11 @@ export class WccDashboard extends DeesElement {
116
118
  frame.requestUpdate();
117
119
  }
118
120
  }}
119
- @toggleFullscreen=${() => {
120
- this.toggleFullscreen();
121
+ @toggleNative=${() => {
122
+ this.toggleNative();
121
123
  }}
122
124
  ></wcc-properties>
123
- <wcc-frame id="wccFrame" viewport=${this.selectedViewport} .isFullscreen=${this.isFullscreen}>
125
+ <wcc-frame id="wccFrame" viewport=${this.selectedViewport} .isNative=${this.isNative}>
124
126
  </wcc-frame>
125
127
  `;
126
128
  }
@@ -135,17 +137,20 @@ export class WccDashboard extends DeesElement {
135
137
  }
136
138
  }
137
139
 
138
- public toggleFullscreen() {
139
- this.isFullscreen = !this.isFullscreen;
140
+ public toggleNative() {
141
+ // Toggle between 'native' and 'desktop' viewports
142
+ this.selectedViewport = this.selectedViewport === 'native' ? 'desktop' : 'native';
143
+ this.buildUrl();
140
144
  }
141
145
 
142
146
  public async firstUpdated() {
143
147
  this.domtools = await plugins.deesDomtools.DomTools.setupDomTools();
144
148
 
145
- // Add ESC key handler for fullscreen mode
149
+ // Add ESC key handler for native mode
146
150
  document.addEventListener('keydown', (event) => {
147
- if (event.key === 'Escape' && this.isFullscreen) {
148
- this.isFullscreen = false;
151
+ if (event.key === 'Escape' && this.isNative) {
152
+ this.selectedViewport = 'desktop';
153
+ this.buildUrl();
149
154
  }
150
155
  });
151
156
 
@@ -17,7 +17,7 @@ export class WccFrame extends DeesElement {
17
17
  accessor advancedEditorOpen: boolean = false;
18
18
 
19
19
  @property({ type: Boolean })
20
- accessor isFullscreen: boolean = false;
20
+ accessor isNative: boolean = false;
21
21
 
22
22
  public static styles = [
23
23
  css`
@@ -46,7 +46,7 @@ export class WccFrame extends DeesElement {
46
46
  return html`
47
47
  <style>
48
48
  :host {
49
- ${this.isFullscreen ? `
49
+ ${this.isNative ? `
50
50
  border: none !important;
51
51
  left: 0px !important;
52
52
  right: 0px !important;
@@ -58,7 +58,7 @@ export class WccFrame extends DeesElement {
58
58
  left: 200px;
59
59
  `}
60
60
  transition: all 0.3s ease;
61
- ${this.isFullscreen ? 'padding: 0px;' : (() => {
61
+ ${this.isNative ? 'padding: 0px;' : (() => {
62
62
  switch (this.viewport) {
63
63
  case 'desktop':
64
64
  return `
@@ -87,7 +87,7 @@ export class WccFrame extends DeesElement {
87
87
  }
88
88
 
89
89
  .viewport {
90
- ${!this.isFullscreen && this.viewport !== 'desktop'
90
+ ${!this.isNative && this.viewport !== 'desktop'
91
91
  ? html` border-right: 1px dotted #444; border-left: 1px dotted #444; `
92
92
  : html``
93
93
  }
@@ -35,7 +35,7 @@ export class WccProperties extends DeesElement {
35
35
  accessor warning: string = null;
36
36
 
37
37
  @property()
38
- accessor isFullscreen: boolean = false;
38
+ accessor isNative: boolean = false;
39
39
 
40
40
  @state()
41
41
  accessor propertyContent: TemplateResult[] = [];
@@ -96,11 +96,11 @@ export class WccProperties extends DeesElement {
96
96
  overflow: hidden;
97
97
  background: var(--background);
98
98
  color: var(--foreground);
99
- display: ${this.isFullscreen ? 'none' : 'block'};
99
+ display: ${this.isNative ? 'none' : 'block'};
100
100
  }
101
101
  .grid {
102
102
  display: grid;
103
- grid-template-columns: 1fr 150px 300px 70px 70px;
103
+ grid-template-columns: 1fr 150px 350px 70px;
104
104
  height: 100%;
105
105
  }
106
106
  .properties {
@@ -214,6 +214,11 @@ export class WccProperties extends DeesElement {
214
214
  grid-template-columns: repeat(4, 1fr);
215
215
  flex: 1;
216
216
  }
217
+ .selectorButtons5 {
218
+ display: grid;
219
+ grid-template-columns: repeat(5, 1fr);
220
+ flex: 1;
221
+ }
217
222
  .button {
218
223
  display: flex;
219
224
  flex-direction: column;
@@ -623,7 +628,7 @@ export class WccProperties extends DeesElement {
623
628
  </div>
624
629
  <div class="viewportSelector">
625
630
  <div class="panelheading">Viewport</div>
626
- <div class="selectorButtons4">
631
+ <div class="selectorButtons5">
627
632
  <div
628
633
  class="button ${this.selectedViewport === 'phone' ? 'selected' : null}"
629
634
  @click=${() => {
@@ -649,23 +654,23 @@ export class WccProperties extends DeesElement {
649
654
  Tablet<i class="material-symbols-outlined">tablet</i>
650
655
  </div>
651
656
  <div
652
- class="button ${this.selectedViewport === 'desktop' ||
653
- this.selectedViewport === 'native'
654
- ? 'selected'
655
- : null}"
657
+ class="button ${this.selectedViewport === 'desktop' ? 'selected' : null}"
656
658
  @click=${() => {
657
- this.selectViewport('native');
659
+ this.selectViewport('desktop');
658
660
  }}
659
661
  >
660
662
  Desktop<i class="material-symbols-outlined">desktop_windows</i>
661
663
  </div>
664
+ <div
665
+ class="button ${this.selectedViewport === 'native' ? 'selected' : null}"
666
+ @click=${() => {
667
+ this.selectViewport('native');
668
+ }}
669
+ >
670
+ Native<i class="material-symbols-outlined">fullscreen</i>
671
+ </div>
662
672
  </div>
663
673
  </div>
664
- <div class="docs" @click=${() => this.toggleFullscreen()}>
665
- <i class="material-symbols-outlined" style="font-size: 20px;">
666
- ${this.isFullscreen ? 'fullscreen_exit' : 'fullscreen'}
667
- </i>
668
- </div>
669
674
  <!-- Recording Button -->
670
675
  <wcc-record-button
671
676
  .state=${this.isRecording ? 'recording' : 'idle'}
@@ -1016,9 +1021,9 @@ export class WccProperties extends DeesElement {
1016
1021
  );
1017
1022
  }
1018
1023
 
1019
- private toggleFullscreen() {
1024
+ private toggleNative() {
1020
1025
  this.dispatchEvent(
1021
- new CustomEvent('toggleFullscreen', {
1026
+ new CustomEvent('toggleNative', {
1022
1027
  bubbles: true
1023
1028
  })
1024
1029
  );
@@ -18,7 +18,7 @@ export class WccSidebar extends DeesElement {
18
18
  accessor dashboardRef: WccDashboard;
19
19
 
20
20
  @property()
21
- accessor isFullscreen: boolean = false;
21
+ accessor isNative: boolean = false;
22
22
 
23
23
  // Track which elements are expanded (for multi-demo elements)
24
24
  @state()
@@ -45,7 +45,7 @@ export class WccSidebar extends DeesElement {
45
45
  --ring: #3b82f6;
46
46
  --radius: 4px;
47
47
 
48
- display: ${this.isFullscreen ? 'none' : 'block'};
48
+ display: ${this.isNative ? 'none' : 'block'};
49
49
  border-right: 1px solid rgba(255, 255, 255, 0.08);
50
50
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
51
51
  font-size: 14px;
@@ -1,42 +0,0 @@
1
- /**
2
- * FFmpegService - Handles client-side video format conversion using FFmpeg.wasm
3
- * Uses a custom worker implementation to bypass COEP/CORS issues with the standard library
4
- */
5
- export interface IConversionProgress {
6
- stage: 'loading' | 'converting' | 'finalizing';
7
- progress: number;
8
- message: string;
9
- }
10
- export interface IConversionOptions {
11
- inputBlob: Blob;
12
- outputFormat: 'mp4' | 'webm';
13
- onProgress?: (progress: IConversionProgress) => void;
14
- }
15
- export declare class FFmpegService {
16
- private worker;
17
- private core;
18
- private loadPromise;
19
- private messageId;
20
- private pendingMessages;
21
- private onLog?;
22
- private onProgress?;
23
- /**
24
- * Lazy load FFmpeg.wasm from CDN using custom worker
25
- */
26
- ensureLoaded(onProgress?: (progress: IConversionProgress) => void): Promise<void>;
27
- private loadFFmpeg;
28
- private sendMessage;
29
- /**
30
- * Convert WebM blob to MP4
31
- */
32
- convertToMp4(options: IConversionOptions): Promise<Blob>;
33
- /**
34
- * Check if FFmpeg is currently loaded
35
- */
36
- get isLoaded(): boolean;
37
- /**
38
- * Terminate FFmpeg worker to free resources
39
- */
40
- terminate(): Promise<void>;
41
- }
42
- export declare function getFFmpegService(): FFmpegService;
@@ -1,276 +0,0 @@
1
- /**
2
- * FFmpegService - Handles client-side video format conversion using FFmpeg.wasm
3
- * Uses a custom worker implementation to bypass COEP/CORS issues with the standard library
4
- */
5
- export class FFmpegService {
6
- worker = null;
7
- core = null;
8
- loadPromise = null;
9
- messageId = 0;
10
- pendingMessages = new Map();
11
- onLog;
12
- onProgress;
13
- /**
14
- * Lazy load FFmpeg.wasm from CDN using custom worker
15
- */
16
- async ensureLoaded(onProgress) {
17
- if (this.worker && this.core)
18
- return;
19
- if (this.loadPromise) {
20
- await this.loadPromise;
21
- return;
22
- }
23
- this.loadPromise = this.loadFFmpeg(onProgress);
24
- await this.loadPromise;
25
- }
26
- async loadFFmpeg(onProgress) {
27
- console.log('[FFmpeg] Starting FFmpeg load with custom worker...');
28
- onProgress?.({
29
- stage: 'loading',
30
- progress: 0,
31
- message: 'Loading FFmpeg library...'
32
- });
33
- // Import toBlobURL utility
34
- const { toBlobURL } = await import('@ffmpeg/util');
35
- // Use jsdelivr CDN (has proper CORS/CORP headers)
36
- const coreBaseURL = 'https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.9/dist/umd';
37
- onProgress?.({
38
- stage: 'loading',
39
- progress: 10,
40
- message: 'Downloading FFmpeg core (~31MB)...'
41
- });
42
- console.log('[FFmpeg] Creating blob URLs...');
43
- const coreURL = await toBlobURL(`${coreBaseURL}/ffmpeg-core.js`, 'text/javascript');
44
- const wasmURL = await toBlobURL(`${coreBaseURL}/ffmpeg-core.wasm`, 'application/wasm');
45
- console.log('[FFmpeg] Blob URLs created');
46
- onProgress?.({
47
- stage: 'loading',
48
- progress: 50,
49
- message: 'Initializing FFmpeg...'
50
- });
51
- // Create custom worker code that bypasses @ffmpeg/ffmpeg wrapper issues
52
- const workerCode = `
53
- let ffmpeg = null;
54
-
55
- self.onmessage = async (e) => {
56
- const { id, type, data } = e.data;
57
-
58
- try {
59
- switch (type) {
60
- case 'LOAD': {
61
- const { coreURL, wasmURL } = data;
62
- console.log('[FFmpeg Worker] Loading core...');
63
- importScripts(coreURL);
64
-
65
- console.log('[FFmpeg Worker] Initializing with WASM...');
66
- ffmpeg = await self.createFFmpegCore({
67
- mainScriptUrlOrBlob: coreURL + '#' + btoa(JSON.stringify({ wasmURL }))
68
- });
69
-
70
- // Set up logging
71
- ffmpeg.setLogger((log) => {
72
- console.log('[FFmpeg Worker Log]', log);
73
- self.postMessage({ type: 'LOG', data: log });
74
- });
75
-
76
- // Set up progress - progress is an object { ratio: 0-1, time: seconds }
77
- ffmpeg.setProgress((progress) => {
78
- const ratio = typeof progress === 'number' ? progress : (progress.ratio || 0);
79
- console.log('[FFmpeg Worker Progress]', ratio);
80
- self.postMessage({ type: 'PROGRESS', data: ratio });
81
- });
82
-
83
- console.log('[FFmpeg Worker] Core initialized successfully');
84
- console.log('[FFmpeg Worker] Available methods:', Object.keys(ffmpeg).join(', '));
85
- self.postMessage({ id, type: 'LOAD', data: true });
86
- break;
87
- }
88
-
89
- case 'EXEC': {
90
- const { args, timeout = -1 } = data;
91
- console.log('[FFmpeg Worker] Executing:', args.join(' '));
92
-
93
- try {
94
- ffmpeg.setTimeout(timeout);
95
- ffmpeg.exec(...args);
96
- const ret = ffmpeg.ret;
97
- console.log('[FFmpeg Worker] Exec returned:', ret);
98
- ffmpeg.reset();
99
- self.postMessage({ id, type: 'EXEC', data: ret });
100
- } catch (execErr) {
101
- console.error('[FFmpeg Worker] Exec error:', execErr);
102
- throw execErr;
103
- }
104
- break;
105
- }
106
-
107
- case 'WRITE_FILE': {
108
- const { path, fileData } = data;
109
- console.log('[FFmpeg Worker] Writing file:', path, 'size:', fileData.length);
110
- ffmpeg.FS.writeFile(path, fileData);
111
- self.postMessage({ id, type: 'WRITE_FILE', data: true });
112
- break;
113
- }
114
-
115
- case 'READ_FILE': {
116
- const { path } = data;
117
- console.log('[FFmpeg Worker] Reading file:', path);
118
- const fileData = ffmpeg.FS.readFile(path);
119
- console.log('[FFmpeg Worker] Read file size:', fileData.length);
120
- self.postMessage({ id, type: 'READ_FILE', data: fileData }, [fileData.buffer]);
121
- break;
122
- }
123
-
124
- case 'DELETE_FILE': {
125
- const { path } = data;
126
- console.log('[FFmpeg Worker] Deleting file:', path);
127
- ffmpeg.FS.unlink(path);
128
- self.postMessage({ id, type: 'DELETE_FILE', data: true });
129
- break;
130
- }
131
-
132
- default:
133
- throw new Error('Unknown message type: ' + type);
134
- }
135
- } catch (err) {
136
- console.error('[FFmpeg Worker] Error:', err);
137
- self.postMessage({ id, type: 'ERROR', data: err.message || String(err) });
138
- }
139
- };
140
- `;
141
- // Create worker from blob
142
- const workerBlob = new Blob([workerCode], { type: 'text/javascript' });
143
- const workerURL = URL.createObjectURL(workerBlob);
144
- this.worker = new Worker(workerURL);
145
- // Set up message handler
146
- this.worker.onmessage = (e) => {
147
- const { id, type, data } = e.data;
148
- if (type === 'LOG') {
149
- console.log('[FFmpeg Log]', data);
150
- this.onLog?.(data.message || data);
151
- return;
152
- }
153
- if (type === 'PROGRESS') {
154
- this.onProgress?.(data);
155
- return;
156
- }
157
- const pending = this.pendingMessages.get(id);
158
- if (pending) {
159
- this.pendingMessages.delete(id);
160
- if (type === 'ERROR') {
161
- pending.reject(new Error(data));
162
- }
163
- else {
164
- pending.resolve(data);
165
- }
166
- }
167
- };
168
- this.worker.onerror = (e) => {
169
- console.error('[FFmpeg] Worker error:', e);
170
- };
171
- // Initialize FFmpeg in worker
172
- console.log('[FFmpeg] Initializing worker...');
173
- await this.sendMessage('LOAD', { coreURL, wasmURL });
174
- this.core = true; // Mark as loaded
175
- console.log('[FFmpeg] Worker initialized successfully');
176
- onProgress?.({
177
- stage: 'loading',
178
- progress: 100,
179
- message: 'FFmpeg loaded successfully'
180
- });
181
- }
182
- sendMessage(type, data) {
183
- return new Promise((resolve, reject) => {
184
- const id = ++this.messageId;
185
- this.pendingMessages.set(id, { resolve, reject });
186
- this.worker.postMessage({ id, type, data });
187
- });
188
- }
189
- /**
190
- * Convert WebM blob to MP4
191
- */
192
- async convertToMp4(options) {
193
- const { inputBlob, onProgress } = options;
194
- // Check file size limit (2GB)
195
- const MAX_FILE_SIZE = 2 * 1024 * 1024 * 1024;
196
- if (inputBlob.size > MAX_FILE_SIZE) {
197
- throw new Error('File size exceeds 2GB limit for conversion');
198
- }
199
- // Set up progress callback
200
- this.onProgress = (progress) => {
201
- const percent = Math.round((progress || 0) * 100);
202
- console.log('[FFmpeg] Conversion progress:', percent + '%');
203
- onProgress?.({
204
- stage: 'converting',
205
- progress: percent,
206
- message: `Converting video... ${percent}%`
207
- });
208
- };
209
- await this.ensureLoaded(onProgress);
210
- onProgress?.({
211
- stage: 'converting',
212
- progress: 0,
213
- message: 'Preparing video for conversion...'
214
- });
215
- // Read input blob as Uint8Array
216
- const inputData = new Uint8Array(await inputBlob.arrayBuffer());
217
- // Write input file to virtual filesystem
218
- await this.sendMessage('WRITE_FILE', { path: 'input.webm', fileData: inputData });
219
- // Execute conversion with optimized settings for web playback
220
- await this.sendMessage('EXEC', {
221
- args: [
222
- '-i', 'input.webm',
223
- '-c:v', 'libx264',
224
- '-preset', 'fast',
225
- '-crf', '23',
226
- '-c:a', 'aac',
227
- '-b:a', '128k',
228
- '-movflags', '+faststart',
229
- 'output.mp4'
230
- ]
231
- });
232
- onProgress?.({
233
- stage: 'finalizing',
234
- progress: 95,
235
- message: 'Finalizing video...'
236
- });
237
- // Read output file
238
- const outputData = await this.sendMessage('READ_FILE', { path: 'output.mp4' });
239
- // Clean up virtual filesystem
240
- await this.sendMessage('DELETE_FILE', { path: 'input.webm' });
241
- await this.sendMessage('DELETE_FILE', { path: 'output.mp4' });
242
- onProgress?.({
243
- stage: 'finalizing',
244
- progress: 100,
245
- message: 'Conversion complete!'
246
- });
247
- return new Blob([new Uint8Array(outputData)], { type: 'video/mp4' });
248
- }
249
- /**
250
- * Check if FFmpeg is currently loaded
251
- */
252
- get isLoaded() {
253
- return this.worker !== null && this.core !== null;
254
- }
255
- /**
256
- * Terminate FFmpeg worker to free resources
257
- */
258
- async terminate() {
259
- if (this.worker) {
260
- this.worker.terminate();
261
- this.worker = null;
262
- this.core = null;
263
- this.loadPromise = null;
264
- this.pendingMessages.clear();
265
- }
266
- }
267
- }
268
- // Singleton instance for caching
269
- let ffmpegServiceInstance = null;
270
- export function getFFmpegService() {
271
- if (!ffmpegServiceInstance) {
272
- ffmpegServiceInstance = new FFmpegService();
273
- }
274
- return ffmpegServiceInstance;
275
- }
276
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmZtcGVnLnNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c193ZWIvc2VydmljZXMvZmZtcGVnLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztHQUdHO0FBdUJILE1BQU0sT0FBTyxhQUFhO0lBQ2hCLE1BQU0sR0FBa0IsSUFBSSxDQUFDO0lBQzdCLElBQUksR0FBUSxJQUFJLENBQUM7SUFDakIsV0FBVyxHQUF5QixJQUFJLENBQUM7SUFDekMsU0FBUyxHQUFHLENBQUMsQ0FBQztJQUNkLGVBQWUsR0FBeUQsSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUNsRixLQUFLLENBQTZCO0lBQ2xDLFVBQVUsQ0FBOEI7SUFFaEQ7O09BRUc7SUFDSCxLQUFLLENBQUMsWUFBWSxDQUFDLFVBQW9EO1FBQ3JFLElBQUksSUFBSSxDQUFDLE1BQU0sSUFBSSxJQUFJLENBQUMsSUFBSTtZQUFFLE9BQU87UUFFckMsSUFBSSxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7WUFDckIsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDO1lBQ3ZCLE9BQU87UUFDVCxDQUFDO1FBRUQsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQy9DLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQztJQUN6QixDQUFDO0lBRU8sS0FBSyxDQUFDLFVBQVUsQ0FBQyxVQUFvRDtRQUMzRSxPQUFPLENBQUMsR0FBRyxDQUFDLHFEQUFxRCxDQUFDLENBQUM7UUFFbkUsVUFBVSxFQUFFLENBQUM7WUFDWCxLQUFLLEVBQUUsU0FBUztZQUNoQixRQUFRLEVBQUUsQ0FBQztZQUNYLE9BQU8sRUFBRSwyQkFBMkI7U0FDckMsQ0FBQyxDQUFDO1FBRUgsMkJBQTJCO1FBQzNCLE1BQU0sRUFBRSxTQUFTLEVBQUUsR0FBRyxNQUFNLE1BQU0sQ0FBQyxjQUFjLENBQUMsQ0FBQztRQUVuRCxrREFBa0Q7UUFDbEQsTUFB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
@@ -1,32 +0,0 @@
1
- /**
2
- * MP4Service - Handles client-side video format conversion using Mediabunny
3
- * Uses WebCodecs API for hardware-accelerated encoding (10-50x faster than FFmpeg.wasm)
4
- * @see https://mediabunny.dev/
5
- */
6
- export interface IConversionProgress {
7
- stage: 'loading' | 'converting' | 'finalizing';
8
- progress: number;
9
- message: string;
10
- }
11
- export interface IConversionOptions {
12
- inputBlob: Blob;
13
- outputFormat: 'mp4' | 'webm';
14
- onProgress?: (progress: IConversionProgress) => void;
15
- }
16
- export declare class MP4Service {
17
- /**
18
- * Check if WebCodecs is supported in this browser
19
- */
20
- static isSupported(): boolean;
21
- /**
22
- * Check if H.264 encoding is actually available (not just API presence)
23
- */
24
- static isEncodingSupported(): Promise<boolean>;
25
- /**
26
- * Convert WebM blob to MP4 using Mediabunny (hardware accelerated via WebCodecs)
27
- */
28
- convertToMp4(options: IConversionOptions): Promise<Blob>;
29
- }
30
- export declare function getMP4Service(): MP4Service;
31
- export { MP4Service as FFmpegService };
32
- export { getMP4Service as getFFmpegService };