react-native-compressor 1.8.0 → 1.8.2

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.
Files changed (47) hide show
  1. package/README.md +22 -1
  2. package/android/build.gradle +6 -2
  3. package/android/src/main/java/com/reactnativecompressor/Audio/AudioCompressor.kt +2 -2
  4. package/android/src/main/java/com/reactnativecompressor/CompressorModule.kt +12 -0
  5. package/android/src/main/java/com/reactnativecompressor/Utils/Uploader.kt +11 -11
  6. package/android/src/main/java/com/reactnativecompressor/Utils/Utils.kt +42 -36
  7. package/android/src/main/java/com/reactnativecompressor/Utils/createVideoThumbnail.kt +158 -0
  8. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/CompressionInterface.kt +73 -0
  9. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/VideoCompressorClass.kt +148 -0
  10. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/compressor/Compressor.kt +562 -0
  11. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/utils/Atoms.kt +55 -0
  12. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/utils/CompressorUtils.kt +196 -0
  13. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/utils/NumbersUtils.kt +47 -0
  14. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/utils/StreamableVideo.kt +209 -0
  15. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/InputSurface.kt +128 -0
  16. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/MP4Builder.kt +388 -0
  17. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/Mdat.kt +74 -0
  18. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/Mp4Movie.kt +54 -0
  19. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/OutputSurface.kt +102 -0
  20. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/Result.kt +9 -0
  21. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/Sample.kt +3 -0
  22. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/TextureRenderer.kt +214 -0
  23. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/Track.kt +286 -0
  24. package/android/src/oldarch/CompressorSpec.kt +2 -0
  25. package/ios/Compressor.mm +9 -0
  26. package/ios/CompressorManager.swift +11 -0
  27. package/ios/Utils/CreateVideoThumbnail.swift +111 -0
  28. package/lib/commonjs/Spec/NativeCompressor.js.map +1 -1
  29. package/lib/commonjs/index.js +14 -0
  30. package/lib/commonjs/index.js.map +1 -1
  31. package/lib/commonjs/utils/index.js +12 -1
  32. package/lib/commonjs/utils/index.js.map +1 -1
  33. package/lib/module/Spec/NativeCompressor.js.map +1 -1
  34. package/lib/module/index.js +4 -2
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/module/utils/index.js +7 -0
  37. package/lib/module/utils/index.js.map +1 -1
  38. package/lib/typescript/Spec/NativeCompressor.d.ts +8 -0
  39. package/lib/typescript/Spec/NativeCompressor.d.ts.map +1 -1
  40. package/lib/typescript/index.d.ts +15 -3
  41. package/lib/typescript/index.d.ts.map +1 -1
  42. package/lib/typescript/utils/index.d.ts +16 -1
  43. package/lib/typescript/utils/index.d.ts.map +1 -1
  44. package/package.json +1 -1
  45. package/src/Spec/NativeCompressor.ts +11 -0
  46. package/src/index.tsx +6 -0
  47. package/src/utils/index.tsx +28 -1
@@ -0,0 +1,214 @@
1
+ package com.reactnativecompressor.Video.VideoCompressor.video
2
+
3
+ import android.graphics.SurfaceTexture
4
+ import android.opengl.GLES11Ext
5
+ import android.opengl.GLES20
6
+ import android.opengl.Matrix
7
+ import java.nio.ByteBuffer
8
+ import java.nio.ByteOrder
9
+ import java.nio.FloatBuffer
10
+
11
+ class TextureRenderer {
12
+
13
+ private val floatSizeBytes = 4
14
+ private val triangleVerticesDataStrideBytes = 5 * floatSizeBytes
15
+ private val triangleVerticesDataPosOffset = 0
16
+ private val triangleVerticesDataUvOffset = 3
17
+ private var mTriangleVertices: FloatBuffer
18
+
19
+ private val vertexShader = """uniform mat4 uMVPMatrix;
20
+ uniform mat4 uSTMatrix;
21
+ attribute vec4 aPosition;
22
+ attribute vec4 aTextureCoord;
23
+ varying vec2 vTextureCoord;
24
+ void main() {
25
+ gl_Position = uMVPMatrix * aPosition;
26
+ vTextureCoord = (uSTMatrix * aTextureCoord).xy;
27
+ }
28
+ """
29
+
30
+ private val fragmentShader = """#extension GL_OES_EGL_image_external : require
31
+ precision mediump float;
32
+ varying vec2 vTextureCoord;
33
+ uniform samplerExternalOES sTexture;
34
+ void main() {
35
+ gl_FragColor = texture2D(sTexture, vTextureCoord);
36
+ }
37
+ """
38
+
39
+ private val mMVPMatrix = FloatArray(16)
40
+ private val mSTMatrix = FloatArray(16)
41
+
42
+ private var mProgram = 0
43
+ private var mTextureID = -12345
44
+ private var muMVPMatrixHandle = 0
45
+ private var muSTMatrixHandle = 0
46
+ private var maPositionHandle = 0
47
+ private var maTextureHandle = 0
48
+
49
+ init {
50
+ val mTriangleVerticesData = floatArrayOf( // X, Y, Z, U, V
51
+ -1.0f, -1.0f, 0f, 0f, 0f,
52
+ 1.0f, -1.0f, 0f, 1f, 0f,
53
+ -1.0f, 1.0f, 0f, 0f, 1f,
54
+ 1.0f, 1.0f, 0f, 1f, 1f
55
+ )
56
+ mTriangleVertices = ByteBuffer.allocateDirect(
57
+ mTriangleVerticesData.size * floatSizeBytes
58
+ )
59
+ .order(ByteOrder.nativeOrder()).asFloatBuffer()
60
+ mTriangleVertices.put(mTriangleVerticesData).position(0)
61
+
62
+ Matrix.setIdentityM(mSTMatrix, 0)
63
+ }
64
+
65
+ fun getTextureId(): Int = mTextureID
66
+
67
+ fun drawFrame(st: SurfaceTexture) {
68
+ checkGlError("onDrawFrame start")
69
+ st.getTransformMatrix(mSTMatrix)
70
+
71
+ GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f)
72
+ GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT or GLES20.GL_COLOR_BUFFER_BIT)
73
+
74
+ GLES20.glUseProgram(mProgram)
75
+ checkGlError("glUseProgram")
76
+
77
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
78
+ GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID)
79
+
80
+ mTriangleVertices.position(triangleVerticesDataPosOffset)
81
+ GLES20.glVertexAttribPointer(
82
+ maPositionHandle, 3, GLES20.GL_FLOAT, false,
83
+ triangleVerticesDataStrideBytes, mTriangleVertices
84
+ )
85
+ checkGlError("glVertexAttribPointer maPosition")
86
+ GLES20.glEnableVertexAttribArray(maPositionHandle)
87
+ checkGlError("glEnableVertexAttribArray maPositionHandle")
88
+
89
+ mTriangleVertices.position(triangleVerticesDataUvOffset)
90
+ GLES20.glVertexAttribPointer(
91
+ maTextureHandle, 2, GLES20.GL_FLOAT, false,
92
+ triangleVerticesDataStrideBytes, mTriangleVertices
93
+ )
94
+ checkGlError("glVertexAttribPointer maTextureHandle")
95
+ GLES20.glEnableVertexAttribArray(maTextureHandle)
96
+ checkGlError("glEnableVertexAttribArray maTextureHandle")
97
+
98
+ Matrix.setIdentityM(mMVPMatrix, 0)
99
+ GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0)
100
+ GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0)
101
+
102
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
103
+ checkGlError("glDrawArrays")
104
+ GLES20.glFinish()
105
+ }
106
+
107
+ /**
108
+ * Initializes GL state. Call this after the EGL surface has been created and made current.
109
+ */
110
+ fun surfaceCreated() {
111
+ mProgram = createProgram()
112
+ if (mProgram == 0) {
113
+ throw RuntimeException("failed creating program")
114
+ }
115
+ maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition")
116
+ checkGlError("glGetAttribLocation aPosition")
117
+ if (maPositionHandle == -1) {
118
+ throw RuntimeException("Could not get attrib location for aPosition")
119
+ }
120
+ maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord")
121
+ checkGlError("glGetAttribLocation aTextureCoord")
122
+ if (maTextureHandle == -1) {
123
+ throw RuntimeException("Could not get attrib location for aTextureCoord")
124
+ }
125
+
126
+ muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix")
127
+ checkGlError("glGetUniformLocation uMVPMatrix")
128
+ if (muMVPMatrixHandle == -1) {
129
+ throw RuntimeException("Could not get attrib location for uMVPMatrix")
130
+ }
131
+
132
+ muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix")
133
+ checkGlError("glGetUniformLocation uSTMatrix")
134
+ if (muSTMatrixHandle == -1) {
135
+ throw RuntimeException("Could not get attrib location for uSTMatrix")
136
+ }
137
+
138
+ val textures = IntArray(1)
139
+ GLES20.glGenTextures(1, textures, 0)
140
+ mTextureID = textures[0]
141
+ GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID)
142
+ checkGlError("glBindTexture mTextureID")
143
+
144
+ GLES20.glTexParameterf(
145
+ GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
146
+ GLES20.GL_NEAREST.toFloat()
147
+ )
148
+ GLES20.glTexParameterf(
149
+ GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
150
+ GLES20.GL_LINEAR.toFloat()
151
+ )
152
+ GLES20.glTexParameteri(
153
+ GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
154
+ GLES20.GL_CLAMP_TO_EDGE
155
+ )
156
+ GLES20.glTexParameteri(
157
+ GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
158
+ GLES20.GL_CLAMP_TO_EDGE
159
+ )
160
+ checkGlError("glTexParameter")
161
+ }
162
+
163
+ private fun loadShader(shaderType: Int, source: String): Int {
164
+ var shader = GLES20.glCreateShader(shaderType)
165
+ checkGlError("glCreateShader type=$shaderType")
166
+ GLES20.glShaderSource(shader, source)
167
+ GLES20.glCompileShader(shader)
168
+ val compiled = IntArray(1)
169
+ GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0)
170
+ if (compiled[0] == 0) {
171
+ GLES20.glDeleteShader(shader)
172
+ shader = 0
173
+ }
174
+ return shader
175
+ }
176
+
177
+ private fun createProgram(): Int {
178
+ val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShader)
179
+ if (vertexShader == 0) {
180
+ return 0
181
+ }
182
+
183
+ val pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader)
184
+ if (pixelShader == 0) {
185
+ return 0
186
+ }
187
+
188
+ var program = GLES20.glCreateProgram()
189
+ checkGlError("glCreateProgram")
190
+ if (program == 0) {
191
+ return 0
192
+ }
193
+ GLES20.glAttachShader(program, vertexShader)
194
+ checkGlError("glAttachShader")
195
+ GLES20.glAttachShader(program, pixelShader)
196
+ checkGlError("glAttachShader")
197
+ GLES20.glLinkProgram(program)
198
+ val linkStatus = IntArray(1)
199
+ GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0)
200
+ if (linkStatus[0] != GLES20.GL_TRUE) {
201
+ GLES20.glDeleteProgram(program)
202
+ program = 0
203
+ }
204
+ return program
205
+ }
206
+
207
+ fun checkGlError(op: String) {
208
+ var error: Int
209
+ if (GLES20.glGetError().also { error = it } != GLES20.GL_NO_ERROR) {
210
+ throw RuntimeException("$op: glError $error")
211
+ }
212
+ }
213
+
214
+ }
@@ -0,0 +1,286 @@
1
+ package com.reactnativecompressor.Video.VideoCompressor.video
2
+
3
+ import android.media.MediaCodec
4
+ import android.media.MediaCodecInfo
5
+ import android.media.MediaFormat
6
+ import com.coremedia.iso.boxes.SampleDescriptionBox
7
+ import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry
8
+ import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry
9
+ import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox
10
+ import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.AudioSpecificConfig
11
+ import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.DecoderConfigDescriptor
12
+ import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.ESDescriptor
13
+ import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.SLConfigDescriptor
14
+ import com.mp4parser.iso14496.part15.AvcConfigurationBox
15
+ import java.util.*
16
+
17
+ class Track(id: Int, format: MediaFormat, audio: Boolean) {
18
+
19
+ private var trackId: Long = 0
20
+ private val samples = ArrayList<Sample>()
21
+ private var duration: Long = 0
22
+ private var handler: String
23
+ private var sampleDescriptionBox: SampleDescriptionBox
24
+ private var syncSamples: LinkedList<Int>? = null
25
+ private var timeScale = 0
26
+ private val creationTime = Date()
27
+ private var height = 0
28
+ private var width = 0
29
+ private var volume = 0f
30
+ private val sampleDurations = ArrayList<Long>()
31
+ private val isAudio = audio
32
+ private var samplingFrequencyIndexMap: Map<Int, Int> = HashMap()
33
+ private var lastPresentationTimeUs: Long = 0
34
+ private var first = true
35
+
36
+ init {
37
+ samplingFrequencyIndexMap = mapOf(
38
+ 96000 to 0x0,
39
+ 88200 to 0x1,
40
+ 64000 to 0x2,
41
+ 48000 to 0x3,
42
+ 44100 to 0x4,
43
+ 32000 to 0x5,
44
+ 24000 to 0x6,
45
+ 22050 to 0x7,
46
+ 16000 to 0x8,
47
+ 12000 to 0x9,
48
+ 11025 to 0xa,
49
+ 8000 to 0xb,
50
+ )
51
+
52
+ trackId = id.toLong()
53
+ if (!isAudio) {
54
+ sampleDurations.add(3015.toLong())
55
+ duration = 3015
56
+ width = format.getInteger(MediaFormat.KEY_WIDTH)
57
+ height = format.getInteger(MediaFormat.KEY_HEIGHT)
58
+ timeScale = 90000
59
+ syncSamples = LinkedList()
60
+ handler = "vide"
61
+
62
+ sampleDescriptionBox = SampleDescriptionBox()
63
+ val mime = format.getString(MediaFormat.KEY_MIME)
64
+ if (mime == "video/avc") {
65
+ val visualSampleEntry =
66
+ VisualSampleEntry(VisualSampleEntry.TYPE3).setup(width, height)
67
+
68
+ val avcConfigurationBox = AvcConfigurationBox()
69
+ if (format.getByteBuffer("csd-0") != null) {
70
+ val spsArray = ArrayList<ByteArray>()
71
+ val spsBuff = format.getByteBuffer("csd-0")
72
+ spsBuff!!.position(4)
73
+
74
+ val spsBytes = ByteArray(spsBuff.remaining())
75
+ spsBuff[spsBytes]
76
+ spsArray.add(spsBytes)
77
+
78
+ val ppsArray = ArrayList<ByteArray>()
79
+ val ppsBuff = format.getByteBuffer("csd-1")
80
+ ppsBuff?.let {
81
+ it.position(4)
82
+
83
+ val ppsBytes = ByteArray(it.remaining())
84
+ it[ppsBytes]
85
+
86
+ ppsArray.add(ppsBytes)
87
+ avcConfigurationBox.sequenceParameterSets = spsArray
88
+ avcConfigurationBox.pictureParameterSets = ppsArray
89
+ }
90
+ }
91
+
92
+ if (format.containsKey("level")) {
93
+ when (format.getInteger("level")) {
94
+ MediaCodecInfo.CodecProfileLevel.AVCLevel1 -> {
95
+ avcConfigurationBox.avcLevelIndication = 1
96
+ }
97
+ MediaCodecInfo.CodecProfileLevel.AVCLevel2 -> {
98
+ avcConfigurationBox.avcLevelIndication = 2
99
+ }
100
+ MediaCodecInfo.CodecProfileLevel.AVCLevel11 -> {
101
+ avcConfigurationBox.avcLevelIndication = 11
102
+ }
103
+ MediaCodecInfo.CodecProfileLevel.AVCLevel12 -> {
104
+ avcConfigurationBox.avcLevelIndication = 12
105
+ }
106
+ MediaCodecInfo.CodecProfileLevel.AVCLevel13 -> {
107
+ avcConfigurationBox.avcLevelIndication = 13
108
+ }
109
+ MediaCodecInfo.CodecProfileLevel.AVCLevel21 -> {
110
+ avcConfigurationBox.avcLevelIndication = 21
111
+ }
112
+ MediaCodecInfo.CodecProfileLevel.AVCLevel22 -> {
113
+ avcConfigurationBox.avcLevelIndication = 22
114
+ }
115
+ MediaCodecInfo.CodecProfileLevel.AVCLevel3 -> {
116
+ avcConfigurationBox.avcLevelIndication = 3
117
+ }
118
+ MediaCodecInfo.CodecProfileLevel.AVCLevel31 -> {
119
+ avcConfigurationBox.avcLevelIndication = 31
120
+ }
121
+ MediaCodecInfo.CodecProfileLevel.AVCLevel32 -> {
122
+ avcConfigurationBox.avcLevelIndication = 32
123
+ }
124
+ MediaCodecInfo.CodecProfileLevel.AVCLevel4 -> {
125
+ avcConfigurationBox.avcLevelIndication = 4
126
+ }
127
+ MediaCodecInfo.CodecProfileLevel.AVCLevel41 -> {
128
+ avcConfigurationBox.avcLevelIndication = 41
129
+ }
130
+ MediaCodecInfo.CodecProfileLevel.AVCLevel42 -> {
131
+ avcConfigurationBox.avcLevelIndication = 42
132
+ }
133
+ MediaCodecInfo.CodecProfileLevel.AVCLevel5 -> {
134
+ avcConfigurationBox.avcLevelIndication = 5
135
+ }
136
+ MediaCodecInfo.CodecProfileLevel.AVCLevel51 -> {
137
+ avcConfigurationBox.avcLevelIndication = 51
138
+ }
139
+ MediaCodecInfo.CodecProfileLevel.AVCLevel52 -> {
140
+ avcConfigurationBox.avcLevelIndication = 52
141
+ }
142
+ MediaCodecInfo.CodecProfileLevel.AVCLevel1b -> {
143
+ avcConfigurationBox.avcLevelIndication = 0x1b
144
+ }
145
+ else -> avcConfigurationBox.avcLevelIndication = 13
146
+ }
147
+ } else {
148
+ avcConfigurationBox.avcLevelIndication = 13
149
+ }
150
+
151
+ avcConfigurationBox.avcProfileIndication = 100
152
+ avcConfigurationBox.bitDepthLumaMinus8 = -1
153
+ avcConfigurationBox.bitDepthChromaMinus8 = -1
154
+ avcConfigurationBox.chromaFormat = -1
155
+ avcConfigurationBox.configurationVersion = 1
156
+ avcConfigurationBox.lengthSizeMinusOne = 3
157
+ avcConfigurationBox.profileCompatibility = 0
158
+
159
+ visualSampleEntry.addBox(avcConfigurationBox)
160
+ sampleDescriptionBox.addBox(visualSampleEntry)
161
+
162
+ } else if (mime == "video/mp4v") {
163
+ val visualSampleEntry =
164
+ VisualSampleEntry(VisualSampleEntry.TYPE1).setup(width, height)
165
+ sampleDescriptionBox.addBox(visualSampleEntry)
166
+ }
167
+ } else {
168
+ sampleDurations.add(1024.toLong())
169
+ duration = 1024
170
+ volume = 1f
171
+ timeScale = format.getInteger(MediaFormat.KEY_SAMPLE_RATE)
172
+ handler = "soun"
173
+ sampleDescriptionBox = SampleDescriptionBox()
174
+
175
+ val audioSampleEntry = AudioSampleEntry(AudioSampleEntry.TYPE3).setup(format)
176
+
177
+ val esds = ESDescriptorBox()
178
+
179
+ val descriptor = ESDescriptor()
180
+ descriptor.esId = 0
181
+
182
+ val slConfigDescriptor = SLConfigDescriptor()
183
+ slConfigDescriptor.predefined = 2
184
+ descriptor.slConfigDescriptor = slConfigDescriptor
185
+
186
+ val decoderConfigDescriptor = DecoderConfigDescriptor().setup()
187
+
188
+ val audioSpecificConfig = AudioSpecificConfig()
189
+ audioSpecificConfig.setAudioObjectType(2)
190
+ audioSpecificConfig.setSamplingFrequencyIndex(
191
+ samplingFrequencyIndexMap[audioSampleEntry.sampleRate.toInt()]!!
192
+ )
193
+ audioSpecificConfig.setChannelConfiguration(audioSampleEntry.channelCount)
194
+ decoderConfigDescriptor.audioSpecificInfo = audioSpecificConfig
195
+ descriptor.decoderConfigDescriptor = decoderConfigDescriptor
196
+
197
+ val data = descriptor.serialize()
198
+ esds.esDescriptor = descriptor
199
+ esds.data = data
200
+ audioSampleEntry.addBox(esds)
201
+ sampleDescriptionBox.addBox(audioSampleEntry)
202
+ }
203
+ }
204
+
205
+ fun getTrackId(): Long = trackId
206
+
207
+ fun addSample(offset: Long, bufferInfo: MediaCodec.BufferInfo) {
208
+ val isSyncFrame = !isAudio && bufferInfo.flags and MediaCodec.BUFFER_FLAG_KEY_FRAME != 0
209
+
210
+ samples.add(Sample(offset, bufferInfo.size.toLong()))
211
+
212
+ if (syncSamples != null && isSyncFrame) {
213
+ syncSamples?.add(samples.size)
214
+ }
215
+ var delta = bufferInfo.presentationTimeUs - lastPresentationTimeUs
216
+ lastPresentationTimeUs = bufferInfo.presentationTimeUs
217
+ delta = (delta * timeScale + 500000L) / 1000000L
218
+ if (!first) {
219
+ sampleDurations.add(sampleDurations.size - 1, delta)
220
+ duration += delta
221
+ }
222
+ first = false
223
+ }
224
+
225
+ fun getSamples(): ArrayList<Sample> = samples
226
+
227
+ fun getDuration(): Long = duration
228
+
229
+ fun getHandler(): String = handler
230
+
231
+ fun getSampleDescriptionBox(): SampleDescriptionBox = sampleDescriptionBox
232
+
233
+ fun getSyncSamples(): LongArray? {
234
+ if (syncSamples == null || syncSamples!!.isEmpty()) {
235
+ return null
236
+ }
237
+ val returns = LongArray(syncSamples!!.size)
238
+ for (i in syncSamples!!.indices) {
239
+ returns[i] = syncSamples!![i].toLong()
240
+ }
241
+ return returns
242
+ }
243
+
244
+ fun getTimeScale(): Int = timeScale
245
+
246
+ fun getCreationTime(): Date = creationTime
247
+
248
+ fun getWidth(): Int = width
249
+
250
+ fun getHeight(): Int = height
251
+
252
+ fun getVolume(): Float = volume
253
+
254
+ fun getSampleDurations(): ArrayList<Long> = sampleDurations
255
+
256
+ fun isAudio(): Boolean = isAudio
257
+
258
+ private fun DecoderConfigDescriptor.setup(): DecoderConfigDescriptor = apply {
259
+ objectTypeIndication = 0x40
260
+ streamType = 5
261
+ bufferSizeDB = 1536
262
+ maxBitRate = 96000
263
+ avgBitRate = 96000
264
+ }
265
+
266
+ private fun VisualSampleEntry.setup(w: Int, h: Int): VisualSampleEntry = apply {
267
+ dataReferenceIndex = 1
268
+ depth = 24
269
+ frameCount = 1
270
+ horizresolution = 72.0
271
+ vertresolution = 72.0
272
+ width = w
273
+ height = h
274
+ compressorname = "AVC Coding"
275
+ }
276
+
277
+ private fun AudioSampleEntry.setup(format: MediaFormat): AudioSampleEntry = apply {
278
+ channelCount =
279
+ if (format.getInteger(MediaFormat.KEY_CHANNEL_COUNT) == 1) 2 else format.getInteger(
280
+ MediaFormat.KEY_CHANNEL_COUNT
281
+ )
282
+ sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE).toLong()
283
+ dataReferenceIndex = 1
284
+ sampleSize = 16
285
+ }
286
+ }
@@ -27,6 +27,8 @@ abstract class CompressorSpec(context: ReactApplicationContext?) : ReactContextB
27
27
  abstract fun download(fileUrl: String, options: ReadableMap, promise: Promise)
28
28
  abstract fun activateBackgroundTask(options: ReadableMap, promise: Promise)
29
29
  abstract fun deactivateBackgroundTask(options: ReadableMap, promise: Promise)
30
+ abstract fun createVideoThumbnail(fileUrl: String?, options: ReadableMap?, promise: Promise)
31
+ abstract fun clearCache(cacheDir: String?, promise: com.facebook.react.bridge.Promise?)
30
32
  abstract fun addListener(eventName: String)
31
33
  abstract fun removeListeners(count: Double)
32
34
  }
package/ios/Compressor.mm CHANGED
@@ -52,6 +52,15 @@ RCT_EXTERN_METHOD(deactivateBackgroundTask: (NSDictionary *)options
52
52
  withResolver:(RCTPromiseResolveBlock)resolve
53
53
  withRejecter:(RCTPromiseRejectBlock)reject)
54
54
 
55
+ RCT_EXTERN_METHOD(createVideoThumbnail:(NSString *)fileUrl
56
+ withOptions:(NSDictionary *)options
57
+ withResolver:(RCTPromiseResolveBlock)resolve
58
+ withRejecter:(RCTPromiseRejectBlock)reject)
59
+
60
+ RCT_EXTERN_METHOD(clearCache:(NSString *)cacheDir
61
+ withResolver:(RCTPromiseResolveBlock)resolve
62
+ withRejecter:(RCTPromiseRejectBlock)reject)
63
+
55
64
  RCT_EXTERN_METHOD(cancelCompression:(NSString *)uuid)
56
65
 
57
66
  // Don't compile this code when we build for the old architecture.
@@ -92,5 +92,16 @@ class Compressor: RCTEventEmitter {
92
92
  resolve(downloadedPath)
93
93
  }
94
94
  }
95
+
96
+ @objc(createVideoThumbnail:withOptions:withResolver:withRejecter:)
97
+ func createVideoThumbnail(fileUrl: String, options: NSDictionary, resolve:@escaping RCTPromiseResolveBlock, reject:@escaping RCTPromiseRejectBlock) -> Void {
98
+ let videoThumbnail=CreateVideoThumbnail()
99
+ videoThumbnail.create(fileUrl,options: options, resolve: resolve, rejecter: reject)
100
+ }
101
+
102
+ @objc(clearCache:withResolver:withRejecter:)
103
+ func clearCache(cacheDir: String, resolve:@escaping RCTPromiseResolveBlock, reject:@escaping RCTPromiseRejectBlock) -> Void {
104
+ CreateVideoThumbnail.cleanCacheDir(cacheDir: cacheDir,resolve: resolve,rejecter: reject)
105
+ }
95
106
 
96
107
  }
@@ -0,0 +1,111 @@
1
+ //
2
+ // CreateVideoThumbnail.swift
3
+ // react-native-compressor
4
+ //
5
+ // Created by Numan on 13/09/2023.
6
+ //
7
+
8
+ import Foundation
9
+ import AVFoundation
10
+ import UIKit
11
+
12
+ class CreateVideoThumbnail: NSObject {
13
+
14
+ func create(_ fileUrl:String, options: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
15
+ let headers = options["headers"] as? [String: Any] ?? [:]
16
+ let format = "jpeg"
17
+
18
+ do {
19
+ // Prepare cache folder
20
+ var tempDirectory = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last ?? ""
21
+ tempDirectory = (tempDirectory as NSString).appendingPathComponent("thumbnails/")
22
+ // Create thumbnail directory if not exists
23
+ try FileManager.default.createDirectory(atPath: tempDirectory, withIntermediateDirectories: true, attributes: nil)
24
+ let fileName = "thumb-\(ProcessInfo.processInfo.globallyUniqueString).\(format)"
25
+ let fullPath = (tempDirectory as NSString).appendingPathComponent(fileName)
26
+
27
+ if FileManager.default.fileExists(atPath: fullPath) {
28
+ if let imageData = try? Data(contentsOf: URL(fileURLWithPath: fullPath)) {
29
+ if let thumbnail = UIImage(data: imageData) {
30
+ resolve([
31
+ "path": fullPath,
32
+ "size": Float(imageData.count),
33
+ "mime": "image/\(format)",
34
+ "width": Float(thumbnail.size.width),
35
+ "height": Float(thumbnail.size.height)
36
+ ])
37
+ return
38
+ }
39
+ }
40
+ }
41
+
42
+ var vidURL: URL?
43
+ let fileUrlLowerCase = fileUrl.lowercased()
44
+
45
+ if fileUrlLowerCase.hasPrefix("http://") || fileUrlLowerCase.hasPrefix("https://") || fileUrlLowerCase.hasPrefix("file://") {
46
+ vidURL = URL(string: fileUrl)
47
+ } else {
48
+ // Consider it's file url path
49
+ vidURL = URL(fileURLWithPath: fileUrl)
50
+ }
51
+
52
+ let asset = AVURLAsset(url: vidURL!, options: ["AVURLAssetHTTPHeaderFieldsKey": headers])
53
+ generateThumbImage(asset: asset, atTime: 0, completion: { thumbnail in
54
+ // Generate thumbnail
55
+ var data: Data? = thumbnail.jpegData(compressionQuality: 1.0)
56
+
57
+ if let data = data {
58
+ try? data.write(to: URL(fileURLWithPath: fullPath))
59
+ resolve([
60
+ "path": fullPath,
61
+ "size": Float(data.count),
62
+ "mime": "image/\(format)",
63
+ "width": Float(thumbnail.size.width),
64
+ "height": Float(thumbnail.size.height)
65
+ ] as [String : Any])
66
+ }
67
+ }, failure: { error in
68
+ reject(error._domain, error.localizedDescription, nil)
69
+ })
70
+
71
+ } catch {
72
+ reject("Error", error.localizedDescription, nil)
73
+ }
74
+ }
75
+
76
+ static func cleanCacheDir(cacheDir: String, resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
77
+ let cachePathDir: String = cacheDir.isEmpty ? "thumbnails/" : cacheDir
78
+ var cacheDirectoryPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last ?? ""
79
+ cacheDirectoryPath = (cacheDirectoryPath as NSString).appendingPathComponent(cachePathDir)
80
+
81
+ let fm = FileManager.default
82
+ do {
83
+ let files = try fm.contentsOfDirectory(atPath: cacheDirectoryPath)
84
+ for file in files {
85
+ let filePath = (cacheDirectoryPath as NSString).appendingPathComponent(file)
86
+ try fm.removeItem(atPath: filePath)
87
+ }
88
+ resolve("done")
89
+ } catch {
90
+ // Handle error if necessary
91
+ reject("Error", error.localizedDescription, nil)
92
+ }
93
+ }
94
+
95
+ func generateThumbImage(asset: AVURLAsset, atTime timeStamp: Int, completion: @escaping (UIImage) -> Void, failure: @escaping (Error) -> Void) {
96
+ let generator = AVAssetImageGenerator(asset: asset)
97
+ generator.appliesPreferredTrackTransform = true
98
+ generator.maximumSize = CGSize(width: 512, height: 512)
99
+ generator.requestedTimeToleranceBefore = CMTimeMake(value: 0, timescale: 1000)
100
+ generator.requestedTimeToleranceAfter = CMTimeMake(value: 0, timescale: 1000)
101
+ let time = CMTimeMake(value: Int64(timeStamp), timescale: 1000)
102
+ generator.generateCGImagesAsynchronously(forTimes: [NSValue(time: time)]) { _, image, _, result, error in
103
+ if result == .succeeded, let cgImage = image {
104
+ let thumbnail = UIImage(cgImage: cgImage)
105
+ completion(thumbnail)
106
+ } else if let error = error {
107
+ failure(error)
108
+ }
109
+ }
110
+ }
111
+ }
@@ -1 +1 @@
1
- {"version":3,"names":["_reactNative","require","_default","TurboModuleRegistry","getEnforcing","exports","default"],"sourceRoot":"../../../src","sources":["Spec/NativeCompressor.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAAmD,IAAAC,QAAA,GAyBpCC,gCAAmB,CAACC,YAAY,CAAO,YAAY,CAAC;AAAAC,OAAA,CAAAC,OAAA,GAAAJ,QAAA"}
1
+ {"version":3,"names":["_reactNative","require","_default","TurboModuleRegistry","getEnforcing","exports","default"],"sourceRoot":"../../../src","sources":["Spec/NativeCompressor.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAAmD,IAAAC,QAAA,GAoCpCC,gCAAmB,CAACC,YAAY,CAAO,YAAY,CAAC;AAAAC,OAAA,CAAAC,OAAA,GAAAJ,QAAA"}
@@ -27,6 +27,18 @@ Object.defineProperty(exports, "backgroundUpload", {
27
27
  return _utils.backgroundUpload;
28
28
  }
29
29
  });
30
+ Object.defineProperty(exports, "clearCache", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _utils.clearCache;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "createVideoThumbnail", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _utils.createVideoThumbnail;
40
+ }
41
+ });
30
42
  exports.default = void 0;
31
43
  Object.defineProperty(exports, "download", {
32
44
  enumerable: true,
@@ -86,6 +98,8 @@ var _default = {
86
98
  getVideoMetaData: _utils.getVideoMetaData,
87
99
  getFileSize: _utils.getFileSize,
88
100
  backgroundUpload: _utils.backgroundUpload,
101
+ createVideoThumbnail: _utils.createVideoThumbnail,
102
+ clearCache: _utils.clearCache,
89
103
  download: _utils.download
90
104
  };
91
105
  exports.default = _default;