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