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.
- package/README.md +22 -1
- package/android/build.gradle +6 -2
- package/android/src/main/java/com/reactnativecompressor/Audio/AudioCompressor.kt +2 -2
- package/android/src/main/java/com/reactnativecompressor/CompressorModule.kt +12 -0
- 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 +158 -0
- 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/android/src/oldarch/CompressorSpec.kt +2 -0
- package/ios/Compressor.mm +9 -0
- package/ios/CompressorManager.swift +11 -0
- package/ios/Utils/CreateVideoThumbnail.swift +111 -0
- package/lib/commonjs/Spec/NativeCompressor.js.map +1 -1
- package/lib/commonjs/index.js +14 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/utils/index.js +12 -1
- package/lib/commonjs/utils/index.js.map +1 -1
- package/lib/module/Spec/NativeCompressor.js.map +1 -1
- package/lib/module/index.js +4 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/index.js +7 -0
- package/lib/module/utils/index.js.map +1 -1
- package/lib/typescript/Spec/NativeCompressor.d.ts +8 -0
- package/lib/typescript/Spec/NativeCompressor.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +15 -3
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/utils/index.d.ts +16 -1
- package/lib/typescript/utils/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Spec/NativeCompressor.ts +11 -0
- package/src/index.tsx +6 -0
- package/src/utils/index.tsx +28 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
package com.reactnativecompressor.Video.VideoCompressor.utils
|
|
2
|
+
|
|
3
|
+
import android.media.MediaCodecInfo
|
|
4
|
+
import android.media.MediaCodecList
|
|
5
|
+
import android.media.MediaExtractor
|
|
6
|
+
import android.media.MediaFormat
|
|
7
|
+
import android.media.MediaMetadataRetriever
|
|
8
|
+
import android.util.Log
|
|
9
|
+
import com.reactnativecompressor.Video.VideoCompressor.video.Mp4Movie
|
|
10
|
+
import java.io.File
|
|
11
|
+
|
|
12
|
+
object CompressorUtils {
|
|
13
|
+
|
|
14
|
+
// Minimum height and width for videos
|
|
15
|
+
private const val MIN_HEIGHT = 640.0
|
|
16
|
+
private const val MIN_WIDTH = 368.0
|
|
17
|
+
|
|
18
|
+
// Interval between I-frames (keyframes) in seconds
|
|
19
|
+
private const val I_FRAME_INTERVAL = 1
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the width of the video from metadata or use a default value if not available.
|
|
23
|
+
*/
|
|
24
|
+
fun prepareVideoWidth(
|
|
25
|
+
mediaMetadataRetriever: MediaMetadataRetriever,
|
|
26
|
+
): Double {
|
|
27
|
+
val widthData =
|
|
28
|
+
mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
|
|
29
|
+
return if (widthData.isNullOrEmpty()) {
|
|
30
|
+
MIN_WIDTH
|
|
31
|
+
} else {
|
|
32
|
+
widthData.toDouble()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the height of the video from metadata or use a default value if not available.
|
|
38
|
+
*/
|
|
39
|
+
fun prepareVideoHeight(
|
|
40
|
+
mediaMetadataRetriever: MediaMetadataRetriever,
|
|
41
|
+
): Double {
|
|
42
|
+
val heightData =
|
|
43
|
+
mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
|
|
44
|
+
return if (heightData.isNullOrEmpty()) {
|
|
45
|
+
MIN_HEIGHT
|
|
46
|
+
} else {
|
|
47
|
+
heightData.toDouble()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Set up an Mp4Movie with rotation and cache file.
|
|
53
|
+
*/
|
|
54
|
+
fun setUpMP4Movie(
|
|
55
|
+
rotation: Int,
|
|
56
|
+
cacheFile: File,
|
|
57
|
+
): Mp4Movie {
|
|
58
|
+
val movie = Mp4Movie()
|
|
59
|
+
movie.apply {
|
|
60
|
+
setCacheFile(cacheFile)
|
|
61
|
+
setRotation(rotation)
|
|
62
|
+
}
|
|
63
|
+
return movie
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Set output parameters like bitrate and frame rate for video encoding.
|
|
68
|
+
*/
|
|
69
|
+
fun setOutputFileParameters(
|
|
70
|
+
inputFormat: MediaFormat,
|
|
71
|
+
outputFormat: MediaFormat,
|
|
72
|
+
newBitrate: Int,
|
|
73
|
+
) {
|
|
74
|
+
val newFrameRate = getFrameRate(inputFormat)
|
|
75
|
+
val iFrameInterval = getIFrameIntervalRate(inputFormat)
|
|
76
|
+
outputFormat.apply {
|
|
77
|
+
setInteger(
|
|
78
|
+
MediaFormat.KEY_COLOR_FORMAT,
|
|
79
|
+
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
setInteger(MediaFormat.KEY_FRAME_RATE, newFrameRate)
|
|
83
|
+
setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval)
|
|
84
|
+
// Bitrate in bits per second
|
|
85
|
+
setInteger(MediaFormat.KEY_BIT_RATE, newBitrate)
|
|
86
|
+
setInteger(
|
|
87
|
+
MediaFormat.KEY_BITRATE_MODE,
|
|
88
|
+
MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
getColorStandard(inputFormat)?.let {
|
|
92
|
+
setInteger(MediaFormat.KEY_COLOR_STANDARD, it)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getColorTransfer(inputFormat)?.let {
|
|
96
|
+
setInteger(MediaFormat.KEY_COLOR_TRANSFER, it)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
getColorRange(inputFormat)?.let {
|
|
100
|
+
setInteger(MediaFormat.KEY_COLOR_RANGE, it)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
Log.i(
|
|
104
|
+
"Output file parameters",
|
|
105
|
+
"videoFormat: $this"
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Get the frame rate from the input format or use a default value
|
|
111
|
+
private fun getFrameRate(format: MediaFormat): Int {
|
|
112
|
+
return if (format.containsKey(MediaFormat.KEY_FRAME_RATE)) format.getInteger(MediaFormat.KEY_FRAME_RATE)
|
|
113
|
+
else 30
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Get the I-frame (keyframe) interval from the input format or use a default value
|
|
117
|
+
private fun getIFrameIntervalRate(format: MediaFormat): Int {
|
|
118
|
+
return if (format.containsKey(MediaFormat.KEY_I_FRAME_INTERVAL)) format.getInteger(
|
|
119
|
+
MediaFormat.KEY_I_FRAME_INTERVAL
|
|
120
|
+
)
|
|
121
|
+
else I_FRAME_INTERVAL
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Get the color standard from the input format or null if not available
|
|
125
|
+
private fun getColorStandard(format: MediaFormat): Int? {
|
|
126
|
+
return if (format.containsKey(MediaFormat.KEY_COLOR_STANDARD)) format.getInteger(
|
|
127
|
+
MediaFormat.KEY_COLOR_STANDARD
|
|
128
|
+
)
|
|
129
|
+
else null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Get the color transfer from the input format or null if not available
|
|
133
|
+
private fun getColorTransfer(format: MediaFormat): Int? {
|
|
134
|
+
return if (format.containsKey(MediaFormat.KEY_COLOR_TRANSFER)) format.getInteger(
|
|
135
|
+
MediaFormat.KEY_COLOR_TRANSFER
|
|
136
|
+
)
|
|
137
|
+
else null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Get the color range from the input format or null if not available
|
|
141
|
+
private fun getColorRange(format: MediaFormat): Int? {
|
|
142
|
+
return if (format.containsKey(MediaFormat.KEY_COLOR_RANGE)) format.getInteger(
|
|
143
|
+
MediaFormat.KEY_COLOR_RANGE
|
|
144
|
+
)
|
|
145
|
+
else null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Find the track index for video or audio in the media extractor.
|
|
150
|
+
*
|
|
151
|
+
* @param extractor MediaExtractor used to extract data from the media source.
|
|
152
|
+
* @param isVideo Determines whether we are looking for a video or audio track.
|
|
153
|
+
* @return Index of the requested track, or -5 if not found.
|
|
154
|
+
*/
|
|
155
|
+
fun findTrack(
|
|
156
|
+
extractor: MediaExtractor,
|
|
157
|
+
isVideo: Boolean,
|
|
158
|
+
): Int {
|
|
159
|
+
val numTracks = extractor.trackCount
|
|
160
|
+
for (i in 0 until numTracks) {
|
|
161
|
+
val format = extractor.getTrackFormat(i)
|
|
162
|
+
val mime = format.getString(MediaFormat.KEY_MIME)
|
|
163
|
+
if (isVideo) {
|
|
164
|
+
if (mime?.startsWith("video/")!!) return i
|
|
165
|
+
} else {
|
|
166
|
+
if (mime?.startsWith("audio/")!!) return i
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return -5
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Log an exception with a meaningful message.
|
|
174
|
+
*/
|
|
175
|
+
fun printException(exception: Exception) {
|
|
176
|
+
var message = "An error has occurred!"
|
|
177
|
+
exception.localizedMessage?.let {
|
|
178
|
+
message = it
|
|
179
|
+
}
|
|
180
|
+
Log.e("Compressor", message, exception)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Check if the device has QTI (Qualcomm Technologies, Inc.) codecs.
|
|
185
|
+
*/
|
|
186
|
+
fun hasQTI(): Boolean {
|
|
187
|
+
val list = MediaCodecList(MediaCodecList.REGULAR_CODECS).codecInfos
|
|
188
|
+
for (codec in list) {
|
|
189
|
+
Log.i("CODECS: ", codec.name)
|
|
190
|
+
if (codec.name.contains("qti.avc")) {
|
|
191
|
+
return true
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
}
|
package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/utils/NumbersUtils.kt
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
package com.reactnativecompressor.Video.VideoCompressor.utils
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converts a 32-bit unsigned integer to a long.
|
|
5
|
+
* @param int32 The 32-bit unsigned integer to convert.
|
|
6
|
+
* @return The equivalent long value.
|
|
7
|
+
*/
|
|
8
|
+
fun uInt32ToLong(int32: Int): Long {
|
|
9
|
+
return int32.toLong()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Converts a long to a 32-bit unsigned integer, throwing an exception if the value is too large.
|
|
14
|
+
* @param uInt32 The long value to convert.
|
|
15
|
+
* @return The equivalent 32-bit unsigned integer.
|
|
16
|
+
* @throws Exception if the provided long value is greater than Int.MAX_VALUE or negative.
|
|
17
|
+
*/
|
|
18
|
+
fun uInt32ToInt(uInt32: Long): Int {
|
|
19
|
+
if (uInt32 > Int.MAX_VALUE || uInt32 < 0) {
|
|
20
|
+
throw Exception("uInt32 value is too large or negative")
|
|
21
|
+
}
|
|
22
|
+
return uInt32.toInt()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Converts a 64-bit unsigned integer to a long, throwing an exception if the value is negative.
|
|
27
|
+
* @param uInt64 The 64-bit unsigned integer to convert.
|
|
28
|
+
* @return The equivalent long value.
|
|
29
|
+
* @throws Exception if the provided long value is negative.
|
|
30
|
+
*/
|
|
31
|
+
fun uInt64ToLong(uInt64: Long): Long {
|
|
32
|
+
if (uInt64 < 0) throw Exception("uInt64 value is negative")
|
|
33
|
+
return uInt64
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Converts a 32-bit unsigned integer to an integer, throwing an exception if the value is negative.
|
|
38
|
+
* @param uInt32 The 32-bit unsigned integer to convert.
|
|
39
|
+
* @return The equivalent integer value.
|
|
40
|
+
* @throws Exception if the provided integer value is negative.
|
|
41
|
+
*/
|
|
42
|
+
fun uInt32ToInt(uInt32: Int): Int {
|
|
43
|
+
if (uInt32 < 0) {
|
|
44
|
+
throw Exception("uInt32 value is negative")
|
|
45
|
+
}
|
|
46
|
+
return uInt32
|
|
47
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
package com.reactnativecompressor.Video.VideoCompressor.utils
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.reactnativecompressor.Video.VideoCompressor.data.*
|
|
5
|
+
import java.io.*
|
|
6
|
+
import java.nio.ByteBuffer
|
|
7
|
+
import java.nio.ByteOrder
|
|
8
|
+
import java.nio.channels.FileChannel
|
|
9
|
+
|
|
10
|
+
object StreamableVideo {
|
|
11
|
+
|
|
12
|
+
private const val tag = "StreamableVideo"
|
|
13
|
+
private const val ATOM_PREAMBLE_SIZE = 8
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Starts the process of making a video file "fast start" and saves it to the output file.
|
|
17
|
+
*
|
|
18
|
+
* @param inputVideoFile The input video file.
|
|
19
|
+
* @param outputVideoFile The output video file.
|
|
20
|
+
* @return `true` if the operation is successful, `false` if the input file is already fast start.
|
|
21
|
+
* @throws IOException if an I/O error occurs.
|
|
22
|
+
*/
|
|
23
|
+
@Throws(IOException::class)
|
|
24
|
+
fun start(`in`: File?, out: File): Boolean {
|
|
25
|
+
var ret = false
|
|
26
|
+
var inStream: FileInputStream? = null
|
|
27
|
+
var outStream: FileOutputStream? = null
|
|
28
|
+
return try {
|
|
29
|
+
inStream = FileInputStream(`in`)
|
|
30
|
+
val infile = inStream.channel
|
|
31
|
+
outStream = FileOutputStream(out)
|
|
32
|
+
val outfile = outStream.channel
|
|
33
|
+
convert(infile, outfile).also { ret = it }
|
|
34
|
+
} finally {
|
|
35
|
+
safeClose(inStream)
|
|
36
|
+
safeClose(outStream)
|
|
37
|
+
if (!ret) {
|
|
38
|
+
out.delete()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Throws(IOException::class)
|
|
44
|
+
private fun convert(inputFileChannel: FileChannel, outputFileChannel: FileChannel): Boolean {
|
|
45
|
+
val atomBytes = ByteBuffer.allocate(ATOM_PREAMBLE_SIZE).order(ByteOrder.BIG_ENDIAN)
|
|
46
|
+
var atomType = 0
|
|
47
|
+
var atomSize: Long = 0
|
|
48
|
+
val lastOffset: Long
|
|
49
|
+
val moovAtom: ByteBuffer
|
|
50
|
+
var ftypAtom: ByteBuffer? = null
|
|
51
|
+
var startOffset: Long = 0
|
|
52
|
+
|
|
53
|
+
// Traverse through the atoms in the file to ensure that 'moov' is at the end.
|
|
54
|
+
while (readAndFill(inputFileChannel, atomBytes)) {
|
|
55
|
+
atomSize = uInt32ToLong(atomBytes.int)
|
|
56
|
+
atomType = atomBytes.int
|
|
57
|
+
|
|
58
|
+
// Keep the 'ftyp' atom.
|
|
59
|
+
if (atomType == FTYP_ATOM) {
|
|
60
|
+
val ftypAtomSize = uInt32ToInt(atomSize)
|
|
61
|
+
ftypAtom = ByteBuffer.allocate(ftypAtomSize).order(ByteOrder.BIG_ENDIAN)
|
|
62
|
+
atomBytes.rewind()
|
|
63
|
+
ftypAtom.put(atomBytes)
|
|
64
|
+
if (inputFileChannel.read(ftypAtom) < ftypAtomSize - ATOM_PREAMBLE_SIZE) break
|
|
65
|
+
ftypAtom.flip()
|
|
66
|
+
startOffset = inputFileChannel.position() // After 'ftyp' atom.
|
|
67
|
+
} else {
|
|
68
|
+
if (atomSize == 1L) {
|
|
69
|
+
/* 64-bit special case */
|
|
70
|
+
atomBytes.clear()
|
|
71
|
+
if (!readAndFill(inputFileChannel, atomBytes)) break
|
|
72
|
+
atomSize = uInt64ToLong(atomBytes.long)
|
|
73
|
+
inputFileChannel.position(inputFileChannel.position() + atomSize - ATOM_PREAMBLE_SIZE * 2) // Seek.
|
|
74
|
+
} else {
|
|
75
|
+
inputFileChannel.position(inputFileChannel.position() + atomSize - ATOM_PREAMBLE_SIZE) // Seek.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (atomType != FREE_ATOM
|
|
79
|
+
&& atomType != JUNK_ATOM
|
|
80
|
+
&& atomType != MDAT_ATOM
|
|
81
|
+
&& atomType != MOOV_ATOM
|
|
82
|
+
&& atomType != PNOT_ATOM
|
|
83
|
+
&& atomType != SKIP_ATOM
|
|
84
|
+
&& atomType != WIDE_ATOM
|
|
85
|
+
&& atomType != PICT_ATOM
|
|
86
|
+
&& atomType != UUID_ATOM
|
|
87
|
+
&& atomType != FTYP_ATOM
|
|
88
|
+
) {
|
|
89
|
+
Log.wtf(tag, "Encountered a non-QT top-level atom (Is this a QuickTime file?)")
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* The atom header is 8 (or 16 bytes). If the atom size (which
|
|
94
|
+
* includes these 8 or 16 bytes) is less than that, we won't be
|
|
95
|
+
* able to continue scanning sensibly after this atom, so break. */
|
|
96
|
+
if (atomSize < 8) break
|
|
97
|
+
}
|
|
98
|
+
if (atomType != MOOV_ATOM) {
|
|
99
|
+
Log.wtf(tag, "The last atom in the file was not a 'moov' atom")
|
|
100
|
+
return false
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 'atomSize' is 'uint64', but for 'moov', 'uint32' should be stored.
|
|
104
|
+
val moovAtomSize: Int = uInt32ToInt(atomSize)
|
|
105
|
+
lastOffset =
|
|
106
|
+
inputFileChannel.size() - moovAtomSize
|
|
107
|
+
moovAtom = ByteBuffer.allocate(moovAtomSize).order(ByteOrder.BIG_ENDIAN)
|
|
108
|
+
if (!readAndFill(inputFileChannel, moovAtom, lastOffset)) {
|
|
109
|
+
throw Exception("Failed to read 'moov' atom")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (moovAtom.getInt(12) == CMOV_ATOM) {
|
|
113
|
+
throw Exception("This utility does not support compressed 'moov' atoms yet")
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Crawl through the 'moov' chunk in search of 'stco' or 'co64' atoms.
|
|
117
|
+
while (moovAtom.remaining() >= 8) {
|
|
118
|
+
val atomHead = moovAtom.position()
|
|
119
|
+
atomType = moovAtom.getInt(atomHead + 4)
|
|
120
|
+
if (!(atomType == STCO_ATOM || atomType == CO64_ATOM)) {
|
|
121
|
+
moovAtom.position(moovAtom.position() + 1)
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
atomSize = uInt32ToLong(moovAtom.getInt(atomHead)) // 'uint32'
|
|
125
|
+
if (atomSize > moovAtom.remaining()) {
|
|
126
|
+
throw Exception("Bad atom size")
|
|
127
|
+
}
|
|
128
|
+
// Skip size (4 bytes), type (4 bytes), version (1 byte), and flags (3 bytes).
|
|
129
|
+
moovAtom.position(atomHead + 12)
|
|
130
|
+
if (moovAtom.remaining() < 4) {
|
|
131
|
+
throw Exception("Malformed atom")
|
|
132
|
+
}
|
|
133
|
+
// 'uint32_t', but assuming 'moovAtomSize' is in 'int32' range, so this will be in 'int32' range.
|
|
134
|
+
val offsetCount = uInt32ToInt(moovAtom.int)
|
|
135
|
+
if (atomType == STCO_ATOM) {
|
|
136
|
+
Log.i(tag, "Patching 'stco' atom...")
|
|
137
|
+
if (moovAtom.remaining() < offsetCount * 4) {
|
|
138
|
+
throw Exception("Bad atom size/element count")
|
|
139
|
+
}
|
|
140
|
+
for (i in 0 until offsetCount) {
|
|
141
|
+
val currentOffset = moovAtom.getInt(moovAtom.position())
|
|
142
|
+
val newOffset =
|
|
143
|
+
currentOffset + moovAtomSize // Calculate 'uint32' in 'int', bitwise addition.
|
|
144
|
+
|
|
145
|
+
if (currentOffset < 0 && newOffset >= 0) {
|
|
146
|
+
throw Exception(
|
|
147
|
+
"This is a bug in the original 'qt-faststart.c': " +
|
|
148
|
+
"'stco' atom should be extended to 'co64' atom as the new offset value overflows 'uint32', " +
|
|
149
|
+
"but it is not implemented."
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
moovAtom.putInt(newOffset)
|
|
153
|
+
}
|
|
154
|
+
} else if (atomType == CO64_ATOM) {
|
|
155
|
+
Log.wtf(tag, "Patching 'co64' atom...")
|
|
156
|
+
if (moovAtom.remaining() < offsetCount * 8) {
|
|
157
|
+
throw Exception("Bad atom size/element count")
|
|
158
|
+
}
|
|
159
|
+
for (i in 0 until offsetCount) {
|
|
160
|
+
val currentOffset = moovAtom.getLong(moovAtom.position())
|
|
161
|
+
moovAtom.putLong(currentOffset + moovAtomSize) // Calculate 'uint64' in 'long', bitwise addition.
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
inputFileChannel.position(startOffset) // Seek after 'ftyp' atom.
|
|
166
|
+
if (ftypAtom != null) {
|
|
167
|
+
// Dump the same 'ftyp' atom.
|
|
168
|
+
Log.i(tag, "Writing 'ftyp' atom...")
|
|
169
|
+
ftypAtom.rewind()
|
|
170
|
+
outputFileChannel.write(ftypAtom)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Dump the new 'moov' atom.
|
|
174
|
+
Log.i(tag, "Writing 'moov' atom...")
|
|
175
|
+
moovAtom.rewind()
|
|
176
|
+
outputFileChannel.write(moovAtom)
|
|
177
|
+
|
|
178
|
+
// Copy the remainder of the input file, from offset 0 -> (lastOffset - startOffset) - 1.
|
|
179
|
+
Log.i(tag, "Copying the rest of the file...")
|
|
180
|
+
inputFileChannel.transferTo(startOffset, lastOffset - startOffset, outputFileChannel)
|
|
181
|
+
return true
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private fun safeClose(closeable: Closeable?) {
|
|
185
|
+
if (closeable != null) {
|
|
186
|
+
try {
|
|
187
|
+
closeable.close()
|
|
188
|
+
} catch (e: IOException) {
|
|
189
|
+
Log.wtf(tag, "Failed to close file: ")
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@Throws(IOException::class)
|
|
195
|
+
private fun readAndFill(inputFileChannel: FileChannel, buffer: ByteBuffer): Boolean {
|
|
196
|
+
buffer.clear()
|
|
197
|
+
val size = inputFileChannel.read(buffer)
|
|
198
|
+
buffer.flip()
|
|
199
|
+
return size == buffer.capacity()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@Throws(IOException::class)
|
|
203
|
+
private fun readAndFill(inputFileChannel: FileChannel, buffer: ByteBuffer, position: Long): Boolean {
|
|
204
|
+
buffer.clear()
|
|
205
|
+
val size = inputFileChannel.read(buffer, position)
|
|
206
|
+
buffer.flip()
|
|
207
|
+
return size == buffer.capacity()
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
package com.reactnativecompressor.Video.VideoCompressor.video
|
|
2
|
+
|
|
3
|
+
import android.opengl.EGL14
|
|
4
|
+
import android.opengl.EGLConfig
|
|
5
|
+
import android.opengl.EGLContext
|
|
6
|
+
import android.opengl.EGLDisplay
|
|
7
|
+
import android.opengl.EGLExt
|
|
8
|
+
import android.opengl.EGLSurface
|
|
9
|
+
import android.view.Surface
|
|
10
|
+
|
|
11
|
+
class InputSurface(surface: Surface?) {
|
|
12
|
+
|
|
13
|
+
private val eglRecordableAndroid = 0x3142
|
|
14
|
+
private val eglOpenGlES2Bit = 4
|
|
15
|
+
private var mEGLDisplay: EGLDisplay? = null
|
|
16
|
+
private var mEGLContext: EGLContext? = null
|
|
17
|
+
private var mEGLSurface: EGLSurface? = null
|
|
18
|
+
private var mSurface: Surface? = null
|
|
19
|
+
|
|
20
|
+
init {
|
|
21
|
+
if (surface == null) {
|
|
22
|
+
throw NullPointerException()
|
|
23
|
+
}
|
|
24
|
+
mSurface = surface
|
|
25
|
+
eglSetup()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private fun eglSetup() {
|
|
29
|
+
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
|
|
30
|
+
if (mEGLDisplay === EGL14.EGL_NO_DISPLAY) {
|
|
31
|
+
throw RuntimeException("unable to get EGL14 display")
|
|
32
|
+
}
|
|
33
|
+
val version = IntArray(2)
|
|
34
|
+
if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
|
|
35
|
+
mEGLDisplay = null
|
|
36
|
+
throw RuntimeException("unable to initialize EGL14")
|
|
37
|
+
}
|
|
38
|
+
val attribList = intArrayOf(
|
|
39
|
+
EGL14.EGL_RED_SIZE,
|
|
40
|
+
8,
|
|
41
|
+
EGL14.EGL_GREEN_SIZE,
|
|
42
|
+
8,
|
|
43
|
+
EGL14.EGL_BLUE_SIZE,
|
|
44
|
+
8,
|
|
45
|
+
EGL14.EGL_RENDERABLE_TYPE,
|
|
46
|
+
eglOpenGlES2Bit,
|
|
47
|
+
eglRecordableAndroid,
|
|
48
|
+
1,
|
|
49
|
+
EGL14.EGL_NONE,
|
|
50
|
+
)
|
|
51
|
+
val configs = arrayOfNulls<EGLConfig>(1)
|
|
52
|
+
val numConfigs = IntArray(1)
|
|
53
|
+
if (!EGL14.eglChooseConfig(
|
|
54
|
+
mEGLDisplay, attribList, 0, configs, 0, configs.size,
|
|
55
|
+
numConfigs, 0
|
|
56
|
+
)
|
|
57
|
+
) {
|
|
58
|
+
throw RuntimeException("unable to find RGB888+recordable ES2 EGL config")
|
|
59
|
+
}
|
|
60
|
+
val attrs = intArrayOf(
|
|
61
|
+
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
|
|
62
|
+
EGL14.EGL_NONE
|
|
63
|
+
)
|
|
64
|
+
mEGLContext =
|
|
65
|
+
EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, attrs, 0)
|
|
66
|
+
checkEglError()
|
|
67
|
+
if (mEGLContext == null) {
|
|
68
|
+
throw RuntimeException("null context")
|
|
69
|
+
}
|
|
70
|
+
val surfaceAttrs = intArrayOf(
|
|
71
|
+
EGL14.EGL_NONE
|
|
72
|
+
)
|
|
73
|
+
mEGLSurface = EGL14.eglCreateWindowSurface(
|
|
74
|
+
mEGLDisplay, configs[0], mSurface,
|
|
75
|
+
surfaceAttrs, 0
|
|
76
|
+
)
|
|
77
|
+
checkEglError()
|
|
78
|
+
if (mEGLSurface == null) {
|
|
79
|
+
throw RuntimeException("surface was null")
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fun release() {
|
|
84
|
+
if (EGL14.eglGetCurrentContext() == mEGLContext) {
|
|
85
|
+
EGL14.eglMakeCurrent(
|
|
86
|
+
mEGLDisplay,
|
|
87
|
+
EGL14.EGL_NO_SURFACE,
|
|
88
|
+
EGL14.EGL_NO_SURFACE,
|
|
89
|
+
EGL14.EGL_NO_CONTEXT
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface)
|
|
93
|
+
EGL14.eglDestroyContext(mEGLDisplay, mEGLContext)
|
|
94
|
+
|
|
95
|
+
mSurface?.release()
|
|
96
|
+
|
|
97
|
+
mEGLDisplay = null
|
|
98
|
+
mEGLContext = null
|
|
99
|
+
mEGLSurface = null
|
|
100
|
+
|
|
101
|
+
mSurface = null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fun makeCurrent() {
|
|
105
|
+
if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
|
|
106
|
+
throw RuntimeException("eglMakeCurrent failed")
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fun swapBuffers(): Boolean =
|
|
111
|
+
EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
fun setPresentationTime(nsecs: Long) {
|
|
115
|
+
EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private fun checkEglError() {
|
|
119
|
+
var failed = false
|
|
120
|
+
while (EGL14.eglGetError() != EGL14.EGL_SUCCESS) {
|
|
121
|
+
failed = true
|
|
122
|
+
}
|
|
123
|
+
if (failed) {
|
|
124
|
+
throw RuntimeException("EGL error encountered (see log)")
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
}
|