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.
- package/LICENSE +21 -0
- package/README.md +908 -2
- package/THIRD_PARTY_NOTICES.md +30 -0
- package/android/av1/build.gradle +67 -0
- package/android/av1/consumer-rules.pro +4 -0
- package/android/av1/src/main/cpp/Av1Codec_jni.cpp +8 -0
- package/android/av1/src/main/cpp/CMakeLists.txt +34 -0
- package/android/av1/src/main/java/dev/mediacache/alphavideo/av1/Av1Codec.java +21 -0
- package/android/build.gradle +90 -0
- package/android/consumer-rules.pro +2 -0
- package/android/gradle.properties +1 -0
- package/android/publish.gradle +82 -0
- package/android/settings.gradle +5 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/CMakeLists.txt +59 -0
- package/android/src/main/java/dev/mediacache/alphavideo/AlphaVideoCodecs.java +73 -0
- package/android/src/main/java/dev/mediacache/alphavideo/AlphaVideoView.java +1982 -0
- package/android/src/main/java/dev/mediacache/alphavideo/BufferedDualStreamPlayer.java +978 -0
- package/android/src/main/java/dev/mediacache/alphavideo/Fetcher.java +37 -0
- package/android/src/main/java/dev/mediacache/alphavideo/HttpFetcher.java +156 -0
- package/android/src/main/java/dev/mediacache/alphavideo/MediaCodecColor.java +47 -0
- package/android/src/main/java/dev/mediacache/alphavideo/MediaCodecDecoders.java +135 -0
- package/android/src/main/java/dev/mediacache/alphavideo/RangeSourceLink.java +54 -0
- package/android/src/main/java/dev/mediacache/alphavideo/SourceReader.java +174 -0
- package/android/src/main/java/dev/mediacache/alphavideo/SurfaceTextureVideoDecoder.java +802 -0
- package/android/src/main/java/dev/mediacache/alphavideo/WebMAlphaPlayerNative.java +95 -0
- package/android/src/main/java/dev/mediacache/alphavideo/WebMFrame.java +54 -0
- package/android/src/main/java/dev/mediacache/alphavideo/WebMSource.java +160 -0
- package/android/src/rangesource/java/dev/mediacache/alphavideo/AppDataSource.java +50 -0
- package/android/vpx/build.gradle +67 -0
- package/android/vpx/consumer-rules.pro +4 -0
- package/android/vpx/src/main/cpp/CMakeLists.txt +34 -0
- package/android/vpx/src/main/cpp/VpxCodec_jni.cpp +8 -0
- package/android/vpx/src/main/java/dev/mediacache/alphavideo/vpx/VpxCodec.java +21 -0
- package/codecs/av1/Dav1dDecoderPlugin.cpp +157 -0
- package/codecs/av1/Dav1dDecoderPlugin.h +17 -0
- package/codecs/vpx/VpxDecoderPlugin.cpp +103 -0
- package/codecs/vpx/VpxDecoderPlugin.h +17 -0
- package/cpp/AlphaDemuxer.cpp +984 -0
- package/cpp/AlphaDemuxer.h +123 -0
- package/cpp/AlphaVideoDecoderPlugin.h +84 -0
- package/cpp/Av1SequenceHeader.cpp +175 -0
- package/cpp/Av1SequenceHeader.h +25 -0
- package/cpp/ByteSource.cpp +116 -0
- package/cpp/ByteSource.h +59 -0
- package/cpp/RangeSourceClient.h +40 -0
- package/cpp/VideoDecoder.cpp +157 -0
- package/cpp/VideoDecoder.h +83 -0
- package/cpp/WebMAlphaPlayer.cpp +1666 -0
- package/cpp/WebMAlphaPlayer.h +389 -0
- package/cpp/WebMAlphaPlayer_jni.cpp +1380 -0
- package/cpp/YuvToRgb.cpp +39 -0
- package/cpp/YuvToRgb.h +40 -0
- package/ios/Sources/AlphaVideo/AlphaVideoCodecs.mm +63 -0
- package/ios/Sources/AlphaVideo/AlphaVideoInternal.h +70 -0
- package/ios/Sources/AlphaVideo/AlphaVideoPlayer.mm +994 -0
- package/ios/Sources/AlphaVideo/AlphaVideoSource.mm +476 -0
- package/ios/Sources/AlphaVideo/AlphaVideoView.mm +374 -0
- package/ios/Sources/AlphaVideo/include/AlphaVideo.h +303 -0
- package/ios/Sources/AlphaVideoAV1/AlphaVideoAV1Codec.mm +16 -0
- package/ios/Sources/AlphaVideoAV1/include/AlphaVideoAV1.h +18 -0
- package/ios/Sources/AlphaVideoVPX/AlphaVideoVPXCodec.mm +16 -0
- package/ios/Sources/AlphaVideoVPX/include/AlphaVideoVPX.h +17 -0
- package/lib/commonjs/AlphaVideoView.js +187 -0
- package/lib/commonjs/AlphaVideoView.js.map +1 -0
- package/lib/commonjs/AlphaVideoViewNativeComponent.js +47 -0
- package/lib/commonjs/AlphaVideoViewNativeComponent.js.map +1 -0
- package/lib/commonjs/AlphaVideoViewNativeComponent.web.js +11 -0
- package/lib/commonjs/AlphaVideoViewNativeComponent.web.js.map +1 -0
- package/lib/commonjs/NativeAlphaVideoModule.js +10 -0
- package/lib/commonjs/NativeAlphaVideoModule.js.map +1 -0
- package/lib/commonjs/NativeAlphaVideoModule.web.js +9 -0
- package/lib/commonjs/NativeAlphaVideoModule.web.js.map +1 -0
- package/lib/commonjs/WebPlayer.js +12 -0
- package/lib/commonjs/WebPlayer.js.map +1 -0
- package/lib/commonjs/WebPlayer.web.js +115 -0
- package/lib/commonjs/WebPlayer.web.js.map +1 -0
- package/lib/commonjs/index.js +124 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/module/AlphaVideoView.js +187 -0
- package/lib/module/AlphaVideoView.js.map +1 -0
- package/lib/module/AlphaVideoViewNativeComponent.js +47 -0
- package/lib/module/AlphaVideoViewNativeComponent.js.map +1 -0
- package/lib/module/AlphaVideoViewNativeComponent.web.js +11 -0
- package/lib/module/AlphaVideoViewNativeComponent.web.js.map +1 -0
- package/lib/module/NativeAlphaVideoModule.js +10 -0
- package/lib/module/NativeAlphaVideoModule.js.map +1 -0
- package/lib/module/NativeAlphaVideoModule.web.js +9 -0
- package/lib/module/NativeAlphaVideoModule.web.js.map +1 -0
- package/lib/module/WebPlayer.js +12 -0
- package/lib/module/WebPlayer.js.map +1 -0
- package/lib/module/WebPlayer.web.js +115 -0
- package/lib/module/WebPlayer.web.js.map +1 -0
- package/lib/module/index.js +124 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/react-native/src/AlphaVideoView.d.ts +189 -0
- package/lib/typescript/react-native/src/AlphaVideoView.d.ts.map +1 -0
- package/lib/typescript/react-native/src/AlphaVideoViewNativeComponent.d.ts +109 -0
- package/lib/typescript/react-native/src/AlphaVideoViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/react-native/src/AlphaVideoViewNativeComponent.web.d.ts +5 -0
- package/lib/typescript/react-native/src/AlphaVideoViewNativeComponent.web.d.ts.map +1 -0
- package/lib/typescript/react-native/src/NativeAlphaVideoModule.d.ts +21 -0
- package/lib/typescript/react-native/src/NativeAlphaVideoModule.d.ts.map +1 -0
- package/lib/typescript/react-native/src/NativeAlphaVideoModule.web.d.ts +3 -0
- package/lib/typescript/react-native/src/NativeAlphaVideoModule.web.d.ts.map +1 -0
- package/lib/typescript/react-native/src/WebPlayer.d.ts +3 -0
- package/lib/typescript/react-native/src/WebPlayer.d.ts.map +1 -0
- package/lib/typescript/react-native/src/WebPlayer.web.d.ts +9 -0
- package/lib/typescript/react-native/src/WebPlayer.web.d.ts.map +1 -0
- package/lib/typescript/react-native/src/index.d.ts +77 -0
- package/lib/typescript/react-native/src/index.d.ts.map +1 -0
- package/licenses/dav1d-COPYING.txt +23 -0
- package/licenses/libvpx-LICENSE.txt +31 -0
- package/licenses/libvpx-PATENTS.txt +23 -0
- package/licenses/libwebm-LICENSE.txt +30 -0
- package/licenses/libwebm-PATENTS.txt +23 -0
- package/package.json +152 -4
- package/prebuilt/android/arm64-v8a/lib/libdav1d.a +0 -0
- package/prebuilt/android/arm64-v8a/lib/libvpx.a +0 -0
- package/prebuilt/android/armeabi-v7a/lib/libdav1d.a +0 -0
- package/prebuilt/android/armeabi-v7a/lib/libvpx.a +0 -0
- package/prebuilt/android/x86_64/lib/libdav1d.a +0 -0
- package/prebuilt/android/x86_64/lib/libvpx.a +0 -0
- package/prebuilt/include/dav1d/common.h +94 -0
- package/prebuilt/include/dav1d/data.h +117 -0
- package/prebuilt/include/dav1d/dav1d.h +329 -0
- package/prebuilt/include/dav1d/headers.h +440 -0
- package/prebuilt/include/dav1d/picture.h +157 -0
- package/prebuilt/include/dav1d/version.h +50 -0
- package/prebuilt/include/vpx/vp8.h +136 -0
- package/prebuilt/include/vpx/vp8dx.h +228 -0
- package/prebuilt/include/vpx/vpx_codec.h +475 -0
- package/prebuilt/include/vpx/vpx_decoder.h +365 -0
- package/prebuilt/include/vpx/vpx_frame_buffer.h +83 -0
- package/prebuilt/include/vpx/vpx_image.h +209 -0
- package/prebuilt/include/vpx/vpx_integer.h +40 -0
- package/prebuilt/ios/lib/libdav1d.a +0 -0
- package/prebuilt/ios/lib/libvpx.a +0 -0
- package/prebuilt/ios-simulator/include/dav1d/common.h +94 -0
- package/prebuilt/ios-simulator/include/dav1d/data.h +117 -0
- package/prebuilt/ios-simulator/include/dav1d/dav1d.h +329 -0
- package/prebuilt/ios-simulator/include/dav1d/headers.h +440 -0
- package/prebuilt/ios-simulator/include/dav1d/picture.h +157 -0
- package/prebuilt/ios-simulator/include/dav1d/version.h +50 -0
- package/prebuilt/ios-simulator/include/vpx/vp8.h +136 -0
- package/prebuilt/ios-simulator/include/vpx/vp8dx.h +228 -0
- package/prebuilt/ios-simulator/include/vpx/vpx_codec.h +475 -0
- package/prebuilt/ios-simulator/include/vpx/vpx_decoder.h +365 -0
- package/prebuilt/ios-simulator/include/vpx/vpx_frame_buffer.h +83 -0
- package/prebuilt/ios-simulator/include/vpx/vpx_image.h +209 -0
- package/prebuilt/ios-simulator/include/vpx/vpx_integer.h +40 -0
- package/prebuilt/ios-simulator/lib/libdav1d.a +0 -0
- package/prebuilt/ios-simulator/lib/libvpx.a +0 -0
- package/react-native/android/CMakeLists.txt +14 -0
- package/react-native/android/build.gradle +113 -0
- package/react-native/android/src/main/AndroidManifest.xml +1 -0
- package/react-native/android/src/main/java/dev/mediacache/alphavideo/reactnative/AlphaVideoModule.java +102 -0
- package/react-native/android/src/main/java/dev/mediacache/alphavideo/reactnative/AlphaVideoPackage.java +50 -0
- package/react-native/android/src/main/java/dev/mediacache/alphavideo/reactnative/AlphaVideoViewManager.java +317 -0
- package/react-native/android/src/newarch/java/dev/mediacache/alphavideo/reactnative/AlphaVideoModuleSpec.java +10 -0
- package/react-native/android/src/newarch/java/dev/mediacache/alphavideo/reactnative/AlphaVideoViewManagerSpec.java +23 -0
- package/react-native/android/src/norangesource/java/dev/mediacache/alphavideo/AppDataSource.java +15 -0
- package/react-native/android/src/oldarch/java/dev/mediacache/alphavideo/reactnative/AlphaVideoModuleSpec.java +30 -0
- package/react-native/android/src/oldarch/java/dev/mediacache/alphavideo/reactnative/AlphaVideoViewManagerSpec.java +61 -0
- package/react-native/ios/RNAlphaVideoViewComponentView.mm +173 -0
- package/react-native/ios/RNAlphaVideoViewManager.mm +147 -0
- package/react-native/src/AlphaVideoView.tsx +329 -0
- package/react-native/src/AlphaVideoViewNativeComponent.ts +132 -0
- package/react-native/src/AlphaVideoViewNativeComponent.web.ts +5 -0
- package/react-native/src/NativeAlphaVideoModule.ts +22 -0
- package/react-native/src/NativeAlphaVideoModule.web.ts +2 -0
- package/react-native/src/WebPlayer.tsx +7 -0
- package/react-native/src/WebPlayer.web.tsx +87 -0
- package/react-native/src/index.tsx +144 -0
- package/react-native-alpha-video.podspec +99 -0
- package/react-native.config.js +12 -0
- package/scripts/build_codecs_android.sh +246 -0
- package/scripts/build_codecs_ios.sh +31 -0
- package/scripts/build_codecs_ios_device.sh +210 -0
- package/scripts/build_codecs_ios_simulator.sh +256 -0
- package/scripts/build_wasm.sh +230 -0
- package/scripts/build_xcframework.sh +49 -0
- package/scripts/fetch-prebuilt.sh +48 -0
- package/scripts/package_release.sh +107 -0
- package/scripts/publish-android.sh +85 -0
- package/scripts/vendor_range_source.sh +47 -0
- package/third_party/libwebm/AUTHORS.TXT +5 -0
- package/third_party/libwebm/LICENSE.TXT +30 -0
- package/third_party/libwebm/PATENTS.TXT +23 -0
- package/third_party/libwebm/README.md +7 -0
- package/third_party/libwebm/webm_parser/include/webm/buffer_reader.h +138 -0
- package/third_party/libwebm/webm_parser/include/webm/callback.h +363 -0
- package/third_party/libwebm/webm_parser/include/webm/dom_types.h +1781 -0
- package/third_party/libwebm/webm_parser/include/webm/element.h +208 -0
- package/third_party/libwebm/webm_parser/include/webm/file_reader.h +103 -0
- package/third_party/libwebm/webm_parser/include/webm/id.h +1085 -0
- package/third_party/libwebm/webm_parser/include/webm/istream_reader.h +99 -0
- package/third_party/libwebm/webm_parser/include/webm/reader.h +94 -0
- package/third_party/libwebm/webm_parser/include/webm/status.h +166 -0
- package/third_party/libwebm/webm_parser/include/webm/webm_parser.h +133 -0
- package/third_party/libwebm/webm_parser/src/ancestory.cc +334 -0
- package/third_party/libwebm/webm_parser/src/ancestory.h +83 -0
- package/third_party/libwebm/webm_parser/src/audio_parser.h +84 -0
- package/third_party/libwebm/webm_parser/src/bit_utils.cc +26 -0
- package/third_party/libwebm/webm_parser/src/bit_utils.h +24 -0
- package/third_party/libwebm/webm_parser/src/block_additions_parser.h +30 -0
- package/third_party/libwebm/webm_parser/src/block_group_parser.h +71 -0
- package/third_party/libwebm/webm_parser/src/block_header_parser.cc +76 -0
- package/third_party/libwebm/webm_parser/src/block_header_parser.h +65 -0
- package/third_party/libwebm/webm_parser/src/block_more_parser.h +32 -0
- package/third_party/libwebm/webm_parser/src/block_parser.cc +285 -0
- package/third_party/libwebm/webm_parser/src/block_parser.h +126 -0
- package/third_party/libwebm/webm_parser/src/bool_parser.h +95 -0
- package/third_party/libwebm/webm_parser/src/buffer_reader.cc +110 -0
- package/third_party/libwebm/webm_parser/src/byte_parser.h +144 -0
- package/third_party/libwebm/webm_parser/src/callback.cc +155 -0
- package/third_party/libwebm/webm_parser/src/chapter_atom_parser.h +42 -0
- package/third_party/libwebm/webm_parser/src/chapter_display_parser.h +34 -0
- package/third_party/libwebm/webm_parser/src/chapters_parser.h +28 -0
- package/third_party/libwebm/webm_parser/src/cluster_parser.h +48 -0
- package/third_party/libwebm/webm_parser/src/colour_parser.h +55 -0
- package/third_party/libwebm/webm_parser/src/content_enc_aes_settings_parser.h +32 -0
- package/third_party/libwebm/webm_parser/src/content_encoding_parser.h +38 -0
- package/third_party/libwebm/webm_parser/src/content_encodings_parser.h +30 -0
- package/third_party/libwebm/webm_parser/src/content_encryption_parser.h +37 -0
- package/third_party/libwebm/webm_parser/src/cue_point_parser.h +38 -0
- package/third_party/libwebm/webm_parser/src/cue_track_positions_parser.h +39 -0
- package/third_party/libwebm/webm_parser/src/cues_parser.h +27 -0
- package/third_party/libwebm/webm_parser/src/date_parser.cc +72 -0
- package/third_party/libwebm/webm_parser/src/date_parser.h +64 -0
- package/third_party/libwebm/webm_parser/src/ebml_parser.h +48 -0
- package/third_party/libwebm/webm_parser/src/edition_entry_parser.h +35 -0
- package/third_party/libwebm/webm_parser/src/element_parser.h +102 -0
- package/third_party/libwebm/webm_parser/src/file_reader.cc +142 -0
- package/third_party/libwebm/webm_parser/src/float_parser.cc +77 -0
- package/third_party/libwebm/webm_parser/src/float_parser.h +65 -0
- package/third_party/libwebm/webm_parser/src/id_element_parser.cc +49 -0
- package/third_party/libwebm/webm_parser/src/id_element_parser.h +56 -0
- package/third_party/libwebm/webm_parser/src/id_parser.cc +75 -0
- package/third_party/libwebm/webm_parser/src/id_parser.h +45 -0
- package/third_party/libwebm/webm_parser/src/info_parser.h +44 -0
- package/third_party/libwebm/webm_parser/src/int_parser.h +121 -0
- package/third_party/libwebm/webm_parser/src/istream_reader.cc +133 -0
- package/third_party/libwebm/webm_parser/src/master_parser.cc +298 -0
- package/third_party/libwebm/webm_parser/src/master_parser.h +230 -0
- package/third_party/libwebm/webm_parser/src/master_value_parser.h +533 -0
- package/third_party/libwebm/webm_parser/src/mastering_metadata_parser.h +57 -0
- package/third_party/libwebm/webm_parser/src/parser.h +34 -0
- package/third_party/libwebm/webm_parser/src/parser_utils.cc +34 -0
- package/third_party/libwebm/webm_parser/src/parser_utils.h +64 -0
- package/third_party/libwebm/webm_parser/src/projection_parser.h +40 -0
- package/third_party/libwebm/webm_parser/src/recursive_parser.h +93 -0
- package/third_party/libwebm/webm_parser/src/seek_head_parser.h +27 -0
- package/third_party/libwebm/webm_parser/src/seek_parser.h +37 -0
- package/third_party/libwebm/webm_parser/src/segment_parser.cc +86 -0
- package/third_party/libwebm/webm_parser/src/segment_parser.h +53 -0
- package/third_party/libwebm/webm_parser/src/simple_tag_parser.h +37 -0
- package/third_party/libwebm/webm_parser/src/size_parser.cc +56 -0
- package/third_party/libwebm/webm_parser/src/size_parser.h +43 -0
- package/third_party/libwebm/webm_parser/src/skip_callback.h +63 -0
- package/third_party/libwebm/webm_parser/src/skip_parser.cc +59 -0
- package/third_party/libwebm/webm_parser/src/skip_parser.h +35 -0
- package/third_party/libwebm/webm_parser/src/slices_parser.h +30 -0
- package/third_party/libwebm/webm_parser/src/tag_parser.h +37 -0
- package/third_party/libwebm/webm_parser/src/tags_parser.h +27 -0
- package/third_party/libwebm/webm_parser/src/targets_parser.h +35 -0
- package/third_party/libwebm/webm_parser/src/time_slice_parser.h +30 -0
- package/third_party/libwebm/webm_parser/src/track_entry_parser.h +65 -0
- package/third_party/libwebm/webm_parser/src/tracks_parser.h +27 -0
- package/third_party/libwebm/webm_parser/src/unknown_parser.cc +49 -0
- package/third_party/libwebm/webm_parser/src/unknown_parser.h +38 -0
- package/third_party/libwebm/webm_parser/src/var_int_parser.cc +71 -0
- package/third_party/libwebm/webm_parser/src/var_int_parser.h +56 -0
- package/third_party/libwebm/webm_parser/src/video_parser.h +122 -0
- package/third_party/libwebm/webm_parser/src/virtual_block_parser.cc +72 -0
- package/third_party/libwebm/webm_parser/src/virtual_block_parser.h +69 -0
- package/third_party/libwebm/webm_parser/src/void_parser.cc +49 -0
- package/third_party/libwebm/webm_parser/src/void_parser.h +41 -0
- package/third_party/libwebm/webm_parser/src/webm_parser.cc +282 -0
- package/third_party/range-source/LICENSE +21 -0
- package/third_party/range-source/README.md +8 -0
- package/third_party/range-source/include/range_source.h +395 -0
- 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
|
+
}
|