capacitor-native-video-compressor 1.1.1 → 1.1.3
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 +50 -6
- package/android/src/main/AndroidManifest.xml +7 -4
- package/android/src/main/java/com/atomic/videocompressor/NativeVideoCompressorPlugin.java +95 -40
- package/android/src/main/java/com/atomic/videocompressor/VideoCompressionService.java +272 -55
- package/dist/docs.json +46 -0
- package/dist/esm/definitions.d.ts +30 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +5 -0
- package/dist/esm/web.js +7 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +7 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +7 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/NativeVideoCompressorPlugin/NativeVideoCompressorPlugin.swift +9 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -226,7 +226,9 @@ The `quality` parameter determines the output resolution and compression ratio.
|
|
|
226
226
|
* [`compressVideo(...)`](#compressvideo)
|
|
227
227
|
* [`cancel(...)`](#cancel)
|
|
228
228
|
* [`initialize()`](#initialize)
|
|
229
|
+
* [`finishUpload(...)`](#finishupload)
|
|
229
230
|
* [`addListener('onProgress', ...)`](#addlisteneronprogress-)
|
|
231
|
+
* [`addListener('onCancelRequested', ...)`](#addlisteneroncancelrequested-)
|
|
230
232
|
* [Interfaces](#interfaces)
|
|
231
233
|
|
|
232
234
|
</docgen-index>
|
|
@@ -282,6 +284,24 @@ initialize() => Promise<{ success: boolean; message?: string; }>
|
|
|
282
284
|
--------------------
|
|
283
285
|
|
|
284
286
|
|
|
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
|
+
|
|
285
305
|
### addListener('onProgress', ...)
|
|
286
306
|
|
|
287
307
|
```typescript
|
|
@@ -298,6 +318,29 @@ addListener(eventName: 'onProgress', listenerFunc: (info: { status: string; perc
|
|
|
298
318
|
--------------------
|
|
299
319
|
|
|
300
320
|
|
|
321
|
+
### addListener('onCancelRequested', ...)
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
addListener(eventName: 'onCancelRequested', listenerFunc: () => void) => Promise<PluginListenerHandle>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Android: user đã bấm "Huỷ" trên notification của foreground service.
|
|
328
|
+
|
|
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".
|
|
333
|
+
|
|
334
|
+
| Param | Type |
|
|
335
|
+
| ------------------ | -------------------------------- |
|
|
336
|
+
| **`eventName`** | <code>'onCancelRequested'</code> |
|
|
337
|
+
| **`listenerFunc`** | <code>() => void</code> |
|
|
338
|
+
|
|
339
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
340
|
+
|
|
341
|
+
--------------------
|
|
342
|
+
|
|
343
|
+
|
|
301
344
|
### Interfaces
|
|
302
345
|
|
|
303
346
|
|
|
@@ -311,12 +354,13 @@ addListener(eventName: 'onProgress', listenerFunc: (info: { status: string; perc
|
|
|
311
354
|
|
|
312
355
|
#### CompressOptions
|
|
313
356
|
|
|
314
|
-
| Prop
|
|
315
|
-
|
|
|
316
|
-
| **`sourcePath`**
|
|
317
|
-
| **`destPath`**
|
|
318
|
-
| **`quality`**
|
|
319
|
-
| **`jobId`**
|
|
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. |
|
|
320
364
|
|
|
321
365
|
|
|
322
366
|
#### CancelResult
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
2
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
3
|
-
<!--
|
|
4
|
-
|
|
5
|
-
|
|
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. -->
|
|
6
8
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
|
9
|
+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
7
10
|
|
|
8
11
|
<application>
|
|
9
12
|
<service
|
|
10
13
|
android:name="com.atomic.videocompressor.VideoCompressionService"
|
|
11
|
-
android:foregroundServiceType="
|
|
14
|
+
android:foregroundServiceType="dataSync"
|
|
12
15
|
android:exported="false" />
|
|
13
16
|
</application>
|
|
14
17
|
</manifest>
|
|
@@ -16,6 +16,7 @@ import com.getcapacitor.PluginCall;
|
|
|
16
16
|
import com.getcapacitor.PluginMethod;
|
|
17
17
|
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
18
18
|
import java.io.File;
|
|
19
|
+
import java.lang.ref.WeakReference;
|
|
19
20
|
import java.util.Arrays;
|
|
20
21
|
import java.util.Collections;
|
|
21
22
|
|
|
@@ -30,9 +31,52 @@ public class NativeVideoCompressorPlugin extends Plugin {
|
|
|
30
31
|
|
|
31
32
|
private static final String STORAGE_SUBFOLDER = "compressed_videos";
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Instance đang sống, để service gọi ngược khi user bấm "Huỷ" trên
|
|
36
|
+
* notification. WeakReference vì service sống lâu hơn bridge: giữ cứng ở
|
|
37
|
+
* đây là giữ luôn cả WebView sau khi Activity đã chết.
|
|
38
|
+
*/
|
|
39
|
+
private static WeakReference<NativeVideoCompressorPlugin> active = new WeakReference<>(null);
|
|
40
|
+
|
|
33
41
|
/** The job compressVideo is running right now, for cancel({ jobId }). */
|
|
34
42
|
private String currentJobId = null;
|
|
35
43
|
|
|
44
|
+
@Override
|
|
45
|
+
public void load() {
|
|
46
|
+
super.load();
|
|
47
|
+
active = new WeakReference<>(this);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Override
|
|
51
|
+
protected void handleOnDestroy() {
|
|
52
|
+
if (active.get() == this) active = new WeakReference<>(null);
|
|
53
|
+
super.handleOnDestroy();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* User bấm "Huỷ" trên notification. Phải huỷ được cả hai pha:
|
|
58
|
+
*
|
|
59
|
+
* - pha nén chạy trong LightCompressor (native) -> cancel() tĩnh, gọi được
|
|
60
|
+
* kể cả khi bridge đã chết
|
|
61
|
+
* - pha upload chạy trong JS -> chỉ có thể nhờ JS tự dừng, nên bắn
|
|
62
|
+
* onCancelRequested; không có event này thì bấm Huỷ lúc đang upload chỉ
|
|
63
|
+
* tắt được notification còn request vẫn chạy tiếp, và Play coi như
|
|
64
|
+
* "không dừng được bởi user".
|
|
65
|
+
*/
|
|
66
|
+
static void cancelAllFromNotification() {
|
|
67
|
+
try {
|
|
68
|
+
VideoCompressor.cancel();
|
|
69
|
+
} catch (Exception e) {
|
|
70
|
+
Log.w("NativeVideoCompressor", "Could not cancel compression", e);
|
|
71
|
+
}
|
|
72
|
+
NativeVideoCompressorPlugin plugin = active.get();
|
|
73
|
+
if (plugin == null) return;
|
|
74
|
+
synchronized (plugin) {
|
|
75
|
+
plugin.currentJobId = null;
|
|
76
|
+
}
|
|
77
|
+
plugin.notifyListeners("onCancelRequested", new JSObject());
|
|
78
|
+
}
|
|
79
|
+
|
|
36
80
|
/**
|
|
37
81
|
* Nothing to do natively; LightCompressor needs no warm-up. Answered
|
|
38
82
|
* rather than left unimplemented so a caller can await it unconditionally
|
|
@@ -73,6 +117,15 @@ public class NativeVideoCompressorPlugin extends Plugin {
|
|
|
73
117
|
call.resolve(ret);
|
|
74
118
|
}
|
|
75
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
|
+
|
|
76
129
|
@PluginMethod
|
|
77
130
|
public void compressVideo(PluginCall call) {
|
|
78
131
|
String sourcePath = call.getString("sourcePath");
|
|
@@ -83,6 +136,7 @@ public class NativeVideoCompressorPlugin extends Plugin {
|
|
|
83
136
|
}
|
|
84
137
|
|
|
85
138
|
String jobId = call.getString("jobId", "default");
|
|
139
|
+
boolean holdForUpload = Boolean.TRUE.equals(call.getBoolean("holdForUpload", false));
|
|
86
140
|
synchronized (this) {
|
|
87
141
|
currentJobId = jobId;
|
|
88
142
|
}
|
|
@@ -140,7 +194,19 @@ public class NativeVideoCompressorPlugin extends Plugin {
|
|
|
140
194
|
|
|
141
195
|
// Khởi động Foreground Service để giữ app luôn sống khi chạy ngầm
|
|
142
196
|
Intent serviceIntent = new Intent(getContext(), VideoCompressionService.class);
|
|
143
|
-
|
|
197
|
+
serviceIntent.setAction(VideoCompressionService.ACTION_START);
|
|
198
|
+
serviceIntent.putExtra(VideoCompressionService.EXTRA_JOB_ID, jobId);
|
|
199
|
+
try {
|
|
200
|
+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
|
201
|
+
getContext().startForegroundService(serviceIntent);
|
|
202
|
+
} else {
|
|
203
|
+
getContext().startService(serviceIntent);
|
|
204
|
+
}
|
|
205
|
+
} catch (Exception e) {
|
|
206
|
+
synchronized (this) { currentJobId = null; }
|
|
207
|
+
call.reject("Cannot start foreground video processing", e);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
144
210
|
|
|
145
211
|
// 4. Bắt đầu nén (Đã cập nhật List Uri và int index)
|
|
146
212
|
VideoCompressor.start(
|
|
@@ -153,6 +219,11 @@ public class NativeVideoCompressorPlugin extends Plugin {
|
|
|
153
219
|
new CompressionListener() {
|
|
154
220
|
@Override
|
|
155
221
|
public void onProgress(int i, float v) {
|
|
222
|
+
// Notification phải nhúc nhích theo đúng con số mà UI trong
|
|
223
|
+
// app đang hiển thị: một notification đứng im là lý do Play
|
|
224
|
+
// đánh trượt tiêu chí "user perceptible".
|
|
225
|
+
VideoCompressionService.publishProgress(jobId, Math.round(v));
|
|
226
|
+
|
|
156
227
|
// Gửi % tiến độ về Javascript để vẽ thanh Progress
|
|
157
228
|
JSObject ret = new JSObject();
|
|
158
229
|
ret.put("status", "progress");
|
|
@@ -162,13 +233,13 @@ public class NativeVideoCompressorPlugin extends Plugin {
|
|
|
162
233
|
|
|
163
234
|
@Override
|
|
164
235
|
public void onFailure(int i, @NonNull String failureMessage) {
|
|
165
|
-
finishJob(
|
|
236
|
+
finishJob(jobId, false);
|
|
166
237
|
call.reject("Nén thất bại: " + failureMessage);
|
|
167
238
|
}
|
|
168
239
|
|
|
169
240
|
@Override
|
|
170
241
|
public void onSuccess(int i, long l, @Nullable String s) {
|
|
171
|
-
finishJob(
|
|
242
|
+
finishJob(jobId, holdForUpload);
|
|
172
243
|
// Nén xong, trả kết quả đường dẫn file mới về cho JS
|
|
173
244
|
JSObject ret = new JSObject();
|
|
174
245
|
ret.put("success", true);
|
|
@@ -185,7 +256,7 @@ public class NativeVideoCompressorPlugin extends Plugin {
|
|
|
185
256
|
|
|
186
257
|
@Override
|
|
187
258
|
public void onCancelled(int i) {
|
|
188
|
-
finishJob(
|
|
259
|
+
finishJob(jobId, false);
|
|
189
260
|
// LightCompressor leaves whatever it had already written
|
|
190
261
|
// behind; without this the cache keeps a partial mp4 per
|
|
191
262
|
// cancel, and nothing ever collects them.
|
|
@@ -198,49 +269,33 @@ public class NativeVideoCompressorPlugin extends Plugin {
|
|
|
198
269
|
);
|
|
199
270
|
}
|
|
200
271
|
|
|
201
|
-
/**
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
* và Android giết cả app bằng ForegroundServiceDidNotStartInTimeException.
|
|
208
|
-
* Command thì dispatch tuần tự trên main thread của service nên luôn đúng thứ tự.
|
|
209
|
-
*/
|
|
210
|
-
private void finishJob(Intent serviceIntent) {
|
|
211
|
-
Intent stopIntent = new Intent(getContext(), VideoCompressionService.class);
|
|
212
|
-
stopIntent.setAction(VideoCompressionService.ACTION_STOP);
|
|
213
|
-
if (!startServiceCompat(stopIntent)) {
|
|
214
|
-
// Không gửi được command (hệ thống chặn start) thì service cũng không
|
|
215
|
-
// còn sống để mà treo - dọn bằng cách cũ cho chắc.
|
|
216
|
-
try {
|
|
217
|
-
getContext().stopService(serviceIntent);
|
|
218
|
-
} catch (Exception e) {
|
|
219
|
-
Log.w("NativeVideoCompressor", "Could not stop compression service", e);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
272
|
+
/** 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
|
+
);
|
|
222
278
|
synchronized (this) {
|
|
223
279
|
currentJobId = null;
|
|
224
280
|
}
|
|
225
281
|
}
|
|
226
282
|
|
|
227
|
-
/**
|
|
228
|
-
|
|
283
|
+
/**
|
|
284
|
+
* Lệnh gửi vào service, nuốt lỗi có chủ đích.
|
|
285
|
+
*
|
|
286
|
+
* startService() từ background bị chặn khi app không còn foreground service
|
|
287
|
+
* nào chạy - đúng tình huống xảy ra khi service đã tự tắt bằng watchdog rồi
|
|
288
|
+
* JS mới gọi finishUpload. Ném ra ở đây sẽ biến một lần dọn dẹp vô hại thành
|
|
289
|
+
* một lần nén báo lỗi cho user.
|
|
290
|
+
*/
|
|
291
|
+
private void sendToService(String action, String jobId) {
|
|
229
292
|
try {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
return true;
|
|
293
|
+
Intent intent = new Intent(getContext(), VideoCompressionService.class);
|
|
294
|
+
intent.setAction(action);
|
|
295
|
+
intent.putExtra(VideoCompressionService.EXTRA_JOB_ID, jobId);
|
|
296
|
+
getContext().startService(intent);
|
|
236
297
|
} catch (Exception e) {
|
|
237
|
-
|
|
238
|
-
getContext().startService(intent);
|
|
239
|
-
return true;
|
|
240
|
-
} catch (Exception inner) {
|
|
241
|
-
Log.w("NativeVideoCompressor", "Could not start compression service", inner);
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
298
|
+
Log.w("NativeVideoCompressor", "Could not send " + action + " to service", e);
|
|
244
299
|
}
|
|
245
300
|
}
|
|
246
301
|
|