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,978 @@
|
|
|
1
|
+
package dev.mediacache.alphavideo;
|
|
2
|
+
|
|
3
|
+
import android.media.MediaFormat;
|
|
4
|
+
import android.os.Handler;
|
|
5
|
+
import android.os.HandlerThread;
|
|
6
|
+
import android.util.Log;
|
|
7
|
+
|
|
8
|
+
import java.nio.ByteBuffer;
|
|
9
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
|
10
|
+
import java.util.concurrent.atomic.AtomicInteger;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Direct OES dual-stream alpha player (VP8, VP9 or AV1).
|
|
14
|
+
*
|
|
15
|
+
* Colour and alpha come from two tracks, or from one VP8 or VP9 track whose alpha is in each block's
|
|
16
|
+
* BlockAdditional (a standard alpha WebM); the native demuxer hands both out as stream 0 and 1.
|
|
17
|
+
*
|
|
18
|
+
* Architecture (per instructions3.json):
|
|
19
|
+
* - Native WebM demux → MediaCodec (Surface) → SurfaceTexture → OES texture
|
|
20
|
+
* - Single GL draw call composites both OES textures in fragment shader
|
|
21
|
+
* - NO intermediate FBO ring buffer — SurfaceTexture IS the buffer
|
|
22
|
+
*
|
|
23
|
+
* Timing model:
|
|
24
|
+
* - MediaCodec + SurfaceTexture control decode cadence and buffering
|
|
25
|
+
* - Playback clock (frame-index based) controls presentation rate
|
|
26
|
+
* - Both SurfaceTextures are drained at frame rate to prevent backpressure
|
|
27
|
+
*
|
|
28
|
+
* Looping protocol:
|
|
29
|
+
* 1. Stop feeding packets
|
|
30
|
+
* 2. Drain SurfaceTextures via updateTexImage()
|
|
31
|
+
* 3. Flush both MediaCodecs
|
|
32
|
+
* 4. Seek demuxer to frame 0
|
|
33
|
+
* 5. Reset frame index, restart feeding
|
|
34
|
+
*/
|
|
35
|
+
public class BufferedDualStreamPlayer implements SurfaceTextureVideoDecoder.FrameAvailableCallback {
|
|
36
|
+
private static final String TAG = "DualStreamOES";
|
|
37
|
+
|
|
38
|
+
// Native demuxer handle
|
|
39
|
+
private long nativeDemuxerPtr = 0;
|
|
40
|
+
private String[] decoderNames;
|
|
41
|
+
|
|
42
|
+
// Surface-based decoders (OES texture output)
|
|
43
|
+
private SurfaceTextureVideoDecoder colorDecoder;
|
|
44
|
+
private SurfaceTextureVideoDecoder alphaDecoder;
|
|
45
|
+
|
|
46
|
+
// Video info
|
|
47
|
+
private int width;
|
|
48
|
+
private int height;
|
|
49
|
+
private int totalFrames;
|
|
50
|
+
private double frameRate;
|
|
51
|
+
// For a shader converting the decoders' raw YUV itself; set on load
|
|
52
|
+
private volatile float[] colorYuvToRgb;
|
|
53
|
+
private volatile float[] alphaRange;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Wall-clock rate multiplier. Volatile: written from the UI thread, read by the playback loop.
|
|
57
|
+
*
|
|
58
|
+
* Scales only how long each frame is shown. The frame index and the PTS handed to the
|
|
59
|
+
* decoders are in the media's own timebase and must not move, or a rate change would seek
|
|
60
|
+
* as well as accelerate.
|
|
61
|
+
*/
|
|
62
|
+
private volatile double playbackSpeed = 1.0;
|
|
63
|
+
private double duration;
|
|
64
|
+
|
|
65
|
+
// State
|
|
66
|
+
private final AtomicBoolean isLoaded = new AtomicBoolean(false);
|
|
67
|
+
private final AtomicBoolean isPlaying = new AtomicBoolean(false);
|
|
68
|
+
private final AtomicBoolean shouldStop = new AtomicBoolean(false);
|
|
69
|
+
private final AtomicInteger playbackFrameIndex = new AtomicInteger(0);
|
|
70
|
+
|
|
71
|
+
// A seek the demux thread hasn't carried out yet (-1: none); a later seek replaces it
|
|
72
|
+
private final AtomicInteger seekRequest = new AtomicInteger(-1);
|
|
73
|
+
// From a seek call until the decoders are repositioned; the playback loop leaves their targets alone
|
|
74
|
+
private final AtomicBoolean seeking = new AtomicBoolean(false);
|
|
75
|
+
// Frame to show once both decoders have it, playing or paused: the first one after load or a seek (-1: none)
|
|
76
|
+
private final AtomicInteger presentFrame = new AtomicInteger(-1);
|
|
77
|
+
// Frame pairs consumeFrames() has taken; a redraw for some other reason may show presentFrame first
|
|
78
|
+
private final AtomicInteger consumedPairs = new AtomicInteger(0);
|
|
79
|
+
private volatile int consumedPairsAtPresent = 0;
|
|
80
|
+
// Presentation time of the last pair consumeFrames() took (-1: none since the last flush)
|
|
81
|
+
private volatile long consumedPtsUs = -1;
|
|
82
|
+
// How long a loop waits for the last frame to be drawn once playback has passed it, in case a
|
|
83
|
+
// decoder never outputs it
|
|
84
|
+
private static final long LAST_FRAME_WAIT_NS = 250_000_000L;
|
|
85
|
+
|
|
86
|
+
// Frame availability — set by onFrameAvailable, used only for initial sync
|
|
87
|
+
// After initial frames arrive, the playback loop renders unconditionally at frame rate
|
|
88
|
+
private final AtomicBoolean colorDirty = new AtomicBoolean(false);
|
|
89
|
+
private final AtomicBoolean alphaDirty = new AtomicBoolean(false);
|
|
90
|
+
|
|
91
|
+
// Transform matrices (updated on GL thread by consumeFrames())
|
|
92
|
+
private final float[] colorTransformMatrix = new float[16];
|
|
93
|
+
private final float[] alphaTransformMatrix = new float[16];
|
|
94
|
+
|
|
95
|
+
// Decode tracking
|
|
96
|
+
private volatile int decodeFrameIndex = 0;
|
|
97
|
+
|
|
98
|
+
// Threading
|
|
99
|
+
private HandlerThread demuxThread;
|
|
100
|
+
private Handler demuxHandler;
|
|
101
|
+
private HandlerThread playbackThread;
|
|
102
|
+
private Handler playbackHandler;
|
|
103
|
+
|
|
104
|
+
// GL operations executor (runs tasks on GL thread)
|
|
105
|
+
private GLExecutor glExecutor;
|
|
106
|
+
|
|
107
|
+
// Configuration
|
|
108
|
+
private boolean loop = true;
|
|
109
|
+
private volatile boolean verbose = false;
|
|
110
|
+
private RenderCallback renderCallback;
|
|
111
|
+
private EventCallback eventCallback;
|
|
112
|
+
|
|
113
|
+
// Performance tracking
|
|
114
|
+
private long lastPerfLogTime = 0;
|
|
115
|
+
private int framesRendered = 0;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Callback to signal the view that a frame is ready for compositing.
|
|
119
|
+
* The view should call consumeFrames() on the GL thread, then composite
|
|
120
|
+
* using the OES textures and transform matrices.
|
|
121
|
+
*/
|
|
122
|
+
public interface RenderCallback {
|
|
123
|
+
void onRenderRequested(int frameIndex);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Interface for executing tasks on the GL thread.
|
|
128
|
+
*/
|
|
129
|
+
public interface GLExecutor {
|
|
130
|
+
void executeOnGLThread(Runnable task);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Lifecycle event callback.
|
|
135
|
+
*/
|
|
136
|
+
public interface EventCallback {
|
|
137
|
+
void onLoaded(int totalFrames, int width, int height, double fps, double duration);
|
|
138
|
+
void onError(String error);
|
|
139
|
+
void onLooped();
|
|
140
|
+
void onEnded();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// -------------------------------------------------------------------------
|
|
144
|
+
// Public API
|
|
145
|
+
// -------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
public void setLoop(boolean loop) {
|
|
148
|
+
this.loop = loop;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Lifecycle and performance chatter, only with verbose. Warnings and errors always log. */
|
|
152
|
+
private void logV(String message) {
|
|
153
|
+
if (verbose) Log.i(TAG, message);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
public void setVerbose(boolean verbose) {
|
|
157
|
+
this.verbose = verbose;
|
|
158
|
+
// Propagate to decoders
|
|
159
|
+
SurfaceTextureVideoDecoder cd = colorDecoder;
|
|
160
|
+
SurfaceTextureVideoDecoder ad = alphaDecoder;
|
|
161
|
+
if (cd != null) cd.setVerbose(verbose);
|
|
162
|
+
if (ad != null) ad.setVerbose(verbose);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public void setRenderCallback(RenderCallback callback) {
|
|
166
|
+
this.renderCallback = callback;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public void setEventCallback(EventCallback callback) {
|
|
170
|
+
this.eventCallback = callback;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public void setGLExecutor(GLExecutor executor) {
|
|
174
|
+
this.glExecutor = executor;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Load a dual-stream WebM from bytes [offset, offset + length) of fd (length -1: to end of file).
|
|
179
|
+
* The demuxer duplicates fd, so the caller may close it as soon as this returns.
|
|
180
|
+
* MUST be called from GL thread so OES textures are created in correct EGL context.
|
|
181
|
+
*/
|
|
182
|
+
public boolean loadFromFd(int fd, long offset, long length) {
|
|
183
|
+
if (isLoaded.get()) {
|
|
184
|
+
unload();
|
|
185
|
+
}
|
|
186
|
+
return load(nativeCreateDemuxer(fd, offset, length));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Load through a range reader, which fetches the file as it plays.
|
|
191
|
+
* MUST be called from GL thread so OES textures are created in correct EGL context.
|
|
192
|
+
*/
|
|
193
|
+
boolean loadFromReader(SourceReader reader) {
|
|
194
|
+
if (isLoaded.get()) {
|
|
195
|
+
unload();
|
|
196
|
+
}
|
|
197
|
+
long reference = reader.retain();
|
|
198
|
+
if (reference == 0) return false;
|
|
199
|
+
try {
|
|
200
|
+
return load(nativeCreateDemuxerFromReader(reference));
|
|
201
|
+
} finally {
|
|
202
|
+
SourceReader.release(reference);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private boolean load(long demuxerPtr) {
|
|
207
|
+
try {
|
|
208
|
+
nativeDemuxerPtr = demuxerPtr;
|
|
209
|
+
if (nativeDemuxerPtr == 0) {
|
|
210
|
+
Log.e(TAG, "Failed to create demuxer");
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Get video info: [width, height, totalFrames, frameRate*1000, streamCount, codec]
|
|
215
|
+
int[] videoInfo = nativeGetVideoInfo(nativeDemuxerPtr);
|
|
216
|
+
if (videoInfo == null || videoInfo.length < 6) {
|
|
217
|
+
Log.e(TAG, "Failed to get video info");
|
|
218
|
+
nativeDestroyDemuxer(nativeDemuxerPtr);
|
|
219
|
+
nativeDemuxerPtr = 0;
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
width = videoInfo[0];
|
|
224
|
+
height = videoInfo[1];
|
|
225
|
+
totalFrames = videoInfo[2];
|
|
226
|
+
int fpsMul1000 = videoInfo[3];
|
|
227
|
+
int numStreams = videoInfo[4];
|
|
228
|
+
int codec = videoInfo[5];
|
|
229
|
+
|
|
230
|
+
String mimeType = MediaCodecDecoders.mimeFor(codec);
|
|
231
|
+
|
|
232
|
+
frameRate = fpsMul1000 / 1000.0;
|
|
233
|
+
duration = totalFrames / frameRate;
|
|
234
|
+
|
|
235
|
+
logV("Video: " + width + "x" + height + ", " + totalFrames + " frames, " +
|
|
236
|
+
String.format("%.2f", frameRate) + " fps, " + numStreams + " streams, codec=" + mimeType);
|
|
237
|
+
|
|
238
|
+
if (numStreams < 2) {
|
|
239
|
+
Log.e(TAG, "Not a dual-stream file");
|
|
240
|
+
nativeDestroyDemuxer(nativeDemuxerPtr);
|
|
241
|
+
nativeDemuxerPtr = 0;
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Create decoders with detected MIME type — OES textures are created here (must be on GL thread)
|
|
246
|
+
if (decoderNames == null) {
|
|
247
|
+
Log.e(TAG, "No decoders reserved");
|
|
248
|
+
nativeDestroyDemuxer(nativeDemuxerPtr);
|
|
249
|
+
nativeDemuxerPtr = 0;
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
colorDecoder = new SurfaceTextureVideoDecoder("color", mimeType, decoderNames[0]);
|
|
253
|
+
alphaDecoder = new SurfaceTextureVideoDecoder("alpha", mimeType, decoderNames[1]);
|
|
254
|
+
colorDecoder.setVerbose(verbose);
|
|
255
|
+
alphaDecoder.setVerbose(verbose);
|
|
256
|
+
|
|
257
|
+
// [colour range, colour space, alpha range, alpha space]
|
|
258
|
+
int[] colorInfo = nativeGetColorInfo(nativeDemuxerPtr);
|
|
259
|
+
colorYuvToRgb = WebMAlphaPlayerNative.nativeYuvToRgbFor(colorInfo[0], colorInfo[1], width, height);
|
|
260
|
+
alphaRange = MediaCodecColor.range(colorInfo[2]) == MediaFormat.COLOR_RANGE_FULL
|
|
261
|
+
? new float[] {1f, 0f} : new float[] {255f / 219f, 16f / 255f};
|
|
262
|
+
if (!colorDecoder.configure(width, height, colorInfo[0], colorInfo[1])
|
|
263
|
+
|| !alphaDecoder.configure(width, height, colorInfo[2], colorInfo[3])) {
|
|
264
|
+
Log.e(TAG, "Failed to configure decoders");
|
|
265
|
+
cleanup();
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// onFrameAvailable just sets dirty flags — no FBO copy needed
|
|
270
|
+
colorDecoder.setFrameCallback(this);
|
|
271
|
+
alphaDecoder.setFrameCallback(this);
|
|
272
|
+
|
|
273
|
+
if (!colorDecoder.start() || !alphaDecoder.start()) {
|
|
274
|
+
Log.e(TAG, "Failed to start decoders");
|
|
275
|
+
cleanup();
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Start threads
|
|
280
|
+
demuxThread = new HandlerThread("DemuxThread");
|
|
281
|
+
demuxThread.start();
|
|
282
|
+
demuxHandler = new Handler(demuxThread.getLooper());
|
|
283
|
+
|
|
284
|
+
playbackThread = new HandlerThread("PlaybackThread");
|
|
285
|
+
playbackThread.start();
|
|
286
|
+
playbackHandler = new Handler(playbackThread.getLooper());
|
|
287
|
+
|
|
288
|
+
isLoaded.set(true);
|
|
289
|
+
|
|
290
|
+
// Decoding starts now, playing or not, so that frame 0 shows even when paused.
|
|
291
|
+
shouldStop.set(false);
|
|
292
|
+
playbackFrameIndex.set(0);
|
|
293
|
+
consumedPairsAtPresent = consumedPairs.get();
|
|
294
|
+
presentFrame.set(0);
|
|
295
|
+
demuxHandler.post(this::demuxLoop);
|
|
296
|
+
playbackHandler.post(this::playbackLoop);
|
|
297
|
+
|
|
298
|
+
logV("Loaded successfully (direct OES pipeline)");
|
|
299
|
+
|
|
300
|
+
if (eventCallback != null) {
|
|
301
|
+
eventCallback.onLoaded(totalFrames, width, height, frameRate, duration);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return true;
|
|
305
|
+
|
|
306
|
+
} catch (Exception e) {
|
|
307
|
+
Log.e(TAG, "Load error: " + e.getMessage());
|
|
308
|
+
cleanup();
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Start or resume playback. A clip that doesn't loop and has ended starts over.
|
|
315
|
+
*/
|
|
316
|
+
public void play() {
|
|
317
|
+
if (!isLoaded.get() || isPlaying.get()) return;
|
|
318
|
+
|
|
319
|
+
if (!loop && playbackFrameIndex.get() >= totalFrames && seekRequest.get() < 0) {
|
|
320
|
+
seekToFrame(0);
|
|
321
|
+
}
|
|
322
|
+
framesRendered = 0;
|
|
323
|
+
lastPerfLogTime = System.nanoTime();
|
|
324
|
+
isPlaying.set(true);
|
|
325
|
+
logV("Playing");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Pause playback.
|
|
330
|
+
*/
|
|
331
|
+
public void pause() {
|
|
332
|
+
isPlaying.set(false);
|
|
333
|
+
logV("Paused");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Stop playback and go back to frame 0.
|
|
338
|
+
*/
|
|
339
|
+
public void stop() {
|
|
340
|
+
isPlaying.set(false);
|
|
341
|
+
seekToFrame(0);
|
|
342
|
+
logV("Stopped");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Moves to a frame (clamped to the clip), staying playing or paused; a paused player shows it.
|
|
347
|
+
*
|
|
348
|
+
* The demux thread carries it out: it flushes both decoders, seeks to the keyframe at or
|
|
349
|
+
* before the frame and decodes from there, discarding the frames before it unshown.
|
|
350
|
+
*/
|
|
351
|
+
public void seekToFrame(int frame) {
|
|
352
|
+
if (!isLoaded.get() || totalFrames <= 0) return;
|
|
353
|
+
seeking.set(true);
|
|
354
|
+
seekRequest.set(Math.max(0, Math.min(frame, totalFrames - 1)));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** Moves to the frame showing at {@code seconds}; see {@link #seekToFrame}. */
|
|
358
|
+
public void seekToTime(double seconds) {
|
|
359
|
+
if (!(seconds > 0)) seconds = 0;
|
|
360
|
+
// The epsilon keeps a time computed as frame / fps from rounding down to the frame before.
|
|
361
|
+
seekToFrame((int) Math.floor(seconds * frameRate + 1e-6));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Unload the video.
|
|
366
|
+
*/
|
|
367
|
+
public void unload() {
|
|
368
|
+
logV("Unloading");
|
|
369
|
+
shouldStop.set(true);
|
|
370
|
+
isPlaying.set(false);
|
|
371
|
+
isLoaded.set(false);
|
|
372
|
+
cleanup();
|
|
373
|
+
MediaCodecDecoders.release(decoderNames);
|
|
374
|
+
decoderNames = null;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Changes how fast frames are presented, leaving the playhead where it is.
|
|
379
|
+
*
|
|
380
|
+
* Takes effect on the next frame, so this is safe to call while playing — which is the
|
|
381
|
+
* intended use: a button that speeds its animation up on hover must not also jump.
|
|
382
|
+
*/
|
|
383
|
+
public void setPlaybackSpeed(double speed) {
|
|
384
|
+
playbackSpeed = speed > 0.01 ? speed : 0.01;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Getters
|
|
388
|
+
public boolean isLoaded() { return isLoaded.get(); }
|
|
389
|
+
public double getPlaybackSpeed() { return playbackSpeed; }
|
|
390
|
+
public boolean isPlaying() { return isPlaying.get(); }
|
|
391
|
+
public int getWidth() { return width; }
|
|
392
|
+
public int getHeight() { return height; }
|
|
393
|
+
public int getTotalFrames() { return totalFrames; }
|
|
394
|
+
public double getFrameRate() { return frameRate; }
|
|
395
|
+
public double getDuration() { return duration; }
|
|
396
|
+
public int getCurrentFrame() { return playbackFrameIndex.get(); }
|
|
397
|
+
|
|
398
|
+
public SurfaceTextureVideoDecoder getColorDecoder() { return colorDecoder; }
|
|
399
|
+
public SurfaceTextureVideoDecoder getAlphaDecoder() { return alphaDecoder; }
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Consume pending frames from both SurfaceTextures.
|
|
403
|
+
* MUST be called on the GL thread.
|
|
404
|
+
*
|
|
405
|
+
* Calls updateTexImage() on both decoders to consume any frame that the
|
|
406
|
+
* PTS-gated pollOutput() has released. After consuming, clears the release
|
|
407
|
+
* flags so the decoder knows it can release the next frame on the next cycle.
|
|
408
|
+
*
|
|
409
|
+
* @return true if decoders are valid and textures were updated
|
|
410
|
+
*/
|
|
411
|
+
public boolean consumeFrames() {
|
|
412
|
+
SurfaceTextureVideoDecoder cd = colorDecoder;
|
|
413
|
+
SurfaceTextureVideoDecoder ad = alphaDecoder;
|
|
414
|
+
if (cd == null || ad == null) return false;
|
|
415
|
+
|
|
416
|
+
// Only consume when BOTH decoders have released a frame.
|
|
417
|
+
// This guarantees color and alpha are from the same temporal position.
|
|
418
|
+
// If one decoder is slightly slower, we wait until next render cycle.
|
|
419
|
+
if (!cd.wasFrameReleased() || !ad.wasFrameReleased()) {
|
|
420
|
+
// Don't clear flags — let the slow decoder catch up.
|
|
421
|
+
// Return false to indicate no new synchronized frame pair.
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// A released frame may not have reached its SurfaceTexture yet; the next render latches it
|
|
426
|
+
boolean colorLatched = cd.latchReleasedFrame();
|
|
427
|
+
boolean alphaLatched = ad.latchReleasedFrame();
|
|
428
|
+
if (!colorLatched || !alphaLatched) return false;
|
|
429
|
+
|
|
430
|
+
// Clear release flags so the decoder output loop can release
|
|
431
|
+
// the next frame on the next playback cycle
|
|
432
|
+
cd.clearFrameReleased();
|
|
433
|
+
ad.clearFrameReleased();
|
|
434
|
+
|
|
435
|
+
// Get transform matrices for the vertex shader
|
|
436
|
+
cd.getTransformMatrix(colorTransformMatrix);
|
|
437
|
+
ad.getTransformMatrix(alphaTransformMatrix);
|
|
438
|
+
|
|
439
|
+
consumedPtsUs = cd.getLatchedTimestampNs() / 1000;
|
|
440
|
+
consumedPairs.incrementAndGet();
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Get color OES texture ID for use in compositor shader.
|
|
445
|
+
*/
|
|
446
|
+
public int getColorOesTextureId() {
|
|
447
|
+
SurfaceTextureVideoDecoder cd = colorDecoder;
|
|
448
|
+
return cd != null ? cd.getOesTextureId() : -1;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Get alpha OES texture ID for use in compositor shader.
|
|
453
|
+
*/
|
|
454
|
+
public int getAlphaOesTextureId() {
|
|
455
|
+
SurfaceTextureVideoDecoder ad = alphaDecoder;
|
|
456
|
+
return ad != null ? ad.getOesTextureId() : -1;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get color transform matrix (valid after consumeFrames()).
|
|
461
|
+
*/
|
|
462
|
+
public float[] getColorTransformMatrix() {
|
|
463
|
+
return colorTransformMatrix;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Get alpha transform matrix (valid after consumeFrames()).
|
|
468
|
+
*/
|
|
469
|
+
public float[] getAlphaTransformMatrix() {
|
|
470
|
+
return alphaTransformMatrix;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/** The colour stream's YUV to RGB from its tags, a column-major mat4; null before load. */
|
|
474
|
+
public float[] getColorYuvToRgb() {
|
|
475
|
+
return colorYuvToRgb;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** {scale, offset} taking the alpha stream's luma to 0..1: scale * (y - offset); null before load. */
|
|
479
|
+
public float[] getAlphaRange() {
|
|
480
|
+
return alphaRange;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// -------------------------------------------------------------------------
|
|
484
|
+
// Frame availability callback (from SurfaceTextureVideoDecoder)
|
|
485
|
+
// -------------------------------------------------------------------------
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Called when a decoder has a new frame available on its SurfaceTexture.
|
|
489
|
+
*
|
|
490
|
+
* Per the architecture spec, this ONLY sets a dirty flag and captures metadata.
|
|
491
|
+
* The actual updateTexImage() happens on the GL render thread via consumeFrames().
|
|
492
|
+
*/
|
|
493
|
+
@Override
|
|
494
|
+
public void onFrameAvailable(SurfaceTextureVideoDecoder decoder, int frameIndex, long timestampNs) {
|
|
495
|
+
if (shouldStop.get()) return;
|
|
496
|
+
|
|
497
|
+
if (decoder == colorDecoder) {
|
|
498
|
+
colorDirty.set(true);
|
|
499
|
+
} else if (decoder == alphaDecoder) {
|
|
500
|
+
alphaDirty.set(true);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
@Override
|
|
505
|
+
public void onDecoderError(SurfaceTextureVideoDecoder decoder, String error) {
|
|
506
|
+
Log.e(TAG, "[" + decoder.getLabel() + "] Error: " + error);
|
|
507
|
+
if (eventCallback != null) {
|
|
508
|
+
eventCallback.onError(error);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// -------------------------------------------------------------------------
|
|
513
|
+
// Demux loop — feeds compressed packets to MediaCodec decoders
|
|
514
|
+
// -------------------------------------------------------------------------
|
|
515
|
+
|
|
516
|
+
private int loopCount = 0;
|
|
517
|
+
|
|
518
|
+
private void demuxLoop() {
|
|
519
|
+
logV("Demux loop started");
|
|
520
|
+
|
|
521
|
+
decodeFrameIndex = 0;
|
|
522
|
+
loopCount = 0;
|
|
523
|
+
|
|
524
|
+
while (!shouldStop.get() && isLoaded.get()) {
|
|
525
|
+
if (nativeDemuxerPtr == 0 || colorDecoder == null || alphaDecoder == null) {
|
|
526
|
+
Log.w(TAG, "Demux loop: invalid state, exiting");
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
int seekTarget = seekRequest.getAndSet(-1);
|
|
531
|
+
if (seekTarget >= 0) {
|
|
532
|
+
reposition(seekTarget);
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Read two packets (one color, one alpha)
|
|
537
|
+
Object[] packet1 = nativeReadPacket(nativeDemuxerPtr);
|
|
538
|
+
if (packet1 == null) {
|
|
539
|
+
// End of file
|
|
540
|
+
logV("End of file at frame " + decodeFrameIndex);
|
|
541
|
+
queueEndOfStream();
|
|
542
|
+
|
|
543
|
+
if (loop) {
|
|
544
|
+
// Flushing now would drop the frames still queued in the decoders, so wait
|
|
545
|
+
// until the last one is drawn (paused: until play or a seek).
|
|
546
|
+
waitForLastFrameDrawn();
|
|
547
|
+
if (seekRequest.get() >= 0) continue;
|
|
548
|
+
|
|
549
|
+
loopCount++;
|
|
550
|
+
logV("Loop #" + loopCount + " - handling loop transition");
|
|
551
|
+
|
|
552
|
+
// Detect stuck loop
|
|
553
|
+
if (decodeFrameIndex == 0) {
|
|
554
|
+
Log.e(TAG, "Stuck loop detected: EOF at frame 0");
|
|
555
|
+
if (loopCount > 5) {
|
|
556
|
+
Log.e(TAG, "Too many stuck loops, stopping");
|
|
557
|
+
if (eventCallback != null) {
|
|
558
|
+
eventCallback.onError("Demuxer seek failed - cannot loop");
|
|
559
|
+
}
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
try { Thread.sleep(100); } catch (InterruptedException e) { break; }
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
handleLoop();
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
// The playback loop ends the clip once it has shown the last frame. Until then,
|
|
569
|
+
// or afterwards, a seek resumes reading.
|
|
570
|
+
while (!shouldStop.get() && isLoaded.get() && seekRequest.get() < 0) {
|
|
571
|
+
try { Thread.sleep(10); } catch (InterruptedException e) { break; }
|
|
572
|
+
}
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
Object[] packet2 = nativeReadPacket(nativeDemuxerPtr);
|
|
577
|
+
if (packet2 == null) {
|
|
578
|
+
Log.w(TAG, "Missing second packet for frame " + decodeFrameIndex);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Sort into color (stream 0) and alpha (stream 1)
|
|
583
|
+
int stream1 = (Integer) packet1[0];
|
|
584
|
+
int stream2 = (Integer) packet2[0];
|
|
585
|
+
|
|
586
|
+
Object[] colorPacket;
|
|
587
|
+
Object[] alphaPacket;
|
|
588
|
+
|
|
589
|
+
if (stream1 == 0 && stream2 == 1) {
|
|
590
|
+
colorPacket = packet1;
|
|
591
|
+
alphaPacket = packet2;
|
|
592
|
+
} else if (stream1 == 1 && stream2 == 0) {
|
|
593
|
+
colorPacket = packet2;
|
|
594
|
+
alphaPacket = packet1;
|
|
595
|
+
} else {
|
|
596
|
+
Log.e(TAG, "Stream mismatch: got " + stream1 + "," + stream2 +
|
|
597
|
+
" for frame " + decodeFrameIndex);
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if ((Integer) alphaPacket[2] == 0) {
|
|
602
|
+
// A BlockAdditional alpha stream that skips a frame can't be decoded on from here
|
|
603
|
+
Log.e(TAG, "No alpha for frame " + decodeFrameIndex);
|
|
604
|
+
if (eventCallback != null) {
|
|
605
|
+
eventCallback.onError("No alpha for frame " + decodeFrameIndex);
|
|
606
|
+
}
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
ByteBuffer colorData = (ByteBuffer) colorPacket[1];
|
|
611
|
+
long colorPts = (Long) colorPacket[3];
|
|
612
|
+
ByteBuffer alphaData = (ByteBuffer) alphaPacket[1];
|
|
613
|
+
long alphaPts = (Long) alphaPacket[3];
|
|
614
|
+
|
|
615
|
+
if (shouldStop.get()) break;
|
|
616
|
+
|
|
617
|
+
SurfaceTextureVideoDecoder cd = colorDecoder;
|
|
618
|
+
SurfaceTextureVideoDecoder ad = alphaDecoder;
|
|
619
|
+
if (cd == null || ad == null) break;
|
|
620
|
+
|
|
621
|
+
// Queue to color decoder — retry until accepted, stopped or overtaken by a seek
|
|
622
|
+
// MediaCodec backpressure naturally throttles when SurfaceTexture output is full
|
|
623
|
+
while (!cd.queueInputPacket(colorData, colorPts, decodeFrameIndex)
|
|
624
|
+
&& !shouldStop.get() && seekRequest.get() < 0) {
|
|
625
|
+
try { Thread.sleep(1); } catch (InterruptedException e) { break; }
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Queue to alpha decoder
|
|
629
|
+
while (!ad.queueInputPacket(alphaData, alphaPts, decodeFrameIndex)
|
|
630
|
+
&& !shouldStop.get() && seekRequest.get() < 0) {
|
|
631
|
+
try { Thread.sleep(1); } catch (InterruptedException e) { break; }
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
decodeFrameIndex++;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
logV("Demux loop ended");
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Returns once playback has passed the last frame read and that frame has been drawn, or
|
|
642
|
+
* LAST_FRAME_WAIT_NS after passing it without; or on stop or a seek.
|
|
643
|
+
*/
|
|
644
|
+
private void waitForLastFrameDrawn() {
|
|
645
|
+
long frameUs = (long) (1_000_000.0 / frameRate);
|
|
646
|
+
// Half a frame of slack, as the playback loop's targets have
|
|
647
|
+
long lastFramePtsUs = (decodeFrameIndex - 1) * frameUs - frameUs / 2;
|
|
648
|
+
long passedAtNs = 0;
|
|
649
|
+
while (!shouldStop.get() && isLoaded.get() && seekRequest.get() < 0) {
|
|
650
|
+
boolean passed = presentFrame.get() < 0 && playbackFrameIndex.get() >= decodeFrameIndex;
|
|
651
|
+
if (passed && passedAtNs == 0) passedAtNs = System.nanoTime();
|
|
652
|
+
boolean drawn = consumedPtsUs >= lastFramePtsUs;
|
|
653
|
+
if (passed && (drawn || System.nanoTime() - passedAtNs > LAST_FRAME_WAIT_NS)) return;
|
|
654
|
+
try { Thread.sleep(5); } catch (InterruptedException e) { return; }
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/** Lets both decoders output their last frames; the next loop or seek flushes them. */
|
|
659
|
+
private void queueEndOfStream() {
|
|
660
|
+
for (SurfaceTextureVideoDecoder decoder : new SurfaceTextureVideoDecoder[] {colorDecoder, alphaDecoder}) {
|
|
661
|
+
while (decoder != null && !decoder.queueEndOfStream() && !shouldStop.get() && seekRequest.get() < 0) {
|
|
662
|
+
try { Thread.sleep(1); } catch (InterruptedException e) { return; }
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Handle loop transition per spec:
|
|
669
|
+
* 1. Drain SurfaceTextures via updateTexImage()
|
|
670
|
+
* 2. Flush both MediaCodecs
|
|
671
|
+
* 3. Seek demuxer to frame 0
|
|
672
|
+
* 4. Reset state and restart
|
|
673
|
+
*/
|
|
674
|
+
private void handleLoop() {
|
|
675
|
+
logV("Loop transition starting...");
|
|
676
|
+
|
|
677
|
+
if (shouldStop.get()) return;
|
|
678
|
+
|
|
679
|
+
drainAndFlushDecoders();
|
|
680
|
+
// Hold the new pass until the playback loop asks for frame 0; a paused view keeps its frame.
|
|
681
|
+
SurfaceTextureVideoDecoder cd = colorDecoder;
|
|
682
|
+
SurfaceTextureVideoDecoder ad = alphaDecoder;
|
|
683
|
+
if (cd != null) cd.setTargetPresentationTimeUs(-1);
|
|
684
|
+
if (ad != null) ad.setTargetPresentationTimeUs(-1);
|
|
685
|
+
|
|
686
|
+
// Step 3: Seek demuxer to start
|
|
687
|
+
logV("Seeking demuxer to start...");
|
|
688
|
+
if (nativeDemuxerPtr != 0) {
|
|
689
|
+
nativeSeekToStart(nativeDemuxerPtr);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Step 4: Reset state
|
|
693
|
+
colorDirty.set(false);
|
|
694
|
+
alphaDirty.set(false);
|
|
695
|
+
decodeFrameIndex = 0;
|
|
696
|
+
playbackFrameIndex.set(0);
|
|
697
|
+
|
|
698
|
+
// Reset perf counters
|
|
699
|
+
framesRendered = 0;
|
|
700
|
+
lastPerfLogTime = System.nanoTime();
|
|
701
|
+
|
|
702
|
+
if (eventCallback != null) {
|
|
703
|
+
eventCallback.onLooped();
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
logV("Loop transition complete - resuming from frame 0");
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Restarts decoding at {@code frame}, from the keyframe at or before it. The decoders drop
|
|
711
|
+
* the frames before it, and the playback loop shows it as soon as both have it.
|
|
712
|
+
*/
|
|
713
|
+
private void reposition(int frame) {
|
|
714
|
+
seeking.set(true);
|
|
715
|
+
logV("Seeking to frame " + frame);
|
|
716
|
+
|
|
717
|
+
drainAndFlushDecoders();
|
|
718
|
+
|
|
719
|
+
long frameUs = (long) (1_000_000.0 / frameRate);
|
|
720
|
+
// Half a frame either side absorbs the container's rounding of timestamps (WebM: 1 ms).
|
|
721
|
+
if (nativeDemuxerPtr != 0) {
|
|
722
|
+
nativeSeekToTime(nativeDemuxerPtr, frame * frameUs + frameUs / 2);
|
|
723
|
+
}
|
|
724
|
+
SurfaceTextureVideoDecoder cd = colorDecoder;
|
|
725
|
+
SurfaceTextureVideoDecoder ad = alphaDecoder;
|
|
726
|
+
if (cd != null) cd.setDropBeforeUs(frame * frameUs - frameUs / 2);
|
|
727
|
+
if (ad != null) ad.setDropBeforeUs(frame * frameUs - frameUs / 2);
|
|
728
|
+
|
|
729
|
+
colorDirty.set(false);
|
|
730
|
+
alphaDirty.set(false);
|
|
731
|
+
decodeFrameIndex = frame;
|
|
732
|
+
playbackFrameIndex.set(frame);
|
|
733
|
+
consumedPairsAtPresent = consumedPairs.get();
|
|
734
|
+
presentFrame.set(frame);
|
|
735
|
+
// A seek made meanwhile goes next, and the playback loop waits for that one instead.
|
|
736
|
+
if (seekRequest.get() < 0) seeking.set(false);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/** Loop and seek steps 1–2: drain the SurfaceTextures, then flush both decoders. */
|
|
740
|
+
private void drainAndFlushDecoders() {
|
|
741
|
+
// Step 1: Drain SurfaceTextures to prevent stale frames
|
|
742
|
+
// Post to GL thread and wait for completion
|
|
743
|
+
if (glExecutor != null) {
|
|
744
|
+
final Object drainLock = new Object();
|
|
745
|
+
final AtomicBoolean drained = new AtomicBoolean(false);
|
|
746
|
+
|
|
747
|
+
glExecutor.executeOnGLThread(() -> {
|
|
748
|
+
SurfaceTextureVideoDecoder cd = colorDecoder;
|
|
749
|
+
SurfaceTextureVideoDecoder ad = alphaDecoder;
|
|
750
|
+
if (cd != null && cd.hasNewFrame()) {
|
|
751
|
+
cd.updateTexImage();
|
|
752
|
+
}
|
|
753
|
+
if (ad != null && ad.hasNewFrame()) {
|
|
754
|
+
ad.updateTexImage();
|
|
755
|
+
}
|
|
756
|
+
synchronized (drainLock) {
|
|
757
|
+
drained.set(true);
|
|
758
|
+
drainLock.notifyAll();
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
// Wait for drain to complete (with timeout)
|
|
763
|
+
synchronized (drainLock) {
|
|
764
|
+
long deadline = System.currentTimeMillis() + 500;
|
|
765
|
+
while (!drained.get() && System.currentTimeMillis() < deadline) {
|
|
766
|
+
try { drainLock.wait(50); } catch (InterruptedException e) { break; }
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Step 2: Flush both decoders (resets VP9 reference frames and timestamps)
|
|
772
|
+
logV("Flushing decoders...");
|
|
773
|
+
if (colorDecoder != null) colorDecoder.flush();
|
|
774
|
+
if (alphaDecoder != null) alphaDecoder.flush();
|
|
775
|
+
consumedPtsUs = -1;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// -------------------------------------------------------------------------
|
|
779
|
+
// Playback loop — triggers rendering at frame rate
|
|
780
|
+
// -------------------------------------------------------------------------
|
|
781
|
+
|
|
782
|
+
private void playbackLoop() {
|
|
783
|
+
logV("Playback loop started");
|
|
784
|
+
|
|
785
|
+
// Media timebase — one frame's worth of presentation time, whatever the playback rate.
|
|
786
|
+
long frameDurationUs = (long)(1_000_000.0 / frameRate);
|
|
787
|
+
long nextFrameTimeNs = System.nanoTime();
|
|
788
|
+
|
|
789
|
+
while (!shouldStop.get() && isLoaded.get()) {
|
|
790
|
+
SurfaceTextureVideoDecoder cd = colorDecoder;
|
|
791
|
+
SurfaceTextureVideoDecoder ad = alphaDecoder;
|
|
792
|
+
if (cd == null || ad == null) break;
|
|
793
|
+
|
|
794
|
+
// Read per iteration so a mid-playback rate change takes effect on the next frame
|
|
795
|
+
// rather than at the next load.
|
|
796
|
+
double speed = playbackSpeed;
|
|
797
|
+
if (speed <= 0.01) speed = 0.01;
|
|
798
|
+
long frameDurationNs = (long)(1_000_000_000.0 / (frameRate * speed));
|
|
799
|
+
|
|
800
|
+
if (seeking.get()) {
|
|
801
|
+
try { Thread.sleep(2); } catch (InterruptedException e) { break; }
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// The first frame after load or a seek: hold both decoders at it until each has
|
|
806
|
+
// released it, render until the view has drawn it, and start the clock from there.
|
|
807
|
+
int present = presentFrame.get();
|
|
808
|
+
if (present >= 0) {
|
|
809
|
+
long presentPtsUs = present * frameDurationUs + frameDurationUs / 2;
|
|
810
|
+
cd.setTargetPresentationTimeUs(presentPtsUs);
|
|
811
|
+
ad.setTargetPresentationTimeUs(presentPtsUs);
|
|
812
|
+
boolean drawn = consumedPairs.get() != consumedPairsAtPresent;
|
|
813
|
+
if (drawn && presentFrame.compareAndSet(present, -1)) {
|
|
814
|
+
playbackFrameIndex.set(present + 1);
|
|
815
|
+
nextFrameTimeNs = System.nanoTime() + frameDurationNs;
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
if (!drawn && cd.wasFrameReleased() && ad.wasFrameReleased() && renderCallback != null) {
|
|
819
|
+
renderCallback.onRenderRequested(present);
|
|
820
|
+
}
|
|
821
|
+
try { Thread.sleep(2); } catch (InterruptedException e) { break; }
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (!isPlaying.get()) {
|
|
826
|
+
try { Thread.sleep(16); } catch (InterruptedException e) { break; }
|
|
827
|
+
nextFrameTimeNs = System.nanoTime();
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
long now = System.nanoTime();
|
|
832
|
+
|
|
833
|
+
if (now >= nextFrameTimeNs) {
|
|
834
|
+
int frameIdx = playbackFrameIndex.get();
|
|
835
|
+
|
|
836
|
+
if (!loop && frameIdx >= totalFrames) {
|
|
837
|
+
isPlaying.set(false);
|
|
838
|
+
if (eventCallback != null) {
|
|
839
|
+
eventCallback.onEnded();
|
|
840
|
+
}
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Advance the target PTS on BOTH decoders so they release exactly
|
|
845
|
+
// this frame's data to their SurfaceTextures. Both decoders get the
|
|
846
|
+
// same target, ensuring color and alpha stay synchronized. Half a frame
|
|
847
|
+
// of slack covers the container rounding a timestamp up.
|
|
848
|
+
long targetPtsUs = frameIdx * frameDurationUs + frameDurationUs / 2;
|
|
849
|
+
cd.setTargetPresentationTimeUs(targetPtsUs);
|
|
850
|
+
ad.setTargetPresentationTimeUs(targetPtsUs);
|
|
851
|
+
|
|
852
|
+
// Request render — the view will call consumeFrames() which calls
|
|
853
|
+
// updateTexImage() to grab the frame we just allowed to be released
|
|
854
|
+
if (renderCallback != null) {
|
|
855
|
+
renderCallback.onRenderRequested(frameIdx);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// A seek or loop meanwhile has moved the playhead; keep it.
|
|
859
|
+
playbackFrameIndex.compareAndSet(frameIdx, frameIdx + 1);
|
|
860
|
+
framesRendered++;
|
|
861
|
+
nextFrameTimeNs += frameDurationNs;
|
|
862
|
+
|
|
863
|
+
// Don't fall too far behind
|
|
864
|
+
if (nextFrameTimeNs < now - frameDurationNs * 3) {
|
|
865
|
+
nextFrameTimeNs = now;
|
|
866
|
+
}
|
|
867
|
+
} else {
|
|
868
|
+
// Wait for next frame time
|
|
869
|
+
long sleepMs = (nextFrameTimeNs - now) / 1_000_000;
|
|
870
|
+
if (sleepMs > 0) {
|
|
871
|
+
try { Thread.sleep(Math.min(sleepMs, 16)); } catch (InterruptedException e) { break; }
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Performance logging every 2 seconds (verbose only)
|
|
876
|
+
if (verbose && now - lastPerfLogTime >= 2_000_000_000L) {
|
|
877
|
+
double elapsed = (now - lastPerfLogTime) / 1_000_000_000.0;
|
|
878
|
+
double fps = framesRendered / elapsed;
|
|
879
|
+
|
|
880
|
+
logV(String.format("PERF: fps=%.1f (target=%.1f), rendered=%d, playbackFrame=%d",
|
|
881
|
+
fps, frameRate, framesRendered, playbackFrameIndex.get()));
|
|
882
|
+
|
|
883
|
+
framesRendered = 0;
|
|
884
|
+
lastPerfLogTime = now;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
logV("Playback loop ended");
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// -------------------------------------------------------------------------
|
|
892
|
+
// Cleanup
|
|
893
|
+
// -------------------------------------------------------------------------
|
|
894
|
+
|
|
895
|
+
private void cleanup() {
|
|
896
|
+
logV("Starting cleanup...");
|
|
897
|
+
|
|
898
|
+
shouldStop.set(true);
|
|
899
|
+
|
|
900
|
+
// Stop demux thread
|
|
901
|
+
if (demuxThread != null) {
|
|
902
|
+
demuxThread.quitSafely();
|
|
903
|
+
try { demuxThread.join(500); } catch (InterruptedException e) {}
|
|
904
|
+
demuxThread = null;
|
|
905
|
+
demuxHandler = null;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// Stop playback thread
|
|
909
|
+
if (playbackThread != null) {
|
|
910
|
+
playbackThread.quitSafely();
|
|
911
|
+
try { playbackThread.join(500); } catch (InterruptedException e) {}
|
|
912
|
+
playbackThread = null;
|
|
913
|
+
playbackHandler = null;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Release decoders
|
|
917
|
+
if (colorDecoder != null) {
|
|
918
|
+
colorDecoder.release();
|
|
919
|
+
colorDecoder = null;
|
|
920
|
+
}
|
|
921
|
+
if (alphaDecoder != null) {
|
|
922
|
+
alphaDecoder.release();
|
|
923
|
+
alphaDecoder = null;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// Destroy native demuxer
|
|
927
|
+
if (nativeDemuxerPtr != 0) {
|
|
928
|
+
nativeDestroyDemuxer(nativeDemuxerPtr);
|
|
929
|
+
nativeDemuxerPtr = 0;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
playbackFrameIndex.set(0);
|
|
933
|
+
colorDirty.set(false);
|
|
934
|
+
alphaDirty.set(false);
|
|
935
|
+
|
|
936
|
+
logV("Cleanup complete");
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// -------------------------------------------------------------------------
|
|
940
|
+
// Native methods
|
|
941
|
+
// -------------------------------------------------------------------------
|
|
942
|
+
|
|
943
|
+
private static native long nativeCreateDemuxer(int fd, long offset, long length);
|
|
944
|
+
private static native long nativeCreateDemuxerFromReader(long reader);
|
|
945
|
+
private static native void nativeDestroyDemuxer(long ptr);
|
|
946
|
+
private static native int[] nativeGetVideoInfo(long ptr);
|
|
947
|
+
private static native int[] nativeGetColorInfo(long ptr);
|
|
948
|
+
private static native Object[] nativeReadPacket(long ptr);
|
|
949
|
+
private static native void nativeSeekToStart(long ptr);
|
|
950
|
+
private static native void nativeSeekToTime(long ptr, long timeUs);
|
|
951
|
+
|
|
952
|
+
public static final int TRACK_VP8 = 0;
|
|
953
|
+
public static final int TRACK_VP9 = 1;
|
|
954
|
+
public static final int TRACK_AV1 = 2;
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* The colour and alpha bitstreams in bytes [offset, offset + length) of fd, as pairs of codec
|
|
958
|
+
* ({@link #TRACK_VP8}, {@link #TRACK_VP9} or {@link #TRACK_AV1}) and profile. They come from
|
|
959
|
+
* 2 or more VP8/VP9/AV1 tracks, or from one VP8 or VP9 track with its alpha in BlockAdditional
|
|
960
|
+
* (a standard alpha WebM). Null for any other file.
|
|
961
|
+
*/
|
|
962
|
+
public static native int[] nativeColorAlphaTracks(int fd, long offset, long length);
|
|
963
|
+
|
|
964
|
+
/** The same for a {@link SourceReader} reference; blocks while the header is fetched. */
|
|
965
|
+
static native int[] nativeColorAlphaTracksReader(long reader);
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* The decoders to load with, colour then alpha, reserved by {@link MediaCodecDecoders#reserve}.
|
|
969
|
+
* Call before loading; the player gives them back in {@link #unload}.
|
|
970
|
+
*/
|
|
971
|
+
void useDecoders(String[] names) {
|
|
972
|
+
decoderNames = names;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
static {
|
|
976
|
+
System.loadLibrary("alphavideo");
|
|
977
|
+
}
|
|
978
|
+
}
|