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.
@@ -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&lt;<a href="#compressresult">CompressResult</a>&gt;</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; }) =&gt; void</code> |
50
+
51
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</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>() =&gt; Promise&lt;void&gt;</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,11 @@
1
+ package com.atomic.videocompressor;
2
+
3
+ import com.getcapacitor.Logger;
4
+
5
+ public class NativeVideoCompressor {
6
+
7
+ public String echo(String value) {
8
+ Logger.info("Echo", value);
9
+ return value;
10
+ }
11
+ }
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=definitions.js.map
@@ -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,4 @@
1
+ import type { NativeVideoCompressorPlugin } from './definitions';
2
+ declare const NativeVideoCompressor: NativeVideoCompressorPlugin;
3
+ export * from './definitions';
4
+ export { NativeVideoCompressor };
@@ -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
+ }
@@ -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,8 @@
1
+ import Foundation
2
+
3
+ @objc public class NativeVideoCompressor: NSObject {
4
+ @objc public func echo(_ value: String) -> String {
5
+ print(value)
6
+ return value
7
+ }
8
+ }
@@ -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
+ }