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,37 @@
1
+ package dev.mediacache.alphavideo;
2
+
3
+ import android.net.Uri;
4
+
5
+ import java.io.IOException;
6
+ import java.util.Map;
7
+
8
+ /**
9
+ * Where {@link SourceReader}'s bytes come from: the built-in {@link HttpFetcher}, or an app's
10
+ * data source through the range-source add-on ({@link AppDataSource}). The shape of range-source's
11
+ * {@code RangeDataSource}, so that the player reads URLs without the add-on too.
12
+ */
13
+ interface Fetcher {
14
+
15
+ final class Info {
16
+ static final long UNKNOWN_SIZE = -1;
17
+
18
+ final long size;
19
+ final String etag;
20
+ final String lastModified;
21
+
22
+ Info(long size, String etag, String lastModified) {
23
+ this.size = size >= 0 ? size : UNKNOWN_SIZE;
24
+ this.etag = etag;
25
+ this.lastModified = lastModified;
26
+ }
27
+ }
28
+
29
+ Info open(Uri uri, long position, Map<String, String> headers) throws IOException;
30
+
31
+ /** How many bytes were read, or -1 at the end of the file. */
32
+ int read(byte[] buffer, int offset, int length) throws IOException;
33
+
34
+ void close() throws IOException;
35
+
36
+ void cancel();
37
+ }
@@ -0,0 +1,156 @@
1
+ package dev.mediacache.alphavideo;
2
+
3
+ import android.net.Uri;
4
+
5
+ import java.io.IOException;
6
+ import java.io.InputStream;
7
+ import java.net.HttpURLConnection;
8
+ import java.net.URL;
9
+ import java.util.Map;
10
+ import java.util.regex.Matcher;
11
+ import java.util.regex.Pattern;
12
+
13
+ /**
14
+ * The player's fetcher for {@code http(s)://} URLs, with or without the range-source add-on:
15
+ * {@code HttpURLConnection} with {@code Range: bytes=N-} requests. A server that ignores ranges
16
+ * answers 200 with the whole file, which is read from the position on; that works, but each seek
17
+ * then reads from the start. Redirects are followed, between http and https too, keeping the
18
+ * headers.
19
+ */
20
+ final class HttpFetcher implements Fetcher {
21
+
22
+ private static final int CONNECT_TIMEOUT_MS = 15000;
23
+ private static final int READ_TIMEOUT_MS = 30000;
24
+ private static final String USER_AGENT = "AlphaVideo/1.0";
25
+
26
+ private static final int MAX_REDIRECTS = 5;
27
+ private static final Pattern CONTENT_RANGE = Pattern.compile("bytes\\s+(\\d+)-\\d+/(\\d+|\\*)");
28
+ private static final Pattern UNSATISFIED_RANGE = Pattern.compile("bytes\\s+\\*/(\\d+)");
29
+
30
+ // Set on the reading thread, disconnected by cancel() from any
31
+ private volatile HttpURLConnection connection;
32
+ private volatile boolean cancelled = false;
33
+ // Null once the read has ended
34
+ private InputStream in;
35
+
36
+ @Override
37
+ public Info open(Uri uri, long position, Map<String, String> headers) throws IOException {
38
+ close();
39
+ URL url = new URL(uri.toString());
40
+ for (int redirects = 0; ; redirects++) {
41
+ HttpURLConnection c = connect(url, position, headers);
42
+ int status = c.getResponseCode();
43
+ if (isRedirect(status)) {
44
+ String location = c.getHeaderField("Location");
45
+ close();
46
+ if (location == null) throw new IOException("HTTP " + status + " without a Location for " + url);
47
+ if (redirects == MAX_REDIRECTS) throw new IOException("Too many redirects for " + uri);
48
+ url = new URL(url, location);
49
+ continue;
50
+ }
51
+ String etag = c.getHeaderField("ETag");
52
+ String lastModified = c.getHeaderField("Last-Modified");
53
+ switch (status) {
54
+ case HttpURLConnection.HTTP_PARTIAL: {
55
+ String contentRange = c.getHeaderField("Content-Range");
56
+ Matcher range = CONTENT_RANGE.matcher(String.valueOf(contentRange));
57
+ if (!range.lookingAt() || Long.parseLong(range.group(1)) != position) {
58
+ close();
59
+ throw new IOException("Asked " + url + " for bytes from " + position + " and got " + contentRange);
60
+ }
61
+ in = c.getInputStream();
62
+ String total = range.group(2);
63
+ return new Info(total.equals("*") ? Info.UNKNOWN_SIZE : Long.parseLong(total), etag, lastModified);
64
+ }
65
+ case HttpURLConnection.HTTP_OK: {
66
+ long size = c.getContentLengthLong();
67
+ in = c.getInputStream();
68
+ skip(position);
69
+ return new Info(size, etag, lastModified);
70
+ }
71
+ case 416: {
72
+ // At or past the end: a read that ends at once
73
+ Matcher range = UNSATISFIED_RANGE.matcher(String.valueOf(c.getHeaderField("Content-Range")));
74
+ long size = range.lookingAt() ? Long.parseLong(range.group(1)) : Info.UNKNOWN_SIZE;
75
+ close();
76
+ return new Info(size, etag, lastModified);
77
+ }
78
+ default:
79
+ close();
80
+ throw new IOException("HTTP " + status + " for " + url);
81
+ }
82
+ }
83
+ }
84
+
85
+ @Override
86
+ public int read(byte[] buffer, int offset, int length) throws IOException {
87
+ if (in == null) return -1;
88
+ try {
89
+ return in.read(buffer, offset, length);
90
+ } catch (IOException e) {
91
+ if (cancelled) throw new IOException("Cancelled");
92
+ throw e;
93
+ }
94
+ }
95
+
96
+ @Override
97
+ public void close() {
98
+ InputStream stream = in;
99
+ in = null;
100
+ if (stream != null) {
101
+ try {
102
+ stream.close();
103
+ } catch (IOException ignored) {
104
+ }
105
+ }
106
+ HttpURLConnection c = connection;
107
+ connection = null;
108
+ if (c != null) c.disconnect();
109
+ }
110
+
111
+ @Override
112
+ public void cancel() {
113
+ cancelled = true;
114
+ HttpURLConnection c = connection;
115
+ if (c != null) c.disconnect();
116
+ }
117
+
118
+ private HttpURLConnection connect(URL url, long position, Map<String, String> headers) throws IOException {
119
+ String protocol = url.getProtocol();
120
+ if (!protocol.equals("https") && !protocol.equals("http")) {
121
+ throw new IOException("Not an http(s) URL: " + url);
122
+ }
123
+ HttpURLConnection c = (HttpURLConnection) url.openConnection();
124
+ connection = c;
125
+ if (cancelled) {
126
+ close();
127
+ throw new IOException("Cancelled");
128
+ }
129
+ c.setConnectTimeout(CONNECT_TIMEOUT_MS);
130
+ c.setReadTimeout(READ_TIMEOUT_MS);
131
+ c.setInstanceFollowRedirects(false);
132
+ c.setRequestProperty("User-Agent", USER_AGENT);
133
+ for (Map.Entry<String, String> header : headers.entrySet()) {
134
+ c.setRequestProperty(header.getKey(), header.getValue());
135
+ }
136
+ // Offsets are into the file as stored, so no transparent gzip
137
+ c.setRequestProperty("Accept-Encoding", "identity");
138
+ c.setRequestProperty("Range", "bytes=" + position + "-");
139
+ return c;
140
+ }
141
+
142
+ /** Reads past the first {@code count} bytes of a 200 answer; stops early at its end. */
143
+ private void skip(long count) throws IOException {
144
+ byte[] scratch = new byte[64 * 1024];
145
+ while (count > 0) {
146
+ int n = in.read(scratch, 0, (int) Math.min(scratch.length, count));
147
+ if (n < 0) return;
148
+ count -= n;
149
+ }
150
+ }
151
+
152
+ private static boolean isRedirect(int status) {
153
+ return status == HttpURLConnection.HTTP_MOVED_PERM || status == HttpURLConnection.HTTP_MOVED_TEMP
154
+ || status == HttpURLConnection.HTTP_SEE_OTHER || status == 307 || status == 308;
155
+ }
156
+ }
@@ -0,0 +1,47 @@
1
+ package dev.mediacache.alphavideo;
2
+
3
+ import android.media.MediaFormat;
4
+
5
+ /**
6
+ * MediaCodec's colour keys from a stream's colour tags. The decoder's output buffers carry them,
7
+ * and the external-texture sampler converts YUV to RGB with them; without them a driver may use
8
+ * BT.601 for everything.
9
+ */
10
+ final class MediaCodecColor {
11
+ // The core's ColorRange and ColorMatrix values (YuvToRgb.h), numbered as FFmpeg's
12
+ private static final int AVCOL_RANGE_JPEG = 2;
13
+ private static final int AVCOL_SPC_BT709 = 1;
14
+ private static final int AVCOL_SPC_UNSPECIFIED = 2;
15
+ private static final int AVCOL_SPC_BT470BG = 5;
16
+ private static final int AVCOL_SPC_SMPTE170M = 6;
17
+ private static final int AVCOL_SPC_BT2020_NCL = 9;
18
+ private static final int AVCOL_SPC_BT2020_CL = 10;
19
+
20
+ private MediaCodecColor() {}
21
+
22
+ /** Untagged clips follow the C++ core (yuvToRgbFor): BT.601 up to 720x576, BT.709 above. */
23
+ static int standard(int avColorSpace, int width, int height) {
24
+ switch (avColorSpace) {
25
+ case AVCOL_SPC_BT709: return MediaFormat.COLOR_STANDARD_BT709;
26
+ case AVCOL_SPC_BT470BG: return MediaFormat.COLOR_STANDARD_BT601_PAL;
27
+ case AVCOL_SPC_SMPTE170M: return MediaFormat.COLOR_STANDARD_BT601_NTSC;
28
+ case AVCOL_SPC_BT2020_NCL:
29
+ case AVCOL_SPC_BT2020_CL: return MediaFormat.COLOR_STANDARD_BT2020;
30
+ case AVCOL_SPC_UNSPECIFIED:
31
+ default:
32
+ boolean sd = width <= 720 && height <= 576;
33
+ return sd ? MediaFormat.COLOR_STANDARD_BT601_NTSC : MediaFormat.COLOR_STANDARD_BT709;
34
+ }
35
+ }
36
+
37
+ /** Untagged clips are limited range, like the C++ core. */
38
+ static int range(int avColorRange) {
39
+ return avColorRange == AVCOL_RANGE_JPEG ? MediaFormat.COLOR_RANGE_FULL : MediaFormat.COLOR_RANGE_LIMITED;
40
+ }
41
+
42
+ static void apply(MediaFormat format, int avColorRange, int avColorSpace, int width, int height) {
43
+ format.setInteger(MediaFormat.KEY_COLOR_STANDARD, standard(avColorSpace, width, height));
44
+ format.setInteger(MediaFormat.KEY_COLOR_RANGE, range(avColorRange));
45
+ format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
46
+ }
47
+ }
@@ -0,0 +1,135 @@
1
+ package dev.mediacache.alphavideo;
2
+
3
+ import android.media.MediaCodecInfo;
4
+ import android.media.MediaCodecList;
5
+ import android.os.Build;
6
+
7
+ import java.util.Arrays;
8
+ import java.util.HashMap;
9
+ import java.util.LinkedHashSet;
10
+ import java.util.Map;
11
+
12
+ /**
13
+ * Picks the MediaCodec decoders for a file's colour and alpha tracks, and counts the instances
14
+ * players hold, so a load only takes a decoder with one free. Decoders cap their concurrent
15
+ * instances (a Snapdragon 778G's VP9 hardware decoder allows 3), and each player needs 2.
16
+ */
17
+ final class MediaCodecDecoders {
18
+
19
+ enum Kind {
20
+ HARDWARE,
21
+ SOFTWARE,
22
+ /** Hardware where it has an instance free, else software; chosen per track. */
23
+ ANY,
24
+ }
25
+
26
+ // Instances reserved per decoder name, by players that hold them or are about to create them
27
+ private static final Map<String, Integer> reserved = new HashMap<>();
28
+
29
+ private MediaCodecDecoders() {}
30
+
31
+ /**
32
+ * Reserves a decoder of {@code kind} for the colour and the alpha track, each one listing the
33
+ * track's profile and having an instance free.
34
+ *
35
+ * @param tracks pairs of codec and profile, colour first, from
36
+ * {@link BufferedDualStreamPlayer#nativeColorAlphaTracks}
37
+ * @return the decoder names, colour first; null if a track got none, and then none is reserved.
38
+ * Give them back with {@link #release}.
39
+ */
40
+ static synchronized String[] reserve(int[] tracks, Kind kind) {
41
+ String[] names = new String[2];
42
+ for (int track = 0; track < names.length; track++) {
43
+ String name = pick(mimeFor(tracks[2 * track]), tracks[2 * track + 1], kind);
44
+ if (name == null) {
45
+ release(names);
46
+ return null;
47
+ }
48
+ reserved.merge(name, 1, Integer::sum);
49
+ names[track] = name;
50
+ }
51
+ return names;
52
+ }
53
+
54
+ static synchronized void release(String[] names) {
55
+ if (names == null) return;
56
+ for (String name : names) {
57
+ if (name != null) reserved.computeIfPresent(name, (n, count) -> count > 1 ? count - 1 : null);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * The engine and decoders, as onLoad reports them: "mediacodec-hw (c2.qti.vp9.decoder)",
63
+ * "mediacodec-sw (...)", or "mediacodec (hardware, software)" for one of each.
64
+ */
65
+ static String describe(String[] names) {
66
+ int hardware = 0;
67
+ for (String name : names) {
68
+ if (isHardware(infoFor(name))) hardware++;
69
+ }
70
+ String engine = hardware == names.length ? "mediacodec-hw" : hardware == 0 ? "mediacodec-sw" : "mediacodec";
71
+ return engine + " (" + String.join(", ", new LinkedHashSet<>(Arrays.asList(names))) + ")";
72
+ }
73
+
74
+ static String mimeFor(int codec) {
75
+ switch (codec) {
76
+ case BufferedDualStreamPlayer.TRACK_VP8: return SurfaceTextureVideoDecoder.MIME_VP8;
77
+ case BufferedDualStreamPlayer.TRACK_AV1: return SurfaceTextureVideoDecoder.MIME_AV1;
78
+ default: return SurfaceTextureVideoDecoder.MIME_VP9;
79
+ }
80
+ }
81
+
82
+ private static String pick(String mime, int avProfile, Kind kind) {
83
+ String software = null;
84
+ for (MediaCodecInfo info : new MediaCodecList(MediaCodecList.REGULAR_CODECS).getCodecInfos()) {
85
+ if (info.isEncoder() || info.getName().contains(".secure") || !decodes(info, mime)) continue;
86
+ // An alias shares its codec's instances
87
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && info.isAlias()) continue;
88
+ MediaCodecInfo.CodecCapabilities caps = info.getCapabilitiesForType(mime);
89
+ if (!listsProfile(caps, mime, avProfile)) continue;
90
+ if (reserved.getOrDefault(info.getName(), 0) >= caps.getMaxSupportedInstances()) continue;
91
+ boolean hardware = isHardware(info);
92
+ if (hardware && kind != Kind.SOFTWARE) return info.getName();
93
+ if (!hardware && kind != Kind.HARDWARE && software == null) software = info.getName();
94
+ }
95
+ return software;
96
+ }
97
+
98
+ private static boolean decodes(MediaCodecInfo info, String mime) {
99
+ for (String type : info.getSupportedTypes()) {
100
+ if (type.equalsIgnoreCase(mime)) return true;
101
+ }
102
+ return false;
103
+ }
104
+
105
+ /**
106
+ * Android VP9 decoders rarely list the 4:4:4 profiles 1 and 3, and MediaCodec has no AV1
107
+ * profile beyond Main. VP8's versions (its profiles) only change the loop filter, and every
108
+ * VP8 decoder takes all four. An unknown (negative) profile counts as listed.
109
+ */
110
+ private static boolean listsProfile(MediaCodecInfo.CodecCapabilities caps, String mime, int avProfile) {
111
+ if (avProfile < 0) return true;
112
+ if (SurfaceTextureVideoDecoder.MIME_VP8.equalsIgnoreCase(mime)) return true;
113
+ if (SurfaceTextureVideoDecoder.MIME_AV1.equalsIgnoreCase(mime)) return avProfile == 0;
114
+ if (avProfile > 3) return false;
115
+ int profile = MediaCodecInfo.CodecProfileLevel.VP9Profile0 << avProfile; // 1, 2, 4, 8
116
+ for (MediaCodecInfo.CodecProfileLevel level : caps.profileLevels) {
117
+ if (level.profile == profile) return true;
118
+ }
119
+ return false;
120
+ }
121
+
122
+ private static boolean isHardware(MediaCodecInfo info) {
123
+ if (info == null) return false;
124
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) return info.isHardwareAccelerated();
125
+ String name = info.getName().toLowerCase();
126
+ return !name.contains("sw") && !name.contains("google") && !name.contains("android");
127
+ }
128
+
129
+ private static MediaCodecInfo infoFor(String name) {
130
+ for (MediaCodecInfo info : new MediaCodecList(MediaCodecList.REGULAR_CODECS).getCodecInfos()) {
131
+ if (info.getName().equals(name)) return info;
132
+ }
133
+ return null;
134
+ }
135
+ }
@@ -0,0 +1,54 @@
1
+ package dev.mediacache.alphavideo;
2
+
3
+ import android.util.Log;
4
+
5
+ /**
6
+ * The range-source add-on ({@code dev.mediacache:range-source}), if the app has it: its native
7
+ * table of functions, which {@link SourceReader} reads URLs and data sources through. The player
8
+ * depends on it, so it's there unless the app's build excludes it. Without it, a URL is downloaded
9
+ * whole into a temporary file (the basic input), and data sources, storage and cache keys aren't
10
+ * available.
11
+ */
12
+ final class RangeSourceLink {
13
+ private static final String TAG = "AlphaVideo";
14
+ // Its RangeSource class has a static plugin(), the address of its RangeSourcePlugin
15
+ private static final String ADD_ON = "dev.mediacache.rangesource.RangeSource";
16
+
17
+ static {
18
+ System.loadLibrary("alphavideo");
19
+ }
20
+
21
+ private static final long PLUGIN = discover();
22
+
23
+ private RangeSourceLink() {}
24
+
25
+ /** The add-on's table, or 0 without it. */
26
+ static long plugin() {
27
+ return PLUGIN;
28
+ }
29
+
30
+ static boolean available() {
31
+ return PLUGIN != 0;
32
+ }
33
+
34
+ private static long discover() {
35
+ Class<?> addOn;
36
+ try {
37
+ addOn = Class.forName(ADD_ON);
38
+ } catch (ClassNotFoundException e) {
39
+ return 0;
40
+ } catch (LinkageError e) {
41
+ Log.e(TAG, "range-source is there but its native library didn't load", e);
42
+ return 0;
43
+ }
44
+ try {
45
+ return nativeUsablePlugin((Long) addOn.getMethod("plugin").invoke(null));
46
+ } catch (ReflectiveOperationException | RuntimeException | LinkageError e) {
47
+ Log.e(TAG, "range-source didn't give its table", e);
48
+ return 0;
49
+ }
50
+ }
51
+
52
+ /** {@code plugin} if this player can use it (its ABI and functions), else 0, logging why. */
53
+ private static native long nativeUsablePlugin(long plugin);
54
+ }
@@ -0,0 +1,174 @@
1
+ package dev.mediacache.alphavideo;
2
+
3
+ import android.net.Uri;
4
+
5
+ import java.io.IOException;
6
+ import java.util.Map;
7
+ import java.util.concurrent.ExecutorService;
8
+ import java.util.concurrent.Executors;
9
+
10
+ /**
11
+ * A native reader over a {@link Fetcher}, fetching on its own thread. With the
12
+ * range-source add-on ({@link RangeSourceLink}), it reads by range, through blocks in memory and an
13
+ * optional store. Without it, the basic input: the file is downloaded whole into a temporary file,
14
+ * and read as it arrives. The probe and the player of one source share it.
15
+ *
16
+ * Native calls take their own reference with {@link #retain} and drop it with {@link #release},
17
+ * so that {@link #close} on the main thread never frees a reader under a read.
18
+ */
19
+ final class SourceReader {
20
+
21
+ static {
22
+ System.loadLibrary("alphavideo");
23
+ }
24
+
25
+ /** Where a reader keeps what it fetches between plays. */
26
+ enum Storage { DISK, MEMORY, NONE }
27
+
28
+ // Guarded by this; 0 once closed
29
+ private long handle;
30
+
31
+ private SourceReader(long handle) {
32
+ this.handle = handle;
33
+ }
34
+
35
+ /**
36
+ * Opens the source at 0 to learn its size and version, unless the store holds it whole. Blocks
37
+ * on the network.
38
+ *
39
+ * @param storeDir the folder of the disk store, or the name of the memory one; with the add-on
40
+ * @param key what the store calls this file; with the add-on
41
+ * @param downloadDir where the basic input downloads to, without the add-on
42
+ */
43
+ static SourceReader open(Fetcher source, Uri uri, Map<String, String> headers,
44
+ Storage storage, String storeDir, String key, long maxStorageBytes,
45
+ String downloadDir) throws IOException {
46
+ String[] error = new String[1];
47
+ long handle = nativeOpen(RangeSourceLink.plugin(), new Upstream(source, uri, headers), storage.ordinal(),
48
+ storeDir, key, maxStorageBytes, downloadDir, error);
49
+ if (handle == 0) throw new IOException(error[0] != null ? error[0] : "Cannot open " + uri);
50
+ return new SourceReader(handle);
51
+ }
52
+
53
+ /** A new reference to the native reader, for one native call; 0 once closed. */
54
+ synchronized long retain() {
55
+ return handle != 0 ? nativeRetain(handle) : 0;
56
+ }
57
+
58
+ static void release(long reference) {
59
+ if (reference != 0) nativeRelease(reference);
60
+ }
61
+
62
+ /** Reads the whole file into the store. Blocks; throws if it fails or the reader closes. */
63
+ void fetchAll() throws IOException {
64
+ long reference = retain();
65
+ if (reference == 0) throw new IOException("The reader is closed");
66
+ try {
67
+ String error = nativeFetchAll(reference);
68
+ if (error != null) throw new IOException(error);
69
+ } finally {
70
+ release(reference);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Drops this reference. A player still reading the file keeps the reader open until it
76
+ * unloads; then it closes and its fetch stops.
77
+ */
78
+ void close() {
79
+ long h;
80
+ synchronized (this) {
81
+ h = handle;
82
+ handle = 0;
83
+ }
84
+ release(h);
85
+ }
86
+
87
+ /**
88
+ * The fetcher as the native reader calls it, on the reader's fetch thread, with failures
89
+ * as return values.
90
+ */
91
+ static final class Upstream {
92
+ // cancel() comes from whichever thread closes the reader, the main thread too, and an
93
+ // HTTPS disconnect can write to the socket.
94
+ private static final ExecutorService cancelExecutor = Executors.newCachedThreadPool(r -> {
95
+ Thread t = new Thread(r, "AlphaVideoCancel");
96
+ t.setDaemon(true);
97
+ return t;
98
+ });
99
+
100
+ private final Fetcher source;
101
+ private final Uri uri;
102
+ private final Map<String, String> headers;
103
+ // Read by native code after open() and failures
104
+ String etag;
105
+ String lastModified;
106
+ String error;
107
+
108
+ Upstream(Fetcher source, Uri uri, Map<String, String> headers) {
109
+ this.source = source;
110
+ this.uri = uri;
111
+ this.headers = headers;
112
+ }
113
+
114
+ /** The file's size, -1 if not known, or -2 with {@link #error} set. */
115
+ long open(long position) {
116
+ try {
117
+ Fetcher.Info info = source.open(uri, position, headers);
118
+ etag = info.etag;
119
+ lastModified = info.lastModified;
120
+ return info.size;
121
+ } catch (Exception e) {
122
+ error = describe(e);
123
+ return -2;
124
+ }
125
+ }
126
+
127
+ /** How many bytes were read into {@code buffer}, 0 at the end, or -1 with {@link #error} set. */
128
+ int read(byte[] buffer, int length) {
129
+ try {
130
+ int n;
131
+ do {
132
+ n = source.read(buffer, 0, length);
133
+ } while (n == 0);
134
+ return n < 0 ? 0 : n;
135
+ } catch (Exception e) {
136
+ error = describe(e);
137
+ return -1;
138
+ }
139
+ }
140
+
141
+ void close() {
142
+ try {
143
+ source.close();
144
+ } catch (Exception ignored) {
145
+ }
146
+ }
147
+
148
+ void cancel() {
149
+ cancelExecutor.execute(() -> {
150
+ try {
151
+ source.cancel();
152
+ } catch (Exception ignored) {
153
+ }
154
+ });
155
+ }
156
+
157
+ private static String describe(Exception e) {
158
+ return e.getMessage() != null ? e.getMessage() : e.toString();
159
+ }
160
+ }
161
+
162
+ /**
163
+ * @param plugin the add-on's table, or 0 for the basic input
164
+ * @param storage a {@link Storage} ordinal
165
+ * @param error set to why, when it returns 0
166
+ * @return a reference to the reader, or 0
167
+ */
168
+ private static native long nativeOpen(long plugin, Upstream upstream, int storage, String storeDir, String key,
169
+ long maxStorageBytes, String downloadDir, String[] error);
170
+ private static native long nativeRetain(long reference);
171
+ private static native void nativeRelease(long reference);
172
+ /** Null once the whole file is held, else why not. */
173
+ private static native String nativeFetchAll(long reference);
174
+ }