react-native-alpha-video 0.0.0-stage → 1.0.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 (283) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +908 -2
  3. package/THIRD_PARTY_NOTICES.md +30 -0
  4. package/android/av1/build.gradle +67 -0
  5. package/android/av1/consumer-rules.pro +4 -0
  6. package/android/av1/src/main/cpp/Av1Codec_jni.cpp +8 -0
  7. package/android/av1/src/main/cpp/CMakeLists.txt +34 -0
  8. package/android/av1/src/main/java/dev/mediacache/alphavideo/av1/Av1Codec.java +21 -0
  9. package/android/build.gradle +90 -0
  10. package/android/consumer-rules.pro +2 -0
  11. package/android/gradle.properties +1 -0
  12. package/android/publish.gradle +82 -0
  13. package/android/settings.gradle +5 -0
  14. package/android/src/main/AndroidManifest.xml +1 -0
  15. package/android/src/main/cpp/CMakeLists.txt +59 -0
  16. package/android/src/main/java/dev/mediacache/alphavideo/AlphaVideoCodecs.java +73 -0
  17. package/android/src/main/java/dev/mediacache/alphavideo/AlphaVideoView.java +1982 -0
  18. package/android/src/main/java/dev/mediacache/alphavideo/BufferedDualStreamPlayer.java +978 -0
  19. package/android/src/main/java/dev/mediacache/alphavideo/Fetcher.java +37 -0
  20. package/android/src/main/java/dev/mediacache/alphavideo/HttpFetcher.java +156 -0
  21. package/android/src/main/java/dev/mediacache/alphavideo/MediaCodecColor.java +47 -0
  22. package/android/src/main/java/dev/mediacache/alphavideo/MediaCodecDecoders.java +135 -0
  23. package/android/src/main/java/dev/mediacache/alphavideo/RangeSourceLink.java +54 -0
  24. package/android/src/main/java/dev/mediacache/alphavideo/SourceReader.java +174 -0
  25. package/android/src/main/java/dev/mediacache/alphavideo/SurfaceTextureVideoDecoder.java +802 -0
  26. package/android/src/main/java/dev/mediacache/alphavideo/WebMAlphaPlayerNative.java +95 -0
  27. package/android/src/main/java/dev/mediacache/alphavideo/WebMFrame.java +54 -0
  28. package/android/src/main/java/dev/mediacache/alphavideo/WebMSource.java +160 -0
  29. package/android/src/rangesource/java/dev/mediacache/alphavideo/AppDataSource.java +50 -0
  30. package/android/vpx/build.gradle +67 -0
  31. package/android/vpx/consumer-rules.pro +4 -0
  32. package/android/vpx/src/main/cpp/CMakeLists.txt +34 -0
  33. package/android/vpx/src/main/cpp/VpxCodec_jni.cpp +8 -0
  34. package/android/vpx/src/main/java/dev/mediacache/alphavideo/vpx/VpxCodec.java +21 -0
  35. package/codecs/av1/Dav1dDecoderPlugin.cpp +157 -0
  36. package/codecs/av1/Dav1dDecoderPlugin.h +17 -0
  37. package/codecs/vpx/VpxDecoderPlugin.cpp +103 -0
  38. package/codecs/vpx/VpxDecoderPlugin.h +17 -0
  39. package/cpp/AlphaDemuxer.cpp +984 -0
  40. package/cpp/AlphaDemuxer.h +123 -0
  41. package/cpp/AlphaVideoDecoderPlugin.h +84 -0
  42. package/cpp/Av1SequenceHeader.cpp +175 -0
  43. package/cpp/Av1SequenceHeader.h +25 -0
  44. package/cpp/ByteSource.cpp +116 -0
  45. package/cpp/ByteSource.h +59 -0
  46. package/cpp/RangeSourceClient.h +40 -0
  47. package/cpp/VideoDecoder.cpp +157 -0
  48. package/cpp/VideoDecoder.h +83 -0
  49. package/cpp/WebMAlphaPlayer.cpp +1666 -0
  50. package/cpp/WebMAlphaPlayer.h +389 -0
  51. package/cpp/WebMAlphaPlayer_jni.cpp +1380 -0
  52. package/cpp/YuvToRgb.cpp +39 -0
  53. package/cpp/YuvToRgb.h +40 -0
  54. package/ios/Sources/AlphaVideo/AlphaVideoCodecs.mm +63 -0
  55. package/ios/Sources/AlphaVideo/AlphaVideoInternal.h +70 -0
  56. package/ios/Sources/AlphaVideo/AlphaVideoPlayer.mm +994 -0
  57. package/ios/Sources/AlphaVideo/AlphaVideoSource.mm +476 -0
  58. package/ios/Sources/AlphaVideo/AlphaVideoView.mm +374 -0
  59. package/ios/Sources/AlphaVideo/include/AlphaVideo.h +303 -0
  60. package/ios/Sources/AlphaVideoAV1/AlphaVideoAV1Codec.mm +16 -0
  61. package/ios/Sources/AlphaVideoAV1/include/AlphaVideoAV1.h +18 -0
  62. package/ios/Sources/AlphaVideoVPX/AlphaVideoVPXCodec.mm +16 -0
  63. package/ios/Sources/AlphaVideoVPX/include/AlphaVideoVPX.h +17 -0
  64. package/lib/commonjs/AlphaVideoView.js +187 -0
  65. package/lib/commonjs/AlphaVideoView.js.map +1 -0
  66. package/lib/commonjs/AlphaVideoViewNativeComponent.js +47 -0
  67. package/lib/commonjs/AlphaVideoViewNativeComponent.js.map +1 -0
  68. package/lib/commonjs/AlphaVideoViewNativeComponent.web.js +11 -0
  69. package/lib/commonjs/AlphaVideoViewNativeComponent.web.js.map +1 -0
  70. package/lib/commonjs/NativeAlphaVideoModule.js +10 -0
  71. package/lib/commonjs/NativeAlphaVideoModule.js.map +1 -0
  72. package/lib/commonjs/NativeAlphaVideoModule.web.js +9 -0
  73. package/lib/commonjs/NativeAlphaVideoModule.web.js.map +1 -0
  74. package/lib/commonjs/WebPlayer.js +12 -0
  75. package/lib/commonjs/WebPlayer.js.map +1 -0
  76. package/lib/commonjs/WebPlayer.web.js +115 -0
  77. package/lib/commonjs/WebPlayer.web.js.map +1 -0
  78. package/lib/commonjs/index.js +124 -0
  79. package/lib/commonjs/index.js.map +1 -0
  80. package/lib/module/AlphaVideoView.js +187 -0
  81. package/lib/module/AlphaVideoView.js.map +1 -0
  82. package/lib/module/AlphaVideoViewNativeComponent.js +47 -0
  83. package/lib/module/AlphaVideoViewNativeComponent.js.map +1 -0
  84. package/lib/module/AlphaVideoViewNativeComponent.web.js +11 -0
  85. package/lib/module/AlphaVideoViewNativeComponent.web.js.map +1 -0
  86. package/lib/module/NativeAlphaVideoModule.js +10 -0
  87. package/lib/module/NativeAlphaVideoModule.js.map +1 -0
  88. package/lib/module/NativeAlphaVideoModule.web.js +9 -0
  89. package/lib/module/NativeAlphaVideoModule.web.js.map +1 -0
  90. package/lib/module/WebPlayer.js +12 -0
  91. package/lib/module/WebPlayer.js.map +1 -0
  92. package/lib/module/WebPlayer.web.js +115 -0
  93. package/lib/module/WebPlayer.web.js.map +1 -0
  94. package/lib/module/index.js +124 -0
  95. package/lib/module/index.js.map +1 -0
  96. package/lib/typescript/react-native/src/AlphaVideoView.d.ts +189 -0
  97. package/lib/typescript/react-native/src/AlphaVideoView.d.ts.map +1 -0
  98. package/lib/typescript/react-native/src/AlphaVideoViewNativeComponent.d.ts +109 -0
  99. package/lib/typescript/react-native/src/AlphaVideoViewNativeComponent.d.ts.map +1 -0
  100. package/lib/typescript/react-native/src/AlphaVideoViewNativeComponent.web.d.ts +5 -0
  101. package/lib/typescript/react-native/src/AlphaVideoViewNativeComponent.web.d.ts.map +1 -0
  102. package/lib/typescript/react-native/src/NativeAlphaVideoModule.d.ts +21 -0
  103. package/lib/typescript/react-native/src/NativeAlphaVideoModule.d.ts.map +1 -0
  104. package/lib/typescript/react-native/src/NativeAlphaVideoModule.web.d.ts +3 -0
  105. package/lib/typescript/react-native/src/NativeAlphaVideoModule.web.d.ts.map +1 -0
  106. package/lib/typescript/react-native/src/WebPlayer.d.ts +3 -0
  107. package/lib/typescript/react-native/src/WebPlayer.d.ts.map +1 -0
  108. package/lib/typescript/react-native/src/WebPlayer.web.d.ts +9 -0
  109. package/lib/typescript/react-native/src/WebPlayer.web.d.ts.map +1 -0
  110. package/lib/typescript/react-native/src/index.d.ts +77 -0
  111. package/lib/typescript/react-native/src/index.d.ts.map +1 -0
  112. package/licenses/dav1d-COPYING.txt +23 -0
  113. package/licenses/libvpx-LICENSE.txt +31 -0
  114. package/licenses/libvpx-PATENTS.txt +23 -0
  115. package/licenses/libwebm-LICENSE.txt +30 -0
  116. package/licenses/libwebm-PATENTS.txt +23 -0
  117. package/package.json +152 -4
  118. package/prebuilt/android/arm64-v8a/lib/libdav1d.a +0 -0
  119. package/prebuilt/android/arm64-v8a/lib/libvpx.a +0 -0
  120. package/prebuilt/android/armeabi-v7a/lib/libdav1d.a +0 -0
  121. package/prebuilt/android/armeabi-v7a/lib/libvpx.a +0 -0
  122. package/prebuilt/android/x86_64/lib/libdav1d.a +0 -0
  123. package/prebuilt/android/x86_64/lib/libvpx.a +0 -0
  124. package/prebuilt/include/dav1d/common.h +94 -0
  125. package/prebuilt/include/dav1d/data.h +117 -0
  126. package/prebuilt/include/dav1d/dav1d.h +329 -0
  127. package/prebuilt/include/dav1d/headers.h +440 -0
  128. package/prebuilt/include/dav1d/picture.h +157 -0
  129. package/prebuilt/include/dav1d/version.h +50 -0
  130. package/prebuilt/include/vpx/vp8.h +136 -0
  131. package/prebuilt/include/vpx/vp8dx.h +228 -0
  132. package/prebuilt/include/vpx/vpx_codec.h +475 -0
  133. package/prebuilt/include/vpx/vpx_decoder.h +365 -0
  134. package/prebuilt/include/vpx/vpx_frame_buffer.h +83 -0
  135. package/prebuilt/include/vpx/vpx_image.h +209 -0
  136. package/prebuilt/include/vpx/vpx_integer.h +40 -0
  137. package/prebuilt/ios/lib/libdav1d.a +0 -0
  138. package/prebuilt/ios/lib/libvpx.a +0 -0
  139. package/prebuilt/ios-simulator/include/dav1d/common.h +94 -0
  140. package/prebuilt/ios-simulator/include/dav1d/data.h +117 -0
  141. package/prebuilt/ios-simulator/include/dav1d/dav1d.h +329 -0
  142. package/prebuilt/ios-simulator/include/dav1d/headers.h +440 -0
  143. package/prebuilt/ios-simulator/include/dav1d/picture.h +157 -0
  144. package/prebuilt/ios-simulator/include/dav1d/version.h +50 -0
  145. package/prebuilt/ios-simulator/include/vpx/vp8.h +136 -0
  146. package/prebuilt/ios-simulator/include/vpx/vp8dx.h +228 -0
  147. package/prebuilt/ios-simulator/include/vpx/vpx_codec.h +475 -0
  148. package/prebuilt/ios-simulator/include/vpx/vpx_decoder.h +365 -0
  149. package/prebuilt/ios-simulator/include/vpx/vpx_frame_buffer.h +83 -0
  150. package/prebuilt/ios-simulator/include/vpx/vpx_image.h +209 -0
  151. package/prebuilt/ios-simulator/include/vpx/vpx_integer.h +40 -0
  152. package/prebuilt/ios-simulator/lib/libdav1d.a +0 -0
  153. package/prebuilt/ios-simulator/lib/libvpx.a +0 -0
  154. package/react-native/android/CMakeLists.txt +14 -0
  155. package/react-native/android/build.gradle +113 -0
  156. package/react-native/android/src/main/AndroidManifest.xml +1 -0
  157. package/react-native/android/src/main/java/dev/mediacache/alphavideo/reactnative/AlphaVideoModule.java +102 -0
  158. package/react-native/android/src/main/java/dev/mediacache/alphavideo/reactnative/AlphaVideoPackage.java +50 -0
  159. package/react-native/android/src/main/java/dev/mediacache/alphavideo/reactnative/AlphaVideoViewManager.java +317 -0
  160. package/react-native/android/src/newarch/java/dev/mediacache/alphavideo/reactnative/AlphaVideoModuleSpec.java +10 -0
  161. package/react-native/android/src/newarch/java/dev/mediacache/alphavideo/reactnative/AlphaVideoViewManagerSpec.java +23 -0
  162. package/react-native/android/src/norangesource/java/dev/mediacache/alphavideo/AppDataSource.java +15 -0
  163. package/react-native/android/src/oldarch/java/dev/mediacache/alphavideo/reactnative/AlphaVideoModuleSpec.java +30 -0
  164. package/react-native/android/src/oldarch/java/dev/mediacache/alphavideo/reactnative/AlphaVideoViewManagerSpec.java +61 -0
  165. package/react-native/ios/RNAlphaVideoViewComponentView.mm +173 -0
  166. package/react-native/ios/RNAlphaVideoViewManager.mm +147 -0
  167. package/react-native/src/AlphaVideoView.tsx +329 -0
  168. package/react-native/src/AlphaVideoViewNativeComponent.ts +132 -0
  169. package/react-native/src/AlphaVideoViewNativeComponent.web.ts +5 -0
  170. package/react-native/src/NativeAlphaVideoModule.ts +22 -0
  171. package/react-native/src/NativeAlphaVideoModule.web.ts +2 -0
  172. package/react-native/src/WebPlayer.tsx +7 -0
  173. package/react-native/src/WebPlayer.web.tsx +87 -0
  174. package/react-native/src/index.tsx +144 -0
  175. package/react-native-alpha-video.podspec +99 -0
  176. package/react-native.config.js +12 -0
  177. package/scripts/build_codecs_android.sh +246 -0
  178. package/scripts/build_codecs_ios.sh +31 -0
  179. package/scripts/build_codecs_ios_device.sh +210 -0
  180. package/scripts/build_codecs_ios_simulator.sh +256 -0
  181. package/scripts/build_wasm.sh +230 -0
  182. package/scripts/build_xcframework.sh +49 -0
  183. package/scripts/fetch-prebuilt.sh +48 -0
  184. package/scripts/package_release.sh +107 -0
  185. package/scripts/publish-android.sh +85 -0
  186. package/scripts/vendor_range_source.sh +47 -0
  187. package/third_party/libwebm/AUTHORS.TXT +5 -0
  188. package/third_party/libwebm/LICENSE.TXT +30 -0
  189. package/third_party/libwebm/PATENTS.TXT +23 -0
  190. package/third_party/libwebm/README.md +7 -0
  191. package/third_party/libwebm/webm_parser/include/webm/buffer_reader.h +138 -0
  192. package/third_party/libwebm/webm_parser/include/webm/callback.h +363 -0
  193. package/third_party/libwebm/webm_parser/include/webm/dom_types.h +1781 -0
  194. package/third_party/libwebm/webm_parser/include/webm/element.h +208 -0
  195. package/third_party/libwebm/webm_parser/include/webm/file_reader.h +103 -0
  196. package/third_party/libwebm/webm_parser/include/webm/id.h +1085 -0
  197. package/third_party/libwebm/webm_parser/include/webm/istream_reader.h +99 -0
  198. package/third_party/libwebm/webm_parser/include/webm/reader.h +94 -0
  199. package/third_party/libwebm/webm_parser/include/webm/status.h +166 -0
  200. package/third_party/libwebm/webm_parser/include/webm/webm_parser.h +133 -0
  201. package/third_party/libwebm/webm_parser/src/ancestory.cc +334 -0
  202. package/third_party/libwebm/webm_parser/src/ancestory.h +83 -0
  203. package/third_party/libwebm/webm_parser/src/audio_parser.h +84 -0
  204. package/third_party/libwebm/webm_parser/src/bit_utils.cc +26 -0
  205. package/third_party/libwebm/webm_parser/src/bit_utils.h +24 -0
  206. package/third_party/libwebm/webm_parser/src/block_additions_parser.h +30 -0
  207. package/third_party/libwebm/webm_parser/src/block_group_parser.h +71 -0
  208. package/third_party/libwebm/webm_parser/src/block_header_parser.cc +76 -0
  209. package/third_party/libwebm/webm_parser/src/block_header_parser.h +65 -0
  210. package/third_party/libwebm/webm_parser/src/block_more_parser.h +32 -0
  211. package/third_party/libwebm/webm_parser/src/block_parser.cc +285 -0
  212. package/third_party/libwebm/webm_parser/src/block_parser.h +126 -0
  213. package/third_party/libwebm/webm_parser/src/bool_parser.h +95 -0
  214. package/third_party/libwebm/webm_parser/src/buffer_reader.cc +110 -0
  215. package/third_party/libwebm/webm_parser/src/byte_parser.h +144 -0
  216. package/third_party/libwebm/webm_parser/src/callback.cc +155 -0
  217. package/third_party/libwebm/webm_parser/src/chapter_atom_parser.h +42 -0
  218. package/third_party/libwebm/webm_parser/src/chapter_display_parser.h +34 -0
  219. package/third_party/libwebm/webm_parser/src/chapters_parser.h +28 -0
  220. package/third_party/libwebm/webm_parser/src/cluster_parser.h +48 -0
  221. package/third_party/libwebm/webm_parser/src/colour_parser.h +55 -0
  222. package/third_party/libwebm/webm_parser/src/content_enc_aes_settings_parser.h +32 -0
  223. package/third_party/libwebm/webm_parser/src/content_encoding_parser.h +38 -0
  224. package/third_party/libwebm/webm_parser/src/content_encodings_parser.h +30 -0
  225. package/third_party/libwebm/webm_parser/src/content_encryption_parser.h +37 -0
  226. package/third_party/libwebm/webm_parser/src/cue_point_parser.h +38 -0
  227. package/third_party/libwebm/webm_parser/src/cue_track_positions_parser.h +39 -0
  228. package/third_party/libwebm/webm_parser/src/cues_parser.h +27 -0
  229. package/third_party/libwebm/webm_parser/src/date_parser.cc +72 -0
  230. package/third_party/libwebm/webm_parser/src/date_parser.h +64 -0
  231. package/third_party/libwebm/webm_parser/src/ebml_parser.h +48 -0
  232. package/third_party/libwebm/webm_parser/src/edition_entry_parser.h +35 -0
  233. package/third_party/libwebm/webm_parser/src/element_parser.h +102 -0
  234. package/third_party/libwebm/webm_parser/src/file_reader.cc +142 -0
  235. package/third_party/libwebm/webm_parser/src/float_parser.cc +77 -0
  236. package/third_party/libwebm/webm_parser/src/float_parser.h +65 -0
  237. package/third_party/libwebm/webm_parser/src/id_element_parser.cc +49 -0
  238. package/third_party/libwebm/webm_parser/src/id_element_parser.h +56 -0
  239. package/third_party/libwebm/webm_parser/src/id_parser.cc +75 -0
  240. package/third_party/libwebm/webm_parser/src/id_parser.h +45 -0
  241. package/third_party/libwebm/webm_parser/src/info_parser.h +44 -0
  242. package/third_party/libwebm/webm_parser/src/int_parser.h +121 -0
  243. package/third_party/libwebm/webm_parser/src/istream_reader.cc +133 -0
  244. package/third_party/libwebm/webm_parser/src/master_parser.cc +298 -0
  245. package/third_party/libwebm/webm_parser/src/master_parser.h +230 -0
  246. package/third_party/libwebm/webm_parser/src/master_value_parser.h +533 -0
  247. package/third_party/libwebm/webm_parser/src/mastering_metadata_parser.h +57 -0
  248. package/third_party/libwebm/webm_parser/src/parser.h +34 -0
  249. package/third_party/libwebm/webm_parser/src/parser_utils.cc +34 -0
  250. package/third_party/libwebm/webm_parser/src/parser_utils.h +64 -0
  251. package/third_party/libwebm/webm_parser/src/projection_parser.h +40 -0
  252. package/third_party/libwebm/webm_parser/src/recursive_parser.h +93 -0
  253. package/third_party/libwebm/webm_parser/src/seek_head_parser.h +27 -0
  254. package/third_party/libwebm/webm_parser/src/seek_parser.h +37 -0
  255. package/third_party/libwebm/webm_parser/src/segment_parser.cc +86 -0
  256. package/third_party/libwebm/webm_parser/src/segment_parser.h +53 -0
  257. package/third_party/libwebm/webm_parser/src/simple_tag_parser.h +37 -0
  258. package/third_party/libwebm/webm_parser/src/size_parser.cc +56 -0
  259. package/third_party/libwebm/webm_parser/src/size_parser.h +43 -0
  260. package/third_party/libwebm/webm_parser/src/skip_callback.h +63 -0
  261. package/third_party/libwebm/webm_parser/src/skip_parser.cc +59 -0
  262. package/third_party/libwebm/webm_parser/src/skip_parser.h +35 -0
  263. package/third_party/libwebm/webm_parser/src/slices_parser.h +30 -0
  264. package/third_party/libwebm/webm_parser/src/tag_parser.h +37 -0
  265. package/third_party/libwebm/webm_parser/src/tags_parser.h +27 -0
  266. package/third_party/libwebm/webm_parser/src/targets_parser.h +35 -0
  267. package/third_party/libwebm/webm_parser/src/time_slice_parser.h +30 -0
  268. package/third_party/libwebm/webm_parser/src/track_entry_parser.h +65 -0
  269. package/third_party/libwebm/webm_parser/src/tracks_parser.h +27 -0
  270. package/third_party/libwebm/webm_parser/src/unknown_parser.cc +49 -0
  271. package/third_party/libwebm/webm_parser/src/unknown_parser.h +38 -0
  272. package/third_party/libwebm/webm_parser/src/var_int_parser.cc +71 -0
  273. package/third_party/libwebm/webm_parser/src/var_int_parser.h +56 -0
  274. package/third_party/libwebm/webm_parser/src/video_parser.h +122 -0
  275. package/third_party/libwebm/webm_parser/src/virtual_block_parser.cc +72 -0
  276. package/third_party/libwebm/webm_parser/src/virtual_block_parser.h +69 -0
  277. package/third_party/libwebm/webm_parser/src/void_parser.cc +49 -0
  278. package/third_party/libwebm/webm_parser/src/void_parser.h +41 -0
  279. package/third_party/libwebm/webm_parser/src/webm_parser.cc +282 -0
  280. package/third_party/range-source/LICENSE +21 -0
  281. package/third_party/range-source/README.md +8 -0
  282. package/third_party/range-source/include/range_source.h +395 -0
  283. package/third_party/range-source/include/range_source_download.h +309 -0
@@ -0,0 +1,1380 @@
1
+ /**
2
+ * WebMAlphaPlayer JNI Bridge
3
+ * Connects Java/Kotlin to C++ WebMAlphaPlayer core
4
+ */
5
+
6
+ #include <jni.h>
7
+ #include <android/log.h>
8
+ #include <android/native_window.h>
9
+ #include <android/native_window_jni.h>
10
+
11
+ #include "WebMAlphaPlayer.h"
12
+ #include "AlphaDemuxer.h"
13
+ #include "ByteSource.h"
14
+ #include "VideoDecoder.h"
15
+ #include <pthread.h>
16
+ #include <algorithm>
17
+ #include <atomic>
18
+ #include <memory>
19
+ #include <unordered_map>
20
+ #include <unordered_set>
21
+ #include <mutex>
22
+ #include <vector>
23
+ #include <string>
24
+
25
+ #define LOG_TAG "WebMAlphaPlayer"
26
+ // JNI lifecycle traces, compiled in only with WEBM_DEBUG_LOGS
27
+ #if defined(WEBM_DEBUG_LOGS)
28
+ #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
29
+ #else
30
+ #define LOGI(...) do {} while (0)
31
+ #endif
32
+ #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
33
+
34
+ namespace {
35
+
36
+ // Store player instances by ID
37
+ // Using shared_ptr to allow safe reference during async operations
38
+ std::unordered_map<jlong, std::shared_ptr<webm::WebMAlphaPlayer>> players;
39
+ std::mutex playersMutex;
40
+ jlong nextPlayerId = 1;
41
+
42
+ // Calls that can block or emit callbacks must run outside playersMutex: a preload holds the
43
+ // decode lock while it emits events, and the "loaded" event takes playersMutex under
44
+ // callbacksMutex, so holding playersMutex across them deadlocks.
45
+ std::shared_ptr<webm::WebMAlphaPlayer> findPlayer(jlong playerId) {
46
+ std::lock_guard<std::mutex> lock(playersMutex);
47
+ auto it = players.find(playerId);
48
+ return it != players.end() ? it->second : nullptr;
49
+ }
50
+
51
+ // Track which players are destroyed (to reject callbacks from destroyed players)
52
+ std::unordered_set<jlong> destroyedPlayers;
53
+ std::mutex destroyedMutex;
54
+
55
+ // Track which players are stopped/unloaded (to reject callbacks during soft-release)
56
+ // This is separate from destroyedPlayers because stopped players can be re-started
57
+ std::unordered_set<jlong> stoppedPlayers;
58
+ std::mutex stoppedMutex;
59
+
60
+ // JNI callback references
61
+ struct CallbackRefs {
62
+ JavaVM* jvm = nullptr;
63
+ jobject listener = nullptr;
64
+ jmethodID onLoad = nullptr;
65
+ jmethodID onFrame = nullptr;
66
+ jmethodID onPlay = nullptr;
67
+ jmethodID onPause = nullptr;
68
+ jmethodID onStop = nullptr;
69
+ jmethodID onLoop = nullptr;
70
+ jmethodID onEnd = nullptr;
71
+ jmethodID onError = nullptr;
72
+ jmethodID onPreloadComplete = nullptr;
73
+ };
74
+
75
+ std::unordered_map<jlong, CallbackRefs> callbacks;
76
+ std::mutex callbacksMutex;
77
+
78
+ JNIEnv* getJNIEnv(JavaVM* jvm) {
79
+ if (!jvm) {
80
+ LOGE("getJNIEnv called with null jvm!");
81
+ return nullptr;
82
+ }
83
+ JNIEnv* env = nullptr;
84
+ jint result = jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
85
+ if (result == JNI_EDETACHED) {
86
+ if (jvm->AttachCurrentThread(&env, nullptr) != JNI_OK) {
87
+ LOGE("Failed to attach thread to JVM");
88
+ return nullptr;
89
+ }
90
+ } else if (result != JNI_OK) {
91
+ LOGE("GetEnv failed with result %d", result);
92
+ return nullptr;
93
+ }
94
+ return env;
95
+ }
96
+
97
+ std::string stringFromJava(JNIEnv* env, jstring value) {
98
+ if (!value) return {};
99
+ const char* chars = env->GetStringUTFChars(value, nullptr);
100
+ std::string result(chars);
101
+ env->ReleaseStringUTFChars(value, chars);
102
+ return result;
103
+ }
104
+
105
+ //==============================================================================
106
+ // Range readers over a Java AlphaVideoDataSource (SourceReader.Upstream)
107
+ //==============================================================================
108
+
109
+ pthread_key_t detachKey;
110
+ pthread_once_t detachKeyOnce = PTHREAD_ONCE_INIT;
111
+
112
+ void detachThread(void* vm) {
113
+ static_cast<JavaVM*>(vm)->DetachCurrentThread();
114
+ }
115
+
116
+ // This thread's env. A native thread is attached until it exits, as a reader's fetch thread calls
117
+ // into Java for as long as it runs.
118
+ JNIEnv* threadEnv(JavaVM* vm) {
119
+ JNIEnv* env = nullptr;
120
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) == JNI_OK) return env;
121
+ if (vm->AttachCurrentThread(&env, nullptr) != JNI_OK) return nullptr;
122
+ pthread_once(&detachKeyOnce, [] { pthread_key_create(&detachKey, detachThread); });
123
+ pthread_setspecific(detachKey, vm);
124
+ return env;
125
+ }
126
+
127
+ // Clears a pending Java exception; true if there was one.
128
+ bool clearException(JNIEnv* env) {
129
+ if (!env->ExceptionCheck()) return false;
130
+ env->ExceptionDescribe();
131
+ env->ExceptionClear();
132
+ return true;
133
+ }
134
+
135
+ class JavaUpstream : public webm::Upstream {
136
+ public:
137
+ static std::unique_ptr<JavaUpstream> create(JNIEnv* env, jobject upstream) {
138
+ auto result = std::unique_ptr<JavaUpstream>(new JavaUpstream());
139
+ env->GetJavaVM(&result->vm_);
140
+ jclass cls = env->GetObjectClass(upstream);
141
+ result->open_ = env->GetMethodID(cls, "open", "(J)J");
142
+ result->read_ = env->GetMethodID(cls, "read", "([BI)I");
143
+ result->close_ = env->GetMethodID(cls, "close", "()V");
144
+ result->cancel_ = env->GetMethodID(cls, "cancel", "()V");
145
+ result->etag_ = env->GetFieldID(cls, "etag", "Ljava/lang/String;");
146
+ result->lastModified_ = env->GetFieldID(cls, "lastModified", "Ljava/lang/String;");
147
+ result->error_ = env->GetFieldID(cls, "error", "Ljava/lang/String;");
148
+ env->DeleteLocalRef(cls);
149
+ jbyteArray buffer = env->NewByteArray(kBufferBytes);
150
+ if (clearException(env) || !buffer || !result->open_ || !result->read_ || !result->close_ ||
151
+ !result->cancel_ || !result->etag_ || !result->lastModified_ || !result->error_) {
152
+ return nullptr;
153
+ }
154
+ result->object_ = env->NewGlobalRef(upstream);
155
+ result->buffer_ = static_cast<jbyteArray>(env->NewGlobalRef(buffer));
156
+ env->DeleteLocalRef(buffer);
157
+ return result;
158
+ }
159
+
160
+ ~JavaUpstream() override {
161
+ JNIEnv* env = threadEnv(vm_);
162
+ if (!env) return;
163
+ if (object_) env->DeleteGlobalRef(object_);
164
+ if (buffer_) env->DeleteGlobalRef(buffer_);
165
+ }
166
+
167
+ bool open(int64_t position, webm::SourceInfo& info, std::string& error) override {
168
+ JNIEnv* env = threadEnv(vm_);
169
+ if (!env) {
170
+ error = "Cannot attach the reader's thread to Java";
171
+ return false;
172
+ }
173
+ jlong size = env->CallLongMethod(object_, open_, static_cast<jlong>(position));
174
+ if (clearException(env)) {
175
+ error = "The data source threw";
176
+ return false;
177
+ }
178
+ if (size == -2) {
179
+ error = stringField(env, error_);
180
+ return false;
181
+ }
182
+ info.size = size >= 0 ? size : -1;
183
+ info.etag = stringField(env, etag_);
184
+ info.lastModified = stringField(env, lastModified_);
185
+ return true;
186
+ }
187
+
188
+ int64_t read(uint8_t* out, int64_t size, std::string& error) override {
189
+ JNIEnv* env = threadEnv(vm_);
190
+ if (!env) {
191
+ error = "Cannot attach the reader's thread to Java";
192
+ return -1;
193
+ }
194
+ jint n = env->CallIntMethod(object_, read_, buffer_, static_cast<jint>(std::min<int64_t>(size, kBufferBytes)));
195
+ if (clearException(env)) {
196
+ error = "The data source threw";
197
+ return -1;
198
+ }
199
+ if (n < 0) {
200
+ error = stringField(env, error_);
201
+ return -1;
202
+ }
203
+ if (n > 0) env->GetByteArrayRegion(buffer_, 0, n, reinterpret_cast<jbyte*>(out));
204
+ return n;
205
+ }
206
+
207
+ void close() override { call(close_); }
208
+ void cancel() override { call(cancel_); }
209
+
210
+ private:
211
+ static constexpr jint kBufferBytes = 64 * 1024;
212
+
213
+ JavaUpstream() = default;
214
+
215
+ void call(jmethodID method) {
216
+ JNIEnv* env = threadEnv(vm_);
217
+ if (!env) return;
218
+ env->CallVoidMethod(object_, method);
219
+ clearException(env);
220
+ }
221
+
222
+ std::string stringField(JNIEnv* env, jfieldID field) {
223
+ auto value = static_cast<jstring>(env->GetObjectField(object_, field));
224
+ std::string result = stringFromJava(env, value);
225
+ if (value) env->DeleteLocalRef(value);
226
+ return result;
227
+ }
228
+
229
+ JavaVM* vm_ = nullptr;
230
+ jobject object_ = nullptr;
231
+ jbyteArray buffer_ = nullptr;
232
+ jmethodID open_ = nullptr, read_ = nullptr, close_ = nullptr, cancel_ = nullptr;
233
+ jfieldID etag_ = nullptr, lastModified_ = nullptr, error_ = nullptr;
234
+ };
235
+
236
+ // A SourceReader reference: a shared_ptr of its own, so each holder frees only its own.
237
+ using ReaderRef = std::shared_ptr<webm::SourceReader>;
238
+
239
+ const ReaderRef& readerOf(jlong reference) {
240
+ return *reinterpret_cast<ReaderRef*>(reference);
241
+ }
242
+
243
+ } // anonymous namespace
244
+
245
+ extern "C" {
246
+
247
+ //==============================================================================
248
+ // Player Lifecycle
249
+ //==============================================================================
250
+
251
+ JNIEXPORT jlong JNICALL
252
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeCreate(
253
+ JNIEnv* env,
254
+ jclass clazz,
255
+ jint decodeThreads,
256
+ jint cacheSize,
257
+ jboolean preloadAllFrames,
258
+ jboolean loopPlayback,
259
+ jdouble playbackSpeed,
260
+ jstring modeStr,
261
+ jint streamPrefetchAhead
262
+ ) {
263
+ auto player = std::make_shared<webm::WebMAlphaPlayer>();
264
+
265
+ // Parse mode string
266
+ webm::PlaybackMode mode = webm::PlaybackMode::Stream;
267
+ if (modeStr != nullptr) {
268
+ const char* modeChars = env->GetStringUTFChars(modeStr, nullptr);
269
+ if (modeChars) {
270
+ std::string modeString(modeChars);
271
+ if (modeString == "preload") {
272
+ mode = webm::PlaybackMode::Preload;
273
+ } else if (modeString == "streamAndPreload") {
274
+ mode = webm::PlaybackMode::StreamAndPreload;
275
+ }
276
+ env->ReleaseStringUTFChars(modeStr, modeChars);
277
+ }
278
+ }
279
+
280
+ webm::PlayerConfig config;
281
+ config.decodeThreads = decodeThreads;
282
+ config.cacheSize = cacheSize;
283
+ config.preloadAllFrames = preloadAllFrames;
284
+ config.loopPlayback = loopPlayback;
285
+ config.playbackSpeed = playbackSpeed;
286
+ config.mode = mode;
287
+ config.stream = (mode == webm::PlaybackMode::Stream); // Legacy compatibility
288
+ config.streamPrefetchAhead = streamPrefetchAhead > 0 ? streamPrefetchAhead : 8;
289
+ player->setConfig(config);
290
+
291
+ std::lock_guard<std::mutex> lock(playersMutex);
292
+ jlong id = nextPlayerId++;
293
+ players[id] = player;
294
+
295
+ // Remove from destroyed/stopped sets if ID was reused (shouldn't happen, but be safe)
296
+ {
297
+ std::lock_guard<std::mutex> dlock(destroyedMutex);
298
+ destroyedPlayers.erase(id);
299
+ }
300
+ {
301
+ std::lock_guard<std::mutex> slock(stoppedMutex);
302
+ stoppedPlayers.erase(id);
303
+ }
304
+
305
+ LOGI("Created player %lld (threads=%d, cache=%d)", id, decodeThreads, cacheSize);
306
+ return id;
307
+ }
308
+
309
+ JNIEXPORT void JNICALL
310
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeDestroy(
311
+ JNIEnv* env,
312
+ jclass clazz,
313
+ jlong playerId
314
+ ) {
315
+ // Mark as destroyed FIRST so callbacks are rejected immediately
316
+ {
317
+ std::lock_guard<std::mutex> lock(destroyedMutex);
318
+ destroyedPlayers.insert(playerId);
319
+ }
320
+
321
+ // Also mark as stopped for good measure
322
+ {
323
+ std::lock_guard<std::mutex> slock(stoppedMutex);
324
+ stoppedPlayers.insert(playerId);
325
+ }
326
+
327
+ // Get shared_ptr to stop the player WITHOUT holding playersMutex
328
+ // stop() can block waiting for threads to join
329
+ {
330
+ std::shared_ptr<webm::WebMAlphaPlayer> player;
331
+ {
332
+ std::lock_guard<std::mutex> lock(playersMutex);
333
+ auto it = players.find(playerId);
334
+ if (it != players.end()) {
335
+ player = it->second; // Increment refcount
336
+ }
337
+ }
338
+ // Call stop() without holding playersMutex
339
+ if (player) {
340
+ player->stop(); // Signal to stop any ongoing decode/preload
341
+ }
342
+ }
343
+
344
+ // Clean up callbacks
345
+ {
346
+ std::lock_guard<std::mutex> lock(callbacksMutex);
347
+ auto it = callbacks.find(playerId);
348
+ if (it != callbacks.end()) {
349
+ if (it->second.listener) {
350
+ env->DeleteGlobalRef(it->second.listener);
351
+ }
352
+ callbacks.erase(it);
353
+ }
354
+ }
355
+
356
+ // Remove player (shared_ptr may still be held by in-flight operations, but that's OK)
357
+ {
358
+ std::lock_guard<std::mutex> lock(playersMutex);
359
+ players.erase(playerId);
360
+ }
361
+
362
+ // Clean up stopped set entry
363
+ {
364
+ std::lock_guard<std::mutex> slock(stoppedMutex);
365
+ stoppedPlayers.erase(playerId);
366
+ }
367
+
368
+ LOGI("Destroyed player %lld", playerId);
369
+ }
370
+
371
+ //==============================================================================
372
+ // Callbacks
373
+ //==============================================================================
374
+
375
+ JNIEXPORT void JNICALL
376
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeSetListener(
377
+ JNIEnv* env,
378
+ jclass clazz,
379
+ jlong playerId,
380
+ jobject listener
381
+ ) {
382
+ auto player = findPlayer(playerId);
383
+ if (!player) return;
384
+
385
+ CallbackRefs refs;
386
+ env->GetJavaVM(&refs.jvm);
387
+
388
+ if (listener) {
389
+ refs.listener = env->NewGlobalRef(listener);
390
+ if (!refs.listener) {
391
+ LOGE("Failed to create global ref for listener");
392
+ return;
393
+ }
394
+
395
+ jclass listenerClass = env->GetObjectClass(listener);
396
+ if (!listenerClass) {
397
+ LOGE("Failed to get listener class");
398
+ env->DeleteGlobalRef(refs.listener);
399
+ return;
400
+ }
401
+
402
+ refs.onLoad = env->GetMethodID(listenerClass, "onLoad", "(III)V");
403
+ refs.onFrame = env->GetMethodID(listenerClass, "onFrame", "(ILjava/nio/ByteBuffer;Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;II)V");
404
+ refs.onPlay = env->GetMethodID(listenerClass, "onPlay", "()V");
405
+ refs.onPause = env->GetMethodID(listenerClass, "onPause", "()V");
406
+ refs.onStop = env->GetMethodID(listenerClass, "onStop", "()V");
407
+ refs.onLoop = env->GetMethodID(listenerClass, "onLoop", "()V");
408
+ refs.onEnd = env->GetMethodID(listenerClass, "onEnd", "()V");
409
+ refs.onError = env->GetMethodID(listenerClass, "onError", "(Ljava/lang/String;Ljava/lang/String;)V");
410
+ refs.onPreloadComplete = env->GetMethodID(listenerClass, "onPreloadComplete", "(ID)V");
411
+ }
412
+
413
+ {
414
+ std::lock_guard<std::mutex> clock(callbacksMutex);
415
+ // Remove old listener if exists
416
+ auto oldIt = callbacks.find(playerId);
417
+ if (oldIt != callbacks.end() && oldIt->second.listener) {
418
+ env->DeleteGlobalRef(oldIt->second.listener);
419
+ }
420
+ callbacks[playerId] = refs;
421
+ }
422
+
423
+ // Set up C++ callbacks
424
+ player->setFrameCallback([playerId](std::shared_ptr<webm::YUVAFrame> frame) {
425
+ // Check if player was destroyed
426
+ {
427
+ std::lock_guard<std::mutex> dlock(destroyedMutex);
428
+ if (destroyedPlayers.count(playerId)) return;
429
+ }
430
+
431
+ // Check if player was stopped (soft-release)
432
+ {
433
+ std::lock_guard<std::mutex> slock(stoppedMutex);
434
+ if (stoppedPlayers.count(playerId)) return;
435
+ }
436
+
437
+ std::lock_guard<std::mutex> lock(callbacksMutex);
438
+ auto it = callbacks.find(playerId);
439
+ if (it == callbacks.end() || !it->second.listener) return;
440
+
441
+ JNIEnv* env = getJNIEnv(it->second.jvm);
442
+ if (!env) return;
443
+
444
+ // Validate frame data before creating ByteBuffers
445
+ if (!frame || !frame->yPlane || !frame->uPlane || !frame->vPlane || !frame->aPlane ||
446
+ frame->ySize == 0 || frame->uSize == 0 || frame->vSize == 0 || frame->aSize == 0) {
447
+ LOGE("Invalid frame data in callback for player %lld", playerId);
448
+ return;
449
+ }
450
+
451
+ // Validate method ID before calling
452
+ if (!it->second.onFrame) {
453
+ LOGE("onFrame method ID is null for player %lld", playerId);
454
+ return;
455
+ }
456
+
457
+ // Create direct ByteBuffers pointing to frame data (zero-copy!)
458
+ jobject yBuffer = env->NewDirectByteBuffer(frame->yPlane, frame->ySize);
459
+ jobject uBuffer = env->NewDirectByteBuffer(frame->uPlane, frame->uSize);
460
+ jobject vBuffer = env->NewDirectByteBuffer(frame->vPlane, frame->vSize);
461
+ jobject aBuffer = env->NewDirectByteBuffer(frame->aPlane, frame->aSize);
462
+
463
+ // Check if ByteBuffer creation succeeded
464
+ if (!yBuffer || !uBuffer || !vBuffer || !aBuffer) {
465
+ LOGE("Failed to create ByteBuffers for player %lld", playerId);
466
+ if (yBuffer) env->DeleteLocalRef(yBuffer);
467
+ if (uBuffer) env->DeleteLocalRef(uBuffer);
468
+ if (vBuffer) env->DeleteLocalRef(vBuffer);
469
+ if (aBuffer) env->DeleteLocalRef(aBuffer);
470
+ return;
471
+ }
472
+
473
+ env->CallVoidMethod(
474
+ it->second.listener,
475
+ it->second.onFrame,
476
+ frame->frameIndex,
477
+ yBuffer, uBuffer, vBuffer, aBuffer,
478
+ frame->width, frame->height
479
+ );
480
+
481
+ // Check for Java exception
482
+ if (env->ExceptionCheck()) {
483
+ LOGE("Java exception occurred in onFrame for player %lld", playerId);
484
+ env->ExceptionDescribe();
485
+ env->ExceptionClear();
486
+ }
487
+
488
+ env->DeleteLocalRef(yBuffer);
489
+ env->DeleteLocalRef(uBuffer);
490
+ env->DeleteLocalRef(vBuffer);
491
+ env->DeleteLocalRef(aBuffer);
492
+ });
493
+
494
+ std::weak_ptr<webm::WebMAlphaPlayer> weakPlayer = player;
495
+ player->setEventCallback([playerId, weakPlayer](const std::string& event, const std::string& data) {
496
+ // Check if player was destroyed
497
+ {
498
+ std::lock_guard<std::mutex> dlock(destroyedMutex);
499
+ if (destroyedPlayers.count(playerId)) return;
500
+ }
501
+
502
+ // Check if player was stopped (soft-release) - but allow stop event through
503
+ if (event != "stop") {
504
+ std::lock_guard<std::mutex> slock(stoppedMutex);
505
+ if (stoppedPlayers.count(playerId)) return;
506
+ }
507
+
508
+ std::lock_guard<std::mutex> lock(callbacksMutex);
509
+ auto it = callbacks.find(playerId);
510
+ if (it == callbacks.end() || !it->second.listener) return;
511
+
512
+ JNIEnv* env = getJNIEnv(it->second.jvm);
513
+ if (!env) return;
514
+
515
+ if (event == "loaded") {
516
+ // Parse frame count from data
517
+ int frames = std::stoi(data.substr(0, data.find(' ')));
518
+ // Get width/height without holding playersMutex during Java call
519
+ // This prevents deadlock when main thread holds playersMutex and waits for decodeMutex_
520
+ int width = 0, height = 0;
521
+ {
522
+ std::lock_guard<std::mutex> plock(playersMutex);
523
+ auto playerIt = players.find(playerId);
524
+ if (playerIt != players.end()) {
525
+ width = playerIt->second->getWidth();
526
+ height = playerIt->second->getHeight();
527
+ } else {
528
+ return; // Player gone
529
+ }
530
+ }
531
+ // Now call Java without holding playersMutex
532
+ env->CallVoidMethod(it->second.listener, it->second.onLoad, frames, width, height);
533
+ } else if (event == "play") {
534
+ env->CallVoidMethod(it->second.listener, it->second.onPlay);
535
+ } else if (event == "pause") {
536
+ env->CallVoidMethod(it->second.listener, it->second.onPause);
537
+ } else if (event == "stop") {
538
+ env->CallVoidMethod(it->second.listener, it->second.onStop);
539
+ } else if (event == "loop") {
540
+ env->CallVoidMethod(it->second.listener, it->second.onLoop);
541
+ } else if (event == "ended") {
542
+ env->CallVoidMethod(it->second.listener, it->second.onEnd);
543
+ } else if (event == "error") {
544
+ auto erring = weakPlayer.lock();
545
+ const std::string code = erring ? erring->getLastErrorCode() : std::string();
546
+ jstring errStr = env->NewStringUTF(data.c_str());
547
+ jstring codeStr = code.empty() ? nullptr : env->NewStringUTF(code.c_str());
548
+ env->CallVoidMethod(it->second.listener, it->second.onError, errStr, codeStr);
549
+ env->DeleteLocalRef(errStr);
550
+ if (codeStr) env->DeleteLocalRef(codeStr);
551
+ } else if (event == "preload_complete") {
552
+ // "X frames in Yms". Parsed without std::stod, whose exception would abort the app.
553
+ int frames = std::atoi(data.c_str());
554
+ size_t in = data.find(" in ");
555
+ double ms = in != std::string::npos ? std::atof(data.c_str() + in + 4) : 0.0;
556
+ env->CallVoidMethod(it->second.listener, it->second.onPreloadComplete, frames, ms);
557
+ }
558
+ });
559
+
560
+ LOGI("Set listener for player %lld", playerId);
561
+ }
562
+
563
+ //==============================================================================
564
+ // Loading
565
+ //==============================================================================
566
+
567
+ JNIEXPORT jboolean JNICALL
568
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeLoadFromFile(
569
+ JNIEnv* env,
570
+ jclass clazz,
571
+ jlong playerId,
572
+ jstring path
573
+ ) {
574
+ // Check if already destroyed
575
+ {
576
+ std::lock_guard<std::mutex> dlock(destroyedMutex);
577
+ if (destroyedPlayers.count(playerId)) return JNI_FALSE;
578
+ }
579
+
580
+ // Remove from stopped set when loading new content (re-enabling callbacks)
581
+ {
582
+ std::lock_guard<std::mutex> slock(stoppedMutex);
583
+ stoppedPlayers.erase(playerId);
584
+ }
585
+
586
+ // Get shared_ptr to keep player alive during operation
587
+ std::shared_ptr<webm::WebMAlphaPlayer> player;
588
+ {
589
+ std::lock_guard<std::mutex> lock(playersMutex);
590
+ auto it = players.find(playerId);
591
+ if (it == players.end()) return JNI_FALSE;
592
+ player = it->second; // Increment refcount
593
+ }
594
+
595
+ const char* pathStr = env->GetStringUTFChars(path, nullptr);
596
+ std::string pathCopy(pathStr); // Copy before releasing
597
+ env->ReleaseStringUTFChars(path, pathStr);
598
+
599
+ // Check again if destroyed before expensive operation
600
+ {
601
+ std::lock_guard<std::mutex> dlock(destroyedMutex);
602
+ if (destroyedPlayers.count(playerId)) {
603
+ LOGI("LoadFromFile aborted - player %lld destroyed", playerId);
604
+ return JNI_FALSE;
605
+ }
606
+ }
607
+
608
+ bool result = player->loadFromFile(pathCopy);
609
+
610
+ LOGI("LoadFromFile %s: %s", pathCopy.c_str(), result ? "success" : "failed");
611
+ return result ? JNI_TRUE : JNI_FALSE;
612
+ }
613
+
614
+ JNIEXPORT jboolean JNICALL
615
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeLoadFromFd(
616
+ JNIEnv* env,
617
+ jclass clazz,
618
+ jlong playerId,
619
+ jint fd,
620
+ jlong offset,
621
+ jlong length
622
+ ) {
623
+ {
624
+ std::lock_guard<std::mutex> dlock(destroyedMutex);
625
+ if (destroyedPlayers.count(playerId)) return JNI_FALSE;
626
+ }
627
+
628
+ // Remove from stopped set when loading new content (re-enabling callbacks)
629
+ {
630
+ std::lock_guard<std::mutex> slock(stoppedMutex);
631
+ stoppedPlayers.erase(playerId);
632
+ }
633
+
634
+ std::shared_ptr<webm::WebMAlphaPlayer> player;
635
+ {
636
+ std::lock_guard<std::mutex> lock(playersMutex);
637
+ auto it = players.find(playerId);
638
+ if (it == players.end()) return JNI_FALSE;
639
+ player = it->second;
640
+ }
641
+
642
+ {
643
+ std::lock_guard<std::mutex> dlock(destroyedMutex);
644
+ if (destroyedPlayers.count(playerId)) {
645
+ LOGI("LoadFromFd aborted - player %lld destroyed", playerId);
646
+ return JNI_FALSE;
647
+ }
648
+ }
649
+
650
+ bool result = player->loadFromFileRange(fd, offset, length);
651
+ return result ? JNI_TRUE : JNI_FALSE;
652
+ }
653
+
654
+ JNIEXPORT jboolean JNICALL
655
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeLoadFromReader(
656
+ JNIEnv* env,
657
+ jclass clazz,
658
+ jlong playerId,
659
+ jlong reader
660
+ ) {
661
+ {
662
+ std::lock_guard<std::mutex> dlock(destroyedMutex);
663
+ if (destroyedPlayers.count(playerId)) return JNI_FALSE;
664
+ }
665
+
666
+ {
667
+ std::lock_guard<std::mutex> slock(stoppedMutex);
668
+ stoppedPlayers.erase(playerId);
669
+ }
670
+
671
+ std::shared_ptr<webm::WebMAlphaPlayer> player = findPlayer(playerId);
672
+ if (!player) return JNI_FALSE;
673
+ return player->loadFromReader(readerOf(reader)) ? JNI_TRUE : JNI_FALSE;
674
+ }
675
+
676
+ //==============================================================================
677
+ // Range readers (SourceReader)
678
+ //==============================================================================
679
+
680
+ JNIEXPORT jlong JNICALL
681
+ Java_dev_mediacache_alphavideo_RangeSourceLink_nativeUsablePlugin(JNIEnv* env, jclass clazz, jlong address) {
682
+ std::string why;
683
+ const RangeSourcePlugin* plugin = webm::usablePlugin(reinterpret_cast<const RangeSourcePlugin*>(address), why);
684
+ if (!plugin && !why.empty()) LOGE("Not using range-source: %s", why.c_str());
685
+ return reinterpret_cast<jlong>(plugin);
686
+ }
687
+
688
+ JNIEXPORT jlong JNICALL
689
+ Java_dev_mediacache_alphavideo_SourceReader_nativeOpen(
690
+ JNIEnv* env,
691
+ jclass clazz,
692
+ jlong plugin,
693
+ jobject upstream,
694
+ jint storage,
695
+ jstring storeDir,
696
+ jstring key,
697
+ jlong maxStorageBytes,
698
+ jstring downloadDir,
699
+ jobjectArray errorOut
700
+ ) {
701
+ // SourceReader.Storage ordinals
702
+ enum { kDisk = 0, kMemory = 1 };
703
+ std::string error;
704
+ std::shared_ptr<webm::SourceReader> reader;
705
+ std::unique_ptr<JavaUpstream> javaUpstream = JavaUpstream::create(env, upstream);
706
+ const auto* table = reinterpret_cast<const RangeSourcePlugin*>(plugin);
707
+ if (!javaUpstream) {
708
+ error = "Cannot call the data source";
709
+ } else if (table) {
710
+ std::shared_ptr<webm_rs::Store> store;
711
+ if (storage == kDisk) {
712
+ store = webm_rs::Store::disk(table, stringFromJava(env, storeDir), maxStorageBytes);
713
+ } else if (storage == kMemory) {
714
+ store = webm_rs::Store::memory(table, stringFromJava(env, storeDir), maxStorageBytes);
715
+ }
716
+ reader = webm_rs::RangeReader::open(table, std::move(javaUpstream), store, stringFromJava(env, key),
717
+ webm_rs::ReaderOptions(), error);
718
+ } else {
719
+ reader = webm_rs::DownloadReader::open(std::move(javaUpstream), stringFromJava(env, downloadDir), error);
720
+ }
721
+ if (!reader) {
722
+ jstring message = env->NewStringUTF(error.c_str());
723
+ env->SetObjectArrayElement(errorOut, 0, message);
724
+ env->DeleteLocalRef(message);
725
+ return 0;
726
+ }
727
+ return reinterpret_cast<jlong>(new ReaderRef(std::move(reader)));
728
+ }
729
+
730
+ JNIEXPORT jlong JNICALL
731
+ Java_dev_mediacache_alphavideo_SourceReader_nativeRetain(JNIEnv* env, jclass clazz, jlong reference) {
732
+ return reinterpret_cast<jlong>(new ReaderRef(readerOf(reference)));
733
+ }
734
+
735
+ JNIEXPORT void JNICALL
736
+ Java_dev_mediacache_alphavideo_SourceReader_nativeRelease(JNIEnv* env, jclass clazz, jlong reference) {
737
+ delete reinterpret_cast<ReaderRef*>(reference);
738
+ }
739
+
740
+ JNIEXPORT jstring JNICALL
741
+ Java_dev_mediacache_alphavideo_SourceReader_nativeFetchAll(JNIEnv* env, jclass clazz, jlong reference) {
742
+ const ReaderRef& reader = readerOf(reference);
743
+ if (reader->fetchAll()) return nullptr;
744
+ std::string error = reader->lastError();
745
+ return env->NewStringUTF(error.empty() ? "Reading the video failed" : error.c_str());
746
+ }
747
+
748
+ JNIEXPORT jboolean JNICALL
749
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeLoadFromBytes(
750
+ JNIEnv* env,
751
+ jclass clazz,
752
+ jlong playerId,
753
+ jbyteArray data
754
+ ) {
755
+ // Check if already destroyed
756
+ {
757
+ std::lock_guard<std::mutex> dlock(destroyedMutex);
758
+ if (destroyedPlayers.count(playerId)) return JNI_FALSE;
759
+ }
760
+
761
+ // Remove from stopped set when loading new content (re-enabling callbacks)
762
+ {
763
+ std::lock_guard<std::mutex> slock(stoppedMutex);
764
+ stoppedPlayers.erase(playerId);
765
+ }
766
+
767
+ // Get shared_ptr to keep player alive during operation
768
+ std::shared_ptr<webm::WebMAlphaPlayer> player;
769
+ {
770
+ std::lock_guard<std::mutex> lock(playersMutex);
771
+ auto it = players.find(playerId);
772
+ if (it == players.end()) return JNI_FALSE;
773
+ player = it->second; // Increment refcount
774
+ }
775
+
776
+ jsize length = env->GetArrayLength(data);
777
+ jbyte* bytes = env->GetByteArrayElements(data, nullptr);
778
+
779
+ // Copy data since we can't hold JNI array reference across potential GC
780
+ std::vector<uint8_t> dataCopy(reinterpret_cast<const uint8_t*>(bytes),
781
+ reinterpret_cast<const uint8_t*>(bytes) + length);
782
+ env->ReleaseByteArrayElements(data, bytes, JNI_ABORT);
783
+
784
+ // Check again if destroyed before expensive operation
785
+ {
786
+ std::lock_guard<std::mutex> dlock(destroyedMutex);
787
+ if (destroyedPlayers.count(playerId)) {
788
+ LOGI("LoadFromBytes aborted - player %lld destroyed", playerId);
789
+ return JNI_FALSE;
790
+ }
791
+ }
792
+
793
+ bool result = player->loadFromMemory(dataCopy.data(), dataCopy.size());
794
+
795
+ LOGI("LoadFromBytes %d bytes: %s", (int)length, result ? "success" : "failed");
796
+ return result ? JNI_TRUE : JNI_FALSE;
797
+ }
798
+
799
+ JNIEXPORT void JNICALL
800
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeUnload(
801
+ JNIEnv* env,
802
+ jclass clazz,
803
+ jlong playerId
804
+ ) {
805
+ // Get shared_ptr WITHOUT holding playersMutex during unload()
806
+ // unload() can block waiting for decodeMutex_, which could cause deadlock
807
+ // if another thread's event callback needs playersMutex
808
+ std::shared_ptr<webm::WebMAlphaPlayer> player;
809
+ {
810
+ std::lock_guard<std::mutex> lock(playersMutex);
811
+ auto it = players.find(playerId);
812
+ if (it == players.end()) return;
813
+ player = it->second; // Increment refcount
814
+ }
815
+ // Now call unload() without holding playersMutex
816
+ player->unload();
817
+ }
818
+
819
+ //==============================================================================
820
+ // Playback Control
821
+ //==============================================================================
822
+
823
+ JNIEXPORT void JNICALL
824
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativePlay(JNIEnv* env, jclass clazz, jlong playerId) {
825
+ // Remove from stopped set when playing resumes
826
+ {
827
+ std::lock_guard<std::mutex> slock(stoppedMutex);
828
+ stoppedPlayers.erase(playerId);
829
+ }
830
+
831
+ if (auto player = findPlayer(playerId)) player->play();
832
+ }
833
+
834
+ JNIEXPORT void JNICALL
835
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativePause(JNIEnv* env, jclass clazz, jlong playerId) {
836
+ if (auto player = findPlayer(playerId)) player->pause();
837
+ }
838
+
839
+ JNIEXPORT void JNICALL
840
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeStop(JNIEnv* env, jclass clazz, jlong playerId) {
841
+ // Mark as stopped FIRST to block callbacks immediately
842
+ {
843
+ std::lock_guard<std::mutex> slock(stoppedMutex);
844
+ stoppedPlayers.insert(playerId);
845
+ }
846
+
847
+ // Get shared_ptr WITHOUT holding playersMutex during stop()
848
+ // stop() joins the playback thread which could take time
849
+ std::shared_ptr<webm::WebMAlphaPlayer> player;
850
+ {
851
+ std::lock_guard<std::mutex> lock(playersMutex);
852
+ auto it = players.find(playerId);
853
+ if (it == players.end()) return;
854
+ player = it->second; // Increment refcount
855
+ }
856
+ // Now call stop() without holding playersMutex
857
+ player->stop();
858
+
859
+ LOGI("Stopped player %lld (callbacks blocked)", playerId);
860
+ }
861
+
862
+ JNIEXPORT void JNICALL
863
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeSeekToFrame(
864
+ JNIEnv* env, jclass clazz, jlong playerId, jint frameIndex
865
+ ) {
866
+ if (auto player = findPlayer(playerId)) player->seekToFrame(frameIndex);
867
+ }
868
+
869
+ JNIEXPORT void JNICALL
870
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeSetPlaybackSpeed(
871
+ JNIEnv* env, jclass clazz, jlong playerId, jdouble speed
872
+ ) {
873
+ std::lock_guard<std::mutex> lock(playersMutex);
874
+ auto it = players.find(playerId);
875
+ if (it != players.end()) it->second->setPlaybackSpeed(speed);
876
+ }
877
+
878
+ JNIEXPORT void JNICALL
879
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeSetVerbose(
880
+ JNIEnv* env, jclass clazz, jlong playerId, jboolean verbose
881
+ ) {
882
+ std::lock_guard<std::mutex> lock(playersMutex);
883
+ auto it = players.find(playerId);
884
+ if (it != players.end()) it->second->setVerbose(verbose == JNI_TRUE);
885
+ }
886
+
887
+ JNIEXPORT void JNICALL
888
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeSeekToTime(
889
+ JNIEnv* env, jclass clazz, jlong playerId, jdouble seconds
890
+ ) {
891
+ if (auto player = findPlayer(playerId)) player->seekTo(seconds);
892
+ }
893
+
894
+ //==============================================================================
895
+ // State Queries
896
+ //==============================================================================
897
+
898
+ JNIEXPORT jboolean JNICALL
899
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeIsLoaded(JNIEnv* env, jclass clazz, jlong playerId) {
900
+ std::lock_guard<std::mutex> lock(playersMutex);
901
+ auto it = players.find(playerId);
902
+ return (it != players.end() && it->second->isLoaded()) ? JNI_TRUE : JNI_FALSE;
903
+ }
904
+
905
+ JNIEXPORT jboolean JNICALL
906
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeIsPlaying(JNIEnv* env, jclass clazz, jlong playerId) {
907
+ std::lock_guard<std::mutex> lock(playersMutex);
908
+ auto it = players.find(playerId);
909
+ return (it != players.end() && it->second->isPlaying()) ? JNI_TRUE : JNI_FALSE;
910
+ }
911
+
912
+ JNIEXPORT jint JNICALL
913
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeGetCurrentFrame(JNIEnv* env, jclass clazz, jlong playerId) {
914
+ std::lock_guard<std::mutex> lock(playersMutex);
915
+ auto it = players.find(playerId);
916
+ return (it != players.end()) ? it->second->getCurrentFrame() : 0;
917
+ }
918
+
919
+ JNIEXPORT jint JNICALL
920
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeGetTotalFrames(JNIEnv* env, jclass clazz, jlong playerId) {
921
+ std::lock_guard<std::mutex> lock(playersMutex);
922
+ auto it = players.find(playerId);
923
+ return (it != players.end()) ? it->second->getTotalFrames() : 0;
924
+ }
925
+
926
+ JNIEXPORT jint JNICALL
927
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeGetWidth(JNIEnv* env, jclass clazz, jlong playerId) {
928
+ std::lock_guard<std::mutex> lock(playersMutex);
929
+ auto it = players.find(playerId);
930
+ return (it != players.end()) ? it->second->getWidth() : 0;
931
+ }
932
+
933
+ JNIEXPORT jint JNICALL
934
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeGetHeight(JNIEnv* env, jclass clazz, jlong playerId) {
935
+ std::lock_guard<std::mutex> lock(playersMutex);
936
+ auto it = players.find(playerId);
937
+ return (it != players.end()) ? it->second->getHeight() : 0;
938
+ }
939
+
940
+ JNIEXPORT jfloatArray JNICALL
941
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeGetYuvToRgb(JNIEnv* env, jclass clazz, jlong playerId) {
942
+ webm::YuvToRgb m;
943
+ {
944
+ std::lock_guard<std::mutex> lock(playersMutex);
945
+ auto it = players.find(playerId);
946
+ if (it == players.end()) return nullptr;
947
+ m = it->second->getYuvToRgb();
948
+ }
949
+ jfloatArray out = env->NewFloatArray(16);
950
+ if (out) env->SetFloatArrayRegion(out, 0, 16, m.m);
951
+ return out;
952
+ }
953
+
954
+ JNIEXPORT jfloatArray JNICALL
955
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeYuvToRgbFor(
956
+ JNIEnv* env, jclass clazz, jint colorRange, jint colorSpace, jint width, jint height) {
957
+ webm::YuvToRgb m = webm::yuvToRgbFor(colorRange, colorSpace, width, height);
958
+ jfloatArray out = env->NewFloatArray(16);
959
+ if (out) env->SetFloatArrayRegion(out, 0, 16, m.m);
960
+ return out;
961
+ }
962
+
963
+ JNIEXPORT jdouble JNICALL
964
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeGetDuration(JNIEnv* env, jclass clazz, jlong playerId) {
965
+ std::lock_guard<std::mutex> lock(playersMutex);
966
+ auto it = players.find(playerId);
967
+ return (it != players.end()) ? it->second->getDuration() : 0.0;
968
+ }
969
+
970
+ JNIEXPORT jdouble JNICALL
971
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeGetFrameRate(JNIEnv* env, jclass clazz, jlong playerId) {
972
+ std::lock_guard<std::mutex> lock(playersMutex);
973
+ auto it = players.find(playerId);
974
+ return (it != players.end()) ? it->second->getFrameRate() : 0.0;
975
+ }
976
+
977
+ JNIEXPORT jstring JNICALL
978
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeGetDecoderName(JNIEnv* env, jclass clazz, jlong playerId) {
979
+ std::string name;
980
+ {
981
+ std::lock_guard<std::mutex> lock(playersMutex);
982
+ auto it = players.find(playerId);
983
+ if (it == players.end()) return nullptr;
984
+ name = it->second->getDecoderName();
985
+ }
986
+ return env->NewStringUTF(name.c_str());
987
+ }
988
+
989
+ //==============================================================================
990
+ // Direct Frame Access
991
+ //==============================================================================
992
+
993
+ JNIEXPORT jobject JNICALL
994
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeGetFrame(
995
+ JNIEnv* env, jclass clazz, jlong playerId, jint frameIndex
996
+ ) {
997
+ // Check if destroyed first
998
+ {
999
+ std::lock_guard<std::mutex> dlock(destroyedMutex);
1000
+ if (destroyedPlayers.count(playerId)) return nullptr;
1001
+ }
1002
+
1003
+ auto player = findPlayer(playerId);
1004
+ if (!player) return nullptr;
1005
+ std::shared_ptr<webm::YUVAFrame> frame = player->getFrame(frameIndex);
1006
+ if (!frame) return nullptr;
1007
+
1008
+ // Now we have a shared_ptr keeping the frame alive.
1009
+ // CRITICAL: Create Java-owned byte arrays and copy data!
1010
+ // The C++ frame could be freed when the cache is cleared or player destroyed.
1011
+
1012
+ // Create Java byte arrays and copy data
1013
+ jbyteArray yArray = env->NewByteArray(frame->ySize);
1014
+ jbyteArray uArray = env->NewByteArray(frame->uSize);
1015
+ jbyteArray vArray = env->NewByteArray(frame->vSize);
1016
+ jbyteArray aArray = env->NewByteArray(frame->aSize);
1017
+
1018
+ if (!yArray || !uArray || !vArray || !aArray) {
1019
+ LOGE("Failed to allocate byte arrays for frame %d", frameIndex);
1020
+ return nullptr;
1021
+ }
1022
+
1023
+ // Copy data from C++ frame to Java arrays
1024
+ env->SetByteArrayRegion(yArray, 0, frame->ySize, reinterpret_cast<const jbyte*>(frame->yPlane));
1025
+ env->SetByteArrayRegion(uArray, 0, frame->uSize, reinterpret_cast<const jbyte*>(frame->uPlane));
1026
+ env->SetByteArrayRegion(vArray, 0, frame->vSize, reinterpret_cast<const jbyte*>(frame->vPlane));
1027
+ env->SetByteArrayRegion(aArray, 0, frame->aSize, reinterpret_cast<const jbyte*>(frame->aPlane));
1028
+
1029
+ // Wrap byte arrays in ByteBuffers
1030
+ jclass byteBufferClass = env->FindClass("java/nio/ByteBuffer");
1031
+ jmethodID wrapMethod = env->GetStaticMethodID(byteBufferClass, "wrap", "([B)Ljava/nio/ByteBuffer;");
1032
+
1033
+ jobject yBuffer = env->CallStaticObjectMethod(byteBufferClass, wrapMethod, yArray);
1034
+ jobject uBuffer = env->CallStaticObjectMethod(byteBufferClass, wrapMethod, uArray);
1035
+ jobject vBuffer = env->CallStaticObjectMethod(byteBufferClass, wrapMethod, vArray);
1036
+ jobject aBuffer = env->CallStaticObjectMethod(byteBufferClass, wrapMethod, aArray);
1037
+
1038
+ // Find the WebMFrame class
1039
+ jclass frameClass = env->FindClass("dev/mediacache/alphavideo/WebMFrame");
1040
+ if (!frameClass) return nullptr;
1041
+
1042
+ // Create instance with constructor
1043
+ jmethodID constructor = env->GetMethodID(frameClass, "<init>",
1044
+ "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;IIID)V");
1045
+
1046
+ jobject frameObj = env->NewObject(frameClass, constructor,
1047
+ yBuffer, uBuffer, vBuffer, aBuffer,
1048
+ frame->width, frame->height, frame->frameIndex,
1049
+ frame->getDurationMs() / 1000.0);
1050
+
1051
+ // Clean up local refs (byte arrays are now referenced by ByteBuffers)
1052
+ env->DeleteLocalRef(yBuffer);
1053
+ env->DeleteLocalRef(uBuffer);
1054
+ env->DeleteLocalRef(vBuffer);
1055
+ env->DeleteLocalRef(aBuffer);
1056
+ env->DeleteLocalRef(yArray);
1057
+ env->DeleteLocalRef(uArray);
1058
+ env->DeleteLocalRef(vArray);
1059
+ env->DeleteLocalRef(aArray);
1060
+ env->DeleteLocalRef(byteBufferClass);
1061
+
1062
+ return frameObj;
1063
+ }
1064
+
1065
+ //==============================================================================
1066
+ // Statistics
1067
+ //==============================================================================
1068
+
1069
+ JNIEXPORT jlongArray JNICALL
1070
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeGetStats(JNIEnv* env, jclass clazz, jlong playerId) {
1071
+ std::lock_guard<std::mutex> lock(playersMutex);
1072
+ auto it = players.find(playerId);
1073
+ if (it == players.end()) return nullptr;
1074
+
1075
+ auto stats = it->second->getStats();
1076
+
1077
+ // Return as long array: [framesDecoded, cacheHits, cacheMisses, decodeTimeUs]
1078
+ jlongArray result = env->NewLongArray(4);
1079
+ jlong values[] = {
1080
+ stats.framesDecoded,
1081
+ stats.cacheHits,
1082
+ stats.cacheMisses,
1083
+ stats.decodeTimeUs
1084
+ };
1085
+ env->SetLongArrayRegion(result, 0, 4, values);
1086
+
1087
+ return result;
1088
+ }
1089
+
1090
+ JNIEXPORT void JNICALL
1091
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeResetStats(JNIEnv* env, jclass clazz, jlong playerId) {
1092
+ std::lock_guard<std::mutex> lock(playersMutex);
1093
+ auto it = players.find(playerId);
1094
+ if (it != players.end()) {
1095
+ it->second->resetStats();
1096
+ }
1097
+ }
1098
+
1099
+ //==============================================================================
1100
+ // Decoder library versions
1101
+ //==============================================================================
1102
+
1103
+ JNIEXPORT jstring JNICALL
1104
+ Java_dev_mediacache_alphavideo_WebMAlphaPlayerNative_nativeGetLibraryVersions(JNIEnv* env, jclass clazz) {
1105
+ std::string versions = webm::WebMAlphaPlayer::getLibraryVersions();
1106
+ return env->NewStringUTF(versions.c_str());
1107
+ }
1108
+
1109
+ //==============================================================================
1110
+ // Software decoders (codec modules)
1111
+ //==============================================================================
1112
+
1113
+ // Null if registered, else why not
1114
+ JNIEXPORT jstring JNICALL
1115
+ Java_dev_mediacache_alphavideo_AlphaVideoCodecs_nativeRegister(JNIEnv* env, jclass clazz, jlong pluginList) {
1116
+ if (!pluginList) return env->NewStringUTF("A codec module gave no plugin list");
1117
+ std::string error;
1118
+ if (webm::registerDecoderPlugins(reinterpret_cast<AlphaVideoDecoderPluginList>(pluginList), &error)) {
1119
+ return nullptr;
1120
+ }
1121
+ return env->NewStringUTF(error.c_str());
1122
+ }
1123
+
1124
+ JNIEXPORT jobjectArray JNICALL
1125
+ Java_dev_mediacache_alphavideo_AlphaVideoCodecs_nativeSoftwareDecoders(JNIEnv* env, jclass clazz) {
1126
+ const std::vector<std::string> decoders = webm::registeredDecoders();
1127
+ jobjectArray result = env->NewObjectArray(static_cast<jsize>(decoders.size()),
1128
+ env->FindClass("java/lang/String"), nullptr);
1129
+ for (size_t i = 0; i < decoders.size(); i++) {
1130
+ jstring decoder = env->NewStringUTF(decoders[i].c_str());
1131
+ env->SetObjectArrayElement(result, static_cast<jsize>(i), decoder);
1132
+ env->DeleteLocalRef(decoder);
1133
+ }
1134
+ return result;
1135
+ }
1136
+
1137
+ //==============================================================================
1138
+ // Dual-Stream Demuxer for MediaCodec Hardware Decoding
1139
+ // Extracts raw VP8/VP9/AV1 packets from dual-stream WebM files
1140
+ //==============================================================================
1141
+
1142
+ namespace {
1143
+
1144
+ // A demuxer, which owns the source it reads. The demuxer is freed before the abort flag its
1145
+ // download source polls.
1146
+ struct DualStreamDemuxer {
1147
+ // Set on destroy so a read waiting on a download returns
1148
+ webm::AbortFlag aborted{0};
1149
+ std::unique_ptr<webm::AlphaDemuxer> demuxer;
1150
+ };
1151
+
1152
+ jintArray intArray(JNIEnv* env, const std::vector<jint>& values) {
1153
+ jintArray result = env->NewIntArray(static_cast<jsize>(values.size()));
1154
+ if (result) env->SetIntArrayRegion(result, 0, static_cast<jsize>(values.size()), values.data());
1155
+ return result;
1156
+ }
1157
+
1158
+ // The colour and alpha bitstreams of source, for MediaCodec: each one's codec (0 VP8, 1 VP9, 2 AV1) and
1159
+ // profile, in pairs (AlphaDemuxer::colorAlphaTracks). Null unless there are two.
1160
+ jintArray colorAlphaTracks(JNIEnv* env, std::unique_ptr<webm::ByteSource> source) {
1161
+ std::vector<int> tracks = webm::AlphaDemuxer::colorAlphaTracks(std::move(source));
1162
+ if (tracks.size() < 4) return nullptr;
1163
+ return intArray(env, std::vector<jint>(tracks.begin(), tracks.end()));
1164
+ }
1165
+
1166
+ // Shared so that destroying a demuxer while its demux thread waits on a download can't free it
1167
+ // under that read; the read's own reference frees it once it returns.
1168
+ std::unordered_map<jlong, std::shared_ptr<DualStreamDemuxer>> demuxers;
1169
+ std::mutex demuxersMutex;
1170
+ jlong nextDemuxerId = 1;
1171
+
1172
+ std::shared_ptr<DualStreamDemuxer> findDemuxer(jlong demuxerId) {
1173
+ std::lock_guard<std::mutex> lock(demuxersMutex);
1174
+ auto it = demuxers.find(demuxerId);
1175
+ return it == demuxers.end() ? nullptr : it->second;
1176
+ }
1177
+
1178
+ // Opens the demuxer over source and registers it. Returns its id, or 0.
1179
+ jlong registerDemuxer(std::shared_ptr<DualStreamDemuxer> demuxer, std::unique_ptr<webm::ByteSource> source) {
1180
+ if (!source) return 0;
1181
+ demuxer->demuxer = webm::AlphaDemuxer::open(std::move(source));
1182
+ if (!demuxer->demuxer) {
1183
+ LOGE("[Buffered] No VP8/VP9/AV1 video stream found, or the file can't be opened");
1184
+ return 0;
1185
+ }
1186
+ const webm::AlphaDemuxer::Info& info = demuxer->demuxer->info();
1187
+ LOGI("[Buffered] Demuxer created: %dx%d, %d frames, %.2f fps, %d streams, codec=%d",
1188
+ info.width, info.height, info.totalFrames, info.frameRate, info.hasAlpha ? 2 : 1, (int)info.color.codec);
1189
+
1190
+ std::lock_guard<std::mutex> lock(demuxersMutex);
1191
+ jlong id = nextDemuxerId++;
1192
+ demuxers[id] = std::move(demuxer);
1193
+ return id;
1194
+ }
1195
+
1196
+ } // anonymous namespace
1197
+
1198
+ JNIEXPORT jintArray JNICALL
1199
+ Java_dev_mediacache_alphavideo_BufferedDualStreamPlayer_nativeColorAlphaTracks(
1200
+ JNIEnv* env,
1201
+ jclass clazz,
1202
+ jint fd,
1203
+ jlong offset,
1204
+ jlong length
1205
+ ) {
1206
+ std::unique_ptr<webm::ByteSource> source = webm::openFileRange(fd, offset, length);
1207
+ if (!source) {
1208
+ LOGE("Failed to open fd %d range (offset %lld, length %lld)", fd, (long long)offset, (long long)length);
1209
+ return nullptr;
1210
+ }
1211
+ return colorAlphaTracks(env, std::move(source));
1212
+ }
1213
+
1214
+ JNIEXPORT jintArray JNICALL
1215
+ Java_dev_mediacache_alphavideo_BufferedDualStreamPlayer_nativeColorAlphaTracksReader(
1216
+ JNIEnv* env,
1217
+ jclass clazz,
1218
+ jlong reader
1219
+ ) {
1220
+ return colorAlphaTracks(env, webm::openReader(readerOf(reader), nullptr));
1221
+ }
1222
+
1223
+ // ============================================================================
1224
+ // BufferedDualStreamPlayer JNI bindings (FBO ring buffer player)
1225
+ // ============================================================================
1226
+
1227
+ JNIEXPORT jlong JNICALL
1228
+ Java_dev_mediacache_alphavideo_BufferedDualStreamPlayer_nativeCreateDemuxer(
1229
+ JNIEnv* env,
1230
+ jclass clazz,
1231
+ jint fd,
1232
+ jlong offset,
1233
+ jlong length
1234
+ ) {
1235
+ auto demuxer = std::make_shared<DualStreamDemuxer>();
1236
+ std::unique_ptr<webm::ByteSource> source = webm::openFileRange(fd, offset, length);
1237
+ return registerDemuxer(std::move(demuxer), std::move(source));
1238
+ }
1239
+
1240
+ JNIEXPORT jlong JNICALL
1241
+ Java_dev_mediacache_alphavideo_BufferedDualStreamPlayer_nativeCreateDemuxerFromReader(
1242
+ JNIEnv* env,
1243
+ jclass clazz,
1244
+ jlong reader
1245
+ ) {
1246
+ auto demuxer = std::make_shared<DualStreamDemuxer>();
1247
+ std::unique_ptr<webm::ByteSource> source = webm::openReader(readerOf(reader), &demuxer->aborted);
1248
+ return registerDemuxer(std::move(demuxer), std::move(source));
1249
+ }
1250
+
1251
+ JNIEXPORT void JNICALL
1252
+ Java_dev_mediacache_alphavideo_BufferedDualStreamPlayer_nativeDestroyDemuxer(
1253
+ JNIEnv* env,
1254
+ jclass clazz,
1255
+ jlong demuxerId
1256
+ ) {
1257
+ std::shared_ptr<DualStreamDemuxer> demuxer;
1258
+ {
1259
+ std::lock_guard<std::mutex> lock(demuxersMutex);
1260
+ auto it = demuxers.find(demuxerId);
1261
+ if (it == demuxers.end()) return;
1262
+ demuxer = std::move(it->second);
1263
+ demuxers.erase(it);
1264
+ }
1265
+ demuxer->aborted = true;
1266
+ LOGI("[Buffered] Demuxer %lld destroyed", demuxerId);
1267
+ }
1268
+
1269
+ JNIEXPORT jintArray JNICALL
1270
+ Java_dev_mediacache_alphavideo_BufferedDualStreamPlayer_nativeGetVideoInfo(
1271
+ JNIEnv* env,
1272
+ jclass clazz,
1273
+ jlong demuxerId
1274
+ ) {
1275
+ std::shared_ptr<DualStreamDemuxer> demuxer = findDemuxer(demuxerId);
1276
+ if (!demuxer) return nullptr;
1277
+ const webm::AlphaDemuxer::Info& info = demuxer->demuxer->info();
1278
+ // [width, height, totalFrames, frameRate*1000, streamCount, codec (0 VP8, 1 VP9, 2 AV1)]
1279
+ return intArray(env, {
1280
+ info.width,
1281
+ info.height,
1282
+ info.totalFrames,
1283
+ static_cast<jint>(info.frameRate * 1000),
1284
+ info.hasAlpha ? 2 : 1,
1285
+ static_cast<jint>(info.color.codec),
1286
+ });
1287
+ }
1288
+
1289
+ // [colour range, colour space, alpha range, alpha space] as H.273 numbers (YuvToRgb.h)
1290
+ JNIEXPORT jintArray JNICALL
1291
+ Java_dev_mediacache_alphavideo_BufferedDualStreamPlayer_nativeGetColorInfo(
1292
+ JNIEnv* env,
1293
+ jclass clazz,
1294
+ jlong demuxerId
1295
+ ) {
1296
+ std::shared_ptr<DualStreamDemuxer> demuxer = findDemuxer(demuxerId);
1297
+ if (!demuxer) return nullptr;
1298
+ const webm::AlphaDemuxer::Info& info = demuxer->demuxer->info();
1299
+ return intArray(env, {info.color.colorRange, info.color.colorSpace, info.alpha.colorRange, info.alpha.colorSpace});
1300
+ }
1301
+
1302
+ // [Integer streamIndex, ByteBuffer data (a copy), Integer size, Long pts, Integer frameIndex]
1303
+ static jobjectArray packetArray(JNIEnv* env, int streamIndex, const uint8_t* data, int size,
1304
+ long presentationTimeUs, int frameIndex) {
1305
+ jclass objectClass = env->FindClass("java/lang/Object");
1306
+ jobjectArray result = env->NewObjectArray(5, objectClass, nullptr);
1307
+
1308
+ // [0] Integer streamIndex
1309
+ jclass integerClass = env->FindClass("java/lang/Integer");
1310
+ jmethodID intValueOf = env->GetStaticMethodID(integerClass, "valueOf", "(I)Ljava/lang/Integer;");
1311
+ env->SetObjectArrayElement(result, 0, env->CallStaticObjectMethod(integerClass, intValueOf, streamIndex));
1312
+
1313
+ // [1] ByteBuffer data, copied since the packet will be freed
1314
+ jclass byteBufferClass = env->FindClass("java/nio/ByteBuffer");
1315
+ jmethodID allocate = env->GetStaticMethodID(byteBufferClass, "allocate", "(I)Ljava/nio/ByteBuffer;");
1316
+ jobject copyBuffer = env->CallStaticObjectMethod(byteBufferClass, allocate, size);
1317
+ if (size > 0) {
1318
+ jobject byteBuffer = env->NewDirectByteBuffer(const_cast<uint8_t*>(data), size);
1319
+ jmethodID putMethod = env->GetMethodID(byteBufferClass, "put", "(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer;");
1320
+ env->CallObjectMethod(copyBuffer, putMethod, byteBuffer);
1321
+ jmethodID rewindMethod = env->GetMethodID(byteBufferClass, "rewind", "()Ljava/nio/Buffer;");
1322
+ env->CallObjectMethod(copyBuffer, rewindMethod);
1323
+ }
1324
+ env->SetObjectArrayElement(result, 1, copyBuffer);
1325
+
1326
+ // [2] Integer size
1327
+ env->SetObjectArrayElement(result, 2, env->CallStaticObjectMethod(integerClass, intValueOf, size));
1328
+
1329
+ // [3] Long pts
1330
+ jclass longClass = env->FindClass("java/lang/Long");
1331
+ jmethodID longValueOf = env->GetStaticMethodID(longClass, "valueOf", "(J)Ljava/lang/Long;");
1332
+ env->SetObjectArrayElement(result, 3, env->CallStaticObjectMethod(longClass, longValueOf, presentationTimeUs));
1333
+
1334
+ // [4] Integer frameIndex
1335
+ env->SetObjectArrayElement(result, 4, env->CallStaticObjectMethod(integerClass, intValueOf, frameIndex));
1336
+ return result;
1337
+ }
1338
+
1339
+ JNIEXPORT jobjectArray JNICALL
1340
+ Java_dev_mediacache_alphavideo_BufferedDualStreamPlayer_nativeReadPacket(
1341
+ JNIEnv* env,
1342
+ jclass clazz,
1343
+ jlong demuxerId
1344
+ ) {
1345
+ // Returns Object[] with: [Integer streamIndex, ByteBuffer data, Integer size, Long pts, Integer frameIndex]
1346
+ std::shared_ptr<DualStreamDemuxer> demuxer = findDemuxer(demuxerId);
1347
+ if (!demuxer) return nullptr;
1348
+ webm::AlphaDemuxer::Packet packet;
1349
+ if (!demuxer->demuxer->readPacket(&packet)) return nullptr;
1350
+ return packetArray(env, packet.stream, packet.data, packet.size, static_cast<long>(packet.ptsUs), packet.frameIndex);
1351
+ }
1352
+
1353
+ JNIEXPORT void JNICALL
1354
+ Java_dev_mediacache_alphavideo_BufferedDualStreamPlayer_nativeSeekToStart(
1355
+ JNIEnv* env,
1356
+ jclass clazz,
1357
+ jlong demuxerId
1358
+ ) {
1359
+ // Not under demuxersMutex: on a download the seek reads the Cues, which may still be arriving
1360
+ std::shared_ptr<DualStreamDemuxer> demuxer = findDemuxer(demuxerId);
1361
+ if (!demuxer) return;
1362
+ demuxer->demuxer->seekToStart();
1363
+ LOGI("[Buffered] Demuxer %lld seeked to start", demuxerId);
1364
+ }
1365
+
1366
+ JNIEXPORT void JNICALL
1367
+ Java_dev_mediacache_alphavideo_BufferedDualStreamPlayer_nativeSeekToTime(
1368
+ JNIEnv* env,
1369
+ jclass clazz,
1370
+ jlong demuxerId,
1371
+ jlong timeUs
1372
+ ) {
1373
+ // To the keyframe at or before timeUs, in the same timebase as the packet PTS handed out.
1374
+ // Not under demuxersMutex, for the same reason as nativeSeekToStart.
1375
+ std::shared_ptr<DualStreamDemuxer> demuxer = findDemuxer(demuxerId);
1376
+ if (!demuxer) return;
1377
+ demuxer->demuxer->seekToTimeUs(timeUs);
1378
+ }
1379
+
1380
+ } // extern "C"