capacitor-native-video-compressor 1.1.2 → 1.2.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/README.md CHANGED
@@ -52,18 +52,22 @@ allprojects {
52
52
  }
53
53
  ```
54
54
 
55
- #### compileSdk 35+ (required)
56
-
57
- The compression foreground service declares `android:foregroundServiceType="mediaProcessing|dataSync"`.
58
- `mediaProcessing` exists only from **API 35 (Android 15)**, so your app must build with
59
- `compileSdkVersion` 35 or higher.
60
-
61
- At runtime the service picks the type by API level: `mediaProcessing` on API 35+,
62
- `dataSync` on API 29-34. Both types and both permissions are declared, so the plugin
63
- runs correctly on Android 10 through 15+. Passing only `mediaProcessing` on API 34
64
- makes the system read the type as unknown and `startForeground()` throws
65
- `InvalidForegroundServiceTypeException` - the service never reaches the foreground
66
- and compression is killed as soon as the user leaves the app.
55
+ #### minSdk 35 / compileSdk 35+ (required)
56
+
57
+ The compression foreground service declares `android:foregroundServiceType="mediaProcessing"`,
58
+ and `mediaProcessing` exists only from **API 35 (Android 15)**. On API 34 and below the
59
+ system reads the type as unknown and `startForeground()` throws
60
+ `InvalidForegroundServiceTypeException` - the service never reaches the foreground and
61
+ compression is killed as soon as the user leaves the app. There is no second type to fall
62
+ back to, so the plugin requires `minSdkVersion` 35.
63
+
64
+ **The service covers compression only.** It stops as soon as the compress finishes. Anything
65
+ you do with the output afterwards - uploading it, for instance - runs without a foreground
66
+ service, because JS in the webview is paused while the app is backgrounded anyway: holding
67
+ the service open for it would declare work that does not happen. Google Play classifies
68
+ foreground services by *use case*, not by how many services you run, and a `dataSync`
69
+ declaration covering a phase that is really "waiting for the user to come back" is what got
70
+ version code 30 of Destini rejected.
67
71
 
68
72
  ## Web Configuration (Required)
69
73
 
@@ -226,7 +230,6 @@ The `quality` parameter determines the output resolution and compression ratio.
226
230
  * [`compressVideo(...)`](#compressvideo)
227
231
  * [`cancel(...)`](#cancel)
228
232
  * [`initialize()`](#initialize)
229
- * [`finishUpload(...)`](#finishupload)
230
233
  * [`addListener('onProgress', ...)`](#addlisteneronprogress-)
231
234
  * [`addListener('onCancelRequested', ...)`](#addlisteneroncancelrequested-)
232
235
  * [Interfaces](#interfaces)
@@ -284,24 +287,6 @@ initialize() => Promise<{ success: boolean; message?: string; }>
284
287
  --------------------
285
288
 
286
289
 
287
- ### finishUpload(...)
288
-
289
- ```typescript
290
- finishUpload(options: { jobId: string; }) => Promise<void>
291
- ```
292
-
293
- Báo rằng phần upload đi kèm `holdForUpload` đã xong (thành công hay thất
294
- bại đều phải gọi) để Android hạ foreground service xuống.
295
-
296
- No-op trên web và iOS.
297
-
298
- | Param | Type |
299
- | ------------- | ------------------------------- |
300
- | **`options`** | <code>{ jobId: string; }</code> |
301
-
302
- --------------------
303
-
304
-
305
290
  ### addListener('onProgress', ...)
306
291
 
307
292
  ```typescript
@@ -326,10 +311,10 @@ addListener(eventName: 'onCancelRequested', listenerFunc: () => void) => Promise
326
311
 
327
312
  Android: user đã bấm "Huỷ" trên notification của foreground service.
328
313
 
329
- Pha nén được plugin tự huỷ trước khi event này bắn, nhưng pha upload chạy
330
- trong JS nên không ai dừng hộ được - caller phải tự abort request đang
331
- chạy. Bỏ qua event này nghĩa là nút Huỷ không thật sự huỷ, và Play đánh
332
- trượt tiêu chí "can be terminated or stopped by the user".
314
+ Pha nén được plugin tự huỷ trước khi event này bắn, nhưng việc caller định
315
+ làm với kết quả nén - upload chẳng hạn - thì không ai dừng hộ được, nên
316
+ caller phải tự abort. Bỏ qua event này nghĩa là nút Huỷ không thật sự huỷ,
317
+ và Play đánh trượt tiêu chí "can be terminated or stopped by the user".
333
318
 
334
319
  | Param | Type |
335
320
  | ------------------ | -------------------------------- |
@@ -354,13 +339,12 @@ trượt tiêu chí "can be terminated or stopped by the user".
354
339
 
355
340
  #### CompressOptions
356
341
 
357
- | Prop | Type | Description |
358
- | ------------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
359
- | **`sourcePath`** | <code>string</code> | |
360
- | **`destPath`** | <code>string</code> | |
361
- | **`quality`** | <code>'VERY_HIGH' \| 'HIGH' \| 'MEDIUM' \| 'LOW' \| 'VERY_LOW' \| '360P'</code> | |
362
- | **`jobId`** | <code>string</code> | Names this compress so `cancel({ jobId })` can target it. Every platform compresses one video at a time, so cancellation is global by nature. Two independent callers in the same app (say a comment queue and a notes queue) therefore need a way to say *which* job they mean — without one, a cancel from either kills whatever the other is running. |
363
- | **`holdForUpload`** | <code>boolean</code> | Android: giữ foreground service sống sau khi nén xong, để phần upload do JS chạy tiếp theo không bị hệ thống giết khi user rời app. Bật cờ này là một lời hứa: caller PHẢI gọi {@link NativeVideoCompressorPlugin.finishUpload} khi upload kết thúc, kể cả ở nhánh lỗi. Không gọi thì service chỉ còn watchdog dọn hộ sau 15 phút, và trong 15 phút đó user nhìn thấy một notification nói rằng app đang làm việc trong khi nó không làm gì cả. Không có tác dụng trên web và iOS. |
342
+ | Prop | Type | Description |
343
+ | ---------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
344
+ | **`sourcePath`** | <code>string</code> | |
345
+ | **`destPath`** | <code>string</code> | |
346
+ | **`quality`** | <code>'VERY_HIGH' \| 'HIGH' \| 'MEDIUM' \| 'LOW' \| 'VERY_LOW' \| '360P'</code> | |
347
+ | **`jobId`** | <code>string</code> | Names this compress so `cancel({ jobId })` can target it. Every platform compresses one video at a time, so cancellation is global by nature. Two independent callers in the same app (say a comment queue and a notes queue) therefore need a way to say *which* job they mean — without one, a cancel from either kills whatever the other is running. |
364
348
 
365
349
 
366
350
  #### CancelResult
@@ -21,7 +21,10 @@ android {
21
21
  namespace = "com.atomic.videocompressor"
22
22
  compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
23
23
  defaultConfig {
24
- minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
24
+ // 35, không phải 24: foreground service của pha nén khai mediaProcessing,
25
+ // type chỉ tồn tại từ API 35. Dưới mức đó hệ thống đọc ra "type unknown"
26
+ // và startForeground() ném InvalidForegroundServiceTypeException.
27
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 35
25
28
  targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
26
29
  versionCode 1
27
30
  versionName "1.0"
@@ -1,17 +1,20 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
2
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
3
- <!-- Chỉ dataSync, không khai mediaProcessing.
4
- Hai type nghĩa là hai quyền phải biện minh riêng trong Play Console với
5
- cùng một video demo, và mediaProcessing khó thuyết phục hơn hẳn. dataSync
6
- phủ được cả hai pha: nén rồi tải lên là một thao tác đồng bộ dữ liệu liên
7
- tục do user khởi tạo - đúng ví dụ "upload photo to the cloud" mà policy nêu. -->
8
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
3
+ <!-- Chỉ mediaProcessing: service sống đúng bằng pha nén, là use case duy
4
+ nhất ở đây thực sự chạy khi user rời app. Không khai dataSync nữa, vì
5
+ pha upload chạy bằng JS trong webview nên nó đứng lại ngay khi app
6
+ xuống nền - giữ foreground service cho nó là khai một việc không diễn
7
+ ra, và Play xét FGS theo use case chứ không theo số service.
8
+
9
+ mediaProcessing chỉ có từ API 35, và ở đây không còn type nào để hạ
10
+ xuống, nên minSdk của plugin là 35 (xem android/build.gradle). -->
11
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING" />
9
12
  <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
10
13
 
11
14
  <application>
12
15
  <service
13
16
  android:name="com.atomic.videocompressor.VideoCompressionService"
14
- android:foregroundServiceType="dataSync"
17
+ android:foregroundServiceType="mediaProcessing"
15
18
  android:exported="false" />
16
19
  </application>
17
20
  </manifest>
@@ -117,15 +117,6 @@ public class NativeVideoCompressorPlugin extends Plugin {
117
117
  call.resolve(ret);
118
118
  }
119
119
 
120
- @PluginMethod
121
- public void finishUpload(PluginCall call) {
122
- String jobId = call.getString("jobId");
123
- if (jobId != null) {
124
- sendToService(VideoCompressionService.ACTION_FINISH, jobId);
125
- }
126
- call.resolve();
127
- }
128
-
129
120
  @PluginMethod
130
121
  public void compressVideo(PluginCall call) {
131
122
  String sourcePath = call.getString("sourcePath");
@@ -136,7 +127,6 @@ public class NativeVideoCompressorPlugin extends Plugin {
136
127
  }
137
128
 
138
129
  String jobId = call.getString("jobId", "default");
139
- boolean holdForUpload = Boolean.TRUE.equals(call.getBoolean("holdForUpload", false));
140
130
  synchronized (this) {
141
131
  currentJobId = jobId;
142
132
  }
@@ -233,13 +223,13 @@ public class NativeVideoCompressorPlugin extends Plugin {
233
223
 
234
224
  @Override
235
225
  public void onFailure(int i, @NonNull String failureMessage) {
236
- finishJob(jobId, false);
226
+ finishJob(jobId);
237
227
  call.reject("Nén thất bại: " + failureMessage);
238
228
  }
239
229
 
240
230
  @Override
241
231
  public void onSuccess(int i, long l, @Nullable String s) {
242
- finishJob(jobId, holdForUpload);
232
+ finishJob(jobId);
243
233
  // Nén xong, trả kết quả đường dẫn file mới về cho JS
244
234
  JSObject ret = new JSObject();
245
235
  ret.put("success", true);
@@ -256,7 +246,7 @@ public class NativeVideoCompressorPlugin extends Plugin {
256
246
 
257
247
  @Override
258
248
  public void onCancelled(int i) {
259
- finishJob(jobId, false);
249
+ finishJob(jobId);
260
250
  // LightCompressor leaves whatever it had already written
261
251
  // behind; without this the cache keeps a partial mp4 per
262
252
  // cancel, and nothing ever collects them.
@@ -270,11 +260,8 @@ public class NativeVideoCompressorPlugin extends Plugin {
270
260
  }
271
261
 
272
262
  /** Every terminal callback ends here, so neither is ever left running. */
273
- private void finishJob(String jobId, boolean holdForUpload) {
274
- sendToService(
275
- holdForUpload ? VideoCompressionService.ACTION_UPLOAD : VideoCompressionService.ACTION_FINISH,
276
- jobId
277
- );
263
+ private void finishJob(String jobId) {
264
+ sendToService(VideoCompressionService.ACTION_FINISH, jobId);
278
265
  synchronized (this) {
279
266
  currentJobId = null;
280
267
  }
@@ -7,7 +7,6 @@ import android.app.PendingIntent;
7
7
  import android.app.Service;
8
8
  import android.content.Intent;
9
9
  import android.content.pm.ServiceInfo;
10
- import android.os.Build;
11
10
  import android.os.Handler;
12
11
  import android.os.IBinder;
13
12
  import android.os.Looper;
@@ -20,13 +19,18 @@ import java.util.LinkedHashMap;
20
19
  import java.util.Map;
21
20
 
22
21
  /**
23
- * Giữ tiến trình nén + upload sống khi user rời app.
22
+ * Giữ pha nén sống khi user rời app.
23
+ *
24
+ * Chỉ pha nén, không kèm upload: upload chạy bằng JS trong webview nên nó đứng
25
+ * lại ngay khi app xuống nền, và một foreground service ngồi chờ user quay lại
26
+ * là khai một việc không diễn ra - Play từ chối version code 30 vì đúng loại
27
+ * sai lệch đó (nén khai dưới dataSync). Service giờ sống đúng bằng pha nén.
24
28
  *
25
29
  * Mọi thứ trong đây phải đáp ứng được 5 tiêu chí foreground service của Google
26
30
  * Play, vì bản thiếu chúng (1.1.0) đã bị từ chối ở version code 29:
27
31
  *
28
- * - user perceptible -> notification có progress thật, cập nhật theo % nén
29
- * và theo pha (nén / tải lên), chứ không phải một dòng chữ tĩnh
32
+ * - user perceptible -> notification có progress thật, cập nhật theo % nén,
33
+ * chứ không phải một dòng chữ tĩnh
30
34
  * - stoppable by user -> action "Huỷ" ngay trên notification, vì khi user đã
31
35
  * rời app thì UI trong app không còn với tới được
32
36
  * - runs only as long as necessary -> watchdog tự tắt khi không còn ai báo
@@ -39,7 +43,6 @@ public class VideoCompressionService extends Service {
39
43
  private static final int NOTIFICATION_ID = 1001;
40
44
 
41
45
  static final String ACTION_START = "com.atomic.videocompressor.START";
42
- static final String ACTION_UPLOAD = "com.atomic.videocompressor.UPLOAD";
43
46
  static final String ACTION_FINISH = "com.atomic.videocompressor.FINISH";
44
47
  /** Do chính user bấm từ notification. */
45
48
  static final String ACTION_CANCEL = "com.atomic.videocompressor.CANCEL";
@@ -49,24 +52,21 @@ public class VideoCompressionService extends Service {
49
52
 
50
53
  /**
51
54
  * Không nhận được update nào trong khoảng này thì coi như phía gọi đã chết
52
- * (WebView bị kill, hoặc một exception thoát ra ngoài nhánh finishUpload) và
53
- * tự dừng. Không có nó, `jobs` không bao giờ rỗng và service chạy mãi với
54
- * một notification treo - đúng thứ khiến Play đánh trượt tiêu chí "runs only
55
- * for as long as necessary".
55
+ * (WebView bị kill, hoặc một exception thoát ra ngoài mọi nhánh kết thúc
56
+ * job) và tự dừng. Không có nó, `jobs` không bao giờ rỗng và service chạy
57
+ * mãi với một notification treo - đúng thứ khiến Play đánh trượt tiêu chí
58
+ * "runs only for as long as necessary".
56
59
  *
57
- * Chọn 15 phút: dài hơn mọi khoảng lặng hợp lệ (upload một video đã nén trên
58
- * mạng chậm, hoặc pha nén chưa kịp bắn progress đầu tiên), ngắn hơn nhiều so
59
- * với mức mà user kịp nhận ra có gì đó treo.
60
+ * Chọn 15 phút: dài hơn mọi khoảng lặng hợp lệ (pha nén chưa kịp bắn
61
+ * progress đầu tiên trên một video dài), ngắn hơn nhiều so với mức mà user
62
+ * kịp nhận ra có gì đó treo.
60
63
  */
61
64
  private static final long WATCHDOG_MS = 15 * 60 * 1000L;
62
65
 
63
66
  /** Đừng vẽ lại notification mỗi tick progress của LightCompressor. */
64
67
  private static final long MIN_REDRAW_INTERVAL_MS = 500L;
65
68
 
66
- private enum Phase { COMPRESSING, UPLOADING }
67
-
68
69
  private static final class Job {
69
- Phase phase = Phase.COMPRESSING;
70
70
  int percent = 0;
71
71
  }
72
72
 
@@ -97,9 +97,9 @@ public class VideoCompressionService extends Service {
97
97
  super.onCreate();
98
98
  instance = this;
99
99
  NotificationChannel channel = new NotificationChannel(
100
- CHANNEL_ID, "Xử lý và tải video lên", NotificationManager.IMPORTANCE_LOW
100
+ CHANNEL_ID, "Nén video", NotificationManager.IMPORTANCE_LOW
101
101
  );
102
- channel.setDescription("Tiến trình nén và tải video lên khi bạn dùng ứng dụng khác");
102
+ channel.setDescription("Tiến trình nén video");
103
103
  NotificationManager manager = getSystemService(NotificationManager.class);
104
104
  if (manager != null) manager.createNotificationChannel(channel);
105
105
  }
@@ -167,14 +167,6 @@ public class VideoCompressionService extends Service {
167
167
 
168
168
  if (ACTION_START.equals(action)) {
169
169
  jobs.put(jobId, new Job());
170
- } else if (ACTION_UPLOAD.equals(action)) {
171
- Job job = jobs.get(jobId);
172
- if (job != null) {
173
- job.phase = Phase.UPLOADING;
174
- // Pha upload không có % nào bắn về, nên thanh progress chuyển
175
- // sang indeterminate thay vì đứng im ở con số của pha nén.
176
- job.percent = -1;
177
- }
178
170
  } else if (ACTION_FINISH.equals(action)) {
179
171
  jobs.remove(jobId);
180
172
  }
@@ -198,29 +190,16 @@ public class VideoCompressionService extends Service {
198
190
  private void render() {
199
191
  Job shown = jobs.values().iterator().next();
200
192
  int running = jobs.size();
201
- boolean indeterminate = shown.percent < 0;
202
193
 
203
- String title;
204
- if (running > 1) {
205
- title = "Đang xử lý " + running + " video";
206
- } else {
207
- title = shown.phase == Phase.UPLOADING ? "Đang tải video lên" : "Đang nén video";
208
- }
209
-
210
- String text;
211
- if (indeterminate) {
212
- text = "Đang tải lên, sẽ tiếp tục khi bạn dùng ứng dụng khác";
213
- } else {
214
- text = shown.percent + "% - "
215
- + (shown.phase == Phase.UPLOADING ? "đang tải lên" : "đang nén")
216
- + ", sẽ tiếp tục khi bạn dùng ứng dụng khác";
217
- }
194
+ String title = running > 1
195
+ ? "Đang nén " + running + " video"
196
+ : "Đang nén video";
218
197
 
219
198
  Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
220
199
  .setContentTitle(title)
221
- .setContentText(text)
200
+ .setContentText(shown.percent + "% - đang nén")
222
201
  .setSmallIcon(resolveSmallIcon())
223
- .setProgress(100, indeterminate ? 0 : shown.percent, indeterminate)
202
+ .setProgress(100, shown.percent, false)
224
203
  .setOngoing(true)
225
204
  // Không có nó, mỗi lần vẽ lại progress là một lần notification
226
205
  // nhảy lên đầu danh sách và rung máy.
@@ -231,8 +210,16 @@ public class VideoCompressionService extends Service {
231
210
  .build();
232
211
 
233
212
  try {
234
- ServiceCompat.startForeground(
235
- this, NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
213
+ // Gọi thẳng Service.startForeground, KHÔNG qua ServiceCompat:
214
+ // ServiceCompat.Api34Impl AND type với một mask cứng các type có từ
215
+ // Android 14 (FOREGROUND_SERVICE_TYPE_ALLOWED_SINCE_U), mà
216
+ // mediaProcessing thì mới có ở Android 15. Qua nó, 8192 & mask = 0
217
+ // và hệ thống ném InvalidForegroundServiceTypeException "type none".
218
+ // Đo trên emulator API 36: startForegroundCount=0, service không bao
219
+ // giờ vào foreground.
220
+ startForeground(
221
+ NOTIFICATION_ID, notification,
222
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING
236
223
  );
237
224
  inForeground = true;
238
225
  } catch (Exception e) {
@@ -282,7 +269,7 @@ public class VideoCompressionService extends Service {
282
269
  }
283
270
 
284
271
  /**
285
- * Android 15+ giới hạn dataSync tổng 6 giờ/24 giờ rồi gọi onTimeout. Phải
272
+ * Android 15+ giới hạn mediaProcessing tổng 6 giờ/24 giờ rồi gọi onTimeout. Phải
286
273
  * dừng trong vài giây, nếu không app ăn ANR. destini-web đặt minSdk 35 nên
287
274
  * mọi thiết bị đều ở trong diện này.
288
275
  */
@@ -293,7 +280,11 @@ public class VideoCompressionService extends Service {
293
280
  stopEverything();
294
281
  }
295
282
 
296
- /** Overload của API 34, giữ lại cho thiết bị chạy Android 14. */
283
+ /**
284
+ * Overload của API 34. minSdk 35 nên hệ thống luôn gọi bản hai tham số ở
285
+ * trên; giữ lại vì bản mặc định của Service không làm gì, và một thiết bị
286
+ * gọi nhầm vào đây mà service không dừng thì app ăn ANR.
287
+ */
297
288
  @Override
298
289
  public void onTimeout(int startId) {
299
290
  Log.w(TAG, "Hệ thống hết hạn foreground service - dừng ngay");
package/dist/docs.json CHANGED
@@ -53,22 +53,6 @@
53
53
  "complexTypes": [],
54
54
  "slug": "initialize"
55
55
  },
56
- {
57
- "name": "finishUpload",
58
- "signature": "(options: { jobId: string; }) => Promise<void>",
59
- "parameters": [
60
- {
61
- "name": "options",
62
- "docs": "",
63
- "type": "{ jobId: string; }"
64
- }
65
- ],
66
- "returns": "Promise<void>",
67
- "tags": [],
68
- "docs": "Báo rằng phần upload đi kèm `holdForUpload` đã xong (thành công hay thất\nbại đều phải gọi) để Android hạ foreground service xuống.\n\nNo-op trên web và iOS.",
69
- "complexTypes": [],
70
- "slug": "finishupload"
71
- },
72
56
  {
73
57
  "name": "addListener",
74
58
  "signature": "(eventName: 'onProgress', listenerFunc: (info: { status: string; percent?: number; }) => void) => Promise<PluginListenerHandle>",
@@ -109,7 +93,7 @@
109
93
  ],
110
94
  "returns": "Promise<PluginListenerHandle>",
111
95
  "tags": [],
112
- "docs": "Android: user đã bấm \"Huỷ\" trên notification của foreground service.\n\nPha nén được plugin tự huỷ trước khi event này bắn, nhưng pha upload chạy\ntrong JS nên không ai dừng hộ được - caller phải tự abort request đang\nchạy. Bỏ qua event này nghĩa là nút Huỷ không thật sự huỷ, và Play đánh\ntrượt tiêu chí \"can be terminated or stopped by the user\".",
96
+ "docs": "Android: user đã bấm \"Huỷ\" trên notification của foreground service.\n\nPha nén được plugin tự huỷ trước khi event này bắn, nhưng việc caller định\nlàm với kết quả nén - upload chẳng hạn - thì không ai dừng hộ được, nên\ncaller phải tự abort. Bỏ qua event này nghĩa là nút Huỷ không thật sự huỷ,\nvà Play đánh trượt tiêu chí \"can be terminated or stopped by the user\".",
113
97
  "complexTypes": [
114
98
  "PluginListenerHandle"
115
99
  ],
@@ -176,13 +160,6 @@
176
160
  "docs": "Names this compress so `cancel({ jobId })` can target it.\n\nEvery platform compresses one video at a time, so cancellation is global\nby nature. Two independent callers in the same app (say a comment queue\nand a notes queue) therefore need a way to say *which* job they mean —\nwithout one, a cancel from either kills whatever the other is running.",
177
161
  "complexTypes": [],
178
162
  "type": "string | undefined"
179
- },
180
- {
181
- "name": "holdForUpload",
182
- "tags": [],
183
- "docs": "Android: giữ foreground service sống sau khi nén xong, để phần upload do\nJS chạy tiếp theo không bị hệ thống giết khi user rời app.\n\nBật cờ này là một lời hứa: caller PHẢI gọi {@link NativeVideoCompressorPlugin.finishUpload}\nkhi upload kết thúc, kể cả ở nhánh lỗi. Không gọi thì service chỉ còn\nwatchdog dọn hộ sau 15 phút, và trong 15 phút đó user nhìn thấy một\nnotification nói rằng app đang làm việc trong khi nó không làm gì cả.\n\nKhông có tác dụng trên web và iOS.",
184
- "complexTypes": [],
185
- "type": "boolean | undefined"
186
163
  }
187
164
  ]
188
165
  },
@@ -18,18 +18,6 @@ export interface CompressOptions {
18
18
  * without one, a cancel from either kills whatever the other is running.
19
19
  */
20
20
  jobId?: string;
21
- /**
22
- * Android: giữ foreground service sống sau khi nén xong, để phần upload do
23
- * JS chạy tiếp theo không bị hệ thống giết khi user rời app.
24
- *
25
- * Bật cờ này là một lời hứa: caller PHẢI gọi {@link NativeVideoCompressorPlugin.finishUpload}
26
- * khi upload kết thúc, kể cả ở nhánh lỗi. Không gọi thì service chỉ còn
27
- * watchdog dọn hộ sau 15 phút, và trong 15 phút đó user nhìn thấy một
28
- * notification nói rằng app đang làm việc trong khi nó không làm gì cả.
29
- *
30
- * Không có tác dụng trên web và iOS.
31
- */
32
- holdForUpload?: boolean;
33
21
  }
34
22
  export interface CompressResult {
35
23
  success: boolean;
@@ -61,15 +49,6 @@ export interface NativeVideoCompressorPlugin {
61
49
  success: boolean;
62
50
  message?: string;
63
51
  }>;
64
- /**
65
- * Báo rằng phần upload đi kèm `holdForUpload` đã xong (thành công hay thất
66
- * bại đều phải gọi) để Android hạ foreground service xuống.
67
- *
68
- * No-op trên web và iOS.
69
- */
70
- finishUpload(options: {
71
- jobId: string;
72
- }): Promise<void>;
73
52
  addListener(eventName: 'onProgress', listenerFunc: (info: {
74
53
  status: string;
75
54
  percent?: number;
@@ -77,10 +56,10 @@ export interface NativeVideoCompressorPlugin {
77
56
  /**
78
57
  * Android: user đã bấm "Huỷ" trên notification của foreground service.
79
58
  *
80
- * Pha nén được plugin tự huỷ trước khi event này bắn, nhưng pha upload chạy
81
- * trong JS nên không ai dừng hộ được - caller phải tự abort request đang
82
- * chạy. Bỏ qua event này nghĩa là nút Huỷ không thật sự huỷ, và Play đánh
83
- * trượt tiêu chí "can be terminated or stopped by the user".
59
+ * Pha nén được plugin tự huỷ trước khi event này bắn, nhưng việc caller định
60
+ * làm với kết quả nén - upload chẳng hạn - thì không ai dừng hộ được, nên
61
+ * caller phải tự abort. Bỏ qua event này nghĩa là nút Huỷ không thật sự huỷ,
62
+ * và Play đánh trượt tiêu chí "can be terminated or stopped by the user".
84
63
  */
85
64
  addListener(eventName: 'onCancelRequested', listenerFunc: () => void): Promise<PluginListenerHandle>;
86
65
  }
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,WAAW,CAAC","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n/**\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\nexport interface CompressOptions {\n sourcePath: string;\n destPath?: string;\n quality?: 'VERY_HIGH' | 'HIGH' | 'MEDIUM' | 'LOW' | 'VERY_LOW' | '360P';\n /**\n * Names this compress so `cancel({ jobId })` can target it.\n *\n * Every platform compresses one video at a time, so cancellation is global\n * by nature. Two independent callers in the same app (say a comment queue\n * and a notes queue) therefore need a way to say *which* job they mean —\n * without one, a cancel from either kills whatever the other is running.\n */\n jobId?: string;\n\n /**\n * Android: giữ foreground service sống sau khi nén xong, để phần upload do\n * JS chạy tiếp theo không bị hệ thống giết khi user rời app.\n *\n * Bật cờ này là một lời hứa: caller PHẢI gọi {@link NativeVideoCompressorPlugin.finishUpload}\n * khi upload kết thúc, kể cả ở nhánh lỗi. Không gọi thì service chỉ còn\n * watchdog dọn hộ sau 15 phút, và trong 15 phút đó user nhìn thấy một\n * notification nói rằng app đang làm việc trong khi nó không làm gì cả.\n *\n * Không có tác dụng trên web và iOS.\n */\n holdForUpload?: boolean;\n}\n\nexport interface CompressResult {\n success: boolean;\n destPath: string;\n}\n\nexport interface CancelOptions {\n /**\n * Only cancel if this is the job currently running. Omit to cancel whatever\n * is running, whoever started it.\n */\n jobId?: string;\n}\n\nexport interface CancelResult {\n /** False when nothing was running, or when a different job was. */\n cancelled: boolean;\n}\n\nexport interface NativeVideoCompressorPlugin {\n compressVideo(options: CompressOptions): Promise<CompressResult>;\n\n /**\n * Stop the compress that is running now. `compressVideo` then rejects with\n * code {@link CANCELLED} and its partial output is deleted.\n *\n * This is the only way to stop one: the work is synchronous WASM inside a\n * web worker (web) or an AVFoundation / LightCompressor pass on its own\n * thread (native), and none of them watch an AbortSignal.\n */\n cancel(options?: CancelOptions): Promise<CancelResult>;\n\n initialize(): Promise<{ success: boolean; message?: string }>;\n\n /**\n * Báo rằng phần upload đi kèm `holdForUpload` đã xong (thành công hay thất\n * bại đều phải gọi) để Android hạ foreground service xuống.\n *\n * No-op trên web và iOS.\n */\n finishUpload(options: { jobId: string }): Promise<void>;\n\n // Đã sửa lại chuẩn cho Capacitor mới nhất (chỉ trả về Promise)\n addListener(\n eventName: 'onProgress',\n listenerFunc: (info: { status: string; percent?: number }) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Android: user đã bấm \"Huỷ\" trên notification của foreground service.\n *\n * Pha nén được plugin tự huỷ trước khi event này bắn, nhưng pha upload chạy\n * trong JS nên không ai dừng hộ được - caller phải tự abort request đang\n * chạy. Bỏ qua event này nghĩa là nút Huỷ không thật sự huỷ, và Play đánh\n * trượt tiêu chí \"can be terminated or stopped by the user\".\n */\n addListener(\n eventName: 'onCancelRequested',\n listenerFunc: () => void,\n ): Promise<PluginListenerHandle>;\n}\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,WAAW,CAAC","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\n/**\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\nexport interface CompressOptions {\n sourcePath: string;\n destPath?: string;\n quality?: 'VERY_HIGH' | 'HIGH' | 'MEDIUM' | 'LOW' | 'VERY_LOW' | '360P';\n /**\n * Names this compress so `cancel({ jobId })` can target it.\n *\n * Every platform compresses one video at a time, so cancellation is global\n * by nature. Two independent callers in the same app (say a comment queue\n * and a notes queue) therefore need a way to say *which* job they mean —\n * without one, a cancel from either kills whatever the other is running.\n */\n jobId?: string;\n}\n\nexport interface CompressResult {\n success: boolean;\n destPath: string;\n}\n\nexport interface CancelOptions {\n /**\n * Only cancel if this is the job currently running. Omit to cancel whatever\n * is running, whoever started it.\n */\n jobId?: string;\n}\n\nexport interface CancelResult {\n /** False when nothing was running, or when a different job was. */\n cancelled: boolean;\n}\n\nexport interface NativeVideoCompressorPlugin {\n compressVideo(options: CompressOptions): Promise<CompressResult>;\n\n /**\n * Stop the compress that is running now. `compressVideo` then rejects with\n * code {@link CANCELLED} and its partial output is deleted.\n *\n * This is the only way to stop one: the work is synchronous WASM inside a\n * web worker (web) or an AVFoundation / LightCompressor pass on its own\n * thread (native), and none of them watch an AbortSignal.\n */\n cancel(options?: CancelOptions): Promise<CancelResult>;\n\n initialize(): Promise<{ success: boolean; message?: string }>;\n\n // Đã sửa lại chuẩn cho Capacitor mới nhất (chỉ trả về Promise)\n addListener(\n eventName: 'onProgress',\n listenerFunc: (info: { status: string; percent?: number }) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Android: user đã bấm \"Huỷ\" trên notification của foreground service.\n *\n * Pha nén được plugin tự huỷ trước khi event này bắn, nhưng việc caller định\n * làm với kết quả nén - upload chẳng hạn - thì không ai dừng hộ được, nên\n * caller phải tự abort. Bỏ qua event này nghĩa là nút Huỷ không thật sự huỷ,\n * và Play đánh trượt tiêu chí \"can be terminated or stopped by the user\".\n */\n addListener(\n eventName: 'onCancelRequested',\n listenerFunc: () => void,\n ): Promise<PluginListenerHandle>;\n}\n"]}
package/dist/esm/web.d.ts CHANGED
@@ -24,11 +24,6 @@ export declare class NativeVideoCompressorWeb extends WebPlugin implements Nativ
24
24
  * stop it — its cost is the loaded core, which is why the core URLs are
25
25
  * cached above.
26
26
  */
27
- /**
28
- * Chỉ Android mới có foreground service để mà hạ xuống. Hiện diện ở đây để
29
- * caller gọi thẳng, không phải rẽ nhánh theo platform ở mọi call site.
30
- */
31
- finishUpload(): Promise<void>;
32
27
  cancel(options?: CancelOptions): Promise<CancelResult>;
33
28
  private fetchSourceBlob;
34
29
  private canUseWebCodecs;
package/dist/esm/web.js CHANGED
@@ -134,13 +134,6 @@ export class NativeVideoCompressorWeb extends WebPlugin {
134
134
  * stop it — its cost is the loaded core, which is why the core URLs are
135
135
  * cached above.
136
136
  */
137
- /**
138
- * Chỉ Android mới có foreground service để mà hạ xuống. Hiện diện ở đây để
139
- * caller gọi thẳng, không phải rẽ nhánh theo platform ở mọi call site.
140
- */
141
- async finishUpload() {
142
- return;
143
- }
144
137
  async cancel(options) {
145
138
  var _a;
146
139
  if (!this.currentJobId)
@@ -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 { toBlobURL } from '@ffmpeg/util';\nimport {\n ALL_FORMATS,\n BlobSource,\n BufferTarget,\n Conversion,\n ConversionCanceledError,\n Input,\n Mp4OutputFormat,\n Output,\n Quality,\n canEncodeVideo,\n} from 'mediabunny';\n\nimport type {\n NativeVideoCompressorPlugin,\n CompressOptions,\n CompressResult,\n CancelOptions,\n CancelResult,\n} from './definitions';\nimport { CANCELLED } from './definitions';\n\ntype VideoQuality = NonNullable<CompressOptions['quality']>;\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 — and on both web engines below.\n */\nclass CancelledError extends Error {\n code = CANCELLED;\n constructor() {\n super('Đã hủy nén');\n this.name = 'CancelledError';\n }\n}\n\n/**\n * WebCodecs target per quality tier: cap height + a bitrate ceiling. The\n * quantizer (constant 26, CRF-like) drives quality where the encoder honours\n * it; H.264 on most browsers is bitrate-driven, so `bitrate` is the real knob.\n * fps is capped to 30 at call time (never upsampled). Audio is left to\n * mediabunny, which re-encodes to AAC for the MP4 container.\n */\nconst WEBCODECS_LADDER: Record<VideoQuality, { height: number; bitrate: number }> = {\n VERY_HIGH: { height: 1080, bitrate: 4000000 },\n HIGH: { height: 720, bitrate: 2000000 },\n MEDIUM: { height: 540, bitrate: 1200000 },\n LOW: { height: 480, bitrate: 1000000 },\n '360P': { height: 360, bitrate: 700000 },\n VERY_LOW: { height: 240, bitrate: 350000 },\n};\n\nconst WEBCODECS_QUANTIZER = 26;\nconst FPS_CAP = 30;\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. Only the ffmpeg\n * fallback ever pays this download; the WebCodecs primary path needs no core.\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 mediabunny conversion running now, so `cancel()` can abort it. */\n private activeConversion: Conversion | null = null;\n /** The job `compressVideo` is running right now, for `cancel({ jobId })`. */\n private currentJobId: string | null = null;\n private cancelRequested = false;\n\n async initialize(): Promise<{ success: boolean; message?: string }> {\n // WebCodecs (the primary path) needs no core download, so there is nothing\n // to warm. Eagerly loading the ffmpeg core here would pull ~30 MB for a\n // path most compresses never take. The fallback lazy-loads its core on\n // first use. Kept as a resolving no-op so callers that await it still pass.\n return { success: true, message: 'ready' };\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 the encode: cancelling\n // an engine only reaches work already handed to it, so a cancel during the\n // source fetch or before init has nothing to interrupt and would otherwise\n // 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 throwIfCancelled();\n\n const blob = await this.fetchSourceBlob(options.sourcePath);\n throwIfCancelled();\n\n const quality: VideoQuality = options.quality ?? 'MEDIUM';\n\n if (await this.canUseWebCodecs(quality)) {\n try {\n const result = await this.compressWithWebCodecs(blob, quality, throwIfCancelled);\n if (result) return result;\n // null → conversion invalid / tracks discarded → fall back to ffmpeg.\n } catch (e: any) {\n if (e?.code === CANCELLED) throw e; // user cancel: never fall back\n console.warn('[compress] WebCodecs failed, falling back to ffmpeg:', e);\n throwIfCancelled();\n }\n }\n\n return await this.compressWithFfmpeg(blob, quality, throwIfCancelled);\n } catch (error: any) {\n if (error?.code === CANCELLED) {\n console.log('[compress] cancelled');\n } else {\n console.error('[compress] failed:', error);\n }\n throw error;\n } finally {\n this.currentJobId = null;\n this.cancelRequested = false;\n this.activeConversion = null;\n }\n }\n\n /**\n * Kill whichever engine is mid-compress. Only one is ever active at a time,\n * so acting on both is safe.\n *\n * WebCodecs: `conversion.cancel()` makes the in-flight `execute()` reject\n * with `ConversionCanceledError`, surfaced as a `CancelledError` below.\n * ffmpeg: `exec()` runs synchronously inside the worker and never reaches a\n * message poll while encoding, so `worker.terminate()` is the only way to\n * stop it — its cost is the loaded core, which is why the core URLs are\n * cached above.\n */\n /**\n * Chỉ Android mới có foreground service để mà hạ xuống. Hiện diện ở đây để\n * caller gọi thẳng, không phải rẽ nhánh theo platform ở mọi call site.\n */\n async finishUpload(): Promise<void> {\n return;\n }\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 neither engine exists yet: the job may still be fetching\n // the source or inside loadFFmpeg, which read this flag at their boundaries.\n this.cancelRequested = true;\n\n const conversion = this.activeConversion;\n this.activeConversion = null;\n // Fire-and-forget: the CancelledError surfaces from the execute() rejection,\n // not from here; awaiting would only make cancel() slower.\n conversion?.cancel().catch(() => undefined);\n\n this.ffmpeg?.terminate();\n this.ffmpeg = null;\n this.isLoaded = false;\n\n return { cancelled: true };\n }\n\n private async fetchSourceBlob(sourcePath: string): Promise<Blob> {\n try {\n const response = await fetch(sourcePath);\n return await response.blob();\n } catch (err) {\n console.error('[compress] Error fetching source file:', err);\n throw new Error('Could not fetch source video file');\n }\n }\n\n private async canUseWebCodecs(quality: VideoQuality): Promise<boolean> {\n try {\n const { bitrate } = WEBCODECS_LADDER[quality];\n return await canEncodeVideo('avc', {\n quality: new Quality({ quantizer: WEBCODECS_QUANTIZER, bitrate }),\n });\n } catch {\n return false;\n }\n }\n\n private async compressWithWebCodecs(\n blob: Blob,\n quality: VideoQuality,\n throwIfCancelled: () => void,\n ): Promise<CompressResult | null> {\n const { height, bitrate } = WEBCODECS_LADDER[quality];\n\n const input = new Input({ formats: ALL_FORMATS, source: new BlobSource(blob) });\n const output = new Output({ format: new Mp4OutputFormat(), target: new BufferTarget() });\n\n // Cap fps at 30, but never upsample: probe the source and take the min.\n // A failed probe just omits frameRate, leaving the source rate untouched.\n let frameRate: number | undefined;\n try {\n const track = await input.getPrimaryVideoTrack();\n if (track) {\n const stats = await track.computePacketStats(100);\n if (stats.averagePacketRate > 0) {\n frameRate = Math.min(FPS_CAP, Math.round(stats.averagePacketRate));\n }\n }\n } catch {\n frameRate = undefined;\n }\n\n throwIfCancelled();\n\n const conversion = await Conversion.init({\n input,\n output,\n video: {\n height,\n quality: new Quality({ quantizer: WEBCODECS_QUANTIZER, bitrate }),\n hardwareAcceleration: 'prefer-hardware',\n ...(frameRate ? { frameRate } : {}),\n },\n });\n\n if (!conversion.isValid) {\n console.warn(\n '[compress] WebCodecs conversion invalid:',\n conversion.discardedTracks.map((t) => t.reason),\n );\n return null;\n }\n\n this.activeConversion = conversion;\n\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n conversion.onProgress = (progress) => {\n this.notifyListeners('onProgress', { status: 'progress', percent: Math.round(progress * 100) });\n };\n\n throwIfCancelled();\n try {\n await conversion.execute();\n } catch (e: any) {\n if (this.cancelRequested || e instanceof ConversionCanceledError) throw new CancelledError();\n throw e;\n }\n\n const buffer = output.target.buffer;\n if (!buffer) throw new Error('WebCodecs produced no output');\n\n const outBlob = new Blob([buffer], { type: 'video/mp4' });\n return { success: true, destPath: URL.createObjectURL(outBlob) };\n }\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 instance.on('progress', ({ progress }) => {\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 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 }\n\n private async compressWithFfmpeg(\n blob: Blob,\n quality: VideoQuality,\n throwIfCancelled: () => void,\n ): Promise<CompressResult> {\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 any\n // of the awaits below can hand back null on a reference the compiler\n // narrowed here — surfacing a cancel as a TypeError, not a CancelledError.\n const ffmpeg = this.ffmpeg;\n\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n\n const inputFileName = 'input.mp4';\n const outputFileName = 'output.mp4';\n\n let crf = '28';\n let scale = '';\n let audioBitrate = '128k';\n\n switch (quality) {\n case 'VERY_HIGH':\n crf = '23';\n scale = '-vf scale=-2:1080';\n audioBitrate = '128k';\n break;\n case 'HIGH':\n crf = '28';\n scale = '-vf scale=-2:720';\n audioBitrate = '128k';\n break;\n case 'MEDIUM':\n crf = '30';\n scale = '-vf scale=-2:540';\n audioBitrate = '96k';\n break;\n case 'LOW':\n crf = '32';\n scale = '-vf scale=-2:480';\n audioBitrate = '96k';\n break;\n case '360P':\n crf = '28';\n scale = '-vf scale=-2:360';\n audioBitrate = '64k';\n break;\n case 'VERY_LOW':\n crf = '35';\n scale = '-vf scale=-2:240';\n audioBitrate = '48k';\n break;\n }\n\n const inputData = new Uint8Array(await blob.arrayBuffer());\n\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 const args: string[] = ['-i', inputFileName];\n args.push('-c:v', 'libx264', '-preset', 'superfast', '-crf', crf);\n if (scale) {\n args.push(...scale.split(' '));\n }\n args.push('-c:a', 'aac', '-b:a', audioBitrate);\n args.push(outputFileName);\n\n try {\n await ffmpeg.exec(args);\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 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 await ffmpeg.deleteFile(inputFileName);\n await ffmpeg.deleteFile(outputFileName);\n\n const outBlob = new Blob([data as any], { type: 'video/mp4' });\n return { success: true, destPath: URL.createObjectURL(outBlob) };\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 { toBlobURL } from '@ffmpeg/util';\nimport {\n ALL_FORMATS,\n BlobSource,\n BufferTarget,\n Conversion,\n ConversionCanceledError,\n Input,\n Mp4OutputFormat,\n Output,\n Quality,\n canEncodeVideo,\n} from 'mediabunny';\n\nimport type {\n NativeVideoCompressorPlugin,\n CompressOptions,\n CompressResult,\n CancelOptions,\n CancelResult,\n} from './definitions';\nimport { CANCELLED } from './definitions';\n\ntype VideoQuality = NonNullable<CompressOptions['quality']>;\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 — and on both web engines below.\n */\nclass CancelledError extends Error {\n code = CANCELLED;\n constructor() {\n super('Đã hủy nén');\n this.name = 'CancelledError';\n }\n}\n\n/**\n * WebCodecs target per quality tier: cap height + a bitrate ceiling. The\n * quantizer (constant 26, CRF-like) drives quality where the encoder honours\n * it; H.264 on most browsers is bitrate-driven, so `bitrate` is the real knob.\n * fps is capped to 30 at call time (never upsampled). Audio is left to\n * mediabunny, which re-encodes to AAC for the MP4 container.\n */\nconst WEBCODECS_LADDER: Record<VideoQuality, { height: number; bitrate: number }> = {\n VERY_HIGH: { height: 1080, bitrate: 4000000 },\n HIGH: { height: 720, bitrate: 2000000 },\n MEDIUM: { height: 540, bitrate: 1200000 },\n LOW: { height: 480, bitrate: 1000000 },\n '360P': { height: 360, bitrate: 700000 },\n VERY_LOW: { height: 240, bitrate: 350000 },\n};\n\nconst WEBCODECS_QUANTIZER = 26;\nconst FPS_CAP = 30;\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. Only the ffmpeg\n * fallback ever pays this download; the WebCodecs primary path needs no core.\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 mediabunny conversion running now, so `cancel()` can abort it. */\n private activeConversion: Conversion | null = null;\n /** The job `compressVideo` is running right now, for `cancel({ jobId })`. */\n private currentJobId: string | null = null;\n private cancelRequested = false;\n\n async initialize(): Promise<{ success: boolean; message?: string }> {\n // WebCodecs (the primary path) needs no core download, so there is nothing\n // to warm. Eagerly loading the ffmpeg core here would pull ~30 MB for a\n // path most compresses never take. The fallback lazy-loads its core on\n // first use. Kept as a resolving no-op so callers that await it still pass.\n return { success: true, message: 'ready' };\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 the encode: cancelling\n // an engine only reaches work already handed to it, so a cancel during the\n // source fetch or before init has nothing to interrupt and would otherwise\n // 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 throwIfCancelled();\n\n const blob = await this.fetchSourceBlob(options.sourcePath);\n throwIfCancelled();\n\n const quality: VideoQuality = options.quality ?? 'MEDIUM';\n\n if (await this.canUseWebCodecs(quality)) {\n try {\n const result = await this.compressWithWebCodecs(blob, quality, throwIfCancelled);\n if (result) return result;\n // null → conversion invalid / tracks discarded → fall back to ffmpeg.\n } catch (e: any) {\n if (e?.code === CANCELLED) throw e; // user cancel: never fall back\n console.warn('[compress] WebCodecs failed, falling back to ffmpeg:', e);\n throwIfCancelled();\n }\n }\n\n return await this.compressWithFfmpeg(blob, quality, throwIfCancelled);\n } catch (error: any) {\n if (error?.code === CANCELLED) {\n console.log('[compress] cancelled');\n } else {\n console.error('[compress] failed:', error);\n }\n throw error;\n } finally {\n this.currentJobId = null;\n this.cancelRequested = false;\n this.activeConversion = null;\n }\n }\n\n /**\n * Kill whichever engine is mid-compress. Only one is ever active at a time,\n * so acting on both is safe.\n *\n * WebCodecs: `conversion.cancel()` makes the in-flight `execute()` reject\n * with `ConversionCanceledError`, surfaced as a `CancelledError` below.\n * ffmpeg: `exec()` runs synchronously inside the worker and never reaches a\n * message poll while encoding, so `worker.terminate()` is the only way to\n * stop it — its cost is the loaded core, which is why the core URLs are\n * 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 neither engine exists yet: the job may still be fetching\n // the source or inside loadFFmpeg, which read this flag at their boundaries.\n this.cancelRequested = true;\n\n const conversion = this.activeConversion;\n this.activeConversion = null;\n // Fire-and-forget: the CancelledError surfaces from the execute() rejection,\n // not from here; awaiting would only make cancel() slower.\n conversion?.cancel().catch(() => undefined);\n\n this.ffmpeg?.terminate();\n this.ffmpeg = null;\n this.isLoaded = false;\n\n return { cancelled: true };\n }\n\n private async fetchSourceBlob(sourcePath: string): Promise<Blob> {\n try {\n const response = await fetch(sourcePath);\n return await response.blob();\n } catch (err) {\n console.error('[compress] Error fetching source file:', err);\n throw new Error('Could not fetch source video file');\n }\n }\n\n private async canUseWebCodecs(quality: VideoQuality): Promise<boolean> {\n try {\n const { bitrate } = WEBCODECS_LADDER[quality];\n return await canEncodeVideo('avc', {\n quality: new Quality({ quantizer: WEBCODECS_QUANTIZER, bitrate }),\n });\n } catch {\n return false;\n }\n }\n\n private async compressWithWebCodecs(\n blob: Blob,\n quality: VideoQuality,\n throwIfCancelled: () => void,\n ): Promise<CompressResult | null> {\n const { height, bitrate } = WEBCODECS_LADDER[quality];\n\n const input = new Input({ formats: ALL_FORMATS, source: new BlobSource(blob) });\n const output = new Output({ format: new Mp4OutputFormat(), target: new BufferTarget() });\n\n // Cap fps at 30, but never upsample: probe the source and take the min.\n // A failed probe just omits frameRate, leaving the source rate untouched.\n let frameRate: number | undefined;\n try {\n const track = await input.getPrimaryVideoTrack();\n if (track) {\n const stats = await track.computePacketStats(100);\n if (stats.averagePacketRate > 0) {\n frameRate = Math.min(FPS_CAP, Math.round(stats.averagePacketRate));\n }\n }\n } catch {\n frameRate = undefined;\n }\n\n throwIfCancelled();\n\n const conversion = await Conversion.init({\n input,\n output,\n video: {\n height,\n quality: new Quality({ quantizer: WEBCODECS_QUANTIZER, bitrate }),\n hardwareAcceleration: 'prefer-hardware',\n ...(frameRate ? { frameRate } : {}),\n },\n });\n\n if (!conversion.isValid) {\n console.warn(\n '[compress] WebCodecs conversion invalid:',\n conversion.discardedTracks.map((t) => t.reason),\n );\n return null;\n }\n\n this.activeConversion = conversion;\n\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n conversion.onProgress = (progress) => {\n this.notifyListeners('onProgress', { status: 'progress', percent: Math.round(progress * 100) });\n };\n\n throwIfCancelled();\n try {\n await conversion.execute();\n } catch (e: any) {\n if (this.cancelRequested || e instanceof ConversionCanceledError) throw new CancelledError();\n throw e;\n }\n\n const buffer = output.target.buffer;\n if (!buffer) throw new Error('WebCodecs produced no output');\n\n const outBlob = new Blob([buffer], { type: 'video/mp4' });\n return { success: true, destPath: URL.createObjectURL(outBlob) };\n }\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 instance.on('progress', ({ progress }) => {\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 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 }\n\n private async compressWithFfmpeg(\n blob: Blob,\n quality: VideoQuality,\n throwIfCancelled: () => void,\n ): Promise<CompressResult> {\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 any\n // of the awaits below can hand back null on a reference the compiler\n // narrowed here — surfacing a cancel as a TypeError, not a CancelledError.\n const ffmpeg = this.ffmpeg;\n\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n\n const inputFileName = 'input.mp4';\n const outputFileName = 'output.mp4';\n\n let crf = '28';\n let scale = '';\n let audioBitrate = '128k';\n\n switch (quality) {\n case 'VERY_HIGH':\n crf = '23';\n scale = '-vf scale=-2:1080';\n audioBitrate = '128k';\n break;\n case 'HIGH':\n crf = '28';\n scale = '-vf scale=-2:720';\n audioBitrate = '128k';\n break;\n case 'MEDIUM':\n crf = '30';\n scale = '-vf scale=-2:540';\n audioBitrate = '96k';\n break;\n case 'LOW':\n crf = '32';\n scale = '-vf scale=-2:480';\n audioBitrate = '96k';\n break;\n case '360P':\n crf = '28';\n scale = '-vf scale=-2:360';\n audioBitrate = '64k';\n break;\n case 'VERY_LOW':\n crf = '35';\n scale = '-vf scale=-2:240';\n audioBitrate = '48k';\n break;\n }\n\n const inputData = new Uint8Array(await blob.arrayBuffer());\n\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 const args: string[] = ['-i', inputFileName];\n args.push('-c:v', 'libx264', '-preset', 'superfast', '-crf', crf);\n if (scale) {\n args.push(...scale.split(' '));\n }\n args.push('-c:a', 'aac', '-b:a', audioBitrate);\n args.push(outputFileName);\n\n try {\n await ffmpeg.exec(args);\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 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 await ffmpeg.deleteFile(inputFileName);\n await ffmpeg.deleteFile(outputFileName);\n\n const outBlob = new Blob([data as any], { type: 'video/mp4' });\n return { success: true, destPath: URL.createObjectURL(outBlob) };\n }\n}\n"]}
@@ -147,13 +147,6 @@ class NativeVideoCompressorWeb extends core.WebPlugin {
147
147
  * stop it — its cost is the loaded core, which is why the core URLs are
148
148
  * cached above.
149
149
  */
150
- /**
151
- * Chỉ Android mới có foreground service để mà hạ xuống. Hiện diện ở đây để
152
- * caller gọi thẳng, không phải rẽ nhánh theo platform ở mọi call site.
153
- */
154
- async finishUpload() {
155
- return;
156
- }
157
150
  async cancel(options) {
158
151
  var _a;
159
152
  if (!this.currentJobId)
@@ -1 +1 @@
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 { toBlobURL } from '@ffmpeg/util';\nimport { ALL_FORMATS, BlobSource, BufferTarget, Conversion, ConversionCanceledError, Input, Mp4OutputFormat, Output, Quality, canEncodeVideo, } from 'mediabunny';\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 — and on both web engines below.\n */\nclass CancelledError extends Error {\n constructor() {\n super('Đã hủy nén');\n this.code = CANCELLED;\n this.name = 'CancelledError';\n }\n}\n/**\n * WebCodecs target per quality tier: cap height + a bitrate ceiling. The\n * quantizer (constant 26, CRF-like) drives quality where the encoder honours\n * it; H.264 on most browsers is bitrate-driven, so `bitrate` is the real knob.\n * fps is capped to 30 at call time (never upsampled). Audio is left to\n * mediabunny, which re-encodes to AAC for the MP4 container.\n */\nconst WEBCODECS_LADDER = {\n VERY_HIGH: { height: 1080, bitrate: 4000000 },\n HIGH: { height: 720, bitrate: 2000000 },\n MEDIUM: { height: 540, bitrate: 1200000 },\n LOW: { height: 480, bitrate: 1000000 },\n '360P': { height: 360, bitrate: 700000 },\n VERY_LOW: { height: 240, bitrate: 350000 },\n};\nconst WEBCODECS_QUANTIZER = 26;\nconst FPS_CAP = 30;\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. Only the ffmpeg\n * fallback ever pays this download; the WebCodecs primary path needs no core.\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 mediabunny conversion running now, so `cancel()` can abort it. */\n this.activeConversion = null;\n /** The job `compressVideo` is running right now, for `cancel({ jobId })`. */\n this.currentJobId = null;\n this.cancelRequested = false;\n }\n async initialize() {\n // WebCodecs (the primary path) needs no core download, so there is nothing\n // to warm. Eagerly loading the ffmpeg core here would pull ~30 MB for a\n // path most compresses never take. The fallback lazy-loads its core on\n // first use. Kept as a resolving no-op so callers that await it still pass.\n return { success: true, message: 'ready' };\n }\n async compressVideo(options) {\n var _a, _b;\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 the encode: cancelling\n // an engine only reaches work already handed to it, so a cancel during the\n // source fetch or before init has nothing to interrupt and would otherwise\n // 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 throwIfCancelled();\n const blob = await this.fetchSourceBlob(options.sourcePath);\n throwIfCancelled();\n const quality = (_b = options.quality) !== null && _b !== void 0 ? _b : 'MEDIUM';\n if (await this.canUseWebCodecs(quality)) {\n try {\n const result = await this.compressWithWebCodecs(blob, quality, throwIfCancelled);\n if (result)\n return result;\n // null → conversion invalid / tracks discarded → fall back to ffmpeg.\n }\n catch (e) {\n if ((e === null || e === void 0 ? void 0 : e.code) === CANCELLED)\n throw e; // user cancel: never fall back\n console.warn('[compress] WebCodecs failed, falling back to ffmpeg:', e);\n throwIfCancelled();\n }\n }\n return await this.compressWithFfmpeg(blob, quality, throwIfCancelled);\n }\n catch (error) {\n if ((error === null || error === void 0 ? void 0 : error.code) === CANCELLED) {\n console.log('[compress] cancelled');\n }\n else {\n console.error('[compress] failed:', error);\n }\n throw error;\n }\n finally {\n this.currentJobId = null;\n this.cancelRequested = false;\n this.activeConversion = null;\n }\n }\n /**\n * Kill whichever engine is mid-compress. Only one is ever active at a time,\n * so acting on both is safe.\n *\n * WebCodecs: `conversion.cancel()` makes the in-flight `execute()` reject\n * with `ConversionCanceledError`, surfaced as a `CancelledError` below.\n * ffmpeg: `exec()` runs synchronously inside the worker and never reaches a\n * message poll while encoding, so `worker.terminate()` is the only way to\n * stop it — its cost is the loaded core, which is why the core URLs are\n * cached above.\n */\n /**\n * Chỉ Android mới có foreground service để mà hạ xuống. Hiện diện ở đây để\n * caller gọi thẳng, không phải rẽ nhánh theo platform ở mọi call site.\n */\n async finishUpload() {\n return;\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 neither engine exists yet: the job may still be fetching\n // the source or inside loadFFmpeg, which read this flag at their boundaries.\n this.cancelRequested = true;\n const conversion = this.activeConversion;\n this.activeConversion = null;\n // Fire-and-forget: the CancelledError surfaces from the execute() rejection,\n // not from here; awaiting would only make cancel() slower.\n conversion === null || conversion === void 0 ? void 0 : conversion.cancel().catch(() => undefined);\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 fetchSourceBlob(sourcePath) {\n try {\n const response = await fetch(sourcePath);\n return await response.blob();\n }\n catch (err) {\n console.error('[compress] Error fetching source file:', err);\n throw new Error('Could not fetch source video file');\n }\n }\n async canUseWebCodecs(quality) {\n try {\n const { bitrate } = WEBCODECS_LADDER[quality];\n return await canEncodeVideo('avc', {\n quality: new Quality({ quantizer: WEBCODECS_QUANTIZER, bitrate }),\n });\n }\n catch (_a) {\n return false;\n }\n }\n async compressWithWebCodecs(blob, quality, throwIfCancelled) {\n const { height, bitrate } = WEBCODECS_LADDER[quality];\n const input = new Input({ formats: ALL_FORMATS, source: new BlobSource(blob) });\n const output = new Output({ format: new Mp4OutputFormat(), target: new BufferTarget() });\n // Cap fps at 30, but never upsample: probe the source and take the min.\n // A failed probe just omits frameRate, leaving the source rate untouched.\n let frameRate;\n try {\n const track = await input.getPrimaryVideoTrack();\n if (track) {\n const stats = await track.computePacketStats(100);\n if (stats.averagePacketRate > 0) {\n frameRate = Math.min(FPS_CAP, Math.round(stats.averagePacketRate));\n }\n }\n }\n catch (_a) {\n frameRate = undefined;\n }\n throwIfCancelled();\n const conversion = await Conversion.init({\n input,\n output,\n video: Object.assign({ height, quality: new Quality({ quantizer: WEBCODECS_QUANTIZER, bitrate }), hardwareAcceleration: 'prefer-hardware' }, (frameRate ? { frameRate } : {})),\n });\n if (!conversion.isValid) {\n console.warn('[compress] WebCodecs conversion invalid:', conversion.discardedTracks.map((t) => t.reason));\n return null;\n }\n this.activeConversion = conversion;\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n conversion.onProgress = (progress) => {\n this.notifyListeners('onProgress', { status: 'progress', percent: Math.round(progress * 100) });\n };\n throwIfCancelled();\n try {\n await conversion.execute();\n }\n catch (e) {\n if (this.cancelRequested || e instanceof ConversionCanceledError)\n throw new CancelledError();\n throw e;\n }\n const buffer = output.target.buffer;\n if (!buffer)\n throw new Error('WebCodecs produced no output');\n const outBlob = new Blob([buffer], { type: 'video/mp4' });\n return { success: true, destPath: URL.createObjectURL(outBlob) };\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 instance.on('progress', ({ progress }) => {\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 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 }\n async compressWithFfmpeg(blob, quality, throwIfCancelled) {\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 any\n // of the awaits below can hand back null on a reference the compiler\n // narrowed here — surfacing a cancel as a TypeError, not a CancelledError.\n const ffmpeg = this.ffmpeg;\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n const inputFileName = 'input.mp4';\n const outputFileName = 'output.mp4';\n let crf = '28';\n let scale = '';\n let audioBitrate = '128k';\n switch (quality) {\n case 'VERY_HIGH':\n crf = '23';\n scale = '-vf scale=-2:1080';\n audioBitrate = '128k';\n break;\n case 'HIGH':\n crf = '28';\n scale = '-vf scale=-2:720';\n audioBitrate = '128k';\n break;\n case 'MEDIUM':\n crf = '30';\n scale = '-vf scale=-2:540';\n audioBitrate = '96k';\n break;\n case 'LOW':\n crf = '32';\n scale = '-vf scale=-2:480';\n audioBitrate = '96k';\n break;\n case '360P':\n crf = '28';\n scale = '-vf scale=-2:360';\n audioBitrate = '64k';\n break;\n case 'VERY_LOW':\n crf = '35';\n scale = '-vf scale=-2:240';\n audioBitrate = '48k';\n break;\n }\n const inputData = new Uint8Array(await blob.arrayBuffer());\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 const args = ['-i', inputFileName];\n args.push('-c:v', 'libx264', '-preset', 'superfast', '-crf', crf);\n if (scale) {\n args.push(...scale.split(' '));\n }\n args.push('-c:a', 'aac', '-b:a', audioBitrate);\n args.push(outputFileName);\n try {\n await ffmpeg.exec(args);\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 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 await ffmpeg.deleteFile(inputFileName);\n await ffmpeg.deleteFile(outputFileName);\n const outBlob = new Blob([data], { type: 'video/mp4' });\n return { success: true, destPath: URL.createObjectURL(outBlob) };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","toBlobURL","WebPlugin","canEncodeVideo","Quality","Input","ALL_FORMATS","BlobSource","Output","Mp4OutputFormat","BufferTarget","Conversion","ConversionCanceledError","FFmpeg"],"mappings":"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"}
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 { toBlobURL } from '@ffmpeg/util';\nimport { ALL_FORMATS, BlobSource, BufferTarget, Conversion, ConversionCanceledError, Input, Mp4OutputFormat, Output, Quality, canEncodeVideo, } from 'mediabunny';\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 — and on both web engines below.\n */\nclass CancelledError extends Error {\n constructor() {\n super('Đã hủy nén');\n this.code = CANCELLED;\n this.name = 'CancelledError';\n }\n}\n/**\n * WebCodecs target per quality tier: cap height + a bitrate ceiling. The\n * quantizer (constant 26, CRF-like) drives quality where the encoder honours\n * it; H.264 on most browsers is bitrate-driven, so `bitrate` is the real knob.\n * fps is capped to 30 at call time (never upsampled). Audio is left to\n * mediabunny, which re-encodes to AAC for the MP4 container.\n */\nconst WEBCODECS_LADDER = {\n VERY_HIGH: { height: 1080, bitrate: 4000000 },\n HIGH: { height: 720, bitrate: 2000000 },\n MEDIUM: { height: 540, bitrate: 1200000 },\n LOW: { height: 480, bitrate: 1000000 },\n '360P': { height: 360, bitrate: 700000 },\n VERY_LOW: { height: 240, bitrate: 350000 },\n};\nconst WEBCODECS_QUANTIZER = 26;\nconst FPS_CAP = 30;\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. Only the ffmpeg\n * fallback ever pays this download; the WebCodecs primary path needs no core.\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 mediabunny conversion running now, so `cancel()` can abort it. */\n this.activeConversion = null;\n /** The job `compressVideo` is running right now, for `cancel({ jobId })`. */\n this.currentJobId = null;\n this.cancelRequested = false;\n }\n async initialize() {\n // WebCodecs (the primary path) needs no core download, so there is nothing\n // to warm. Eagerly loading the ffmpeg core here would pull ~30 MB for a\n // path most compresses never take. The fallback lazy-loads its core on\n // first use. Kept as a resolving no-op so callers that await it still pass.\n return { success: true, message: 'ready' };\n }\n async compressVideo(options) {\n var _a, _b;\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 the encode: cancelling\n // an engine only reaches work already handed to it, so a cancel during the\n // source fetch or before init has nothing to interrupt and would otherwise\n // 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 throwIfCancelled();\n const blob = await this.fetchSourceBlob(options.sourcePath);\n throwIfCancelled();\n const quality = (_b = options.quality) !== null && _b !== void 0 ? _b : 'MEDIUM';\n if (await this.canUseWebCodecs(quality)) {\n try {\n const result = await this.compressWithWebCodecs(blob, quality, throwIfCancelled);\n if (result)\n return result;\n // null → conversion invalid / tracks discarded → fall back to ffmpeg.\n }\n catch (e) {\n if ((e === null || e === void 0 ? void 0 : e.code) === CANCELLED)\n throw e; // user cancel: never fall back\n console.warn('[compress] WebCodecs failed, falling back to ffmpeg:', e);\n throwIfCancelled();\n }\n }\n return await this.compressWithFfmpeg(blob, quality, throwIfCancelled);\n }\n catch (error) {\n if ((error === null || error === void 0 ? void 0 : error.code) === CANCELLED) {\n console.log('[compress] cancelled');\n }\n else {\n console.error('[compress] failed:', error);\n }\n throw error;\n }\n finally {\n this.currentJobId = null;\n this.cancelRequested = false;\n this.activeConversion = null;\n }\n }\n /**\n * Kill whichever engine is mid-compress. Only one is ever active at a time,\n * so acting on both is safe.\n *\n * WebCodecs: `conversion.cancel()` makes the in-flight `execute()` reject\n * with `ConversionCanceledError`, surfaced as a `CancelledError` below.\n * ffmpeg: `exec()` runs synchronously inside the worker and never reaches a\n * message poll while encoding, so `worker.terminate()` is the only way to\n * stop it — its cost is the loaded core, which is why the core URLs are\n * 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 neither engine exists yet: the job may still be fetching\n // the source or inside loadFFmpeg, which read this flag at their boundaries.\n this.cancelRequested = true;\n const conversion = this.activeConversion;\n this.activeConversion = null;\n // Fire-and-forget: the CancelledError surfaces from the execute() rejection,\n // not from here; awaiting would only make cancel() slower.\n conversion === null || conversion === void 0 ? void 0 : conversion.cancel().catch(() => undefined);\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 fetchSourceBlob(sourcePath) {\n try {\n const response = await fetch(sourcePath);\n return await response.blob();\n }\n catch (err) {\n console.error('[compress] Error fetching source file:', err);\n throw new Error('Could not fetch source video file');\n }\n }\n async canUseWebCodecs(quality) {\n try {\n const { bitrate } = WEBCODECS_LADDER[quality];\n return await canEncodeVideo('avc', {\n quality: new Quality({ quantizer: WEBCODECS_QUANTIZER, bitrate }),\n });\n }\n catch (_a) {\n return false;\n }\n }\n async compressWithWebCodecs(blob, quality, throwIfCancelled) {\n const { height, bitrate } = WEBCODECS_LADDER[quality];\n const input = new Input({ formats: ALL_FORMATS, source: new BlobSource(blob) });\n const output = new Output({ format: new Mp4OutputFormat(), target: new BufferTarget() });\n // Cap fps at 30, but never upsample: probe the source and take the min.\n // A failed probe just omits frameRate, leaving the source rate untouched.\n let frameRate;\n try {\n const track = await input.getPrimaryVideoTrack();\n if (track) {\n const stats = await track.computePacketStats(100);\n if (stats.averagePacketRate > 0) {\n frameRate = Math.min(FPS_CAP, Math.round(stats.averagePacketRate));\n }\n }\n }\n catch (_a) {\n frameRate = undefined;\n }\n throwIfCancelled();\n const conversion = await Conversion.init({\n input,\n output,\n video: Object.assign({ height, quality: new Quality({ quantizer: WEBCODECS_QUANTIZER, bitrate }), hardwareAcceleration: 'prefer-hardware' }, (frameRate ? { frameRate } : {})),\n });\n if (!conversion.isValid) {\n console.warn('[compress] WebCodecs conversion invalid:', conversion.discardedTracks.map((t) => t.reason));\n return null;\n }\n this.activeConversion = conversion;\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n conversion.onProgress = (progress) => {\n this.notifyListeners('onProgress', { status: 'progress', percent: Math.round(progress * 100) });\n };\n throwIfCancelled();\n try {\n await conversion.execute();\n }\n catch (e) {\n if (this.cancelRequested || e instanceof ConversionCanceledError)\n throw new CancelledError();\n throw e;\n }\n const buffer = output.target.buffer;\n if (!buffer)\n throw new Error('WebCodecs produced no output');\n const outBlob = new Blob([buffer], { type: 'video/mp4' });\n return { success: true, destPath: URL.createObjectURL(outBlob) };\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 instance.on('progress', ({ progress }) => {\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 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 }\n async compressWithFfmpeg(blob, quality, throwIfCancelled) {\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 any\n // of the awaits below can hand back null on a reference the compiler\n // narrowed here — surfacing a cancel as a TypeError, not a CancelledError.\n const ffmpeg = this.ffmpeg;\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n const inputFileName = 'input.mp4';\n const outputFileName = 'output.mp4';\n let crf = '28';\n let scale = '';\n let audioBitrate = '128k';\n switch (quality) {\n case 'VERY_HIGH':\n crf = '23';\n scale = '-vf scale=-2:1080';\n audioBitrate = '128k';\n break;\n case 'HIGH':\n crf = '28';\n scale = '-vf scale=-2:720';\n audioBitrate = '128k';\n break;\n case 'MEDIUM':\n crf = '30';\n scale = '-vf scale=-2:540';\n audioBitrate = '96k';\n break;\n case 'LOW':\n crf = '32';\n scale = '-vf scale=-2:480';\n audioBitrate = '96k';\n break;\n case '360P':\n crf = '28';\n scale = '-vf scale=-2:360';\n audioBitrate = '64k';\n break;\n case 'VERY_LOW':\n crf = '35';\n scale = '-vf scale=-2:240';\n audioBitrate = '48k';\n break;\n }\n const inputData = new Uint8Array(await blob.arrayBuffer());\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 const args = ['-i', inputFileName];\n args.push('-c:v', 'libx264', '-preset', 'superfast', '-crf', crf);\n if (scale) {\n args.push(...scale.split(' '));\n }\n args.push('-c:a', 'aac', '-b:a', audioBitrate);\n args.push(outputFileName);\n try {\n await ffmpeg.exec(args);\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 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 await ffmpeg.deleteFile(inputFileName);\n await ffmpeg.deleteFile(outputFileName);\n const outBlob = new Blob([data], { type: 'video/mp4' });\n return { success: true, destPath: URL.createObjectURL(outBlob) };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","toBlobURL","WebPlugin","canEncodeVideo","Quality","Input","ALL_FORMATS","BlobSource","Output","Mp4OutputFormat","BufferTarget","Conversion","ConversionCanceledError","FFmpeg"],"mappings":"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"}
package/dist/plugin.js CHANGED
@@ -143,13 +143,6 @@ var capacitorNativeVideoCompressor = (function (exports, core, ffmpeg, util, med
143
143
  * stop it — its cost is the loaded core, which is why the core URLs are
144
144
  * cached above.
145
145
  */
146
- /**
147
- * Chỉ Android mới có foreground service để mà hạ xuống. Hiện diện ở đây để
148
- * caller gọi thẳng, không phải rẽ nhánh theo platform ở mọi call site.
149
- */
150
- async finishUpload() {
151
- return;
152
- }
153
146
  async cancel(options) {
154
147
  var _a;
155
148
  if (!this.currentJobId)
@@ -1 +1 @@
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 { toBlobURL } from '@ffmpeg/util';\nimport { ALL_FORMATS, BlobSource, BufferTarget, Conversion, ConversionCanceledError, Input, Mp4OutputFormat, Output, Quality, canEncodeVideo, } from 'mediabunny';\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 — and on both web engines below.\n */\nclass CancelledError extends Error {\n constructor() {\n super('Đã hủy nén');\n this.code = CANCELLED;\n this.name = 'CancelledError';\n }\n}\n/**\n * WebCodecs target per quality tier: cap height + a bitrate ceiling. The\n * quantizer (constant 26, CRF-like) drives quality where the encoder honours\n * it; H.264 on most browsers is bitrate-driven, so `bitrate` is the real knob.\n * fps is capped to 30 at call time (never upsampled). Audio is left to\n * mediabunny, which re-encodes to AAC for the MP4 container.\n */\nconst WEBCODECS_LADDER = {\n VERY_HIGH: { height: 1080, bitrate: 4000000 },\n HIGH: { height: 720, bitrate: 2000000 },\n MEDIUM: { height: 540, bitrate: 1200000 },\n LOW: { height: 480, bitrate: 1000000 },\n '360P': { height: 360, bitrate: 700000 },\n VERY_LOW: { height: 240, bitrate: 350000 },\n};\nconst WEBCODECS_QUANTIZER = 26;\nconst FPS_CAP = 30;\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. Only the ffmpeg\n * fallback ever pays this download; the WebCodecs primary path needs no core.\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 mediabunny conversion running now, so `cancel()` can abort it. */\n this.activeConversion = null;\n /** The job `compressVideo` is running right now, for `cancel({ jobId })`. */\n this.currentJobId = null;\n this.cancelRequested = false;\n }\n async initialize() {\n // WebCodecs (the primary path) needs no core download, so there is nothing\n // to warm. Eagerly loading the ffmpeg core here would pull ~30 MB for a\n // path most compresses never take. The fallback lazy-loads its core on\n // first use. Kept as a resolving no-op so callers that await it still pass.\n return { success: true, message: 'ready' };\n }\n async compressVideo(options) {\n var _a, _b;\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 the encode: cancelling\n // an engine only reaches work already handed to it, so a cancel during the\n // source fetch or before init has nothing to interrupt and would otherwise\n // 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 throwIfCancelled();\n const blob = await this.fetchSourceBlob(options.sourcePath);\n throwIfCancelled();\n const quality = (_b = options.quality) !== null && _b !== void 0 ? _b : 'MEDIUM';\n if (await this.canUseWebCodecs(quality)) {\n try {\n const result = await this.compressWithWebCodecs(blob, quality, throwIfCancelled);\n if (result)\n return result;\n // null → conversion invalid / tracks discarded → fall back to ffmpeg.\n }\n catch (e) {\n if ((e === null || e === void 0 ? void 0 : e.code) === CANCELLED)\n throw e; // user cancel: never fall back\n console.warn('[compress] WebCodecs failed, falling back to ffmpeg:', e);\n throwIfCancelled();\n }\n }\n return await this.compressWithFfmpeg(blob, quality, throwIfCancelled);\n }\n catch (error) {\n if ((error === null || error === void 0 ? void 0 : error.code) === CANCELLED) {\n console.log('[compress] cancelled');\n }\n else {\n console.error('[compress] failed:', error);\n }\n throw error;\n }\n finally {\n this.currentJobId = null;\n this.cancelRequested = false;\n this.activeConversion = null;\n }\n }\n /**\n * Kill whichever engine is mid-compress. Only one is ever active at a time,\n * so acting on both is safe.\n *\n * WebCodecs: `conversion.cancel()` makes the in-flight `execute()` reject\n * with `ConversionCanceledError`, surfaced as a `CancelledError` below.\n * ffmpeg: `exec()` runs synchronously inside the worker and never reaches a\n * message poll while encoding, so `worker.terminate()` is the only way to\n * stop it — its cost is the loaded core, which is why the core URLs are\n * cached above.\n */\n /**\n * Chỉ Android mới có foreground service để mà hạ xuống. Hiện diện ở đây để\n * caller gọi thẳng, không phải rẽ nhánh theo platform ở mọi call site.\n */\n async finishUpload() {\n return;\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 neither engine exists yet: the job may still be fetching\n // the source or inside loadFFmpeg, which read this flag at their boundaries.\n this.cancelRequested = true;\n const conversion = this.activeConversion;\n this.activeConversion = null;\n // Fire-and-forget: the CancelledError surfaces from the execute() rejection,\n // not from here; awaiting would only make cancel() slower.\n conversion === null || conversion === void 0 ? void 0 : conversion.cancel().catch(() => undefined);\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 fetchSourceBlob(sourcePath) {\n try {\n const response = await fetch(sourcePath);\n return await response.blob();\n }\n catch (err) {\n console.error('[compress] Error fetching source file:', err);\n throw new Error('Could not fetch source video file');\n }\n }\n async canUseWebCodecs(quality) {\n try {\n const { bitrate } = WEBCODECS_LADDER[quality];\n return await canEncodeVideo('avc', {\n quality: new Quality({ quantizer: WEBCODECS_QUANTIZER, bitrate }),\n });\n }\n catch (_a) {\n return false;\n }\n }\n async compressWithWebCodecs(blob, quality, throwIfCancelled) {\n const { height, bitrate } = WEBCODECS_LADDER[quality];\n const input = new Input({ formats: ALL_FORMATS, source: new BlobSource(blob) });\n const output = new Output({ format: new Mp4OutputFormat(), target: new BufferTarget() });\n // Cap fps at 30, but never upsample: probe the source and take the min.\n // A failed probe just omits frameRate, leaving the source rate untouched.\n let frameRate;\n try {\n const track = await input.getPrimaryVideoTrack();\n if (track) {\n const stats = await track.computePacketStats(100);\n if (stats.averagePacketRate > 0) {\n frameRate = Math.min(FPS_CAP, Math.round(stats.averagePacketRate));\n }\n }\n }\n catch (_a) {\n frameRate = undefined;\n }\n throwIfCancelled();\n const conversion = await Conversion.init({\n input,\n output,\n video: Object.assign({ height, quality: new Quality({ quantizer: WEBCODECS_QUANTIZER, bitrate }), hardwareAcceleration: 'prefer-hardware' }, (frameRate ? { frameRate } : {})),\n });\n if (!conversion.isValid) {\n console.warn('[compress] WebCodecs conversion invalid:', conversion.discardedTracks.map((t) => t.reason));\n return null;\n }\n this.activeConversion = conversion;\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n conversion.onProgress = (progress) => {\n this.notifyListeners('onProgress', { status: 'progress', percent: Math.round(progress * 100) });\n };\n throwIfCancelled();\n try {\n await conversion.execute();\n }\n catch (e) {\n if (this.cancelRequested || e instanceof ConversionCanceledError)\n throw new CancelledError();\n throw e;\n }\n const buffer = output.target.buffer;\n if (!buffer)\n throw new Error('WebCodecs produced no output');\n const outBlob = new Blob([buffer], { type: 'video/mp4' });\n return { success: true, destPath: URL.createObjectURL(outBlob) };\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 instance.on('progress', ({ progress }) => {\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 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 }\n async compressWithFfmpeg(blob, quality, throwIfCancelled) {\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 any\n // of the awaits below can hand back null on a reference the compiler\n // narrowed here — surfacing a cancel as a TypeError, not a CancelledError.\n const ffmpeg = this.ffmpeg;\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n const inputFileName = 'input.mp4';\n const outputFileName = 'output.mp4';\n let crf = '28';\n let scale = '';\n let audioBitrate = '128k';\n switch (quality) {\n case 'VERY_HIGH':\n crf = '23';\n scale = '-vf scale=-2:1080';\n audioBitrate = '128k';\n break;\n case 'HIGH':\n crf = '28';\n scale = '-vf scale=-2:720';\n audioBitrate = '128k';\n break;\n case 'MEDIUM':\n crf = '30';\n scale = '-vf scale=-2:540';\n audioBitrate = '96k';\n break;\n case 'LOW':\n crf = '32';\n scale = '-vf scale=-2:480';\n audioBitrate = '96k';\n break;\n case '360P':\n crf = '28';\n scale = '-vf scale=-2:360';\n audioBitrate = '64k';\n break;\n case 'VERY_LOW':\n crf = '35';\n scale = '-vf scale=-2:240';\n audioBitrate = '48k';\n break;\n }\n const inputData = new Uint8Array(await blob.arrayBuffer());\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 const args = ['-i', inputFileName];\n args.push('-c:v', 'libx264', '-preset', 'superfast', '-crf', crf);\n if (scale) {\n args.push(...scale.split(' '));\n }\n args.push('-c:a', 'aac', '-b:a', audioBitrate);\n args.push(outputFileName);\n try {\n await ffmpeg.exec(args);\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 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 await ffmpeg.deleteFile(inputFileName);\n await ffmpeg.deleteFile(outputFileName);\n const outBlob = new Blob([data], { type: 'video/mp4' });\n return { success: true, destPath: URL.createObjectURL(outBlob) };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","toBlobURL","WebPlugin","canEncodeVideo","Quality","Input","ALL_FORMATS","BlobSource","Output","Mp4OutputFormat","BufferTarget","Conversion","ConversionCanceledError","FFmpeg"],"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 { toBlobURL } from '@ffmpeg/util';\nimport { ALL_FORMATS, BlobSource, BufferTarget, Conversion, ConversionCanceledError, Input, Mp4OutputFormat, Output, Quality, canEncodeVideo, } from 'mediabunny';\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 — and on both web engines below.\n */\nclass CancelledError extends Error {\n constructor() {\n super('Đã hủy nén');\n this.code = CANCELLED;\n this.name = 'CancelledError';\n }\n}\n/**\n * WebCodecs target per quality tier: cap height + a bitrate ceiling. The\n * quantizer (constant 26, CRF-like) drives quality where the encoder honours\n * it; H.264 on most browsers is bitrate-driven, so `bitrate` is the real knob.\n * fps is capped to 30 at call time (never upsampled). Audio is left to\n * mediabunny, which re-encodes to AAC for the MP4 container.\n */\nconst WEBCODECS_LADDER = {\n VERY_HIGH: { height: 1080, bitrate: 4000000 },\n HIGH: { height: 720, bitrate: 2000000 },\n MEDIUM: { height: 540, bitrate: 1200000 },\n LOW: { height: 480, bitrate: 1000000 },\n '360P': { height: 360, bitrate: 700000 },\n VERY_LOW: { height: 240, bitrate: 350000 },\n};\nconst WEBCODECS_QUANTIZER = 26;\nconst FPS_CAP = 30;\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. Only the ffmpeg\n * fallback ever pays this download; the WebCodecs primary path needs no core.\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 mediabunny conversion running now, so `cancel()` can abort it. */\n this.activeConversion = null;\n /** The job `compressVideo` is running right now, for `cancel({ jobId })`. */\n this.currentJobId = null;\n this.cancelRequested = false;\n }\n async initialize() {\n // WebCodecs (the primary path) needs no core download, so there is nothing\n // to warm. Eagerly loading the ffmpeg core here would pull ~30 MB for a\n // path most compresses never take. The fallback lazy-loads its core on\n // first use. Kept as a resolving no-op so callers that await it still pass.\n return { success: true, message: 'ready' };\n }\n async compressVideo(options) {\n var _a, _b;\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 the encode: cancelling\n // an engine only reaches work already handed to it, so a cancel during the\n // source fetch or before init has nothing to interrupt and would otherwise\n // 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 throwIfCancelled();\n const blob = await this.fetchSourceBlob(options.sourcePath);\n throwIfCancelled();\n const quality = (_b = options.quality) !== null && _b !== void 0 ? _b : 'MEDIUM';\n if (await this.canUseWebCodecs(quality)) {\n try {\n const result = await this.compressWithWebCodecs(blob, quality, throwIfCancelled);\n if (result)\n return result;\n // null → conversion invalid / tracks discarded → fall back to ffmpeg.\n }\n catch (e) {\n if ((e === null || e === void 0 ? void 0 : e.code) === CANCELLED)\n throw e; // user cancel: never fall back\n console.warn('[compress] WebCodecs failed, falling back to ffmpeg:', e);\n throwIfCancelled();\n }\n }\n return await this.compressWithFfmpeg(blob, quality, throwIfCancelled);\n }\n catch (error) {\n if ((error === null || error === void 0 ? void 0 : error.code) === CANCELLED) {\n console.log('[compress] cancelled');\n }\n else {\n console.error('[compress] failed:', error);\n }\n throw error;\n }\n finally {\n this.currentJobId = null;\n this.cancelRequested = false;\n this.activeConversion = null;\n }\n }\n /**\n * Kill whichever engine is mid-compress. Only one is ever active at a time,\n * so acting on both is safe.\n *\n * WebCodecs: `conversion.cancel()` makes the in-flight `execute()` reject\n * with `ConversionCanceledError`, surfaced as a `CancelledError` below.\n * ffmpeg: `exec()` runs synchronously inside the worker and never reaches a\n * message poll while encoding, so `worker.terminate()` is the only way to\n * stop it — its cost is the loaded core, which is why the core URLs are\n * 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 neither engine exists yet: the job may still be fetching\n // the source or inside loadFFmpeg, which read this flag at their boundaries.\n this.cancelRequested = true;\n const conversion = this.activeConversion;\n this.activeConversion = null;\n // Fire-and-forget: the CancelledError surfaces from the execute() rejection,\n // not from here; awaiting would only make cancel() slower.\n conversion === null || conversion === void 0 ? void 0 : conversion.cancel().catch(() => undefined);\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 fetchSourceBlob(sourcePath) {\n try {\n const response = await fetch(sourcePath);\n return await response.blob();\n }\n catch (err) {\n console.error('[compress] Error fetching source file:', err);\n throw new Error('Could not fetch source video file');\n }\n }\n async canUseWebCodecs(quality) {\n try {\n const { bitrate } = WEBCODECS_LADDER[quality];\n return await canEncodeVideo('avc', {\n quality: new Quality({ quantizer: WEBCODECS_QUANTIZER, bitrate }),\n });\n }\n catch (_a) {\n return false;\n }\n }\n async compressWithWebCodecs(blob, quality, throwIfCancelled) {\n const { height, bitrate } = WEBCODECS_LADDER[quality];\n const input = new Input({ formats: ALL_FORMATS, source: new BlobSource(blob) });\n const output = new Output({ format: new Mp4OutputFormat(), target: new BufferTarget() });\n // Cap fps at 30, but never upsample: probe the source and take the min.\n // A failed probe just omits frameRate, leaving the source rate untouched.\n let frameRate;\n try {\n const track = await input.getPrimaryVideoTrack();\n if (track) {\n const stats = await track.computePacketStats(100);\n if (stats.averagePacketRate > 0) {\n frameRate = Math.min(FPS_CAP, Math.round(stats.averagePacketRate));\n }\n }\n }\n catch (_a) {\n frameRate = undefined;\n }\n throwIfCancelled();\n const conversion = await Conversion.init({\n input,\n output,\n video: Object.assign({ height, quality: new Quality({ quantizer: WEBCODECS_QUANTIZER, bitrate }), hardwareAcceleration: 'prefer-hardware' }, (frameRate ? { frameRate } : {})),\n });\n if (!conversion.isValid) {\n console.warn('[compress] WebCodecs conversion invalid:', conversion.discardedTracks.map((t) => t.reason));\n return null;\n }\n this.activeConversion = conversion;\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n conversion.onProgress = (progress) => {\n this.notifyListeners('onProgress', { status: 'progress', percent: Math.round(progress * 100) });\n };\n throwIfCancelled();\n try {\n await conversion.execute();\n }\n catch (e) {\n if (this.cancelRequested || e instanceof ConversionCanceledError)\n throw new CancelledError();\n throw e;\n }\n const buffer = output.target.buffer;\n if (!buffer)\n throw new Error('WebCodecs produced no output');\n const outBlob = new Blob([buffer], { type: 'video/mp4' });\n return { success: true, destPath: URL.createObjectURL(outBlob) };\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 instance.on('progress', ({ progress }) => {\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 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 }\n async compressWithFfmpeg(blob, quality, throwIfCancelled) {\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 any\n // of the awaits below can hand back null on a reference the compiler\n // narrowed here — surfacing a cancel as a TypeError, not a CancelledError.\n const ffmpeg = this.ffmpeg;\n this.notifyListeners('onProgress', { status: 'started', percent: 0 });\n const inputFileName = 'input.mp4';\n const outputFileName = 'output.mp4';\n let crf = '28';\n let scale = '';\n let audioBitrate = '128k';\n switch (quality) {\n case 'VERY_HIGH':\n crf = '23';\n scale = '-vf scale=-2:1080';\n audioBitrate = '128k';\n break;\n case 'HIGH':\n crf = '28';\n scale = '-vf scale=-2:720';\n audioBitrate = '128k';\n break;\n case 'MEDIUM':\n crf = '30';\n scale = '-vf scale=-2:540';\n audioBitrate = '96k';\n break;\n case 'LOW':\n crf = '32';\n scale = '-vf scale=-2:480';\n audioBitrate = '96k';\n break;\n case '360P':\n crf = '28';\n scale = '-vf scale=-2:360';\n audioBitrate = '64k';\n break;\n case 'VERY_LOW':\n crf = '35';\n scale = '-vf scale=-2:240';\n audioBitrate = '48k';\n break;\n }\n const inputData = new Uint8Array(await blob.arrayBuffer());\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 const args = ['-i', inputFileName];\n args.push('-c:v', 'libx264', '-preset', 'superfast', '-crf', crf);\n if (scale) {\n args.push(...scale.split(' '));\n }\n args.push('-c:a', 'aac', '-b:a', audioBitrate);\n args.push(outputFileName);\n try {\n await ffmpeg.exec(args);\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 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 await ffmpeg.deleteFile(inputFileName);\n await ffmpeg.deleteFile(outputFileName);\n const outBlob = new Blob([data], { type: 'video/mp4' });\n return { success: true, destPath: URL.createObjectURL(outBlob) };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","toBlobURL","WebPlugin","canEncodeVideo","Quality","Input","ALL_FORMATS","BlobSource","Output","Mp4OutputFormat","BufferTarget","Conversion","ConversionCanceledError","FFmpeg"],"mappings":"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"}
@@ -16,7 +16,6 @@ public class NativeVideoCompressorPlugin: CAPPlugin, CAPBridgedPlugin {
16
16
  CAPPluginMethod(name: "compressVideo", returnType: CAPPluginReturnPromise),
17
17
  CAPPluginMethod(name: "cancel", returnType: CAPPluginReturnPromise),
18
18
  CAPPluginMethod(name: "initialize", returnType: CAPPluginReturnPromise),
19
- CAPPluginMethod(name: "finishUpload", returnType: CAPPluginReturnPromise)
20
19
  ]
21
20
 
22
21
  /// The job `compressVideo` is running right now, for `cancel(jobId:)`.
@@ -42,13 +41,6 @@ public class NativeVideoCompressorPlugin: CAPPlugin, CAPBridgedPlugin {
42
41
  * down the `else` branch — `markAsFinished()`, `group.leave()` — and the
43
42
  * existing completion handler runs. No new exit path, no polling.
44
43
  */
45
- /// Chỉ Android mới có foreground service để hạ xuống. Khai ở đây để caller
46
- /// gọi thẳng mà không phải rẽ nhánh theo platform ở mọi call site - thiếu
47
- /// nó thì mỗi lần gọi trên iOS là một promise bị reject "not implemented".
48
- @objc func finishUpload(_ call: CAPPluginCall) {
49
- call.resolve()
50
- }
51
-
52
44
  @objc func cancel(_ call: CAPPluginCall) {
53
45
  stateLock.lock()
54
46
  let running = currentJobId
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-native-video-compressor",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "Native video compressor using LightCompressor and AVFoundation",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",