capacitor-native-video-compressor 0.0.1
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/CapacitorNativeVideoCompressor.podspec +17 -0
- package/Package.swift +28 -0
- package/README.md +82 -0
- package/android/build.gradle +60 -0
- package/android/src/main/AndroidManifest.xml +11 -0
- package/android/src/main/java/com/atomic/videocompressor/NativeVideoCompressor.java +11 -0
- package/android/src/main/java/com/atomic/videocompressor/NativeVideoCompressorPlugin.java +136 -0
- package/android/src/main/java/com/atomic/videocompressor/VideoCompressionService.java +64 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +127 -0
- package/dist/esm/definitions.d.ts +17 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +5 -0
- package/dist/esm/web.js +14 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +28 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +31 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/NativeVideoCompressorPlugin/NativeVideoCompressor.swift +8 -0
- package/ios/Sources/NativeVideoCompressorPlugin/NativeVideoCompressorPlugin.swift +105 -0
- package/ios/Tests/NativeVideoCompressorPluginTests/NativeVideoCompressorTests.swift +15 -0
- package/package.json +81 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'CapacitorNativeVideoCompressor'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.license = package['license']
|
|
10
|
+
s.homepage = package['repository']['url']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
|
|
13
|
+
s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
14
|
+
s.ios.deployment_target = '15.0'
|
|
15
|
+
s.dependency 'Capacitor'
|
|
16
|
+
s.swift_version = '5.1'
|
|
17
|
+
end
|
package/Package.swift
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "CapacitorNativeVideoCompressor",
|
|
6
|
+
platforms: [.iOS(.v15)],
|
|
7
|
+
products: [
|
|
8
|
+
.library(
|
|
9
|
+
name: "CapacitorNativeVideoCompressor",
|
|
10
|
+
targets: ["NativeVideoCompressorPlugin"])
|
|
11
|
+
],
|
|
12
|
+
dependencies: [
|
|
13
|
+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
|
|
14
|
+
],
|
|
15
|
+
targets: [
|
|
16
|
+
.target(
|
|
17
|
+
name: "NativeVideoCompressorPlugin",
|
|
18
|
+
dependencies: [
|
|
19
|
+
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
20
|
+
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
21
|
+
],
|
|
22
|
+
path: "ios/Sources/NativeVideoCompressorPlugin"),
|
|
23
|
+
.testTarget(
|
|
24
|
+
name: "NativeVideoCompressorPluginTests",
|
|
25
|
+
dependencies: ["NativeVideoCompressorPlugin"],
|
|
26
|
+
path: "ios/Tests/NativeVideoCompressorPluginTests")
|
|
27
|
+
]
|
|
28
|
+
)
|
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# capacitor-native-video-compressor
|
|
2
|
+
|
|
3
|
+
Native video compressor using LightCompressor and AVFoundation
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install capacitor-native-video-compressor
|
|
9
|
+
npx cap sync
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## API
|
|
13
|
+
|
|
14
|
+
<docgen-index>
|
|
15
|
+
|
|
16
|
+
* [`compressVideo(...)`](#compressvideo)
|
|
17
|
+
* [`addListener('onProgress', ...)`](#addlisteneronprogress-)
|
|
18
|
+
* [Interfaces](#interfaces)
|
|
19
|
+
|
|
20
|
+
</docgen-index>
|
|
21
|
+
|
|
22
|
+
<docgen-api>
|
|
23
|
+
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
24
|
+
|
|
25
|
+
### compressVideo(...)
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
compressVideo(options: CompressOptions) => Promise<CompressResult>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| Param | Type |
|
|
32
|
+
| ------------- | ----------------------------------------------------------- |
|
|
33
|
+
| **`options`** | <code><a href="#compressoptions">CompressOptions</a></code> |
|
|
34
|
+
|
|
35
|
+
**Returns:** <code>Promise<<a href="#compressresult">CompressResult</a>></code>
|
|
36
|
+
|
|
37
|
+
--------------------
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### addListener('onProgress', ...)
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
addListener(eventName: 'onProgress', listenerFunc: (info: { status: string; percent?: number; }) => void) => Promise<PluginListenerHandle>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Param | Type |
|
|
47
|
+
| ------------------ | --------------------------------------------------------------------- |
|
|
48
|
+
| **`eventName`** | <code>'onProgress'</code> |
|
|
49
|
+
| **`listenerFunc`** | <code>(info: { status: string; percent?: number; }) => void</code> |
|
|
50
|
+
|
|
51
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
52
|
+
|
|
53
|
+
--------------------
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
### Interfaces
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
#### CompressResult
|
|
60
|
+
|
|
61
|
+
| Prop | Type |
|
|
62
|
+
| -------------- | -------------------- |
|
|
63
|
+
| **`success`** | <code>boolean</code> |
|
|
64
|
+
| **`destPath`** | <code>string</code> |
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
#### CompressOptions
|
|
68
|
+
|
|
69
|
+
| Prop | Type |
|
|
70
|
+
| ---------------- | --------------------------------------------------------------------- |
|
|
71
|
+
| **`sourcePath`** | <code>string</code> |
|
|
72
|
+
| **`destPath`** | <code>string</code> |
|
|
73
|
+
| **`quality`** | <code>'VERY_HIGH' \| 'HIGH' \| 'MEDIUM' \| 'LOW' \| 'VERY_LOW'</code> |
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
#### PluginListenerHandle
|
|
77
|
+
|
|
78
|
+
| Prop | Type |
|
|
79
|
+
| ------------ | ----------------------------------------- |
|
|
80
|
+
| **`remove`** | <code>() => Promise<void></code> |
|
|
81
|
+
|
|
82
|
+
</docgen-api>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
ext {
|
|
2
|
+
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
|
3
|
+
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
|
|
4
|
+
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
|
|
5
|
+
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
buildscript {
|
|
9
|
+
repositories {
|
|
10
|
+
google()
|
|
11
|
+
mavenCentral()
|
|
12
|
+
}
|
|
13
|
+
dependencies {
|
|
14
|
+
classpath 'com.android.tools.build:gradle:8.13.0'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
apply plugin: 'com.android.library'
|
|
19
|
+
|
|
20
|
+
android {
|
|
21
|
+
namespace = "com.atomic.videocompressor"
|
|
22
|
+
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
|
|
23
|
+
defaultConfig {
|
|
24
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
|
|
25
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
|
|
26
|
+
versionCode 1
|
|
27
|
+
versionName "1.0"
|
|
28
|
+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
29
|
+
}
|
|
30
|
+
buildTypes {
|
|
31
|
+
release {
|
|
32
|
+
minifyEnabled false
|
|
33
|
+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
lintOptions {
|
|
37
|
+
abortOnError = false
|
|
38
|
+
}
|
|
39
|
+
compileOptions {
|
|
40
|
+
sourceCompatibility JavaVersion.VERSION_21
|
|
41
|
+
targetCompatibility JavaVersion.VERSION_21
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
repositories {
|
|
46
|
+
google()
|
|
47
|
+
mavenCentral()
|
|
48
|
+
maven { url 'https://jitpack.io' }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
dependencies {
|
|
53
|
+
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
54
|
+
implementation project(':capacitor-android')
|
|
55
|
+
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
56
|
+
testImplementation "junit:junit:$junitVersion"
|
|
57
|
+
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
58
|
+
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
59
|
+
implementation 'com.github.AbedElazizShe:LightCompressor:1.3.2'
|
|
60
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
3
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
|
4
|
+
|
|
5
|
+
<application>
|
|
6
|
+
<service
|
|
7
|
+
android:name="com.atomic.videocompressor.VideoCompressionService"
|
|
8
|
+
android:foregroundServiceType="dataSync"
|
|
9
|
+
android:exported="false" />
|
|
10
|
+
</application>
|
|
11
|
+
</manifest>
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
package com.atomic.videocompressor;
|
|
2
|
+
|
|
3
|
+
import android.net.Uri;
|
|
4
|
+
import java.util.Collections;
|
|
5
|
+
import com.abedelazizshe.lightcompressorlibrary.CompressionListener;
|
|
6
|
+
import com.abedelazizshe.lightcompressorlibrary.VideoCompressor;
|
|
7
|
+
import com.abedelazizshe.lightcompressorlibrary.VideoQuality;
|
|
8
|
+
import com.abedelazizshe.lightcompressorlibrary.config.Configuration;
|
|
9
|
+
import com.abedelazizshe.lightcompressorlibrary.config.AppSpecificStorageConfiguration;
|
|
10
|
+
import com.getcapacitor.JSObject;
|
|
11
|
+
import com.getcapacitor.Plugin;
|
|
12
|
+
import com.getcapacitor.PluginCall;
|
|
13
|
+
import com.getcapacitor.PluginMethod;
|
|
14
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
15
|
+
|
|
16
|
+
import androidx.annotation.NonNull;
|
|
17
|
+
import androidx.annotation.Nullable;
|
|
18
|
+
|
|
19
|
+
import java.io.File;
|
|
20
|
+
import java.util.Arrays;
|
|
21
|
+
import android.content.Intent;
|
|
22
|
+
|
|
23
|
+
@CapacitorPlugin(name = "NativeVideoCompressor")
|
|
24
|
+
public class NativeVideoCompressorPlugin extends Plugin {
|
|
25
|
+
|
|
26
|
+
@PluginMethod
|
|
27
|
+
public void compressVideo(PluginCall call) {
|
|
28
|
+
String sourcePath = call.getString("sourcePath");
|
|
29
|
+
|
|
30
|
+
if (sourcePath == null) {
|
|
31
|
+
call.reject("Missing sourcePath");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 1. Chuẩn bị Uri và Tên file xuất ra
|
|
36
|
+
Uri srcUri;
|
|
37
|
+
String fileName = "compressed_" + System.currentTimeMillis() + ".mp4";
|
|
38
|
+
|
|
39
|
+
if (sourcePath.startsWith("content://")) {
|
|
40
|
+
srcUri = Uri.parse(sourcePath);
|
|
41
|
+
} else if (sourcePath.startsWith("file://")) {
|
|
42
|
+
srcUri = Uri.parse(sourcePath);
|
|
43
|
+
} else {
|
|
44
|
+
srcUri = Uri.fromFile(new File(sourcePath));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Đọc quality từ Javascript (Mặc định là MEDIUM)
|
|
48
|
+
String qualityString = call.getString("quality", "MEDIUM");
|
|
49
|
+
VideoQuality videoQuality = VideoQuality.MEDIUM;
|
|
50
|
+
if (qualityString != null) {
|
|
51
|
+
try {
|
|
52
|
+
videoQuality = VideoQuality.valueOf(qualityString);
|
|
53
|
+
} catch (IllegalArgumentException e) {
|
|
54
|
+
videoQuality = VideoQuality.MEDIUM;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 2. Cấu hình thông số nén (Dành riêng cho LightCompressor 1.3.2)
|
|
59
|
+
// Lưu ý: Java phải truyền đủ 9 tham số do thư viện gốc viết bằng Kotlin
|
|
60
|
+
Configuration configuration = new Configuration(
|
|
61
|
+
videoQuality, // Chất lượng nén
|
|
62
|
+
false, // isMinBitrateCheckEnabled
|
|
63
|
+
null, // videoBitrateInMbps
|
|
64
|
+
false, // disableAudio
|
|
65
|
+
false, // keepOriginalResolution
|
|
66
|
+
null, // videoWidth
|
|
67
|
+
null, // videoHeight
|
|
68
|
+
Arrays.asList(fileName) // videoNames
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// 3. Cấu hình nơi lưu (Lưu an toàn vào thư mục nội bộ của App)
|
|
72
|
+
AppSpecificStorageConfiguration storageConfig = new AppSpecificStorageConfiguration("compressed_videos");
|
|
73
|
+
|
|
74
|
+
// Khởi động Foreground Service để giữ app luôn sống khi chạy ngầm
|
|
75
|
+
Intent serviceIntent = new Intent(getContext(), VideoCompressionService.class);
|
|
76
|
+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
|
77
|
+
try {
|
|
78
|
+
getContext().startForegroundService(serviceIntent);
|
|
79
|
+
} catch (Exception e) {
|
|
80
|
+
getContext().startService(serviceIntent);
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
getContext().startService(serviceIntent);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 4. Bắt đầu nén (Đã cập nhật List Uri và int index)
|
|
87
|
+
VideoCompressor.start(
|
|
88
|
+
getContext(),
|
|
89
|
+
Collections.singletonList(srcUri), // Nhận vào một List chứa đường dẫn
|
|
90
|
+
false, // isStreamable
|
|
91
|
+
null, // sharedStorageConfiguration (Không dùng)
|
|
92
|
+
storageConfig, // Cấu hình lưu trữ
|
|
93
|
+
configuration, // Cấu hình chất lượng
|
|
94
|
+
new CompressionListener() {
|
|
95
|
+
|
|
96
|
+
@Override
|
|
97
|
+
public void onProgress(int i, float v) {
|
|
98
|
+
// Gửi % tiến độ về Javascript để vẽ thanh Progress
|
|
99
|
+
JSObject ret = new JSObject();
|
|
100
|
+
ret.put("status", "progress");
|
|
101
|
+
ret.put("percent", v);
|
|
102
|
+
notifyListeners("onProgress", ret);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@Override
|
|
106
|
+
public void onFailure(int i, @NonNull String failureMessage) {
|
|
107
|
+
getContext().stopService(serviceIntent);
|
|
108
|
+
call.reject("Nén thất bại: " + failureMessage);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@Override
|
|
112
|
+
public void onSuccess(int i, long l, @Nullable String s) {
|
|
113
|
+
getContext().stopService(serviceIntent);
|
|
114
|
+
// Nén xong, trả kết quả đường dẫn file mới về cho JS
|
|
115
|
+
JSObject ret = new JSObject();
|
|
116
|
+
ret.put("success", true);
|
|
117
|
+
ret.put("destPath", s);
|
|
118
|
+
call.resolve(ret);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@Override
|
|
122
|
+
public void onStart(int i) {
|
|
123
|
+
JSObject ret = new JSObject();
|
|
124
|
+
ret.put("status", "started");
|
|
125
|
+
notifyListeners("onProgress", ret);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@Override
|
|
129
|
+
public void onCancelled(int i) {
|
|
130
|
+
getContext().stopService(serviceIntent);
|
|
131
|
+
call.reject("Đã hủy nén");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package com.atomic.videocompressor;
|
|
2
|
+
|
|
3
|
+
import android.app.Notification;
|
|
4
|
+
import android.app.NotificationChannel;
|
|
5
|
+
import android.app.NotificationManager;
|
|
6
|
+
import android.app.Service;
|
|
7
|
+
import android.content.Intent;
|
|
8
|
+
import android.os.Build;
|
|
9
|
+
import android.os.IBinder;
|
|
10
|
+
|
|
11
|
+
import androidx.annotation.Nullable;
|
|
12
|
+
import androidx.core.app.NotificationCompat;
|
|
13
|
+
|
|
14
|
+
public class VideoCompressionService extends Service {
|
|
15
|
+
|
|
16
|
+
private static final String CHANNEL_ID = "VideoCompressionChannel";
|
|
17
|
+
private static final int NOTIFICATION_ID = 1001;
|
|
18
|
+
|
|
19
|
+
@Override
|
|
20
|
+
public void onCreate() {
|
|
21
|
+
super.onCreate();
|
|
22
|
+
createNotificationChannel();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Override
|
|
26
|
+
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
27
|
+
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
|
|
28
|
+
.setContentTitle("Đang nén video")
|
|
29
|
+
.setContentText("Tiến trình đang chạy ngầm...")
|
|
30
|
+
.setSmallIcon(android.R.drawable.ic_popup_sync)
|
|
31
|
+
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
32
|
+
.build();
|
|
33
|
+
|
|
34
|
+
// Check for Android 14+ specific foreground service typing requirements if compiled against API 34+
|
|
35
|
+
// In this case, we rely on the manifest declaration for dataSync.
|
|
36
|
+
try {
|
|
37
|
+
startForeground(NOTIFICATION_ID, notification);
|
|
38
|
+
} catch (Exception e) {
|
|
39
|
+
e.printStackTrace();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return START_NOT_STICKY;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@Nullable
|
|
46
|
+
@Override
|
|
47
|
+
public IBinder onBind(Intent intent) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private void createNotificationChannel() {
|
|
52
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
53
|
+
NotificationChannel serviceChannel = new NotificationChannel(
|
|
54
|
+
CHANNEL_ID,
|
|
55
|
+
"Video Compression Service Channel",
|
|
56
|
+
NotificationManager.IMPORTANCE_LOW
|
|
57
|
+
);
|
|
58
|
+
NotificationManager manager = getSystemService(NotificationManager.class);
|
|
59
|
+
if (manager != null) {
|
|
60
|
+
manager.createNotificationChannel(serviceChannel);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
File without changes
|
package/dist/docs.json
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"api": {
|
|
3
|
+
"name": "NativeVideoCompressorPlugin",
|
|
4
|
+
"slug": "nativevideocompressorplugin",
|
|
5
|
+
"docs": "",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"methods": [
|
|
8
|
+
{
|
|
9
|
+
"name": "compressVideo",
|
|
10
|
+
"signature": "(options: CompressOptions) => Promise<CompressResult>",
|
|
11
|
+
"parameters": [
|
|
12
|
+
{
|
|
13
|
+
"name": "options",
|
|
14
|
+
"docs": "",
|
|
15
|
+
"type": "CompressOptions"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"returns": "Promise<CompressResult>",
|
|
19
|
+
"tags": [],
|
|
20
|
+
"docs": "",
|
|
21
|
+
"complexTypes": [
|
|
22
|
+
"CompressResult",
|
|
23
|
+
"CompressOptions"
|
|
24
|
+
],
|
|
25
|
+
"slug": "compressvideo"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "addListener",
|
|
29
|
+
"signature": "(eventName: 'onProgress', listenerFunc: (info: { status: string; percent?: number; }) => void) => Promise<PluginListenerHandle>",
|
|
30
|
+
"parameters": [
|
|
31
|
+
{
|
|
32
|
+
"name": "eventName",
|
|
33
|
+
"docs": "",
|
|
34
|
+
"type": "'onProgress'"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "listenerFunc",
|
|
38
|
+
"docs": "",
|
|
39
|
+
"type": "(info: { status: string; percent?: number | undefined; }) => void"
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"returns": "Promise<PluginListenerHandle>",
|
|
43
|
+
"tags": [],
|
|
44
|
+
"docs": "",
|
|
45
|
+
"complexTypes": [
|
|
46
|
+
"PluginListenerHandle"
|
|
47
|
+
],
|
|
48
|
+
"slug": "addlisteneronprogress-"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"properties": []
|
|
52
|
+
},
|
|
53
|
+
"interfaces": [
|
|
54
|
+
{
|
|
55
|
+
"name": "CompressResult",
|
|
56
|
+
"slug": "compressresult",
|
|
57
|
+
"docs": "",
|
|
58
|
+
"tags": [],
|
|
59
|
+
"methods": [],
|
|
60
|
+
"properties": [
|
|
61
|
+
{
|
|
62
|
+
"name": "success",
|
|
63
|
+
"tags": [],
|
|
64
|
+
"docs": "",
|
|
65
|
+
"complexTypes": [],
|
|
66
|
+
"type": "boolean"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "destPath",
|
|
70
|
+
"tags": [],
|
|
71
|
+
"docs": "",
|
|
72
|
+
"complexTypes": [],
|
|
73
|
+
"type": "string"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "CompressOptions",
|
|
79
|
+
"slug": "compressoptions",
|
|
80
|
+
"docs": "",
|
|
81
|
+
"tags": [],
|
|
82
|
+
"methods": [],
|
|
83
|
+
"properties": [
|
|
84
|
+
{
|
|
85
|
+
"name": "sourcePath",
|
|
86
|
+
"tags": [],
|
|
87
|
+
"docs": "",
|
|
88
|
+
"complexTypes": [],
|
|
89
|
+
"type": "string"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "destPath",
|
|
93
|
+
"tags": [],
|
|
94
|
+
"docs": "",
|
|
95
|
+
"complexTypes": [],
|
|
96
|
+
"type": "string | undefined"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "quality",
|
|
100
|
+
"tags": [],
|
|
101
|
+
"docs": "",
|
|
102
|
+
"complexTypes": [],
|
|
103
|
+
"type": "'VERY_HIGH' | 'HIGH' | 'MEDIUM' | 'LOW' | 'VERY_LOW' | undefined"
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "PluginListenerHandle",
|
|
109
|
+
"slug": "pluginlistenerhandle",
|
|
110
|
+
"docs": "",
|
|
111
|
+
"tags": [],
|
|
112
|
+
"methods": [],
|
|
113
|
+
"properties": [
|
|
114
|
+
{
|
|
115
|
+
"name": "remove",
|
|
116
|
+
"tags": [],
|
|
117
|
+
"docs": "",
|
|
118
|
+
"complexTypes": [],
|
|
119
|
+
"type": "() => Promise<void>"
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
"enums": [],
|
|
125
|
+
"typeAliases": [],
|
|
126
|
+
"pluginConfigs": []
|
|
127
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PluginListenerHandle } from '@capacitor/core';
|
|
2
|
+
export interface CompressOptions {
|
|
3
|
+
sourcePath: string;
|
|
4
|
+
destPath?: string;
|
|
5
|
+
quality?: 'VERY_HIGH' | 'HIGH' | 'MEDIUM' | 'LOW' | 'VERY_LOW';
|
|
6
|
+
}
|
|
7
|
+
export interface CompressResult {
|
|
8
|
+
success: boolean;
|
|
9
|
+
destPath: string;
|
|
10
|
+
}
|
|
11
|
+
export interface NativeVideoCompressorPlugin {
|
|
12
|
+
compressVideo(options: CompressOptions): Promise<CompressResult>;
|
|
13
|
+
addListener(eventName: 'onProgress', listenerFunc: (info: {
|
|
14
|
+
status: string;
|
|
15
|
+
percent?: number;
|
|
16
|
+
}) => void): Promise<PluginListenerHandle>;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\nexport interface CompressOptions {\n sourcePath: string;\n destPath?: string;\n quality?: 'VERY_HIGH' | 'HIGH' | 'MEDIUM' | 'LOW' | 'VERY_LOW';\n}\n\nexport interface CompressResult {\n success: boolean;\n destPath: string;\n}\n\nexport interface NativeVideoCompressorPlugin {\n compressVideo(options: CompressOptions): Promise<CompressResult>;\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"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { registerPlugin } from '@capacitor/core';
|
|
2
|
+
const NativeVideoCompressor = registerPlugin('NativeVideoCompressor', {
|
|
3
|
+
web: () => import('./web').then((m) => new m.NativeVideoCompressorWeb()),
|
|
4
|
+
});
|
|
5
|
+
export * from './definitions';
|
|
6
|
+
export { NativeVideoCompressor };
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,qBAAqB,GAAG,cAAc,CAA8B,uBAAuB,EAAE;IACjG,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,wBAAwB,EAAE,CAAC;CACzE,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,qBAAqB,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { NativeVideoCompressorPlugin } from './definitions';\n\nconst NativeVideoCompressor = registerPlugin<NativeVideoCompressorPlugin>('NativeVideoCompressor', {\n web: () => import('./web').then((m) => new m.NativeVideoCompressorWeb()),\n});\n\nexport * from './definitions';\nexport { NativeVideoCompressor };\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { WebPlugin } from '@capacitor/core';
|
|
2
|
+
import type { NativeVideoCompressorPlugin, CompressOptions, CompressResult } from './definitions';
|
|
3
|
+
export declare class NativeVideoCompressorWeb extends WebPlugin implements NativeVideoCompressorPlugin {
|
|
4
|
+
compressVideo(options: CompressOptions): Promise<CompressResult>;
|
|
5
|
+
}
|
package/dist/esm/web.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { WebPlugin } from '@capacitor/core';
|
|
2
|
+
export class NativeVideoCompressorWeb extends WebPlugin {
|
|
3
|
+
async compressVideo(options) {
|
|
4
|
+
console.warn('Tính năng nén video bằng GPU không hỗ trợ trên trình duyệt Web.', options);
|
|
5
|
+
// Báo lỗi cho chuẩn Capacitor báo rằng nền tảng này không được hỗ trợ
|
|
6
|
+
throw this.unimplemented('Not implemented on web.');
|
|
7
|
+
// (Hoặc nếu bạn không muốn văng lỗi trên web, bạn có thể trả về luôn đường dẫn gốc)
|
|
8
|
+
// return {
|
|
9
|
+
// success: false,
|
|
10
|
+
// destPath: options.sourcePath
|
|
11
|
+
// };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,MAAM,OAAO,wBAAyB,SAAQ,SAAS;IACrD,KAAK,CAAC,aAAa,CAAC,OAAwB;QAC1C,OAAO,CAAC,IAAI,CAAC,iEAAiE,EAAE,OAAO,CAAC,CAAC;QAEzF,sEAAsE;QACtE,MAAM,IAAI,CAAC,aAAa,CAAC,yBAAyB,CAAC,CAAC;QAEpD,oFAAoF;QACpF,WAAW;QACX,oBAAoB;QACpB,iCAAiC;QACjC,KAAK;IACP,CAAC;CACF","sourcesContent":["import { WebPlugin } from '@capacitor/core';\nimport type { NativeVideoCompressorPlugin, CompressOptions, CompressResult } from './definitions';\n\nexport class NativeVideoCompressorWeb extends WebPlugin implements NativeVideoCompressorPlugin {\n async compressVideo(options: CompressOptions): Promise<CompressResult> {\n console.warn('Tính năng nén video bằng GPU không hỗ trợ trên trình duyệt Web.', options);\n\n // Báo lỗi cho chuẩn Capacitor báo rằng nền tảng này không được hỗ trợ\n throw this.unimplemented('Not implemented on web.');\n\n // (Hoặc nếu bạn không muốn văng lỗi trên web, bạn có thể trả về luôn đường dẫn gốc)\n // return {\n // success: false,\n // destPath: options.sourcePath\n // };\n }\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@capacitor/core');
|
|
4
|
+
|
|
5
|
+
const NativeVideoCompressor = core.registerPlugin('NativeVideoCompressor', {
|
|
6
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.NativeVideoCompressorWeb()),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
class NativeVideoCompressorWeb extends core.WebPlugin {
|
|
10
|
+
async compressVideo(options) {
|
|
11
|
+
console.warn('Tính năng nén video bằng GPU không hỗ trợ trên trình duyệt Web.', options);
|
|
12
|
+
// Báo lỗi cho chuẩn Capacitor báo rằng nền tảng này không được hỗ trợ
|
|
13
|
+
throw this.unimplemented('Not implemented on web.');
|
|
14
|
+
// (Hoặc nếu bạn không muốn văng lỗi trên web, bạn có thể trả về luôn đường dẫn gốc)
|
|
15
|
+
// return {
|
|
16
|
+
// success: false,
|
|
17
|
+
// destPath: options.sourcePath
|
|
18
|
+
// };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
23
|
+
__proto__: null,
|
|
24
|
+
NativeVideoCompressorWeb: NativeVideoCompressorWeb
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
exports.NativeVideoCompressor = NativeVideoCompressor;
|
|
28
|
+
//# sourceMappingURL=plugin.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst NativeVideoCompressor = registerPlugin('NativeVideoCompressor', {\n web: () => import('./web').then((m) => new m.NativeVideoCompressorWeb()),\n});\nexport * from './definitions';\nexport { NativeVideoCompressor };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class NativeVideoCompressorWeb extends WebPlugin {\n async compressVideo(options) {\n console.warn('Tính năng nén video bằng GPU không hỗ trợ trên trình duyệt Web.', options);\n // Báo lỗi cho chuẩn Capacitor báo rằng nền tảng này không được hỗ trợ\n throw this.unimplemented('Not implemented on web.');\n // (Hoặc nếu bạn không muốn văng lỗi trên web, bạn có thể trả về luôn đường dẫn gốc)\n // return {\n // success: false,\n // destPath: options.sourcePath\n // };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACK,MAAC,qBAAqB,GAAGA,mBAAc,CAAC,uBAAuB,EAAE;AACtE,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,wBAAwB,EAAE,CAAC;AAC5E,CAAC;;ACFM,MAAM,wBAAwB,SAASC,cAAS,CAAC;AACxD,IAAI,MAAM,aAAa,CAAC,OAAO,EAAE;AACjC,QAAQ,OAAO,CAAC,IAAI,CAAC,iEAAiE,EAAE,OAAO,CAAC;AAChG;AACA,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,yBAAyB,CAAC;AAC3D;AACA;AACA;AACA;AACA;AACA,IAAI;AACJ;;;;;;;;;"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
var capacitorNativeVideoCompressor = (function (exports, core) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const NativeVideoCompressor = core.registerPlugin('NativeVideoCompressor', {
|
|
5
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.NativeVideoCompressorWeb()),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
class NativeVideoCompressorWeb extends core.WebPlugin {
|
|
9
|
+
async compressVideo(options) {
|
|
10
|
+
console.warn('Tính năng nén video bằng GPU không hỗ trợ trên trình duyệt Web.', options);
|
|
11
|
+
// Báo lỗi cho chuẩn Capacitor báo rằng nền tảng này không được hỗ trợ
|
|
12
|
+
throw this.unimplemented('Not implemented on web.');
|
|
13
|
+
// (Hoặc nếu bạn không muốn văng lỗi trên web, bạn có thể trả về luôn đường dẫn gốc)
|
|
14
|
+
// return {
|
|
15
|
+
// success: false,
|
|
16
|
+
// destPath: options.sourcePath
|
|
17
|
+
// };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
22
|
+
__proto__: null,
|
|
23
|
+
NativeVideoCompressorWeb: NativeVideoCompressorWeb
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
exports.NativeVideoCompressor = NativeVideoCompressor;
|
|
27
|
+
|
|
28
|
+
return exports;
|
|
29
|
+
|
|
30
|
+
})({}, capacitorExports);
|
|
31
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst NativeVideoCompressor = registerPlugin('NativeVideoCompressor', {\n web: () => import('./web').then((m) => new m.NativeVideoCompressorWeb()),\n});\nexport * from './definitions';\nexport { NativeVideoCompressor };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class NativeVideoCompressorWeb extends WebPlugin {\n async compressVideo(options) {\n console.warn('Tính năng nén video bằng GPU không hỗ trợ trên trình duyệt Web.', options);\n // Báo lỗi cho chuẩn Capacitor báo rằng nền tảng này không được hỗ trợ\n throw this.unimplemented('Not implemented on web.');\n // (Hoặc nếu bạn không muốn văng lỗi trên web, bạn có thể trả về luôn đường dẫn gốc)\n // return {\n // success: false,\n // destPath: options.sourcePath\n // };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,qBAAqB,GAAGA,mBAAc,CAAC,uBAAuB,EAAE;IACtE,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,wBAAwB,EAAE,CAAC;IAC5E,CAAC;;ICFM,MAAM,wBAAwB,SAASC,cAAS,CAAC;IACxD,IAAI,MAAM,aAAa,CAAC,OAAO,EAAE;IACjC,QAAQ,OAAO,CAAC,IAAI,CAAC,iEAAiE,EAAE,OAAO,CAAC;IAChG;IACA,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,yBAAyB,CAAC;IAC3D;IACA;IACA;IACA;IACA;IACA,IAAI;IACJ;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
import AVFoundation
|
|
4
|
+
import UIKit
|
|
5
|
+
|
|
6
|
+
@objc(NativeVideoCompressorPlugin)
|
|
7
|
+
public class NativeVideoCompressorPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
8
|
+
|
|
9
|
+
// --- BẮT ĐẦU PHẦN ĐĂNG KÝ PLUGIN (Thay thế cho file .m cũ) ---
|
|
10
|
+
public let identifier = "NativeVideoCompressorPlugin"
|
|
11
|
+
public let jsName = "NativeVideoCompressor"
|
|
12
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
13
|
+
CAPPluginMethod(name: "compressVideo", returnType: CAPPluginReturnPromise)
|
|
14
|
+
]
|
|
15
|
+
// --- KẾT THÚC PHẦN ĐĂNG KÝ ---
|
|
16
|
+
private var isCompressing = false
|
|
17
|
+
|
|
18
|
+
@objc func compressVideo(_ call: CAPPluginCall) {
|
|
19
|
+
// 1. Lấy đường dẫn từ Javascript truyền vào
|
|
20
|
+
guard let sourcePath = call.getString("sourcePath") else {
|
|
21
|
+
call.reject("Missing sourcePath")
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 2. Xử lý đường dẫn (hỗ trợ cả dạng file:// và đường dẫn tuyệt đối)
|
|
26
|
+
let videoURL = sourcePath.hasPrefix("file://") ? URL(string: sourcePath)! : URL(fileURLWithPath: sourcePath)
|
|
27
|
+
|
|
28
|
+
// Tạo file đầu ra lưu ở thư mục Temp
|
|
29
|
+
let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("compressed_\(UUID().uuidString).mp4")
|
|
30
|
+
|
|
31
|
+
let asset = AVAsset(url: videoURL)
|
|
32
|
+
|
|
33
|
+
// 3. Khởi tạo bộ nén phần cứng (Chọn chất lượng MediumQuality hoặc 1280x720)
|
|
34
|
+
guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetMediumQuality) else {
|
|
35
|
+
call.reject("Không thể khởi tạo bộ nén iOS")
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
exportSession.outputURL = outputURL
|
|
40
|
+
exportSession.outputFileType = .mp4
|
|
41
|
+
exportSession.shouldOptimizeForNetworkUse = true
|
|
42
|
+
|
|
43
|
+
// Báo cho giao diện biết là đã bắt đầu nén
|
|
44
|
+
self.notifyListeners("onProgress", data:["status": "started"])
|
|
45
|
+
|
|
46
|
+
self.isCompressing = true
|
|
47
|
+
let startTime = Date()
|
|
48
|
+
self.checkProgress(session: exportSession)
|
|
49
|
+
|
|
50
|
+
// Đăng ký Background Task để ứng dụng tiếp tục nén khi thu nhỏ
|
|
51
|
+
var backgroundTask: UIBackgroundTaskIdentifier = .invalid
|
|
52
|
+
|
|
53
|
+
// Cần gọi UIBackgroundTaskIdentifier trên Main Thread (nếu an toàn) hoặc dùng trực tiếp (beginBackgroundTask thread-safe)
|
|
54
|
+
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "VideoCompression") {
|
|
55
|
+
// Callback khi hết thời gian chạy ngầm do OS cấp
|
|
56
|
+
UIApplication.shared.endBackgroundTask(backgroundTask)
|
|
57
|
+
backgroundTask = .invalid
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 5. Bắt đầu tiến trình nén (Chạy ngầm trên GPU)
|
|
61
|
+
exportSession.exportAsynchronously {
|
|
62
|
+
self.isCompressing = false
|
|
63
|
+
let elapsed = Date().timeIntervalSince(startTime)
|
|
64
|
+
print("Compression finished in \(elapsed) seconds. Final status: \(exportSession.status.rawValue)")
|
|
65
|
+
|
|
66
|
+
DispatchQueue.main.async {
|
|
67
|
+
// Kết thúc Background Task khi xử lý xong
|
|
68
|
+
if backgroundTask != .invalid {
|
|
69
|
+
UIApplication.shared.endBackgroundTask(backgroundTask)
|
|
70
|
+
backgroundTask = .invalid
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
switch exportSession.status {
|
|
74
|
+
case .completed:
|
|
75
|
+
// THÀNH CÔNG! Trả kết quả về cho Nuxt.js
|
|
76
|
+
call.resolve([
|
|
77
|
+
"success": true,
|
|
78
|
+
"destPath": outputURL.path
|
|
79
|
+
])
|
|
80
|
+
case .failed:
|
|
81
|
+
call.reject("Nén thất bại: \(exportSession.error?.localizedDescription ?? "Lỗi không xác định")")
|
|
82
|
+
case .cancelled:
|
|
83
|
+
call.reject("Đã hủy nén")
|
|
84
|
+
default:
|
|
85
|
+
call.reject("Lỗi không xác định")
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private func checkProgress(session: AVAssetExportSession) {
|
|
92
|
+
let progress = session.progress * 100
|
|
93
|
+
print("Export Progress: \(progress)%") // Log để debug
|
|
94
|
+
self.notifyListeners("onProgress", data: [
|
|
95
|
+
"status": "progress",
|
|
96
|
+
"percent": Double(progress)
|
|
97
|
+
])
|
|
98
|
+
|
|
99
|
+
if self.isCompressing && (session.status == .exporting || session.status == .waiting || session.status == .unknown) {
|
|
100
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
|
101
|
+
self?.checkProgress(session: session)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import NativeVideoCompressorPlugin
|
|
3
|
+
|
|
4
|
+
class NativeVideoCompressorTests: XCTestCase {
|
|
5
|
+
func testEcho() {
|
|
6
|
+
// This is an example of a functional test case for a plugin.
|
|
7
|
+
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
|
8
|
+
|
|
9
|
+
let implementation = NativeVideoCompressor()
|
|
10
|
+
let value = "Hello, World!"
|
|
11
|
+
let result = implementation.echo(value)
|
|
12
|
+
|
|
13
|
+
XCTAssertEqual(value, result)
|
|
14
|
+
}
|
|
15
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "capacitor-native-video-compressor",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Native video compressor using LightCompressor and AVFoundation",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"unpkg": "dist/plugin.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"android/src/main/",
|
|
11
|
+
"android/build.gradle",
|
|
12
|
+
"dist/",
|
|
13
|
+
"ios/Sources",
|
|
14
|
+
"ios/Tests",
|
|
15
|
+
"Package.swift",
|
|
16
|
+
"CapacitorNativeVideoCompressor.podspec"
|
|
17
|
+
],
|
|
18
|
+
"author": "TuanTran",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/TuanHarry/capacitor-native-video-compressor.git.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/TuanHarry/capacitor-native-video-compressor.git/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"capacitor",
|
|
29
|
+
"plugin",
|
|
30
|
+
"native"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
34
|
+
"verify:ios": "xcodebuild -scheme CapacitorNativeVideoCompressor -destination generic/platform=iOS",
|
|
35
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
36
|
+
"verify:web": "npm run build",
|
|
37
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
38
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
39
|
+
"eslint": "eslint . --ext ts",
|
|
40
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
41
|
+
"swiftlint": "node-swiftlint",
|
|
42
|
+
"docgen": "docgen --api NativeVideoCompressorPlugin --output-readme README.md --output-json dist/docs.json",
|
|
43
|
+
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
44
|
+
"clean": "rimraf ./dist",
|
|
45
|
+
"watch": "tsc --watch",
|
|
46
|
+
"prepublishOnly": "npm run build",
|
|
47
|
+
"prepare": "npm run build"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@capacitor/android": "^8.0.0",
|
|
51
|
+
"@capacitor/core": "^8.0.0",
|
|
52
|
+
"@capacitor/docgen": "^0.3.1",
|
|
53
|
+
"@capacitor/ios": "^8.0.0",
|
|
54
|
+
"@ionic/eslint-config": "^0.4.0",
|
|
55
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
56
|
+
"@ionic/swiftlint-config": "^2.0.0",
|
|
57
|
+
"eslint": "^8.57.1",
|
|
58
|
+
"prettier": "^3.6.2",
|
|
59
|
+
"prettier-plugin-java": "^2.7.7",
|
|
60
|
+
"rimraf": "^6.1.0",
|
|
61
|
+
"rollup": "^4.53.2",
|
|
62
|
+
"swiftlint": "^2.0.0",
|
|
63
|
+
"typescript": "^5.9.3"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"@capacitor/core": ">=8.0.0"
|
|
67
|
+
},
|
|
68
|
+
"prettier": "@ionic/prettier-config",
|
|
69
|
+
"swiftlint": "@ionic/swiftlint-config",
|
|
70
|
+
"eslintConfig": {
|
|
71
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
72
|
+
},
|
|
73
|
+
"capacitor": {
|
|
74
|
+
"ios": {
|
|
75
|
+
"src": "ios"
|
|
76
|
+
},
|
|
77
|
+
"android": {
|
|
78
|
+
"src": "android"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|