react-native-compressor 1.8.1 → 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.
- package/README.md +2 -1
- package/android/build.gradle +5 -2
- package/android/src/main/java/com/reactnativecompressor/Audio/AudioCompressor.kt +2 -2
- package/android/src/main/java/com/reactnativecompressor/CompressorModule.kt +3 -3
- package/android/src/main/java/com/reactnativecompressor/Utils/Uploader.kt +11 -11
- package/android/src/main/java/com/reactnativecompressor/Utils/Utils.kt +42 -36
- package/android/src/main/java/com/reactnativecompressor/Utils/createVideoThumbnail.kt +1 -1
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/CompressionInterface.kt +73 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/VideoCompressorClass.kt +148 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/compressor/Compressor.kt +562 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/utils/Atoms.kt +55 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/utils/CompressorUtils.kt +196 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/utils/NumbersUtils.kt +47 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/utils/StreamableVideo.kt +209 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/InputSurface.kt +128 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/MP4Builder.kt +388 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/Mdat.kt +74 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/Mp4Movie.kt +54 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/OutputSurface.kt +102 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/Result.kt +9 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/Sample.kt +3 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/TextureRenderer.kt +214 -0
- package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/Track.kt +286 -0
- package/lib/commonjs/utils/index.js +2 -2
- package/lib/commonjs/utils/index.js.map +1 -1
- package/lib/module/utils/index.js +2 -2
- package/lib/module/utils/index.js.map +1 -1
- package/lib/typescript/index.d.ts +13 -3
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/utils/index.d.ts +16 -3
- package/lib/typescript/utils/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/utils/index.tsx +22 -8
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
package com.reactnativecompressor.Video.VideoCompressor.video
|
|
2
|
+
|
|
3
|
+
import android.media.MediaCodec
|
|
4
|
+
import android.media.MediaFormat
|
|
5
|
+
import com.coremedia.iso.boxes.*
|
|
6
|
+
import com.googlecode.mp4parser.util.Matrix
|
|
7
|
+
import java.io.FileOutputStream
|
|
8
|
+
import java.nio.ByteBuffer
|
|
9
|
+
import java.nio.channels.FileChannel
|
|
10
|
+
import java.util.*
|
|
11
|
+
|
|
12
|
+
class MP4Builder {
|
|
13
|
+
|
|
14
|
+
private lateinit var mdat: Mdat
|
|
15
|
+
private lateinit var currentMp4Movie: Mp4Movie
|
|
16
|
+
private lateinit var fos: FileOutputStream
|
|
17
|
+
private lateinit var fc: FileChannel
|
|
18
|
+
private var dataOffset: Long = 0
|
|
19
|
+
private var wroteSinceLastMdat: Long = 0
|
|
20
|
+
private var writeNewMdat = true
|
|
21
|
+
private val track2SampleSizes = HashMap<Track, LongArray>()
|
|
22
|
+
private lateinit var sizeBuffer: ByteBuffer
|
|
23
|
+
|
|
24
|
+
@Throws(Exception::class)
|
|
25
|
+
fun createMovie(mp4Movie: Mp4Movie): MP4Builder {
|
|
26
|
+
currentMp4Movie = mp4Movie
|
|
27
|
+
|
|
28
|
+
fos = FileOutputStream(mp4Movie.getCacheFile())
|
|
29
|
+
fc = fos.channel
|
|
30
|
+
|
|
31
|
+
val fileTypeBox: FileTypeBox = createFileTypeBox()
|
|
32
|
+
fileTypeBox.getBox(fc)
|
|
33
|
+
dataOffset += fileTypeBox.size
|
|
34
|
+
wroteSinceLastMdat = dataOffset
|
|
35
|
+
|
|
36
|
+
mdat = Mdat()
|
|
37
|
+
sizeBuffer = ByteBuffer.allocateDirect(4)
|
|
38
|
+
|
|
39
|
+
return this
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@Throws(Exception::class)
|
|
43
|
+
private fun flushCurrentMdat() {
|
|
44
|
+
val oldPosition = fc.position()
|
|
45
|
+
fc.position(mdat.offset)
|
|
46
|
+
mdat.getBox(fc)
|
|
47
|
+
fc.position(oldPosition)
|
|
48
|
+
mdat.setDataOffset(0)
|
|
49
|
+
mdat.setContentSize(0)
|
|
50
|
+
fos.flush()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Throws(Exception::class)
|
|
54
|
+
fun writeSampleData(
|
|
55
|
+
trackIndex: Int,
|
|
56
|
+
byteBuf: ByteBuffer,
|
|
57
|
+
bufferInfo: MediaCodec.BufferInfo,
|
|
58
|
+
isAudio: Boolean
|
|
59
|
+
) {
|
|
60
|
+
|
|
61
|
+
if (writeNewMdat) {
|
|
62
|
+
mdat.apply {
|
|
63
|
+
setContentSize(0)
|
|
64
|
+
getBox(fc)
|
|
65
|
+
setDataOffset(dataOffset)
|
|
66
|
+
}
|
|
67
|
+
dataOffset += 16
|
|
68
|
+
wroteSinceLastMdat += 16
|
|
69
|
+
writeNewMdat = false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
mdat.setContentSize(mdat.getContentSize() + bufferInfo.size)
|
|
73
|
+
wroteSinceLastMdat += bufferInfo.size.toLong()
|
|
74
|
+
|
|
75
|
+
var flush = false
|
|
76
|
+
if (wroteSinceLastMdat >= 32 * 1024) {
|
|
77
|
+
flushCurrentMdat()
|
|
78
|
+
writeNewMdat = true
|
|
79
|
+
flush = true
|
|
80
|
+
wroteSinceLastMdat = 0
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
currentMp4Movie.addSample(trackIndex, dataOffset, bufferInfo)
|
|
84
|
+
|
|
85
|
+
if (!isAudio) {
|
|
86
|
+
byteBuf.position(bufferInfo.offset + 4)
|
|
87
|
+
byteBuf.limit(bufferInfo.offset + bufferInfo.size)
|
|
88
|
+
|
|
89
|
+
sizeBuffer.position(0)
|
|
90
|
+
sizeBuffer.putInt(bufferInfo.size - 4)
|
|
91
|
+
sizeBuffer.position(0)
|
|
92
|
+
fc.write(sizeBuffer)
|
|
93
|
+
} else {
|
|
94
|
+
byteBuf.position(bufferInfo.offset + 0)
|
|
95
|
+
byteBuf.limit(bufferInfo.offset + bufferInfo.size)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fc.write(byteBuf)
|
|
99
|
+
dataOffset += bufferInfo.size.toLong()
|
|
100
|
+
|
|
101
|
+
if (flush) {
|
|
102
|
+
fos.flush()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fun addTrack(mediaFormat: MediaFormat, isAudio: Boolean): Int =
|
|
107
|
+
currentMp4Movie.addTrack(mediaFormat, isAudio)
|
|
108
|
+
|
|
109
|
+
@Throws(Exception::class)
|
|
110
|
+
fun finishMovie() {
|
|
111
|
+
if (mdat.getContentSize() != 0L) {
|
|
112
|
+
flushCurrentMdat()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (track in currentMp4Movie.getTracks()) {
|
|
116
|
+
val samples: List<Sample> = track.getSamples()
|
|
117
|
+
val sizes = LongArray(samples.size)
|
|
118
|
+
for (i in sizes.indices) {
|
|
119
|
+
sizes[i] = samples[i].size
|
|
120
|
+
}
|
|
121
|
+
track2SampleSizes[track] = sizes
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
val moov: Box = createMovieBox(currentMp4Movie)
|
|
125
|
+
moov.getBox(fc)
|
|
126
|
+
|
|
127
|
+
fos.flush()
|
|
128
|
+
fc.close()
|
|
129
|
+
fos.close()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private fun createFileTypeBox(): FileTypeBox {
|
|
133
|
+
// completed list can be found at https://www.ftyps.com/
|
|
134
|
+
val minorBrands = listOf(
|
|
135
|
+
"isom", "iso2", "mp41"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return FileTypeBox("isom", 0, minorBrands)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private fun gcd(a: Long, b: Long): Long {
|
|
142
|
+
return if (b == 0L) a
|
|
143
|
+
else gcd(b, a % b)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private fun getTimescale(mp4Movie: Mp4Movie): Long {
|
|
147
|
+
var timescale: Long = 0
|
|
148
|
+
if (mp4Movie.getTracks().isNotEmpty()) {
|
|
149
|
+
timescale = mp4Movie.getTracks().iterator().next().getTimeScale().toLong()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (track in mp4Movie.getTracks()) {
|
|
153
|
+
timescale = gcd(
|
|
154
|
+
track.getTimeScale().toLong(),
|
|
155
|
+
timescale
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return timescale
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private fun createMovieBox(movie: Mp4Movie): MovieBox {
|
|
163
|
+
val movieBox = MovieBox()
|
|
164
|
+
val mvhd = MovieHeaderBox()
|
|
165
|
+
|
|
166
|
+
mvhd.apply {
|
|
167
|
+
creationTime = Date()
|
|
168
|
+
modificationTime = Date()
|
|
169
|
+
matrix = Matrix.ROTATE_0
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
val movieTimeScale = getTimescale(movie)
|
|
173
|
+
var duration: Long = 0
|
|
174
|
+
|
|
175
|
+
for (track in movie.getTracks()) {
|
|
176
|
+
val tracksDuration = track.getDuration() * movieTimeScale / track.getTimeScale()
|
|
177
|
+
if (tracksDuration > duration) {
|
|
178
|
+
duration = tracksDuration
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
mvhd.duration = duration
|
|
183
|
+
mvhd.timescale = movieTimeScale
|
|
184
|
+
mvhd.nextTrackId = (movie.getTracks().size + 1).toLong()
|
|
185
|
+
movieBox.addBox(mvhd)
|
|
186
|
+
|
|
187
|
+
for (track in movie.getTracks()) {
|
|
188
|
+
movieBox.addBox(createTrackBox(track, movie))
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return movieBox
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private fun createTrackBox(track: Track, movie: Mp4Movie): TrackBox {
|
|
195
|
+
val trackBox = TrackBox()
|
|
196
|
+
val tkhd = TrackHeaderBox()
|
|
197
|
+
tkhd.apply {
|
|
198
|
+
isEnabled = true
|
|
199
|
+
isInPreview = true
|
|
200
|
+
isInMovie = true
|
|
201
|
+
matrix = if (track.isAudio()) {
|
|
202
|
+
Matrix.ROTATE_0
|
|
203
|
+
} else {
|
|
204
|
+
movie.getMatrix()
|
|
205
|
+
}
|
|
206
|
+
alternateGroup = 0
|
|
207
|
+
creationTime = track.getCreationTime()
|
|
208
|
+
duration = track.getDuration() * getTimescale(movie) / track.getTimeScale()
|
|
209
|
+
height = track.getHeight().toDouble()
|
|
210
|
+
width = track.getWidth().toDouble()
|
|
211
|
+
layer = 0
|
|
212
|
+
modificationTime = Date()
|
|
213
|
+
trackId = track.getTrackId() + 1
|
|
214
|
+
volume = track.getVolume()
|
|
215
|
+
}
|
|
216
|
+
trackBox.addBox(tkhd)
|
|
217
|
+
|
|
218
|
+
val mdia = MediaBox()
|
|
219
|
+
trackBox.addBox(mdia)
|
|
220
|
+
|
|
221
|
+
val mdhd = MediaHeaderBox()
|
|
222
|
+
mdhd.apply {
|
|
223
|
+
creationTime = track.getCreationTime()
|
|
224
|
+
duration = track.getDuration()
|
|
225
|
+
timescale = track.getTimeScale().toLong()
|
|
226
|
+
language = "eng"
|
|
227
|
+
}
|
|
228
|
+
mdia.addBox(mdhd)
|
|
229
|
+
|
|
230
|
+
val hdlr = HandlerBox()
|
|
231
|
+
hdlr.apply {
|
|
232
|
+
name = if (track.isAudio()) "SoundHandle" else "VideoHandle"
|
|
233
|
+
handlerType = track.getHandler()
|
|
234
|
+
}
|
|
235
|
+
mdia.addBox(hdlr)
|
|
236
|
+
|
|
237
|
+
val minf = MediaInformationBox()
|
|
238
|
+
when {
|
|
239
|
+
track.getHandler() == "vide" -> {
|
|
240
|
+
minf.addBox(VideoMediaHeaderBox())
|
|
241
|
+
}
|
|
242
|
+
track.getHandler() == "soun" -> {
|
|
243
|
+
minf.addBox(SoundMediaHeaderBox())
|
|
244
|
+
}
|
|
245
|
+
track.getHandler() == "text" -> {
|
|
246
|
+
minf.addBox(NullMediaHeaderBox())
|
|
247
|
+
}
|
|
248
|
+
track.getHandler() == "subt" -> {
|
|
249
|
+
minf.addBox(SubtitleMediaHeaderBox())
|
|
250
|
+
}
|
|
251
|
+
track.getHandler() == "hint" -> {
|
|
252
|
+
minf.addBox(HintMediaHeaderBox())
|
|
253
|
+
}
|
|
254
|
+
track.getHandler() == "sbtl" -> {
|
|
255
|
+
minf.addBox(NullMediaHeaderBox())
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
val dinf = DataInformationBox()
|
|
260
|
+
val dref = DataReferenceBox()
|
|
261
|
+
dinf.addBox(dref)
|
|
262
|
+
|
|
263
|
+
val url = DataEntryUrlBox()
|
|
264
|
+
url.flags = 1
|
|
265
|
+
|
|
266
|
+
dref.addBox(url)
|
|
267
|
+
minf.addBox(dinf)
|
|
268
|
+
|
|
269
|
+
val stbl: Box = createStbl(track)
|
|
270
|
+
minf.addBox(stbl)
|
|
271
|
+
mdia.addBox(minf)
|
|
272
|
+
|
|
273
|
+
return trackBox
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private fun createStbl(track: Track): Box {
|
|
277
|
+
val stbl = SampleTableBox()
|
|
278
|
+
createStsd(track, stbl)
|
|
279
|
+
createStts(track, stbl)
|
|
280
|
+
createStss(track, stbl)
|
|
281
|
+
createStsc(track, stbl)
|
|
282
|
+
createStsz(track, stbl)
|
|
283
|
+
createStco(track, stbl)
|
|
284
|
+
return stbl
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private fun createStsd(track: Track, stbl: SampleTableBox) {
|
|
288
|
+
stbl.addBox(track.getSampleDescriptionBox())
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private fun createStts(track: Track, stbl: SampleTableBox) {
|
|
292
|
+
var lastEntry: TimeToSampleBox.Entry? = null
|
|
293
|
+
val entries: MutableList<TimeToSampleBox.Entry> = ArrayList()
|
|
294
|
+
for (delta in track.getSampleDurations()) {
|
|
295
|
+
if (lastEntry != null && lastEntry.delta == delta) {
|
|
296
|
+
lastEntry.count = lastEntry.count + 1
|
|
297
|
+
} else {
|
|
298
|
+
lastEntry = TimeToSampleBox.Entry(1, delta)
|
|
299
|
+
entries.add(lastEntry)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
val stts = TimeToSampleBox()
|
|
303
|
+
stts.entries = entries
|
|
304
|
+
stbl.addBox(stts)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private fun createStss(track: Track, stbl: SampleTableBox) {
|
|
308
|
+
val syncSamples = track.getSyncSamples()
|
|
309
|
+
if (syncSamples != null && syncSamples.isNotEmpty()) {
|
|
310
|
+
val stss = SyncSampleBox()
|
|
311
|
+
stss.sampleNumber = syncSamples
|
|
312
|
+
stbl.addBox(stss)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private fun createStsc(track: Track, stbl: SampleTableBox) {
|
|
317
|
+
val stsc = SampleToChunkBox()
|
|
318
|
+
stsc.entries = LinkedList()
|
|
319
|
+
|
|
320
|
+
var lastOffset: Long
|
|
321
|
+
var lastChunkNumber = 1
|
|
322
|
+
var lastSampleCount = 0
|
|
323
|
+
var previousWrittenChunkCount = -1
|
|
324
|
+
|
|
325
|
+
val samplesCount = track.getSamples().size
|
|
326
|
+
for (a in 0 until samplesCount) {
|
|
327
|
+
val sample = track.getSamples()[a]
|
|
328
|
+
val offset = sample.offset
|
|
329
|
+
val size = sample.size
|
|
330
|
+
|
|
331
|
+
lastOffset = offset + size
|
|
332
|
+
lastSampleCount++
|
|
333
|
+
|
|
334
|
+
var write = false
|
|
335
|
+
if (a != samplesCount - 1) {
|
|
336
|
+
val nextSample = track.getSamples()[a + 1]
|
|
337
|
+
if (lastOffset != nextSample.offset) {
|
|
338
|
+
write = true
|
|
339
|
+
}
|
|
340
|
+
} else {
|
|
341
|
+
write = true
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (write) {
|
|
345
|
+
if (previousWrittenChunkCount != lastSampleCount) {
|
|
346
|
+
stsc.entries.add(
|
|
347
|
+
SampleToChunkBox.Entry(
|
|
348
|
+
lastChunkNumber.toLong(),
|
|
349
|
+
lastSampleCount.toLong(), 1
|
|
350
|
+
)
|
|
351
|
+
)
|
|
352
|
+
previousWrittenChunkCount = lastSampleCount
|
|
353
|
+
}
|
|
354
|
+
lastSampleCount = 0
|
|
355
|
+
lastChunkNumber++
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
stbl.addBox(stsc)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private fun createStsz(track: Track, stbl: SampleTableBox) {
|
|
362
|
+
val stsz = SampleSizeBox()
|
|
363
|
+
stsz.sampleSizes = track2SampleSizes[track]
|
|
364
|
+
stbl.addBox(stsz)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private fun createStco(track: Track, stbl: SampleTableBox) {
|
|
368
|
+
val chunksOffsets = ArrayList<Long>()
|
|
369
|
+
var lastOffset: Long = -1
|
|
370
|
+
for (sample in track.getSamples()) {
|
|
371
|
+
val offset = sample.offset
|
|
372
|
+
if (lastOffset != -1L && lastOffset != offset) {
|
|
373
|
+
lastOffset = -1
|
|
374
|
+
}
|
|
375
|
+
if (lastOffset == -1L) {
|
|
376
|
+
chunksOffsets.add(offset)
|
|
377
|
+
}
|
|
378
|
+
lastOffset = offset + sample.size
|
|
379
|
+
}
|
|
380
|
+
val chunkOffsetsLong = LongArray(chunksOffsets.size)
|
|
381
|
+
for (a in chunksOffsets.indices) {
|
|
382
|
+
chunkOffsetsLong[a] = chunksOffsets[a]
|
|
383
|
+
}
|
|
384
|
+
val stco = StaticChunkOffsetBox()
|
|
385
|
+
stco.chunkOffsets = chunkOffsetsLong
|
|
386
|
+
stbl.addBox(stco)
|
|
387
|
+
}
|
|
388
|
+
}
|
package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/videoHelpers/Mdat.kt
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
package com.reactnativecompressor.Video.VideoCompressor.video
|
|
2
|
+
|
|
3
|
+
import com.coremedia.iso.BoxParser
|
|
4
|
+
import com.coremedia.iso.IsoFile
|
|
5
|
+
import com.coremedia.iso.IsoTypeWriter
|
|
6
|
+
import com.coremedia.iso.boxes.Box
|
|
7
|
+
import com.coremedia.iso.boxes.Container
|
|
8
|
+
import com.googlecode.mp4parser.DataSource
|
|
9
|
+
import java.nio.ByteBuffer
|
|
10
|
+
import java.nio.channels.WritableByteChannel
|
|
11
|
+
|
|
12
|
+
class Mdat : Box {
|
|
13
|
+
|
|
14
|
+
private lateinit var parent: Container
|
|
15
|
+
private var contentSize = (1024 * 1024 * 1024).toLong()
|
|
16
|
+
private var dataOffset: Long = 0
|
|
17
|
+
|
|
18
|
+
override fun getParent(): Container = parent
|
|
19
|
+
|
|
20
|
+
override fun setParent(parent: Container) {
|
|
21
|
+
this.parent = parent
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override fun getSize(): Long = 16 + contentSize
|
|
25
|
+
|
|
26
|
+
override fun getOffset(): Long = dataOffset
|
|
27
|
+
|
|
28
|
+
fun setDataOffset(offset: Long) {
|
|
29
|
+
dataOffset = offset
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fun setContentSize(contentSize: Long) {
|
|
33
|
+
this.contentSize = contentSize
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fun getContentSize(): Long {
|
|
37
|
+
return contentSize
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override fun getType(): String = "mdat"
|
|
41
|
+
|
|
42
|
+
private fun isSmallBox(contentSize: Long): Boolean = contentSize + 8 < 4294967296L
|
|
43
|
+
|
|
44
|
+
override fun getBox(writableByteChannel: WritableByteChannel) {
|
|
45
|
+
val bb = ByteBuffer.allocate(16)
|
|
46
|
+
val size = size
|
|
47
|
+
if (isSmallBox(size)) {
|
|
48
|
+
if (size >= 0 && size <= 1L shl 32) {
|
|
49
|
+
IsoTypeWriter.writeUInt32(bb, size)
|
|
50
|
+
} else {
|
|
51
|
+
// TODO(ABED): Investigate when this could happen.
|
|
52
|
+
IsoTypeWriter.writeUInt32(bb, 1)
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
IsoTypeWriter.writeUInt32(bb, 1)
|
|
56
|
+
}
|
|
57
|
+
bb.put(IsoFile.fourCCtoBytes("mdat"))
|
|
58
|
+
if (isSmallBox(size)) {
|
|
59
|
+
bb.put(ByteArray(8))
|
|
60
|
+
} else {
|
|
61
|
+
IsoTypeWriter.writeUInt64(bb, if (size >= 0) size else 1)
|
|
62
|
+
}
|
|
63
|
+
bb.rewind()
|
|
64
|
+
writableByteChannel.write(bb)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override fun parse(
|
|
68
|
+
dataSource: DataSource?,
|
|
69
|
+
header: ByteBuffer?,
|
|
70
|
+
contentSize: Long,
|
|
71
|
+
boxParser: BoxParser?
|
|
72
|
+
) {
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
package com.reactnativecompressor.Video.VideoCompressor.video
|
|
2
|
+
|
|
3
|
+
import android.media.MediaCodec
|
|
4
|
+
import android.media.MediaFormat
|
|
5
|
+
import com.googlecode.mp4parser.util.Matrix
|
|
6
|
+
import java.io.File
|
|
7
|
+
import java.util.*
|
|
8
|
+
|
|
9
|
+
class Mp4Movie {
|
|
10
|
+
|
|
11
|
+
private var matrix = Matrix.ROTATE_0
|
|
12
|
+
private val tracks = ArrayList<Track>()
|
|
13
|
+
private var cacheFile: File? = null
|
|
14
|
+
|
|
15
|
+
fun getMatrix(): Matrix? = matrix
|
|
16
|
+
|
|
17
|
+
fun setCacheFile(file: File) {
|
|
18
|
+
cacheFile = file
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fun setRotation(angle: Int) {
|
|
22
|
+
when (angle) {
|
|
23
|
+
0 -> {
|
|
24
|
+
matrix = Matrix.ROTATE_0
|
|
25
|
+
}
|
|
26
|
+
90 -> {
|
|
27
|
+
matrix = Matrix.ROTATE_90
|
|
28
|
+
}
|
|
29
|
+
180 -> {
|
|
30
|
+
matrix = Matrix.ROTATE_180
|
|
31
|
+
}
|
|
32
|
+
270 -> {
|
|
33
|
+
matrix = Matrix.ROTATE_270
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fun getTracks(): ArrayList<Track> = tracks
|
|
39
|
+
|
|
40
|
+
fun getCacheFile(): File? = cacheFile
|
|
41
|
+
|
|
42
|
+
fun addSample(trackIndex: Int, offset: Long, bufferInfo: MediaCodec.BufferInfo) {
|
|
43
|
+
if (trackIndex < 0 || trackIndex >= tracks.size) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
val track = tracks[trackIndex]
|
|
47
|
+
track.addSample(offset, bufferInfo)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fun addTrack(mediaFormat: MediaFormat, isAudio: Boolean): Int {
|
|
51
|
+
tracks.add(Track(tracks.size, mediaFormat, isAudio))
|
|
52
|
+
return tracks.size - 1
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
package com.reactnativecompressor.Video.VideoCompressor.video
|
|
2
|
+
|
|
3
|
+
import android.graphics.SurfaceTexture
|
|
4
|
+
import android.graphics.SurfaceTexture.OnFrameAvailableListener
|
|
5
|
+
import android.view.Surface
|
|
6
|
+
|
|
7
|
+
class OutputSurface : OnFrameAvailableListener {
|
|
8
|
+
|
|
9
|
+
private var mSurfaceTexture: SurfaceTexture? = null
|
|
10
|
+
private var mSurface: Surface? = null
|
|
11
|
+
private val mFrameSyncObject = Object()
|
|
12
|
+
private var mFrameAvailable = false
|
|
13
|
+
private var mTextureRender: TextureRenderer? = null
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates an OutputSurface using the current EGL context. This Surface will be
|
|
17
|
+
* passed to MediaCodec.configure().
|
|
18
|
+
*/
|
|
19
|
+
init {
|
|
20
|
+
setup()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates instances of TextureRender and SurfaceTexture, and a Surface associated
|
|
25
|
+
* with the SurfaceTexture.
|
|
26
|
+
*/
|
|
27
|
+
private fun setup() {
|
|
28
|
+
mTextureRender = TextureRenderer()
|
|
29
|
+
mTextureRender?.let {
|
|
30
|
+
it.surfaceCreated()
|
|
31
|
+
|
|
32
|
+
// Even if we don't access the SurfaceTexture after the constructor returns, we
|
|
33
|
+
// still need to keep a reference to it. The Surface doesn't retain a reference
|
|
34
|
+
// at the Java level, so if we don't either then the object can get GCed, which
|
|
35
|
+
// causes the native finalizer to run.
|
|
36
|
+
mSurfaceTexture = SurfaceTexture(it.getTextureId())
|
|
37
|
+
mSurfaceTexture?.let { surfaceTexture ->
|
|
38
|
+
surfaceTexture.setOnFrameAvailableListener(this)
|
|
39
|
+
mSurface = Surface(mSurfaceTexture)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Discards all resources held by this class, notably the EGL context.
|
|
46
|
+
*/
|
|
47
|
+
fun release() {
|
|
48
|
+
mSurface?.release()
|
|
49
|
+
|
|
50
|
+
mTextureRender = null
|
|
51
|
+
mSurface = null
|
|
52
|
+
mSurfaceTexture = null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns the Surface that we draw onto.
|
|
57
|
+
*/
|
|
58
|
+
fun getSurface(): Surface? = mSurface
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Latches the next buffer into the texture. Must be called from the thread that created
|
|
62
|
+
* the OutputSurface object, after the onFrameAvailable callback has signaled that new
|
|
63
|
+
* data is available.
|
|
64
|
+
*/
|
|
65
|
+
fun awaitNewImage() {
|
|
66
|
+
val timeOutMS = 100
|
|
67
|
+
synchronized(mFrameSyncObject) {
|
|
68
|
+
while (!mFrameAvailable) {
|
|
69
|
+
try {
|
|
70
|
+
// Wait for onFrameAvailable() to signal us. Use a timeout to avoid
|
|
71
|
+
// stalling the test if it doesn't arrive.
|
|
72
|
+
mFrameSyncObject.wait(timeOutMS.toLong())
|
|
73
|
+
if (!mFrameAvailable) {
|
|
74
|
+
throw RuntimeException("Surface frame wait timed out")
|
|
75
|
+
}
|
|
76
|
+
} catch (ie: InterruptedException) {
|
|
77
|
+
throw RuntimeException(ie)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
mFrameAvailable = false
|
|
81
|
+
}
|
|
82
|
+
mTextureRender?.checkGlError("before updateTexImage")
|
|
83
|
+
mSurfaceTexture?.updateTexImage()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Draws the data from SurfaceTexture onto the current EGL surface.
|
|
88
|
+
*/
|
|
89
|
+
fun drawImage() {
|
|
90
|
+
mTextureRender?.drawFrame(mSurfaceTexture!!)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
override fun onFrameAvailable(p0: SurfaceTexture?) {
|
|
94
|
+
synchronized(mFrameSyncObject) {
|
|
95
|
+
if (mFrameAvailable) {
|
|
96
|
+
throw RuntimeException("mFrameAvailable already set, frame could be dropped")
|
|
97
|
+
}
|
|
98
|
+
mFrameAvailable = true
|
|
99
|
+
mFrameSyncObject.notifyAll()
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|