Skip to content

Commit 68bfa12

Browse files
authored
Merge pull request #180 from numandev1/fix/increase-speed-android
fix: increase speed of android video compression
2 parents 1c7745f + 4022fa9 commit 68bfa12

File tree

19 files changed

+2597
-40
lines changed

19 files changed

+2597
-40
lines changed

android/build.gradle

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,12 @@ dependencies {
110110
//noinspection GradleDynamicVersion
111111
implementation "com.facebook.react:react-native:+"
112112
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
113-
implementation 'commons-io:commons-io:2.8.0'
113+
114+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
115+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
116+
implementation "com.googlecode.mp4parser:isoparser:1.0.6"
117+
114118
implementation 'io.github.lizhangqu:coreprogress:1.0.2'
115-
implementation 'com.github.numandev1:VideoCompressor:1a262bba37'
116119
}
117120

118121
if (isNewArchitectureEnabled()) {

android/src/main/java/com/reactnativecompressor/Audio/AudioCompressor.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import android.media.MediaMuxer
1111
import android.os.Build
1212
import android.util.Log
1313
import androidx.annotation.RequiresApi
14-
import numan.dev.videocompressor.codecinputsurface.CodecInputSurface
14+
import com.reactnativecompressor.Video.VideoCompressor.video.OutputSurface
1515
import java.io.File
1616
import java.io.IOException
1717
import java.nio.ByteBuffer
@@ -24,7 +24,7 @@ class AudioCompressor {
2424
private var mEncoder: MediaCodec? = null
2525
private var mDecoder: MediaCodec? = null
2626
private var mTrackIndex = 0
27-
private var mInputSurface: CodecInputSurface? = null
27+
private var mInputSurface: OutputSurface? = null
2828

2929
// bit rate, in bits per second
3030
private var mBitRate = -1

android/src/main/java/com/reactnativecompressor/Utils/Utils.kt

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import android.provider.OpenableColumns
77
import android.util.Log
88
import com.facebook.react.bridge.Promise
99
import com.facebook.react.bridge.ReactApplicationContext
10-
import numan.dev.videocompressor.VideoCompressTask
11-
import numan.dev.videocompressor.VideoCompressor
10+
import com.reactnativecompressor.Video.VideoCompressor.CompressionListener
11+
import com.reactnativecompressor.Video.VideoCompressor.VideoCompressorClass
1212
import java.io.FileNotFoundException
1313
import java.io.IOException
1414
import java.net.HttpURLConnection
@@ -17,7 +17,7 @@ import java.util.UUID
1717

1818
object Utils {
1919
private const val TAG = "react-native-compessor"
20-
var compressorExports: MutableMap<String, VideoCompressTask> = HashMap()
20+
var compressorExports: MutableMap<String, VideoCompressorClass?> = HashMap()
2121
@JvmStatic
2222
fun generateCacheFilePath(extension: String, reactContext: ReactApplicationContext): String {
2323
val outputDir = reactContext.cacheDir
@@ -26,48 +26,54 @@ object Utils {
2626

2727
@JvmStatic
2828
fun compressVideo(srcPath: String, destinationPath: String, resultWidth: Int, resultHeight: Int, videoBitRate: Float, uuid: String,progressDivider:Int, promise: Promise, reactContext: ReactApplicationContext) {
29-
val currentVideoCompression = intArrayOf(0)
30-
try {
31-
val export = VideoCompressor.convertVideo(srcPath, destinationPath, resultWidth, resultHeight, videoBitRate.toInt(), object : VideoCompressor.ProgressListener {
32-
override fun onStart() {
33-
//convert start
29+
val currentVideoCompression = intArrayOf(0)
30+
val videoCompressorClass: VideoCompressorClass? = VideoCompressorClass(reactContext);
31+
compressorExports[uuid] = videoCompressorClass
32+
videoCompressorClass?.start(srcPath, destinationPath, resultWidth, resultHeight, videoBitRate.toInt(),
33+
listener = object : CompressionListener {
34+
override fun onProgress(index: Int, percent: Float) {
35+
if (percent <= 100)
36+
{
37+
val roundProgress = Math.round(percent)
38+
if (progressDivider==0||(roundProgress % progressDivider == 0 && roundProgress > currentVideoCompression[0])) {
39+
EventEmitterHandler.emitVideoCompressProgress((percent / 100).toDouble(),uuid)
40+
currentVideoCompression[0] = roundProgress
3441
}
42+
}
43+
}
3544

36-
override fun onFinish(result: Boolean) {
37-
val fileUrl = "file://$destinationPath"
38-
//convert finish,result(true is success,false is fail)
39-
promise.resolve(fileUrl)
40-
MediaCache.removeCompletedImagePath(fileUrl)
41-
}
45+
override fun onStart(index: Int) {
4246

43-
override fun onError(errorMessage: String) {
44-
if (errorMessage == "class java.lang.AssertionError") {
45-
promise.resolve("file://$srcPath")
46-
} else {
47-
promise.reject("Compression has canncelled")
48-
}
49-
}
47+
}
5048

51-
override fun onProgress(percent: Float) {
52-
val roundProgress = Math.round(percent)
53-
if (progressDivider==0||(roundProgress % progressDivider == 0 && roundProgress > currentVideoCompression[0])) {
54-
EventEmitterHandler.emitVideoCompressProgress((percent / 100).toDouble(),uuid)
55-
currentVideoCompression[0] = roundProgress
56-
}
57-
}
58-
})
59-
compressorExports[uuid] = export
60-
} catch (ex: Exception) {
61-
promise.reject(ex)
62-
} finally {
63-
currentVideoCompression[0] = 0
64-
}
49+
override fun onSuccess(index: Int, size: Long, path: String?) {
50+
val fileUrl = "file://$destinationPath"
51+
//convert finish,result(true is success,false is fail)
52+
promise.resolve(fileUrl)
53+
MediaCache.removeCompletedImagePath(fileUrl)
54+
currentVideoCompression[0] = 0
55+
compressorExports[uuid]=null
56+
}
57+
58+
override fun onFailure(index: Int, failureMessage: String) {
59+
Log.wtf("failureMessage", failureMessage)
60+
currentVideoCompression[0] = 0
61+
}
62+
63+
override fun onCancelled(index: Int) {
64+
Log.wtf("TAG", "compression has been cancelled")
65+
// make UI changes, cleanup, etc
66+
currentVideoCompression[0] = 0
67+
}
68+
},
69+
)
6570
}
6671

6772
fun cancelCompressionHelper(uuid: String) {
6873
try {
6974
val export = compressorExports[uuid]
70-
export!!.cancel(true)
75+
export?.cancel()
76+
compressorExports[uuid]=null
7177
} catch (ex: Exception) {
7278
}
7379
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.reactnativecompressor.Video.VideoCompressor
2+
3+
import androidx.annotation.MainThread
4+
import androidx.annotation.WorkerThread
5+
6+
/**
7+
* Interface for listening to video compression events.
8+
*/
9+
interface CompressionListener {
10+
/**
11+
* Called when video compression starts.
12+
*
13+
* @param index The index of the video being compressed.
14+
*/
15+
@MainThread
16+
fun onStart(index: Int)
17+
18+
/**
19+
* Called when video compression succeeds.
20+
*
21+
* @param index The index of the video that was compressed.
22+
* @param size The size of the compressed video.
23+
* @param path The path to the compressed video file (nullable).
24+
*/
25+
@MainThread
26+
fun onSuccess(index: Int, size: Long, path: String?)
27+
28+
/**
29+
* Called when video compression fails.
30+
*
31+
* @param index The index of the video that failed to compress.
32+
* @param failureMessage A message describing the reason for the failure.
33+
*/
34+
@MainThread
35+
fun onFailure(index: Int, failureMessage: String)
36+
37+
/**
38+
* Called to report the progress of video compression.
39+
*
40+
* @param index The index of the video being compressed.
41+
* @param percent The progress percentage (0.0 to 100.0).
42+
*/
43+
@WorkerThread
44+
fun onProgress(index: Int, percent: Float)
45+
46+
/**
47+
* Called when video compression is canceled.
48+
*
49+
* @param index The index of the video that was canceled.
50+
*/
51+
@WorkerThread
52+
fun onCancelled(index: Int)
53+
}
54+
55+
/**
56+
* Interface for tracking the progress of video compression.
57+
*/
58+
interface CompressionProgressListener {
59+
/**
60+
* Called when the progress of video compression changes.
61+
*
62+
* @param index The index of the video being compressed.
63+
* @param percent The current progress percentage (0.0 to 100.0).
64+
*/
65+
fun onProgressChanged(index: Int, percent: Float)
66+
67+
/**
68+
* Called when the compression progress is canceled.
69+
*
70+
* @param index The index of the video compression that was canceled.
71+
*/
72+
fun onProgressCancelled(index: Int)
73+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package com.reactnativecompressor.Video.VideoCompressor
2+
3+
import android.database.Cursor
4+
import android.net.Uri
5+
import android.provider.MediaStore
6+
import com.facebook.react.bridge.ReactApplicationContext
7+
import com.reactnativecompressor.Video.VideoCompressor.compressor.Compressor.compressVideo
8+
import com.reactnativecompressor.Video.VideoCompressor.compressor.Compressor.isRunning
9+
import com.reactnativecompressor.Video.VideoCompressor.video.Result
10+
import kotlinx.coroutines.CoroutineScope
11+
import kotlinx.coroutines.Dispatchers
12+
import kotlinx.coroutines.Job
13+
import kotlinx.coroutines.launch
14+
import kotlinx.coroutines.withContext
15+
import java.io.File
16+
17+
class VideoCompressorClass(private val context: ReactApplicationContext) {
18+
private var job: Job? = null
19+
20+
@JvmOverloads
21+
fun start(
22+
srcPath: String,
23+
destPath: String,
24+
outputWidth: Int,
25+
outputHeight: Int,
26+
bitrate: Int,
27+
listener: CompressionListener,
28+
) {
29+
val uris = mutableListOf<Uri>()
30+
val uri = Uri.parse(srcPath)
31+
uris.add(uri)
32+
val isStreamable: Boolean = false
33+
doVideoCompression(
34+
uris,
35+
isStreamable,
36+
outputWidth,
37+
outputHeight,
38+
bitrate,
39+
listener,
40+
destPath
41+
)
42+
}
43+
44+
fun cancel() {
45+
job?.cancel()
46+
isRunning = false
47+
}
48+
49+
private fun doVideoCompression(
50+
uris: List<Uri>,
51+
isStreamable: Boolean,
52+
outputWidth: Int,
53+
outputHeight: Int,
54+
bitrate: Int,
55+
listener: CompressionListener,
56+
destPath: String
57+
) {
58+
var streamableFile: File? = null
59+
for (i in uris.indices) {
60+
job = CoroutineScope(Dispatchers.Main).launch {
61+
val desFile = File(destPath)
62+
63+
if (isStreamable)
64+
streamableFile = File(destPath)
65+
66+
desFile?.let {
67+
isRunning = true
68+
listener.onStart(i)
69+
val result = startCompression(
70+
i,
71+
uris[i],
72+
desFile.path,
73+
streamableFile?.path,
74+
outputWidth,
75+
outputHeight,
76+
bitrate,
77+
listener,
78+
)
79+
80+
// Runs in Main(UI) Thread
81+
if (result.success) {
82+
listener.onSuccess(i, result.size, result.path)
83+
} else {
84+
listener.onFailure(i, result.failureMessage ?: "An error has occurred!")
85+
}
86+
}
87+
}
88+
}
89+
}
90+
91+
private suspend fun startCompression(
92+
index: Int,
93+
srcUri: Uri,
94+
destPath: String,
95+
streamableFile: String? = null,
96+
outputWidth: Int,
97+
outputHeight: Int,
98+
bitrate: Int,
99+
listener: CompressionListener,
100+
): Result = withContext(Dispatchers.Default) {
101+
return@withContext compressVideo(
102+
index,
103+
context,
104+
srcUri,
105+
destPath,
106+
streamableFile,
107+
outputWidth,
108+
outputHeight,
109+
bitrate,
110+
object : CompressionProgressListener {
111+
override fun onProgressChanged(index: Int, percent: Float) {
112+
listener.onProgress(index, percent)
113+
}
114+
115+
override fun onProgressCancelled(index: Int) {
116+
listener.onCancelled(index)
117+
}
118+
},
119+
)
120+
}
121+
122+
private fun getMediaPath(uri: Uri): String {
123+
val scheme = uri.scheme
124+
if (scheme == null || scheme == "file") {
125+
// The URI is a file URI, so use the path directly.
126+
return uri.path ?: ""
127+
} else {
128+
// The URI is not a file URI, so try to copy the content to a file.
129+
val resolver = context.contentResolver
130+
val projection = arrayOf(MediaStore.Video.Media.DATA)
131+
var cursor: Cursor? = null
132+
try {
133+
cursor = resolver.query(uri, projection, null, null, null)
134+
if (cursor != null && cursor.moveToFirst()) {
135+
val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
136+
return cursor.getString(columnIndex)
137+
}
138+
} catch (e: Exception) {
139+
// Handle the exception as needed.
140+
} finally {
141+
cursor?.close()
142+
}
143+
}
144+
145+
// If all else fails, return an empty string or handle the error accordingly.
146+
return ""
147+
}
148+
}

0 commit comments

Comments
 (0)