capacitor-native-video-compressor 1.0.3 → 1.0.5

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/dist/plugin.js CHANGED
@@ -1,45 +1,126 @@
1
1
  var capacitorNativeVideoCompressor = (function (exports, core, ffmpeg, util) {
2
2
  'use strict';
3
3
 
4
+ /**
5
+ * `code` on the error `compressVideo` rejects with when `cancel()` stopped it.
6
+ * Callers should treat it as "the user asked for this" — no error toast, no
7
+ * retry prompt — and tell it apart from a genuine compression failure.
8
+ */
9
+ const CANCELLED = 'CANCELLED';
10
+
4
11
  const NativeVideoCompressor = core.registerPlugin('NativeVideoCompressor', {
5
12
  web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.NativeVideoCompressorWeb()),
6
13
  });
7
14
 
15
+ /**
16
+ * Rejection shape for a cancelled compress. `code` matches what `call.reject`
17
+ * puts on the error object on iOS/Android, so one `error.code === 'CANCELLED'`
18
+ * check works on every platform.
19
+ */
20
+ class CancelledError extends Error {
21
+ constructor() {
22
+ super('Đã hủy nén');
23
+ this.code = CANCELLED;
24
+ this.name = 'CancelledError';
25
+ }
26
+ }
27
+ const CORE_BASE_URL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm';
28
+ /**
29
+ * Cancelling means terminating the worker, which drops the loaded core with
30
+ * it — so without this the next compress re-downloads ~30 MB of wasm and
31
+ * "cancel" quietly costs more than it saves. Module scope, not instance: the
32
+ * bytes are identical for every instance and every job.
33
+ */
34
+ let corePromise = null;
35
+ const loadCoreURLs = () => {
36
+ if (!corePromise) {
37
+ corePromise = Promise.all([
38
+ util.toBlobURL(`${CORE_BASE_URL}/ffmpeg-core.js`, 'text/javascript'),
39
+ util.toBlobURL(`${CORE_BASE_URL}/ffmpeg-core.wasm`, 'application/wasm'),
40
+ ])
41
+ .then(([coreURL, wasmURL]) => ({ coreURL, wasmURL }))
42
+ .catch((e) => {
43
+ // Don't cache a failure — a flaky network shouldn't make every later
44
+ // compress in this session fail too.
45
+ corePromise = null;
46
+ throw e;
47
+ });
48
+ }
49
+ return corePromise;
50
+ };
8
51
  class NativeVideoCompressorWeb extends core.WebPlugin {
9
52
  constructor() {
10
53
  super(...arguments);
11
54
  this.ffmpeg = null;
12
55
  this.isLoaded = false;
56
+ /** The job `compressVideo` is running right now, for `cancel({ jobId })`. */
57
+ this.currentJobId = null;
58
+ this.cancelRequested = false;
13
59
  }
14
60
  async loadFFmpeg() {
15
61
  if (this.isLoaded && this.ffmpeg)
16
62
  return;
17
63
  this.notifyListeners('onProgress', { status: 'loading_core', percent: 0 });
64
+ // Built on a local, published to the field only once it is ready and still
65
+ // wanted. Assigning `this.ffmpeg` up front and setting `isLoaded` after the
66
+ // await left a window where a cancel could null the instance and the load,
67
+ // finishing a moment later, would set `isLoaded = true` on nothing at all —
68
+ // after which every compressVideo threw 'FFmpeg failed to load' for the
69
+ // rest of the page's life.
70
+ const instance = new ffmpeg.FFmpeg();
71
+ // Listen to FFmpeg progress and map it to capacitor plugin events
72
+ instance.on('progress', ({ progress }) => {
73
+ // progress is a fraction between 0 and 1
74
+ this.notifyListeners('onProgress', { status: 'progress', percent: Math.round(progress * 100) });
75
+ });
76
+ instance.on('log', ({ message }) => {
77
+ console.log('[FFmpeg Log]', message);
78
+ });
18
79
  try {
19
- this.ffmpeg = new ffmpeg.FFmpeg();
20
- // Listen to FFmpeg progress and map it to capacitor plugin events
21
- this.ffmpeg.on('progress', ({ progress }) => {
22
- // progress is a fraction between 0 and 1
23
- this.notifyListeners('onProgress', { status: 'progress', percent: Math.round(progress * 100) });
24
- });
25
- this.ffmpeg.on('log', ({ message }) => {
26
- console.log('[FFmpeg Log]', message);
27
- });
28
80
  // Use standard single-threaded version for best compatibility across browsers
29
81
  // without requiring complex SharedArrayBuffer headers configuration
30
- const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm';
31
- console.log('Starting to load FFmpeg core from:', baseURL);
32
- await this.ffmpeg.load({
33
- coreURL: await util.toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
34
- wasmURL: await util.toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
35
- });
36
- this.isLoaded = true;
37
- console.log('FFmpeg core loaded successfully');
82
+ console.log('Starting to load FFmpeg core from:', CORE_BASE_URL);
83
+ await instance.load(await loadCoreURLs());
38
84
  }
39
85
  catch (e) {
86
+ instance.terminate();
40
87
  console.error('Failed to load FFmpeg:', e);
41
88
  throw e;
42
89
  }
90
+ // Cancelled while this was loading. Terminate what we just built rather
91
+ // than publish it — the caller asked for none of it.
92
+ if (this.cancelRequested) {
93
+ instance.terminate();
94
+ throw new CancelledError();
95
+ }
96
+ this.ffmpeg = instance;
97
+ this.isLoaded = true;
98
+ console.log('FFmpeg core loaded successfully');
99
+ }
100
+ /**
101
+ * Kill the worker outright — there is no gentler option.
102
+ *
103
+ * `ffmpeg.exec()` runs *synchronously* inside the worker (see
104
+ * @ffmpeg/ffmpeg's worker.js: `ffmpeg.exec(...args)` is not awaited), so the
105
+ * worker never reaches a message poll while encoding. The AbortSignal that
106
+ * `exec()` accepts only rejects the main-thread promise; the encode keeps
107
+ * burning CPU behind it. `worker.terminate()` is what actually stops it.
108
+ *
109
+ * The cost is the loaded core, which is why the core URLs are cached above.
110
+ */
111
+ async cancel(options) {
112
+ var _a;
113
+ if (!this.currentJobId)
114
+ return { cancelled: false };
115
+ if ((options === null || options === void 0 ? void 0 : options.jobId) && options.jobId !== this.currentJobId)
116
+ return { cancelled: false };
117
+ // Set even when there is no instance yet: the job may still be inside
118
+ // loadFFmpeg, which reads this flag before publishing what it built.
119
+ this.cancelRequested = true;
120
+ (_a = this.ffmpeg) === null || _a === void 0 ? void 0 : _a.terminate();
121
+ this.ffmpeg = null;
122
+ this.isLoaded = false;
123
+ return { cancelled: true };
43
124
  }
44
125
  async initialize() {
45
126
  try {
@@ -54,13 +135,30 @@ var capacitorNativeVideoCompressor = (function (exports, core, ffmpeg, util) {
54
135
  }
55
136
  }
56
137
  async compressVideo(options) {
138
+ var _a;
139
+ this.currentJobId = (_a = options.jobId) !== null && _a !== void 0 ? _a : 'default';
140
+ this.cancelRequested = false;
141
+ // Checked at every stage boundary, not just around exec: terminate() only
142
+ // reaches work that has already got to the worker, so a cancel during the
143
+ // core download or the source fetch has nothing to interrupt and would
144
+ // otherwise let the compress carry on as if nothing had happened.
145
+ const throwIfCancelled = () => {
146
+ if (this.cancelRequested)
147
+ throw new CancelledError();
148
+ };
57
149
  try {
58
150
  if (!this.isLoaded) {
59
151
  await this.loadFFmpeg();
60
152
  }
153
+ throwIfCancelled();
61
154
  if (!this.ffmpeg) {
62
155
  throw new Error('FFmpeg failed to load');
63
156
  }
157
+ // Bound once. `this.ffmpeg` is nulled by cancel(), so reading it after
158
+ // any of the four awaits below can hand back null on a reference the
159
+ // compiler narrowed at this line — surfacing a cancel as a TypeError
160
+ // instead of a CancelledError.
161
+ const ffmpeg = this.ffmpeg;
64
162
  this.notifyListeners('onProgress', { status: 'started', percent: 0 });
65
163
  const { sourcePath, quality = 'MEDIUM' } = options;
66
164
  const inputFileName = 'input.mp4';
@@ -110,14 +208,18 @@ var capacitorNativeVideoCompressor = (function (exports, core, ffmpeg, util) {
110
208
  console.error('[FFmpeg] Error fetching source file:', err);
111
209
  throw new Error('Could not fetch source video file');
112
210
  }
211
+ throwIfCancelled();
113
212
  console.log('[FFmpeg] Writing file to FS...', inputData.byteLength, 'bytes');
114
213
  try {
115
- await this.ffmpeg.writeFile(inputFileName, inputData);
214
+ await ffmpeg.writeFile(inputFileName, inputData);
116
215
  }
117
216
  catch (err) {
217
+ if (this.cancelRequested)
218
+ throw new CancelledError();
118
219
  console.error('[FFmpeg] Error writing to FS:', err);
119
220
  throw new Error('Could not write to FFmpeg FS');
120
221
  }
222
+ throwIfCancelled();
121
223
  // Construct FFmpeg command array
122
224
  const args = ['-i', inputFileName];
123
225
  // Video codec and compression parameters (superfast provides much better size than ultrafast)
@@ -132,25 +234,32 @@ var capacitorNativeVideoCompressor = (function (exports, core, ffmpeg, util) {
132
234
  args.push(outputFileName);
133
235
  console.log('[FFmpeg] Executing command:', args.join(' '));
134
236
  try {
135
- const retCode = await this.ffmpeg.exec(args);
237
+ const retCode = await ffmpeg.exec(args);
136
238
  console.log('[FFmpeg] Exec returned code:', retCode);
137
239
  }
138
240
  catch (err) {
241
+ // terminate() rejects every in-flight call, so a cancel arrives here
242
+ // looking like a generic failure unless we name it.
243
+ if (this.cancelRequested)
244
+ throw new CancelledError();
139
245
  console.error('[FFmpeg] Error executing command:', err);
140
246
  throw new Error('FFmpeg execution failed');
141
247
  }
248
+ throwIfCancelled();
142
249
  console.log('[FFmpeg] Reading output file...');
143
250
  let data;
144
251
  try {
145
- data = await this.ffmpeg.readFile(outputFileName);
252
+ data = await ffmpeg.readFile(outputFileName);
146
253
  }
147
254
  catch (err) {
255
+ if (this.cancelRequested)
256
+ throw new CancelledError();
148
257
  console.error('[FFmpeg] Error reading output file:', err);
149
258
  throw new Error('Could not read FFmpeg output');
150
259
  }
151
260
  // Free memory
152
- await this.ffmpeg.deleteFile(inputFileName);
153
- await this.ffmpeg.deleteFile(outputFileName);
261
+ await ffmpeg.deleteFile(inputFileName);
262
+ await ffmpeg.deleteFile(outputFileName);
154
263
  console.log('[FFmpeg] Creating Blob URL from output...', data.byteLength, 'bytes');
155
264
  // Create a blob URL for the resulting file
156
265
  const blob = new Blob([data], { type: 'video/mp4' });
@@ -162,9 +271,18 @@ var capacitorNativeVideoCompressor = (function (exports, core, ffmpeg, util) {
162
271
  };
163
272
  }
164
273
  catch (error) {
165
- console.error('[FFmpeg] Compression process failed:', error);
274
+ if ((error === null || error === void 0 ? void 0 : error.code) === CANCELLED) {
275
+ console.log('[FFmpeg] Compression cancelled');
276
+ }
277
+ else {
278
+ console.error('[FFmpeg] Compression process failed:', error);
279
+ }
166
280
  throw error;
167
281
  }
282
+ finally {
283
+ this.currentJobId = null;
284
+ this.cancelRequested = false;
285
+ }
168
286
  }
169
287
  }
170
288
 
@@ -173,6 +291,7 @@ var capacitorNativeVideoCompressor = (function (exports, core, ffmpeg, util) {
173
291
  NativeVideoCompressorWeb: NativeVideoCompressorWeb
174
292
  });
175
293
 
294
+ exports.CANCELLED = CANCELLED;
176
295
  exports.NativeVideoCompressor = NativeVideoCompressor;
177
296
 
178
297
  return exports;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst NativeVideoCompressor = registerPlugin('NativeVideoCompressor', {\n web: () => import('./web').then((m) => new m.NativeVideoCompressorWeb()),\n});\nexport * from './definitions';\nexport { NativeVideoCompressor };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nimport { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile, toBlobURL } from '@ffmpeg/util';\nexport class NativeVideoCompressorWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.ffmpeg = null;\n this.isLoaded = false;\n }\n async loadFFmpeg() {\n if (this.isLoaded && this.ffmpeg)\n return;\n this.notifyListeners('onProgress', { status: 'loading_core', percent: 0 });\n try {\n this.ffmpeg = new FFmpeg();\n // Listen to FFmpeg progress and map it to capacitor plugin events\n this.ffmpeg.on('progress', ({ progress }) => {\n // progress is a fraction between 0 and 1\n this.notifyListeners('onProgress', { status: 'progress', percent: Math.round(progress * 100) });\n });\n this.ffmpeg.on('log', ({ message }) => {\n console.log('[FFmpeg Log]', message);\n });\n // Use standard single-threaded version for best compatibility across browsers\n // without requiring complex SharedArrayBuffer headers configuration\n const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm';\n console.log('Starting to load FFmpeg core from:', baseURL);\n await this.ffmpeg.load({\n coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),\n wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),\n });\n this.isLoaded = true;\n console.log('FFmpeg core loaded successfully');\n }\n catch (e) {\n console.error('Failed to load FFmpeg:', e);\n throw e;\n }\n }\n async initialize() {\n try {\n if (this.isLoaded) {\n return { success: true, message: 'Already initialized' };\n }\n await this.loadFFmpeg();\n return { success: true, message: 'Initialization successful' };\n }\n catch (e) {\n return { success: false, message: e.message || 'Initialization failed' };\n }\n }\n async compressVideo(options) {\n try {\n if (!this.isLoaded) {\n await this.loadFFmpeg();\n }\n if (!this.ffmpeg) {\n throw new Error('FFmpeg failed to load');\n }\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n const { sourcePath, quality = 'MEDIUM' } = options;\n const inputFileName = 'input.mp4';\n const outputFileName = 'output.mp4';\n // Map quality to CRF, Scale, and Audio Bitrate\n let crf = '28';\n let scale = ''; // default keep resolution\n let audioBitrate = '128k';\n switch (quality) {\n case 'VERY_HIGH': // 1080p\n crf = '23';\n scale = '-vf scale=-2:1080';\n audioBitrate = '128k';\n break;\n case 'HIGH': // 720p\n crf = '28';\n scale = '-vf scale=-2:720';\n audioBitrate = '128k';\n break;\n case 'MEDIUM': // 540p\n crf = '30';\n scale = '-vf scale=-2:540';\n audioBitrate = '96k';\n break;\n case 'LOW': // 480p\n crf = '32';\n scale = '-vf scale=-2:480';\n audioBitrate = '96k';\n break;\n case '360P': // 360p\n crf = '28';\n scale = '-vf scale=-2:360';\n audioBitrate = '64k';\n break;\n case 'VERY_LOW': // 240p\n crf = '35';\n scale = '-vf scale=-2:240';\n audioBitrate = '48k';\n break;\n }\n console.log('[FFmpeg] Fetching source file...', sourcePath);\n let inputData;\n try {\n inputData = await fetchFile(sourcePath);\n }\n catch (err) {\n console.error('[FFmpeg] Error fetching source file:', err);\n throw new Error('Could not fetch source video file');\n }\n console.log('[FFmpeg] Writing file to FS...', inputData.byteLength, 'bytes');\n try {\n await this.ffmpeg.writeFile(inputFileName, inputData);\n }\n catch (err) {\n console.error('[FFmpeg] Error writing to FS:', err);\n throw new Error('Could not write to FFmpeg FS');\n }\n // Construct FFmpeg command array\n const args = ['-i', inputFileName];\n // Video codec and compression parameters (superfast provides much better size than ultrafast)\n args.push('-c:v', 'libx264', '-preset', 'superfast', '-crf', crf);\n // Resize if needed (split by space)\n if (scale) {\n args.push(...scale.split(' '));\n }\n // Audio re-encoding to save space\n args.push('-c:a', 'aac', '-b:a', audioBitrate);\n // Output\n args.push(outputFileName);\n console.log('[FFmpeg] Executing command:', args.join(' '));\n try {\n const retCode = await this.ffmpeg.exec(args);\n console.log('[FFmpeg] Exec returned code:', retCode);\n }\n catch (err) {\n console.error('[FFmpeg] Error executing command:', err);\n throw new Error('FFmpeg execution failed');\n }\n console.log('[FFmpeg] Reading output file...');\n let data;\n try {\n data = await this.ffmpeg.readFile(outputFileName);\n }\n catch (err) {\n console.error('[FFmpeg] Error reading output file:', err);\n throw new Error('Could not read FFmpeg output');\n }\n // Free memory\n await this.ffmpeg.deleteFile(inputFileName);\n await this.ffmpeg.deleteFile(outputFileName);\n console.log('[FFmpeg] Creating Blob URL from output...', data.byteLength, 'bytes');\n // Create a blob URL for the resulting file\n const blob = new Blob([data], { type: 'video/mp4' });\n const destPath = URL.createObjectURL(blob);\n console.log('[FFmpeg] Compression complete, returning path:', destPath);\n return {\n success: true,\n destPath: destPath,\n };\n }\n catch (error) {\n console.error('[FFmpeg] Compression process failed:', error);\n throw error;\n }\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin","FFmpeg","toBlobURL","fetchFile"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sources":["esm/definitions.js","esm/index.js","esm/web.js"],"sourcesContent":["/**\n * `code` on the error `compressVideo` rejects with when `cancel()` stopped it.\n * Callers should treat it as \"the user asked for this\" — no error toast, no\n * retry prompt — and tell it apart from a genuine compression failure.\n */\nexport const CANCELLED = 'CANCELLED';\n//# sourceMappingURL=definitions.js.map","import { registerPlugin } from '@capacitor/core';\nconst NativeVideoCompressor = registerPlugin('NativeVideoCompressor', {\n web: () => import('./web').then((m) => new m.NativeVideoCompressorWeb()),\n});\nexport * from './definitions';\nexport { NativeVideoCompressor };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nimport { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile, toBlobURL } from '@ffmpeg/util';\nimport { CANCELLED } from './definitions';\n/**\n * Rejection shape for a cancelled compress. `code` matches what `call.reject`\n * puts on the error object on iOS/Android, so one `error.code === 'CANCELLED'`\n * check works on every platform.\n */\nclass CancelledError extends Error {\n constructor() {\n super('Đã hủy nén');\n this.code = CANCELLED;\n this.name = 'CancelledError';\n }\n}\nconst CORE_BASE_URL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm';\n/**\n * Cancelling means terminating the worker, which drops the loaded core with\n * it — so without this the next compress re-downloads ~30 MB of wasm and\n * \"cancel\" quietly costs more than it saves. Module scope, not instance: the\n * bytes are identical for every instance and every job.\n */\nlet corePromise = null;\nconst loadCoreURLs = () => {\n if (!corePromise) {\n corePromise = Promise.all([\n toBlobURL(`${CORE_BASE_URL}/ffmpeg-core.js`, 'text/javascript'),\n toBlobURL(`${CORE_BASE_URL}/ffmpeg-core.wasm`, 'application/wasm'),\n ])\n .then(([coreURL, wasmURL]) => ({ coreURL, wasmURL }))\n .catch((e) => {\n // Don't cache a failure — a flaky network shouldn't make every later\n // compress in this session fail too.\n corePromise = null;\n throw e;\n });\n }\n return corePromise;\n};\nexport class NativeVideoCompressorWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.ffmpeg = null;\n this.isLoaded = false;\n /** The job `compressVideo` is running right now, for `cancel({ jobId })`. */\n this.currentJobId = null;\n this.cancelRequested = false;\n }\n async loadFFmpeg() {\n if (this.isLoaded && this.ffmpeg)\n return;\n this.notifyListeners('onProgress', { status: 'loading_core', percent: 0 });\n // Built on a local, published to the field only once it is ready and still\n // wanted. Assigning `this.ffmpeg` up front and setting `isLoaded` after the\n // await left a window where a cancel could null the instance and the load,\n // finishing a moment later, would set `isLoaded = true` on nothing at all —\n // after which every compressVideo threw 'FFmpeg failed to load' for the\n // rest of the page's life.\n const instance = new FFmpeg();\n // Listen to FFmpeg progress and map it to capacitor plugin events\n instance.on('progress', ({ progress }) => {\n // progress is a fraction between 0 and 1\n this.notifyListeners('onProgress', { status: 'progress', percent: Math.round(progress * 100) });\n });\n instance.on('log', ({ message }) => {\n console.log('[FFmpeg Log]', message);\n });\n try {\n // Use standard single-threaded version for best compatibility across browsers\n // without requiring complex SharedArrayBuffer headers configuration\n console.log('Starting to load FFmpeg core from:', CORE_BASE_URL);\n await instance.load(await loadCoreURLs());\n }\n catch (e) {\n instance.terminate();\n console.error('Failed to load FFmpeg:', e);\n throw e;\n }\n // Cancelled while this was loading. Terminate what we just built rather\n // than publish it — the caller asked for none of it.\n if (this.cancelRequested) {\n instance.terminate();\n throw new CancelledError();\n }\n this.ffmpeg = instance;\n this.isLoaded = true;\n console.log('FFmpeg core loaded successfully');\n }\n /**\n * Kill the worker outright — there is no gentler option.\n *\n * `ffmpeg.exec()` runs *synchronously* inside the worker (see\n * @ffmpeg/ffmpeg's worker.js: `ffmpeg.exec(...args)` is not awaited), so the\n * worker never reaches a message poll while encoding. The AbortSignal that\n * `exec()` accepts only rejects the main-thread promise; the encode keeps\n * burning CPU behind it. `worker.terminate()` is what actually stops it.\n *\n * The cost is the loaded core, which is why the core URLs are cached above.\n */\n async cancel(options) {\n var _a;\n if (!this.currentJobId)\n return { cancelled: false };\n if ((options === null || options === void 0 ? void 0 : options.jobId) && options.jobId !== this.currentJobId)\n return { cancelled: false };\n // Set even when there is no instance yet: the job may still be inside\n // loadFFmpeg, which reads this flag before publishing what it built.\n this.cancelRequested = true;\n (_a = this.ffmpeg) === null || _a === void 0 ? void 0 : _a.terminate();\n this.ffmpeg = null;\n this.isLoaded = false;\n return { cancelled: true };\n }\n async initialize() {\n try {\n if (this.isLoaded) {\n return { success: true, message: 'Already initialized' };\n }\n await this.loadFFmpeg();\n return { success: true, message: 'Initialization successful' };\n }\n catch (e) {\n return { success: false, message: e.message || 'Initialization failed' };\n }\n }\n async compressVideo(options) {\n var _a;\n this.currentJobId = (_a = options.jobId) !== null && _a !== void 0 ? _a : 'default';\n this.cancelRequested = false;\n // Checked at every stage boundary, not just around exec: terminate() only\n // reaches work that has already got to the worker, so a cancel during the\n // core download or the source fetch has nothing to interrupt and would\n // otherwise let the compress carry on as if nothing had happened.\n const throwIfCancelled = () => {\n if (this.cancelRequested)\n throw new CancelledError();\n };\n try {\n if (!this.isLoaded) {\n await this.loadFFmpeg();\n }\n throwIfCancelled();\n if (!this.ffmpeg) {\n throw new Error('FFmpeg failed to load');\n }\n // Bound once. `this.ffmpeg` is nulled by cancel(), so reading it after\n // any of the four awaits below can hand back null on a reference the\n // compiler narrowed at this line — surfacing a cancel as a TypeError\n // instead of a CancelledError.\n const ffmpeg = this.ffmpeg;\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n const { sourcePath, quality = 'MEDIUM' } = options;\n const inputFileName = 'input.mp4';\n const outputFileName = 'output.mp4';\n // Map quality to CRF, Scale, and Audio Bitrate\n let crf = '28';\n let scale = ''; // default keep resolution\n let audioBitrate = '128k';\n switch (quality) {\n case 'VERY_HIGH': // 1080p\n crf = '23';\n scale = '-vf scale=-2:1080';\n audioBitrate = '128k';\n break;\n case 'HIGH': // 720p\n crf = '28';\n scale = '-vf scale=-2:720';\n audioBitrate = '128k';\n break;\n case 'MEDIUM': // 540p\n crf = '30';\n scale = '-vf scale=-2:540';\n audioBitrate = '96k';\n break;\n case 'LOW': // 480p\n crf = '32';\n scale = '-vf scale=-2:480';\n audioBitrate = '96k';\n break;\n case '360P': // 360p\n crf = '28';\n scale = '-vf scale=-2:360';\n audioBitrate = '64k';\n break;\n case 'VERY_LOW': // 240p\n crf = '35';\n scale = '-vf scale=-2:240';\n audioBitrate = '48k';\n break;\n }\n console.log('[FFmpeg] Fetching source file...', sourcePath);\n let inputData;\n try {\n inputData = await fetchFile(sourcePath);\n }\n catch (err) {\n console.error('[FFmpeg] Error fetching source file:', err);\n throw new Error('Could not fetch source video file');\n }\n throwIfCancelled();\n console.log('[FFmpeg] Writing file to FS...', inputData.byteLength, 'bytes');\n try {\n await ffmpeg.writeFile(inputFileName, inputData);\n }\n catch (err) {\n if (this.cancelRequested)\n throw new CancelledError();\n console.error('[FFmpeg] Error writing to FS:', err);\n throw new Error('Could not write to FFmpeg FS');\n }\n throwIfCancelled();\n // Construct FFmpeg command array\n const args = ['-i', inputFileName];\n // Video codec and compression parameters (superfast provides much better size than ultrafast)\n args.push('-c:v', 'libx264', '-preset', 'superfast', '-crf', crf);\n // Resize if needed (split by space)\n if (scale) {\n args.push(...scale.split(' '));\n }\n // Audio re-encoding to save space\n args.push('-c:a', 'aac', '-b:a', audioBitrate);\n // Output\n args.push(outputFileName);\n console.log('[FFmpeg] Executing command:', args.join(' '));\n try {\n const retCode = await ffmpeg.exec(args);\n console.log('[FFmpeg] Exec returned code:', retCode);\n }\n catch (err) {\n // terminate() rejects every in-flight call, so a cancel arrives here\n // looking like a generic failure unless we name it.\n if (this.cancelRequested)\n throw new CancelledError();\n console.error('[FFmpeg] Error executing command:', err);\n throw new Error('FFmpeg execution failed');\n }\n throwIfCancelled();\n console.log('[FFmpeg] Reading output file...');\n let data;\n try {\n data = await ffmpeg.readFile(outputFileName);\n }\n catch (err) {\n if (this.cancelRequested)\n throw new CancelledError();\n console.error('[FFmpeg] Error reading output file:', err);\n throw new Error('Could not read FFmpeg output');\n }\n // Free memory\n await ffmpeg.deleteFile(inputFileName);\n await ffmpeg.deleteFile(outputFileName);\n console.log('[FFmpeg] Creating Blob URL from output...', data.byteLength, 'bytes');\n // Create a blob URL for the resulting file\n const blob = new Blob([data], { type: 'video/mp4' });\n const destPath = URL.createObjectURL(blob);\n console.log('[FFmpeg] Compression complete, returning path:', destPath);\n return {\n success: true,\n destPath: destPath,\n };\n }\n catch (error) {\n if ((error === null || error === void 0 ? void 0 : error.code) === CANCELLED) {\n console.log('[FFmpeg] Compression cancelled');\n }\n else {\n console.error('[FFmpeg] Compression process failed:', error);\n }\n throw error;\n }\n finally {\n this.currentJobId = null;\n this.cancelRequested = false;\n }\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","toBlobURL","WebPlugin","FFmpeg","fetchFile"],"mappings":"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"}