react-native-skia-webgpu-video 0.0.0-stage → 0.1.0-beta.1

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 (237) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +303 -2
  3. package/android/BUILD_VALIDATION.md +62 -0
  4. package/android/CMakeLists.txt +99 -0
  5. package/android/build-validation.json +262 -0
  6. package/android/build.gradle +185 -0
  7. package/android/cpp/EncoderTargets.cpp +226 -0
  8. package/android/cpp/EncoderTargets.h +70 -0
  9. package/android/cpp/JNIHelpers.cpp +15 -0
  10. package/android/cpp/JNIHelpers.h +20 -0
  11. package/android/cpp/NativeEventDispatcher.cpp +59 -0
  12. package/android/cpp/NativeEventDispatcher.h +31 -0
  13. package/android/cpp/NativeHardwareBuffer.cpp +397 -0
  14. package/android/cpp/NativeHardwareBuffer.h +34 -0
  15. package/android/cpp/NativeRgbaBuffer.cpp +45 -0
  16. package/android/cpp/NativeRgbaBuffer.h +9 -0
  17. package/android/cpp/ReturnedVideoFrames.h +37 -0
  18. package/android/cpp/RgbaFrameLease.h +53 -0
  19. package/android/cpp/VideoCapabilities.cpp +87 -0
  20. package/android/cpp/VideoCapabilities.h +44 -0
  21. package/android/cpp/VideoComposition.cpp +170 -0
  22. package/android/cpp/VideoComposition.h +36 -0
  23. package/android/cpp/VideoCompositionFramesExtractor.cpp +82 -0
  24. package/android/cpp/VideoCompositionFramesExtractor.h +48 -0
  25. package/android/cpp/VideoCompositionFramesExtractorHostObject.cpp +230 -0
  26. package/android/cpp/VideoCompositionFramesExtractorHostObject.h +42 -0
  27. package/android/cpp/VideoCompositionFramesExtractorSyncHostObject.cpp +133 -0
  28. package/android/cpp/VideoCompositionFramesExtractorSyncHostObject.h +46 -0
  29. package/android/cpp/VideoEncoderHostObject.cpp +318 -0
  30. package/android/cpp/VideoEncoderHostObject.h +76 -0
  31. package/android/cpp/VideoFrame.cpp +293 -0
  32. package/android/cpp/VideoFrame.h +27 -0
  33. package/android/cpp/VideoPlayer.cpp +96 -0
  34. package/android/cpp/VideoPlayer.h +52 -0
  35. package/android/cpp/VideoPlayerHostObject.cpp +225 -0
  36. package/android/cpp/VideoPlayerHostObject.h +43 -0
  37. package/android/cpp/cpp-adapter.cpp +316 -0
  38. package/android/gradle.properties +5 -0
  39. package/android/instrumentation-inventory.json +178 -0
  40. package/android/proguard-rules.pro +4 -0
  41. package/android/src/main/AndroidManifest.xml +2 -0
  42. package/android/src/main/java/com/azzapp/rnskv/AudioCompositionExporter.java +537 -0
  43. package/android/src/main/java/com/azzapp/rnskv/AudioCompositionPlayer.java +321 -0
  44. package/android/src/main/java/com/azzapp/rnskv/BufferLifetime.java +55 -0
  45. package/android/src/main/java/com/azzapp/rnskv/DecodeRequests.java +100 -0
  46. package/android/src/main/java/com/azzapp/rnskv/DecoderWindow.java +55 -0
  47. package/android/src/main/java/com/azzapp/rnskv/DisplayVsyncPeriod.java +72 -0
  48. package/android/src/main/java/com/azzapp/rnskv/EGLResourcesHolder.java +132 -0
  49. package/android/src/main/java/com/azzapp/rnskv/EGLUtils.java +225 -0
  50. package/android/src/main/java/com/azzapp/rnskv/FrameSize.java +50 -0
  51. package/android/src/main/java/com/azzapp/rnskv/GLFrameExtractor.java +510 -0
  52. package/android/src/main/java/com/azzapp/rnskv/JNIHelpers.java +12 -0
  53. package/android/src/main/java/com/azzapp/rnskv/NativeEventDispatcher.java +25 -0
  54. package/android/src/main/java/com/azzapp/rnskv/NativeHardwareBuffer.java +62 -0
  55. package/android/src/main/java/com/azzapp/rnskv/NativeRgbaBuffer.java +43 -0
  56. package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoModule.java +45 -0
  57. package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoPackage.java +43 -0
  58. package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoSpec.java +9 -0
  59. package/android/src/main/java/com/azzapp/rnskv/RgbaBufferAllocator.java +114 -0
  60. package/android/src/main/java/com/azzapp/rnskv/RgbaLayout.java +33 -0
  61. package/android/src/main/java/com/azzapp/rnskv/SurfaceOutputPolicy.java +13 -0
  62. package/android/src/main/java/com/azzapp/rnskv/TextureRenderer.java +150 -0
  63. package/android/src/main/java/com/azzapp/rnskv/TimeHelpers.java +11 -0
  64. package/android/src/main/java/com/azzapp/rnskv/VideoCapabilities.java +354 -0
  65. package/android/src/main/java/com/azzapp/rnskv/VideoComposition.java +147 -0
  66. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionDecoder.java +779 -0
  67. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractor.java +463 -0
  68. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractorSync.java +241 -0
  69. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionItemDecoder.java +492 -0
  70. package/android/src/main/java/com/azzapp/rnskv/VideoEncoder.java +639 -0
  71. package/android/src/main/java/com/azzapp/rnskv/VideoFrame.java +74 -0
  72. package/android/src/main/java/com/azzapp/rnskv/VideoPlayer.java +361 -0
  73. package/cpp/RNSVCheckedSizes.h +54 -0
  74. package/cpp/RNSVEventEmitter.cpp +199 -0
  75. package/cpp/RNSVEventEmitter.h +31 -0
  76. package/cpp/RNSVHostObject.cpp +79 -0
  77. package/cpp/RNSVHostObject.h +67 -0
  78. package/cpp/RNSVMemoryBudget.cpp +68 -0
  79. package/cpp/RNSVMemoryBudget.h +100 -0
  80. package/cpp/RNSVRuntimeLifecycleMonitor.cpp +88 -0
  81. package/cpp/RNSVRuntimeLifecycleMonitor.h +33 -0
  82. package/docs/ANDROID_DEVICE_306.md +95 -0
  83. package/docs/ANDROID_EXPORT_CACHE_306.md +48 -0
  84. package/docs/ANDROID_MEMORY_RETENTION_306.md +67 -0
  85. package/docs/BENCHMARKS.md +368 -0
  86. package/docs/IOS_DEVICE_TESTS.md +115 -0
  87. package/docs/IOS_SIMULATOR_TESTS.md +74 -0
  88. package/docs/MIGRATION.md +82 -0
  89. package/docs/NATIVE_MEMORY_INVESTIGATION_306.md +88 -0
  90. package/docs/PERFORMANCE_AB_IPHONE_303.md +196 -0
  91. package/docs/PERFORMANCE_AB_SIMULATOR.md +135 -0
  92. package/docs/PERFORMANCE_AB_SOAK_SIMULATOR.md +84 -0
  93. package/docs/PERFORMANCE_IPHONE_306_OPTIMIZATION.md +76 -0
  94. package/docs/PERFORMANCE_SIMULATOR.md +89 -0
  95. package/docs/PERFORMANCE_SIMULATOR_INITIAL.md +96 -0
  96. package/docs/PERFORMANCE_SOAK_IPHONE_303.md +41 -0
  97. package/docs/UPSTREAM_NOTES.md +188 -0
  98. package/docs/assets/banner.svg +26 -0
  99. package/examples/webgpu-overlay.ts +147 -0
  100. package/ios/AVAssetTrackUtils.h +24 -0
  101. package/ios/AudioCompositionUtils.h +27 -0
  102. package/ios/AudioCompositionUtils.mm +113 -0
  103. package/ios/DecoderWindow.h +52 -0
  104. package/ios/NativeResourceStats.h +32 -0
  105. package/ios/RNSVColorSpace.h +50 -0
  106. package/ios/RNSVJSIUtils.h +46 -0
  107. package/ios/RNSVScopedPixelBuffer.h +17 -0
  108. package/ios/RNSVVideoPlayer.h +46 -0
  109. package/ios/RNSVVideoPlayer.mm +410 -0
  110. package/ios/ReactNativeSkiaVideo.h +8 -0
  111. package/ios/ReactNativeSkiaVideo.mm +310 -0
  112. package/ios/SeekPolicy.h +27 -0
  113. package/ios/VideoComposition.h +148 -0
  114. package/ios/VideoCompositionFramesExtractorHostObject.h +105 -0
  115. package/ios/VideoCompositionFramesExtractorHostObject.mm +686 -0
  116. package/ios/VideoCompositionFramesExtractorSyncHostObject.h +37 -0
  117. package/ios/VideoCompositionFramesExtractorSyncHostObject.mm +168 -0
  118. package/ios/VideoCompositionItemDecoder.h +60 -0
  119. package/ios/VideoCompositionItemDecoder.mm +480 -0
  120. package/ios/VideoEncoderHostObject.h +83 -0
  121. package/ios/VideoEncoderHostObject.mm +706 -0
  122. package/ios/VideoFrame.h +66 -0
  123. package/ios/VideoFrame.mm +108 -0
  124. package/ios/VideoPlayerHostObject.h +52 -0
  125. package/ios/VideoPlayerHostObject.mm +332 -0
  126. package/lib/module/NativeReactNativeSkiaVideo.js +5 -0
  127. package/lib/module/NativeReactNativeSkiaVideo.js.map +1 -0
  128. package/lib/module/RNSkiaVideoModule.js +20 -0
  129. package/lib/module/RNSkiaVideoModule.js.map +1 -0
  130. package/lib/module/canvas.js +18 -0
  131. package/lib/module/canvas.js.map +1 -0
  132. package/lib/module/exportCache.js +22 -0
  133. package/lib/module/exportCache.js.map +1 -0
  134. package/lib/module/exportFrames.js +338 -0
  135. package/lib/module/exportFrames.js.map +1 -0
  136. package/lib/module/exportQueue.js +52 -0
  137. package/lib/module/exportQueue.js.map +1 -0
  138. package/lib/module/exportTarget.js +137 -0
  139. package/lib/module/exportTarget.js.map +1 -0
  140. package/lib/module/exportVideoComposition.js +492 -0
  141. package/lib/module/exportVideoComposition.js.map +1 -0
  142. package/lib/module/frameInterop.js +532 -0
  143. package/lib/module/frameInterop.js.map +1 -0
  144. package/lib/module/gpu.js +222 -0
  145. package/lib/module/gpu.js.map +1 -0
  146. package/lib/module/gpuDevice.js +54 -0
  147. package/lib/module/gpuDevice.js.map +1 -0
  148. package/lib/module/index.js +36 -0
  149. package/lib/module/index.js.map +1 -0
  150. package/lib/module/memory.js +38 -0
  151. package/lib/module/memory.js.map +1 -0
  152. package/lib/module/package.json +1 -0
  153. package/lib/module/previewLifecycle.js +52 -0
  154. package/lib/module/previewLifecycle.js.map +1 -0
  155. package/lib/module/runtimeYield.js +10 -0
  156. package/lib/module/runtimeYield.js.map +1 -0
  157. package/lib/module/types.js +4 -0
  158. package/lib/module/types.js.map +1 -0
  159. package/lib/module/utils/useEventListener.js +18 -0
  160. package/lib/module/utils/useEventListener.js.map +1 -0
  161. package/lib/module/videoCompositionPlayer.js +368 -0
  162. package/lib/module/videoCompositionPlayer.js.map +1 -0
  163. package/lib/module/videoFrameImage.js +185 -0
  164. package/lib/module/videoFrameImage.js.map +1 -0
  165. package/lib/module/videoPlayer.js +172 -0
  166. package/lib/module/videoPlayer.js.map +1 -0
  167. package/lib/module/yuvBlit.js +299 -0
  168. package/lib/module/yuvBlit.js.map +1 -0
  169. package/lib/typescript/package.json +1 -0
  170. package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts +7 -0
  171. package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts.map +1 -0
  172. package/lib/typescript/src/RNSkiaVideoModule.d.ts +4 -0
  173. package/lib/typescript/src/RNSkiaVideoModule.d.ts.map +1 -0
  174. package/lib/typescript/src/canvas.d.ts +4 -0
  175. package/lib/typescript/src/canvas.d.ts.map +1 -0
  176. package/lib/typescript/src/exportCache.d.ts +6 -0
  177. package/lib/typescript/src/exportCache.d.ts.map +1 -0
  178. package/lib/typescript/src/exportFrames.d.ts +57 -0
  179. package/lib/typescript/src/exportFrames.d.ts.map +1 -0
  180. package/lib/typescript/src/exportQueue.d.ts +5 -0
  181. package/lib/typescript/src/exportQueue.d.ts.map +1 -0
  182. package/lib/typescript/src/exportTarget.d.ts +28 -0
  183. package/lib/typescript/src/exportTarget.d.ts.map +1 -0
  184. package/lib/typescript/src/exportVideoComposition.d.ts +68 -0
  185. package/lib/typescript/src/exportVideoComposition.d.ts.map +1 -0
  186. package/lib/typescript/src/frameInterop.d.ts +17 -0
  187. package/lib/typescript/src/frameInterop.d.ts.map +1 -0
  188. package/lib/typescript/src/gpu.d.ts +39 -0
  189. package/lib/typescript/src/gpu.d.ts.map +1 -0
  190. package/lib/typescript/src/gpuDevice.d.ts +11 -0
  191. package/lib/typescript/src/gpuDevice.d.ts.map +1 -0
  192. package/lib/typescript/src/index.d.ts +21 -0
  193. package/lib/typescript/src/index.d.ts.map +1 -0
  194. package/lib/typescript/src/memory.d.ts +25 -0
  195. package/lib/typescript/src/memory.d.ts.map +1 -0
  196. package/lib/typescript/src/previewLifecycle.d.ts +22 -0
  197. package/lib/typescript/src/previewLifecycle.d.ts.map +1 -0
  198. package/lib/typescript/src/runtimeYield.d.ts +4 -0
  199. package/lib/typescript/src/runtimeYield.d.ts.map +1 -0
  200. package/lib/typescript/src/types.d.ts +631 -0
  201. package/lib/typescript/src/types.d.ts.map +1 -0
  202. package/lib/typescript/src/utils/useEventListener.d.ts +5 -0
  203. package/lib/typescript/src/utils/useEventListener.d.ts.map +1 -0
  204. package/lib/typescript/src/videoCompositionPlayer.d.ts +105 -0
  205. package/lib/typescript/src/videoCompositionPlayer.d.ts.map +1 -0
  206. package/lib/typescript/src/videoFrameImage.d.ts +60 -0
  207. package/lib/typescript/src/videoFrameImage.d.ts.map +1 -0
  208. package/lib/typescript/src/videoPlayer.d.ts +99 -0
  209. package/lib/typescript/src/videoPlayer.d.ts.map +1 -0
  210. package/lib/typescript/src/yuvBlit.d.ts +50 -0
  211. package/lib/typescript/src/yuvBlit.d.ts.map +1 -0
  212. package/package.json +155 -4
  213. package/patches/react-native-skia+3.7.0.patch +502 -0
  214. package/react-native-skia-webgpu-video.podspec +35 -0
  215. package/scripts/apply-skia-canvas-patch.mjs +48 -0
  216. package/scripts/skia-canvas-patch.json +291 -0
  217. package/src/NativeReactNativeSkiaVideo.ts +8 -0
  218. package/src/RNSkiaVideoModule.ts +27 -0
  219. package/src/canvas.ts +22 -0
  220. package/src/exportCache.ts +27 -0
  221. package/src/exportFrames.ts +421 -0
  222. package/src/exportQueue.ts +65 -0
  223. package/src/exportTarget.ts +179 -0
  224. package/src/exportVideoComposition.ts +718 -0
  225. package/src/frameInterop.ts +684 -0
  226. package/src/gpu.ts +290 -0
  227. package/src/gpuDevice.ts +80 -0
  228. package/src/index.tsx +45 -0
  229. package/src/memory.ts +48 -0
  230. package/src/previewLifecycle.ts +60 -0
  231. package/src/runtimeYield.ts +6 -0
  232. package/src/types.ts +661 -0
  233. package/src/utils/useEventListener.ts +22 -0
  234. package/src/videoCompositionPlayer.ts +568 -0
  235. package/src/videoFrameImage.ts +205 -0
  236. package/src/videoPlayer.ts +318 -0
  237. package/src/yuvBlit.ts +331 -0
@@ -0,0 +1,492 @@
1
+ package com.azzapp.rnskv;
2
+
3
+ import android.media.MediaCodec;
4
+ import android.media.MediaCodecInfo.CodecProfileLevel;
5
+ import android.media.MediaCodecList;
6
+ import android.media.MediaExtractor;
7
+ import android.media.MediaFormat;
8
+ import android.os.Build;
9
+ import android.os.Handler;
10
+ import android.os.Looper;
11
+ import android.view.Surface;
12
+
13
+ import androidx.annotation.NonNull;
14
+
15
+ import java.io.IOException;
16
+ import java.nio.ByteBuffer;
17
+ import java.util.ArrayList;
18
+ import java.util.List;
19
+ import java.util.Stack;
20
+
21
+ /**
22
+ * A class that decodes a video item from a video composition asynchronously.
23
+ */
24
+ public class VideoCompositionItemDecoder extends MediaCodec.Callback {
25
+
26
+ private final VideoComposition.Item item;
27
+
28
+ private MediaExtractor extractor;
29
+
30
+ private MediaCodec codec;
31
+
32
+ private MediaFormat format;
33
+
34
+ private boolean inputEOS = false;
35
+
36
+ private boolean hasRenderedFrame = false;
37
+
38
+ private boolean itemEndReached = false;
39
+
40
+ private int videoWidth;
41
+
42
+ private int videoHeight;
43
+
44
+ private int rotation;
45
+
46
+ private boolean prepared = false;
47
+
48
+ private boolean configured = false;
49
+
50
+ private boolean started = false;
51
+
52
+ private volatile boolean released = false;
53
+
54
+ private Surface surface;
55
+
56
+ private long initialPositionUs = 0;
57
+
58
+ private Handler callbackHandler;
59
+
60
+ /**
61
+ * Set by a seek until the codec's thread has run every callback queued
62
+ * before the flush: those name buffers the flush took back, and handling
63
+ * them after it hands out or drops the buffers of the new position.
64
+ */
65
+ private boolean droppingStaleCallbacks = false;
66
+
67
+ private long flushGeneration = 0;
68
+
69
+ private final Stack<Frame> freeFrames = new Stack<>();
70
+
71
+ private final List<Frame> pendingFrames = new ArrayList<>();
72
+
73
+ private OnErrorListener onErrorListener;
74
+
75
+ private OnEndReachedListener onEndReachedListener;
76
+
77
+ private OnFrameAvailableListener onFrameAvailableListener;
78
+
79
+ /**
80
+ * Create a new VideoCompositionItemDecoder.
81
+ *
82
+ * @param item the video composition item to decode
83
+ */
84
+ public VideoCompositionItemDecoder(VideoComposition.Item item) {
85
+ this.item = item;
86
+ }
87
+
88
+ /**
89
+ * Prepare the decoder.
90
+ *
91
+ * @throws IOException if the decoder cannot be prepared
92
+ */
93
+ synchronized public void prepare() throws IOException {
94
+ if (prepared) {
95
+ return;
96
+ }
97
+ extractor = new MediaExtractor();
98
+ extractor.setDataSource(item.getPath());
99
+ int trackIndex = selectTrack(extractor);
100
+ if (trackIndex == -1) {
101
+ throw new RuntimeException("No video track");
102
+ }
103
+ format = extractor.getTrackFormat(trackIndex);
104
+ String mime = format.getString(MediaFormat.KEY_MIME);
105
+ if (mime == null) {
106
+ throw new IOException("Could not determine file mime type");
107
+ }
108
+ mime = playableMime(format, mime);
109
+ codec = MediaCodec.createDecoderByType(mime);
110
+ extractor.selectTrack(trackIndex);
111
+ // Capped at the item's end, as on iOS.
112
+ long offsetUs = Math.min(
113
+ Math.max(initialPositionUs - TimeHelpers.secToUs(item.getCompositionStartTime()), 0),
114
+ TimeHelpers.secToUs(item.getDuration()));
115
+ if (item.getStartTime() != 0 || offsetUs != 0) {
116
+ extractor.seekTo(
117
+ TimeHelpers.secToUs(item.getStartTime()) + offsetUs,
118
+ MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
119
+ }
120
+ videoWidth = format.getInteger(MediaFormat.KEY_WIDTH);
121
+ videoHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
122
+ rotation = format.containsKey(MediaFormat.KEY_ROTATION) ? format.getInteger(MediaFormat.KEY_ROTATION) : 0;
123
+ prepared = true;
124
+ configure();
125
+ }
126
+
127
+ /**
128
+ * Start the decoder
129
+ */
130
+ synchronized public void start() {
131
+ if (!prepared || !configured || started) {
132
+ return;
133
+ }
134
+ codec.start();
135
+ started = true;
136
+ }
137
+
138
+ /**
139
+ * Set the surface to render the video to.
140
+ *
141
+ * @param surface the surface to render the video to
142
+ */
143
+ public void setSurface(Surface surface) {
144
+ this.surface = surface;
145
+ configure();
146
+ }
147
+
148
+
149
+ /**
150
+ * Where decoding starts, as a composition time, for a decoder opened after
151
+ * its item has begun. Must be called before {@link #prepare}.
152
+ */
153
+ public void setInitialPosition(long compositionTimeUs) {
154
+ initialPositionUs = compositionTimeUs;
155
+ }
156
+
157
+ /**
158
+ * The thread the codec calls back on, instead of the one that configures it.
159
+ * Must be called before {@link #setSurface}.
160
+ */
161
+ public void setCallbackHandler(Handler callbackHandler) {
162
+ this.callbackHandler = callbackHandler;
163
+ }
164
+
165
+ public void setOnErrorListener(OnErrorListener onErrorListener) {
166
+ this.onErrorListener = onErrorListener;
167
+ }
168
+
169
+ public void setOnFrameAvailableListener(OnFrameAvailableListener onFrameAvailableListener) {
170
+ this.onFrameAvailableListener = onFrameAvailableListener;
171
+ }
172
+
173
+ public void setOnEndReachedListener(OnEndReachedListener onEndReachedListener) {
174
+ this.onEndReachedListener = onEndReachedListener;
175
+ }
176
+
177
+ /**
178
+ * @return the video composition item
179
+ */
180
+ public VideoComposition.Item getItem() {
181
+ return item;
182
+ }
183
+
184
+ /**
185
+ * @return the width of the decoded video in pixels after rotation
186
+ */
187
+ /**
188
+ * Whether the track decodes to 8-bit SDR pictures: only those go through
189
+ * ImageReader's YUV 4:2:0 buffers; HDR and 10-bit keep the GL path, whose
190
+ * SurfaceTexture handles their formats.
191
+ */
192
+ public synchronized boolean isEightBitSdr() {
193
+ if (format == null) return false;
194
+ if (format.containsKey(MediaFormat.KEY_COLOR_TRANSFER)) {
195
+ int transfer = format.getInteger(MediaFormat.KEY_COLOR_TRANSFER);
196
+ if (transfer == MediaFormat.COLOR_TRANSFER_HLG || transfer == MediaFormat.COLOR_TRANSFER_ST2084)
197
+ return false;
198
+ }
199
+ if (format.containsKey(MediaFormat.KEY_COLOR_STANDARD) &&
200
+ format.getInteger(MediaFormat.KEY_COLOR_STANDARD) == MediaFormat.COLOR_STANDARD_BT2020)
201
+ return false;
202
+ String mime = format.getString(MediaFormat.KEY_MIME);
203
+ if (!MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime) && !MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime))
204
+ return false;
205
+ if (format.containsKey(MediaFormat.KEY_PROFILE)) {
206
+ int profile = format.getInteger(MediaFormat.KEY_PROFILE);
207
+ if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime))
208
+ return profile == android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain;
209
+ return profile != android.media.MediaCodecInfo.CodecProfileLevel.AVCProfileHigh10 &&
210
+ profile != android.media.MediaCodecInfo.CodecProfileLevel.AVCProfileHigh422 &&
211
+ profile != android.media.MediaCodecInfo.CodecProfileLevel.AVCProfileHigh444;
212
+ }
213
+ return true;
214
+ }
215
+
216
+ public int getVideoWidth() {
217
+ return videoWidth;
218
+ }
219
+
220
+ /**
221
+ * @return the height of the decoded video in pixels after rotation
222
+ */
223
+ public int getVideoHeight() {
224
+ return videoHeight;
225
+ }
226
+
227
+ /**
228
+ * @return the rotation of the decoded video in degrees
229
+ */
230
+ public int getRotation() {
231
+ return rotation;
232
+ }
233
+
234
+ @Override
235
+ synchronized public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
236
+ if (!prepared || !configured || released || droppingStaleCallbacks) {
237
+ return;
238
+ }
239
+
240
+ if (inputEOS || itemEndReached) {
241
+ try {
242
+ this.codec.queueInputBuffer(index, 0, 0, 0,
243
+ MediaCodec.BUFFER_FLAG_END_OF_STREAM);
244
+ } catch (Throwable e) {}
245
+ return;
246
+ }
247
+
248
+ ByteBuffer inputBuffer;
249
+ try {
250
+ inputBuffer = this.codec.getInputBuffer(index);
251
+ } catch (Throwable e) {
252
+ return;
253
+ }
254
+ if (inputBuffer == null) {
255
+ return;
256
+ }
257
+
258
+ int sampleSize = extractor.readSampleData(inputBuffer, 0);
259
+ if (sampleSize <= 0) {
260
+ this.codec.queueInputBuffer(index, 0, 0, 0,
261
+ MediaCodec.BUFFER_FLAG_END_OF_STREAM);
262
+ return;
263
+ }
264
+ try {
265
+ this.codec.queueInputBuffer(
266
+ index,
267
+ 0,
268
+ sampleSize,
269
+ extractor.getSampleTime(),
270
+ extractor.getSampleFlags()
271
+ );
272
+ } catch (Throwable e) {
273
+ return;
274
+ }
275
+ extractor.advance();
276
+ inputEOS = extractor.getSampleTime() == -1;
277
+ }
278
+
279
+ @Override
280
+ synchronized public void onOutputBufferAvailable(
281
+ @NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
282
+ if (released || droppingStaleCallbacks) {
283
+ return;
284
+ }
285
+ boolean outputEOS = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
286
+ boolean sampleOutOfBounds =
287
+ info.presentationTimeUs > TimeHelpers.secToUs(item.getStartTime() + item.getDuration());
288
+ boolean sampleBeforeStartTime =
289
+ info.presentationTimeUs < TimeHelpers.secToUs(item.getStartTime());
290
+
291
+ // Surface output has no accessible ByteBuffer: getOutputBuffer returns
292
+ // null by contract. Its callback index and timestamp name the frame to
293
+ // render; probing the buffer would strand it and stall the decoder.
294
+ if (!itemEndReached && SurfaceOutputPolicy.hasFrame(info.size, info.flags)
295
+ && !sampleOutOfBounds && !sampleBeforeStartTime) {
296
+ Frame frame = getFreeFrame();
297
+ frame.outputBufferIndex = index;
298
+ frame.presentationTimeUs = info.presentationTimeUs;
299
+ pendingFrames.add(frame);
300
+ if (onFrameAvailableListener != null) {
301
+ onFrameAvailableListener.onFrameAvailable(frame.presentationTimeUs);
302
+ }
303
+ } else {
304
+ try {
305
+ codec.releaseOutputBuffer(index, false);
306
+ } catch (Throwable e) {
307
+ return;
308
+ }
309
+ }
310
+
311
+ itemEndReached = outputEOS || sampleOutOfBounds;
312
+ if (itemEndReached) {
313
+ if (onEndReachedListener != null) {
314
+ onEndReachedListener.onEndReached();
315
+ }
316
+ }
317
+ }
318
+
319
+ @Override
320
+ public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
321
+ if (released) {
322
+ return;
323
+ }
324
+ if (onErrorListener != null) {
325
+ onErrorListener.onError(e);
326
+ }
327
+ }
328
+
329
+ @Override
330
+ public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
331
+ // Do nothing
332
+ }
333
+
334
+ synchronized public Long render(long compositionTimeUs) {
335
+ if (pendingFrames.isEmpty()) {
336
+ return null;
337
+ }
338
+ long compositionStartTimeUs = TimeHelpers.secToUs(item.getCompositionStartTime());
339
+ long startTimeUs = TimeHelpers.secToUs(item.getStartTime());
340
+
341
+ List<Frame> framesToRenders = new ArrayList<>();
342
+ for (Frame frame : pendingFrames) {
343
+ if (frame.presentationTimeUs - startTimeUs <= compositionTimeUs - compositionStartTimeUs || !hasRenderedFrame) {
344
+ framesToRenders.add(frame);
345
+ hasRenderedFrame = true;
346
+ }
347
+ }
348
+ if (framesToRenders.isEmpty()) {
349
+ return null;
350
+ }
351
+ // Only the latest is drawn: after a seek the batch runs from the previous
352
+ // key frame, and drawing each one into the surface is GPU work nobody sees.
353
+ int lastIndex = framesToRenders.size() - 1;
354
+ for (int i = 0; i <= lastIndex; i++) {
355
+ try {
356
+ codec.releaseOutputBuffer(framesToRenders.get(i).outputBufferIndex, i == lastIndex);
357
+ } catch (Throwable e) {
358
+ // A codec flushed or released meanwhile: the buffer is gone with it.
359
+ }
360
+ }
361
+ freeFrames.addAll(framesToRenders);
362
+ pendingFrames.removeAll(framesToRenders);
363
+
364
+ return framesToRenders.get(framesToRenders.size() - 1).presentationTimeUs;
365
+ }
366
+
367
+ /**
368
+ * Seek to a specific time in the video.
369
+ *
370
+ * @param time the position in the composition timeline, in microseconds
371
+ */
372
+ synchronized public void seekTo(long time) {
373
+ freeFrames.addAll(pendingFrames);
374
+ pendingFrames.clear();
375
+ codec.flush();
376
+ // Queued behind the callbacks from before the flush, and ahead of those
377
+ // start() brings: they run on this handler's thread in order.
378
+ if (callbackHandler != null) {
379
+ droppingStaleCallbacks = true;
380
+ long generation = ++flushGeneration;
381
+ callbackHandler.post(() -> {
382
+ synchronized (this) {
383
+ if (flushGeneration == generation) {
384
+ droppingStaleCallbacks = false;
385
+ }
386
+ }
387
+ });
388
+ }
389
+ long itemTime = time - TimeHelpers.secToUs(item.getCompositionStartTime());
390
+ long seekTime = TimeHelpers.secToUs(item.getStartTime()) + Math.max(itemTime, 0);
391
+ extractor.seekTo(seekTime, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
392
+ itemEndReached = false;
393
+ hasRenderedFrame = false;
394
+ inputEOS = false;
395
+ if (started) {
396
+ codec.start();
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Release the decoder.
402
+ */
403
+ synchronized public void release() {
404
+ if (!released) {
405
+ released = true;
406
+ if (extractor != null) {
407
+ extractor.release();
408
+ }
409
+ if (codec != null) {
410
+ codec.release();
411
+ codec = null;
412
+ }
413
+ }
414
+ }
415
+
416
+ private synchronized void configure() {
417
+ if (prepared && surface != null && !configured) {
418
+ if (callbackHandler == null) {
419
+ // Where MediaCodec would call back without a handler; seekTo posts to it.
420
+ Looper looper = Looper.myLooper();
421
+ callbackHandler = new Handler(looper != null ? looper : Looper.getMainLooper());
422
+ }
423
+ codec.setCallback(this, callbackHandler);
424
+ codec.configure(format, surface, null, 0);
425
+ configured = true;
426
+ }
427
+ }
428
+
429
+ /**
430
+ * The mime type to decode the track as. A Dolby Vision track on a device
431
+ * without a Dolby Vision decoder is decoded as its base layer: HDR clips
432
+ * from an iPhone are profile 8.4, Dolby Vision metadata over an HLG HEVC
433
+ * stream any HEVC decoder plays; profile 9 is over AVC. Sets the format to
434
+ * match.
435
+ */
436
+ private static String playableMime(MediaFormat format, String mime) {
437
+ if (!MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION.equals(mime)
438
+ || new MediaCodecList(MediaCodecList.REGULAR_CODECS).findDecoderForFormat(format) != null) {
439
+ return mime;
440
+ }
441
+ int profile = format.containsKey(MediaFormat.KEY_PROFILE)
442
+ ? format.getInteger(MediaFormat.KEY_PROFILE) : -1;
443
+ String base = profile == CodecProfileLevel.DolbyVisionProfileDvavSe
444
+ || profile == CodecProfileLevel.DolbyVisionProfileDvavPer
445
+ || profile == CodecProfileLevel.DolbyVisionProfileDvavPen
446
+ ? MediaFormat.MIMETYPE_VIDEO_AVC
447
+ : MediaFormat.MIMETYPE_VIDEO_HEVC;
448
+ format.setString(MediaFormat.KEY_MIME, base);
449
+ // The Dolby Vision profile and level mean nothing to the base decoder.
450
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
451
+ format.removeKey(MediaFormat.KEY_PROFILE);
452
+ format.removeKey(MediaFormat.KEY_LEVEL);
453
+ }
454
+ return base;
455
+ }
456
+
457
+ private static int selectTrack(MediaExtractor extractor) {
458
+ int numTracks = extractor.getTrackCount();
459
+ for (int i = 0; i < numTracks; i++) {
460
+ MediaFormat format = extractor.getTrackFormat(i);
461
+ String mime = format.getString(MediaFormat.KEY_MIME);
462
+ if (mime != null && mime.startsWith("video/")) {
463
+ return i;
464
+ }
465
+ }
466
+ return -1;
467
+ }
468
+
469
+ private Frame getFreeFrame() {
470
+ return freeFrames.empty() ? new Frame() : freeFrames.pop();
471
+ }
472
+
473
+ public interface OnErrorListener {
474
+ void onError(Exception error);
475
+ }
476
+
477
+ public interface OnFrameAvailableListener {
478
+ void onFrameAvailable(long presentationTimeUs);
479
+ }
480
+
481
+ public interface OnEndReachedListener {
482
+ void onEndReached();
483
+ }
484
+
485
+ /**
486
+ * A frame representing a decoded output buffer sample.
487
+ */
488
+ private static class Frame {
489
+ private int outputBufferIndex;
490
+ private long presentationTimeUs;
491
+ }
492
+ }