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.
@@ -1 +1 @@
1
- {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"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","sourcesContent":["import { WebPlugin } from '@capacitor/core';\nimport { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile, toBlobURL } from '@ffmpeg/util';\n\nimport type { NativeVideoCompressorPlugin, CompressOptions, CompressResult } from './definitions';\n\nexport class NativeVideoCompressorWeb extends WebPlugin implements NativeVideoCompressorPlugin {\n private ffmpeg: FFmpeg | null = null;\n private isLoaded = false;\n\n private async loadFFmpeg() {\n if (this.isLoaded && this.ffmpeg) return;\n\n this.notifyListeners('onProgress', { status: 'loading_core', percent: 0 });\n\n try {\n this.ffmpeg = new FFmpeg();\n\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\n this.ffmpeg.on('log', ({ message }) => {\n console.log('[FFmpeg Log]', message);\n });\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\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\n this.isLoaded = true;\n console.log('FFmpeg core loaded successfully');\n } catch (e: any) {\n console.error('Failed to load FFmpeg:', e);\n throw e;\n }\n }\n\n async initialize(): Promise<{ success: boolean; message?: string }> {\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 } catch (e: any) {\n return { success: false, message: e.message || 'Initialization failed' };\n }\n }\n\n async compressVideo(options: CompressOptions): Promise<CompressResult> {\n try {\n if (!this.isLoaded) {\n await this.loadFFmpeg();\n }\n\n if (!this.ffmpeg) {\n throw new Error('FFmpeg failed to load');\n }\n\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n\n const { sourcePath, quality = 'MEDIUM' } = options;\n const inputFileName = 'input.mp4';\n const outputFileName = 'output.mp4';\n\n // Map quality to CRF, Scale, and Audio Bitrate\n let crf = '28';\n let scale = ''; // default keep resolution\n let audioBitrate = '128k';\n\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\n console.log('[FFmpeg] Fetching source file...', sourcePath);\n let inputData: Uint8Array;\n try {\n inputData = await fetchFile(sourcePath);\n } catch (err) {\n console.error('[FFmpeg] Error fetching source file:', err);\n throw new Error('Could not fetch source video file');\n }\n\n console.log('[FFmpeg] Writing file to FS...', inputData.byteLength, 'bytes');\n try {\n await this.ffmpeg.writeFile(inputFileName, inputData);\n } catch (err) {\n console.error('[FFmpeg] Error writing to FS:', err);\n throw new Error('Could not write to FFmpeg FS');\n }\n\n // Construct FFmpeg command array\n const args: string[] = ['-i', inputFileName];\n\n // Video codec and compression parameters (superfast provides much better size than ultrafast)\n args.push('-c:v', 'libx264', '-preset', 'superfast', '-crf', crf);\n\n // Resize if needed (split by space)\n if (scale) {\n args.push(...scale.split(' '));\n }\n\n // Audio re-encoding to save space\n args.push('-c:a', 'aac', '-b:a', audioBitrate);\n\n // Output\n args.push(outputFileName);\n\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 } catch (err) {\n console.error('[FFmpeg] Error executing command:', err);\n throw new Error('FFmpeg execution failed');\n }\n\n console.log('[FFmpeg] Reading output file...');\n let data: any;\n try {\n data = await this.ffmpeg.readFile(outputFileName);\n } catch (err) {\n console.error('[FFmpeg] Error reading output file:', err);\n throw new Error('Could not read FFmpeg output');\n }\n\n // Free memory\n await this.ffmpeg.deleteFile(inputFileName);\n await this.ffmpeg.deleteFile(outputFileName);\n\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 as any], { type: 'video/mp4' });\n const destPath = URL.createObjectURL(blob);\n\n console.log('[FFmpeg] Compression complete, returning path:', destPath);\n return {\n success: true,\n destPath: destPath,\n };\n } catch (error: any) {\n console.error('[FFmpeg] Compression process failed:', error);\n throw error;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"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","sourcesContent":["import { WebPlugin } from '@capacitor/core';\nimport { FFmpeg } from '@ffmpeg/ffmpeg';\nimport { fetchFile, toBlobURL } from '@ffmpeg/util';\n\nimport type {\n NativeVideoCompressorPlugin,\n CompressOptions,\n CompressResult,\n CancelOptions,\n CancelResult,\n} from './definitions';\nimport { CANCELLED } from './definitions';\n\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 code = CANCELLED;\n constructor() {\n super('Đã hủy nén');\n this.name = 'CancelledError';\n }\n}\n\nconst CORE_BASE_URL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm';\n\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: Promise<{ coreURL: string; wasmURL: string }> | null = null;\n\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};\n\nexport class NativeVideoCompressorWeb extends WebPlugin implements NativeVideoCompressorPlugin {\n private ffmpeg: FFmpeg | null = null;\n private isLoaded = false;\n /** The job `compressVideo` is running right now, for `cancel({ jobId })`. */\n private currentJobId: string | null = null;\n private cancelRequested = false;\n\n private async loadFFmpeg() {\n if (this.isLoaded && this.ffmpeg) return;\n\n this.notifyListeners('onProgress', { status: 'loading_core', percent: 0 });\n\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\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\n instance.on('log', ({ message }) => {\n console.log('[FFmpeg Log]', message);\n });\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 } catch (e: any) {\n instance.terminate();\n console.error('Failed to load FFmpeg:', e);\n throw e;\n }\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\n this.ffmpeg = instance;\n this.isLoaded = true;\n console.log('FFmpeg core loaded successfully');\n }\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?: CancelOptions): Promise<CancelResult> {\n if (!this.currentJobId) return { cancelled: false };\n if (options?.jobId && options.jobId !== this.currentJobId) return { cancelled: false };\n\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 this.ffmpeg?.terminate();\n this.ffmpeg = null;\n this.isLoaded = false;\n return { cancelled: true };\n }\n\n async initialize(): Promise<{ success: boolean; message?: string }> {\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 } catch (e: any) {\n return { success: false, message: e.message || 'Initialization failed' };\n }\n }\n\n async compressVideo(options: CompressOptions): Promise<CompressResult> {\n this.currentJobId = options.jobId ?? 'default';\n this.cancelRequested = false;\n\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) throw new CancelledError();\n };\n\n try {\n if (!this.isLoaded) {\n await this.loadFFmpeg();\n }\n throwIfCancelled();\n\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\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n\n const { sourcePath, quality = 'MEDIUM' } = options;\n const inputFileName = 'input.mp4';\n const outputFileName = 'output.mp4';\n\n // Map quality to CRF, Scale, and Audio Bitrate\n let crf = '28';\n let scale = ''; // default keep resolution\n let audioBitrate = '128k';\n\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\n console.log('[FFmpeg] Fetching source file...', sourcePath);\n let inputData: Uint8Array;\n try {\n inputData = await fetchFile(sourcePath);\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\n console.log('[FFmpeg] Writing file to FS...', inputData.byteLength, 'bytes');\n try {\n await ffmpeg.writeFile(inputFileName, inputData);\n } catch (err) {\n if (this.cancelRequested) 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\n // Construct FFmpeg command array\n const args: string[] = ['-i', inputFileName];\n\n // Video codec and compression parameters (superfast provides much better size than ultrafast)\n args.push('-c:v', 'libx264', '-preset', 'superfast', '-crf', crf);\n\n // Resize if needed (split by space)\n if (scale) {\n args.push(...scale.split(' '));\n }\n\n // Audio re-encoding to save space\n args.push('-c:a', 'aac', '-b:a', audioBitrate);\n\n // Output\n args.push(outputFileName);\n\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 } 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) throw new CancelledError();\n console.error('[FFmpeg] Error executing command:', err);\n throw new Error('FFmpeg execution failed');\n }\n throwIfCancelled();\n\n console.log('[FFmpeg] Reading output file...');\n let data: any;\n try {\n data = await ffmpeg.readFile(outputFileName);\n } catch (err) {\n if (this.cancelRequested) throw new CancelledError();\n console.error('[FFmpeg] Error reading output file:', err);\n throw new Error('Could not read FFmpeg output');\n }\n\n // Free memory\n await ffmpeg.deleteFile(inputFileName);\n await ffmpeg.deleteFile(outputFileName);\n\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 as any], { type: 'video/mp4' });\n const destPath = URL.createObjectURL(blob);\n\n console.log('[FFmpeg] Compression complete, returning path:', destPath);\n return {\n success: true,\n destPath: destPath,\n };\n } catch (error: any) {\n if (error?.code === CANCELLED) {\n console.log('[FFmpeg] Compression cancelled');\n } else {\n console.error('[FFmpeg] Compression process failed:', error);\n }\n throw error;\n } finally {\n this.currentJobId = null;\n this.cancelRequested = false;\n }\n }\n}\n"]}
@@ -4,45 +4,126 @@ var core = require('@capacitor/core');
4
4
  var ffmpeg = require('@ffmpeg/ffmpeg');
5
5
  var util = require('@ffmpeg/util');
6
6
 
7
+ /**
8
+ * `code` on the error `compressVideo` rejects with when `cancel()` stopped it.
9
+ * Callers should treat it as "the user asked for this" — no error toast, no
10
+ * retry prompt — and tell it apart from a genuine compression failure.
11
+ */
12
+ const CANCELLED = 'CANCELLED';
13
+
7
14
  const NativeVideoCompressor = core.registerPlugin('NativeVideoCompressor', {
8
15
  web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.NativeVideoCompressorWeb()),
9
16
  });
10
17
 
18
+ /**
19
+ * Rejection shape for a cancelled compress. `code` matches what `call.reject`
20
+ * puts on the error object on iOS/Android, so one `error.code === 'CANCELLED'`
21
+ * check works on every platform.
22
+ */
23
+ class CancelledError extends Error {
24
+ constructor() {
25
+ super('Đã hủy nén');
26
+ this.code = CANCELLED;
27
+ this.name = 'CancelledError';
28
+ }
29
+ }
30
+ const CORE_BASE_URL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm';
31
+ /**
32
+ * Cancelling means terminating the worker, which drops the loaded core with
33
+ * it — so without this the next compress re-downloads ~30 MB of wasm and
34
+ * "cancel" quietly costs more than it saves. Module scope, not instance: the
35
+ * bytes are identical for every instance and every job.
36
+ */
37
+ let corePromise = null;
38
+ const loadCoreURLs = () => {
39
+ if (!corePromise) {
40
+ corePromise = Promise.all([
41
+ util.toBlobURL(`${CORE_BASE_URL}/ffmpeg-core.js`, 'text/javascript'),
42
+ util.toBlobURL(`${CORE_BASE_URL}/ffmpeg-core.wasm`, 'application/wasm'),
43
+ ])
44
+ .then(([coreURL, wasmURL]) => ({ coreURL, wasmURL }))
45
+ .catch((e) => {
46
+ // Don't cache a failure — a flaky network shouldn't make every later
47
+ // compress in this session fail too.
48
+ corePromise = null;
49
+ throw e;
50
+ });
51
+ }
52
+ return corePromise;
53
+ };
11
54
  class NativeVideoCompressorWeb extends core.WebPlugin {
12
55
  constructor() {
13
56
  super(...arguments);
14
57
  this.ffmpeg = null;
15
58
  this.isLoaded = false;
59
+ /** The job `compressVideo` is running right now, for `cancel({ jobId })`. */
60
+ this.currentJobId = null;
61
+ this.cancelRequested = false;
16
62
  }
17
63
  async loadFFmpeg() {
18
64
  if (this.isLoaded && this.ffmpeg)
19
65
  return;
20
66
  this.notifyListeners('onProgress', { status: 'loading_core', percent: 0 });
67
+ // Built on a local, published to the field only once it is ready and still
68
+ // wanted. Assigning `this.ffmpeg` up front and setting `isLoaded` after the
69
+ // await left a window where a cancel could null the instance and the load,
70
+ // finishing a moment later, would set `isLoaded = true` on nothing at all —
71
+ // after which every compressVideo threw 'FFmpeg failed to load' for the
72
+ // rest of the page's life.
73
+ const instance = new ffmpeg.FFmpeg();
74
+ // Listen to FFmpeg progress and map it to capacitor plugin events
75
+ instance.on('progress', ({ progress }) => {
76
+ // progress is a fraction between 0 and 1
77
+ this.notifyListeners('onProgress', { status: 'progress', percent: Math.round(progress * 100) });
78
+ });
79
+ instance.on('log', ({ message }) => {
80
+ console.log('[FFmpeg Log]', message);
81
+ });
21
82
  try {
22
- this.ffmpeg = new ffmpeg.FFmpeg();
23
- // Listen to FFmpeg progress and map it to capacitor plugin events
24
- this.ffmpeg.on('progress', ({ progress }) => {
25
- // progress is a fraction between 0 and 1
26
- this.notifyListeners('onProgress', { status: 'progress', percent: Math.round(progress * 100) });
27
- });
28
- this.ffmpeg.on('log', ({ message }) => {
29
- console.log('[FFmpeg Log]', message);
30
- });
31
83
  // Use standard single-threaded version for best compatibility across browsers
32
84
  // without requiring complex SharedArrayBuffer headers configuration
33
- const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm';
34
- console.log('Starting to load FFmpeg core from:', baseURL);
35
- await this.ffmpeg.load({
36
- coreURL: await util.toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
37
- wasmURL: await util.toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
38
- });
39
- this.isLoaded = true;
40
- console.log('FFmpeg core loaded successfully');
85
+ console.log('Starting to load FFmpeg core from:', CORE_BASE_URL);
86
+ await instance.load(await loadCoreURLs());
41
87
  }
42
88
  catch (e) {
89
+ instance.terminate();
43
90
  console.error('Failed to load FFmpeg:', e);
44
91
  throw e;
45
92
  }
93
+ // Cancelled while this was loading. Terminate what we just built rather
94
+ // than publish it — the caller asked for none of it.
95
+ if (this.cancelRequested) {
96
+ instance.terminate();
97
+ throw new CancelledError();
98
+ }
99
+ this.ffmpeg = instance;
100
+ this.isLoaded = true;
101
+ console.log('FFmpeg core loaded successfully');
102
+ }
103
+ /**
104
+ * Kill the worker outright — there is no gentler option.
105
+ *
106
+ * `ffmpeg.exec()` runs *synchronously* inside the worker (see
107
+ * @ffmpeg/ffmpeg's worker.js: `ffmpeg.exec(...args)` is not awaited), so the
108
+ * worker never reaches a message poll while encoding. The AbortSignal that
109
+ * `exec()` accepts only rejects the main-thread promise; the encode keeps
110
+ * burning CPU behind it. `worker.terminate()` is what actually stops it.
111
+ *
112
+ * The cost is the loaded core, which is why the core URLs are cached above.
113
+ */
114
+ async cancel(options) {
115
+ var _a;
116
+ if (!this.currentJobId)
117
+ return { cancelled: false };
118
+ if ((options === null || options === void 0 ? void 0 : options.jobId) && options.jobId !== this.currentJobId)
119
+ return { cancelled: false };
120
+ // Set even when there is no instance yet: the job may still be inside
121
+ // loadFFmpeg, which reads this flag before publishing what it built.
122
+ this.cancelRequested = true;
123
+ (_a = this.ffmpeg) === null || _a === void 0 ? void 0 : _a.terminate();
124
+ this.ffmpeg = null;
125
+ this.isLoaded = false;
126
+ return { cancelled: true };
46
127
  }
47
128
  async initialize() {
48
129
  try {
@@ -57,13 +138,30 @@ class NativeVideoCompressorWeb extends core.WebPlugin {
57
138
  }
58
139
  }
59
140
  async compressVideo(options) {
141
+ var _a;
142
+ this.currentJobId = (_a = options.jobId) !== null && _a !== void 0 ? _a : 'default';
143
+ this.cancelRequested = false;
144
+ // Checked at every stage boundary, not just around exec: terminate() only
145
+ // reaches work that has already got to the worker, so a cancel during the
146
+ // core download or the source fetch has nothing to interrupt and would
147
+ // otherwise let the compress carry on as if nothing had happened.
148
+ const throwIfCancelled = () => {
149
+ if (this.cancelRequested)
150
+ throw new CancelledError();
151
+ };
60
152
  try {
61
153
  if (!this.isLoaded) {
62
154
  await this.loadFFmpeg();
63
155
  }
156
+ throwIfCancelled();
64
157
  if (!this.ffmpeg) {
65
158
  throw new Error('FFmpeg failed to load');
66
159
  }
160
+ // Bound once. `this.ffmpeg` is nulled by cancel(), so reading it after
161
+ // any of the four awaits below can hand back null on a reference the
162
+ // compiler narrowed at this line — surfacing a cancel as a TypeError
163
+ // instead of a CancelledError.
164
+ const ffmpeg = this.ffmpeg;
67
165
  this.notifyListeners('onProgress', { status: 'started', percent: 0 });
68
166
  const { sourcePath, quality = 'MEDIUM' } = options;
69
167
  const inputFileName = 'input.mp4';
@@ -113,14 +211,18 @@ class NativeVideoCompressorWeb extends core.WebPlugin {
113
211
  console.error('[FFmpeg] Error fetching source file:', err);
114
212
  throw new Error('Could not fetch source video file');
115
213
  }
214
+ throwIfCancelled();
116
215
  console.log('[FFmpeg] Writing file to FS...', inputData.byteLength, 'bytes');
117
216
  try {
118
- await this.ffmpeg.writeFile(inputFileName, inputData);
217
+ await ffmpeg.writeFile(inputFileName, inputData);
119
218
  }
120
219
  catch (err) {
220
+ if (this.cancelRequested)
221
+ throw new CancelledError();
121
222
  console.error('[FFmpeg] Error writing to FS:', err);
122
223
  throw new Error('Could not write to FFmpeg FS');
123
224
  }
225
+ throwIfCancelled();
124
226
  // Construct FFmpeg command array
125
227
  const args = ['-i', inputFileName];
126
228
  // Video codec and compression parameters (superfast provides much better size than ultrafast)
@@ -135,25 +237,32 @@ class NativeVideoCompressorWeb extends core.WebPlugin {
135
237
  args.push(outputFileName);
136
238
  console.log('[FFmpeg] Executing command:', args.join(' '));
137
239
  try {
138
- const retCode = await this.ffmpeg.exec(args);
240
+ const retCode = await ffmpeg.exec(args);
139
241
  console.log('[FFmpeg] Exec returned code:', retCode);
140
242
  }
141
243
  catch (err) {
244
+ // terminate() rejects every in-flight call, so a cancel arrives here
245
+ // looking like a generic failure unless we name it.
246
+ if (this.cancelRequested)
247
+ throw new CancelledError();
142
248
  console.error('[FFmpeg] Error executing command:', err);
143
249
  throw new Error('FFmpeg execution failed');
144
250
  }
251
+ throwIfCancelled();
145
252
  console.log('[FFmpeg] Reading output file...');
146
253
  let data;
147
254
  try {
148
- data = await this.ffmpeg.readFile(outputFileName);
255
+ data = await ffmpeg.readFile(outputFileName);
149
256
  }
150
257
  catch (err) {
258
+ if (this.cancelRequested)
259
+ throw new CancelledError();
151
260
  console.error('[FFmpeg] Error reading output file:', err);
152
261
  throw new Error('Could not read FFmpeg output');
153
262
  }
154
263
  // Free memory
155
- await this.ffmpeg.deleteFile(inputFileName);
156
- await this.ffmpeg.deleteFile(outputFileName);
264
+ await ffmpeg.deleteFile(inputFileName);
265
+ await ffmpeg.deleteFile(outputFileName);
157
266
  console.log('[FFmpeg] Creating Blob URL from output...', data.byteLength, 'bytes');
158
267
  // Create a blob URL for the resulting file
159
268
  const blob = new Blob([data], { type: 'video/mp4' });
@@ -165,9 +274,18 @@ class NativeVideoCompressorWeb extends core.WebPlugin {
165
274
  };
166
275
  }
167
276
  catch (error) {
168
- console.error('[FFmpeg] Compression process failed:', error);
277
+ if ((error === null || error === void 0 ? void 0 : error.code) === CANCELLED) {
278
+ console.log('[FFmpeg] Compression cancelled');
279
+ }
280
+ else {
281
+ console.error('[FFmpeg] Compression process failed:', error);
282
+ }
169
283
  throw error;
170
284
  }
285
+ finally {
286
+ this.currentJobId = null;
287
+ this.cancelRequested = false;
288
+ }
171
289
  }
172
290
  }
173
291
 
@@ -176,5 +294,6 @@ var web = /*#__PURE__*/Object.freeze({
176
294
  NativeVideoCompressorWeb: NativeVideoCompressorWeb
177
295
  });
178
296
 
297
+ exports.CANCELLED = CANCELLED;
179
298
  exports.NativeVideoCompressor = NativeVideoCompressor;
180
299
  //# sourceMappingURL=plugin.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.cjs.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":";;;;;;AACK,MAAC,qBAAqB,GAAGA,mBAAc,CAAC,uBAAuB,EAAE;AACtE,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,wBAAwB,EAAE,CAAC;AAC5E,CAAC;;ACAM,MAAM,wBAAwB,SAASC,cAAS,CAAC;AACxD,IAAI,WAAW,GAAG;AAClB,QAAQ,KAAK,CAAC,GAAG,SAAS,CAAC;AAC3B,QAAQ,IAAI,CAAC,MAAM,GAAG,IAAI;AAC1B,QAAQ,IAAI,CAAC,QAAQ,GAAG,KAAK;AAC7B,IAAI;AACJ,IAAI,MAAM,UAAU,GAAG;AACvB,QAAQ,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM;AACxC,YAAY;AACZ,QAAQ,IAAI,CAAC,eAAe,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;AAClF,QAAQ,IAAI;AACZ,YAAY,IAAI,CAAC,MAAM,GAAG,IAAIC,aAAM,EAAE;AACtC;AACA,YAAY,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK;AACzD;AACA,gBAAgB,IAAI,CAAC,eAAe,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,EAAE,CAAC;AAC/G,YAAY,CAAC,CAAC;AACd,YAAY,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK;AACnD,gBAAgB,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC;AACpD,YAAY,CAAC,CAAC;AACd;AACA;AACA,YAAY,MAAM,OAAO,GAAG,gDAAgD;AAC5E,YAAY,OAAO,CAAC,GAAG,CAAC,oCAAoC,EAAE,OAAO,CAAC;AACtE,YAAY,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;AACnC,gBAAgB,OAAO,EAAE,MAAMC,cAAS,CAAC,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,EAAE,iBAAiB,CAAC;AACxF,gBAAgB,OAAO,EAAE,MAAMA,cAAS,CAAC,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,EAAE,kBAAkB,CAAC;AAC3F,aAAa,CAAC;AACd,YAAY,IAAI,CAAC,QAAQ,GAAG,IAAI;AAChC,YAAY,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC;AAC1D,QAAQ;AACR,QAAQ,OAAO,CAAC,EAAE;AAClB,YAAY,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,CAAC,CAAC;AACtD,YAAY,MAAM,CAAC;AACnB,QAAQ;AACR,IAAI;AACJ,IAAI,MAAM,UAAU,GAAG;AACvB,QAAQ,IAAI;AACZ,YAAY,IAAI,IAAI,CAAC,QAAQ,EAAE;AAC/B,gBAAgB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,qBAAqB,EAAE;AACxE,YAAY;AACZ,YAAY,MAAM,IAAI,CAAC,UAAU,EAAE;AACnC,YAAY,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,2BAA2B,EAAE;AAC1E,QAAQ;AACR,QAAQ,OAAO,CAAC,EAAE;AAClB,YAAY,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,uBAAuB,EAAE;AACpF,QAAQ;AACR,IAAI;AACJ,IAAI,MAAM,aAAa,CAAC,OAAO,EAAE;AACjC,QAAQ,IAAI;AACZ,YAAY,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;AAChC,gBAAgB,MAAM,IAAI,CAAC,UAAU,EAAE;AACvC,YAAY;AACZ,YAAY,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;AAC9B,gBAAgB,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC;AACxD,YAAY;AACZ,YAAY,IAAI,CAAC,eAAe,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;AACjF,YAAY,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,QAAQ,EAAE,GAAG,OAAO;AAC9D,YAAY,MAAM,aAAa,GAAG,WAAW;AAC7C,YAAY,MAAM,cAAc,GAAG,YAAY;AAC/C;AACA,YAAY,IAAI,GAAG,GAAG,IAAI;AAC1B,YAAY,IAAI,KAAK,GAAG,EAAE,CAAC;AAC3B,YAAY,IAAI,YAAY,GAAG,MAAM;AACrC,YAAY,QAAQ,OAAO;AAC3B,gBAAgB,KAAK,WAAW;AAChC,oBAAoB,GAAG,GAAG,IAAI;AAC9B,oBAAoB,KAAK,GAAG,mBAAmB;AAC/C,oBAAoB,YAAY,GAAG,MAAM;AACzC,oBAAoB;AACpB,gBAAgB,KAAK,MAAM;AAC3B,oBAAoB,GAAG,GAAG,IAAI;AAC9B,oBAAoB,KAAK,GAAG,kBAAkB;AAC9C,oBAAoB,YAAY,GAAG,MAAM;AACzC,oBAAoB;AACpB,gBAAgB,KAAK,QAAQ;AAC7B,oBAAoB,GAAG,GAAG,IAAI;AAC9B,oBAAoB,KAAK,GAAG,kBAAkB;AAC9C,oBAAoB,YAAY,GAAG,KAAK;AACxC,oBAAoB;AACpB,gBAAgB,KAAK,KAAK;AAC1B,oBAAoB,GAAG,GAAG,IAAI;AAC9B,oBAAoB,KAAK,GAAG,kBAAkB;AAC9C,oBAAoB,YAAY,GAAG,KAAK;AACxC,oBAAoB;AACpB,gBAAgB,KAAK,MAAM;AAC3B,oBAAoB,GAAG,GAAG,IAAI;AAC9B,oBAAoB,KAAK,GAAG,kBAAkB;AAC9C,oBAAoB,YAAY,GAAG,KAAK;AACxC,oBAAoB;AACpB,gBAAgB,KAAK,UAAU;AAC/B,oBAAoB,GAAG,GAAG,IAAI;AAC9B,oBAAoB,KAAK,GAAG,kBAAkB;AAC9C,oBAAoB,YAAY,GAAG,KAAK;AACxC,oBAAoB;AACpB;AACA,YAAY,OAAO,CAAC,GAAG,CAAC,kCAAkC,EAAE,UAAU,CAAC;AACvE,YAAY,IAAI,SAAS;AACzB,YAAY,IAAI;AAChB,gBAAgB,SAAS,GAAG,MAAMC,cAAS,CAAC,UAAU,CAAC;AACvD,YAAY;AACZ,YAAY,OAAO,GAAG,EAAE;AACxB,gBAAgB,OAAO,CAAC,KAAK,CAAC,sCAAsC,EAAE,GAAG,CAAC;AAC1E,gBAAgB,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC;AACpE,YAAY;AACZ,YAAY,OAAO,CAAC,GAAG,CAAC,gCAAgC,EAAE,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC;AACxF,YAAY,IAAI;AAChB,gBAAgB,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,aAAa,EAAE,SAAS,CAAC;AACrE,YAAY;AACZ,YAAY,OAAO,GAAG,EAAE;AACxB,gBAAgB,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,GAAG,CAAC;AACnE,gBAAgB,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC;AAC/D,YAAY;AACZ;AACA,YAAY,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,aAAa,CAAC;AAC9C;AACA,YAAY,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,CAAC;AAC7E;AACA,YAAY,IAAI,KAAK,EAAE;AACvB,gBAAgB,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAC9C,YAAY;AACZ;AACA,YAAY,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC;AAC1D;AACA,YAAY,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC;AACrC,YAAY,OAAO,CAAC,GAAG,CAAC,6BAA6B,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACtE,YAAY,IAAI;AAChB,gBAAgB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;AAC5D,gBAAgB,OAAO,CAAC,GAAG,CAAC,8BAA8B,EAAE,OAAO,CAAC;AACpE,YAAY;AACZ,YAAY,OAAO,GAAG,EAAE;AACxB,gBAAgB,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,GAAG,CAAC;AACvE,gBAAgB,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC;AAC1D,YAAY;AACZ,YAAY,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC;AAC1D,YAAY,IAAI,IAAI;AACpB,YAAY,IAAI;AAChB,gBAAgB,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC;AACjE,YAAY;AACZ,YAAY,OAAO,GAAG,EAAE;AACxB,gBAAgB,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,GAAG,CAAC;AACzE,gBAAgB,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC;AAC/D,YAAY;AACZ;AACA,YAAY,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,aAAa,CAAC;AACvD,YAAY,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC;AACxD,YAAY,OAAO,CAAC,GAAG,CAAC,2CAA2C,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC;AAC9F;AACA,YAAY,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AAChE,YAAY,MAAM,QAAQ,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC;AACtD,YAAY,OAAO,CAAC,GAAG,CAAC,gDAAgD,EAAE,QAAQ,CAAC;AACnF,YAAY,OAAO;AACnB,gBAAgB,OAAO,EAAE,IAAI;AAC7B,gBAAgB,QAAQ,EAAE,QAAQ;AAClC,aAAa;AACb,QAAQ;AACR,QAAQ,OAAO,KAAK,EAAE;AACtB,YAAY,OAAO,CAAC,KAAK,CAAC,sCAAsC,EAAE,KAAK,CAAC;AACxE,YAAY,MAAM,KAAK;AACvB,QAAQ;AACR,IAAI;AACJ;;;;;;;;;"}
1
+ {"version":3,"file":"plugin.cjs.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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"}