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,994 @@
1
+ /**
2
+ * AlphaVideoPlayer.mm
3
+ * The Objective-C++ layer over the C++ WebMAlphaPlayer core: the player and the Metal view.
4
+ */
5
+
6
+ #import "AlphaVideoInternal.h"
7
+ #include "VideoDecoder.h"
8
+ #include "WebMAlphaPlayer.h"
9
+ #include <memory>
10
+
11
+ // Lifecycle and per-frame logging inside AlphaVideoMetalView, only with its verbose flag
12
+ #define WEBM_VLOG(...) do { if (self.verbose) NSLog(__VA_ARGS__); } while (0)
13
+
14
+ //==============================================================================
15
+ // AlphaVideoFrame Implementation
16
+ //==============================================================================
17
+
18
+ @implementation AlphaVideoFrame {
19
+ std::shared_ptr<webm::YUVAFrame> _cppFrame;
20
+ }
21
+
22
+ - (instancetype)initWithCppFrame:(std::shared_ptr<webm::YUVAFrame>)frame {
23
+ self = [super init];
24
+ if (self && frame) {
25
+ _cppFrame = frame;
26
+ }
27
+ return self;
28
+ }
29
+
30
+ - (NSData *)yPlane {
31
+ if (!_cppFrame) return nil;
32
+ return [NSData dataWithBytesNoCopy:_cppFrame->yPlane
33
+ length:_cppFrame->ySize
34
+ freeWhenDone:NO];
35
+ }
36
+
37
+ - (NSData *)uPlane {
38
+ if (!_cppFrame) return nil;
39
+ return [NSData dataWithBytesNoCopy:_cppFrame->uPlane
40
+ length:_cppFrame->uSize
41
+ freeWhenDone:NO];
42
+ }
43
+
44
+ - (NSData *)vPlane {
45
+ if (!_cppFrame) return nil;
46
+ return [NSData dataWithBytesNoCopy:_cppFrame->vPlane
47
+ length:_cppFrame->vSize
48
+ freeWhenDone:NO];
49
+ }
50
+
51
+ - (NSData *)aPlane {
52
+ if (!_cppFrame) return nil;
53
+ return [NSData dataWithBytesNoCopy:_cppFrame->aPlane
54
+ length:_cppFrame->aSize
55
+ freeWhenDone:NO];
56
+ }
57
+
58
+ - (const uint8_t *)yPlanePtr { return _cppFrame ? _cppFrame->yPlane : nullptr; }
59
+ - (const uint8_t *)uPlanePtr { return _cppFrame ? _cppFrame->uPlane : nullptr; }
60
+ - (const uint8_t *)vPlanePtr { return _cppFrame ? _cppFrame->vPlane : nullptr; }
61
+ - (const uint8_t *)aPlanePtr { return _cppFrame ? _cppFrame->aPlane : nullptr; }
62
+
63
+ - (int)yStride { return _cppFrame ? _cppFrame->yStride : 0; }
64
+ - (int)uvStride { return _cppFrame ? _cppFrame->uvStride : 0; }
65
+ - (int)aStride { return _cppFrame ? _cppFrame->aStride : 0; }
66
+
67
+ - (int)width { return _cppFrame ? _cppFrame->width : 0; }
68
+ - (int)height { return _cppFrame ? _cppFrame->height : 0; }
69
+ - (int)frameIndex { return _cppFrame ? _cppFrame->frameIndex : 0; }
70
+ - (double)timestamp { return _cppFrame ? _cppFrame->getDurationMs() / 1000.0 : 0; }
71
+
72
+ @end
73
+
74
+ //==============================================================================
75
+ // AlphaVideoStats Implementation
76
+ //==============================================================================
77
+
78
+ @implementation AlphaVideoStats
79
+
80
+ - (instancetype)initWithStats:(const webm::DecoderStatsSnapshot &)stats {
81
+ self = [super init];
82
+ if (self) {
83
+ _framesDecoded = stats.framesDecoded;
84
+ _cacheHits = stats.cacheHits;
85
+ _cacheMisses = stats.cacheMisses;
86
+ _avgDecodeTimeMs = stats.getAverageDecodeTimeMs();
87
+ _cacheHitRate = stats.getCacheHitRate();
88
+ }
89
+ return self;
90
+ }
91
+
92
+ @end
93
+
94
+ //==============================================================================
95
+ // AlphaVideoConfig Implementation
96
+ //==============================================================================
97
+
98
+ @implementation AlphaVideoConfig
99
+
100
+ + (instancetype)defaultConfig {
101
+ AlphaVideoConfig *config = [[AlphaVideoConfig alloc] init];
102
+ config.decodeThreads = 4;
103
+ config.cacheSize = 250; // Large enough for most animations (181 frames typical)
104
+ config.preloadAllFrames = YES;
105
+ config.loopPlayback = YES;
106
+ config.playbackSpeed = 1.0;
107
+ config.mode = @"stream";
108
+ config.stream = YES;
109
+ config.streamPrefetchAhead = 8;
110
+ return config;
111
+ }
112
+
113
+ - (webm::PlayerConfig)toCppConfig {
114
+ webm::PlayerConfig cfg;
115
+ cfg.decodeThreads = self.decodeThreads;
116
+ cfg.cacheSize = self.cacheSize;
117
+ cfg.preloadAllFrames = self.preloadAllFrames;
118
+ cfg.loopPlayback = self.loopPlayback;
119
+ cfg.playbackSpeed = self.playbackSpeed;
120
+
121
+ // Parse mode string to enum
122
+ if ([self.mode isEqualToString:@"preload"]) {
123
+ cfg.mode = webm::PlaybackMode::Preload;
124
+ cfg.stream = false;
125
+ } else if ([self.mode isEqualToString:@"streamAndPreload"]) {
126
+ cfg.mode = webm::PlaybackMode::StreamAndPreload;
127
+ cfg.stream = false; // Not pure streaming
128
+ } else {
129
+ cfg.mode = webm::PlaybackMode::Stream;
130
+ cfg.stream = true;
131
+ }
132
+
133
+ cfg.streamPrefetchAhead = self.streamPrefetchAhead > 0 ? self.streamPrefetchAhead : 8;
134
+ return cfg;
135
+ }
136
+
137
+ @end
138
+
139
+ //==============================================================================
140
+ // AlphaVideoPlayer Implementation
141
+ //==============================================================================
142
+
143
+ @interface AlphaVideoPlayer ()
144
+ /** The loaded clip's YUV to RGB, as the Metal view's fragment shader takes it. */
145
+ - (webm::YuvToRgb)yuvToRgb;
146
+ @end
147
+
148
+ @implementation AlphaVideoPlayer {
149
+ std::unique_ptr<webm::WebMAlphaPlayer> _player;
150
+ dispatch_queue_t _callbackQueue;
151
+ }
152
+
153
+ - (instancetype)init {
154
+ return [self initWithConfig:[AlphaVideoConfig defaultConfig]];
155
+ }
156
+
157
+ - (instancetype)initWithConfig:(AlphaVideoConfig *)config {
158
+ self = [super init];
159
+ if (self) {
160
+ [AlphaVideoCodecs discover];
161
+ _player = std::make_unique<webm::WebMAlphaPlayer>();
162
+ _player->setConfig([config toCppConfig]);
163
+ _callbackQueue = dispatch_get_main_queue();
164
+
165
+ [self setupCallbacks];
166
+ }
167
+ return self;
168
+ }
169
+
170
+ - (void)setupCallbacks {
171
+ // Use __weak and __block for safe capture in C++ lambdas
172
+ __weak AlphaVideoPlayer *weakSelf = self;
173
+
174
+ // Frame callback - use CFRunLoopPerformBlock with kCFRunLoopCommonModes
175
+ // to ensure frames are processed during scrolling and other UI interactions
176
+ _player->setFrameCallback([weakSelf](std::shared_ptr<webm::YUVAFrame> frame) {
177
+ AlphaVideoPlayer *strongSelf = weakSelf;
178
+ if (!strongSelf) return;
179
+
180
+ AlphaVideoFrame *objcFrame = [[AlphaVideoFrame alloc] initWithCppFrame:frame];
181
+
182
+ // Use CFRunLoopPerformBlock with kCFRunLoopCommonModes for immediate execution
183
+ // This ensures callbacks run even during UI tracking (scrolling)
184
+ CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
185
+ if ([strongSelf.delegate respondsToSelector:@selector(playerDidReceiveFrame:)]) {
186
+ [strongSelf.delegate playerDidReceiveFrame:objcFrame];
187
+ }
188
+ });
189
+ // Wake up the run loop to process the block immediately
190
+ CFRunLoopWakeUp(CFRunLoopGetMain());
191
+ });
192
+
193
+ // Event callback - also use CFRunLoopPerformBlock with kCFRunLoopCommonModes.
194
+ // The core calls it from its own methods and threads, which it stops before it's freed.
195
+ webm::WebMAlphaPlayer *core = _player.get();
196
+ _player->setEventCallback([weakSelf, core](const std::string& event, const std::string& data) {
197
+ AlphaVideoPlayer *strongSelf = weakSelf;
198
+ if (!strongSelf) return;
199
+
200
+ NSString *eventStr = [NSString stringWithUTF8String:event.c_str()];
201
+ NSString *dataStr = [NSString stringWithUTF8String:data.c_str()];
202
+ const std::string code = event == "error" ? core->getLastErrorCode() : std::string();
203
+ NSString *codeStr = code.empty() ? nil : @(code.c_str());
204
+
205
+ CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
206
+ [strongSelf handleEvent:eventStr data:dataStr code:codeStr];
207
+ });
208
+ CFRunLoopWakeUp(CFRunLoopGetMain());
209
+ });
210
+ }
211
+
212
+ - (void)handleEvent:(NSString *)event data:(NSString *)data code:(nullable NSString *)code {
213
+ id<AlphaVideoPlayerDelegate> delegate = self.delegate;
214
+
215
+ if ([event isEqualToString:@"loaded"]) {
216
+ if ([delegate respondsToSelector:@selector(playerDidLoad:width:height:)]) {
217
+ [delegate playerDidLoad:self.totalFrames width:self.width height:self.height];
218
+ }
219
+ } else if ([event isEqualToString:@"play"]) {
220
+ if ([delegate respondsToSelector:@selector(playerDidStartPlaying)]) {
221
+ [delegate playerDidStartPlaying];
222
+ }
223
+ } else if ([event isEqualToString:@"pause"]) {
224
+ if ([delegate respondsToSelector:@selector(playerDidPause)]) {
225
+ [delegate playerDidPause];
226
+ }
227
+ } else if ([event isEqualToString:@"stop"]) {
228
+ if ([delegate respondsToSelector:@selector(playerDidStop)]) {
229
+ [delegate playerDidStop];
230
+ }
231
+ } else if ([event isEqualToString:@"loop"]) {
232
+ if ([delegate respondsToSelector:@selector(playerDidLoop)]) {
233
+ [delegate playerDidLoop];
234
+ }
235
+ } else if ([event isEqualToString:@"ended"]) {
236
+ if ([delegate respondsToSelector:@selector(playerDidEnd)]) {
237
+ [delegate playerDidEnd];
238
+ }
239
+ } else if ([event isEqualToString:@"error"]) {
240
+ if ([delegate respondsToSelector:@selector(playerDidEncounterError:code:)]) {
241
+ [delegate playerDidEncounterError:data code:code];
242
+ }
243
+ } else if ([event isEqualToString:@"preload_complete"]) {
244
+ // Parse "X frames in Yms"
245
+ NSArray *parts = [data componentsSeparatedByString:@" frames in "];
246
+ if (parts.count == 2) {
247
+ int frames = [parts[0] intValue];
248
+ double ms = [[parts[1] stringByReplacingOccurrencesOfString:@"ms" withString:@""] doubleValue];
249
+ if ([delegate respondsToSelector:@selector(playerPreloadComplete:timeMs:)]) {
250
+ [delegate playerPreloadComplete:frames timeMs:ms];
251
+ }
252
+ }
253
+ }
254
+ }
255
+
256
+ - (void)setVerbose:(BOOL)verbose {
257
+ _player->setVerbose(verbose);
258
+ }
259
+
260
+ - (void)setConfig:(AlphaVideoConfig *)config {
261
+ _player->setConfig([config toCppConfig]);
262
+ }
263
+
264
+ - (AlphaVideoConfig *)getConfig {
265
+ webm::PlayerConfig cfg = _player->getConfig();
266
+ AlphaVideoConfig *config = [[AlphaVideoConfig alloc] init];
267
+ config.decodeThreads = cfg.decodeThreads;
268
+ config.cacheSize = cfg.cacheSize;
269
+ config.preloadAllFrames = cfg.preloadAllFrames;
270
+ config.loopPlayback = cfg.loopPlayback;
271
+ config.playbackSpeed = cfg.playbackSpeed;
272
+ config.stream = cfg.stream;
273
+ config.streamPrefetchAhead = cfg.streamPrefetchAhead;
274
+
275
+ // Convert mode enum back to string
276
+ switch (cfg.mode) {
277
+ case webm::PlaybackMode::Preload:
278
+ config.mode = @"preload";
279
+ break;
280
+ case webm::PlaybackMode::StreamAndPreload:
281
+ config.mode = @"streamAndPreload";
282
+ break;
283
+ default:
284
+ config.mode = @"stream";
285
+ break;
286
+ }
287
+ return config;
288
+ }
289
+
290
+ - (BOOL)loadFromFile:(NSString *)path {
291
+ return _player->loadFromFile([path UTF8String]);
292
+ }
293
+
294
+ - (BOOL)loadFromData:(NSData *)data {
295
+ return _player->loadFromMemory(
296
+ static_cast<const uint8_t *>(data.bytes),
297
+ data.length
298
+ );
299
+ }
300
+
301
+ - (void)unload {
302
+ _player->unload();
303
+ }
304
+
305
+ - (void)play { _player->play(); }
306
+ - (void)pause { _player->pause(); }
307
+ - (void)stop { _player->stop(); }
308
+
309
+ - (void)seekToTime:(double)seconds {
310
+ _player->seekTo(seconds);
311
+ }
312
+
313
+ - (void)seekToFrame:(int)frameIndex {
314
+ _player->seekToFrame(frameIndex);
315
+ }
316
+
317
+ - (void)setPlaybackSpeed:(double)speed {
318
+ _player->setPlaybackSpeed(speed);
319
+ }
320
+
321
+ - (double)playbackSpeed {
322
+ return _player->getPlaybackSpeed();
323
+ }
324
+
325
+ - (AlphaVideoFrame *)getFrame:(int)index {
326
+ auto frame = _player->getFrame(index);
327
+ if (!frame) return nil;
328
+ return [[AlphaVideoFrame alloc] initWithCppFrame:frame];
329
+ }
330
+
331
+ - (AlphaVideoFrame *)getCachedFrame:(int)index {
332
+ auto frame = _player->getCachedFrame(index);
333
+ if (!frame) return nil;
334
+ return [[AlphaVideoFrame alloc] initWithCppFrame:frame];
335
+ }
336
+
337
+ - (AlphaVideoFrame *)getCurrentFrame {
338
+ auto frame = _player->getCurrentFrameData();
339
+ if (!frame) return nil;
340
+ return [[AlphaVideoFrame alloc] initWithCppFrame:frame];
341
+ }
342
+
343
+ - (AlphaVideoStats *)getStats {
344
+ return [[AlphaVideoStats alloc] initWithStats:_player->getStats()];
345
+ }
346
+
347
+ - (void)resetStats {
348
+ _player->resetStats();
349
+ }
350
+
351
+ // Properties
352
+ - (BOOL)isLoaded { return _player->isLoaded(); }
353
+ - (BOOL)isPlaying { return _player->isPlaying(); }
354
+ - (int)currentFrame { return _player->getCurrentFrame(); }
355
+ - (double)currentTime { return _player->getCurrentTime(); }
356
+ - (double)duration { return _player->getDuration(); }
357
+ - (int)totalFrames { return _player->getTotalFrames(); }
358
+ - (int)width { return _player->getWidth(); }
359
+ - (int)height { return _player->getHeight(); }
360
+ - (double)frameRate { return _player->getFrameRate(); }
361
+ - (NSString *)decoderName { return [NSString stringWithUTF8String:_player->getDecoderName().c_str()]; }
362
+ - (webm::YuvToRgb)yuvToRgb { return _player->getYuvToRgb(); }
363
+
364
+ + (NSString *)libraryVersions {
365
+ return [NSString stringWithUTF8String:webm::WebMAlphaPlayer::getLibraryVersions().c_str()];
366
+ }
367
+
368
+ @end
369
+
370
+ //==============================================================================
371
+ // AlphaVideoMetalView Implementation
372
+ //==============================================================================
373
+
374
+ @interface AlphaVideoMetalView () <AlphaVideoPlayerDelegate, MTKViewDelegate>
375
+
376
+ @property (nonatomic, strong) id<MTLDevice> metalDevice;
377
+ @property (nonatomic, strong) id<MTLCommandQueue> commandQueue;
378
+ @property (nonatomic, strong) id<MTLRenderPipelineState> pipelineState;
379
+ @property (nonatomic, strong) id<MTLBuffer> vertexBuffer;
380
+
381
+ // Triple-buffered YUVA textures to prevent tearing
382
+ @property (nonatomic, strong) NSArray<id<MTLTexture>> *yTextures;
383
+ @property (nonatomic, strong) NSArray<id<MTLTexture>> *uTextures;
384
+ @property (nonatomic, strong) NSArray<id<MTLTexture>> *vTextures;
385
+ @property (nonatomic, strong) NSArray<id<MTLTexture>> *aTextures;
386
+
387
+ @property (nonatomic) int textureWidth;
388
+ @property (nonatomic) int textureHeight;
389
+
390
+ // Triple buffering state
391
+ @property (nonatomic) int currentTextureIndex;
392
+ @property (nonatomic, strong) dispatch_semaphore_t frameSemaphore;
393
+
394
+ // Frame tracking
395
+ @property (nonatomic) int lastRenderedFrame;
396
+ @property (nonatomic) BOOL isAnimating; // Whether we should be animating
397
+
398
+ // Store latest frame from C++ callback (thread-safe)
399
+ @property (atomic, strong) AlphaVideoFrame *latestFrame;
400
+
401
+ @end
402
+
403
+ @implementation AlphaVideoMetalView
404
+
405
+ - (void)setVerbose:(BOOL)verbose {
406
+ _verbose = verbose;
407
+ [self.player setVerbose:verbose];
408
+ }
409
+
410
+ - (instancetype)initWithFrame:(CGRect)frame {
411
+ return [self initWithFrame:frame config:[AlphaVideoConfig defaultConfig]];
412
+ }
413
+
414
+ - (instancetype)initWithFrame:(CGRect)frame config:(AlphaVideoConfig *)config {
415
+ // Create Metal device
416
+ id<MTLDevice> device = MTLCreateSystemDefaultDevice();
417
+
418
+ self = [super initWithFrame:frame device:device];
419
+ if (self) {
420
+ _metalDevice = device;
421
+ _player = [[AlphaVideoPlayer alloc] initWithConfig:config];
422
+ _player.delegate = self;
423
+ _autoPlay = YES;
424
+ _loop = YES;
425
+ _playbackSpeed = 1.0;
426
+ _currentTextureIndex = 0;
427
+ _lastRenderedFrame = -1;
428
+ _isAnimating = NO;
429
+ // Allow up to 3 frames in flight
430
+ _frameSemaphore = dispatch_semaphore_create(3);
431
+
432
+ [self setupMetal];
433
+ }
434
+ return self;
435
+ }
436
+
437
+ - (void)dealloc {
438
+ // Nothing special needed - MTKView handles its own cleanup
439
+ }
440
+
441
+ /**
442
+ * Forwards to the running player rather than only recording the value.
443
+ *
444
+ * `playerDidLoad:` pushes this into the config, which covers a rate set before the video was
445
+ * ready. On its own that is not enough: props change after load too, and a hover that speeds the
446
+ * animation up would otherwise do nothing until the source was reloaded.
447
+ */
448
+ - (void)setPlaybackSpeed:(double)playbackSpeed {
449
+ _playbackSpeed = playbackSpeed;
450
+ [_player setPlaybackSpeed:playbackSpeed];
451
+ }
452
+
453
+ - (void)setupMetal {
454
+ self.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
455
+ self.clearColor = MTLClearColorMake(0, 0, 0, 0);
456
+ self.opaque = NO;
457
+ self.layer.opaque = NO;
458
+ self.backgroundColor = [UIColor clearColor];
459
+ self.framebufferOnly = NO; // Needed for texture operations
460
+
461
+ // KEY: Use MTKView's built-in continuous rendering loop (like ChatGPT example)
462
+ self.delegate = self;
463
+ self.enableSetNeedsDisplay = NO; // Don't use setNeedsDisplay, use continuous rendering
464
+ self.preferredFramesPerSecond = 30; // Until the clip's frame rate is known (playerDidLoad)
465
+ self.paused = YES; // Start paused, will unpause when playback starts
466
+
467
+ _commandQueue = [_metalDevice newCommandQueue];
468
+
469
+ // Create shader library
470
+ NSString *shaderSource = @R"(
471
+ #include <metal_stdlib>
472
+ using namespace metal;
473
+
474
+ struct VertexOut {
475
+ float4 position [[position]];
476
+ float2 texCoord;
477
+ };
478
+
479
+ vertex VertexOut vertexShader(
480
+ uint vertexID [[vertex_id]],
481
+ constant float4 *vertices [[buffer(0)]]
482
+ ) {
483
+ VertexOut out;
484
+ out.position = float4(vertices[vertexID].xy, 0.0, 1.0);
485
+ out.texCoord = vertices[vertexID].zw;
486
+ return out;
487
+ }
488
+
489
+ fragment float4 fragmentShader(
490
+ VertexOut in [[stage_in]],
491
+ texture2d<float> yTexture [[texture(0)]],
492
+ texture2d<float> uTexture [[texture(1)]],
493
+ texture2d<float> vTexture [[texture(2)]],
494
+ texture2d<float> aTexture [[texture(3)]],
495
+ constant float4x4 &yuvToRgb [[buffer(0)]]
496
+ ) {
497
+ constexpr sampler texSampler(mag_filter::linear, min_filter::linear);
498
+
499
+ float4 yuv = float4(yTexture.sample(texSampler, in.texCoord).r,
500
+ uTexture.sample(texSampler, in.texCoord).r,
501
+ vTexture.sample(texSampler, in.texCoord).r, 1.0);
502
+ float a = aTexture.sample(texSampler, in.texCoord).r;
503
+
504
+ // The clip's range and matrix (webm::yuvToRgbFor)
505
+ float3 rgb = clamp((yuvToRgb * yuv).rgb, 0.0, 1.0);
506
+
507
+ // Premultiplied alpha for correct compositing
508
+ return float4(rgb * a, a);
509
+ }
510
+ )";
511
+
512
+ NSError *error;
513
+ id<MTLLibrary> library = [_metalDevice newLibraryWithSource:shaderSource
514
+ options:nil
515
+ error:&error];
516
+ if (error) {
517
+ NSLog(@"AlphaVideoMetalView shader compile error: %@", error);
518
+ return;
519
+ }
520
+
521
+ id<MTLFunction> vertexFunc = [library newFunctionWithName:@"vertexShader"];
522
+ id<MTLFunction> fragmentFunc = [library newFunctionWithName:@"fragmentShader"];
523
+
524
+ MTLRenderPipelineDescriptor *pipelineDesc = [[MTLRenderPipelineDescriptor alloc] init];
525
+ pipelineDesc.vertexFunction = vertexFunc;
526
+ pipelineDesc.fragmentFunction = fragmentFunc;
527
+ pipelineDesc.colorAttachments[0].pixelFormat = self.colorPixelFormat;
528
+
529
+ // Enable alpha blending
530
+ pipelineDesc.colorAttachments[0].blendingEnabled = YES;
531
+ pipelineDesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne;
532
+ pipelineDesc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
533
+ pipelineDesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
534
+ pipelineDesc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
535
+
536
+ _pipelineState = [_metalDevice newRenderPipelineStateWithDescriptor:pipelineDesc error:&error];
537
+ if (error) {
538
+ NSLog(@"AlphaVideoMetalView pipeline error: %@", error);
539
+ return;
540
+ }
541
+
542
+ // Create vertex buffer (fullscreen quad)
543
+ float vertices[] = {
544
+ // position texCoord
545
+ -1.0, -1.0, 0.0, 1.0,
546
+ 1.0, -1.0, 1.0, 1.0,
547
+ -1.0, 1.0, 0.0, 0.0,
548
+ 1.0, 1.0, 1.0, 0.0,
549
+ };
550
+ _vertexBuffer = [_metalDevice newBufferWithBytes:vertices
551
+ length:sizeof(vertices)
552
+ options:MTLResourceStorageModeShared];
553
+
554
+ WEBM_VLOG(@"AlphaVideoMetalView Metal setup complete");
555
+ }
556
+
557
+ - (void)createTexturesForWidth:(int)width height:(int)height {
558
+ if (_textureWidth == width && _textureHeight == height) {
559
+ return; // Already correct size
560
+ }
561
+
562
+ _textureWidth = width;
563
+ _textureHeight = height;
564
+
565
+ // Create triple-buffered textures
566
+ NSMutableArray *yTexs = [NSMutableArray arrayWithCapacity:3];
567
+ NSMutableArray *uTexs = [NSMutableArray arrayWithCapacity:3];
568
+ NSMutableArray *vTexs = [NSMutableArray arrayWithCapacity:3];
569
+ NSMutableArray *aTexs = [NSMutableArray arrayWithCapacity:3];
570
+
571
+ // Y texture descriptor (full resolution)
572
+ MTLTextureDescriptor *yDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR8Unorm
573
+ width:width
574
+ height:height
575
+ mipmapped:NO];
576
+ yDesc.usage = MTLTextureUsageShaderRead;
577
+ yDesc.storageMode = MTLStorageModeShared; // CPU+GPU access
578
+
579
+ // UV texture descriptor (half resolution, rounded up for odd sizes)
580
+ int uvWidth = (width + 1) / 2;
581
+ int uvHeight = (height + 1) / 2;
582
+ MTLTextureDescriptor *uvDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR8Unorm
583
+ width:uvWidth
584
+ height:uvHeight
585
+ mipmapped:NO];
586
+ uvDesc.usage = MTLTextureUsageShaderRead;
587
+ uvDesc.storageMode = MTLStorageModeShared;
588
+
589
+ // Create 3 sets of textures for triple buffering
590
+ for (int i = 0; i < 3; i++) {
591
+ [yTexs addObject:[_metalDevice newTextureWithDescriptor:yDesc]];
592
+ [uTexs addObject:[_metalDevice newTextureWithDescriptor:uvDesc]];
593
+ [vTexs addObject:[_metalDevice newTextureWithDescriptor:uvDesc]];
594
+ [aTexs addObject:[_metalDevice newTextureWithDescriptor:yDesc]]; // Alpha is full resolution
595
+ }
596
+
597
+ _yTextures = yTexs;
598
+ _uTextures = uTexs;
599
+ _vTextures = vTexs;
600
+ _aTextures = aTexs;
601
+
602
+ WEBM_VLOG(@"Created triple-buffered textures %dx%d (UV: %dx%d)", width, height, uvWidth, uvHeight);
603
+ }
604
+
605
+ - (BOOL)loadFromPath:(NSString *)path {
606
+ _sourcePath = path;
607
+ return [_player loadFromFile:path];
608
+ }
609
+
610
+ - (void)unloadVideo {
611
+ [_player unload];
612
+ _yTextures = nil;
613
+ _uTextures = nil;
614
+ _vTextures = nil;
615
+ _aTextures = nil;
616
+ _textureWidth = 0;
617
+ _textureHeight = 0;
618
+ _currentTextureIndex = 0;
619
+ }
620
+
621
+ - (void)play { [_player play]; }
622
+ - (void)pause { [_player pause]; }
623
+ - (void)stop { [_player stop]; }
624
+ - (void)seekToFrame:(int)frame { [_player seekToFrame:frame]; }
625
+ - (void)seekToTime:(double)seconds { [_player seekToTime:seconds]; }
626
+
627
+ - (void)renderFrame:(AlphaVideoFrame *)frame {
628
+ if (!frame || !_pipelineState || !_frameSemaphore) {
629
+ NSLog(@"renderFrame early return: frame=%@, pipeline=%@, semaphore=%@",
630
+ frame ? @"YES" : @"NO", _pipelineState ? @"YES" : @"NO", _frameSemaphore ? @"YES" : @"NO");
631
+ return;
632
+ }
633
+
634
+ // Debug: log render attempts
635
+ static int renderLogCount = 0;
636
+ if (renderLogCount++ < 20 || renderLogCount % 30 == 0) {
637
+ WEBM_VLOG(@"renderFrame called: frame=%d, size=%dx%d", frame.frameIndex, frame.width, frame.height);
638
+ }
639
+
640
+ // Wait for a texture set to become available (max 3 in flight)
641
+ // Use a short timeout to avoid blocking the main thread too long
642
+ if (dispatch_semaphore_wait(_frameSemaphore, dispatch_time(DISPATCH_TIME_NOW, 16 * NSEC_PER_MSEC)) != 0) {
643
+ // Timeout - skip this frame to avoid blocking
644
+ if (renderLogCount < 10) NSLog(@"renderFrame timeout waiting for semaphore");
645
+ return;
646
+ }
647
+
648
+ // Ensure textures exist and are correct size
649
+ [self createTexturesForWidth:frame.width height:frame.height];
650
+
651
+ if (!_yTextures || _yTextures.count < 3) {
652
+ dispatch_semaphore_signal(_frameSemaphore);
653
+ return;
654
+ }
655
+
656
+ // Get current texture set and advance index
657
+ int texIdx = _currentTextureIndex;
658
+ _currentTextureIndex = (_currentTextureIndex + 1) % 3;
659
+
660
+ id<MTLTexture> yTex = _yTextures[texIdx];
661
+ id<MTLTexture> uTex = _uTextures[texIdx];
662
+ id<MTLTexture> vTex = _vTextures[texIdx];
663
+ id<MTLTexture> aTex = _aTextures[texIdx];
664
+
665
+ // Upload plane data to this texture set
666
+ MTLRegion yRegion = MTLRegionMake2D(0, 0, frame.width, frame.height);
667
+ [yTex replaceRegion:yRegion
668
+ mipmapLevel:0
669
+ withBytes:frame.yPlanePtr
670
+ bytesPerRow:frame.yStride];
671
+
672
+ [aTex replaceRegion:yRegion
673
+ mipmapLevel:0
674
+ withBytes:frame.aPlanePtr
675
+ bytesPerRow:frame.aStride];
676
+
677
+ int uvWidth = (frame.width + 1) / 2;
678
+ int uvHeight = (frame.height + 1) / 2;
679
+ MTLRegion uvRegion = MTLRegionMake2D(0, 0, uvWidth, uvHeight);
680
+
681
+ [uTex replaceRegion:uvRegion
682
+ mipmapLevel:0
683
+ withBytes:frame.uPlanePtr
684
+ bytesPerRow:frame.uvStride];
685
+
686
+ [vTex replaceRegion:uvRegion
687
+ mipmapLevel:0
688
+ withBytes:frame.vPlanePtr
689
+ bytesPerRow:frame.uvStride];
690
+
691
+ // Get drawable - use MTKView's currentDrawable when called from drawInMTKView
692
+ // or nextDrawable from layer when called directly
693
+ id<CAMetalDrawable> drawable = self.currentDrawable;
694
+ if (!drawable) {
695
+ // Fallback to getting from layer directly
696
+ CAMetalLayer *metalLayer = (CAMetalLayer *)self.layer;
697
+ drawable = [metalLayer nextDrawable];
698
+ }
699
+
700
+ if (!drawable) {
701
+ static int noDrawableCount = 0;
702
+ if (noDrawableCount++ < 10) {
703
+ NSLog(@"renderFrame: no drawable available (view size: %.0fx%.0f)",
704
+ self.bounds.size.width, self.bounds.size.height);
705
+ }
706
+ dispatch_semaphore_signal(_frameSemaphore);
707
+ return;
708
+ }
709
+
710
+ static int drawableSuccessCount = 0;
711
+ if (drawableSuccessCount++ < 3) {
712
+ WEBM_VLOG(@"Got drawable: %@ texture size: %lux%lu", drawable,
713
+ (unsigned long)drawable.texture.width, (unsigned long)drawable.texture.height);
714
+ }
715
+
716
+ // Use currentRenderPassDescriptor if available, otherwise create manually
717
+ MTLRenderPassDescriptor *passDesc = self.currentRenderPassDescriptor;
718
+ if (!passDesc) {
719
+ passDesc = [MTLRenderPassDescriptor renderPassDescriptor];
720
+ passDesc.colorAttachments[0].texture = drawable.texture;
721
+ }
722
+
723
+ passDesc.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0);
724
+ passDesc.colorAttachments[0].loadAction = MTLLoadActionClear;
725
+ passDesc.colorAttachments[0].storeAction = MTLStoreActionStore;
726
+
727
+ id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
728
+
729
+ // Signal semaphore when GPU completes this frame
730
+ __block dispatch_semaphore_t sem = _frameSemaphore;
731
+ [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> _Nonnull buffer) {
732
+ dispatch_semaphore_signal(sem);
733
+ }];
734
+
735
+ id<MTLRenderCommandEncoder> encoder = [commandBuffer renderCommandEncoderWithDescriptor:passDesc];
736
+
737
+ [encoder setRenderPipelineState:_pipelineState];
738
+ [encoder setVertexBuffer:_vertexBuffer offset:0 atIndex:0];
739
+ [encoder setFragmentTexture:yTex atIndex:0];
740
+ [encoder setFragmentTexture:uTex atIndex:1];
741
+ [encoder setFragmentTexture:vTex atIndex:2];
742
+ [encoder setFragmentTexture:aTex atIndex:3];
743
+ webm::YuvToRgb yuvToRgb = [_player yuvToRgb];
744
+ [encoder setFragmentBytes:yuvToRgb.m length:sizeof(yuvToRgb.m) atIndex:0];
745
+
746
+ [encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
747
+ [encoder endEncoding];
748
+
749
+ [commandBuffer presentDrawable:drawable];
750
+ [commandBuffer commit];
751
+
752
+ // Debug: log successful render
753
+ static int commitCount = 0;
754
+ if (commitCount++ < 20 || commitCount % 30 == 0) {
755
+ WEBM_VLOG(@"Frame %d committed to GPU", frame.frameIndex);
756
+ }
757
+ }
758
+
759
+ - (void)renderCurrentFrame {
760
+ AlphaVideoFrame *frame = [_player getCurrentFrame];
761
+ if (frame) {
762
+ self.latestFrame = frame;
763
+ _lastRenderedFrame = -1;
764
+ [self draw];
765
+ }
766
+ }
767
+
768
+ #pragma mark - MTKViewDelegate
769
+
770
+ - (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {
771
+ // Handle resize if needed - textures are created dynamically per frame
772
+ }
773
+
774
+ - (void)drawInMTKView:(MTKView *)view {
775
+ // This is called by MTKView at preferredFramesPerSecond (the clip's frame rate)
776
+ // It's the core render loop - equivalent to CADisplayLink callback
777
+
778
+ // Debug: log that we're being called
779
+ static int callCount = 0;
780
+ if (callCount++ < 10) {
781
+ WEBM_VLOG(@"drawInMTKView called #%d! isAnimating=%d, isLoaded=%d, paused=%d, bounds=%.0fx%.0f, window=%@",
782
+ callCount, _isAnimating, _player.isLoaded, self.paused,
783
+ self.bounds.size.width, self.bounds.size.height, self.window);
784
+ }
785
+
786
+ if (!_player || !_player.isLoaded) {
787
+ return;
788
+ }
789
+
790
+ // Even if not "animating", we should render if we have a frame
791
+ // This ensures the first frame shows up
792
+
793
+ // Draw the last frame the C++ clock emitted. Its frame index has already moved on to the
794
+ // next frame, which in the stream modes may not be decoded yet (a slow download, a slow
795
+ // device), so looking that one up in the cache would mostly miss and show nothing new.
796
+ AlphaVideoFrame *frame = self.latestFrame;
797
+
798
+ static int debugCounter = 0;
799
+ if (debugCounter++ % 30 == 0) {
800
+ WEBM_VLOG(@"drawInMTKView: latest=%d, lastRendered=%d, isPlaying=%d, isAnimating=%d",
801
+ frame ? frame.frameIndex : -1, _lastRenderedFrame, _player.isPlaying, _isAnimating);
802
+ }
803
+
804
+ if (frame && (frame.frameIndex != _lastRenderedFrame || _lastRenderedFrame == -1)) {
805
+ _lastRenderedFrame = frame.frameIndex;
806
+ [self renderFrame:frame];
807
+ }
808
+ }
809
+
810
+ #pragma mark - AlphaVideoPlayerDelegate
811
+
812
+ - (void)playerDidLoad:(int)totalFrames width:(int)width height:(int)height {
813
+ WEBM_VLOG(@"AlphaVideoMetalView loaded: %d frames, %dx%d", totalFrames, width, height);
814
+
815
+ // Update config from player settings
816
+ AlphaVideoConfig *config = [_player getConfig];
817
+ config.loopPlayback = _loop;
818
+ config.playbackSpeed = _playbackSpeed;
819
+ [_player setConfig:config];
820
+
821
+ // The C++ clock advances frames at the file's rate; redraw at least that often, or a 60 fps
822
+ // clip would show every other frame. MTKView rounds to a rate the display supports.
823
+ double frameRate = _player.frameRate;
824
+ if (frameRate > 0) {
825
+ self.preferredFramesPerSecond = (NSInteger)ceil(frameRate * MAX(_playbackSpeed, 1.0));
826
+ }
827
+
828
+ // Render first frame
829
+ AlphaVideoFrame *firstFrame = [_player getFrame:0];
830
+ if (firstFrame) {
831
+ WEBM_VLOG(@"First frame details: w=%d h=%d yStride=%d uvStride=%d aStride=%d",
832
+ firstFrame.width, firstFrame.height,
833
+ firstFrame.yStride, firstFrame.uvStride, firstFrame.aStride);
834
+
835
+ // Check alpha plane - sample first few bytes
836
+ const uint8_t *aPtr = firstFrame.aPlanePtr;
837
+ if (aPtr) {
838
+ WEBM_VLOG(@"Alpha plane first 8 bytes: %02x %02x %02x %02x %02x %02x %02x %02x",
839
+ aPtr[0], aPtr[1], aPtr[2], aPtr[3], aPtr[4], aPtr[5], aPtr[6], aPtr[7]);
840
+ }
841
+
842
+ // Through draw rather than renderFrame directly: a drawable presented outside the view's
843
+ // draw cycle is handed out again on the next draw, and Metal rejects it.
844
+ self.latestFrame = firstFrame;
845
+ _lastRenderedFrame = -1;
846
+ [self draw];
847
+ WEBM_VLOG(@"First frame rendered via C++ → Metal GPU pipeline");
848
+ }
849
+
850
+ if (_autoPlay) {
851
+ [_player play];
852
+ // Start MTKView's continuous rendering loop
853
+ self.paused = NO;
854
+ _isAnimating = YES;
855
+ WEBM_VLOG(@"MTKView rendering started for animation (paused=%d, bounds=%.0fx%.0f, delegate=%@)",
856
+ self.paused, self.bounds.size.width, self.bounds.size.height, self.delegate);
857
+ }
858
+
859
+ if ([_playerDelegate respondsToSelector:@selector(playerDidLoad:width:height:)]) {
860
+ [_playerDelegate playerDidLoad:totalFrames width:width height:height];
861
+ }
862
+ }
863
+
864
+ - (void)playerDidReceiveFrame:(AlphaVideoFrame *)frame {
865
+ // Store the latest frame for display link to render
866
+ // This is thread-safe due to atomic property
867
+ self.latestFrame = frame;
868
+
869
+ // While paused the view's draw loop is stopped, but a seek still has to show its frame.
870
+ if (!_isAnimating) {
871
+ dispatch_async(dispatch_get_main_queue(), ^{
872
+ if (self.paused) [self draw];
873
+ });
874
+ }
875
+
876
+ // Forward to external delegate if needed
877
+ if ([_playerDelegate respondsToSelector:@selector(playerDidReceiveFrame:)]) {
878
+ [_playerDelegate playerDidReceiveFrame:frame];
879
+ }
880
+ }
881
+
882
+ - (void)playerDidStartPlaying {
883
+ // Start MTKView's continuous rendering loop
884
+ self.paused = NO;
885
+ _isAnimating = YES;
886
+ WEBM_VLOG(@"MTKView rendering started (playerDidStartPlaying)");
887
+
888
+ if ([_playerDelegate respondsToSelector:@selector(playerDidStartPlaying)]) {
889
+ [_playerDelegate playerDidStartPlaying];
890
+ }
891
+ }
892
+
893
+ - (void)playerDidPause {
894
+ // Pause MTKView's rendering loop
895
+ self.paused = YES;
896
+ _isAnimating = NO;
897
+ WEBM_VLOG(@"MTKView rendering paused");
898
+
899
+ if ([_playerDelegate respondsToSelector:@selector(playerDidPause)]) {
900
+ [_playerDelegate playerDidPause];
901
+ }
902
+ }
903
+
904
+ - (void)playerDidStop {
905
+ self.paused = YES;
906
+ _isAnimating = NO;
907
+ _lastRenderedFrame = -1;
908
+ self.latestFrame = nil;
909
+ if ([_playerDelegate respondsToSelector:@selector(playerDidStop)]) {
910
+ [_playerDelegate playerDidStop];
911
+ }
912
+ }
913
+
914
+ - (void)playerDidLoop {
915
+ // Reset last rendered frame on loop to ensure smooth transition
916
+ _lastRenderedFrame = -1;
917
+
918
+ if ([_playerDelegate respondsToSelector:@selector(playerDidLoop)]) {
919
+ [_playerDelegate playerDidLoop];
920
+ }
921
+ }
922
+
923
+ - (void)playerDidEnd {
924
+ // Stop MTKView rendering when playback ends
925
+ self.paused = YES;
926
+ _isAnimating = NO;
927
+ _lastRenderedFrame = -1;
928
+ self.latestFrame = nil;
929
+ WEBM_VLOG(@"MTKView rendering stopped (playback ended)");
930
+
931
+ if ([_playerDelegate respondsToSelector:@selector(playerDidEnd)]) {
932
+ [_playerDelegate playerDidEnd];
933
+ }
934
+ }
935
+
936
+ - (void)playerDidEncounterError:(NSString *)error code:(nullable NSString *)code {
937
+ NSLog(@"AlphaVideoMetalView error: %@", error);
938
+ if ([_playerDelegate respondsToSelector:@selector(playerDidEncounterError:code:)]) {
939
+ [_playerDelegate playerDidEncounterError:error code:code];
940
+ }
941
+ }
942
+
943
+ - (void)playerPreloadComplete:(int)totalFrames timeMs:(double)timeMs {
944
+ WEBM_VLOG(@"Preload complete: %d frames in %.1fms (C++ core)", totalFrames, timeMs);
945
+ if ([_playerDelegate respondsToSelector:@selector(playerPreloadComplete:timeMs:)]) {
946
+ [_playerDelegate playerPreloadComplete:totalFrames timeMs:timeMs];
947
+ }
948
+ }
949
+
950
+ @end
951
+
952
+ //==============================================================================
953
+ // Range readers
954
+ //==============================================================================
955
+
956
+ @implementation AlphaVideoPlayer (SourceReader)
957
+
958
+ - (BOOL)loadFromReader:(AlphaVideoSourceReader *)reader {
959
+ return _player->loadFromReader(reader.rangeReader);
960
+ }
961
+
962
+ @end
963
+
964
+ @implementation AlphaVideoMetalView (SourceReader)
965
+
966
+ - (BOOL)loadFromReader:(AlphaVideoSourceReader *)reader {
967
+ self.sourcePath = nil;
968
+ return [self.player loadFromReader:reader];
969
+ }
970
+
971
+ @end
972
+
973
+ //==============================================================================
974
+ // Local sources
975
+ //==============================================================================
976
+
977
+ NSString *AlphaVideoLocalPathForSource(NSString *uri) {
978
+ if ([uri hasPrefix:@"asset://"]) {
979
+ NSString *relative = [uri substringFromIndex:@"asset://".length];
980
+ relative = [relative stringByRemovingPercentEncoding] ?: relative;
981
+ while ([relative hasPrefix:@"/"]) {
982
+ relative = [relative substringFromIndex:1];
983
+ }
984
+ return relative.length > 0 ? [[NSBundle mainBundle].resourcePath stringByAppendingPathComponent:relative] : nil;
985
+ }
986
+ if ([uri hasPrefix:@"file://"]) {
987
+ NSURL *url = [NSURL URLWithString:uri];
988
+ return url.isFileURL ? url.path : [uri substringFromIndex:@"file://".length];
989
+ }
990
+ if ([uri hasPrefix:@"/"]) {
991
+ return uri;
992
+ }
993
+ return nil;
994
+ }