react-native-compressor 1.8.15 → 1.8.17
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 +36 -9
- package/android/src/main/java/com/reactnativecompressor/Audio/AudioCompressor.kt +58 -27
- package/android/src/main/java/com/reactnativecompressor/Audio/AudioHelper.kt +49 -0
- package/android/src/main/java/com/reactnativecompressor/Audio/AudioMain.kt +2 -6
- package/android/src/main/java/com/reactnativecompressor/Utils/createVideoThumbnail.kt +0 -2
- package/android/src/main/java/com/reactnativecompressor/Video/VideoMain.kt +6 -6
- package/ios/Audio/AudioHelper.swift +49 -0
- package/ios/Audio/AudioMain.swift +46 -82
- package/ios/Audio/AudioOptions.swift +37 -0
- package/ios/Audio/FormatConverter/FormatConverter+Compressed.swift +326 -0
- package/ios/Audio/FormatConverter/FormatConverter+PCM.swift +239 -0
- package/ios/Audio/FormatConverter/FormatConverter+Utilities.swift +103 -0
- package/ios/Audio/FormatConverter/FormatConverter.swift +264 -0
- package/ios/Compressor.xcodeproj/xcuserdata/apple.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/ios/Video/NextLevelSessionExporter.swift +694 -0
- package/ios/Video/VideoMain.swift +9 -8
- package/lib/commonjs/Audio/index.js +1 -3
- package/lib/commonjs/Audio/index.js.map +1 -1
- package/lib/commonjs/utils/index.js +0 -2
- package/lib/commonjs/utils/index.js.map +1 -1
- package/lib/module/Audio/index.js +1 -3
- package/lib/module/Audio/index.js.map +1 -1
- package/lib/module/utils/index.js +0 -3
- package/lib/module/utils/index.js.map +1 -1
- package/lib/typescript/Audio/index.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +7 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/utils/index.d.ts +11 -2
- package/lib/typescript/utils/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/react-native-compressor.podspec +0 -1
- package/src/Audio/index.tsx +1 -3
- package/src/utils/index.tsx +12 -3
package/README.md
CHANGED
|
@@ -129,9 +129,9 @@ react-native link react-native-compressor
|
|
|
129
129
|
|
|
130
130
|
#### iOS
|
|
131
131
|
|
|
132
|
-
1. In XCode,
|
|
133
|
-
2.
|
|
134
|
-
3.
|
|
132
|
+
1. In XCode, open Podfile
|
|
133
|
+
2. paste this line `pod 'react-native-compressor', :path => '../node_modules/react-native-compressor'` into `Podfile`
|
|
134
|
+
3. run this command inside ios folder `pod install`
|
|
135
135
|
4. Run your project (`Cmd+R`)<
|
|
136
136
|
|
|
137
137
|
#### Android
|
|
@@ -270,6 +270,17 @@ const result = await Audio.compress(
|
|
|
270
270
|
'file://path_of_file/file_example_MP3_2MG.wav', // recommended wav file but can be use mp3 file
|
|
271
271
|
{ quality: 'medium' }
|
|
272
272
|
);
|
|
273
|
+
|
|
274
|
+
// OR
|
|
275
|
+
|
|
276
|
+
const result = await Audio.compress(
|
|
277
|
+
'file://path_of_file/file_example_MP3_2MG.wav', // recommended wav file but can be use mp3 file
|
|
278
|
+
{
|
|
279
|
+
bitrate: 64000,
|
|
280
|
+
samplerate: 44100,
|
|
281
|
+
channels: 1,
|
|
282
|
+
}
|
|
283
|
+
);
|
|
273
284
|
```
|
|
274
285
|
|
|
275
286
|
### Background Upload
|
|
@@ -377,8 +388,14 @@ await clearCache(); // this will clear cache of thumbnails cache directory
|
|
|
377
388
|
- ###### `compress(url: string, options?: videoCompresssionType , onProgress?: (progress: number)): Promise<string>`
|
|
378
389
|
|
|
379
390
|
- ###### `cancelCompression(cancellationId: string): void`
|
|
391
|
+
|
|
380
392
|
we can get cancellationId from `getCancellationId` which is the callback method of compress method options
|
|
381
393
|
|
|
394
|
+
- ###### `activateBackgroundTask(onExpired?: (data: any) => void): Promise<any>`
|
|
395
|
+
if you wanna compress video while app is in backgroup then you should call this method before compression
|
|
396
|
+
- ###### `deactivateBackgroundTask(): Promise<any>`
|
|
397
|
+
if you call `activateBackgroundTask` method, then after video compression, you should call `deactivateBackgroundTask` for disable background task mode.
|
|
398
|
+
|
|
382
399
|
### videoCompresssionType
|
|
383
400
|
|
|
384
401
|
- ###### `compressionMethod: compressionMethod` (default: "manual")
|
|
@@ -415,10 +432,20 @@ await clearCache(); // this will clear cache of thumbnails cache directory
|
|
|
415
432
|
|
|
416
433
|
### audioCompresssionType
|
|
417
434
|
|
|
418
|
-
- ###### `quality
|
|
435
|
+
- ###### `quality?: qualityType` (default: medium)
|
|
436
|
+
|
|
419
437
|
we can also control bitrate through quality. qualityType can be `low` | `medium` | `high`
|
|
420
438
|
|
|
421
|
-
|
|
439
|
+
- ###### `bitrate?: number` Range [64000-320000]
|
|
440
|
+
|
|
441
|
+
we can control bitrate of audio through bitrate, it should be in the range of `64000-320000`
|
|
442
|
+
|
|
443
|
+
- ###### `samplerate?: number` Range [44100 - 192000]
|
|
444
|
+
|
|
445
|
+
we can control samplerate of audio through samplerate, it should be in the range of `44100 - 192000`
|
|
446
|
+
|
|
447
|
+
- ###### `channels?: number` Typically 1 or 2
|
|
448
|
+
we can control channels of audio through channels, Typically 1 or 2
|
|
422
449
|
|
|
423
450
|
## Background Upload
|
|
424
451
|
|
|
@@ -492,11 +519,11 @@ const metaData = await getVideoMetaData(filePath);
|
|
|
492
519
|
|
|
493
520
|
```
|
|
494
521
|
{
|
|
495
|
-
"duration":
|
|
522
|
+
"duration": 20.11,
|
|
496
523
|
"extension": "mp4",
|
|
497
|
-
"height":
|
|
498
|
-
"size":
|
|
499
|
-
"width":
|
|
524
|
+
"height": 1080,
|
|
525
|
+
"size": 16940.0,
|
|
526
|
+
"width": 1920
|
|
500
527
|
}
|
|
501
528
|
```
|
|
502
529
|
|
|
@@ -4,6 +4,7 @@ package com.reactnativecompressor.Audio
|
|
|
4
4
|
import android.annotation.SuppressLint
|
|
5
5
|
import com.facebook.react.bridge.Promise
|
|
6
6
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
|
+
import com.facebook.react.bridge.ReadableMap
|
|
7
8
|
import com.naman14.androidlame.LameBuilder
|
|
8
9
|
import com.naman14.androidlame.WaveReader
|
|
9
10
|
import com.reactnativecompressor.Utils.MediaCache
|
|
@@ -27,21 +28,22 @@ class AudioCompressor {
|
|
|
27
28
|
@JvmStatic
|
|
28
29
|
fun CompressAudio(
|
|
29
30
|
fileUrl: String,
|
|
30
|
-
|
|
31
|
+
optionMap: ReadableMap,
|
|
31
32
|
context: ReactApplicationContext,
|
|
32
33
|
promise: Promise,
|
|
33
34
|
) {
|
|
34
|
-
|
|
35
|
+
val realPath = Utils.getRealPath(fileUrl, context)
|
|
36
|
+
var _fileUrl=realPath
|
|
37
|
+
val filePathWithoutFileUri = realPath!!.replace("file://", "")
|
|
35
38
|
try {
|
|
36
|
-
|
|
37
|
-
var wavPath=filePath;
|
|
39
|
+
var wavPath=filePathWithoutFileUri;
|
|
38
40
|
var isNonWav:Boolean=false
|
|
39
41
|
if (fileUrl.endsWith(".mp4", ignoreCase = true))
|
|
40
42
|
{
|
|
41
43
|
addLog("mp4 file found")
|
|
42
44
|
val mp3Path= Utils.generateCacheFilePath("mp3", context)
|
|
43
45
|
AudioExtractor().genVideoUsingMuxer(fileUrl, mp3Path, -1, -1, true, false)
|
|
44
|
-
_fileUrl=
|
|
46
|
+
_fileUrl=Utils.slashifyFilePath(mp3Path)
|
|
45
47
|
wavPath= Utils.generateCacheFilePath("wav", context)
|
|
46
48
|
try {
|
|
47
49
|
val converter = Converter()
|
|
@@ -58,7 +60,7 @@ class AudioCompressor {
|
|
|
58
60
|
wavPath= Utils.generateCacheFilePath("wav", context)
|
|
59
61
|
try {
|
|
60
62
|
val converter = Converter()
|
|
61
|
-
converter.convert(
|
|
63
|
+
converter.convert(filePathWithoutFileUri, wavPath)
|
|
62
64
|
} catch (e: JavaLayerException) {
|
|
63
65
|
addLog("JavaLayerException error"+e.localizedMessage)
|
|
64
66
|
e.printStackTrace();
|
|
@@ -67,7 +69,7 @@ class AudioCompressor {
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
|
|
70
|
-
autoCompressHelper(wavPath,
|
|
72
|
+
autoCompressHelper(wavPath,filePathWithoutFileUri, optionMap,context) { mp3Path, finished ->
|
|
71
73
|
if (finished) {
|
|
72
74
|
val returnableFilePath:String="file://$mp3Path"
|
|
73
75
|
addLog("finished: " + returnableFilePath)
|
|
@@ -87,28 +89,23 @@ class AudioCompressor {
|
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
|
|
90
|
-
private fun getAudioBitrateByQuality(quality: String): Int {
|
|
91
|
-
return when (quality) {
|
|
92
|
-
"low" -> 64 // Set your low bitrate value here
|
|
93
|
-
"medium" -> 128 // Set your medium bitrate value here
|
|
94
|
-
"high" -> 256 // Set your high bitrate value here
|
|
95
|
-
else -> 128 // Default to medium bitrate if quality is not recognized
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
92
|
@SuppressLint("WrongConstant")
|
|
100
93
|
private fun autoCompressHelper(
|
|
101
94
|
fileUrl: String,
|
|
102
|
-
|
|
95
|
+
actualFileUrl: String,
|
|
96
|
+
optionMap: ReadableMap,
|
|
103
97
|
context: ReactApplicationContext,
|
|
104
98
|
completeCallback: (String, Boolean) -> Unit
|
|
105
99
|
) {
|
|
100
|
+
|
|
101
|
+
val options = AudioHelper.fromMap(optionMap)
|
|
102
|
+
val quality = options.quality
|
|
103
|
+
|
|
106
104
|
var isCompletedCallbackTriggered:Boolean=false
|
|
107
105
|
try {
|
|
108
106
|
var mp3Path = Utils.generateCacheFilePath("mp3", context)
|
|
109
107
|
val input = File(fileUrl)
|
|
110
108
|
val output = File(mp3Path)
|
|
111
|
-
val audioBitrate= getAudioBitrateByQuality(quality)
|
|
112
109
|
|
|
113
110
|
val CHUNK_SIZE = 8192
|
|
114
111
|
addLog("Initialising wav reader")
|
|
@@ -122,12 +119,46 @@ class AudioCompressor {
|
|
|
122
119
|
}
|
|
123
120
|
|
|
124
121
|
addLog("Intitialising encoder")
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
.
|
|
130
|
-
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
// for bitrate
|
|
125
|
+
var audioBitrate:Int
|
|
126
|
+
if(options.bitrate != -1)
|
|
127
|
+
{
|
|
128
|
+
audioBitrate= options.bitrate/1000
|
|
129
|
+
}
|
|
130
|
+
else
|
|
131
|
+
{
|
|
132
|
+
audioBitrate=AudioHelper.getDestinationBitrateByQuality(actualFileUrl, quality!!)
|
|
133
|
+
Utils.addLog("dest bitrate: $audioBitrate")
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
var androidLame = LameBuilder();
|
|
137
|
+
androidLame.setOutBitrate(audioBitrate)
|
|
138
|
+
|
|
139
|
+
// for channels
|
|
140
|
+
var audioChannels:Int
|
|
141
|
+
if(options.channels != -1){
|
|
142
|
+
audioChannels= options.channels!!
|
|
143
|
+
}
|
|
144
|
+
else
|
|
145
|
+
{
|
|
146
|
+
audioChannels=waveReader!!.channels
|
|
147
|
+
}
|
|
148
|
+
androidLame.setOutChannels(audioChannels)
|
|
149
|
+
|
|
150
|
+
// for sample rate
|
|
151
|
+
androidLame.setInSampleRate(waveReader!!.sampleRate)
|
|
152
|
+
var audioSampleRate:Int
|
|
153
|
+
if(options.samplerate != -1){
|
|
154
|
+
audioSampleRate= options.samplerate!!
|
|
155
|
+
}
|
|
156
|
+
else
|
|
157
|
+
{
|
|
158
|
+
audioSampleRate=waveReader!!.sampleRate
|
|
159
|
+
}
|
|
160
|
+
androidLame.setOutSampleRate(audioSampleRate)
|
|
161
|
+
val androidLameBuild=androidLame.build()
|
|
131
162
|
|
|
132
163
|
try {
|
|
133
164
|
outputStream = BufferedOutputStream(FileOutputStream(output), OUTPUT_STREAM_BUFFER)
|
|
@@ -154,7 +185,7 @@ class AudioCompressor {
|
|
|
154
185
|
if (bytesRead > 0) {
|
|
155
186
|
|
|
156
187
|
var bytesEncoded = 0
|
|
157
|
-
bytesEncoded =
|
|
188
|
+
bytesEncoded = androidLameBuild.encode(buffer_l, buffer_r, bytesRead, mp3Buf)
|
|
158
189
|
addLog("bytes encoded=$bytesEncoded")
|
|
159
190
|
|
|
160
191
|
if (bytesEncoded > 0) {
|
|
@@ -177,7 +208,7 @@ class AudioCompressor {
|
|
|
177
208
|
if (bytesRead > 0) {
|
|
178
209
|
var bytesEncoded = 0
|
|
179
210
|
|
|
180
|
-
bytesEncoded =
|
|
211
|
+
bytesEncoded = androidLameBuild.encode(buffer_l, buffer_l, bytesRead, mp3Buf)
|
|
181
212
|
addLog("bytes encoded=$bytesEncoded")
|
|
182
213
|
|
|
183
214
|
if (bytesEncoded > 0) {
|
|
@@ -202,7 +233,7 @@ class AudioCompressor {
|
|
|
202
233
|
}
|
|
203
234
|
|
|
204
235
|
addLog("flushing final mp3buffer")
|
|
205
|
-
val outputMp3buf =
|
|
236
|
+
val outputMp3buf = androidLameBuild.flush(mp3Buf)
|
|
206
237
|
addLog("flushed $outputMp3buf bytes")
|
|
207
238
|
if (outputMp3buf > 0) {
|
|
208
239
|
try {
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
package com.reactnativecompressor.Audio
|
|
2
2
|
|
|
3
|
+
import android.media.MediaExtractor
|
|
4
|
+
import android.media.MediaFormat
|
|
3
5
|
import com.facebook.react.bridge.ReadableMap
|
|
6
|
+
import com.reactnativecompressor.Utils.Utils
|
|
7
|
+
import java.io.File
|
|
8
|
+
import java.io.IOException
|
|
9
|
+
|
|
4
10
|
|
|
5
11
|
class AudioHelper {
|
|
6
12
|
|
|
7
13
|
var quality: String? = "medium"
|
|
14
|
+
var bitrate: Int = -1
|
|
15
|
+
var samplerate: Int = -1
|
|
16
|
+
var channels: Int = -1
|
|
8
17
|
var progressDivider: Int? = 0
|
|
9
18
|
|
|
10
19
|
companion object {
|
|
@@ -15,9 +24,49 @@ class AudioHelper {
|
|
|
15
24
|
val key = iterator.nextKey()
|
|
16
25
|
when (key) {
|
|
17
26
|
"quality" -> options.quality = map.getString(key)
|
|
27
|
+
"bitrate" -> {
|
|
28
|
+
val bitrate = map.getInt(key)
|
|
29
|
+
options.bitrate = if (bitrate > 320000 || bitrate < 64000) 64000 else bitrate
|
|
30
|
+
}
|
|
31
|
+
"samplerate" -> options.samplerate = map.getInt(key)
|
|
32
|
+
"channels" -> options.channels = map.getInt(key)
|
|
18
33
|
}
|
|
19
34
|
}
|
|
20
35
|
return options
|
|
21
36
|
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
fun getAudioBitrate(path: String): Int {
|
|
40
|
+
val file = File(path)
|
|
41
|
+
val fileSize = file.length() * 8 // size in bits
|
|
42
|
+
|
|
43
|
+
val mex = MediaExtractor()
|
|
44
|
+
try {
|
|
45
|
+
mex.setDataSource(path)
|
|
46
|
+
} catch (e: IOException) {
|
|
47
|
+
e.printStackTrace()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
val mf = mex.getTrackFormat(0)
|
|
51
|
+
val durationUs = mf.getLong(MediaFormat.KEY_DURATION)
|
|
52
|
+
val durationSec = durationUs / 1_000_000.0 // convert duration to seconds
|
|
53
|
+
|
|
54
|
+
return (fileSize / durationSec).toInt()/1000 // bitrate in bits per second
|
|
55
|
+
}
|
|
56
|
+
fun getDestinationBitrateByQuality(path: String, quality: String): Int {
|
|
57
|
+
val originalBitrate = getAudioBitrate(path)
|
|
58
|
+
var destinationBitrate = originalBitrate
|
|
59
|
+
Utils.addLog("source bitrate: $originalBitrate")
|
|
60
|
+
|
|
61
|
+
when (quality.toLowerCase()) {
|
|
62
|
+
"low" -> destinationBitrate = maxOf(64, (originalBitrate * 0.3).toInt())
|
|
63
|
+
"medium" -> destinationBitrate = (originalBitrate * 0.5).toInt()
|
|
64
|
+
"high" -> destinationBitrate = minOf(320, (originalBitrate * 0.7).toInt())
|
|
65
|
+
else -> Utils.addLog("Invalid quality level. Please enter 'low', 'medium', or 'high'.")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return destinationBitrate
|
|
69
|
+
}
|
|
70
|
+
|
|
22
71
|
}
|
|
23
72
|
}
|
|
@@ -3,7 +3,6 @@ package com.reactnativecompressor.Audio
|
|
|
3
3
|
import com.facebook.react.bridge.Promise
|
|
4
4
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
5
|
import com.facebook.react.bridge.ReadableMap
|
|
6
|
-
import com.reactnativecompressor.Utils.Utils
|
|
7
6
|
|
|
8
7
|
class AudioMain(private val reactContext: ReactApplicationContext) {
|
|
9
8
|
fun compress_audio(
|
|
@@ -11,11 +10,8 @@ class AudioMain(private val reactContext: ReactApplicationContext) {
|
|
|
11
10
|
optionMap: ReadableMap,
|
|
12
11
|
promise: Promise) {
|
|
13
12
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
val realPath = Utils.getRealPath(fileUrl, reactContext)
|
|
17
|
-
Utils.addLog(fileUrl + "\n realPath= " + realPath)
|
|
18
|
-
AudioCompressor.CompressAudio(realPath!!, quality!!,reactContext,promise)
|
|
13
|
+
|
|
14
|
+
AudioCompressor.CompressAudio(fileUrl,optionMap,reactContext,promise)
|
|
19
15
|
} catch (ex: Exception) {
|
|
20
16
|
promise.reject(ex)
|
|
21
17
|
}
|
|
@@ -8,7 +8,6 @@ import android.net.Uri
|
|
|
8
8
|
import android.os.Build
|
|
9
9
|
import android.text.TextUtils
|
|
10
10
|
import android.webkit.URLUtil
|
|
11
|
-
import androidx.annotation.RequiresApi
|
|
12
11
|
import com.facebook.react.bridge.Arguments
|
|
13
12
|
import com.facebook.react.bridge.GuardedResultAsyncTask
|
|
14
13
|
import com.facebook.react.bridge.Promise
|
|
@@ -39,7 +38,6 @@ class CreateVideoThumbnailClass(private val reactContext: ReactApplicationContex
|
|
|
39
38
|
weakContext = WeakReference(reactContext.applicationContext)
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
@RequiresApi(Build.VERSION_CODES.N)
|
|
43
41
|
override fun doInBackgroundGuarded(): ReadableMap? {
|
|
44
42
|
val format = "jpeg"
|
|
45
43
|
val cacheName = if (options.hasKey("cacheName")) options.getString("cacheName") else ""
|
|
@@ -65,17 +65,17 @@ class VideoMain(private val reactContext: ReactApplicationContext) {
|
|
|
65
65
|
val metaRetriever = MediaMetadataRetriever()
|
|
66
66
|
metaRetriever.setDataSource(srcPath)
|
|
67
67
|
val file = File(srcPath)
|
|
68
|
-
val sizeInKBs = (file.length() / 1024).
|
|
68
|
+
val sizeInKBs = (file.length() / 1024).toDouble()
|
|
69
69
|
val actualHeight = metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)!!.toInt()
|
|
70
70
|
val actualWidth = metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)!!.toInt()
|
|
71
|
-
val duration = metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)!!.
|
|
71
|
+
val duration = metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)!!.toDouble()
|
|
72
72
|
val creationTime = metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE)
|
|
73
73
|
val extension = filePath!!.substring(filePath.lastIndexOf(".") + 1)
|
|
74
74
|
val params = Arguments.createMap()
|
|
75
|
-
params.
|
|
76
|
-
params.
|
|
77
|
-
params.
|
|
78
|
-
params.
|
|
75
|
+
params.putDouble("size", sizeInKBs)
|
|
76
|
+
params.putInt("width", actualWidth)
|
|
77
|
+
params.putInt("height", actualHeight)
|
|
78
|
+
params.putDouble("duration", duration / 1000)
|
|
79
79
|
params.putString("extension", extension)
|
|
80
80
|
params.putString("creationTime", creationTime.toString())
|
|
81
81
|
promise.resolve(params)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//
|
|
2
|
+
// AudioHelper.swift
|
|
3
|
+
// react-native-compressor
|
|
4
|
+
//
|
|
5
|
+
// Created by Numan on 12/11/2023.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import AVFoundation
|
|
9
|
+
|
|
10
|
+
class AudioHelper {
|
|
11
|
+
|
|
12
|
+
static func getAudioBitrate(path: String) -> Int {
|
|
13
|
+
let audioURL = URL(fileURLWithPath: path)
|
|
14
|
+
let avAsset = AVURLAsset(url: audioURL)
|
|
15
|
+
let keys: Set<URLResourceKey> = [.totalFileSizeKey, .fileSizeKey]
|
|
16
|
+
let resourceValues = try? audioURL.resourceValues(forKeys: keys)
|
|
17
|
+
let fileSize = resourceValues?.fileSize ?? resourceValues?.totalFileSize
|
|
18
|
+
|
|
19
|
+
// Calculate bitrate in kbps
|
|
20
|
+
if let fileSize = fileSize, avAsset.duration.seconds > 0 {
|
|
21
|
+
return Int(Double(fileSize) * 8 / avAsset.duration.seconds)
|
|
22
|
+
}
|
|
23
|
+
return 0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static func getDestinationBitrateByQuality(path: String, quality: String) -> Int {
|
|
27
|
+
let originalBitrate = getAudioBitrate(path: path)
|
|
28
|
+
var destinationBitrate = originalBitrate
|
|
29
|
+
print("source bitrate: \(originalBitrate)")
|
|
30
|
+
|
|
31
|
+
// Calculate the percentage of the original bitrate relative to the range 64000 to 320000
|
|
32
|
+
let percentage = Double(originalBitrate - 64000) / Double(320000 - 64000)
|
|
33
|
+
|
|
34
|
+
switch quality.lowercased() {
|
|
35
|
+
case "low":
|
|
36
|
+
destinationBitrate = max(64000, Int(Double(originalBitrate) * 0.3))
|
|
37
|
+
case "medium":
|
|
38
|
+
// Set destination bitrate to 60% of the original bitrate
|
|
39
|
+
destinationBitrate = Int(Double(originalBitrate) * 0.5)
|
|
40
|
+
case "high":
|
|
41
|
+
// Set destination bitrate to 80% of the original bitrate
|
|
42
|
+
destinationBitrate = min(320000,Int(Double(originalBitrate) * 0.7))
|
|
43
|
+
default:
|
|
44
|
+
print("Invalid quality level. Please enter 'low', 'medium', or 'high'.")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return destinationBitrate
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -5,16 +5,21 @@
|
|
|
5
5
|
// Created by Numan on 10/09/2023.
|
|
6
6
|
//
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
import AVFoundation
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
10
13
|
let AlAsset_Library_Scheme = "assets-library"
|
|
11
14
|
class AudioMain{
|
|
12
15
|
static func compress_audio(_ fileUrl: String, optionMap: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
13
16
|
do {
|
|
14
17
|
var fileUrl = fileUrl
|
|
18
|
+
|
|
15
19
|
if fileUrl.hasPrefix("file://") {
|
|
16
20
|
fileUrl = fileUrl.replacingOccurrences(of: "file://", with: "")
|
|
17
21
|
}
|
|
22
|
+
|
|
18
23
|
let fileManager = FileManager.default
|
|
19
24
|
var isDir: ObjCBool = false
|
|
20
25
|
if !fileManager.fileExists(atPath: fileUrl, isDirectory: &isDir) || isDir.boolValue {
|
|
@@ -23,93 +28,52 @@ class AudioMain{
|
|
|
23
28
|
return
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
let
|
|
27
|
-
let
|
|
28
|
-
|
|
29
|
-
let
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
} catch {
|
|
38
|
-
reject(error.localizedDescription, error.localizedDescription, nil)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
static func getAudioQualityConstant(_ quality: String) -> String {
|
|
43
|
-
let audioQualityArray = ["low", "medium", "high"]
|
|
44
|
-
if let index = audioQualityArray.firstIndex(of: quality) {
|
|
45
|
-
switch index {
|
|
46
|
-
case 0:
|
|
47
|
-
return AVAssetExportPresetLowQuality
|
|
48
|
-
case 1:
|
|
49
|
-
return AVAssetExportPresetMediumQuality
|
|
50
|
-
case 2:
|
|
51
|
-
return AVAssetExportPresetHighestQuality
|
|
52
|
-
default:
|
|
53
|
-
return AVAssetExportPresetMediumQuality
|
|
31
|
+
let audioOptions = AudioOptions.fromDictionary((optionMap as! [String : Any]))
|
|
32
|
+
let outputMp3Path = "file://\(Utils.generateCacheFilePath("m4a"))"
|
|
33
|
+
|
|
34
|
+
let oldUfileUrlRL = URL(string: fileUrl)!
|
|
35
|
+
let newURL = URL(string: outputMp3Path)!
|
|
36
|
+
|
|
37
|
+
var options = FormatConverter.Options()
|
|
38
|
+
|
|
39
|
+
if(audioOptions.samplerate != -1){
|
|
40
|
+
options.sampleRate = Double(audioOptions.samplerate)
|
|
54
41
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
static func auto_compress_helper(_ avAsset: AVURLAsset, qualityConstant: String, complete completeCallback: @escaping (_ mp3Path: String?, _ finished: Bool) -> Void) {
|
|
60
|
-
var path: String
|
|
61
|
-
if avAsset.url.scheme == AlAsset_Library_Scheme {
|
|
62
|
-
path = avAsset.url.query ?? ""
|
|
63
|
-
if path.isEmpty {
|
|
64
|
-
completeCallback(nil, false)
|
|
65
|
-
return
|
|
42
|
+
|
|
43
|
+
if(audioOptions.bitrate != -1){
|
|
44
|
+
options.bitRate = UInt32(audioOptions.bitrate)
|
|
66
45
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
46
|
+
else
|
|
47
|
+
{
|
|
48
|
+
let bitrate = AudioHelper.getDestinationBitrateByQuality(path: fileUrl,quality: audioOptions.quality)
|
|
49
|
+
print("output bitrate: \(bitrate)")
|
|
50
|
+
options.bitRate = UInt32(bitrate)
|
|
72
51
|
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
let mp3Path = Utils.generateCacheFilePath("m4a")
|
|
76
|
-
|
|
77
|
-
if FileManager.default.fileExists(atPath: mp3Path) {
|
|
78
|
-
completeCallback(mp3Path, true)
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
let compatiblePresets = AVAssetExportSession.exportPresets(compatibleWith: avAsset)
|
|
83
|
-
|
|
84
|
-
if compatiblePresets.contains(qualityConstant) {
|
|
85
|
-
let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetAppleM4A)
|
|
86
52
|
|
|
87
|
-
let mp3Url = URL(fileURLWithPath: mp3Path)
|
|
88
|
-
exportSession?.outputURL = mp3Url
|
|
89
|
-
exportSession?.shouldOptimizeForNetworkUse = true
|
|
90
|
-
exportSession?.outputFileType = .m4a
|
|
91
53
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
print("
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
default:
|
|
109
|
-
break
|
|
54
|
+
if(audioOptions.channels != -1){
|
|
55
|
+
options.channels = UInt32(audioOptions.channels)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
options.format = .m4a
|
|
59
|
+
options.eraseFile = false
|
|
60
|
+
options.bitDepthRule = .any
|
|
61
|
+
|
|
62
|
+
let converter = FormatConverter(inputURL: oldUfileUrlRL, outputURL: newURL, options: options)
|
|
63
|
+
converter.start { error in
|
|
64
|
+
// check to see if error isn't nil, otherwise you're good
|
|
65
|
+
if((error) != nil)
|
|
66
|
+
{
|
|
67
|
+
print("error=> \(error?.localizedDescription)")
|
|
68
|
+
reject(error?.localizedDescription,error?.localizedDescription, error)
|
|
69
|
+
return
|
|
110
70
|
}
|
|
111
|
-
|
|
112
|
-
|
|
71
|
+
|
|
72
|
+
resolve(newURL.absoluteString)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
} catch {
|
|
76
|
+
reject(error.localizedDescription, error.localizedDescription, nil)
|
|
113
77
|
}
|
|
114
78
|
}
|
|
115
79
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
class AudioOptions: NSObject {
|
|
2
|
+
static func fromDictionary(_ dictionary: [String: Any]?) -> AudioOptions {
|
|
3
|
+
let options = AudioOptions()
|
|
4
|
+
|
|
5
|
+
guard let dictionary = dictionary else {
|
|
6
|
+
return options
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
for (key, value) in dictionary {
|
|
10
|
+
switch key {
|
|
11
|
+
case "quality":
|
|
12
|
+
options.quality = (value as? String) ?? "medium"
|
|
13
|
+
case "bitrate":
|
|
14
|
+
options.bitrate = (value as? Int) ?? -1
|
|
15
|
+
case "samplerate":
|
|
16
|
+
options.samplerate = (value as? Int) ?? -1
|
|
17
|
+
case "channels":
|
|
18
|
+
options.channels = (value as? Int) ?? -1
|
|
19
|
+
|
|
20
|
+
default:
|
|
21
|
+
break
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return options
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
var quality: String = "medium"
|
|
29
|
+
var bitrate: Int = -1
|
|
30
|
+
var samplerate: Int = -1
|
|
31
|
+
var channels: Int = -1
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
override init() {
|
|
35
|
+
super.init()
|
|
36
|
+
}
|
|
37
|
+
}
|