From d1d70e1559a67bd5c4336547d215a03260153b00 Mon Sep 17 00:00:00 2001 From: otsmr Date: Sat, 22 Aug 2026 20:30:14 +0200 Subject: [PATCH] add new dependencies --- .../test/adaptive_number_test.dart | 128 - audio_waveforms/LICENSE | 21 + audio_waveforms/android/.gitignore | 8 + audio_waveforms/android/build.gradle | 55 + audio_waveforms/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + audio_waveforms/android/settings.gradle | 1 + .../android/src/main/AndroidManifest.xml | 3 + .../simform/audio_waveforms/AudioPlayer.kt | 239 + .../simform/audio_waveforms/AudioRecorder.kt | 263 + .../audio_waveforms/AudioWaveformsPlugin.kt | 338 + .../audio_waveforms/RecorderSettings.kt | 61 + .../com/simform/audio_waveforms/Utils.kt | 329 + .../audio_waveforms/WaveformExtractor.kt | 432 + .../audio_waveforms/encoders/CommonEncoder.kt | 429 + .../audio_waveforms/encoders/WavEncoder.kt | 168 + audio_waveforms/ios/.gitignore | 38 + .../ios/Assets/.gitkeep | 0 audio_waveforms/ios/Classes/AudioPlayer.swift | 174 + .../ios/Classes/AudioRecorder.swift | 194 + .../ios/Classes/AudioWaveformsPlugin.h | 4 + .../ios/Classes/AudioWaveformsPlugin.m | 15 + .../Classes/RecorderBytesStreamEngine.swift | 98 + .../ios/Classes/RecordingSettings.swift | 47 + .../Classes/SwiftAudioWaveformsPlugin.swift | 219 + audio_waveforms/ios/Classes/Utils.swift | 93 + .../ios/Classes/WaveformExtractor.swift | 209 + audio_waveforms/ios/audio_waveforms.podspec | 23 + audio_waveforms/lib/audio_waveforms.dart | 12 + .../lib/src/audio_file_waveforms.dart | 436 + audio_waveforms/lib/src/audio_waveforms.dart | 309 + .../src/base/audio_waveforms_interface.dart | 273 + audio_waveforms/lib/src/base/constants.dart | 62 + audio_waveforms/lib/src/base/label.dart | 16 + .../lib/src/base/platform_streams.dart | 132 + .../lib/src/base/player_identifier.dart | 10 + .../lib/src/base/player_wave_style.dart | 83 + audio_waveforms/lib/src/base/utils.dart | 201 + .../lib/src/base/wave_clipper.dart | 28 + audio_waveforms/lib/src/base/wave_style.dart | 136 + .../src/controllers/player_controller.dart | 385 + .../src/controllers/recorder_controller.dart | 349 + .../waveform_extraction_controller.dart | 114 + .../src/models/android_encoder_settings.dart | 16 + .../lib/src/models/ios_encoder_setting.dart | 40 + .../lib/src/models/recorder_settings.dart | 60 + .../lib/src/painters/player_wave_painter.dart | 103 + .../src/painters/recorder_wave_painter.dart | 283 + audio_waveforms/pubspec.yaml | 29 + avatar_maker/LICENSE | 21 + avatar_maker/assets/icons/accessories.svg | 15 + avatar_maker/assets/icons/background.svg | 20 + avatar_maker/assets/icons/eyebrows.svg | 8 + avatar_maker/assets/icons/eyes.svg | 12 + avatar_maker/assets/icons/facial_hair.svg | 10 + .../assets/icons/facial_hair_color.svg | 12 + avatar_maker/assets/icons/hair.svg | 19 + avatar_maker/assets/icons/hair_color.svg | 8 + avatar_maker/assets/icons/mouth.svg | 5 + avatar_maker/assets/icons/noses.svg | 15 + avatar_maker/assets/icons/outfit.svg | 15 + avatar_maker/assets/icons/outfit_color.svg | 12 + avatar_maker/assets/icons/skin.svg | 7 + avatar_maker/lib/avatar_maker.dart | 36 + avatar_maker/lib/l10n/app_localizations.dart | 209 + .../lib/l10n/app_localizations_en.dart | 45 + .../lib/l10n/app_localizations_fr.dart | 46 + .../lib/src/avatar/avatar_maker_avatar.dart | 63 + .../controllers/avatar_maker_controller.dart | 297 + .../lib/src/core/controllers/controllers.dart | 4 + ...on_persistent_avatar_maker_controller.dart | 71 + .../persistent_avatar_maker_controller.dart | 134 + .../lib/src/core/enums/placeholders.dart | 23 + .../lib/src/core/enums/preferences_label.dart | 8 + .../src/core/enums/property_categories.dart | 166 + .../src/core/enums/property_category_ids.dart | 16 + .../enums/property_items/accessories.dart | 130 + .../property_items/background_style.dart | 25 + .../core/enums/property_items/eyebrows.dart | 202 + .../src/core/enums/property_items/eyes.dart | 233 + .../property_items/facial_hair_colors.dart | 29 + .../property_items/facial_hair_types.dart | 79 + .../enums/property_items/hair_colors.dart | 29 + .../enums/property_items/hair_styles.dart | 1165 + .../src/core/enums/property_items/mouths.dart | 157 + .../src/core/enums/property_items/noses.dart | 18 + .../enums/property_items/outfit_colors.dart | 33 + .../enums/property_items/outfit_types.dart | 177 + .../enums/property_items/property_items.dart | 13 + .../enums/property_items/skin_colors.dart | 63 + .../models/customized_property_category.dart | 35 + .../src/core/models/property_category.dart | 34 + .../lib/src/core/models/property_item.dart | 6 + .../lib/src/core/models/theme_data.dart | 175 + .../avatar_maker_controller_provider.dart | 47 + .../lib/src/core/providers/providers.dart | 2 + .../src/core/services/accessory_service.dart | 13 + .../lib/src/core/services/avatar_service.dart | 144 + .../src/core/services/background_service.dart | 27 + .../lib/src/core/services/color_service.dart | 13 + .../lib/src/core/services/eye_service.dart | 13 + .../src/core/services/eyebrow_service.dart | 11 + .../core/services/facial_hairs_service.dart | 33 + .../lib/src/core/services/hair_service.dart | 33 + .../lib/src/core/services/mouth_service.dart | 13 + .../lib/src/core/services/nose_service.dart | 13 + .../src/core/services/options_service.dart | 39 + .../lib/src/core/services/outfit_service.dart | 31 + .../services/property_category_service.dart | 121 + .../lib/src/core/services/skin_service.dart | 36 + .../customizer/avatar_maker_customizer.dart | 207 + .../avatar_maker_random_widget.dart | 76 + .../customizer/avatar_maker_reset_widget.dart | 81 + .../customizer/avatar_maker_save_widget.dart | 82 + .../customizer/widgets/customizer_appbar.dart | 57 + .../widgets/customizer_arrow_button.dart | 43 + .../customizer/widgets/customizer_body.dart | 163 + .../widgets/customizer_bottom_navbar.dart | 36 + avatar_maker/pubspec.yaml | 34 + blurhash_dart/LICENSE | 20 + blurhash_dart/lib/blurhash_dart.dart | 3 + blurhash_dart/lib/blurhash_extensions.dart | 1 + blurhash_dart/lib/src/blurhash.dart | 330 + blurhash_dart/lib/src/encoding.dart | 117 + blurhash_dart/lib/src/exception.dart | 21 + blurhash_dart/lib/src/extension.dart | 260 + blurhash_dart/lib/src/foundation.dart | 90 + blurhash_dart/pubspec.yaml | 17 + camera_android_camerax/LICENSE | 25 + .../android/build.gradle.kts | 94 + .../android/lint-baseline.xml | 323 + .../android/settings.gradle.kts | 1 + .../android/src/main/AndroidManifest.xml | 8 + .../plugins/camerax/AnalyzerProxyApi.java | 65 + .../camerax/AspectRatioStrategyProxyApi.java | 97 + .../camerax/Camera2CameraControlProxyApi.java | 67 + .../camerax/Camera2CameraInfoProxyApi.java | 67 + .../camerax/CameraAndroidCameraxPlugin.java | 85 + .../CameraCharacteristicsProxyApi.java | 37 + .../camerax/CameraControlProxyApi.java | 174 + .../plugins/camerax/CameraInfoProxyApi.java | 64 + .../camerax/CameraIntegerRangeProxyApi.java | 35 + .../camerax/CameraPermissionsError.java | 45 + .../CameraPermissionsErrorProxyApi.java | 31 + .../camerax/CameraPermissionsManager.java | 124 + .../plugins/camerax/CameraProxyApi.java | 33 + .../camerax/CameraSelectorProxyApi.java | 73 + .../plugins/camerax/CameraSizeProxyApi.java | 35 + .../plugins/camerax/CameraStateProxyApi.java | 45 + .../CameraStateStateErrorProxyApi.java | 42 + .../plugins/camerax/CameraXLibrary.g.kt | 7771 ++ .../CaptureRequestOptionsProxyApi.java | 66 + .../camerax/CaptureRequestProxyApi.java | 31 + .../camerax/DeviceOrientationManager.java | 215 + .../DeviceOrientationManagerProxyApi.java | 53 + ...yOrientedMeteringPointFactoryProxyApi.java | 42 + .../camerax/ExposureStateProxyApi.java | 31 + .../camerax/FallbackStrategyProxyApi.java | 64 + .../FocusMeteringActionBuilderProxyApi.java | 71 + .../camerax/FocusMeteringActionProxyApi.java | 44 + .../camerax/FocusMeteringResultProxyApi.java | 24 + .../camerax/ImageAnalysisProxyApi.java | 96 + .../plugins/camerax/ImageCaptureProxyApi.java | 173 + .../plugins/camerax/ImageProxyProxyApi.java | 48 + .../plugins/camerax/ImageProxyUtils.java | 97 + .../camerax/ImageProxyUtilsProxyApi.java | 37 + .../plugins/camerax/LiveDataProxyApi.java | 81 + .../camerax/MeteringPointFactoryProxyApi.java | 33 + .../camerax/MeteringPointProxyApi.java | 24 + .../plugins/camerax/ObserverProxyApi.java | 64 + .../camerax/PendingRecordingProxyApi.java | 61 + .../plugins/camerax/PlaneProxyProxyApi.java | 41 + .../plugins/camerax/PreviewProxyApi.java | 185 + .../ProcessCameraProviderProxyApi.java | 93 + .../plugins/camerax/ProxyApiRegistrar.java | 439 + .../camerax/ProxyLifecycleProvider.java | 91 + .../camerax/QualitySelectorProxyApi.java | 80 + .../plugins/camerax/RecorderProxyApi.java | 87 + .../plugins/camerax/RecordingProxyApi.java | 39 + .../camerax/ResolutionFilterProxyApi.java | 40 + .../camerax/ResolutionInfoProxyApi.java | 27 + .../camerax/ResolutionSelectorProxyApi.java | 70 + .../camerax/ResolutionStrategyProxyApi.java | 81 + .../flutter/plugins/camerax/ResultCompat.kt | 47 + .../camerax/SystemServicesManager.java | 60 + .../SystemServicesManagerProxyApi.java | 110 + .../plugins/camerax/VideoCaptureProxyApi.java | 58 + .../camerax/VideoRecordEventListener.java | 12 + .../VideoRecordEventListenerProxyApi.java | 67 + .../plugins/camerax/ZoomStateProxyApi.java | 29 + .../test/java/android/util/FakeActivity.java | 46 + .../src/test/java/android/util/Range.java | 25 + .../src/test/java/android/util/Rational.java | 22 + .../src/test/java/android/util/Size.java | 39 + .../flutter/plugins/camerax/AnalyzerTest.java | 36 + .../camerax/AspectRatioStrategyTest.java | 53 + .../camerax/Camera2CameraControlTest.java | 127 + .../camerax/Camera2CameraInfoTest.java | 107 + .../CameraAndroidCameraxPluginTest.java | 217 + .../plugins/camerax/CameraControlTest.java | 216 + .../plugins/camerax/CameraInfoTest.java | 102 + .../camerax/CameraIntegerRangeTest.java | 48 + .../camerax/CameraPermissionsErrorTest.java | 33 + .../camerax/CameraPermissionsManagerTest.java | 98 + .../plugins/camerax/CameraSelectorTest.java | 60 + .../plugins/camerax/CameraSizeTest.java | 48 + .../camerax/CameraStateStateErrorTest.java | 27 + .../plugins/camerax/CameraStateTest.java | 36 + .../flutter/plugins/camerax/CameraTest.java | 38 + .../camerax/CaptureRequestOptionsTest.java | 55 + .../ConcurrentFuturesDependencyTest.java | 32 + .../DeviceOrientationManagerApiTest.java | 85 + .../camerax/DeviceOrientationManagerTest.java | 240 + .../plugins/camerax/ExposureStateTest.java | 39 + .../plugins/camerax/FallbackStrategyTest.java | 89 + .../FocusMeteringActionBuilderTest.java | 64 + .../camerax/FocusMeteringActionTest.java | 67 + .../camerax/FocusMeteringResultTest.java | 26 + .../plugins/camerax/ImageAnalysisTest.java | 116 + .../plugins/camerax/ImageCaptureTest.java | 300 + .../plugins/camerax/ImageProxyTest.java | 72 + .../camerax/ImageProxyUtilsApiTest.java | 48 + .../plugins/camerax/ImageProxyUtilsTest.java | 159 + .../flutter/plugins/camerax/LiveDataTest.java | 89 + .../camerax/MeteringPointFactoryTest.java | 44 + .../plugins/camerax/MeteringPointTest.java | 25 + .../flutter/plugins/camerax/ObserverTest.java | 28 + .../plugins/camerax/PendingRecordingTest.java | 141 + .../plugins/camerax/PlaneProxyTest.java | 36 + .../flutter/plugins/camerax/PreviewTest.java | 313 + .../camerax/ProcessCameraProviderTest.java | 147 + .../camerax/ProxyLifecycleProviderTest.java | 121 + .../plugins/camerax/QualitySelectorTest.java | 77 + .../flutter/plugins/camerax/RecorderTest.java | 83 + .../plugins/camerax/RecordingTest.java | 53 + .../plugins/camerax/ResolutionFilterTest.java | 48 + .../plugins/camerax/ResolutionInfoTest.java | 26 + .../camerax/ResolutionSelectorTest.java | 126 + .../camerax/ResolutionStrategyTest.java | 59 + .../plugins/camerax/SystemServicesTest.java | 172 + .../camerax/TestProxyApiRegistrar.java | 32 + .../plugins/camerax/VideoCaptureTest.java | 74 + .../camerax/VideoRecordEventListenerTest.java | 40 + .../plugins/camerax/ZoomStateTest.java | 36 + .../lib/camera_android_camerax.dart | 5 + .../lib/src/android_camera_camerax.dart | 1829 + .../lib/src/camerax_library.dart | 239 + .../lib/src/camerax_library.g.dart | 8324 ++ .../lib/src/image_reader_rotated_preview.dart | 167 + .../lib/src/rotated_preview_delegate.dart | 97 + .../lib/src/rotated_preview_utils.dart | 34 + .../src/surface_texture_rotated_preview.dart | 117 + camera_android_camerax/pubspec.yaml | 45 + config.lock.yaml | 24 - config.yaml | 73 - .../test/benchmark/ed25519_benchmark.dart | 38 - .../test/benchmark/rate_benchmark.dart | 64 - ed25519_edwards/test/ed25519_test.dart | 67 - exif/test/data/avif-test.avif | Bin 10859 -> 0 bytes exif/test/data/avif-test.avif.dump | 59 - exif/test/data/heic-test.heic | Bin 2808983 -> 0 bytes exif/test/data/heic-test.heic.dump | 80 - exif/test/data/png-test.png | Bin 183276 -> 0 bytes exif/test/data/png-test.png.dump | 13 - exif/test/data/webp-test.webp | Bin 13878 -> 0 bytes exif/test/data/webp-test.webp.dump | 59 - exif/test/read_file_test.dart | 52 - exif/test/read_samples.dart | 94 - exif/test/regression_test.dart | 23 - exif/test/sample_file.dart | 25 - exif/test/sample_file.g.dart | 19 - exif/test/samples_test.dart | 14 - exif/test/util_test.dart | 11 - exif/test/web_hybrid_main.dart | 13 - exif/test/web_test.dart | 21 - flutter_markdown_plus/test/all.dart | 45 - .../image_test/custom_builder_asset_logo.png | Bin 24987 -> 0 bytes .../golden/image_test/resource_asset_logo.png | Bin 24987 -> 0 bytes .../test/assets/images/logo.png | Bin 16495 -> 0 bytes .../test/blockquote_test.dart | 106 - .../test/custom_syntax_test.dart | 445 - flutter_markdown_plus/test/emphasis_test.dart | 4287 - .../test/flutter_test_config.dart | 13 - flutter_markdown_plus/test/footnote_test.dart | 281 - flutter_markdown_plus/test/header_test.dart | 35 - .../test/horizontal_rule_test.dart | 88 - flutter_markdown_plus/test/html_test.dart | 55 - flutter_markdown_plus/test/image_test.dart | 448 - .../test/image_test_mocks.dart | 375 - .../test/inline_widget_test.dart | 78 - .../test/line_break_test.dart | 357 - flutter_markdown_plus/test/link_test.dart | 2498 - flutter_markdown_plus/test/list_test.dart | 302 - .../test/markdown_body_shrink_wrap_test.dart | 76 - flutter_markdown_plus/test/padding_test.dart | 67 - .../test/scrollable_test.dart | 195 - .../selection_area_compatibility_test.dart | 71 - .../test/style_sheet_test.dart | 445 - flutter_markdown_plus/test/table_test.dart | 702 - .../test/text_alignment_test.dart | 107 - .../test/text_scaler_test.dart | 76 - flutter_markdown_plus/test/text_test.dart | 400 - flutter_markdown_plus/test/uri_test.dart | 94 - flutter_markdown_plus/test/utils.dart | 248 - ...er_sharing_intent_method_channel_test.dart | 27 - .../test/flutter_sharing_intent_test.dart | 45 - .../test/signature_control_test.dart | 133 - hashlib/test/adler32_test.dart | 38 - hashlib/test/argon2_test.dart | 565 - hashlib/test/bcrypt/bcrypt_2a_test.dart | 134 - hashlib/test/bcrypt/bcrypt_2b_test.dart | 186 - hashlib/test/bcrypt/bcrypt_2y_test.dart | 24 - hashlib/test/bcrypt/bcrypt_basic_test.dart | 221 - hashlib/test/bcrypt/bcrypt_cost_test.dart | 110 - hashlib/test/blake2b_test.dart | 213 - hashlib/test/blake2s_test.dart | 182 - .../test/compare/compare_blake2b512_test.dart | 109 - hashlib/test/compare/compare_hmac_test.dart | 55 - hashlib/test/compare/compare_keccak_test.dart | 45 - hashlib/test/compare/compare_md4_test.dart | 36 - hashlib/test/compare/compare_md5_test.dart | 54 - hashlib/test/compare/compare_sha224_test.dart | 62 - hashlib/test/compare/compare_sha384_test.dart | 38 - .../test/compare/compare_sha512_224_test.dart | 38 - .../test/compare/compare_sha512_256_test.dart | 38 - hashlib/test/compare/compare_sha512_test.dart | 38 - hashlib/test/compare/compare_sm3_test.dart | 36 - hashlib/test/core/block_hash_test.dart | 102 - hashlib/test/core/hash_base_file_test.dart | 51 - hashlib/test/core/hash_digest_test.dart | 166 - hashlib/test/core/hash_sink_test.dart | 102 - hashlib/test/core/kdf_base_test.dart | 52 - hashlib/test/core/mac_base_test.dart | 82 - hashlib/test/crc16_test.dart | 103 - hashlib/test/crc32_test.dart | 79 - hashlib/test/crc64_test.dart | 95 - hashlib/test/fixures/long.txt | 27 - hashlib/test/fixures/xxh32.csv | 10240 --- hashlib/test/fixures/xxh3_128.csv | 10240 --- hashlib/test/fixures/xxh3_64.csv | 10240 --- hashlib/test/fixures/xxh64.csv | 10240 --- hashlib/test/hmac_test.dart | 137 - hashlib/test/keccak_test.dart | 78 - hashlib/test/md2_test.dart | 88 - hashlib/test/md4_test.dart | 80 - hashlib/test/md5_test.dart | 118 - hashlib/test/pbkdf2_test.dart | 187 - hashlib/test/poly1305_test.dart | 295 - hashlib/test/random/random_test.dart | 429 - hashlib/test/random/uuid_test.dart | 297 - hashlib/test/registry_test.dart | 89 - hashlib/test/ripemd128_test.dart | 66 - hashlib/test/ripemd160_test.dart | 84 - hashlib/test/ripemd256_test.dart | 68 - hashlib/test/ripemd320_test.dart | 68 - hashlib/test/scrypt_test.dart | 268 - hashlib/test/sha1_test.dart | 76 - hashlib/test/sha224_test.dart | 72 - hashlib/test/sha256_test.dart | 72 - hashlib/test/sha384_test.dart | 79 - hashlib/test/sha3_test.dart | 83 - hashlib/test/sha512_224_test.dart | 77 - hashlib/test/sha512_256_test.dart | 77 - hashlib/test/sha512_test.dart | 82 - hashlib/test/shake_test.dart | 145 - hashlib/test/sm3_test.dart | 93 - hashlib/test/totp_test.dart | 112 - hashlib/test/xxh128_test.dart | 128 - hashlib/test/xxh32_test.dart | 66 - hashlib/test/xxh3_test.dart | 173 - hashlib/test/xxh64_test.dart | 76 - hashlib/test/xxh_test.dart | 71 - hashlib_codecs/test/base16_test.dart | 173 - hashlib_codecs/test/base2_test.dart | 87 - hashlib_codecs/test/base32_test.dart | 457 - hashlib_codecs/test/base64_test.dart | 173 - hashlib_codecs/test/base64url_test.dart | 60 - hashlib_codecs/test/base8_test.dart | 122 - hashlib_codecs/test/bigint_test.dart | 134 - hashlib_codecs/test/core/alphabet_test.dart | 172 - hashlib_codecs/test/core/byte_test.dart | 74 - hashlib_codecs/test/core/codec_test.dart | 101 - hashlib_codecs/test/core/decoder_test.dart | 77 - hashlib_codecs/test/core/encoder_test.dart | 82 - .../test/crypt/crypt_builder_test.dart | 90 - hashlib_codecs/test/crypt/crypt_test.dart | 108 - hashlib_codecs/test/crypt_test.dart | 179 - hashlib_codecs/test/utf8_test.dart | 249 - hashlib_codecs/test/utils.dart | 47 - image/LICENSE | 22 + image/lib/image.dart | 248 + image/lib/src/color/channel.dart | 18 + image/lib/src/color/channel_iterator.dart | 18 + image/lib/src/color/channel_order.dart | 24 + image/lib/src/color/color.dart | 110 + image/lib/src/color/color_float16.dart | 204 + image/lib/src/color/color_float32.dart | 194 + image/lib/src/color/color_float64.dart | 187 + image/lib/src/color/color_int16.dart | 184 + image/lib/src/color/color_int32.dart | 184 + image/lib/src/color/color_int8.dart | 184 + image/lib/src/color/color_uint1.dart | 187 + image/lib/src/color/color_uint16.dart | 184 + image/lib/src/color/color_uint2.dart | 173 + image/lib/src/color/color_uint32.dart | 184 + image/lib/src/color/color_uint4.dart | 185 + image/lib/src/color/color_uint8.dart | 209 + image/lib/src/color/const_color_uint8.dart | 230 + image/lib/src/color/format.dart | 319 + image/lib/src/command/_executor.dart | 20 + image/lib/src/command/_executor_html.dart | 31 + image/lib/src/command/_executor_io.dart | 74 + image/lib/src/command/command.dart | 1296 + .../src/command/draw/composite_image_cmd.dart | 60 + image/lib/src/command/draw/draw_char_cmd.dart | 43 + .../lib/src/command/draw/draw_circle_cmd.dart | 44 + image/lib/src/command/draw/draw_line_cmd.dart | 50 + .../lib/src/command/draw/draw_pixel_cmd.dart | 42 + .../src/command/draw/draw_polygon_cmd.dart | 36 + image/lib/src/command/draw/draw_rect_cmd.dart | 50 + .../lib/src/command/draw/draw_string_cmd.dart | 49 + .../lib/src/command/draw/fill_circle_cmd.dart | 44 + image/lib/src/command/draw/fill_cmd.dart | 23 + .../lib/src/command/draw/fill_flood_cmd.dart | 40 + .../src/command/draw/fill_polygon_cmd.dart | 36 + image/lib/src/command/draw/fill_rect_cmd.dart | 43 + image/lib/src/command/execute_result.dart | 13 + image/lib/src/command/executor.dart | 1 + .../src/command/filter/adjust_color_cmd.dart | 57 + .../lib/src/command/filter/billboard_cmd.dart | 27 + .../src/command/filter/bleach_bypass_cmd.dart | 23 + .../command/filter/bulge_distortion_cmd.dart | 40 + .../command/filter/bump_to_normal_cmd.dart | 15 + .../filter/chromatic_aberration_cmd.dart | 23 + .../command/filter/color_halftone_cmd.dart | 39 + .../src/command/filter/color_offset_cmd.dart | 36 + .../lib/src/command/filter/contrast_cmd.dart | 28 + .../src/command/filter/convolution_cmd.dart | 37 + .../filter/copy_image_channels_cmd.dart | 43 + .../src/command/filter/dither_image_cmd.dart | 39 + .../src/command/filter/dot_screen_cmd.dart | 39 + .../src/command/filter/drop_shadow_cmd.dart | 23 + .../lib/src/command/filter/edge_glow_cmd.dart | 23 + image/lib/src/command/filter/emboss_cmd.dart | 22 + image/lib/src/command/filter/filter_cmd.dart | 40 + image/lib/src/command/filter/gamma_cmd.dart | 23 + .../src/command/filter/gaussian_blur_cmd.dart | 23 + .../lib/src/command/filter/grayscale_cmd.dart | 23 + .../src/command/filter/hdr_to_ldr_cmd.dart | 15 + .../command/filter/hexagon_pixelate_cmd.dart | 36 + image/lib/src/command/filter/invert_cmd.dart | 20 + .../filter/luminance_threshold_cmd.dart | 33 + .../src/command/filter/monochrome_cmd.dart | 31 + image/lib/src/command/filter/noise_cmd.dart | 30 + .../lib/src/command/filter/normalize_cmd.dart | 27 + .../lib/src/command/filter/pixelate_cmd.dart | 27 + .../lib/src/command/filter/quantize_cmd.dart | 41 + .../command/filter/reinhard_tonemap_cmd.dart | 19 + .../src/command/filter/remap_colors_cmd.dart | 25 + .../src/command/filter/scale_rgba_cmd.dart | 24 + .../filter/separable_convolution_cmd.dart | 24 + image/lib/src/command/filter/sepia_cmd.dart | 22 + image/lib/src/command/filter/sketch_cmd.dart | 22 + image/lib/src/command/filter/smooth_cmd.dart | 22 + image/lib/src/command/filter/sobel_cmd.dart | 22 + .../filter/stretch_distortion_cmd.dart | 34 + .../lib/src/command/filter/vignette_cmd.dart | 37 + image/lib/src/command/formats/bmp_cmd.dart | 61 + image/lib/src/command/formats/cur_cmd.dart | 33 + .../src/command/formats/decode_image_cmd.dart | 16 + .../formats/decode_image_file_cmd.dart | 14 + .../formats/decode_named_image_cmd.dart | 22 + image/lib/src/command/formats/exr_cmd.dart | 30 + image/lib/src/command/formats/gif_cmd.dart | 107 + image/lib/src/command/formats/ico_cmd.dart | 61 + image/lib/src/command/formats/jpg_cmd.dart | 64 + image/lib/src/command/formats/png_cmd.dart | 71 + image/lib/src/command/formats/psd_cmd.dart | 30 + image/lib/src/command/formats/pvr_cmd.dart | 61 + image/lib/src/command/formats/tga_cmd.dart | 61 + image/lib/src/command/formats/tiff_cmd.dart | 61 + image/lib/src/command/formats/webp_cmd.dart | 61 + .../command/formats/write_to_file_cmd.dart | 22 + .../lib/src/command/image/add_frames_cmd.dart | 27 + image/lib/src/command/image/convert_cmd.dart | 23 + .../lib/src/command/image/copy_image_cmd.dart | 12 + .../src/command/image/create_image_cmd.dart | 48 + image/lib/src/command/image/image_cmd.dart | 13 + .../transform/bake_orientation_cmd.dart | 13 + .../transform/copy_crop_circle_cmd.dart | 26 + .../src/command/transform/copy_crop_cmd.dart | 35 + .../transform/copy_expand_canvas_cmd.dart | 58 + .../src/command/transform/copy_flip_cmd.dart | 16 + .../command/transform/copy_rectify_cmd.dart | 33 + .../command/transform/copy_resize_cmd.dart | 33 + .../copy_resize_crop_square_cmd.dart | 30 + .../command/transform/copy_rotate_cmd.dart | 21 + image/lib/src/command/transform/flip_cmd.dart | 15 + image/lib/src/command/transform/trim_cmd.dart | 18 + .../src/draw/_calculate_circumference.dart | 69 + .../lib/src/draw/_draw_antialias_circle.dart | 69 + image/lib/src/draw/blend_mode.dart | 17 + image/lib/src/draw/composite_image.dart | 156 + image/lib/src/draw/draw_char.dart | 45 + image/lib/src/draw/draw_circle.dart | 37 + image/lib/src/draw/draw_line.dart | 512 + image/lib/src/draw/draw_pixel.dart | 271 + image/lib/src/draw/draw_polygon.dart | 74 + image/lib/src/draw/draw_rect.dart | 153 + image/lib/src/draw/draw_string.dart | 166 + image/lib/src/draw/fill.dart | 25 + image/lib/src/draw/fill_circle.dart | 49 + image/lib/src/draw/fill_flood.dart | 276 + image/lib/src/draw/fill_polygon.dart | 96 + image/lib/src/draw/fill_rect.dart | 116 + image/lib/src/exif/exif_data.dart | 504 + image/lib/src/exif/exif_tag.dart | 414 + image/lib/src/exif/ifd_container.dart | 52 + image/lib/src/exif/ifd_directory.dart | 385 + image/lib/src/exif/ifd_value.dart | 779 + image/lib/src/filter/adjust_color.dart | 171 + image/lib/src/filter/billboard.dart | 72 + image/lib/src/filter/bleach_bypass.dart | 52 + image/lib/src/filter/bulge_distortion.dart | 59 + image/lib/src/filter/bump_to_normal.dart | 48 + .../lib/src/filter/chromatic_aberration.dart | 34 + image/lib/src/filter/color_halftone.dart | 75 + image/lib/src/filter/color_offset.dart | 44 + image/lib/src/filter/contrast.dart | 77 + image/lib/src/filter/convolution.dart | 57 + image/lib/src/filter/copy_image_channels.dart | 56 + image/lib/src/filter/dither_image.dart | 392 + image/lib/src/filter/dot_screen.dart | 49 + image/lib/src/filter/drop_shadow.dart | 65 + image/lib/src/filter/edge_glow.dart | 93 + image/lib/src/filter/emboss.dart | 16 + image/lib/src/filter/gamma.dart | 32 + image/lib/src/filter/gaussian_blur.dart | 48 + image/lib/src/filter/grayscale.dart | 54 + image/lib/src/filter/hdr_to_ldr.dart | 86 + image/lib/src/filter/hexagon_pixelate.dart | 105 + .../src/filter/histogram_equalization.dart | 263 + image/lib/src/filter/invert.dart | 44 + image/lib/src/filter/luminance_threshold.dart | 42 + image/lib/src/filter/monochrome.dart | 39 + image/lib/src/filter/noise.dart | 174 + image/lib/src/filter/normalize.dart | 55 + image/lib/src/filter/pixelate.dart | 101 + image/lib/src/filter/quantize.dart | 41 + image/lib/src/filter/reinhard_tone_map.dart | 52 + image/lib/src/filter/remap_colors.dart | 42 + image/lib/src/filter/scale_rgba.dart | 32 + .../lib/src/filter/separable_convolution.dart | 24 + image/lib/src/filter/separable_kernel.dart | 104 + image/lib/src/filter/sepia.dart | 32 + image/lib/src/filter/sketch.dart | 60 + image/lib/src/filter/smooth.dart | 20 + image/lib/src/filter/sobel.dart | 54 + image/lib/src/filter/solarize.dart | 109 + image/lib/src/filter/stretch_distortion.dart | 58 + image/lib/src/filter/vignette.dart | 66 + image/lib/src/font/arial_14.dart | 5775 ++ image/lib/src/font/arial_24.dart | 9309 ++ image/lib/src/font/arial_48.dart | 18940 ++++ image/lib/src/font/bitmap_font.dart | 609 + image/lib/src/formats/bmp/bmp_info.dart | 286 + image/lib/src/formats/bmp_decoder.dart | 143 + image/lib/src/formats/bmp_encoder.dart | 312 + image/lib/src/formats/cur_encoder.dart | 32 + image/lib/src/formats/decode_info.dart | 16 + image/lib/src/formats/decoder.dart | 50 + image/lib/src/formats/encoder.dart | 15 + image/lib/src/formats/exr/exr_attribute.dart | 12 + .../src/formats/exr/exr_b44_compressor.dart | 44 + image/lib/src/formats/exr/exr_channel.dart | 83 + image/lib/src/formats/exr/exr_compressor.dart | 94 + image/lib/src/formats/exr/exr_huffman.dart | 352 + image/lib/src/formats/exr/exr_image.dart | 328 + image/lib/src/formats/exr/exr_part.dart | 441 + .../src/formats/exr/exr_piz_compressor.dart | 182 + .../src/formats/exr/exr_pxr24_compressor.dart | 133 + .../src/formats/exr/exr_rle_compressor.dart | 107 + image/lib/src/formats/exr/exr_wavelet.dart | 159 + .../src/formats/exr/exr_zip_compressor.dart | 96 + image/lib/src/formats/exr_decoder.dart | 54 + image/lib/src/formats/formats.dart | 689 + image/lib/src/formats/gif/gif_color_map.dart | 92 + image/lib/src/formats/gif/gif_image_desc.dart | 49 + image/lib/src/formats/gif/gif_info.dart | 21 + image/lib/src/formats/gif_decoder.dart | 695 + image/lib/src/formats/gif_encoder.dart | 495 + image/lib/src/formats/ico/ico_info.dart | 86 + image/lib/src/formats/ico_decoder.dart | 161 + image/lib/src/formats/ico_encoder.dart | 86 + image/lib/src/formats/image_format.dart | 17 + .../lib/src/formats/jpeg/_component_data.dart | 23 + image/lib/src/formats/jpeg/_jpeg_huffman.dart | 13 + .../src/formats/jpeg/_jpeg_quantize_html.dart | 448 + .../src/formats/jpeg/_jpeg_quantize_io.dart | 450 + image/lib/src/formats/jpeg/jpeg_adobe.dart | 6 + .../lib/src/formats/jpeg/jpeg_component.dart | 21 + image/lib/src/formats/jpeg/jpeg_data.dart | 626 + image/lib/src/formats/jpeg/jpeg_frame.dart | 54 + image/lib/src/formats/jpeg/jpeg_info.dart | 16 + image/lib/src/formats/jpeg/jpeg_jfif.dart | 12 + image/lib/src/formats/jpeg/jpeg_marker.dart | 71 + image/lib/src/formats/jpeg/jpeg_scan.dart | 362 + image/lib/src/formats/jpeg/jpeg_util.dart | 196 + image/lib/src/formats/jpeg_decoder.dart | 61 + image/lib/src/formats/jpeg_encoder.dart | 1404 + image/lib/src/formats/png/png_frame.dart | 57 + image/lib/src/formats/png/png_info.dart | 173 + image/lib/src/formats/png_decoder.dart | 878 + image/lib/src/formats/png_encoder.dart | 440 + image/lib/src/formats/pnm_decoder.dart | 221 + .../formats/psd/effect/psd_bevel_effect.dart | 18 + .../psd/effect/psd_drop_shadow_effect.dart | 13 + .../src/formats/psd/effect/psd_effect.dart | 4 + .../psd/effect/psd_inner_glow_effect.dart | 11 + .../psd/effect/psd_inner_shadow_effect.dart | 13 + .../psd/effect/psd_outer_glow_effect.dart | 10 + .../psd/effect/psd_solid_fill_effect.dart | 8 + .../layer_data/psd_layer_additional_data.dart | 8 + .../layer_data/psd_layer_section_divider.dart | 37 + .../src/formats/psd/psd_blending_ranges.dart | 39 + image/lib/src/formats/psd/psd_channel.dart | 135 + image/lib/src/formats/psd/psd_image.dart | 614 + .../src/formats/psd/psd_image_resource.dart | 9 + image/lib/src/formats/psd/psd_layer.dart | 402 + image/lib/src/formats/psd/psd_layer_data.dart | 18 + image/lib/src/formats/psd/psd_mask.dart | 40 + image/lib/src/formats/psd_decoder.dart | 73 + .../lib/src/formats/pvr/pvr_bit_utility.dart | 1620 + image/lib/src/formats/pvr/pvr_color.dart | 119 + .../formats/pvr/pvr_color_bounding_box.dart | 15 + image/lib/src/formats/pvr/pvr_info.dart | 66 + image/lib/src/formats/pvr/pvr_packet.dart | 512 + image/lib/src/formats/pvr_decoder.dart | 641 + image/lib/src/formats/pvr_encoder.dart | 348 + image/lib/src/formats/tga/tga_info.dart | 98 + image/lib/src/formats/tga_decoder.dart | 297 + image/lib/src/formats/tga_encoder.dart | 49 + .../lib/src/formats/tiff/tiff_bit_reader.dart | 55 + image/lib/src/formats/tiff/tiff_entry.dart | 66 + .../src/formats/tiff/tiff_fax_decoder.dart | 1635 + image/lib/src/formats/tiff/tiff_image.dart | 898 + image/lib/src/formats/tiff/tiff_info.dart | 21 + .../src/formats/tiff/tiff_lzw_decoder.dart | 146 + image/lib/src/formats/tiff_decoder.dart | 149 + image/lib/src/formats/tiff_encoder.dart | 82 + image/lib/src/formats/webp/vp8.dart | 2181 + .../lib/src/formats/webp/vp8_bit_reader.dart | 363 + image/lib/src/formats/webp/vp8_filter.dart | 739 + image/lib/src/formats/webp/vp8_types.dart | 256 + image/lib/src/formats/webp/vp8l.dart | 1529 + .../lib/src/formats/webp/vp8l_bit_reader.dart | 128 + .../src/formats/webp/vp8l_color_cache.dart | 23 + .../lib/src/formats/webp/vp8l_transform.dart | 479 + image/lib/src/formats/webp/webp_alpha.dart | 150 + image/lib/src/formats/webp/webp_filters.dart | 188 + image/lib/src/formats/webp/webp_frame.dart | 52 + image/lib/src/formats/webp/webp_huffman.dart | 108 + image/lib/src/formats/webp/webp_info.dart | 76 + image/lib/src/formats/webp_decoder.dart | 338 + image/lib/src/formats/webp_encoder.dart | 933 + image/lib/src/image/icc_profile.dart | 43 + image/lib/src/image/image.dart | 981 + image/lib/src/image/image_data.dart | 189 + image/lib/src/image/image_data_float16.dart | 117 + image/lib/src/image/image_data_float32.dart | 116 + image/lib/src/image/image_data_float64.dart | 116 + image/lib/src/image/image_data_int16.dart | 117 + image/lib/src/image/image_data_int32.dart | 117 + image/lib/src/image/image_data_int8.dart | 117 + image/lib/src/image/image_data_uint1.dart | 130 + image/lib/src/image/image_data_uint16.dart | 125 + image/lib/src/image/image_data_uint2.dart | 130 + image/lib/src/image/image_data_uint32.dart | 117 + image/lib/src/image/image_data_uint4.dart | 136 + image/lib/src/image/image_data_uint8.dart | 159 + image/lib/src/image/interpolation.dart | 14 + image/lib/src/image/palette.dart | 79 + image/lib/src/image/palette_float16.dart | 111 + image/lib/src/image/palette_float32.dart | 109 + image/lib/src/image/palette_float64.dart | 115 + image/lib/src/image/palette_int16.dart | 109 + image/lib/src/image/palette_int32.dart | 109 + image/lib/src/image/palette_int8.dart | 109 + image/lib/src/image/palette_uint16.dart | 109 + image/lib/src/image/palette_uint32.dart | 109 + image/lib/src/image/palette_uint8.dart | 121 + image/lib/src/image/palette_undefined.dart | 42 + image/lib/src/image/pixel.dart | 55 + image/lib/src/image/pixel_float16.dart | 308 + image/lib/src/image/pixel_float32.dart | 271 + image/lib/src/image/pixel_float64.dart | 271 + image/lib/src/image/pixel_int16.dart | 275 + image/lib/src/image/pixel_int32.dart | 275 + image/lib/src/image/pixel_int8.dart | 275 + image/lib/src/image/pixel_range_iterator.dart | 29 + image/lib/src/image/pixel_uint1.dart | 337 + image/lib/src/image/pixel_uint16.dart | 317 + image/lib/src/image/pixel_uint2.dart | 334 + image/lib/src/image/pixel_uint32.dart | 275 + image/lib/src/image/pixel_uint4.dart | 337 + image/lib/src/image/pixel_uint8.dart | 331 + image/lib/src/image/pixel_undefined.dart | 117 + image/lib/src/transform/bake_orientation.dart | 38 + image/lib/src/transform/copy_crop.dart | 98 + image/lib/src/transform/copy_crop_circle.dart | 65 + .../lib/src/transform/copy_expand_canvas.dart | 138 + image/lib/src/transform/copy_flip.dart | 6 + image/lib/src/transform/copy_rectify.dart | 47 + image/lib/src/transform/copy_resize.dart | 233 + .../transform/copy_resize_crop_square.dart | 136 + image/lib/src/transform/copy_rotate.dart | 125 + image/lib/src/transform/flip.dart | 167 + image/lib/src/transform/resize.dart | 193 + image/lib/src/transform/trim.dart | 177 + image/lib/src/util/_cast.dart | 4 + image/lib/src/util/_circle_test.dart | 76 + image/lib/src/util/_file_access.dart | 9 + image/lib/src/util/_file_access_html.dart | 7 + image/lib/src/util/_file_access_io.dart | 13 + image/lib/src/util/_internal.dart | 7 + image/lib/src/util/binary_quantizer.dart | 32 + image/lib/src/util/bit_utils.dart | 380 + image/lib/src/util/clip_line.dart | 108 + image/lib/src/util/color_util.dart | 552 + image/lib/src/util/file_access.dart | 1 + image/lib/src/util/float16.dart | 362 + image/lib/src/util/image_exception.dart | 10 + image/lib/src/util/input_buffer.dart | 278 + image/lib/src/util/math_util.dart | 21 + image/lib/src/util/min_max.dart | 22 + image/lib/src/util/neural_quantizer.dart | 517 + image/lib/src/util/octree_quantizer.dart | 259 + image/lib/src/util/output_buffer.dart | 155 + image/lib/src/util/point.dart | 25 + image/lib/src/util/quantizer.dart | 42 + image/lib/src/util/random.dart | 33 + image/lib/src/util/rational.dart | 32 + image/pubspec.yaml | 22 + introduction_screen/test/src/helper_test.dart | 17 - .../test/src/introduction_screen_test.dart | 234 - .../test/src/model/page_decoration_test.dart | 65 - .../test/src/model/position_test.dart | 43 - .../test/widget/intro_ui_test.dart | 161 - .../test/devices/device_consistency_test.dart | 86 - .../test/ecc/curve25519_test.dart | 459 - .../numeric_fingerprint_generator_test.dart | 553 - .../test/groups/group_cipher_test.dart | 322 - .../test/kdf/hkdf_test.dart | 555 - .../test/provisioning_cipher_test.dart | 29 - .../test/ratchet/chain_key_test.dart | 314 - .../test/ratchet/ratcheting_session_test.dart | 418 - .../test/ratchet/root_key_test.dart | 243 - .../test/session_builder_test.dart | 347 - .../test/session_cipher_test.dart | 256 - .../in_memory_identity_key_store_test.dart | 42 - .../impl/in_memory_pre_key_store_test.dart | 26 - .../impl/in_memory_session_store_test.dart | 48 - .../in_memory_signal_protocol_store_test.dart | 104 - .../in_memory_signed_pre_key_store_test.dart | 41 - .../test_in_memory_identity_key_store.dart | 19 - .../test_in_memory_signal_protocol_store.dart | 19 - .../test/util/byte_util_test.dart | 113 - lottie/pubspec.yaml | 2 + mutex/test/mutex_multiple_read_test.dart | 102 - mutex/test/mutex_readwrite_test.dart | 485 - mutex/test/mutex_test.dart | 340 - optional/test/optional_test.dart | 20 - optional/test/src/async.dart | 68 - optional/test/src/equality.dart | 29 - optional/test/src/extension.dart | 107 - optional/test/src/iterable.dart | 68 - optional/test/src/methods.dart | 286 - photo_view/test/controller_test.dart | 103 - .../test/scale_state_controller_test.dart | 66 - .../stream_cipher_as_block_cipher_test.dart | 22 - .../test/asn1/asn1_all_test-disabled.dart | 40 - pointycastle/test/asn1/asn1_dump_test.dart | 153 - pointycastle/test/asn1/asn1_object_test.dart | 101 - pointycastle/test/asn1/asn1_parser_test.dart | 336 - pointycastle/test/asn1/asn1_utils_test.dart | 155 - .../asn1/primitives/asn1_bit_string_test.dart | 245 - .../asn1/primitives/asn1_bmp_string_test.dart | 421 - .../asn1/primitives/asn1_boolean_test.dart | 36 - .../asn1/primitives/asn1_enumerated_test.dart | 37 - .../asn1_generalized_time_test.dart | 109 - .../asn1/primitives/asn1_ia5_string_test.dart | 449 - .../asn1/primitives/asn1_integer_test.dart | 113 - .../test/asn1/primitives/asn1_null_test.dart | 60 - .../asn1_object_identifier_test.dart | 79 - .../primitives/asn1_octet_string_test.dart | 269 - .../asn1_printable_string_test.dart | 286 - .../asn1/primitives/asn1_sequence_test.dart | 253 - .../test/asn1/primitives/asn1_set_test.dart | 103 - .../primitives/asn1_teletext_string_test.dart | 239 - .../asn1/primitives/asn1_utc_time_test.dart | 101 - .../primitives/asn1_utf8_string_test.dart | 312 - .../test/asymmetric/ec_elgamal_test.dart | 80 - pointycastle/test/asymmetric/oaep_test.dart | 1005 - pointycastle/test/asymmetric/pkcs1_test.dart | 30 - pointycastle/test/asymmetric/rsa_test.dart | 62 - pointycastle/test/block/aes_fast_test.dart | 73 - pointycastle/test/block/aes_test.dart | 229 - pointycastle/test/block/blowfish_test.dart | 29 - pointycastle/test/block/camellia_test.dart | 40 - .../test/block/desede_engine_test.dart | 45 - pointycastle/test/block/rc2_engine_test.dart | 104 - pointycastle/test/block/twofish_test.dart | 36 - pointycastle/test/digests/blake2b_test.dart | 73 - pointycastle/test/digests/cshake_test.dart | 240 - pointycastle/test/digests/keccak_test.dart | 356 - pointycastle/test/digests/md2_test.dart | 14 - pointycastle/test/digests/md4_test.dart | 14 - pointycastle/test/digests/md5_test.dart | 14 - pointycastle/test/digests/ripemd128_test.dart | 14 - pointycastle/test/digests/ripemd160_test.dart | 20 - pointycastle/test/digests/ripemd256_test.dart | 14 - pointycastle/test/digests/ripemd320_test.dart | 14 - pointycastle/test/digests/sha1_test.dart | 16 - pointycastle/test/digests/sha224_test.dart | 14 - pointycastle/test/digests/sha256_test.dart | 16 - pointycastle/test/digests/sha384_test.dart | 16 - pointycastle/test/digests/sha3_test.dart | 182 - pointycastle/test/digests/sha512_test.dart | 16 - pointycastle/test/digests/sha512t_test.dart | 32 - pointycastle/test/digests/shake_test.dart | 130 - pointycastle/test/digests/sm3_test.dart | 54 - pointycastle/test/digests/tiger_test.dart | 14 - pointycastle/test/digests/whirlpool_test.dart | 14 - pointycastle/test/ecc/curves_test.dart | 54 - pointycastle/test/ecc/ecdh_test.dart | 338 - pointycastle/test/impl_test.dart | 168 - .../key_derivators/argon2_nonvm_test.dart | 66 - .../test/key_derivators/argon2_vm_test.dart | 142 - .../key_derivators/concatkdf_nonvm_test.dart | 8415 -- .../test/key_derivators/concatkdf_test.dart | 269 - .../test/key_derivators/hkdf_test.dart | 247 - .../test/key_derivators/pbkdf2_test.dart | 21 - .../pkcs12_parameter_generator_test.dart | 97 - .../key_derivators/scrypt_nonvm_test.dart | 29 - .../test/key_derivators/scrypt_vm_test.dart | 36 - .../key_generators/ec_key_generator_test.dart | 42 - .../rsa_key_generator_test.dart | 254 - .../test/macs/cbc_block_cipher_mac_test.dart | 31 - pointycastle/test/macs/cmac_test.dart | 53 - pointycastle/test/macs/hmac_test.dart | 222 - pointycastle/test/macs/poly1305_test.dart | 80 - pointycastle/test/macs/poly1305_web_test.dart | 22 - pointycastle/test/modes/cbc_test.dart | 40 - pointycastle/test/modes/ccm_test.dart | 71 - pointycastle/test/modes/cfb_test.dart | 40 - pointycastle/test/modes/ecb_test.dart | 40 - pointycastle/test/modes/gcm_test.dart | 297 - pointycastle/test/modes/gctr_test.dart | 40 - pointycastle/test/modes/ige_test.dart | 36 - pointycastle/test/modes/ofb_test.dart | 40 - pointycastle/test/modes/sic_test.dart | 55 - .../test/paddings/iso7816d4_test.dart | 13 - .../paddings/padded_block_cipher_test.dart | 112 - pointycastle/test/paddings/pkcs7_test.dart | 15 - .../test/platform/platform_native_test.dart | 15 - .../test/platform/platform_web_test.dart | 16 - .../auto_seed_block_ctr_random_test.dart | 65 - .../test/random/block_ctr_random_test.dart | 4138 - pointycastle/test/random/fixed_rng_test.dart | 33 - .../test/random/fortuna_random_test.dart | 64 - .../test/signers/ecdsa_signer_test.dart | 71 - pointycastle/test/signers/ecdsa_vec.dart | 4147 - .../test/signers/ecdsa_vector_vm_test.dart | 192 - .../test/signers/pss_signer_test.dart | 243 - .../test/signers/rsa_signer_test.dart | 49 - pointycastle/test/src/ct_nonvm_test.dart | 65 - pointycastle/test/src/ct_test.dart | 66 - pointycastle/test/src/ufixnum_test.dart | 786 - pointycastle/test/src/utils_test.dart | 82 - pointycastle/test/srp/srp_test.dart | 298 - pointycastle/test/stream/chacha20_test.dart | 156 - .../test/stream/chacha20poly1305_test.dart | 90 - pointycastle/test/stream/eax_test.dart | 313 - pointycastle/test/stream/rc4_engine_test.dart | 34 - pointycastle/test/stream/salsa20_test.dart | 39 - .../test/runners/asymmetric_block_cipher.dart | 78 - .../test/test/runners/block_cipher.dart | 94 - pointycastle/test/test/runners/digest.dart | 43 - .../test/test/runners/key_derivators.dart | 35 - .../test/test/runners/key_generators.dart | 24 - pointycastle/test/test/runners/mac.dart | 91 - pointycastle/test/test/runners/padding.dart | 33 - pointycastle/test/test/runners/registry.dart | 92 - pointycastle/test/test/runners/signer.dart | 114 - .../test/test/runners/stream_cipher.dart | 80 - .../test/test/src/fixed_secure_random.dart | 58 - pointycastle/test/test/src/helpers.dart | 158 - .../src/null_asymmetric_block_cipher.dart | 49 - .../test/test/src/null_block_cipher.dart | 37 - pointycastle/test/test/src/null_digest.dart | 36 - .../test/test/src/null_secure_random.dart | 20 - .../test/test/src/null_stream_cipher.dart | 30 - .../test/test_resources/kdf-56c/KDA.req.json | 72385 ---------------- .../test/test_resources/kdf-56c/KDA.rsp.json | 23010 ----- pro_video_editor/LICENSE | 28 + pro_video_editor/android/.gitignore | 9 + pro_video_editor/android/build.gradle | 87 + pro_video_editor/android/gradle.properties | 1 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43583 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + pro_video_editor/android/gradlew | 251 + pro_video_editor/android/gradlew.bat | 94 + pro_video_editor/android/settings.gradle | 1 + .../android/src/main/AndroidManifest.xml | 3 + .../pro_video_editor/ProVideoEditorPlugin.kt | 938 + .../src/core/constants/LoggingConstants.kt | 3 + .../src/features/audio/ExtractAudio.kt | 656 + .../src/features/audio/MergeAudio.kt | 670 + .../src/features/audio/PcmSpeedProcessor.kt | 84 + .../src/features/audio/WavFileWriter.kt | 434 + .../audio/models/AudioExtractConfig.kt | 84 + .../audio/models/AudioExtractJobHandle.kt | 19 + .../features/audio/models/AudioExtractTask.kt | 56 + .../features/audio/models/AudioMergeConfig.kt | 121 + .../src/features/metadata/Metadata.kt | 397 + .../metadata/models/MetadataConfig.kt | 31 + .../src/features/render/RenderVideo.kt | 1089 + .../features/render/helpers/ApplyAnimation.kt | 290 + .../features/render/helpers/ApplyBitrate.kt | 108 + .../src/features/render/helpers/ApplyBlur.kt | 24 + .../features/render/helpers/ApplyChromaKey.kt | 56 + .../render/helpers/ApplyClipTransition.kt | 88 + .../render/helpers/ApplyColorMatrix.kt | 141 + .../render/helpers/ApplyComposition.kt | 86 + .../src/features/render/helpers/ApplyCrop.kt | 104 + .../src/features/render/helpers/ApplyFlip.kt | 24 + .../features/render/helpers/ApplyFrameRate.kt | 25 + .../render/helpers/ApplyImageLayer.kt | 397 + .../features/render/helpers/ApplyOpacity.kt | 24 + .../render/helpers/ApplyPlaybackSpeed.kt | 32 + .../features/render/helpers/ApplyRotation.kt | 23 + .../src/features/render/helpers/ApplyScale.kt | 26 + .../render/helpers/AudioPreRenderer.kt | 492 + .../render/helpers/AudioSequenceBuilder.kt | 144 + .../render/helpers/BitrateCapPolicy.kt | 45 + .../render/helpers/ChromaKeyEffect.kt | 321 + .../features/render/helpers/ChromaKeyMath.kt | 90 + .../render/helpers/ClipTransitionGeometry.kt | 160 + .../render/helpers/ClipTransitionRenderer.kt | 1002 + .../render/helpers/CompositionBuilder.kt | 166 + .../helpers/ConfigurableInAppMp4Muxer.kt | 137 + .../helpers/EncoderFailureClassifier.kt | 71 + .../src/features/render/helpers/GifDecoder.kt | 76 + .../helpers/LayeredCompositionBuilder.kt | 556 + .../render/helpers/MediaInfoExtractor.kt | 364 + .../helpers/ResilientVideoEncoderFactory.kt | 309 + .../helpers/VideoCompositionTransformation.kt | 192 + .../render/helpers/VideoEncoderConfig.kt | 179 + .../helpers/VideoGlobalTrimCalculator.kt | 147 + .../features/render/helpers/VideoReverser.kt | 1044 + .../render/helpers/VideoSequenceBuilder.kt | 905 + .../VideoTimelineDurationCalculator.kt | 19 + .../render/helpers/VideoTranscoder.kt | 242 + .../render/helpers/VolumeAudioProcessor.kt | 82 + .../render/helpers/VolumeControlAudioMixer.kt | 182 + .../models/CodecResourceExhaustedException.kt | 24 + .../features/render/models/RenderConfig.kt | 568 + .../features/render/models/RenderJobHandle.kt | 45 + .../src/features/render/models/RenderTask.kt | 33 + .../VideoEncoderConfigurationException.kt | 20 + .../features/render/utils/EffectsProcessor.kt | 66 + .../render/utils/RotatedVideoDimensions.kt | 68 + .../features/render/utils/VideoMimeUtils.kt | 34 + .../features/split/SplitExportDiagnostics.kt | 49 + .../src/features/split/SplitVideo.kt | 502 + .../stopmotion/StopMotionGenerator.kt | 323 + .../stopmotion/models/StopMotionConfig.kt | 79 + .../thumbnail/SequentialFrameDecoder.kt | 616 + .../features/thumbnail/ThumbnailGenerator.kt | 678 + .../thumbnail/models/ThumbnailConfig.kt | 63 + .../features/waveform/WaveformGenerator.kt | 437 + .../waveform/models/WaveformConfig.kt | 56 + .../features/waveform/models/WaveformTask.kt | 32 + .../src/shared/concurrency/ExportGate.kt | 102 + .../src/shared/logging/PluginLog.kt | 113 + .../src/shared/media/EncodedImage.kt | 101 + .../src/shared/media/ImageOrientation.kt | 265 + .../src/shared/media/PcmRangeDecoder.kt | 239 + .../ProVideoEditorPluginTest.kt | 53 + .../render/helpers/AudioPreRendererTest.kt | 159 + .../render/helpers/BitrateCapPolicyTest.kt | 69 + .../render/helpers/ChromaKeyMathTest.kt | 261 + .../helpers/ClipTransitionGeometryTest.kt | 291 + .../helpers/EncoderFailureClassifierTest.kt | 108 + .../render/helpers/OverlayDecodeSizeTest.kt | 87 + .../render/helpers/SlideOffsetTest.kt | 130 + .../render/helpers/VideoEncoderConfigTest.kt | 133 + .../helpers/VideoGlobalTrimCalculatorTest.kt | 113 + .../VideoTimelineDurationCalculatorTest.kt | 54 + .../split/SplitExportDiagnosticsTest.kt | 57 + .../src/shared/concurrency/ExportGateTest.kt | 109 + .../src/shared/media/EncodedImageTest.kt | 159 + .../src/shared/media/ImageOrientationTest.kt | 223 + .../darwin/FlutterFramework/Package.swift | 23 + .../FlutterFramework/FlutterFramework.swift | 3 + .../darwin/pro_video_editor.podspec | 36 + .../darwin/pro_video_editor/Package.swift | 41 + .../Resources/PrivacyInfo.xcprivacy | 14 + .../ProVideoEditorPlugin.swift | 946 + .../src/shared/concurrency/ExportGate.swift | 79 + .../concurrency/ExportSessionDriver.swift | 185 + .../concurrency/ExportSessionGuard.swift | 85 + .../shared/concurrency/ExportWatchdog.swift | 198 + .../core/constants/LoggingConstants.swift | 7 + .../src/shared/core/models/EncodedImage.swift | 88 + .../src/shared/core/models/RenderTask.swift | 73 + .../src/shared/extensions/StreamHandler.swift | 91 + .../shared/features/audio/ExtractAudio.swift | 655 + .../shared/features/audio/MergeAudio.swift | 481 + .../audio/models/AudioExtractConfig.swift | 88 + .../audio/models/AudioExtractTask.swift | 76 + .../audio/models/AudioMergeConfig.swift | 123 + .../shared/features/metadata/Metadata.swift | 464 + .../metadata/models/MetadataConfig.swift | 35 + .../shared/features/render/RenderVideo.swift | 1087 + .../render/helpers/ApplyAnimation.swift | 198 + .../render/helpers/ApplyBitrate.swift | 112 + .../features/render/helpers/ApplyBlur.swift | 23 + .../render/helpers/ApplyChromaKey.swift | 175 + .../render/helpers/ApplyColorMatrix.swift | 100 + .../render/helpers/ApplyComposition.swift | 45 + .../features/render/helpers/ApplyCrop.swift | 53 + .../features/render/helpers/ApplyFlip.swift | 24 + .../render/helpers/ApplyImageLayer.swift | 65 + .../render/helpers/ApplyPlaybackSpeed.swift | 57 + .../render/helpers/ApplyRotation.swift | 32 + .../features/render/helpers/ApplyScale.swift | 30 + .../render/helpers/AudioPreRenderer.swift | 352 + .../render/helpers/AudioReverser.swift | 308 + .../render/helpers/AudioSequenceBuilder.swift | 162 + .../render/helpers/BitrateCapPolicy.swift | 77 + .../helpers/BitrateCappedExporter.swift | 501 + .../helpers/ClipTransitionGeometry.swift | 111 + .../helpers/ClipTransitionRenderer.swift | 397 + .../render/helpers/CompositionBuilder.swift | 464 + .../render/helpers/DecodeOrientedImage.swift | 60 + .../helpers/LayeredCompositionBuilder.swift | 371 + .../render/helpers/MediaInfoExtractor.swift | 381 + .../render/helpers/TrackEndTrimmer.swift | 112 + .../render/helpers/VideoSequenceBuilder.swift | 555 + .../render/helpers/VideoTranscoder.swift | 283 + .../features/render/models/RenderConfig.swift | 592 + .../features/render/models/VideoClip.swift | 81 + .../render/models/VideoCompositorConfig.swift | 78 + .../render/utils/VideoCompositor.swift | 1014 + .../render/utils/VideoMimeUtils.swift | 24 + .../shared/features/split/SplitVideo.swift | 355 + .../stopmotion/StopMotionGenerator.swift | 359 + .../stopmotion/models/StopMotionConfig.swift | 91 + .../thumbnail/ThumbnailGenerator.swift | 409 + .../thumbnail/models/ThumbnailConfig.swift | 91 + .../features/waveform/WaveformGenerator.swift | 615 + .../waveform/models/WaveformConfig.swift | 57 + .../waveform/models/WaveformTask.swift | 51 + .../src/shared/logging/PluginLog.swift | 108 + .../audio/audio_extract_configs_model.dart | 123 + .../core/models/audio/audio_format_model.dart | 56 + .../audio/audio_merge_configs_model.dart | 221 + .../audio/audio_merge_result_model.dart | 101 + .../core/models/audio/audio_track_model.dart | 152 + .../models/audio/waveform_chunk_model.dart | 151 + .../models/audio/waveform_configs_model.dart | 178 + .../models/audio/waveform_data_model.dart | 205 + .../models/exceptions/audio_exceptions.dart | 8 + .../models/exceptions/render_exceptions.dart | 53 + .../image/editor_layer_image_model.dart | 218 + .../core/models/image/image_layer_model.dart | 203 + .../models/image/layer_animation_model.dart | 210 + .../models/platform/native_log_entry.dart | 77 + .../models/platform/native_log_level.dart | 51 + .../thumbnail/key_frames_configs_model.dart | 38 + .../single_thumbnail_configs_model.dart | 72 + .../thumbnail/thumbnail_base_abstract.dart | 68 + .../thumbnail/thumbnail_box_fit_model.dart | 15 + .../thumbnail/thumbnail_configs_model.dart | 38 + .../thumbnail/thumbnail_format_model.dart | 13 + .../core/models/video/chroma_key_model.dart | 523 + .../models/video/clip_transition_model.dart | 194 + .../core/models/video/color_filter_model.dart | 87 + .../core/models/video/editor_video_model.dart | 331 + .../models/video/export_transform_model.dart | 138 + .../lib/core/models/video/progress_model.dart | 21 + .../models/video/segment_transform_model.dart | 110 + .../core/models/video/split_video_model.dart | 142 + .../video/stop_motion_render_data_model.dart | 325 + .../models/video/video_composition_model.dart | 136 + .../core/models/video/video_layer_model.dart | 150 + .../models/video/video_metadata_model.dart | 353 + .../models/video/video_quality_config.dart | 123 + .../models/video/video_quality_preset.dart | 105 + .../models/video/video_render_data_model.dart | 715 + .../models/video/video_segment_model.dart | 275 + .../lib/core/platform/io/io_helper.dart | 1 + .../lib/core/platform/io/io_web.dart | 165 + .../core/platform/native_method_channel.dart | 747 + .../platform/path/path_provider_helper.dart | 2 + .../platform/path/path_provider_native.dart | 1 + .../core/platform/path/path_provider_web.dart | 9 + .../lib/core/platform/platform_interface.dart | 709 + .../lib/core/platform/web_method_channel.dart | 219 + .../lib/core/services/web/web_manager.dart | 52 + .../services/web/web_meta_data_reader.dart | 88 + .../services/web/web_thumbnail_generator.dart | 161 + .../lib/core/utils/chroma_key_detector.dart | 233 + .../lib/core/utils/web_blob_utils.dart | 50 + .../lib/core/utils/web_canvas_utils.dart | 41 + .../features/audio/models/waveform_style.dart | 105 + .../audio/widgets/audio_waveform.dart | 256 + .../audio/widgets/streaming_waveform.dart | 266 + .../audio/widgets/waveform_painter.dart | 145 + pro_video_editor/lib/pro_video_editor.dart | 46 + .../lib/shared/models/time_range_mixin.dart | 19 + .../lib/shared/utils/converters.dart | 195 + .../shared/utils/file_constructor_utils.dart | 36 + .../shared/utils/parser/double_parser.dart | 50 + .../lib/shared/utils/parser/int_parser.dart | 53 + .../lib/shared/utils/parser/size_parser.dart | 35 + pro_video_editor/pubspec.yaml | 67 + pubspec.yaml | 3 - qr/test/qr_alphanumeric_test.dart | 89 - qr/test/qr_bit_buffer_test.dart | 75 - qr/test/qr_byte_test.dart | 21 - qr/test/qr_code_test.dart | 194 - qr/test/qr_code_test_data.dart | 16248 ---- qr/test/qr_code_test_data_with_mask.dart | 194 - qr/test/qr_numeric_test.dart | 115 - .../qr_image_data_module_styled_golden.png | Bin 32358 -> 0 bytes ...qr_image_eye_data_module_styled_golden.png | Bin 41232 -> 0 bytes .../.golden/qr_image_eye_styled_golden.png | Bin 15879 -> 0 bytes .../qr_image_foreground_colored_golden.png | Bin 41271 -> 0 bytes qr_flutter/test/.golden/qr_image_golden.png | Bin 5035 -> 0 bytes .../test/.golden/qr_image_logo_golden.png | Bin 12850 -> 0 bytes qr_flutter/test/.golden/qr_painter_golden.png | Bin 1505 -> 0 bytes qr_flutter/test/.images/logo_yakka.png | Bin 8424 -> 0 bytes qr_flutter/test/all_test.dart | 14 - qr_flutter/test/image_test.dart | 218 - qr_flutter/test/painter_test.dart | 41 - restart_app/test/restart_app_test.dart | 101 - .../test/screen_protector_test.dart | 22 - sprintf/test/sprintf_test.dart | 225 - sprintf/test/testing_data.dart | 12140 --- update.py | 105 - video_player/LICENSE | 25 + video_player/lib/src/closed_caption_file.dart | 86 + video_player/lib/src/sub_rip.dart | 127 + video_player/lib/src/web_vtt.dart | 209 + video_player/lib/video_player.dart | 1623 + video_player/pubspec.yaml | 41 + video_player_android/LICENSE | 25 + video_player_android/android/build.gradle.kts | 87 + .../android/lint-baseline.xml | 15 + .../android/settings.gradle.kts | 1 + .../android/src/main/AndroidManifest.xml | 3 + .../videoplayer/ExoPlayerEventListener.java | 204 + .../plugins/videoplayer/HttpVideoAsset.java | 94 + .../plugins/videoplayer/LocalVideoAsset.java | 29 + .../plugins/videoplayer/QueuingEventSink.java | 81 + .../plugins/videoplayer/RtspVideoAsset.java | 33 + .../plugins/videoplayer/VideoAsset.java | 99 + .../plugins/videoplayer/VideoPlayer.java | 464 + .../videoplayer/VideoPlayerCallbacks.java | 31 + .../VideoPlayerEventCallbacks.java | 76 + .../videoplayer/VideoPlayerOptions.java | 25 + .../videoplayer/VideoPlayerPlugin.java | 237 + .../platformview/PlatformVideoView.java | 125 + .../PlatformVideoViewFactory.java | 69 + .../PlatformViewExoPlayerEventListener.java | 45 + .../platformview/PlatformViewVideoPlayer.java | 92 + .../TextureExoPlayerEventListener.java | 62 + .../texture/TextureVideoPlayer.java | 142 + .../flutter/plugins/videoplayer/Messages.kt | 1636 + .../ExoPlayerEventListenerTest.java | 181 + .../plugins/videoplayer/FakeVideoAsset.java | 37 + .../PlatformVideoViewFactoryTest.java | 45 + .../videoplayer/PlatformVideoViewTest.java | 125 + ...latformViewExoPlayerEventListenerTest.java | 80 + .../PlatformViewVideoPlayerTest.java | 70 + .../TextureExoPlayerEventListenerTest.java | 127 + .../videoplayer/TextureVideoPlayerTest.java | 230 + .../plugins/videoplayer/VideoAssetTest.java | 140 + .../VideoPlayerEventCallbacksTest.java | 115 + .../videoplayer/VideoPlayerPluginTest.java | 116 + .../plugins/videoplayer/VideoPlayerTest.java | 1124 + .../lib/src/android_video_player.dart | 628 + video_player_android/lib/src/messages.g.dart | 1303 + .../lib/src/platform_view_player.dart | 52 + .../lib/video_player_android.dart | 5 + video_player_android/pubspec.yaml | 35 + video_player_avfoundation/LICENSE | 25 + .../darwin/Assets/.gitkeep | 0 .../darwin/RunnerTests/TestClasses.swift | 297 + .../darwin/RunnerTests/VideoPlayerTests.swift | 1077 + .../darwin/video_player_avfoundation.podspec | 32 + .../video_player_avfoundation/Package.swift | 57 + .../NativeVideoViewFactory.swift | 66 + .../Resources/PrivacyInfo.xcprivacy | 14 + .../VideoPlayerPlugin.swift | 323 + .../VideoPlayerPluginMessages.g.swift | 440 + .../FVPNativeVideoView.m | 38 + .../include/.gitkeep | 0 .../include/FVPEmpty.h | 6 + .../FVPCoreVideoDisplayLink.m | 99 + .../FVPNativeVideoView.m | 25 + .../include/.gitkeep | 0 .../include/FVPEmpty.h | 6 + .../AVAssetTrackUtils.m | 44 + .../FVPAVFactory.m | 163 + .../FVPCADisplayLink.m | 84 + .../FVPEventBridge.m | 122 + .../FVPFrameUpdater.m | 19 + .../FVPTextureBasedVideoPlayer.m | 218 + .../FVPVideoPlayer.m | 678 + .../FVPViewProvider.m | 38 + .../VideoPlayerInstanceMessages.g.m | 772 + .../AVAssetTrackUtils.h | 12 + .../FVPAVFactory.h | 114 + .../FVPAssetProvider.h | 34 + .../FVPDisplayLink.h | 55 + .../FVPEventBridge.h | 26 + .../FVPFrameUpdater.h | 36 + .../FVPNativeVideoView.h | 27 + .../FVPTextureBasedVideoPlayer.h | 33 + .../FVPTextureBasedVideoPlayer_Test.h | 29 + .../FVPVideoEventListener.h | 36 + .../FVPVideoPlayer.h | 47 + .../FVPVideoPlayer_Internal.h | 40 + .../FVPViewProvider.h | 35 + .../VideoPlayerInstanceMessages.g.h | 127 + .../lib/src/avfoundation_video_player.dart | 463 + .../src/video_player_instance_messages.g.dart | 609 + .../src/video_player_plugin_messages.g.dart | 349 + .../lib/video_player_avfoundation.dart | 5 + video_player_avfoundation/pubspec.yaml | 39 + .../test/benchmark/curve25519_benchmark.dart | 35 - x25519/test/benchmark/rate_benchmark.dart | 42 - x25519/test/curve25519_test.dart | 59 - x25519/test/vectors_test.dart | 1894 - 1247 files changed, 189203 insertions(+), 227002 deletions(-) delete mode 100644 adaptive_number/test/adaptive_number_test.dart create mode 100644 audio_waveforms/LICENSE create mode 100644 audio_waveforms/android/.gitignore create mode 100644 audio_waveforms/android/build.gradle create mode 100644 audio_waveforms/android/gradle.properties create mode 100644 audio_waveforms/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 audio_waveforms/android/settings.gradle create mode 100644 audio_waveforms/android/src/main/AndroidManifest.xml create mode 100644 audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/AudioPlayer.kt create mode 100644 audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/AudioRecorder.kt create mode 100644 audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/AudioWaveformsPlugin.kt create mode 100644 audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/RecorderSettings.kt create mode 100644 audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/Utils.kt create mode 100644 audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/WaveformExtractor.kt create mode 100644 audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/encoders/CommonEncoder.kt create mode 100644 audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/encoders/WavEncoder.kt create mode 100644 audio_waveforms/ios/.gitignore rename exif/test/data/test-data => audio_waveforms/ios/Assets/.gitkeep (100%) create mode 100644 audio_waveforms/ios/Classes/AudioPlayer.swift create mode 100644 audio_waveforms/ios/Classes/AudioRecorder.swift create mode 100644 audio_waveforms/ios/Classes/AudioWaveformsPlugin.h create mode 100644 audio_waveforms/ios/Classes/AudioWaveformsPlugin.m create mode 100644 audio_waveforms/ios/Classes/RecorderBytesStreamEngine.swift create mode 100644 audio_waveforms/ios/Classes/RecordingSettings.swift create mode 100644 audio_waveforms/ios/Classes/SwiftAudioWaveformsPlugin.swift create mode 100644 audio_waveforms/ios/Classes/Utils.swift create mode 100644 audio_waveforms/ios/Classes/WaveformExtractor.swift create mode 100644 audio_waveforms/ios/audio_waveforms.podspec create mode 100644 audio_waveforms/lib/audio_waveforms.dart create mode 100644 audio_waveforms/lib/src/audio_file_waveforms.dart create mode 100644 audio_waveforms/lib/src/audio_waveforms.dart create mode 100644 audio_waveforms/lib/src/base/audio_waveforms_interface.dart create mode 100644 audio_waveforms/lib/src/base/constants.dart create mode 100644 audio_waveforms/lib/src/base/label.dart create mode 100644 audio_waveforms/lib/src/base/platform_streams.dart create mode 100644 audio_waveforms/lib/src/base/player_identifier.dart create mode 100644 audio_waveforms/lib/src/base/player_wave_style.dart create mode 100644 audio_waveforms/lib/src/base/utils.dart create mode 100644 audio_waveforms/lib/src/base/wave_clipper.dart create mode 100644 audio_waveforms/lib/src/base/wave_style.dart create mode 100644 audio_waveforms/lib/src/controllers/player_controller.dart create mode 100644 audio_waveforms/lib/src/controllers/recorder_controller.dart create mode 100644 audio_waveforms/lib/src/controllers/waveform_extraction_controller.dart create mode 100644 audio_waveforms/lib/src/models/android_encoder_settings.dart create mode 100644 audio_waveforms/lib/src/models/ios_encoder_setting.dart create mode 100644 audio_waveforms/lib/src/models/recorder_settings.dart create mode 100644 audio_waveforms/lib/src/painters/player_wave_painter.dart create mode 100644 audio_waveforms/lib/src/painters/recorder_wave_painter.dart create mode 100644 audio_waveforms/pubspec.yaml create mode 100644 avatar_maker/LICENSE create mode 100644 avatar_maker/assets/icons/accessories.svg create mode 100644 avatar_maker/assets/icons/background.svg create mode 100644 avatar_maker/assets/icons/eyebrows.svg create mode 100644 avatar_maker/assets/icons/eyes.svg create mode 100644 avatar_maker/assets/icons/facial_hair.svg create mode 100644 avatar_maker/assets/icons/facial_hair_color.svg create mode 100644 avatar_maker/assets/icons/hair.svg create mode 100644 avatar_maker/assets/icons/hair_color.svg create mode 100644 avatar_maker/assets/icons/mouth.svg create mode 100644 avatar_maker/assets/icons/noses.svg create mode 100644 avatar_maker/assets/icons/outfit.svg create mode 100644 avatar_maker/assets/icons/outfit_color.svg create mode 100644 avatar_maker/assets/icons/skin.svg create mode 100644 avatar_maker/lib/avatar_maker.dart create mode 100644 avatar_maker/lib/l10n/app_localizations.dart create mode 100644 avatar_maker/lib/l10n/app_localizations_en.dart create mode 100644 avatar_maker/lib/l10n/app_localizations_fr.dart create mode 100644 avatar_maker/lib/src/avatar/avatar_maker_avatar.dart create mode 100644 avatar_maker/lib/src/core/controllers/avatar_maker_controller.dart create mode 100644 avatar_maker/lib/src/core/controllers/controllers.dart create mode 100644 avatar_maker/lib/src/core/controllers/non_persistent_avatar_maker_controller.dart create mode 100644 avatar_maker/lib/src/core/controllers/persistent_avatar_maker_controller.dart create mode 100644 avatar_maker/lib/src/core/enums/placeholders.dart create mode 100644 avatar_maker/lib/src/core/enums/preferences_label.dart create mode 100644 avatar_maker/lib/src/core/enums/property_categories.dart create mode 100644 avatar_maker/lib/src/core/enums/property_category_ids.dart create mode 100644 avatar_maker/lib/src/core/enums/property_items/accessories.dart create mode 100644 avatar_maker/lib/src/core/enums/property_items/background_style.dart create mode 100644 avatar_maker/lib/src/core/enums/property_items/eyebrows.dart create mode 100644 avatar_maker/lib/src/core/enums/property_items/eyes.dart create mode 100644 avatar_maker/lib/src/core/enums/property_items/facial_hair_colors.dart create mode 100644 avatar_maker/lib/src/core/enums/property_items/facial_hair_types.dart create mode 100644 avatar_maker/lib/src/core/enums/property_items/hair_colors.dart create mode 100644 avatar_maker/lib/src/core/enums/property_items/hair_styles.dart create mode 100644 avatar_maker/lib/src/core/enums/property_items/mouths.dart create mode 100644 avatar_maker/lib/src/core/enums/property_items/noses.dart create mode 100644 avatar_maker/lib/src/core/enums/property_items/outfit_colors.dart create mode 100644 avatar_maker/lib/src/core/enums/property_items/outfit_types.dart create mode 100644 avatar_maker/lib/src/core/enums/property_items/property_items.dart create mode 100644 avatar_maker/lib/src/core/enums/property_items/skin_colors.dart create mode 100644 avatar_maker/lib/src/core/models/customized_property_category.dart create mode 100644 avatar_maker/lib/src/core/models/property_category.dart create mode 100644 avatar_maker/lib/src/core/models/property_item.dart create mode 100644 avatar_maker/lib/src/core/models/theme_data.dart create mode 100644 avatar_maker/lib/src/core/providers/avatar_maker_controller_provider.dart create mode 100644 avatar_maker/lib/src/core/providers/providers.dart create mode 100644 avatar_maker/lib/src/core/services/accessory_service.dart create mode 100644 avatar_maker/lib/src/core/services/avatar_service.dart create mode 100644 avatar_maker/lib/src/core/services/background_service.dart create mode 100644 avatar_maker/lib/src/core/services/color_service.dart create mode 100644 avatar_maker/lib/src/core/services/eye_service.dart create mode 100644 avatar_maker/lib/src/core/services/eyebrow_service.dart create mode 100644 avatar_maker/lib/src/core/services/facial_hairs_service.dart create mode 100644 avatar_maker/lib/src/core/services/hair_service.dart create mode 100644 avatar_maker/lib/src/core/services/mouth_service.dart create mode 100644 avatar_maker/lib/src/core/services/nose_service.dart create mode 100644 avatar_maker/lib/src/core/services/options_service.dart create mode 100644 avatar_maker/lib/src/core/services/outfit_service.dart create mode 100644 avatar_maker/lib/src/core/services/property_category_service.dart create mode 100644 avatar_maker/lib/src/core/services/skin_service.dart create mode 100644 avatar_maker/lib/src/customizer/avatar_maker_customizer.dart create mode 100644 avatar_maker/lib/src/customizer/avatar_maker_random_widget.dart create mode 100644 avatar_maker/lib/src/customizer/avatar_maker_reset_widget.dart create mode 100644 avatar_maker/lib/src/customizer/avatar_maker_save_widget.dart create mode 100644 avatar_maker/lib/src/customizer/widgets/customizer_appbar.dart create mode 100644 avatar_maker/lib/src/customizer/widgets/customizer_arrow_button.dart create mode 100644 avatar_maker/lib/src/customizer/widgets/customizer_body.dart create mode 100644 avatar_maker/lib/src/customizer/widgets/customizer_bottom_navbar.dart create mode 100644 avatar_maker/pubspec.yaml create mode 100644 blurhash_dart/LICENSE create mode 100644 blurhash_dart/lib/blurhash_dart.dart create mode 100644 blurhash_dart/lib/blurhash_extensions.dart create mode 100644 blurhash_dart/lib/src/blurhash.dart create mode 100644 blurhash_dart/lib/src/encoding.dart create mode 100644 blurhash_dart/lib/src/exception.dart create mode 100644 blurhash_dart/lib/src/extension.dart create mode 100644 blurhash_dart/lib/src/foundation.dart create mode 100644 blurhash_dart/pubspec.yaml create mode 100644 camera_android_camerax/LICENSE create mode 100644 camera_android_camerax/android/build.gradle.kts create mode 100644 camera_android_camerax/android/lint-baseline.xml create mode 100644 camera_android_camerax/android/settings.gradle.kts create mode 100644 camera_android_camerax/android/src/main/AndroidManifest.xml create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AnalyzerProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/AspectRatioStrategyProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraControlProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/Camera2CameraInfoProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraCharacteristicsProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraControlProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraIntegerRangeProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsError.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsErrorProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsManager.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSizeProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraStateProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraStateStateErrorProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXLibrary.g.kt create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestOptionsProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CaptureRequestProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManagerProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DisplayOrientedMeteringPointFactoryProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ExposureStateProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FallbackStrategyProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionBuilderProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringActionProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/FocusMeteringResultProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageAnalysisProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageProxyProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageProxyUtils.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageProxyUtilsProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/LiveDataProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/MeteringPointFactoryProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/MeteringPointProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ObserverProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PendingRecordingProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PlaneProxyProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProxyApiRegistrar.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProxyLifecycleProvider.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/QualitySelectorProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/RecorderProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/RecordingProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionFilterProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionInfoProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionSelectorProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResolutionStrategyProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ResultCompat.kt create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesManager.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesManagerProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/VideoCaptureProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/VideoRecordEventListener.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/VideoRecordEventListenerProxyApi.java create mode 100644 camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ZoomStateProxyApi.java create mode 100644 camera_android_camerax/android/src/test/java/android/util/FakeActivity.java create mode 100644 camera_android_camerax/android/src/test/java/android/util/Range.java create mode 100644 camera_android_camerax/android/src/test/java/android/util/Rational.java create mode 100644 camera_android_camerax/android/src/test/java/android/util/Size.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/AnalyzerTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/AspectRatioStrategyTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraControlTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/Camera2CameraInfoTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraAndroidCameraxPluginTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraControlTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraInfoTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraIntegerRangeTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraPermissionsErrorTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraPermissionsManagerTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraSelectorTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraSizeTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraStateStateErrorTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraStateTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CaptureRequestOptionsTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ConcurrentFuturesDependencyTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerApiTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ExposureStateTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FallbackStrategyTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringActionBuilderTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringActionTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/FocusMeteringResultTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageAnalysisTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageProxyTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageProxyUtilsApiTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageProxyUtilsTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/LiveDataTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/MeteringPointFactoryTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/MeteringPointTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ObserverTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PendingRecordingTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PlaneProxyTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProxyLifecycleProviderTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/QualitySelectorTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/RecorderTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/RecordingTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionFilterTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionInfoTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionSelectorTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ResolutionStrategyTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/TestProxyApiRegistrar.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/VideoCaptureTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/VideoRecordEventListenerTest.java create mode 100644 camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ZoomStateTest.java create mode 100644 camera_android_camerax/lib/camera_android_camerax.dart create mode 100644 camera_android_camerax/lib/src/android_camera_camerax.dart create mode 100644 camera_android_camerax/lib/src/camerax_library.dart create mode 100644 camera_android_camerax/lib/src/camerax_library.g.dart create mode 100644 camera_android_camerax/lib/src/image_reader_rotated_preview.dart create mode 100644 camera_android_camerax/lib/src/rotated_preview_delegate.dart create mode 100644 camera_android_camerax/lib/src/rotated_preview_utils.dart create mode 100644 camera_android_camerax/lib/src/surface_texture_rotated_preview.dart create mode 100644 camera_android_camerax/pubspec.yaml delete mode 100644 config.lock.yaml delete mode 100644 config.yaml delete mode 100644 ed25519_edwards/test/benchmark/ed25519_benchmark.dart delete mode 100644 ed25519_edwards/test/benchmark/rate_benchmark.dart delete mode 100644 ed25519_edwards/test/ed25519_test.dart delete mode 100644 exif/test/data/avif-test.avif delete mode 100644 exif/test/data/avif-test.avif.dump delete mode 100644 exif/test/data/heic-test.heic delete mode 100644 exif/test/data/heic-test.heic.dump delete mode 100644 exif/test/data/png-test.png delete mode 100644 exif/test/data/png-test.png.dump delete mode 100644 exif/test/data/webp-test.webp delete mode 100644 exif/test/data/webp-test.webp.dump delete mode 100644 exif/test/read_file_test.dart delete mode 100644 exif/test/read_samples.dart delete mode 100644 exif/test/regression_test.dart delete mode 100644 exif/test/sample_file.dart delete mode 100644 exif/test/sample_file.g.dart delete mode 100644 exif/test/samples_test.dart delete mode 100644 exif/test/util_test.dart delete mode 100644 exif/test/web_hybrid_main.dart delete mode 100644 exif/test/web_test.dart delete mode 100644 flutter_markdown_plus/test/all.dart delete mode 100644 flutter_markdown_plus/test/assets/images/golden/image_test/custom_builder_asset_logo.png delete mode 100644 flutter_markdown_plus/test/assets/images/golden/image_test/resource_asset_logo.png delete mode 100644 flutter_markdown_plus/test/assets/images/logo.png delete mode 100644 flutter_markdown_plus/test/blockquote_test.dart delete mode 100644 flutter_markdown_plus/test/custom_syntax_test.dart delete mode 100644 flutter_markdown_plus/test/emphasis_test.dart delete mode 100644 flutter_markdown_plus/test/flutter_test_config.dart delete mode 100644 flutter_markdown_plus/test/footnote_test.dart delete mode 100644 flutter_markdown_plus/test/header_test.dart delete mode 100644 flutter_markdown_plus/test/horizontal_rule_test.dart delete mode 100644 flutter_markdown_plus/test/html_test.dart delete mode 100644 flutter_markdown_plus/test/image_test.dart delete mode 100644 flutter_markdown_plus/test/image_test_mocks.dart delete mode 100644 flutter_markdown_plus/test/inline_widget_test.dart delete mode 100644 flutter_markdown_plus/test/line_break_test.dart delete mode 100644 flutter_markdown_plus/test/link_test.dart delete mode 100644 flutter_markdown_plus/test/list_test.dart delete mode 100644 flutter_markdown_plus/test/markdown_body_shrink_wrap_test.dart delete mode 100644 flutter_markdown_plus/test/padding_test.dart delete mode 100644 flutter_markdown_plus/test/scrollable_test.dart delete mode 100644 flutter_markdown_plus/test/selection_area_compatibility_test.dart delete mode 100644 flutter_markdown_plus/test/style_sheet_test.dart delete mode 100644 flutter_markdown_plus/test/table_test.dart delete mode 100644 flutter_markdown_plus/test/text_alignment_test.dart delete mode 100644 flutter_markdown_plus/test/text_scaler_test.dart delete mode 100644 flutter_markdown_plus/test/text_test.dart delete mode 100644 flutter_markdown_plus/test/uri_test.dart delete mode 100644 flutter_markdown_plus/test/utils.dart delete mode 100644 flutter_sharing_intent/test/flutter_sharing_intent_method_channel_test.dart delete mode 100644 flutter_sharing_intent/test/flutter_sharing_intent_test.dart delete mode 100644 hand_signature/test/signature_control_test.dart delete mode 100644 hashlib/test/adler32_test.dart delete mode 100644 hashlib/test/argon2_test.dart delete mode 100644 hashlib/test/bcrypt/bcrypt_2a_test.dart delete mode 100644 hashlib/test/bcrypt/bcrypt_2b_test.dart delete mode 100644 hashlib/test/bcrypt/bcrypt_2y_test.dart delete mode 100644 hashlib/test/bcrypt/bcrypt_basic_test.dart delete mode 100644 hashlib/test/bcrypt/bcrypt_cost_test.dart delete mode 100644 hashlib/test/blake2b_test.dart delete mode 100644 hashlib/test/blake2s_test.dart delete mode 100644 hashlib/test/compare/compare_blake2b512_test.dart delete mode 100644 hashlib/test/compare/compare_hmac_test.dart delete mode 100644 hashlib/test/compare/compare_keccak_test.dart delete mode 100644 hashlib/test/compare/compare_md4_test.dart delete mode 100644 hashlib/test/compare/compare_md5_test.dart delete mode 100644 hashlib/test/compare/compare_sha224_test.dart delete mode 100644 hashlib/test/compare/compare_sha384_test.dart delete mode 100644 hashlib/test/compare/compare_sha512_224_test.dart delete mode 100644 hashlib/test/compare/compare_sha512_256_test.dart delete mode 100644 hashlib/test/compare/compare_sha512_test.dart delete mode 100644 hashlib/test/compare/compare_sm3_test.dart delete mode 100644 hashlib/test/core/block_hash_test.dart delete mode 100644 hashlib/test/core/hash_base_file_test.dart delete mode 100644 hashlib/test/core/hash_digest_test.dart delete mode 100644 hashlib/test/core/hash_sink_test.dart delete mode 100644 hashlib/test/core/kdf_base_test.dart delete mode 100644 hashlib/test/core/mac_base_test.dart delete mode 100644 hashlib/test/crc16_test.dart delete mode 100644 hashlib/test/crc32_test.dart delete mode 100644 hashlib/test/crc64_test.dart delete mode 100644 hashlib/test/fixures/long.txt delete mode 100644 hashlib/test/fixures/xxh32.csv delete mode 100644 hashlib/test/fixures/xxh3_128.csv delete mode 100644 hashlib/test/fixures/xxh3_64.csv delete mode 100644 hashlib/test/fixures/xxh64.csv delete mode 100644 hashlib/test/hmac_test.dart delete mode 100644 hashlib/test/keccak_test.dart delete mode 100644 hashlib/test/md2_test.dart delete mode 100644 hashlib/test/md4_test.dart delete mode 100644 hashlib/test/md5_test.dart delete mode 100644 hashlib/test/pbkdf2_test.dart delete mode 100644 hashlib/test/poly1305_test.dart delete mode 100644 hashlib/test/random/random_test.dart delete mode 100644 hashlib/test/random/uuid_test.dart delete mode 100644 hashlib/test/registry_test.dart delete mode 100644 hashlib/test/ripemd128_test.dart delete mode 100644 hashlib/test/ripemd160_test.dart delete mode 100644 hashlib/test/ripemd256_test.dart delete mode 100644 hashlib/test/ripemd320_test.dart delete mode 100644 hashlib/test/scrypt_test.dart delete mode 100644 hashlib/test/sha1_test.dart delete mode 100644 hashlib/test/sha224_test.dart delete mode 100644 hashlib/test/sha256_test.dart delete mode 100644 hashlib/test/sha384_test.dart delete mode 100644 hashlib/test/sha3_test.dart delete mode 100644 hashlib/test/sha512_224_test.dart delete mode 100644 hashlib/test/sha512_256_test.dart delete mode 100644 hashlib/test/sha512_test.dart delete mode 100644 hashlib/test/shake_test.dart delete mode 100644 hashlib/test/sm3_test.dart delete mode 100644 hashlib/test/totp_test.dart delete mode 100644 hashlib/test/xxh128_test.dart delete mode 100644 hashlib/test/xxh32_test.dart delete mode 100644 hashlib/test/xxh3_test.dart delete mode 100644 hashlib/test/xxh64_test.dart delete mode 100644 hashlib/test/xxh_test.dart delete mode 100644 hashlib_codecs/test/base16_test.dart delete mode 100644 hashlib_codecs/test/base2_test.dart delete mode 100644 hashlib_codecs/test/base32_test.dart delete mode 100644 hashlib_codecs/test/base64_test.dart delete mode 100644 hashlib_codecs/test/base64url_test.dart delete mode 100644 hashlib_codecs/test/base8_test.dart delete mode 100644 hashlib_codecs/test/bigint_test.dart delete mode 100644 hashlib_codecs/test/core/alphabet_test.dart delete mode 100644 hashlib_codecs/test/core/byte_test.dart delete mode 100644 hashlib_codecs/test/core/codec_test.dart delete mode 100644 hashlib_codecs/test/core/decoder_test.dart delete mode 100644 hashlib_codecs/test/core/encoder_test.dart delete mode 100644 hashlib_codecs/test/crypt/crypt_builder_test.dart delete mode 100644 hashlib_codecs/test/crypt/crypt_test.dart delete mode 100644 hashlib_codecs/test/crypt_test.dart delete mode 100644 hashlib_codecs/test/utf8_test.dart delete mode 100644 hashlib_codecs/test/utils.dart create mode 100644 image/LICENSE create mode 100644 image/lib/image.dart create mode 100644 image/lib/src/color/channel.dart create mode 100644 image/lib/src/color/channel_iterator.dart create mode 100644 image/lib/src/color/channel_order.dart create mode 100644 image/lib/src/color/color.dart create mode 100644 image/lib/src/color/color_float16.dart create mode 100644 image/lib/src/color/color_float32.dart create mode 100644 image/lib/src/color/color_float64.dart create mode 100644 image/lib/src/color/color_int16.dart create mode 100644 image/lib/src/color/color_int32.dart create mode 100644 image/lib/src/color/color_int8.dart create mode 100644 image/lib/src/color/color_uint1.dart create mode 100644 image/lib/src/color/color_uint16.dart create mode 100644 image/lib/src/color/color_uint2.dart create mode 100644 image/lib/src/color/color_uint32.dart create mode 100644 image/lib/src/color/color_uint4.dart create mode 100644 image/lib/src/color/color_uint8.dart create mode 100644 image/lib/src/color/const_color_uint8.dart create mode 100644 image/lib/src/color/format.dart create mode 100644 image/lib/src/command/_executor.dart create mode 100644 image/lib/src/command/_executor_html.dart create mode 100644 image/lib/src/command/_executor_io.dart create mode 100644 image/lib/src/command/command.dart create mode 100644 image/lib/src/command/draw/composite_image_cmd.dart create mode 100644 image/lib/src/command/draw/draw_char_cmd.dart create mode 100644 image/lib/src/command/draw/draw_circle_cmd.dart create mode 100644 image/lib/src/command/draw/draw_line_cmd.dart create mode 100644 image/lib/src/command/draw/draw_pixel_cmd.dart create mode 100644 image/lib/src/command/draw/draw_polygon_cmd.dart create mode 100644 image/lib/src/command/draw/draw_rect_cmd.dart create mode 100644 image/lib/src/command/draw/draw_string_cmd.dart create mode 100644 image/lib/src/command/draw/fill_circle_cmd.dart create mode 100644 image/lib/src/command/draw/fill_cmd.dart create mode 100644 image/lib/src/command/draw/fill_flood_cmd.dart create mode 100644 image/lib/src/command/draw/fill_polygon_cmd.dart create mode 100644 image/lib/src/command/draw/fill_rect_cmd.dart create mode 100644 image/lib/src/command/execute_result.dart create mode 100644 image/lib/src/command/executor.dart create mode 100644 image/lib/src/command/filter/adjust_color_cmd.dart create mode 100644 image/lib/src/command/filter/billboard_cmd.dart create mode 100644 image/lib/src/command/filter/bleach_bypass_cmd.dart create mode 100644 image/lib/src/command/filter/bulge_distortion_cmd.dart create mode 100644 image/lib/src/command/filter/bump_to_normal_cmd.dart create mode 100644 image/lib/src/command/filter/chromatic_aberration_cmd.dart create mode 100644 image/lib/src/command/filter/color_halftone_cmd.dart create mode 100644 image/lib/src/command/filter/color_offset_cmd.dart create mode 100644 image/lib/src/command/filter/contrast_cmd.dart create mode 100644 image/lib/src/command/filter/convolution_cmd.dart create mode 100644 image/lib/src/command/filter/copy_image_channels_cmd.dart create mode 100644 image/lib/src/command/filter/dither_image_cmd.dart create mode 100644 image/lib/src/command/filter/dot_screen_cmd.dart create mode 100644 image/lib/src/command/filter/drop_shadow_cmd.dart create mode 100644 image/lib/src/command/filter/edge_glow_cmd.dart create mode 100644 image/lib/src/command/filter/emboss_cmd.dart create mode 100644 image/lib/src/command/filter/filter_cmd.dart create mode 100644 image/lib/src/command/filter/gamma_cmd.dart create mode 100644 image/lib/src/command/filter/gaussian_blur_cmd.dart create mode 100644 image/lib/src/command/filter/grayscale_cmd.dart create mode 100644 image/lib/src/command/filter/hdr_to_ldr_cmd.dart create mode 100644 image/lib/src/command/filter/hexagon_pixelate_cmd.dart create mode 100644 image/lib/src/command/filter/invert_cmd.dart create mode 100644 image/lib/src/command/filter/luminance_threshold_cmd.dart create mode 100644 image/lib/src/command/filter/monochrome_cmd.dart create mode 100644 image/lib/src/command/filter/noise_cmd.dart create mode 100644 image/lib/src/command/filter/normalize_cmd.dart create mode 100644 image/lib/src/command/filter/pixelate_cmd.dart create mode 100644 image/lib/src/command/filter/quantize_cmd.dart create mode 100644 image/lib/src/command/filter/reinhard_tonemap_cmd.dart create mode 100644 image/lib/src/command/filter/remap_colors_cmd.dart create mode 100644 image/lib/src/command/filter/scale_rgba_cmd.dart create mode 100644 image/lib/src/command/filter/separable_convolution_cmd.dart create mode 100644 image/lib/src/command/filter/sepia_cmd.dart create mode 100644 image/lib/src/command/filter/sketch_cmd.dart create mode 100644 image/lib/src/command/filter/smooth_cmd.dart create mode 100644 image/lib/src/command/filter/sobel_cmd.dart create mode 100644 image/lib/src/command/filter/stretch_distortion_cmd.dart create mode 100644 image/lib/src/command/filter/vignette_cmd.dart create mode 100644 image/lib/src/command/formats/bmp_cmd.dart create mode 100644 image/lib/src/command/formats/cur_cmd.dart create mode 100644 image/lib/src/command/formats/decode_image_cmd.dart create mode 100644 image/lib/src/command/formats/decode_image_file_cmd.dart create mode 100644 image/lib/src/command/formats/decode_named_image_cmd.dart create mode 100644 image/lib/src/command/formats/exr_cmd.dart create mode 100644 image/lib/src/command/formats/gif_cmd.dart create mode 100644 image/lib/src/command/formats/ico_cmd.dart create mode 100644 image/lib/src/command/formats/jpg_cmd.dart create mode 100644 image/lib/src/command/formats/png_cmd.dart create mode 100644 image/lib/src/command/formats/psd_cmd.dart create mode 100644 image/lib/src/command/formats/pvr_cmd.dart create mode 100644 image/lib/src/command/formats/tga_cmd.dart create mode 100644 image/lib/src/command/formats/tiff_cmd.dart create mode 100644 image/lib/src/command/formats/webp_cmd.dart create mode 100644 image/lib/src/command/formats/write_to_file_cmd.dart create mode 100644 image/lib/src/command/image/add_frames_cmd.dart create mode 100644 image/lib/src/command/image/convert_cmd.dart create mode 100644 image/lib/src/command/image/copy_image_cmd.dart create mode 100644 image/lib/src/command/image/create_image_cmd.dart create mode 100644 image/lib/src/command/image/image_cmd.dart create mode 100644 image/lib/src/command/transform/bake_orientation_cmd.dart create mode 100644 image/lib/src/command/transform/copy_crop_circle_cmd.dart create mode 100644 image/lib/src/command/transform/copy_crop_cmd.dart create mode 100644 image/lib/src/command/transform/copy_expand_canvas_cmd.dart create mode 100644 image/lib/src/command/transform/copy_flip_cmd.dart create mode 100644 image/lib/src/command/transform/copy_rectify_cmd.dart create mode 100644 image/lib/src/command/transform/copy_resize_cmd.dart create mode 100644 image/lib/src/command/transform/copy_resize_crop_square_cmd.dart create mode 100644 image/lib/src/command/transform/copy_rotate_cmd.dart create mode 100644 image/lib/src/command/transform/flip_cmd.dart create mode 100644 image/lib/src/command/transform/trim_cmd.dart create mode 100644 image/lib/src/draw/_calculate_circumference.dart create mode 100644 image/lib/src/draw/_draw_antialias_circle.dart create mode 100644 image/lib/src/draw/blend_mode.dart create mode 100644 image/lib/src/draw/composite_image.dart create mode 100644 image/lib/src/draw/draw_char.dart create mode 100644 image/lib/src/draw/draw_circle.dart create mode 100644 image/lib/src/draw/draw_line.dart create mode 100644 image/lib/src/draw/draw_pixel.dart create mode 100644 image/lib/src/draw/draw_polygon.dart create mode 100644 image/lib/src/draw/draw_rect.dart create mode 100644 image/lib/src/draw/draw_string.dart create mode 100644 image/lib/src/draw/fill.dart create mode 100644 image/lib/src/draw/fill_circle.dart create mode 100644 image/lib/src/draw/fill_flood.dart create mode 100644 image/lib/src/draw/fill_polygon.dart create mode 100644 image/lib/src/draw/fill_rect.dart create mode 100644 image/lib/src/exif/exif_data.dart create mode 100644 image/lib/src/exif/exif_tag.dart create mode 100644 image/lib/src/exif/ifd_container.dart create mode 100644 image/lib/src/exif/ifd_directory.dart create mode 100644 image/lib/src/exif/ifd_value.dart create mode 100644 image/lib/src/filter/adjust_color.dart create mode 100644 image/lib/src/filter/billboard.dart create mode 100644 image/lib/src/filter/bleach_bypass.dart create mode 100644 image/lib/src/filter/bulge_distortion.dart create mode 100644 image/lib/src/filter/bump_to_normal.dart create mode 100644 image/lib/src/filter/chromatic_aberration.dart create mode 100644 image/lib/src/filter/color_halftone.dart create mode 100644 image/lib/src/filter/color_offset.dart create mode 100644 image/lib/src/filter/contrast.dart create mode 100644 image/lib/src/filter/convolution.dart create mode 100644 image/lib/src/filter/copy_image_channels.dart create mode 100644 image/lib/src/filter/dither_image.dart create mode 100644 image/lib/src/filter/dot_screen.dart create mode 100644 image/lib/src/filter/drop_shadow.dart create mode 100644 image/lib/src/filter/edge_glow.dart create mode 100644 image/lib/src/filter/emboss.dart create mode 100644 image/lib/src/filter/gamma.dart create mode 100644 image/lib/src/filter/gaussian_blur.dart create mode 100644 image/lib/src/filter/grayscale.dart create mode 100644 image/lib/src/filter/hdr_to_ldr.dart create mode 100644 image/lib/src/filter/hexagon_pixelate.dart create mode 100644 image/lib/src/filter/histogram_equalization.dart create mode 100644 image/lib/src/filter/invert.dart create mode 100644 image/lib/src/filter/luminance_threshold.dart create mode 100644 image/lib/src/filter/monochrome.dart create mode 100644 image/lib/src/filter/noise.dart create mode 100644 image/lib/src/filter/normalize.dart create mode 100644 image/lib/src/filter/pixelate.dart create mode 100644 image/lib/src/filter/quantize.dart create mode 100644 image/lib/src/filter/reinhard_tone_map.dart create mode 100644 image/lib/src/filter/remap_colors.dart create mode 100644 image/lib/src/filter/scale_rgba.dart create mode 100644 image/lib/src/filter/separable_convolution.dart create mode 100644 image/lib/src/filter/separable_kernel.dart create mode 100644 image/lib/src/filter/sepia.dart create mode 100644 image/lib/src/filter/sketch.dart create mode 100644 image/lib/src/filter/smooth.dart create mode 100644 image/lib/src/filter/sobel.dart create mode 100644 image/lib/src/filter/solarize.dart create mode 100644 image/lib/src/filter/stretch_distortion.dart create mode 100644 image/lib/src/filter/vignette.dart create mode 100644 image/lib/src/font/arial_14.dart create mode 100644 image/lib/src/font/arial_24.dart create mode 100644 image/lib/src/font/arial_48.dart create mode 100644 image/lib/src/font/bitmap_font.dart create mode 100644 image/lib/src/formats/bmp/bmp_info.dart create mode 100644 image/lib/src/formats/bmp_decoder.dart create mode 100644 image/lib/src/formats/bmp_encoder.dart create mode 100644 image/lib/src/formats/cur_encoder.dart create mode 100644 image/lib/src/formats/decode_info.dart create mode 100644 image/lib/src/formats/decoder.dart create mode 100644 image/lib/src/formats/encoder.dart create mode 100644 image/lib/src/formats/exr/exr_attribute.dart create mode 100644 image/lib/src/formats/exr/exr_b44_compressor.dart create mode 100644 image/lib/src/formats/exr/exr_channel.dart create mode 100644 image/lib/src/formats/exr/exr_compressor.dart create mode 100644 image/lib/src/formats/exr/exr_huffman.dart create mode 100644 image/lib/src/formats/exr/exr_image.dart create mode 100644 image/lib/src/formats/exr/exr_part.dart create mode 100644 image/lib/src/formats/exr/exr_piz_compressor.dart create mode 100644 image/lib/src/formats/exr/exr_pxr24_compressor.dart create mode 100644 image/lib/src/formats/exr/exr_rle_compressor.dart create mode 100644 image/lib/src/formats/exr/exr_wavelet.dart create mode 100644 image/lib/src/formats/exr/exr_zip_compressor.dart create mode 100644 image/lib/src/formats/exr_decoder.dart create mode 100644 image/lib/src/formats/formats.dart create mode 100644 image/lib/src/formats/gif/gif_color_map.dart create mode 100644 image/lib/src/formats/gif/gif_image_desc.dart create mode 100644 image/lib/src/formats/gif/gif_info.dart create mode 100644 image/lib/src/formats/gif_decoder.dart create mode 100644 image/lib/src/formats/gif_encoder.dart create mode 100644 image/lib/src/formats/ico/ico_info.dart create mode 100644 image/lib/src/formats/ico_decoder.dart create mode 100644 image/lib/src/formats/ico_encoder.dart create mode 100644 image/lib/src/formats/image_format.dart create mode 100644 image/lib/src/formats/jpeg/_component_data.dart create mode 100644 image/lib/src/formats/jpeg/_jpeg_huffman.dart create mode 100644 image/lib/src/formats/jpeg/_jpeg_quantize_html.dart create mode 100644 image/lib/src/formats/jpeg/_jpeg_quantize_io.dart create mode 100644 image/lib/src/formats/jpeg/jpeg_adobe.dart create mode 100644 image/lib/src/formats/jpeg/jpeg_component.dart create mode 100644 image/lib/src/formats/jpeg/jpeg_data.dart create mode 100644 image/lib/src/formats/jpeg/jpeg_frame.dart create mode 100644 image/lib/src/formats/jpeg/jpeg_info.dart create mode 100644 image/lib/src/formats/jpeg/jpeg_jfif.dart create mode 100644 image/lib/src/formats/jpeg/jpeg_marker.dart create mode 100644 image/lib/src/formats/jpeg/jpeg_scan.dart create mode 100644 image/lib/src/formats/jpeg/jpeg_util.dart create mode 100644 image/lib/src/formats/jpeg_decoder.dart create mode 100644 image/lib/src/formats/jpeg_encoder.dart create mode 100644 image/lib/src/formats/png/png_frame.dart create mode 100644 image/lib/src/formats/png/png_info.dart create mode 100644 image/lib/src/formats/png_decoder.dart create mode 100644 image/lib/src/formats/png_encoder.dart create mode 100644 image/lib/src/formats/pnm_decoder.dart create mode 100644 image/lib/src/formats/psd/effect/psd_bevel_effect.dart create mode 100644 image/lib/src/formats/psd/effect/psd_drop_shadow_effect.dart create mode 100644 image/lib/src/formats/psd/effect/psd_effect.dart create mode 100644 image/lib/src/formats/psd/effect/psd_inner_glow_effect.dart create mode 100644 image/lib/src/formats/psd/effect/psd_inner_shadow_effect.dart create mode 100644 image/lib/src/formats/psd/effect/psd_outer_glow_effect.dart create mode 100644 image/lib/src/formats/psd/effect/psd_solid_fill_effect.dart create mode 100644 image/lib/src/formats/psd/layer_data/psd_layer_additional_data.dart create mode 100644 image/lib/src/formats/psd/layer_data/psd_layer_section_divider.dart create mode 100644 image/lib/src/formats/psd/psd_blending_ranges.dart create mode 100644 image/lib/src/formats/psd/psd_channel.dart create mode 100644 image/lib/src/formats/psd/psd_image.dart create mode 100644 image/lib/src/formats/psd/psd_image_resource.dart create mode 100644 image/lib/src/formats/psd/psd_layer.dart create mode 100644 image/lib/src/formats/psd/psd_layer_data.dart create mode 100644 image/lib/src/formats/psd/psd_mask.dart create mode 100644 image/lib/src/formats/psd_decoder.dart create mode 100644 image/lib/src/formats/pvr/pvr_bit_utility.dart create mode 100644 image/lib/src/formats/pvr/pvr_color.dart create mode 100644 image/lib/src/formats/pvr/pvr_color_bounding_box.dart create mode 100644 image/lib/src/formats/pvr/pvr_info.dart create mode 100644 image/lib/src/formats/pvr/pvr_packet.dart create mode 100644 image/lib/src/formats/pvr_decoder.dart create mode 100644 image/lib/src/formats/pvr_encoder.dart create mode 100644 image/lib/src/formats/tga/tga_info.dart create mode 100644 image/lib/src/formats/tga_decoder.dart create mode 100644 image/lib/src/formats/tga_encoder.dart create mode 100644 image/lib/src/formats/tiff/tiff_bit_reader.dart create mode 100644 image/lib/src/formats/tiff/tiff_entry.dart create mode 100644 image/lib/src/formats/tiff/tiff_fax_decoder.dart create mode 100644 image/lib/src/formats/tiff/tiff_image.dart create mode 100644 image/lib/src/formats/tiff/tiff_info.dart create mode 100644 image/lib/src/formats/tiff/tiff_lzw_decoder.dart create mode 100644 image/lib/src/formats/tiff_decoder.dart create mode 100644 image/lib/src/formats/tiff_encoder.dart create mode 100644 image/lib/src/formats/webp/vp8.dart create mode 100644 image/lib/src/formats/webp/vp8_bit_reader.dart create mode 100644 image/lib/src/formats/webp/vp8_filter.dart create mode 100644 image/lib/src/formats/webp/vp8_types.dart create mode 100644 image/lib/src/formats/webp/vp8l.dart create mode 100644 image/lib/src/formats/webp/vp8l_bit_reader.dart create mode 100644 image/lib/src/formats/webp/vp8l_color_cache.dart create mode 100644 image/lib/src/formats/webp/vp8l_transform.dart create mode 100644 image/lib/src/formats/webp/webp_alpha.dart create mode 100644 image/lib/src/formats/webp/webp_filters.dart create mode 100644 image/lib/src/formats/webp/webp_frame.dart create mode 100644 image/lib/src/formats/webp/webp_huffman.dart create mode 100644 image/lib/src/formats/webp/webp_info.dart create mode 100644 image/lib/src/formats/webp_decoder.dart create mode 100644 image/lib/src/formats/webp_encoder.dart create mode 100644 image/lib/src/image/icc_profile.dart create mode 100644 image/lib/src/image/image.dart create mode 100644 image/lib/src/image/image_data.dart create mode 100644 image/lib/src/image/image_data_float16.dart create mode 100644 image/lib/src/image/image_data_float32.dart create mode 100644 image/lib/src/image/image_data_float64.dart create mode 100644 image/lib/src/image/image_data_int16.dart create mode 100644 image/lib/src/image/image_data_int32.dart create mode 100644 image/lib/src/image/image_data_int8.dart create mode 100644 image/lib/src/image/image_data_uint1.dart create mode 100644 image/lib/src/image/image_data_uint16.dart create mode 100644 image/lib/src/image/image_data_uint2.dart create mode 100644 image/lib/src/image/image_data_uint32.dart create mode 100644 image/lib/src/image/image_data_uint4.dart create mode 100644 image/lib/src/image/image_data_uint8.dart create mode 100644 image/lib/src/image/interpolation.dart create mode 100644 image/lib/src/image/palette.dart create mode 100644 image/lib/src/image/palette_float16.dart create mode 100644 image/lib/src/image/palette_float32.dart create mode 100644 image/lib/src/image/palette_float64.dart create mode 100644 image/lib/src/image/palette_int16.dart create mode 100644 image/lib/src/image/palette_int32.dart create mode 100644 image/lib/src/image/palette_int8.dart create mode 100644 image/lib/src/image/palette_uint16.dart create mode 100644 image/lib/src/image/palette_uint32.dart create mode 100644 image/lib/src/image/palette_uint8.dart create mode 100644 image/lib/src/image/palette_undefined.dart create mode 100644 image/lib/src/image/pixel.dart create mode 100644 image/lib/src/image/pixel_float16.dart create mode 100644 image/lib/src/image/pixel_float32.dart create mode 100644 image/lib/src/image/pixel_float64.dart create mode 100644 image/lib/src/image/pixel_int16.dart create mode 100644 image/lib/src/image/pixel_int32.dart create mode 100644 image/lib/src/image/pixel_int8.dart create mode 100644 image/lib/src/image/pixel_range_iterator.dart create mode 100644 image/lib/src/image/pixel_uint1.dart create mode 100644 image/lib/src/image/pixel_uint16.dart create mode 100644 image/lib/src/image/pixel_uint2.dart create mode 100644 image/lib/src/image/pixel_uint32.dart create mode 100644 image/lib/src/image/pixel_uint4.dart create mode 100644 image/lib/src/image/pixel_uint8.dart create mode 100644 image/lib/src/image/pixel_undefined.dart create mode 100644 image/lib/src/transform/bake_orientation.dart create mode 100644 image/lib/src/transform/copy_crop.dart create mode 100644 image/lib/src/transform/copy_crop_circle.dart create mode 100644 image/lib/src/transform/copy_expand_canvas.dart create mode 100644 image/lib/src/transform/copy_flip.dart create mode 100644 image/lib/src/transform/copy_rectify.dart create mode 100644 image/lib/src/transform/copy_resize.dart create mode 100644 image/lib/src/transform/copy_resize_crop_square.dart create mode 100644 image/lib/src/transform/copy_rotate.dart create mode 100644 image/lib/src/transform/flip.dart create mode 100644 image/lib/src/transform/resize.dart create mode 100644 image/lib/src/transform/trim.dart create mode 100644 image/lib/src/util/_cast.dart create mode 100644 image/lib/src/util/_circle_test.dart create mode 100644 image/lib/src/util/_file_access.dart create mode 100644 image/lib/src/util/_file_access_html.dart create mode 100644 image/lib/src/util/_file_access_io.dart create mode 100644 image/lib/src/util/_internal.dart create mode 100644 image/lib/src/util/binary_quantizer.dart create mode 100644 image/lib/src/util/bit_utils.dart create mode 100644 image/lib/src/util/clip_line.dart create mode 100644 image/lib/src/util/color_util.dart create mode 100644 image/lib/src/util/file_access.dart create mode 100644 image/lib/src/util/float16.dart create mode 100644 image/lib/src/util/image_exception.dart create mode 100644 image/lib/src/util/input_buffer.dart create mode 100644 image/lib/src/util/math_util.dart create mode 100644 image/lib/src/util/min_max.dart create mode 100644 image/lib/src/util/neural_quantizer.dart create mode 100644 image/lib/src/util/octree_quantizer.dart create mode 100644 image/lib/src/util/output_buffer.dart create mode 100644 image/lib/src/util/point.dart create mode 100644 image/lib/src/util/quantizer.dart create mode 100644 image/lib/src/util/random.dart create mode 100644 image/lib/src/util/rational.dart create mode 100644 image/pubspec.yaml delete mode 100644 introduction_screen/test/src/helper_test.dart delete mode 100644 introduction_screen/test/src/introduction_screen_test.dart delete mode 100644 introduction_screen/test/src/model/page_decoration_test.dart delete mode 100644 introduction_screen/test/src/model/position_test.dart delete mode 100644 introduction_screen/test/widget/intro_ui_test.dart delete mode 100644 libsignal_protocol_dart/test/devices/device_consistency_test.dart delete mode 100644 libsignal_protocol_dart/test/ecc/curve25519_test.dart delete mode 100644 libsignal_protocol_dart/test/fingerprint/numeric_fingerprint_generator_test.dart delete mode 100644 libsignal_protocol_dart/test/groups/group_cipher_test.dart delete mode 100644 libsignal_protocol_dart/test/kdf/hkdf_test.dart delete mode 100644 libsignal_protocol_dart/test/provisioning_cipher_test.dart delete mode 100644 libsignal_protocol_dart/test/ratchet/chain_key_test.dart delete mode 100644 libsignal_protocol_dart/test/ratchet/ratcheting_session_test.dart delete mode 100644 libsignal_protocol_dart/test/ratchet/root_key_test.dart delete mode 100644 libsignal_protocol_dart/test/session_builder_test.dart delete mode 100644 libsignal_protocol_dart/test/session_cipher_test.dart delete mode 100644 libsignal_protocol_dart/test/state/impl/in_memory_identity_key_store_test.dart delete mode 100644 libsignal_protocol_dart/test/state/impl/in_memory_pre_key_store_test.dart delete mode 100644 libsignal_protocol_dart/test/state/impl/in_memory_session_store_test.dart delete mode 100644 libsignal_protocol_dart/test/state/impl/in_memory_signal_protocol_store_test.dart delete mode 100644 libsignal_protocol_dart/test/state/impl/in_memory_signed_pre_key_store_test.dart delete mode 100644 libsignal_protocol_dart/test/test_in_memory_identity_key_store.dart delete mode 100644 libsignal_protocol_dart/test/test_in_memory_signal_protocol_store.dart delete mode 100644 libsignal_protocol_dart/test/util/byte_util_test.dart delete mode 100644 mutex/test/mutex_multiple_read_test.dart delete mode 100644 mutex/test/mutex_readwrite_test.dart delete mode 100644 mutex/test/mutex_test.dart delete mode 100644 optional/test/optional_test.dart delete mode 100644 optional/test/src/async.dart delete mode 100644 optional/test/src/equality.dart delete mode 100644 optional/test/src/extension.dart delete mode 100644 optional/test/src/iterable.dart delete mode 100644 optional/test/src/methods.dart delete mode 100644 photo_view/test/controller_test.dart delete mode 100644 photo_view/test/scale_state_controller_test.dart delete mode 100644 pointycastle/test/adapters/stream_cipher_as_block_cipher_test.dart delete mode 100644 pointycastle/test/asn1/asn1_all_test-disabled.dart delete mode 100644 pointycastle/test/asn1/asn1_dump_test.dart delete mode 100644 pointycastle/test/asn1/asn1_object_test.dart delete mode 100644 pointycastle/test/asn1/asn1_parser_test.dart delete mode 100644 pointycastle/test/asn1/asn1_utils_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_bit_string_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_bmp_string_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_boolean_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_enumerated_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_generalized_time_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_ia5_string_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_integer_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_null_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_object_identifier_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_octet_string_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_printable_string_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_sequence_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_set_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_teletext_string_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_utc_time_test.dart delete mode 100644 pointycastle/test/asn1/primitives/asn1_utf8_string_test.dart delete mode 100644 pointycastle/test/asymmetric/ec_elgamal_test.dart delete mode 100644 pointycastle/test/asymmetric/oaep_test.dart delete mode 100644 pointycastle/test/asymmetric/pkcs1_test.dart delete mode 100644 pointycastle/test/asymmetric/rsa_test.dart delete mode 100644 pointycastle/test/block/aes_fast_test.dart delete mode 100644 pointycastle/test/block/aes_test.dart delete mode 100644 pointycastle/test/block/blowfish_test.dart delete mode 100644 pointycastle/test/block/camellia_test.dart delete mode 100644 pointycastle/test/block/desede_engine_test.dart delete mode 100644 pointycastle/test/block/rc2_engine_test.dart delete mode 100644 pointycastle/test/block/twofish_test.dart delete mode 100644 pointycastle/test/digests/blake2b_test.dart delete mode 100644 pointycastle/test/digests/cshake_test.dart delete mode 100644 pointycastle/test/digests/keccak_test.dart delete mode 100644 pointycastle/test/digests/md2_test.dart delete mode 100644 pointycastle/test/digests/md4_test.dart delete mode 100644 pointycastle/test/digests/md5_test.dart delete mode 100644 pointycastle/test/digests/ripemd128_test.dart delete mode 100644 pointycastle/test/digests/ripemd160_test.dart delete mode 100644 pointycastle/test/digests/ripemd256_test.dart delete mode 100644 pointycastle/test/digests/ripemd320_test.dart delete mode 100644 pointycastle/test/digests/sha1_test.dart delete mode 100644 pointycastle/test/digests/sha224_test.dart delete mode 100644 pointycastle/test/digests/sha256_test.dart delete mode 100644 pointycastle/test/digests/sha384_test.dart delete mode 100644 pointycastle/test/digests/sha3_test.dart delete mode 100644 pointycastle/test/digests/sha512_test.dart delete mode 100644 pointycastle/test/digests/sha512t_test.dart delete mode 100644 pointycastle/test/digests/shake_test.dart delete mode 100644 pointycastle/test/digests/sm3_test.dart delete mode 100644 pointycastle/test/digests/tiger_test.dart delete mode 100644 pointycastle/test/digests/whirlpool_test.dart delete mode 100644 pointycastle/test/ecc/curves_test.dart delete mode 100644 pointycastle/test/ecc/ecdh_test.dart delete mode 100644 pointycastle/test/impl_test.dart delete mode 100644 pointycastle/test/key_derivators/argon2_nonvm_test.dart delete mode 100644 pointycastle/test/key_derivators/argon2_vm_test.dart delete mode 100644 pointycastle/test/key_derivators/concatkdf_nonvm_test.dart delete mode 100644 pointycastle/test/key_derivators/concatkdf_test.dart delete mode 100644 pointycastle/test/key_derivators/hkdf_test.dart delete mode 100644 pointycastle/test/key_derivators/pbkdf2_test.dart delete mode 100644 pointycastle/test/key_derivators/pkcs12_parameter_generator_test.dart delete mode 100644 pointycastle/test/key_derivators/scrypt_nonvm_test.dart delete mode 100644 pointycastle/test/key_derivators/scrypt_vm_test.dart delete mode 100644 pointycastle/test/key_generators/ec_key_generator_test.dart delete mode 100644 pointycastle/test/key_generators/rsa_key_generator_test.dart delete mode 100644 pointycastle/test/macs/cbc_block_cipher_mac_test.dart delete mode 100644 pointycastle/test/macs/cmac_test.dart delete mode 100644 pointycastle/test/macs/hmac_test.dart delete mode 100644 pointycastle/test/macs/poly1305_test.dart delete mode 100644 pointycastle/test/macs/poly1305_web_test.dart delete mode 100644 pointycastle/test/modes/cbc_test.dart delete mode 100644 pointycastle/test/modes/ccm_test.dart delete mode 100644 pointycastle/test/modes/cfb_test.dart delete mode 100644 pointycastle/test/modes/ecb_test.dart delete mode 100644 pointycastle/test/modes/gcm_test.dart delete mode 100644 pointycastle/test/modes/gctr_test.dart delete mode 100644 pointycastle/test/modes/ige_test.dart delete mode 100644 pointycastle/test/modes/ofb_test.dart delete mode 100644 pointycastle/test/modes/sic_test.dart delete mode 100644 pointycastle/test/paddings/iso7816d4_test.dart delete mode 100644 pointycastle/test/paddings/padded_block_cipher_test.dart delete mode 100644 pointycastle/test/paddings/pkcs7_test.dart delete mode 100644 pointycastle/test/platform/platform_native_test.dart delete mode 100644 pointycastle/test/platform/platform_web_test.dart delete mode 100644 pointycastle/test/random/auto_seed_block_ctr_random_test.dart delete mode 100644 pointycastle/test/random/block_ctr_random_test.dart delete mode 100644 pointycastle/test/random/fixed_rng_test.dart delete mode 100644 pointycastle/test/random/fortuna_random_test.dart delete mode 100644 pointycastle/test/signers/ecdsa_signer_test.dart delete mode 100644 pointycastle/test/signers/ecdsa_vec.dart delete mode 100644 pointycastle/test/signers/ecdsa_vector_vm_test.dart delete mode 100644 pointycastle/test/signers/pss_signer_test.dart delete mode 100644 pointycastle/test/signers/rsa_signer_test.dart delete mode 100644 pointycastle/test/src/ct_nonvm_test.dart delete mode 100644 pointycastle/test/src/ct_test.dart delete mode 100644 pointycastle/test/src/ufixnum_test.dart delete mode 100644 pointycastle/test/src/utils_test.dart delete mode 100644 pointycastle/test/srp/srp_test.dart delete mode 100644 pointycastle/test/stream/chacha20_test.dart delete mode 100644 pointycastle/test/stream/chacha20poly1305_test.dart delete mode 100644 pointycastle/test/stream/eax_test.dart delete mode 100644 pointycastle/test/stream/rc4_engine_test.dart delete mode 100644 pointycastle/test/stream/salsa20_test.dart delete mode 100644 pointycastle/test/test/runners/asymmetric_block_cipher.dart delete mode 100644 pointycastle/test/test/runners/block_cipher.dart delete mode 100644 pointycastle/test/test/runners/digest.dart delete mode 100644 pointycastle/test/test/runners/key_derivators.dart delete mode 100644 pointycastle/test/test/runners/key_generators.dart delete mode 100644 pointycastle/test/test/runners/mac.dart delete mode 100644 pointycastle/test/test/runners/padding.dart delete mode 100644 pointycastle/test/test/runners/registry.dart delete mode 100644 pointycastle/test/test/runners/signer.dart delete mode 100644 pointycastle/test/test/runners/stream_cipher.dart delete mode 100644 pointycastle/test/test/src/fixed_secure_random.dart delete mode 100644 pointycastle/test/test/src/helpers.dart delete mode 100644 pointycastle/test/test/src/null_asymmetric_block_cipher.dart delete mode 100644 pointycastle/test/test/src/null_block_cipher.dart delete mode 100644 pointycastle/test/test/src/null_digest.dart delete mode 100644 pointycastle/test/test/src/null_secure_random.dart delete mode 100644 pointycastle/test/test/src/null_stream_cipher.dart delete mode 100644 pointycastle/test/test_resources/kdf-56c/KDA.req.json delete mode 100644 pointycastle/test/test_resources/kdf-56c/KDA.rsp.json create mode 100644 pro_video_editor/LICENSE create mode 100644 pro_video_editor/android/.gitignore create mode 100644 pro_video_editor/android/build.gradle create mode 100644 pro_video_editor/android/gradle.properties create mode 100644 pro_video_editor/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 pro_video_editor/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 pro_video_editor/android/gradlew create mode 100644 pro_video_editor/android/gradlew.bat create mode 100644 pro_video_editor/android/settings.gradle create mode 100644 pro_video_editor/android/src/main/AndroidManifest.xml create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/ProVideoEditorPlugin.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/core/constants/LoggingConstants.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/audio/ExtractAudio.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/audio/MergeAudio.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/audio/PcmSpeedProcessor.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/audio/WavFileWriter.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/audio/models/AudioExtractConfig.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/audio/models/AudioExtractJobHandle.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/audio/models/AudioExtractTask.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/audio/models/AudioMergeConfig.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/metadata/Metadata.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/metadata/models/MetadataConfig.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/RenderVideo.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyAnimation.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyBitrate.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyBlur.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyChromaKey.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyClipTransition.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyColorMatrix.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyComposition.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyCrop.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyFlip.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyFrameRate.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyImageLayer.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyOpacity.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyPlaybackSpeed.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyRotation.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyScale.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/AudioPreRenderer.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/AudioSequenceBuilder.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/BitrateCapPolicy.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ChromaKeyEffect.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ChromaKeyMath.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ClipTransitionGeometry.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ClipTransitionRenderer.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/CompositionBuilder.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ConfigurableInAppMp4Muxer.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/EncoderFailureClassifier.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/GifDecoder.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/LayeredCompositionBuilder.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/MediaInfoExtractor.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ResilientVideoEncoderFactory.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/VideoCompositionTransformation.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/VideoEncoderConfig.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/VideoGlobalTrimCalculator.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/VideoReverser.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/VideoSequenceBuilder.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/VideoTimelineDurationCalculator.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/VideoTranscoder.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/VolumeAudioProcessor.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/VolumeControlAudioMixer.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/models/CodecResourceExhaustedException.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/models/RenderConfig.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/models/RenderJobHandle.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/models/RenderTask.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/models/VideoEncoderConfigurationException.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/utils/EffectsProcessor.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/utils/RotatedVideoDimensions.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/utils/VideoMimeUtils.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/split/SplitExportDiagnostics.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/split/SplitVideo.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/stopmotion/StopMotionGenerator.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/stopmotion/models/StopMotionConfig.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/thumbnail/SequentialFrameDecoder.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/thumbnail/ThumbnailGenerator.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/thumbnail/models/ThumbnailConfig.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/waveform/WaveformGenerator.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/waveform/models/WaveformConfig.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/waveform/models/WaveformTask.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/shared/concurrency/ExportGate.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/shared/logging/PluginLog.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/shared/media/EncodedImage.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/shared/media/ImageOrientation.kt create mode 100644 pro_video_editor/android/src/main/kotlin/ch/waio/pro_video_editor/src/shared/media/PcmRangeDecoder.kt create mode 100644 pro_video_editor/android/src/test/kotlin/ch/waio/pro_video_editor/ProVideoEditorPluginTest.kt create mode 100644 pro_video_editor/android/src/test/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/AudioPreRendererTest.kt create mode 100644 pro_video_editor/android/src/test/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/BitrateCapPolicyTest.kt create mode 100644 pro_video_editor/android/src/test/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ChromaKeyMathTest.kt create mode 100644 pro_video_editor/android/src/test/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ClipTransitionGeometryTest.kt create mode 100644 pro_video_editor/android/src/test/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/EncoderFailureClassifierTest.kt create mode 100644 pro_video_editor/android/src/test/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/OverlayDecodeSizeTest.kt create mode 100644 pro_video_editor/android/src/test/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/SlideOffsetTest.kt create mode 100644 pro_video_editor/android/src/test/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/VideoEncoderConfigTest.kt create mode 100644 pro_video_editor/android/src/test/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/VideoGlobalTrimCalculatorTest.kt create mode 100644 pro_video_editor/android/src/test/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/VideoTimelineDurationCalculatorTest.kt create mode 100644 pro_video_editor/android/src/test/kotlin/ch/waio/pro_video_editor/src/features/split/SplitExportDiagnosticsTest.kt create mode 100644 pro_video_editor/android/src/test/kotlin/ch/waio/pro_video_editor/src/shared/concurrency/ExportGateTest.kt create mode 100644 pro_video_editor/android/src/test/kotlin/ch/waio/pro_video_editor/src/shared/media/EncodedImageTest.kt create mode 100644 pro_video_editor/android/src/test/kotlin/ch/waio/pro_video_editor/src/shared/media/ImageOrientationTest.kt create mode 100644 pro_video_editor/darwin/FlutterFramework/Package.swift create mode 100644 pro_video_editor/darwin/FlutterFramework/Sources/FlutterFramework/FlutterFramework.swift create mode 100644 pro_video_editor/darwin/pro_video_editor.podspec create mode 100644 pro_video_editor/darwin/pro_video_editor/Package.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Resources/PrivacyInfo.xcprivacy create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/ProVideoEditorPlugin.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/concurrency/ExportGate.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/concurrency/ExportSessionDriver.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/concurrency/ExportSessionGuard.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/concurrency/ExportWatchdog.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/core/constants/LoggingConstants.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/core/models/EncodedImage.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/core/models/RenderTask.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/extensions/StreamHandler.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/audio/ExtractAudio.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/audio/MergeAudio.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/audio/models/AudioExtractConfig.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/audio/models/AudioExtractTask.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/audio/models/AudioMergeConfig.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/metadata/Metadata.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/metadata/models/MetadataConfig.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/RenderVideo.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/ApplyAnimation.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/ApplyBitrate.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/ApplyBlur.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/ApplyChromaKey.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/ApplyColorMatrix.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/ApplyComposition.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/ApplyCrop.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/ApplyFlip.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/ApplyImageLayer.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/ApplyPlaybackSpeed.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/ApplyRotation.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/ApplyScale.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/AudioPreRenderer.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/AudioReverser.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/AudioSequenceBuilder.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/BitrateCapPolicy.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/BitrateCappedExporter.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/ClipTransitionGeometry.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/ClipTransitionRenderer.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/CompositionBuilder.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/DecodeOrientedImage.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/LayeredCompositionBuilder.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/MediaInfoExtractor.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/TrackEndTrimmer.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/VideoSequenceBuilder.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/helpers/VideoTranscoder.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/models/RenderConfig.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/models/VideoClip.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/models/VideoCompositorConfig.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/utils/VideoCompositor.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/render/utils/VideoMimeUtils.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/split/SplitVideo.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/stopmotion/StopMotionGenerator.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/stopmotion/models/StopMotionConfig.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/thumbnail/ThumbnailGenerator.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/thumbnail/models/ThumbnailConfig.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/waveform/WaveformGenerator.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/waveform/models/WaveformConfig.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/features/waveform/models/WaveformTask.swift create mode 100644 pro_video_editor/darwin/pro_video_editor/Sources/pro_video_editor/src/shared/logging/PluginLog.swift create mode 100644 pro_video_editor/lib/core/models/audio/audio_extract_configs_model.dart create mode 100644 pro_video_editor/lib/core/models/audio/audio_format_model.dart create mode 100644 pro_video_editor/lib/core/models/audio/audio_merge_configs_model.dart create mode 100644 pro_video_editor/lib/core/models/audio/audio_merge_result_model.dart create mode 100644 pro_video_editor/lib/core/models/audio/audio_track_model.dart create mode 100644 pro_video_editor/lib/core/models/audio/waveform_chunk_model.dart create mode 100644 pro_video_editor/lib/core/models/audio/waveform_configs_model.dart create mode 100644 pro_video_editor/lib/core/models/audio/waveform_data_model.dart create mode 100644 pro_video_editor/lib/core/models/exceptions/audio_exceptions.dart create mode 100644 pro_video_editor/lib/core/models/exceptions/render_exceptions.dart create mode 100644 pro_video_editor/lib/core/models/image/editor_layer_image_model.dart create mode 100644 pro_video_editor/lib/core/models/image/image_layer_model.dart create mode 100644 pro_video_editor/lib/core/models/image/layer_animation_model.dart create mode 100644 pro_video_editor/lib/core/models/platform/native_log_entry.dart create mode 100644 pro_video_editor/lib/core/models/platform/native_log_level.dart create mode 100644 pro_video_editor/lib/core/models/thumbnail/key_frames_configs_model.dart create mode 100644 pro_video_editor/lib/core/models/thumbnail/single_thumbnail_configs_model.dart create mode 100644 pro_video_editor/lib/core/models/thumbnail/thumbnail_base_abstract.dart create mode 100644 pro_video_editor/lib/core/models/thumbnail/thumbnail_box_fit_model.dart create mode 100644 pro_video_editor/lib/core/models/thumbnail/thumbnail_configs_model.dart create mode 100644 pro_video_editor/lib/core/models/thumbnail/thumbnail_format_model.dart create mode 100644 pro_video_editor/lib/core/models/video/chroma_key_model.dart create mode 100644 pro_video_editor/lib/core/models/video/clip_transition_model.dart create mode 100644 pro_video_editor/lib/core/models/video/color_filter_model.dart create mode 100644 pro_video_editor/lib/core/models/video/editor_video_model.dart create mode 100644 pro_video_editor/lib/core/models/video/export_transform_model.dart create mode 100644 pro_video_editor/lib/core/models/video/progress_model.dart create mode 100644 pro_video_editor/lib/core/models/video/segment_transform_model.dart create mode 100644 pro_video_editor/lib/core/models/video/split_video_model.dart create mode 100644 pro_video_editor/lib/core/models/video/stop_motion_render_data_model.dart create mode 100644 pro_video_editor/lib/core/models/video/video_composition_model.dart create mode 100644 pro_video_editor/lib/core/models/video/video_layer_model.dart create mode 100644 pro_video_editor/lib/core/models/video/video_metadata_model.dart create mode 100644 pro_video_editor/lib/core/models/video/video_quality_config.dart create mode 100644 pro_video_editor/lib/core/models/video/video_quality_preset.dart create mode 100644 pro_video_editor/lib/core/models/video/video_render_data_model.dart create mode 100644 pro_video_editor/lib/core/models/video/video_segment_model.dart create mode 100644 pro_video_editor/lib/core/platform/io/io_helper.dart create mode 100644 pro_video_editor/lib/core/platform/io/io_web.dart create mode 100644 pro_video_editor/lib/core/platform/native_method_channel.dart create mode 100644 pro_video_editor/lib/core/platform/path/path_provider_helper.dart create mode 100644 pro_video_editor/lib/core/platform/path/path_provider_native.dart create mode 100644 pro_video_editor/lib/core/platform/path/path_provider_web.dart create mode 100644 pro_video_editor/lib/core/platform/platform_interface.dart create mode 100644 pro_video_editor/lib/core/platform/web_method_channel.dart create mode 100644 pro_video_editor/lib/core/services/web/web_manager.dart create mode 100644 pro_video_editor/lib/core/services/web/web_meta_data_reader.dart create mode 100644 pro_video_editor/lib/core/services/web/web_thumbnail_generator.dart create mode 100644 pro_video_editor/lib/core/utils/chroma_key_detector.dart create mode 100644 pro_video_editor/lib/core/utils/web_blob_utils.dart create mode 100644 pro_video_editor/lib/core/utils/web_canvas_utils.dart create mode 100644 pro_video_editor/lib/features/audio/models/waveform_style.dart create mode 100644 pro_video_editor/lib/features/audio/widgets/audio_waveform.dart create mode 100644 pro_video_editor/lib/features/audio/widgets/streaming_waveform.dart create mode 100644 pro_video_editor/lib/features/audio/widgets/waveform_painter.dart create mode 100644 pro_video_editor/lib/pro_video_editor.dart create mode 100644 pro_video_editor/lib/shared/models/time_range_mixin.dart create mode 100644 pro_video_editor/lib/shared/utils/converters.dart create mode 100644 pro_video_editor/lib/shared/utils/file_constructor_utils.dart create mode 100644 pro_video_editor/lib/shared/utils/parser/double_parser.dart create mode 100644 pro_video_editor/lib/shared/utils/parser/int_parser.dart create mode 100644 pro_video_editor/lib/shared/utils/parser/size_parser.dart create mode 100644 pro_video_editor/pubspec.yaml delete mode 100644 pubspec.yaml delete mode 100644 qr/test/qr_alphanumeric_test.dart delete mode 100644 qr/test/qr_bit_buffer_test.dart delete mode 100644 qr/test/qr_byte_test.dart delete mode 100644 qr/test/qr_code_test.dart delete mode 100644 qr/test/qr_code_test_data.dart delete mode 100644 qr/test/qr_code_test_data_with_mask.dart delete mode 100644 qr/test/qr_numeric_test.dart delete mode 100644 qr_flutter/test/.golden/qr_image_data_module_styled_golden.png delete mode 100644 qr_flutter/test/.golden/qr_image_eye_data_module_styled_golden.png delete mode 100644 qr_flutter/test/.golden/qr_image_eye_styled_golden.png delete mode 100644 qr_flutter/test/.golden/qr_image_foreground_colored_golden.png delete mode 100644 qr_flutter/test/.golden/qr_image_golden.png delete mode 100644 qr_flutter/test/.golden/qr_image_logo_golden.png delete mode 100644 qr_flutter/test/.golden/qr_painter_golden.png delete mode 100644 qr_flutter/test/.images/logo_yakka.png delete mode 100644 qr_flutter/test/all_test.dart delete mode 100644 qr_flutter/test/image_test.dart delete mode 100644 qr_flutter/test/painter_test.dart delete mode 100644 restart_app/test/restart_app_test.dart delete mode 100644 screen_protector/test/screen_protector_test.dart delete mode 100644 sprintf/test/sprintf_test.dart delete mode 100644 sprintf/test/testing_data.dart delete mode 100644 update.py create mode 100644 video_player/LICENSE create mode 100644 video_player/lib/src/closed_caption_file.dart create mode 100644 video_player/lib/src/sub_rip.dart create mode 100644 video_player/lib/src/web_vtt.dart create mode 100644 video_player/lib/video_player.dart create mode 100644 video_player/pubspec.yaml create mode 100644 video_player_android/LICENSE create mode 100644 video_player_android/android/build.gradle.kts create mode 100644 video_player_android/android/lint-baseline.xml create mode 100644 video_player_android/android/settings.gradle.kts create mode 100644 video_player_android/android/src/main/AndroidManifest.xml create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/ExoPlayerEventListener.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/HttpVideoAsset.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/LocalVideoAsset.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/QueuingEventSink.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/RtspVideoAsset.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoAsset.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerCallbacks.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerEventCallbacks.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerOptions.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformVideoView.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformVideoViewFactory.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewExoPlayerEventListener.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/platformview/PlatformViewVideoPlayer.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureExoPlayerEventListener.java create mode 100644 video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/texture/TextureVideoPlayer.java create mode 100644 video_player_android/android/src/main/kotlin/io/flutter/plugins/videoplayer/Messages.kt create mode 100644 video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/ExoPlayerEventListenerTest.java create mode 100644 video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/FakeVideoAsset.java create mode 100644 video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/PlatformVideoViewFactoryTest.java create mode 100644 video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/PlatformVideoViewTest.java create mode 100644 video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/PlatformViewExoPlayerEventListenerTest.java create mode 100644 video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/PlatformViewVideoPlayerTest.java create mode 100644 video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/TextureExoPlayerEventListenerTest.java create mode 100644 video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/TextureVideoPlayerTest.java create mode 100644 video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoAssetTest.java create mode 100644 video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerEventCallbacksTest.java create mode 100644 video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerPluginTest.java create mode 100644 video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java create mode 100644 video_player_android/lib/src/android_video_player.dart create mode 100644 video_player_android/lib/src/messages.g.dart create mode 100644 video_player_android/lib/src/platform_view_player.dart create mode 100644 video_player_android/lib/video_player_android.dart create mode 100644 video_player_android/pubspec.yaml create mode 100644 video_player_avfoundation/LICENSE create mode 100644 video_player_avfoundation/darwin/Assets/.gitkeep create mode 100644 video_player_avfoundation/darwin/RunnerTests/TestClasses.swift create mode 100644 video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.swift create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation.podspec create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Package.swift create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/NativeVideoViewFactory.swift create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/Resources/PrivacyInfo.xcprivacy create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/VideoPlayerPlugin.swift create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/VideoPlayerPluginMessages.g.swift create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_ios/FVPNativeVideoView.m create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_ios/include/.gitkeep create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_ios/include/FVPEmpty.h create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_macos/FVPCoreVideoDisplayLink.m create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_macos/FVPNativeVideoView.m create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_macos/include/.gitkeep create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_macos/include/FVPEmpty.h create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/AVAssetTrackUtils.m create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/FVPAVFactory.m create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/FVPCADisplayLink.m create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/FVPEventBridge.m create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/FVPFrameUpdater.m create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/FVPTextureBasedVideoPlayer.m create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/FVPVideoPlayer.m create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/FVPViewProvider.m create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/VideoPlayerInstanceMessages.g.m create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/AVAssetTrackUtils.h create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/FVPAVFactory.h create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/FVPAssetProvider.h create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/FVPDisplayLink.h create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/FVPEventBridge.h create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/FVPFrameUpdater.h create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/FVPNativeVideoView.h create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/FVPTextureBasedVideoPlayer.h create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/FVPTextureBasedVideoPlayer_Test.h create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/FVPVideoEventListener.h create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/FVPVideoPlayer.h create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/FVPVideoPlayer_Internal.h create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/FVPViewProvider.h create mode 100644 video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_objc/include/video_player_avfoundation_objc/VideoPlayerInstanceMessages.g.h create mode 100644 video_player_avfoundation/lib/src/avfoundation_video_player.dart create mode 100644 video_player_avfoundation/lib/src/video_player_instance_messages.g.dart create mode 100644 video_player_avfoundation/lib/src/video_player_plugin_messages.g.dart create mode 100644 video_player_avfoundation/lib/video_player_avfoundation.dart create mode 100644 video_player_avfoundation/pubspec.yaml delete mode 100644 x25519/test/benchmark/curve25519_benchmark.dart delete mode 100644 x25519/test/benchmark/rate_benchmark.dart delete mode 100644 x25519/test/curve25519_test.dart delete mode 100644 x25519/test/vectors_test.dart diff --git a/adaptive_number/test/adaptive_number_test.dart b/adaptive_number/test/adaptive_number_test.dart deleted file mode 100644 index 52931d7..0000000 --- a/adaptive_number/test/adaptive_number_test.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'package:adaptive_number/adaptive_number.dart'; -import 'package:test/test.dart'; - -// Since both implementations of Number only forward most method call, -// tests are kept minimal and should mainly concentrate on verifying that no -// wrong operator and/or method is being called because of a mix up -void main() { - group('utility functions', () { - test('retrieve value as int', () { - expect(Number.zero.intValue, 0); - expect(Number.one.intValue, 1); - expect(Number.two.intValue, 2); - expect(Number(42).intValue, 42); - }); - }); - - group('other overwrites', () { - test('hash code', () { - expect(Number.zero.hashCode, 0.hashCode); - expect(Number.one.hashCode, 1.hashCode); - expect(Number.two.hashCode, 2.hashCode); - expect(Number(42).hashCode, 42); - }); - - test('to string', () { - expect(Number.one.toString(), '1'); - expect((-Number.one).toString(), '-1'); - }); - - test('toRadixString', () { - expect(Number(42).toRadixString(2), '101010'); - }); - - test('abs', () { - expect(Number(-42).abs(), Number(42)); - }); - - test('compare to', () { - expect(Number.one.compareTo(Number.two), -1); - expect(Number.two.compareTo(Number.one), 1); - expect(Number.one.compareTo(Number.one), 0); - }); - }); - - group('operator tests', () { - test('add', () { - expect(Number.one + Number.two, Number(3)); - }); - - test('subtract', () { - expect(Number.two - Number.one, Number.one); - }); - - test('invert', () { - expect((-Number.one).intValue, -1); - expect((-Number.zero).intValue, 0); - }); - - test('multiply', () { - expect(Number.two * Number(3), Number(6)); - }); - - test('& bitwise and', () { - expect(Number(1234) & Number(9876), Number(1168)); - expect(Number(-1234) & Number(9876), Number(8708)); - }); - - test('>> bitwise shift right', () { - expect(Number(23423) >> 8, Number(91)); - expect(Number(-87653) >> 12, Number(-22)); - }); - - test('<< bitwise shift left', () { - expect(Number(23423) << 8, Number(5996288)); - expect(Number(-87653) << 12, Number(-359026688)); - }); - - test('^ bitwise xor', () { - expect(Number(1234) ^ Number(9876), Number(8774)); - }); - - test('| bitwise or', () { - expect(Number(1234) | Number(9876), Number(9942)); - expect(Number(-1234) | Number(9876), Number(-66)); - }); - - test('< less than', () { - expect(Number.one < Number.two, isTrue); - expect(Number.two < Number.one, isFalse); - }); - - test('< less than or equal', () { - expect(Number.two <= Number.one, isFalse); - expect(Number.one <= Number.two, isTrue); - expect(Number.two <= Number.two, isTrue); - }); - - test('< greater than', () { - expect(Number.two > Number.one, isTrue); - expect(Number.one > Number.two, isFalse); - }); - - test('< greater than or equal', () { - expect(Number.two >= Number.one, isTrue); - expect(Number.one >= Number.two, isFalse); - expect(Number.two >= Number.two, isTrue); - }); - - test('modulo', () { - expect(Number.two % Number.one, Number.zero); - expect(Number(3) % Number.one, Number.zero); - expect(Number.one % Number.two, Number.one); - }); - - test('truncating division', () { - expect(Number(1000) ~/ Number(-3), Number(-333)); - }); - - test('equals', () { - expect(Number(0) == Number.zero, isTrue); - expect(Number(1) == Number.one, isTrue); - expect(Number(2) == Number.two, isTrue); - - expect(Number(1) == Number.zero, isFalse); - expect(Number.one == Number.zero, isFalse); - }); - }); -} diff --git a/audio_waveforms/LICENSE b/audio_waveforms/LICENSE new file mode 100644 index 0000000..328279e --- /dev/null +++ b/audio_waveforms/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Simform Solutions + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/audio_waveforms/android/.gitignore b/audio_waveforms/android/.gitignore new file mode 100644 index 0000000..c6cbe56 --- /dev/null +++ b/audio_waveforms/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/audio_waveforms/android/build.gradle b/audio_waveforms/android/build.gradle new file mode 100644 index 0000000..a8b70a7 --- /dev/null +++ b/audio_waveforms/android/build.gradle @@ -0,0 +1,55 @@ +group 'com.simform.audio_waveforms' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.8.20' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + if (project.android.hasProperty("namespace")) { + namespace "com.simform.audio_waveforms" + } + + compileSdk 34 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + minSdkVersion 21 + } +} + +dependencies { + implementation("androidx.multidex:multidex:2.0.1") + implementation "com.google.android.exoplayer:exoplayer:2.17.1" +} diff --git a/audio_waveforms/android/gradle.properties b/audio_waveforms/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/audio_waveforms/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/audio_waveforms/android/gradle/wrapper/gradle-wrapper.properties b/audio_waveforms/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..8bc9958 --- /dev/null +++ b/audio_waveforms/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip diff --git a/audio_waveforms/android/settings.gradle b/audio_waveforms/android/settings.gradle new file mode 100644 index 0000000..f28d6ea --- /dev/null +++ b/audio_waveforms/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'audio_waveforms' diff --git a/audio_waveforms/android/src/main/AndroidManifest.xml b/audio_waveforms/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..10b1be9 --- /dev/null +++ b/audio_waveforms/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/AudioPlayer.kt b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/AudioPlayer.kt new file mode 100644 index 0000000..2948bcd --- /dev/null +++ b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/AudioPlayer.kt @@ -0,0 +1,239 @@ +package com.simform.audio_waveforms + +import android.content.Context +import android.net.Uri +import android.os.Handler +import android.os.Looper +import com.google.android.exoplayer2.ExoPlayer +import com.google.android.exoplayer2.MediaItem +import com.google.android.exoplayer2.PlaybackException +import com.google.android.exoplayer2.Player +import io.flutter.plugin.common.MethodChannel + +class AudioPlayer( + context: Context, + channel: MethodChannel, + playerKey: String +) { + private var handler: Handler = Handler(Looper.getMainLooper()) + private var runnable: Runnable? = null + private var methodChannel = channel + private var appContext = context + private var player: ExoPlayer? = null + private var playerListener: Player.Listener? = null + private var isPlayerPrepared: Boolean = false + private var finishMode = FinishMode.Stop + private var key = playerKey + private var updateFrequency: Long = 200 + + fun preparePlayer( + result: MethodChannel.Result, + path: String?, + volume: Float?, + frequency: Long?, + ) { + if (path != null) { + frequency?.let { + updateFrequency = it + } + val uri = Uri.parse(path) + val mediaItem = MediaItem.fromUri(uri) + stop() + player?.clearMediaItems() + player = ExoPlayer.Builder(appContext).build() + player?.setMediaItem(mediaItem) + player?.prepare() + playerListener = object : Player.Listener { + + override fun onPlayerError(error: PlaybackException) { + super.onPlayerError(error) + result.error(Constants.LOG_TAG, error.message, "Unable to load media source.") + } + + override fun onPlayerStateChanged(isReady: Boolean, state: Int) { + if (!isPlayerPrepared) { + if (state == Player.STATE_READY) { + player?.volume = volume ?: 1F + isPlayerPrepared = true + result.success(true) + } + } + if (state == Player.STATE_ENDED) { + val args: MutableMap = HashMap() + when (finishMode) { + FinishMode.Stop -> { + player?.stop() + player?.release() + player = null + stopListening() + args[Constants.finishType] = 2 + } + + FinishMode.Loop -> { + player?.seekTo(0) + player?.play() + args[Constants.finishType] = 0 + } + + FinishMode.Pause -> { + player?.seekTo(0) + player?.playWhenReady = false + stopListening() + args[Constants.finishType] = 1 + } + } + args[Constants.playerKey] = key + methodChannel.invokeMethod( + Constants.onDidFinishPlayingAudio, + args + ) + } + } + } + player?.addListener(playerListener!!) + } else { + result.error(Constants.LOG_TAG, "path to audio file or unique key can't be null", "") + } + } + + fun seekToPosition(result: MethodChannel.Result, progress: Long?) { + if (progress != null) { + player?.seekTo(progress) + sendCurrentDuration() + result.success(true) + } else { + result.success(false) + } + } + + fun start(result: MethodChannel.Result) { + try { + player?.playWhenReady = true + player?.play() + result.success(true) + startListening(result) + } catch (e: Exception) { + result.error(Constants.LOG_TAG, "Can not start the player", e.toString()) + } + } + + fun getDuration(result: MethodChannel.Result, durationType: DurationType) { + try { + if (durationType == DurationType.Current) { + val duration = player?.currentPosition + result.success(duration) + } else { + val duration = player?.duration + result.success(duration) + } + } catch (e: Exception) { + result.error(Constants.LOG_TAG, "Can not get duration", e.toString()) + } + } + + fun stop() { + stopListening() + if (playerListener != null) { + player?.removeListener(playerListener!!) + } + isPlayerPrepared = false + player?.stop() + } + + + fun pause() { + stopListening() + player?.pause() + } + + fun release(result: MethodChannel.Result) { + try { + player?.release() + result.success(true) + } catch (e: Exception) { + result.error(Constants.LOG_TAG, "Failed to release player resource", e.toString()) + } + + } + + fun setVolume(volume: Float?, result: MethodChannel.Result) { + try { + if (volume != null) { + player?.volume = volume + result.success(true) + } else { + result.success(false) + } + } catch (e: Exception) { + result.success(false) + } + } + + fun setRate(rate: Float?, result: MethodChannel.Result) { + try { + if (rate != null) { + player?.setPlaybackSpeed(rate) + result.success(true) + } else { + result.success(false) + } + } catch (e: Exception) { + result.success(false) + } + } + + fun setFinishMode(result: MethodChannel.Result, releaseModeType: Int?) { + try { + when (releaseModeType) { + 0 -> { + this.finishMode = FinishMode.Loop + } + + 1 -> { + this.finishMode = FinishMode.Pause + } + + 2 -> { + this.finishMode = FinishMode.Stop + } + + null -> { + throw Exception("Release mode is null") + } + + else -> { + throw Exception("Invalid Finish mode") + } + } + result.success(null) + } catch (e: Exception) { + result.error(Constants.LOG_TAG, "Can not set the release mode", e.toString()) + } + } + + private fun startListening(result: MethodChannel.Result) { + runnable = object : Runnable { + override fun run() { + sendCurrentDuration() + handler.postDelayed(this, updateFrequency) + } + } + handler.post(runnable!!) + + } + + private fun stopListening() { + runnable?.let { handler.removeCallbacks(it) } + sendCurrentDuration() + } + + private fun sendCurrentDuration() { + val currentPosition = player?.currentPosition ?: 0 + val args: MutableMap = HashMap() + args[Constants.current] = currentPosition + args[Constants.playerKey] = key + methodChannel.invokeMethod(Constants.onCurrentDuration, args) + } + + +} diff --git a/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/AudioRecorder.kt b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/AudioRecorder.kt new file mode 100644 index 0000000..24fd73b --- /dev/null +++ b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/AudioRecorder.kt @@ -0,0 +1,263 @@ +package com.simform.audio_waveforms + +import android.Manifest +import android.annotation.SuppressLint +import android.app.Activity +import android.content.pm.PackageManager +import android.media.AudioFormat +import android.media.AudioRecord +import android.media.MediaMetadataRetriever +import android.media.MediaMetadataRetriever.METADATA_KEY_DURATION +import android.media.MediaRecorder +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.core.app.ActivityCompat +import com.simform.audio_waveforms.Constants.LOG_TAG +import com.simform.audio_waveforms.encoders.* +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.Result +import io.flutter.plugin.common.PluginRegistry +import java.io.File +import kotlin.math.sqrt + +class AudioRecorder : PluginRegistry.RequestPermissionsResultListener { + private var permissions = arrayOf(Manifest.permission.RECORD_AUDIO) + private var audioRecord: AudioRecord? = null + private var channelConfig: Int = AudioFormat.CHANNEL_IN_MONO + private var audioFormat: Int = AudioFormat.ENCODING_PCM_16BIT + private var bufferSize: Int? = null + private var recorderState: RecorderState = RecorderState.Disposed + private var filePath: String? = null + private var recordingThread: Thread? = null + private var recorderSettings: RecorderSettings? = null + private var encoder: Encoder? = null + lateinit var channel: MethodChannel + private var commonEncoder = CommonEncoder() + private var wavEncoder: WavEncoder? = null + private var successCallback: RequestPermissionsSuccessCallback? = null + private var totalSamples = 0L + private val channelCount: Int + get() = when (channelConfig) { + AudioFormat.CHANNEL_IN_MONO -> 1 + AudioFormat.CHANNEL_IN_STEREO -> 2 + else -> 1 + } + + override fun onRequestPermissionsResult( + requestCode: Int, permissions: Array, grantResults: IntArray + ): Boolean { + return if (requestCode == Constants.RECORD_AUDIO_REQUEST_CODE) { + successCallback?.onSuccess(grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) + grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED + } else { + false + } + } + + private fun isPermissionGranted(activity: Activity?): Boolean { + val result = ActivityCompat.checkSelfPermission(activity!!, permissions[0]) + return result == PackageManager.PERMISSION_GRANTED + } + + fun checkPermission( + result: Result, activity: Activity?, successCallback: RequestPermissionsSuccessCallback + ) { + this.successCallback = successCallback + if (!isPermissionGranted(activity)) { + activity?.let { + ActivityCompat.requestPermissions( + it, permissions, Constants.RECORD_AUDIO_REQUEST_CODE + ) + } + } else { + result.success(true) + } + } + + @RequiresApi(Build.VERSION_CODES.M) + @SuppressLint("MissingPermission") + fun initRecorder( + recorderSettings: RecorderSettings, channel: MethodChannel, result: Result + ) { + filePath = recorderSettings.path + if (filePath == null) return + this.channel = channel + bufferSize = + AudioRecord.getMinBufferSize(recorderSettings.sampleRate, channelConfig, audioFormat) + + if (bufferSize == AudioRecord.ERROR || bufferSize == AudioRecord.ERROR_BAD_VALUE) { + result.error( + LOG_TAG, + "Invalid buffer size: $bufferSize", + null + ) + } + try { + audioRecord = AudioRecord( + MediaRecorder.AudioSource.MIC, + recorderSettings.sampleRate, + channelConfig, + audioFormat, + bufferSize!! + ) + } catch (e: Exception) { + result.error( + LOG_TAG, + "Error initializing AudioRecord: ${e.message}", + null + ) + return + } + this.recorderSettings = recorderSettings + encoder = recorderSettings.encoder + recorderState = RecorderState.Initialised + result.success(true) + } + + fun start(result: Result) { + if (recorderSettings == null || bufferSize == null) { + result.error( + LOG_TAG, + "recorder settings is null or bufferSize is null", + "recorderSettings: $recorderSettings, bufferSize: $bufferSize" + ) + return + } + audioRecord?.startRecording() + recorderState = RecorderState.Recording + if (encoder?.encodeForWav == true) { + wavEncoder = WavEncoder( + wavFile = File(recorderSettings!!.path!!), + sampleRate = recorderSettings!!.sampleRate + ) + wavEncoder?.start(result) + } else { + commonEncoder.initCodec(recorderSettings = recorderSettings!!, result = result) { + recordingThread?.join() + } + } + val buffer = ByteArray(bufferSize!!) + recordingThread = Thread { + while (recorderState == RecorderState.Recording || recorderState == RecorderState.Paused) { + if (recorderState == RecorderState.Recording) { + val read = audioRecord?.read(buffer, 0, buffer.size) ?: 0 + + if (read > 0) { + val audioData = buffer.copyOf(read) + if (encoder?.encodeForWav == true) { + wavEncoder?.writePcmData(audioData) + } else { + commonEncoder.queueInputBuffer(audioData) + } + val rms = calculateRms(audioData, read) + totalSamples += read / channelCount + val durationSec = + totalSamples.toDouble() / (recorderSettings?.sampleRate + ?: Constants.DEFAULT_SAMPLE_RATE) + val milliSeconds = (durationSec * 1000).toLong() + sendBytesToFlutter(audioData, rms, milliSeconds) + } + } + } + } + recordingThread?.start() + result.success(true) + } + + fun stop(result: Result) { + try { + audioRecord?.stop() + totalSamples = 0L + recorderState = RecorderState.Stopped + if (encoder?.encodeForWav == true) { + wavEncoder?.stop(result) + recordingThread?.join() + sendRecordingResult(result) + } else { + commonEncoder.setOnEncodingCompleted { + sendRecordingResult(result) + } + commonEncoder.signalToStop() + } + + } catch (e: Exception) { + result.error(LOG_TAG, e.message, "An error occurred while stopping the recorder") + return + } + release() + } + + private fun sendRecordingResult(result: Result) { + val duration = getDuration(recorderSettings?.path) + val hashMap = HashMap() + hashMap[Constants.resultFilePath] = recorderSettings?.path + hashMap[Constants.resultDuration] = duration + result.success(hashMap) + } + + private fun sendBytesToFlutter(chunk: ByteArray, rms: Double, milliSeconds: Long) { + val args: MutableMap = HashMap() + args[Constants.normalisedRms] = rms + args[Constants.bytes] = chunk + args[Constants.recordedDuration] = milliSeconds + Handler(Looper.getMainLooper()).post { + channel.invokeMethod(Constants.onAudioChunk, args) + } + } + + private fun calculateRms(chunk: ByteArray, size: Int): Double { + var sum = 0.0 + var count = 0 + + val adjustedSize = if (size % 2 == 0) size else size - 1 + for (i in 0 until adjustedSize step 2) { + val low = chunk[i].toInt() and 0xff + val high = chunk[i + 1].toInt() + val sample = (high shl 8) or low + + sum += sample * sample.toDouble() + count++ + } + + val normalisedRms = sqrt(sum / count) / 32767.0 + return normalisedRms + } + + fun pause(result: Result) { + recorderState = RecorderState.Paused + result.success(false) + } + + fun resume(result: Result) { + recorderState = RecorderState.Recording + result.success(true) + } + + fun release() { + try { + audioRecord?.release() + } catch (e: Exception) { + Log.e(LOG_TAG, "Error releasing AudioRecord: ${e.message}") + } + + audioRecord = null + recorderState = RecorderState.Disposed + } + + private fun getDuration(path: String?): Int { + val mediaMetadataRetriever = MediaMetadataRetriever() + try { + mediaMetadataRetriever.setDataSource(path) + val duration = mediaMetadataRetriever.extractMetadata(METADATA_KEY_DURATION) + return duration?.toInt() ?: -1 + } catch (e: Exception) { + Log.e(LOG_TAG, "Error getting duration: ${e.message}") + } finally { + mediaMetadataRetriever.release() + } + return -1 + } +} diff --git a/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/AudioWaveformsPlugin.kt b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/AudioWaveformsPlugin.kt new file mode 100644 index 0000000..4252ff1 --- /dev/null +++ b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/AudioWaveformsPlugin.kt @@ -0,0 +1,338 @@ +package com.simform.audio_waveforms + +import android.app.Activity +import android.content.Context +import android.media.MediaRecorder +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result +import java.io.File +import java.io.IOException +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + + +/** AudioWaveformsPlugin */ +class AudioWaveformsPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { + private lateinit var channel: MethodChannel + private var recorder: MediaRecorder? = null + private var activity: Activity? = null + private lateinit var audioRecorder: AudioRecorder + private var recorderSettings = RecorderSettings(path = null) + private lateinit var applicationContext: Context + private var audioPlayers = mutableMapOf() + private var extractors = mutableMapOf() + private var pluginBinding: ActivityPluginBinding? = null + private var record: AudioRecorder = AudioRecorder() + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, Constants.methodChannelName) + channel.setMethodCallHandler(this) + audioRecorder = AudioRecorder() + applicationContext = flutterPluginBinding.applicationContext + } + + @RequiresApi(Build.VERSION_CODES.N) + override fun onMethodCall(call: MethodCall, result: Result) { + when (call.method) { + Constants.initRecorder -> { + val arguments = call.arguments + if (arguments is Map<*, *>) { + recorderSettings = RecorderSettings.fromJson(arguments) + checkPathAndInitialiseRecorder(result, recorderSettings) + } else { + result.error( + Constants.LOG_TAG, "Failed to initialise Recorder", "Invalid Arguments" + ) + } + } + + Constants.startRecording -> record.start(result) + + Constants.stopRecording -> { + record.stop(result) + recorder = null + } + + Constants.pauseRecording -> record.pause(result) + + Constants.resumeRecording -> record.resume(result) + Constants.checkPermission -> audioRecorder.checkPermission( + result, activity, result::success + ) + + Constants.preparePlayer -> { + val audioPath = call.argument(Constants.path) as String? + val volume = call.argument(Constants.volume) as Double? + val key = call.argument(Constants.playerKey) as String? + val frequency = call.argument(Constants.updateFrequency) as Int? + if (key != null) { + initPlayer(key) + audioPlayers[key]?.preparePlayer( + result, + audioPath, + volume?.toFloat(), + frequency?.toLong(), + ) + } else { + result.error(Constants.LOG_TAG, "Player key can't be null", "") + } + + } + + Constants.startPlayer -> { + val key = call.argument(Constants.playerKey) as String? + if (key != null) { + audioPlayers[key]?.start(result) + } else { + result.error(Constants.LOG_TAG, "Player key can't be null", "") + } + } + + Constants.stopPlayer -> { + val key = call.argument(Constants.playerKey) as String? + if (key != null) { + try { + audioPlayers[key]?.stop() + result.success(true) + } catch (e: Exception) { + result.error(Constants.LOG_TAG, "Failed to stop player", e.message) + } + } else { + result.error(Constants.LOG_TAG, "Player key can't be null", "") + } + } + + Constants.pausePlayer -> { + val key = call.argument(Constants.playerKey) as String? + if (key != null) { + try { + audioPlayers[key]?.pause() + result.success(true) + } catch (e: Exception) { + result.error(Constants.LOG_TAG, "Failed to pause player", e.message) + } + } else { + result.error(Constants.LOG_TAG, "Player key can't be null", "") + } + } + + Constants.releasePlayer -> { + val key = call.argument(Constants.playerKey) as String? + audioPlayers[key]?.release(result) + } + + Constants.seekTo -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val progress = call.argument(Constants.progress) as Int? + val key = call.argument(Constants.playerKey) as String? + if (key != null) { + audioPlayers[key]?.seekToPosition(result, progress?.toLong()) + } else { + result.error(Constants.LOG_TAG, "Player key can't be null", "") + } + } else { + Log.e( + Constants.LOG_TAG, + "Minimum android O is required for seekTo function to works" + ) + } + } + + Constants.setVolume -> { + val volume = call.argument(Constants.volume) as Double? + val key = call.argument(Constants.playerKey) as String? + if (key != null) { + audioPlayers[key]?.setVolume(volume?.toFloat(), result) + } else { + result.error(Constants.LOG_TAG, "Player key can't be null", "") + } + } + + Constants.setRate -> { + val rate = call.argument(Constants.rate) as Double? + val key = call.argument(Constants.playerKey) as String? + if (key != null) { + audioPlayers[key]?.setRate(rate?.toFloat(), result) + } else { + result.error(Constants.LOG_TAG, "Player key can't be null", "") + } + } + + Constants.getDuration -> { + val type = + if ((call.argument(Constants.durationType) as Int?) == 0) DurationType.Current else DurationType.Max + val key = call.argument(Constants.playerKey) as String? + if (key != null) { + audioPlayers[key]?.getDuration(result, type) + } else { + result.error(Constants.LOG_TAG, "Player key can't be null", "") + } + } + + Constants.extractWaveformData -> { + val key = call.argument(Constants.playerKey) as String? + val path = call.argument(Constants.path) as String? + val noOfSample = call.argument(Constants.noOfSamples) as Int? + if (key != null) { + createOrUpdateExtractor( + playerKey = key, + result = result, + path = path, + noOfSamples = noOfSample ?: 100, + ) + } else { + result.error(Constants.LOG_TAG, "Waveform key can't be null", "") + } + } + + Constants.STOP_EXTRACTION -> { + val key = call.argument(Constants.playerKey) as String? + key?.let { + extractors[it]?.stop() + result.success(true) + } ?: result.error(Constants.LOG_TAG, "Waveform key can't be null", "") + } + + Constants.stopAllPlayers -> { + stopAllPlayer(result) + } + + Constants.finishMode -> { + val releaseType = call.argument(Constants.finishType) + val key = call.argument(Constants.playerKey) + key?.let { + audioPlayers[it]?.setFinishMode(result, releaseType) + } + } + + Constants.pauseAllPlayers -> { + pauseAllPlayer(result) + } + + else -> result.notImplemented() + } + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun checkPathAndInitialiseRecorder( + result: Result, recorderSettings: RecorderSettings + ) { + if (recorderSettings.path == null) { + val outputDir = activity?.cacheDir + val outputFile: File? + val dateTimeInstance = SimpleDateFormat(Constants.fileNameFormat, Locale.US) + val currentDate = dateTimeInstance.format(Date()) + try { + outputFile = File.createTempFile(currentDate, ".m4a", outputDir) + recorderSettings.path = outputFile.path + } catch (e: IOException) { + result.error(Constants.LOG_TAG, "Failed to create file", e.message) + return + } + } + record.initRecorder(recorderSettings, channel, result) + } + + private fun initPlayer(playerKey: String) { + if (!audioPlayers.containsKey(playerKey)) { + val newPlayer = AudioPlayer( + context = applicationContext, + channel = channel, + playerKey = playerKey, + ) + audioPlayers[playerKey] = newPlayer + } + return + } + + private fun createOrUpdateExtractor( + playerKey: String, + noOfSamples: Int, + path: String?, + result: Result, + ) { + if (path == null) { + result.error(Constants.LOG_TAG, "Path can't be null", "") + return + } + extractors[playerKey]?.stop() + extractors[playerKey] = WaveformExtractor( + context = applicationContext, + methodChannel = channel, + expectedPoints = noOfSamples, + key = playerKey, + path = path, + result = result, + extractorCallBack = object : ExtractorCallBack { + override fun onProgress(value: Float) { + if (value == 1.0F) { + result.success(extractors[playerKey]?.sampleData) + } + } + + }) + extractors[playerKey]?.startDecode() + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + activity = binding.activity + pluginBinding = binding + pluginBinding!!.addRequestPermissionsResultListener(this.audioRecorder) + + } + + override fun onDetachedFromActivityForConfigChanges() { + activity = null + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + activity = binding.activity + } + + override fun onDetachedFromActivity() { + recorder?.release() + recorder = null + audioPlayers.clear() + extractors.clear() + activity = null + if (pluginBinding != null) { + pluginBinding!!.removeRequestPermissionsResultListener(this.audioRecorder) + } + } + + private fun stopAllPlayer(result: Result) { + try { + for ((key, _) in audioPlayers) { + audioPlayers[key]?.stop() + audioPlayers[key] = null + } + result.success(true) + } catch (e: Exception) { + result.error(Constants.LOG_TAG, "Failed to stop players", e.message) + } + } + + private fun pauseAllPlayer(result: Result) { + try { + for ((key, _) in audioPlayers) { + audioPlayers[key]?.pause() + } + result.success(true) + } catch (e: Exception) { + result.error(Constants.LOG_TAG, "Failed to pause players", e.message) + } + } +} diff --git a/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/RecorderSettings.kt b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/RecorderSettings.kt new file mode 100644 index 0000000..b1d58dd --- /dev/null +++ b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/RecorderSettings.kt @@ -0,0 +1,61 @@ +package com.simform.audio_waveforms + +/** + * Configuration class for audio recording settings + * + * This data class encapsulates all the settings required for configuring + * the audio recorder, including: + * - Output file path + * - Audio encoder/format + * - Sample rate + * - Bit rate + * + * It provides sensible defaults for common recording scenarios and includes + * a factory method to create instances from JSON/Map data received from Flutter. + */ +data class RecorderSettings( + /** + * Path where the recorded audio file will be saved + * Can be null if not specified by the caller + */ + var path: String?, + + /** + * Audio encoder to use for the recording + * Defaults to AAC Low Complexity (AAC_LC) + */ + val encoder: Encoder = Encoder.AAC_LC, + + /** + * Sample rate in Hz (samples per second) + * Defaults to 44100Hz (CD quality) + */ + val sampleRate: Int = 44100, + + /** + * Bit rate in bits per second + * Defaults to 128kbps (good quality for most audio) + */ + val bitRate: Int = 128000 +) { + companion object { + /** + * Creates a RecorderSettings instance from a Map/JSON object + * + * This factory method is used to convert parameters received from Flutter + * into a RecorderSettings object. It handles default values when parameters + * are missing. + * + * @param json The map containing recorder settings from Flutter + * @return A configured RecorderSettings instance + */ + fun fromJson(json: Map<*, *>): RecorderSettings { + return RecorderSettings( + path = json[Constants.path] as String?, + encoder = Encoder.fromString(json[Constants.encoder] as String?), + sampleRate = (json[Constants.sampleRate] as Int?) ?: 44100, + bitRate = json[Constants.bitRate] as Int + ) + } + } +} \ No newline at end of file diff --git a/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/Utils.kt b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/Utils.kt new file mode 100644 index 0000000..59bbfcf --- /dev/null +++ b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/Utils.kt @@ -0,0 +1,329 @@ +package com.simform.audio_waveforms + +import android.media.MediaCodecInfo +import android.media.MediaFormat +import android.media.MediaMuxer +import android.media.MediaRecorder +import android.os.Build +import android.util.Log +import com.simform.audio_waveforms.Constants.LOG_TAG + +/** + * Specifies which duration value to retrieve from an audio player + */ +enum class DurationType { + /** Current playback position */ + Current, + /** Total duration of the audio file */ + Max +} + +/** + * Global constants used throughout the audio_waveforms plugin + * + * This object contains method channel names, parameter keys, default values, + * and other constants needed for the plugin's operation. + */ +object Constants { + + // TODO: Update all const to uppercase + const val initRecorder = "initRecorder" + const val startRecording = "startRecording" + const val stopRecording = "stopRecording" + const val pauseRecording = "pauseRecording" + const val resumeRecording = "resumeRecording" + const val checkPermission = "checkPermission" + const val path = "path" + const val LOG_TAG = "AudioWaveforms" + const val methodChannelName = "simform_audio_waveforms_plugin/methods" + const val encoder = "encoder" + const val sampleRate = "sampleRate" + const val bitRate = "bitRate" + const val fileNameFormat = "dd-MM-yy-hh-mm-ss" + + const val preparePlayer = "preparePlayer" + const val startPlayer = "startPlayer" + const val stopPlayer = "stopPlayer" + const val pausePlayer = "pausePlayer" + const val releasePlayer = "releasePlayer" + const val seekTo = "seekTo" + const val progress = "progress" + const val setVolume = "setVolume" + const val finishMode = "finishMode" + const val finishType = "finishType" + const val volume = "volume" + const val setRate = "setRate" + const val rate = "rate" + const val getDuration = "getDuration" + const val durationType = "durationType" + const val playerKey = "playerKey" + const val current = "current" + const val onCurrentDuration = "onCurrentDuration" + const val stopAllPlayers = "stopAllPlayers" + const val onDidFinishPlayingAudio = "onDidFinishPlayingAudio" + const val extractWaveformData = "extractWaveformData" + const val noOfSamples = "noOfSamples" + const val onCurrentExtractedWaveformData = "onCurrentExtractedWaveformData" + const val waveformData = "waveformData" + const val updateFrequency = "updateFrequency" + const val STOP_EXTRACTION = "stopExtraction" + + const val resultFilePath = "resultFilePath" + const val resultDuration = "resultDuration" + const val pauseAllPlayers = "pauseAllPlayers" + const val normalisedRms = "normalisedRms" + const val bytes = "bytes" + const val recordedDuration = "recordedDuration" + const val onAudioChunk = "onAudioChunk" + + // TODO: make user can set this in future + const val CHANNEL: Int = 1 + const val BIT_PER_SAMPLE: Int = 16 + + const val RECORD_AUDIO_REQUEST_CODE = 1001 + + /// Indicates 128 bits in a single channel for 8-bit PCM + const val EIGHT_BITS = 128f + + /// Indicates 32767 bits in a single channel for 16-bit PCM + const val SIXTEEN_BITS = 32767f + + /// Indicates 2147483648f bits in a single channel for 32-bit PCM + const val THIRTY_TWO_BITS = 2.14748365E9f + const val ENCODER_THREAD = "EncoderThread" + const val AAC_FILE_EXTENSION = "aac" + const val DEFAULT_SAMPLE_RATE = 44100 +} + +/** + * Defines behavior when audio playback reaches the end + * + * Controls what happens when an audio file finishes playing. + * + * @property value The integer value sent to the platform channel + */ +enum class FinishMode(val value: Int) { + /** Restart playback from the beginning */ + Loop(0), + + /** Pause at the end of the file */ + Pause(1), + + /** Stop playback and release resources */ + Stop(2) +} + + +/** + * Callback interface for permission request results + * + * This functional interface is used to notify when a permission request + * has been processed, providing the result of the permission check. + */ +fun interface RequestPermissionsSuccessCallback { + /** + * Called when permission request completes + * + * @param results true if all required permissions were granted, false otherwise + */ + fun onSuccess(results: Boolean?) +} + +/** + * Defines the possible states of the audio recorder + * + * These states allow tracking the recorder's lifecycle and determine + * which operations are valid at any given time. + */ +enum class RecorderState { + /** Recorder is initialized and ready to start recording */ + Initialised, + + /** Recorder is actively recording audio */ + Recording, + + /** Recorder has been temporarily paused but can resume */ + Paused, + + /** Recording has been stopped (can't resume, but can start a new recording) */ + Stopped, + + /** Recorder has been disposed and cannot be used anymore */ + Disposed +} + +/** + * Defines the supported audio encoders for recording + * + * This enum provides configuration details for various audio encoders, + * including: + * - MIME type + * - Buffer size + * - Output format + * - AAC profile (when applicable) + * - MediaMuxer usage requirements + * + * Each encoder has different characteristics in terms of audio quality, + * file size, and compatibility across platforms. + */ +enum class Encoder { + /** Uncompressed PCM audio in WAV container */ + WAV, + /** AAC Low Complexity profile - good quality/size balance */ + AAC_LC, + /** AAC High Efficiency profile - better compression than AAC_LC */ + AAC_HE, + /** AAC Enhanced Low Delay - optimized for real-time communication */ + AAC_ELD, + /** Adaptive Multi-Rate Narrowband - speech optimized, low bitrate */ + AMR_NB, + /** Adaptive Multi-Rate Wideband - better speech quality than AMR_NB */ + AMR_WB, + /** Opus codec - versatile audio codec with good compression */ + OPUS; + + /** + * Gets the MIME type string for this encoder + * + * Used when configuring MediaCodec encoders + */ + val mimeType: String + get() = when (this) { + WAV -> MediaFormat.MIMETYPE_AUDIO_RAW + AAC_LC, AAC_HE, AAC_ELD -> MediaFormat.MIMETYPE_AUDIO_AAC + AMR_NB -> MediaFormat.MIMETYPE_AUDIO_AMR_NB + AMR_WB -> MediaFormat.MIMETYPE_AUDIO_AMR_WB + OPUS -> MediaFormat.MIMETYPE_AUDIO_OPUS + } + + /** + * Gets the recommended buffer size for this encoder + * + * Different encoders work optimally with different buffer sizes. + * This property returns the recommended size for each encoder type. + */ + val bufferSize: Int + get() = when (this) { + AAC_LC -> 2048 + AAC_HE -> 2048 + AAC_ELD -> 2048 + AMR_NB -> 1024 + AMR_WB -> 2048 + WAV -> 8192 + OPUS -> 2048 + } + + /** + * Gets the appropriate output format for this encoder + * + * Maps each encoder to its corresponding container format in MediaRecorder. + * For OPUS, uses OGG container format on Android Q and above. + * + * @throws IllegalArgumentException if WAV is selected (uses raw PCM) + * @throws Exception if OPUS is selected on Android below Q + */ + val toOutputFormat: Int + get() = when (this) { + WAV -> throw IllegalArgumentException("Illegal format selection.") + AAC_LC, AAC_HE, AAC_ELD -> MediaRecorder.OutputFormat.MPEG_4 + AMR_NB -> MediaRecorder.OutputFormat.AMR_NB + AMR_WB -> MediaRecorder.OutputFormat.AMR_WB + OPUS -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG + } else { + throw Exception("Minimum android Q is required for $this encoder.") + } + } + } + + /** + * Gets the appropriate MediaMuxer output format for this encoder + * + * This property is specifically for MediaMuxer, which uses different + * constants than MediaRecorder.OutputFormat. + */ + val toMuxerOutputFormat: Int + get() = when (this) { + WAV, AMR_NB, AMR_WB -> throw IllegalArgumentException("MediaMuxer not used for this encoder.") + AAC_LC, AAC_HE, AAC_ELD -> MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 + OPUS -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG + } else { + throw Exception("Minimum android Q is required for $this encoder.") + } + } + } + + + /** + * Indicates whether this encoder requires MediaMuxer + * + * Some encoders (AAC, OPUS) require a container format that needs MediaMuxer + * for proper file creation, while others (WAV, AMR) don't. + */ + val useMediaMuxer: Boolean + get() = when (this) { + WAV, AMR_NB, AMR_WB -> false + AAC_LC, AAC_HE, AAC_ELD, OPUS -> true + } + + /** + * Gets the AAC profile for this encoder if it's an AAC variant + * + * Returns the appropriate MediaCodecInfo profile constant for AAC encoders, + * or null for non-AAC encoders. + */ + val aacProfile: Int? + get() = when (this) { + AAC_LC -> MediaCodecInfo.CodecProfileLevel.AACObjectLC + AAC_HE -> MediaCodecInfo.CodecProfileLevel.AACObjectHE + AAC_ELD -> MediaCodecInfo.CodecProfileLevel.AACObjectELD + else -> null + } + + companion object { + /** + * Creates an Encoder from a string value + * + * Safely converts a string to its corresponding Encoder enum value. + * Returns AAC_LC as a fallback if the string is null or invalid. + * + * @param value The string representation of the encoder + * @return The matching Encoder, or AAC_LC if invalid + */ + fun fromString(value: String?): Encoder { + return try { + if (value == null) { + Log.e(LOG_TAG, "Encoder type is null. Defaulting to AAC_LC.") + return AAC_LC + } + valueOf(value) + } catch (_: IllegalArgumentException) { + Log.e(LOG_TAG, "Invalid encoder type: $value. Defaulting to AAC_LC.") + AAC_LC + } + } + } + + /** + * Indicates if this encoder uses WAV format + * + * WAV format requires special handling for raw PCM data + */ + val encodeForWav: Boolean + get() { + return this == WAV + } + + /** + * Indicates if this encoder is an AAC variant + * + * AAC encoders may need ADTS headers when writing raw frames + */ + val isAAC: Boolean + get() { + return this == AAC_LC || this == AAC_HE || this == AAC_ELD + } +} \ No newline at end of file diff --git a/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/WaveformExtractor.kt b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/WaveformExtractor.kt new file mode 100644 index 0000000..46201a9 --- /dev/null +++ b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/WaveformExtractor.kt @@ -0,0 +1,432 @@ +package com.simform.audio_waveforms + +import android.content.Context +import android.media.AudioFormat +import android.media.MediaCodec +import android.media.MediaExtractor +import android.media.MediaFormat +import android.os.Build +import android.util.Log +import io.flutter.plugin.common.MethodChannel +import java.nio.ByteBuffer +import java.util.concurrent.CountDownLatch +import kotlin.math.pow +import kotlin.math.sqrt +import androidx.core.net.toUri + +/** + * WaveformExtractor handles the process of extracting amplitude data from audio files + * to generate waveform visualizations. + * + * This class uses the Android MediaCodec API to decode audio files and extract + * RMS (Root Mean Square) values at regular intervals, which represent the amplitude + * of the audio signal. These values can then be used to draw waveform visualizations. + * + * The extractor supports various audio formats and bit depths (8-bit, 16-bit, and 32-bit) + * and handles both mono and stereo audio channels. + */ +class WaveformExtractor( + /** Path to the audio file to analyze */ + private val path: String, + /** Number of waveform data points to generate */ + private val expectedPoints: Int, + /** Unique identifier for this extraction process */ + private val key: String, + /** Method channel for sending progress updates to Flutter */ + private val methodChannel: MethodChannel, + /** Result callback for sending the final result back to Flutter */ + private val result: MethodChannel.Result, + /** Callback for notifying about progress changes */ + private val extractorCallBack: ExtractorCallBack, + /** Application context for accessing content URIs */ + private val context: Context, +) { + /** MediaCodec for decoding audio data */ + private var decoder: MediaCodec? = null + /** MediaExtractor for reading audio tracks from the file */ + private var extractor: MediaExtractor? = null + /** Duration of the audio file in milliseconds */ + private var durationMillis = 0L + /** Current extraction progress (0.0 to 1.0) */ + private var progress = 0F + /** Number of processed chunks */ + private var currentProgress = 0F + + /** Latch for synchronizing completion of the extraction process */ + private val finishCount = CountDownLatch(1) + /** Flag indicating end of input data */ + private var inputEof = false + /** Sample rate of the audio in Hz */ + private var sampleRate = 0 + /** Number of audio channels (1=mono, 2=stereo) */ + private var channels = 1 + /** Bit depth of the audio (8, 16, or 32) */ + private var pcmEncodingBit = 16 + /** Total number of audio samples */ + private var totalSamples = 0L + /** Number of audio samples per waveform data point */ + private var perSamplePoints = 0L + /** Flag to prevent submitting multiple results */ + private var isReplySubmitted = false + + /** + * Retrieves the audio format from the given media file + * + * This method: + * 1. Creates a MediaExtractor to read the file + * 2. Finds the first audio track in the file + * 3. Retrieves and selects that track + * 4. Extracts the audio duration + * + * @param path Path to the audio file (content URI format) + * @return MediaFormat of the audio track, or null if no audio track is found + */ + private fun getFormat(path: String): MediaFormat? { + if (path.isEmpty()) { + return null + } + val mediaExtractor = MediaExtractor() + this.extractor = mediaExtractor + val uri = path.toUri() + mediaExtractor.setDataSource(context, uri, null) + val trackCount = mediaExtractor.trackCount + repeat(trackCount) { + val format = mediaExtractor.getTrackFormat(it) + val mime = format.getString(MediaFormat.KEY_MIME) ?: "" + if (mime.contains("audio")) { + durationMillis = format.getLong(MediaFormat.KEY_DURATION) / 1000 + mediaExtractor.selectTrack(it) + return format + } + } + return null + } + + /** + * Starts the decoding and waveform extraction process + * + * This method initializes the MediaCodec decoder with appropriate + * callbacks to process audio frames. It handles: + * 1. Setting up the decoder with the proper format + * 2. Processing input buffers from the MediaExtractor + * 3. Processing decoded PCM audio data in output buffers + * 4. Calculating RMS values for waveform visualization + * 5. Reporting progress via the callback interface and method channel + */ + fun startDecode() { + try { + val format = getFormat(path) ?: error("No audio format found") + val mime = format.getString(MediaFormat.KEY_MIME) ?: error("No MIME type found") + decoder = MediaCodec.createDecoderByType(mime).also { + it.configure(format, null, null, 0) + it.setCallback(object : MediaCodec.Callback() { + override fun onInputBufferAvailable(codec: MediaCodec, index: Int) { + if (inputEof || index < 0) return + val extractor = extractor ?: return + codec.getInputBuffer(index)?.let { buf -> + val size = extractor.readSampleData(buf, 0) + val sampleTime = extractor.sampleTime + if (size > 0 && sampleTime >= 0) { + try { + codec.queueInputBuffer(index, 0, size, sampleTime, 0) + extractor.advance() + } catch (e: Exception) { + inputEof = true + result.error( + Constants.LOG_TAG, + e.message, + "Invalid input buffer." + ) + } + } else { + codec.queueInputBuffer( + index, + 0, + 0, + 0, + MediaCodec.BUFFER_FLAG_END_OF_STREAM + ) + inputEof = true + } + } + } + + override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) { + sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE) + channels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT) + pcmEncodingBit = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (format.containsKey(MediaFormat.KEY_PCM_ENCODING)) { + when (format.getInteger(MediaFormat.KEY_PCM_ENCODING)) { + AudioFormat.ENCODING_PCM_16BIT -> 16 + AudioFormat.ENCODING_PCM_8BIT -> 8 + AudioFormat.ENCODING_PCM_FLOAT -> 32 + else -> 16 + } + } else { + 16 + } + } else { + 16 + } + totalSamples = (sampleRate.toLong() * durationMillis) / 1000 + perSamplePoints = totalSamples / expectedPoints + } + + override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) { + if (!isReplySubmitted) { + result.error( + Constants.LOG_TAG, + e.message, + "An error is thrown while decoding the audio file" + ) + isReplySubmitted = true + finishCount.countDown() + } + } + + override fun onOutputBufferAvailable( + codec: MediaCodec, + index: Int, + info: MediaCodec.BufferInfo + ) { + if (index < 0 || decoder == null) return + + try { + if (info.size > 0) { + codec.getOutputBuffer(index)?.let { buf -> + try { + val size = info.size + // Set both position and limit to ensure buffer is accessible + buf.position(info.offset) + buf.limit(info.offset + info.size) + + when (pcmEncodingBit) { + 8 -> { + handle8bit(size, buf) + } + 16 -> { + handle16bit(size, buf) + } + 32 -> { + handle32bit(size, buf) + } + else -> { + Log.e(Constants.LOG_TAG, "Unsupported PCM encoding bit: $pcmEncodingBit") + } + } + } catch (e: Exception) { + Log.e(Constants.LOG_TAG, "Error processing output buffer: ${e.message}") + } + } + } + } finally { + // Always release the buffer, even if processing failed + try { + codec.releaseOutputBuffer(index, false) + } catch (e: IllegalStateException) { + Log.e(Constants.LOG_TAG, "Error releasing output buffer: ${e.message}") + } + } + + if (info.isEof()) { + updateProgress() + val rms = sqrt(sampleSum / perSamplePoints).toFloat() + sendProgress(rms) + stop() + } + } + + }) + it.start() + } + + } catch (e: Exception) { + if (!isReplySubmitted) { + result.error( + Constants.LOG_TAG, + e.message, + "An error is thrown before decoding the audio file" + ) + isReplySubmitted = true + } + } + + + } + + /** Collected waveform amplitude data points */ + var sampleData = ArrayList() + /** Count of samples processed for the current data point */ + private var sampleCount = 0L + /** Sum of squared sample values for RMS calculation */ + private var sampleSum = 0.0 + + /** + * Processes each audio sample and accumulates data for RMS calculation + * + * This method: + * 1. Accumulates squared sample values + * 2. When enough samples are collected for a data point, calculates the RMS + * 3. Updates progress and sends the new data point to Flutter + * + * @param value The normalized audio sample value (-1.0 to 1.0) + */ + private fun handleBufferDivision(value: Float) { + if (sampleCount == perSamplePoints) { + updateProgress() + + // Discard redundant values and release resources + if (progress > 1.0F) { + stop() + return + } + val rms = sqrt(sampleSum / perSamplePoints).toFloat() + sendProgress(rms) + } + + sampleCount++ + sampleSum += value.toDouble().pow(2.0) + } + + /** + * Processes 8-bit PCM audio data + * + * Reads 8-bit samples from the buffer, normalizes them to the range [-1.0, 1.0], + * and passes them to handleBufferDivision for RMS calculation. + * + * @param size Size of the buffer in bytes + * @param buf ByteBuffer containing the audio data + */ + private fun handle8bit(size: Int, buf: ByteBuffer) { + repeat(size / if (channels == 2) 2 else 1) { + val result = buf.get().toInt() / Constants.EIGHT_BITS + if (channels == 2) { + buf.get() + } + handleBufferDivision(result) + } + } + + /** + * Processes 16-bit PCM audio data + * + * Reads 16-bit samples from the buffer, normalizes them to the range [-1.0, 1.0], + * and passes them to handleBufferDivision for RMS calculation. + * + * @param size Size of the buffer in bytes + * @param buf ByteBuffer containing the audio data + */ + private fun handle16bit(size: Int, buf: ByteBuffer) { + repeat(size / if (channels == 2) 4 else 2) { + val first = buf.get().toInt() + val second = buf.get().toInt() shl 8 + val value = (first or second) / Constants.SIXTEEN_BITS + if (channels == 2) { + buf.get() + buf.get() + } + handleBufferDivision(value) + } + } + + /** + * Processes 32-bit PCM audio data + * + * Reads 32-bit samples from the buffer, normalizes them to the range [-1.0, 1.0], + * and passes them to handleBufferDivision for RMS calculation. + * + * @param size Size of the buffer in bytes + * @param buf ByteBuffer containing the audio data + */ + private fun handle32bit(size: Int, buf: ByteBuffer) { + repeat(size / if (channels == 2) 8 else 4) { + val first = buf.get().toLong() + val second = buf.get().toLong() shl 8 + val third = buf.get().toLong() shl 16 + val forth = buf.get().toLong() shl 24 + val value = (first or second or third or forth) / Constants.THIRTY_TWO_BITS + if (channels == 2) { + buf.get() + buf.get() + buf.get() + buf.get() + } + handleBufferDivision(value) + } + } + + /** + * Updates the extraction progress + * + * Increments the progress counter and calculates the overall + * extraction progress as a ratio of current to expected data points. + */ + private fun updateProgress() { + currentProgress++ + progress = currentProgress / expectedPoints + } + + /** + * Sends a new waveform data point and current progress to Flutter + * + * This method: + * 1. Adds the new RMS value to the waveform data + * 2. Reports the progress via the callback interface + * 3. Resets the sample counters for the next data point + * 4. Sends the current waveform data and progress to Flutter via the method channel + * + * @param rms The calculated RMS value for this data point + */ + private fun sendProgress(rms: Float) { + sampleData.add(rms) + extractorCallBack.onProgress(progress) + sampleCount = 0 + sampleSum = 0.0 + + val args: MutableMap = HashMap() + args[Constants.waveformData] = sampleData + args[Constants.progress] = progress + args[Constants.playerKey] = key + methodChannel.invokeMethod( + Constants.onCurrentExtractedWaveformData, + args + ) + } + + /** + * Stops the extraction process and releases resources + * + * This method: + * 1. Stops and releases the MediaCodec decoder + * 2. Releases the MediaExtractor + * 3. Signals completion via the countdown latch + */ + fun stop() { + decoder?.stop() + decoder?.release() + extractor?.release() + finishCount.countDown() + } +} + +/** + * Extension function to check if a buffer contains the end-of-stream flag + * + * @return true if this buffer marks the end of the stream, false otherwise + */ +fun MediaCodec.BufferInfo.isEof() = flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0 + +/** + * Callback interface for reporting waveform extraction progress + * + * Implementations of this interface receive progress updates during + * the waveform extraction process. + */ +interface ExtractorCallBack { + /** + * Called when extraction progress changes + * + * @param value Progress value from 0.0 to 1.0, where 1.0 indicates completion + */ + fun onProgress(value: Float) +} \ No newline at end of file diff --git a/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/encoders/CommonEncoder.kt b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/encoders/CommonEncoder.kt new file mode 100644 index 0000000..182a7fa --- /dev/null +++ b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/encoders/CommonEncoder.kt @@ -0,0 +1,429 @@ +package com.simform.audio_waveforms.encoders + +import android.media.AudioFormat +import android.media.AudioRecord +import android.media.MediaCodec +import android.media.MediaFormat +import android.media.MediaMuxer +import android.os.Handler +import android.os.HandlerThread +import android.util.Log +import com.simform.audio_waveforms.Constants +import com.simform.audio_waveforms.Encoder +import com.simform.audio_waveforms.RecorderSettings +import com.simform.audio_waveforms.isEof +import io.flutter.plugin.common.MethodChannel +import java.io.FileOutputStream +import java.util.LinkedList + +/** + * CommonEncoder provides a unified interface for audio encoding functionality across + * multiple encoder types (AMR, AAC, etc.). It handles the complexities of: + * + * - Initializing the appropriate MediaCodec encoder based on the selected encoder type + * - Managing input and output buffer processing + * - Handling MediaMuxer operations when required (for container formats like MP4) + * - Writing encoded audio data to the output file + * - Adding format-specific headers like ADTS headers for AAC + * + * This class acts as a bridge between raw audio data captured from AudioRecord + * and the encoded output file ready for playback. + */ +class CommonEncoder { + /** MediaCodec instance for encoding audio data */ + private lateinit var mediaCodec: MediaCodec + + /** The selected encoder type (AAC_LC, AMR_NB, etc.) */ + private lateinit var encoder: Encoder + + /** Background thread for encoder operations */ + private lateinit var handlerThread: HandlerThread + + /** Handler for the encoder thread */ + private lateinit var handler: Handler + + /** Output stream for writing encoded audio data */ + private lateinit var outputStream: FileOutputStream + + /** Configuration for the recorder and encoder */ + private lateinit var recorderSettings: RecorderSettings + + /** MediaMuxer instance for container formats (optional) */ + private var mediaMuxer: MediaMuxer? = null + + /** Queue for audio data waiting to be encoded */ + private val inputQueue = LinkedList() + + /** Current available input buffer index (-1 if none available) */ + private var currentInputBufferIndex = -1 + + /** Flag indicating if the muxer has been started */ + private var isMuxerStarted = false + + /** Flag indicating encoding process should complete */ + private var isEncodingComplete = false + + /** Flag indicating encoder has been stopped */ + private var isEncoderStopped = false + + /** Track index for the audio track in the muxer */ + private var trackIndex = -1 + + /** Callback to invoke when encoding is complete */ + private var completionCallback: (() -> Unit)? = null + + /** Total bytes encoded so far, used for calculating presentation timestamps */ + private var totalBytesEncoded = 0L + + /** Track the first output timestamp to normalize subsequent timestamps */ + private var firstOutputTimestamp = -1L + + /** Track the last valid output timestamp to ensure monotonic increasing */ + private var lastOutputTimestamp = 0L + + /** + * Initializes the MediaCodec encoder with the specified settings + * + * @param recorderSettings The configuration for the recorder and encoder + * @param result The Flutter method channel result to report initialization status + * @param onEncodingCompleted Optional callback to invoke when encoding is completed + */ + fun initCodec( + recorderSettings: RecorderSettings, + result: MethodChannel.Result, + onEncodingCompleted: (() -> Unit)? = null, + ) { + // Reset state at the beginning + isMuxerStarted = false + trackIndex = -1 + isEncodingComplete = false + isEncoderStopped = false + inputQueue.clear() + currentInputBufferIndex = -1 + totalBytesEncoded = 0L + firstOutputTimestamp = -1L + lastOutputTimestamp = 0L + + this.recorderSettings = recorderSettings + encoder = recorderSettings.encoder + completionCallback = onEncodingCompleted + var useMediaMuxer = encoder.useMediaMuxer + var format: MediaFormat + try { + outputStream = FileOutputStream(recorderSettings.path!!) + mediaCodec = MediaCodec.createEncoderByType(encoder.mimeType) + + format = MediaFormat.createAudioFormat(encoder.mimeType, recorderSettings.sampleRate, 1) + + + recorderSettings.bitRate.let { + format.setInteger(MediaFormat.KEY_BIT_RATE, it) + } + encoder.aacProfile?.let { + format.setInteger( + MediaFormat.KEY_AAC_PROFILE, it + ) + } + format.setInteger( + MediaFormat.KEY_MAX_INPUT_SIZE, AudioRecord.getMinBufferSize( + recorderSettings.sampleRate, Constants.CHANNEL, AudioFormat.ENCODING_PCM_16BIT + ) + ) + + handlerThread = HandlerThread(Constants.ENCODER_THREAD) + handlerThread.start() + handler = Handler(handlerThread.looper) + + if (encoder == Encoder.AMR_NB) { + outputStream.write("#!AMR\n".toByteArray()) + } else if (encoder == Encoder.AMR_WB) { + outputStream.write("#!AMR-WB\n".toByteArray()) + } + + if (useMediaMuxer) { + mediaMuxer = MediaMuxer(recorderSettings.path!!, encoder.toMuxerOutputFormat) + } + } catch (e: Exception) { + result.error( + Constants.LOG_TAG, "Error initializing encoder: ${e.message}", null + ) + return + } + + mediaCodec.setCallback(object : MediaCodec.Callback() { + override fun onInputBufferAvailable(codec: MediaCodec, index: Int) { + if (isEncodingComplete && inputQueue.isEmpty()) { + // Use the last calculated presentation time for EOF, not system time + val eofTimestamp = if (totalBytesEncoded > 0) { + val bytesPerSample = 2L + val channels = 1L + (totalBytesEncoded * 1_000_000L) / (recorderSettings.sampleRate * channels * bytesPerSample) + } else { + 0L + } + codec.queueInputBuffer( + index, 0, 0, eofTimestamp, MediaCodec.BUFFER_FLAG_END_OF_STREAM + ) + } else { + currentInputBufferIndex = index + feedEncoder() + } + } + + override fun onOutputBufferAvailable( + codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo + ) { + val buffer = codec.getOutputBuffer(index) ?: return + + if (info.isEof()) { + codec.releaseOutputBuffer(index, false) + stopEncoder() + return + } + + // Normalize output timestamps to start from 0 + if (firstOutputTimestamp == -1L && info.presentationTimeUs > 0) { + firstOutputTimestamp = info.presentationTimeUs + } + + // Calculate normalized timestamp (starting from 0) + val normalizedTimestamp = if (firstOutputTimestamp >= 0) { + maxOf(info.presentationTimeUs - firstOutputTimestamp, lastOutputTimestamp) + } else { + info.presentationTimeUs + } + + lastOutputTimestamp = normalizedTimestamp + + if (useMediaMuxer) { + if (!isMuxerStarted) { + trackIndex = mediaMuxer?.addTrack(mediaCodec.outputFormat) ?: -1 + mediaMuxer?.start() + isMuxerStarted = true + } + + // Ensure buffer is positioned correctly for MediaMuxer + buffer.position(info.offset) + buffer.limit(info.offset + info.size) + + // Create a new BufferInfo with normalized timestamp to avoid modifying the original + val normalizedInfo = MediaCodec.BufferInfo() + normalizedInfo.set(info.offset, info.size, normalizedTimestamp, info.flags) + + // Write sample data with normalized timestamps + mediaMuxer?.writeSampleData(trackIndex, buffer, normalizedInfo) + } else { + // For raw output (AAC/AMR without container), extract the encoded data + buffer.position(info.offset) + buffer.limit(info.offset + info.size) + val encodedData = ByteArray(info.size) + buffer.get(encodedData) + + addADTSIfAAC() + outputStream.write(encodedData) + } + + codec.releaseOutputBuffer(index, false) + } + + override fun onError( + codec: MediaCodec, e: MediaCodec.CodecException + ) { + Log.e( + Constants.LOG_TAG, "Error while encoding: ${e.message}" + ) + stopEncoder() + } + + override fun onOutputFormatChanged( + codec: MediaCodec, format: MediaFormat + ) { + if ((useMediaMuxer) && !isMuxerStarted) { + trackIndex = mediaMuxer?.addTrack(format) ?: -1 + mediaMuxer?.start() + isMuxerStarted = true + } + } + }) + + mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) + mediaCodec.start() + } + + /** + * Queues audio data for encoding + * + * This method safely adds audio data to the input queue and attempts to feed + * it to the encoder if an input buffer is available. Thread-safe. + * + * @param buffer The raw audio data to encode + */ + fun queueInputBuffer(buffer: ByteArray) { + synchronized(inputQueue) { + inputQueue.add(buffer) + } + + if (currentInputBufferIndex >= 0) { + feedEncoder() + } + } + + /** + * Signals that no more audio data will be provided + * + * This method marks the encoding process as complete. Once all queued data + * is processed, the encoder will be sent an end-of-stream signal. + */ + fun signalToStop() { + isEncodingComplete = true + } + + /** + * Sets the callback to be invoked when encoding is completed + * + * @param callback The function to call when encoding completes + */ + fun setOnEncodingCompleted(callback: () -> Unit) { + completionCallback = callback + } + + + /** + * Feeds available audio data to the encoder + * + * This method is called when both audio data is available in the queue + * and an input buffer is available from the encoder. It's synchronized + * to ensure thread safety when accessing the input queue. + * + * The presentation timestamp is calculated based on the amount of audio data + * encoded so far, starting from 0. This ensures monotonic timestamps that + * represent the actual audio timeline for proper playback and looping. + */ + private fun feedEncoder() { + synchronized(inputQueue) { + if (inputQueue.isEmpty() || currentInputBufferIndex < 0) return + + val data = inputQueue.poll() ?: return + val inputBuffer = mediaCodec.getInputBuffer(currentInputBufferIndex) ?: return + inputBuffer.clear() + inputBuffer.put(data) + + // Calculate presentation time based on actual audio data encoded + // Formula: presentationTimeUs = (totalBytes * 1,000,000) / (sampleRate * channels * bytesPerSample) + // For 16-bit PCM mono: bytesPerSample = 2, channels = 1 + val bytesPerSample = 2L // 16-bit = 2 bytes + val channels = 1L // Mono + val presentationTimeUs = (totalBytesEncoded * 1_000_000L) / (recorderSettings.sampleRate * channels * bytesPerSample) + totalBytesEncoded += data.size + + mediaCodec.queueInputBuffer( + currentInputBufferIndex, 0, data.size, presentationTimeUs, 0 + ) + currentInputBufferIndex = -1 + } + } + + /** + * Adds ADTS (Audio Data Transport Stream) header for AAC audio files + * + * AAC raw frames require an ADTS header to be properly recognized by players. + * This method adds the header when necessary based on the encoder type and file extension. + */ + private fun addADTSIfAAC() { + if ((encoder.isAAC && recorderSettings.path!!.endsWith( + Constants.AAC_FILE_EXTENSION + )) + ) { + outputStream.write(addADTSPacket(encoder.bufferSize, recorderSettings.sampleRate)) + } + } + + /** + * Creates an ADTS header packet for AAC audio data + * + * @param dataLength The length of the AAC frame data + * @param sampleRate The sample rate of the audio in Hz + * @param channelConfig The channel configuration (mono/stereo) + * @return The ADTS header as a byte array + */ + private fun addADTSPacket( + dataLength: Int, sampleRate: Int, channelConfig: Int = Constants.CHANNEL + ): ByteArray { + val packet = ByteArray(7) + val profile = when (encoder) { + Encoder.AAC_LC -> 2 + Encoder.AAC_HE -> 5 + Encoder.AAC_ELD -> 39 + else -> 2 + } + val frameLength = dataLength + 7 + + val freqIdx = when (sampleRate) { + 96000 -> 0 + 88200 -> 1 + 64000 -> 2 + 48000 -> 3 + 44100 -> 4 + 32000 -> 5 + 24000 -> 6 + 22050 -> 7 + 16000 -> 8 + 12000 -> 9 + 11025 -> 10 + 8000 -> 11 + 7350 -> 12 + else -> 4 + } + + packet[0] = 0xFF.toByte() + packet[1] = 0xF9.toByte() + packet[2] = ((profile - 1 shl 6) + (freqIdx shl 2) + (channelConfig shr 2)).toByte() + packet[3] = ((channelConfig and 3 shl 6) + (frameLength shr 11)).toByte() + packet[4] = (frameLength and 0x7FF shr 3).toByte() + packet[5] = ((frameLength and 7 shl 5) + 0x1F).toByte() + packet[6] = 0xFC.toByte() + + return packet + } + + /** + * Stops the encoder and releases all resources + * + * This method is called when encoding is complete or when an error occurs. + * It: + * 1. Stops and releases the MediaCodec + * 2. Stops and releases the MediaMuxer (if used) + * 3. Closes the output file stream + * 4. Shuts down the handler thread + * 5. Calls the completion callback + * + * This method is designed to be idempotent (can be called multiple times safely). + */ + private fun stopEncoder() { + if (isEncoderStopped) return + isEncoderStopped = true + + try { + mediaCodec.stop() + mediaCodec.release() + mediaMuxer?.stop() + mediaMuxer?.release() + outputStream.close() + handlerThread.quitSafely() + handlerThread.join() + completionCallback?.invoke() + } catch (e: Exception) { + Log.e(Constants.LOG_TAG, "Error stopping encoder: ${e.message}") + } + + // Reset state for next recording + isMuxerStarted = false + trackIndex = -1 + isEncodingComplete = false + inputQueue.clear() + currentInputBufferIndex = -1 + totalBytesEncoded = 0L + firstOutputTimestamp = -1L + lastOutputTimestamp = 0L + } +} \ No newline at end of file diff --git a/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/encoders/WavEncoder.kt b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/encoders/WavEncoder.kt new file mode 100644 index 0000000..ae56d06 --- /dev/null +++ b/audio_waveforms/android/src/main/kotlin/com/simform/audio_waveforms/encoders/WavEncoder.kt @@ -0,0 +1,168 @@ +package com.simform.audio_waveforms.encoders + +import com.simform.audio_waveforms.Constants +import io.flutter.plugin.common.MethodChannel +import java.io.File +import java.io.FileOutputStream +import java.io.RandomAccessFile + +/** + * WavEncoder handles the creation of WAV audio files from raw PCM data. + * + * This class manages the process of writing PCM audio data to a file with the proper + * WAV file format headers. WAV is an uncompressed audio format that preserves the full + * quality of the recorded audio but results in larger file sizes compared to + * compressed formats like AAC or MP3. + * + * The encoder works in three main steps: + * 1. Start - Creates the file and writes a placeholder header + * 2. Write - Appends PCM audio data to the file + * 3. Stop - Updates the header with the final file size and audio parameters + */ +class WavEncoder( + /** Target file where the WAV audio will be written */ + private val wavFile: File, + + /** Sample rate of the audio in Hz (e.g., 44100, 48000) */ + private val sampleRate: Int +) { + /** Stream for writing data to the WAV file */ + private lateinit var outputStream: FileOutputStream + + /** Running count of audio data length in bytes */ + private var totalAudioLen = 0L + + /** Flag indicating if the encoder is currently active */ + private var isWriting = false + + /** + * Starts the WAV encoding process + * + * Creates a new file with a placeholder WAV header. The header will be + * updated with the correct values when [stop] is called. + * + * @param result Flutter result callback to report success or errors + */ + fun start(result: MethodChannel.Result) { + isWriting = true + try { + outputStream = FileOutputStream(wavFile) + // These are placeholder bytes for the header which will be updated later. + val header = ByteArray(44) + outputStream.write(header) + } catch (e: Exception) { + result.error( + "WAV_FILE_WRITE_ERROR", + "Error writing to WAV file: ${e.message}", + null + ) + } + } + + /** + * Writes PCM audio data to the WAV file + * + * Appends raw audio samples to the file and updates the total audio length counter. + * This method should be called for each chunk of audio data received from the recorder. + * + * @param pcmData Array of PCM audio samples to write to the file + */ + fun writePcmData(pcmData: ByteArray) { + if (!isWriting) return + outputStream.write(pcmData) + totalAudioLen += pcmData.size + } + + /** + * Stops the encoding process and finalizes the WAV file + * + * This method: + * 1. Marks the encoder as no longer writing + * 2. Closes the output stream + * 3. Updates the WAV header with the final file size and audio parameters + * + * @param result Flutter result callback to report success or errors + */ + fun stop(result: MethodChannel.Result) { + try { + isWriting = false + outputStream.close() + updateWavHeader() + } catch (e: Exception) { + result.error( + "WAV_FILE_CLOSE_ERROR", + "Error closing WAV file: ${e.message}", + null + ) + } + } + + /** + * Updates the WAV file header with the final audio parameters + * + * The WAV format requires a specific header structure with information about: + * - File format (RIFF/WAVE) + * - Audio format (PCM) + * - Number of channels + * - Sample rate + * - Bit depth + * - Total data size + * + * This method writes all these parameters to the beginning of the file after + * all audio data has been written and the total size is known. + */ + private fun updateWavHeader() { + val totalDataLen = totalAudioLen + 36 + val byteRate = sampleRate * Constants.CHANNEL * Constants.BIT_PER_SAMPLE / 8 + val header = ByteArray(44) + + /** + * Writes a 32-bit integer to the header in little-endian format + * + * @param offset Starting position in the header array + * @param value Integer value to write + */ + fun writeInt(offset: Int, value: Int) { + header[offset] = (value and 0xff).toByte() + header[offset + 1] = ((value shr 8) and 0xff).toByte() + header[offset + 2] = ((value shr 16) and 0xff).toByte() + header[offset + 3] = ((value shr 24) and 0xff).toByte() + } + + /** + * Writes a 16-bit short to the header in little-endian format + * + * @param offset Starting position in the header array + * @param value Short value to write + */ + fun writeShort(offset: Int, value: Short) { + header[offset] = (value.toInt() and 0xff).toByte() + header[offset + 1] = ((value.toInt() shr 8) and 0xff).toByte() + } + + // RIFF chunk descriptor + "RIFF".toByteArray().copyInto(header, 0) // ChunkID: "RIFF" in ASCII + writeInt(4, totalDataLen.toInt()) // ChunkSize: total size minus 8 bytes + "WAVE".toByteArray().copyInto(header, 8) // Format: "WAVE" in ASCII + + // "fmt " sub-chunk (format information) + "fmt ".toByteArray().copyInto(header, 12) // Subchunk1ID: "fmt " in ASCII + writeInt(16, 16) // Subchunk1Size: 16 for PCM format + writeShort(20, 1) // AudioFormat: 1 for PCM (uncompressed) + writeShort(22, Constants.CHANNEL.toShort()) // NumChannels: Mono=1, Stereo=2 + writeInt(24, sampleRate) // SampleRate: samples per second + writeInt(28, byteRate) // ByteRate: bytes per second + writeShort(32, (Constants.CHANNEL * Constants.BIT_PER_SAMPLE / 8).toShort()) // BlockAlign + writeShort(34, Constants.BIT_PER_SAMPLE.toShort()) // BitsPerSample: 8, 16, etc. + + // "data" sub-chunk (the actual sound data) + "data".toByteArray().copyInto(header, 36) // Subchunk2ID: "data" in ASCII + writeInt(40, totalAudioLen.toInt()) // Subchunk2Size: size of the audio data + + // Write the completed header back to the beginning of the file + val raf = RandomAccessFile(wavFile, "rw") + raf.seek(0) + raf.write(header) + raf.close() + } +} \ No newline at end of file diff --git a/audio_waveforms/ios/.gitignore b/audio_waveforms/ios/.gitignore new file mode 100644 index 0000000..0c88507 --- /dev/null +++ b/audio_waveforms/ios/.gitignore @@ -0,0 +1,38 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/ephemeral/ +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/exif/test/data/test-data b/audio_waveforms/ios/Assets/.gitkeep similarity index 100% rename from exif/test/data/test-data rename to audio_waveforms/ios/Assets/.gitkeep diff --git a/audio_waveforms/ios/Classes/AudioPlayer.swift b/audio_waveforms/ios/Classes/AudioPlayer.swift new file mode 100644 index 0000000..f886574 --- /dev/null +++ b/audio_waveforms/ios/Classes/AudioPlayer.swift @@ -0,0 +1,174 @@ +import Foundation + +import AVKit + +class AudioPlayer: NSObject, AVAudioPlayerDelegate { + private var seekToStart = true + private var stopWhenCompleted = false + private var timer: Timer? + private var player: AVAudioPlayer? + private var finishMode:FinishMode = FinishMode.stop + private var updateFrequency = 200 + var plugin: SwiftAudioWaveformsPlugin + var playerKey: String + var flutterChannel: FlutterMethodChannel + + + init(plugin: SwiftAudioWaveformsPlugin, playerKey: String, channel: FlutterMethodChannel) { + self.plugin = plugin + self.playerKey = playerKey + flutterChannel = channel + } + + func preparePlayer(path: String?, volume: Double?, updateFrequency: Int?,result: @escaping FlutterResult, overrideAudioSession : Bool) { + if(!(path ?? "").isEmpty) { + self.updateFrequency = updateFrequency ?? 200 + let audioUrl = URL.init(string: path!) + if(audioUrl == nil){ + result(FlutterError(code: Constants.audioWaveforms, message: "Failed to initialise Url from provided audio file", details: "If path contains `file://` try removing it")) + return + } + do { + stopPlayer() + player = nil + player = try AVAudioPlayer(contentsOf: audioUrl!) + do { + if overrideAudioSession { + try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default) + try AVAudioSession.sharedInstance().setActive(true) + } + } catch { + result(FlutterError(code: Constants.audioWaveforms, message: "Couldn't set audio session.", details: error.localizedDescription)) + return + } + + } catch { + result(FlutterError(code: Constants.audioWaveforms, message: "Failed to prepare player", details: error.localizedDescription)) + return + } + player?.enableRate = true + player?.rate = 1.0 + player?.prepareToPlay() + player?.volume = Float(volume ?? 1.0) + result(true) + } else { + result(FlutterError(code: Constants.audioWaveforms, message: "Audio file path can't be empty or null", details: nil)) + } + } + + func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer,successfully flag: Bool) { + var finishType = 2 + + switch self.finishMode{ + + case .loop: + self.player?.currentTime = 0 + self.player?.play() + finishType = 0 + + case .pause: + self.player?.pause() + stopListening() + finishType = 1 + + case .stop: + self.player?.stop() + stopListening() + self.player = nil + finishType = 2 + + + } + + plugin.flutterChannel.invokeMethod(Constants.onDidFinishPlayingAudio, arguments: [ + Constants.finishType: finishType, + Constants.playerKey: playerKey]) + + } + + func startPlyer(result: @escaping FlutterResult) { + player?.play() + player?.delegate = self + startListening() + result(true) + } + + + func pausePlayer() { + stopListening() + player?.pause() + } + + func stopPlayer() { + stopListening() + player?.stop() + timer = nil + } + + func release(result: @escaping FlutterResult) { + player = nil + result(true) + } + + func getDuration(_ type: DurationType, _ result: @escaping FlutterResult) throws { + if type == .Current { + let ms = (player?.currentTime ?? 0) * 1000 + result(Int(ms)) + } else { + let ms = (player?.duration ?? 0) * 1000 + result(Int(ms)) + } + } + + func setVolume(_ volume: Double?, _ result: @escaping FlutterResult) { + player?.volume = Float(volume ?? 1.0) + result(true) + } + + func setRate(_ rate: Double?, _ result: @escaping FlutterResult) { + player?.rate = Float(rate ?? 1.0); + result(true) + } + + func seekTo(_ time: Int?, _ result: @escaping FlutterResult) { + if(time != nil) { + player?.currentTime = Double(time! / 1000) + sendCurrentDuration() + result(true) + } else { + result(false) + } + } + + func setFinishMode(result : @escaping FlutterResult, releaseType : Int?){ + if(releaseType != nil && releaseType == 0){ + self.finishMode = FinishMode.loop + }else if(releaseType != nil && releaseType == 1){ + self.finishMode = FinishMode.pause + }else{ + self.finishMode = FinishMode.stop + } + result(nil) + } + + func startListening() { + if #available(iOS 10.0, *) { + timer = Timer.scheduledTimer(withTimeInterval: (Double(updateFrequency) / 1000), repeats: true, block: { _ in + self.sendCurrentDuration() + }) + } else { + // Fallback on earlier versions + } + } + + func stopListening() { + timer?.invalidate() + timer = nil + sendCurrentDuration() + } + + func sendCurrentDuration() { + let ms = (player?.currentTime ?? 0) * 1000 + flutterChannel.invokeMethod(Constants.onCurrentDuration, arguments: [Constants.current: Int(ms), Constants.playerKey: playerKey]) + } +} diff --git a/audio_waveforms/ios/Classes/AudioRecorder.swift b/audio_waveforms/ios/Classes/AudioRecorder.swift new file mode 100644 index 0000000..64f2884 --- /dev/null +++ b/audio_waveforms/ios/Classes/AudioRecorder.swift @@ -0,0 +1,194 @@ +import AVFoundation +import Accelerate + +public class AudioRecorder: NSObject, AVAudioRecorderDelegate{ + var audioRecorder: AVAudioRecorder? + var path: String? + var useLegacyNormalization: Bool = false + var audioUrl: URL? + var recordedDuration: CMTime = CMTime.zero + var flutterChannel: FlutterMethodChannel + var bytesStreamEngine: RecorderBytesStreamEngine + init(channel: FlutterMethodChannel){ + flutterChannel = channel + bytesStreamEngine = RecorderBytesStreamEngine(channel: channel) + } + + func startRecording(_ result: @escaping FlutterResult,_ recordingSettings: RecordingSettings){ + useLegacyNormalization = recordingSettings.useLegacy ?? false + + var settings: [String: Any] = [ + AVFormatIDKey: getEncoder(recordingSettings.encoder ?? 0), + AVSampleRateKey: recordingSettings.sampleRate ?? 44100, + AVNumberOfChannelsKey: 1, + AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue + ] + + if (recordingSettings.bitRate != nil) { + settings[AVEncoderBitRateKey] = recordingSettings.bitRate + } + + if ((recordingSettings.encoder ?? 0) == Constants.kAudioFormatLinearPCM) { + settings[AVLinearPCMBitDepthKey] = recordingSettings.linearPCMBitDepth + settings[AVLinearPCMIsBigEndianKey] = recordingSettings.linearPCMIsBigEndian + settings[AVLinearPCMIsFloatKey] = recordingSettings.linearPCMIsFloat + } + + let options: AVAudioSession.CategoryOptions = [.defaultToSpeaker, .allowBluetooth] + if (recordingSettings.path == nil) { + let documentDirectory = getDocumentDirectory(result) + let date = Date() + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = recordingSettings.fileNameFormat + let fileName = dateFormatter.string(from: date) + ".m4a" + self.path = "\(documentDirectory)/\(fileName)" + } else { + self.path = recordingSettings.path + } + + + do { + if recordingSettings.overrideAudioSession { + try AVAudioSession.sharedInstance().setCategory(.playAndRecord, options: options) + try AVAudioSession.sharedInstance().setActive(true) + } + audioUrl = URL(fileURLWithPath: self.path!) + + if(audioUrl == nil){ + result(FlutterError(code: Constants.audioWaveforms, message: "Failed to initialise file URL", details: nil)) + return + } + audioRecorder = try AVAudioRecorder(url: audioUrl!, settings: settings as [String : Any]) + + audioRecorder?.delegate = self + audioRecorder?.isMeteringEnabled = true + audioRecorder?.record() + bytesStreamEngine + .attach( + result: result, + sampleRate: recordingSettings.sampleRate ?? Constants.defaultSampleRate + ) + result(true) + } catch { + result(FlutterError(code: Constants.audioWaveforms, message: "Failed to start recording", details: error.localizedDescription)) + } + } + + public func stopRecording(_ result: @escaping FlutterResult) { + audioRecorder?.stop() + bytesStreamEngine.detach() + if(audioUrl != nil) { + let asset = AVURLAsset(url: audioUrl!) + + if #available(iOS 15.0, *) { + Task { + do { + recordedDuration = try await asset.load(.duration) + sendResult(result, duration: Int(recordedDuration.seconds * 1000)) + } catch let err { + debugPrint(err.localizedDescription) + sendResult(result, duration: Int(CMTime.zero.seconds)) + } + } + } else { + recordedDuration = asset.duration + sendResult(result, duration: Int(recordedDuration.seconds * 1000)) + } + } else { + sendResult(result, duration: Int(CMTime.zero.seconds)) + } + audioRecorder = nil + } + + private func sendResult(_ result: @escaping FlutterResult, duration:Int){ + var params = [String:Any?]() + params[Constants.resultFilePath] = path + params[Constants.resultDuration] = duration + result(params) + } + + public func pauseRecording(_ result: @escaping FlutterResult) { + audioRecorder?.pause() + bytesStreamEngine.togglePause() + result(false) + } + + public func resumeRecording(_ result: @escaping FlutterResult) { + audioRecorder?.record() + bytesStreamEngine.togglePause(); + result(true) + } + + public func getDecibel(_ result: @escaping FlutterResult) { + audioRecorder?.updateMeters() + if(useLegacyNormalization){ + let amp = audioRecorder?.averagePower(forChannel: 0) ?? 0.0 + result(amp) + } else { + let amp = audioRecorder?.peakPower(forChannel: 0) ?? 0.0 + let linear = pow(10, amp / 20); + result(linear) + } + } + + public func checkHasPermission(_ result: @escaping FlutterResult){ + switch AVAudioSession.sharedInstance().recordPermission{ + + case .undetermined: + AVAudioSession.sharedInstance().requestRecordPermission() { [unowned self] allowed in + DispatchQueue.main.async { + result(allowed) + } + } + case .denied: + result(false) + case .granted: + result(true) + @unknown default: + result(false) + } + } + public func getEncoder(_ enCoder: Int) -> Int { + switch(enCoder) { + case Constants.kAudioFormatMPEG4AAC: + return Int(kAudioFormatMPEG4AAC) + case Constants.kAudioFormatMPEGLayer1: + return Int(kAudioFormatMPEGLayer1) + case Constants.kAudioFormatMPEGLayer2: + return Int(kAudioFormatMPEGLayer2) + case Constants.kAudioFormatMPEGLayer3: + return Int(kAudioFormatMPEGLayer3) + case Constants.kAudioFormatMPEG4AAC_ELD: + return Int(kAudioFormatMPEG4AAC_ELD) + case Constants.kAudioFormatMPEG4AAC_HE: + return Int(kAudioFormatMPEG4AAC_HE) + case Constants.kAudioFormatOpus: + return Int(kAudioFormatOpus) + case Constants.kAudioFormatAMR: + return Int(kAudioFormatAMR) + case Constants.kAudioFormatAMR_WB: + return Int(kAudioFormatAMR_WB) + case Constants.kAudioFormatLinearPCM: + return Int(kAudioFormatLinearPCM) + case Constants.kAudioFormatAppleLossless: + return Int(kAudioFormatAppleLossless) + case Constants.kAudioFormatMPEG4AAC_HE_V2: + return Int(kAudioFormatMPEG4AAC_HE_V2) + default: + return Int(kAudioFormatMPEG4AAC) + } + } + + private func getDocumentDirectory(_ result: @escaping FlutterResult) -> String { + let directory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + let ifExists = FileManager.default.fileExists(atPath: directory) + if(directory.isEmpty){ + result(FlutterError(code: Constants.audioWaveforms, message: "The document directory path is empty", details: nil)) + return "" + } else if(!ifExists) { + result(FlutterError(code: Constants.audioWaveforms, message: "The document directory does't exists", details: nil)) + return "" + } + return directory + } +} diff --git a/audio_waveforms/ios/Classes/AudioWaveformsPlugin.h b/audio_waveforms/ios/Classes/AudioWaveformsPlugin.h new file mode 100644 index 0000000..5946602 --- /dev/null +++ b/audio_waveforms/ios/Classes/AudioWaveformsPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface AudioWaveformsPlugin : NSObject +@end diff --git a/audio_waveforms/ios/Classes/AudioWaveformsPlugin.m b/audio_waveforms/ios/Classes/AudioWaveformsPlugin.m new file mode 100644 index 0000000..92a1dc9 --- /dev/null +++ b/audio_waveforms/ios/Classes/AudioWaveformsPlugin.m @@ -0,0 +1,15 @@ +#import "AudioWaveformsPlugin.h" +#if __has_include() +#import +#else +// Support project import fallback if the generated compatibility header +// is not copied when this plugin is created as a library. +// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 +#import "audio_waveforms-Swift.h" +#endif + +@implementation AudioWaveformsPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [SwiftAudioWaveformsPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/audio_waveforms/ios/Classes/RecorderBytesStreamEngine.swift b/audio_waveforms/ios/Classes/RecorderBytesStreamEngine.swift new file mode 100644 index 0000000..68841c7 --- /dev/null +++ b/audio_waveforms/ios/Classes/RecorderBytesStreamEngine.swift @@ -0,0 +1,98 @@ +// +// RecorderBytesStreamHandler.swift +// audio_waveforms +// +// Created by Ujas Majithiya on 10/04/25. +// + +import Foundation +import AVFAudio +import Accelerate + +class RecorderBytesStreamEngine { + private var audioEngine = AVAudioEngine() + private var flutterChannel: FlutterMethodChannel + private var paused: Bool = false + private var totalFrames: AVAudioFramePosition = 0 + + init(channel: FlutterMethodChannel) { + flutterChannel = channel + } + + func attach(result: @escaping FlutterResult, sampleRate: Int) { + let inputNode = audioEngine.inputNode + inputNode.installTap(onBus: 0, bufferSize: 1024, format: nil) { (buffer, time) in + if self.paused { + return + } + if let (convertedBytes, normalizedRms) = self.convertToFlutterType(buffer) { + self.totalFrames += AVAudioFramePosition(buffer.frameLength) + let effectiveSampleRate = buffer.format.sampleRate > 0 ? buffer.format.sampleRate : Double(sampleRate) + let duration = Double(self.totalFrames) / effectiveSampleRate + let milliseconds = Int(duration * 1000) + self.sendToFlutter( + convertedBytes, + normalizedRms: normalizedRms, + milliSeconds: milliseconds + ) + } + } + do { + try audioEngine.start() + } catch { + result(FlutterError(code: Constants.audioWaveforms, message: "Error starting Audio Engine", details: error.localizedDescription)) + } + } + + func togglePause() { + paused = !paused + } + + func detach() { + totalFrames = 0 + audioEngine.inputNode.removeTap(onBus: 0) + audioEngine.stop() + } + + private func convertToFlutterType(_ buffer: AVAudioPCMBuffer) -> (FlutterStandardTypedData, Double)? { + guard let channelData = buffer.floatChannelData?[0] else { return nil } + let frameLength = Int(buffer.frameLength) + + // Convert Float32 buffer to UInt8 (byte array) + var audioSamples = [Float32](repeating: 0.0, count: frameLength) + var sumOfSquares: Float = 0.0 + + for i in 0.. RecordingSettings { + let path = json[Constants.path] as? String + let encoder = json[Constants.encoder] as? Int + let sampleRate = json[Constants.sampleRate] as? Int + let bitRate = json[Constants.bitRate] as? Int + let fileNameFormat = Constants.fileNameFormat + let useLegacy = json[Constants.useLegacyNormalization] as? Bool + let overrideAudioSession = json[Constants.overrideAudioSession] as? Bool ?? true + let linearPCMBitDepth = json[Constants.linearPCMBitDepth] as? Int ?? 16 + let linearPCMIsBigEndian = json[Constants.linearPCMIsBigEndian] as? Bool ?? false + let linearPCMIsFloat = json[Constants.linearPCMIsFloat] as? Bool ?? false + + return RecordingSettings( + path: path, + encoder: encoder, + sampleRate: sampleRate, + bitRate: bitRate, + fileNameFormat: fileNameFormat, + useLegacy: useLegacy, + overrideAudioSession: overrideAudioSession, + linearPCMBitDepth: linearPCMBitDepth, + linearPCMIsBigEndian: linearPCMIsBigEndian, + linearPCMIsFloat: linearPCMIsFloat + ) + } +} diff --git a/audio_waveforms/ios/Classes/SwiftAudioWaveformsPlugin.swift b/audio_waveforms/ios/Classes/SwiftAudioWaveformsPlugin.swift new file mode 100644 index 0000000..ddcf70f --- /dev/null +++ b/audio_waveforms/ios/Classes/SwiftAudioWaveformsPlugin.swift @@ -0,0 +1,219 @@ +import Flutter +import UIKit + +public class SwiftAudioWaveformsPlugin: NSObject, FlutterPlugin { + + let audioRecorder: AudioRecorder + var audioPlayers = [String: AudioPlayer]() + var extractors = [String: WaveformExtractor]() + var flutterChannel: FlutterMethodChannel + + init(registrar: FlutterPluginRegistrar, flutterChannel: FlutterMethodChannel) { + self.flutterChannel = flutterChannel + audioRecorder = AudioRecorder(channel: flutterChannel) + super.init() + } + + deinit { + audioPlayers.removeAll() + extractors.removeAll() + } + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: Constants.methodChannelName, binaryMessenger: registrar.messenger()) + let instance = SwiftAudioWaveformsPlugin(registrar: registrar, flutterChannel: channel) + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + let args = call.arguments as? Dictionary + switch call.method { + case Constants.startRecording: + guard let args = call.arguments as? Dictionary else { + result(FlutterError(code: Constants.audioWaveforms, message: "Invalid Arguments", details: nil)) + return + } + audioRecorder.startRecording(result, RecordingSettings.fromJson((args))) + break + case Constants.pauseRecording: + audioRecorder.pauseRecording(result) + break + case Constants.resumeRecording: + audioRecorder.resumeRecording(result) + case Constants.stopRecording: + audioRecorder.stopRecording(result) + break + case Constants.getDecibel: + audioRecorder.getDecibel(result) + break + case Constants.checkPermission: + audioRecorder.checkHasPermission(result) + break + case Constants.preparePlayer: + let key = args?[Constants.playerKey] as? String + if(key != nil){ + initPlayer(playerKey: key!) + audioPlayers[key!]?.preparePlayer(path: args?[Constants.path] as? String, + volume: args?[Constants.volume] as? Double, + updateFrequency: args?[Constants.updateFrequency] as? Int, + result: result, + overrideAudioSession: (args?[Constants.overrideAudioSession] as? Bool) ?? false) + } else { + result(FlutterError(code: Constants.audioWaveforms, message: "Can not prepare player", details: "Player key is null")) + } + break + case Constants.startPlayer: + let key = args?[Constants.playerKey] as? String + if(key != nil){ + audioPlayers[key!]?.startPlyer(result: result) + } else { + result(FlutterError(code: Constants.audioWaveforms, message: "Can not start player", details: "Player key is null")) + } + break + case Constants.finishMode: + let key = args?[Constants.playerKey] as? String + let releaseType = args?[Constants.finishType] as? Int + if(key != nil){ + audioPlayers[key!]?.setFinishMode(result: result, releaseType: releaseType) + }else{ + result(FlutterError(code: Constants.audioWaveforms, message: "Can not set release mode", details: "Player key is null")) + } + case Constants.pausePlayer: + let key = args?[Constants.playerKey] as? String + if(key != nil){ + audioPlayers[key!]?.pausePlayer() + result(true) + } else { + result(FlutterError(code: Constants.audioWaveforms, message: "Can not pause player", details: "Player key is null")) + } + break + case Constants.stopPlayer: + let key = args?[Constants.playerKey] as? String + if(key != nil){ + audioPlayers[key!]?.stopPlayer() + result(true) + } else { + result(FlutterError(code: Constants.audioWaveforms, message: "Can not stop player", details: "Player key is null")) + } + break + case Constants.releasePlayer: + let key = args?[Constants.playerKey] as? String + if(key != nil){ + audioPlayers[key!]?.release(result: result) + } + break; + case Constants.seekTo: + let key = args?[Constants.playerKey] as? String + if(key != nil){ + audioPlayers[key!]?.seekTo(args?[Constants.progress] as? Int,result) + } else { + result(FlutterError(code: Constants.audioWaveforms, message: "Can not seek to postion", details: "Player key is null")) + } + case Constants.setVolume: + let key = args?[Constants.playerKey] as? String + if(key != nil){ + audioPlayers[key!]?.setVolume(args?[Constants.volume] as? Double,result) + } else { + result(FlutterError(code: Constants.audioWaveforms, message: "Can not set volume", details: "Player key is null")) + } + case Constants.setRate: + let key = args?[Constants.playerKey] as? String + if(key != nil){ + audioPlayers[key!]?.setRate(args?[Constants.rate] as? Double,result) + } else { + result(FlutterError(code: Constants.audioWaveforms, message: "Can not set rate", details: "Player key is null")) + } + case Constants.getDuration: + let type = args?[Constants.durationType] as? Int + let key = args?[Constants.playerKey] as? String + if(key != nil){ + do{ + if(type == 0){ + try audioPlayers[key!]?.getDuration(DurationType.Current,result) + } else { + try audioPlayers[key!]?.getDuration( DurationType.Max,result) + } + } catch{ + result(FlutterError(code: "", message: "Failed to get duration", details: nil)) + } + } else { + result(FlutterError(code: Constants.audioWaveforms, message: "Can not get duration", details: "Player key is null")) + } + case Constants.stopAllPlayers: + for (playerKey,_) in audioPlayers { + audioPlayers[playerKey]?.stopPlayer() + audioPlayers[playerKey] = nil + } + result(true) + case Constants.extractWaveformData: + guard let key = args?[Constants.playerKey] as? String else { + result( + FlutterError( + code: Constants.audioWaveforms, + message: "Can not get waveform data", + details: "Waveform key is null" + ) + ) + break + } + let path = args?[Constants.path] as? String + let noOfSamples = args?[Constants.noOfSamples] as? Int + createOrUpdateExtractor( + playerKey: key, + result: result, + path: path, + noOfSamples: noOfSamples + ) + case Constants.stopExtraction: + guard let key = args?[Constants.playerKey] as? String else { + result(FlutterError(code: Constants.audioWaveforms, message: "Can not get waveform data", details: "Waveform key is null")) + break + } + extractors[key]?.cancel() + result(true) + case Constants.pauseAllPlayers: + for(playerKey,_) in audioPlayers { + audioPlayers[playerKey]?.pausePlayer() + } + result(true) + break + default: + result(FlutterMethodNotImplemented) + break + } + } + + + func initPlayer(playerKey: String) { + if audioPlayers[playerKey] == nil { + let newPlayer = AudioPlayer(plugin: self,playerKey: playerKey,channel: flutterChannel) + audioPlayers[playerKey] = newPlayer + } + } + + func createOrUpdateExtractor(playerKey: String, result: @escaping FlutterResult,path: String?, noOfSamples: Int?) { + if(!(path ?? "").isEmpty) { + do { + let audioUrl = URL.init(string: path!) + if(audioUrl == nil){ + result(FlutterError(code: Constants.audioWaveforms, message: "Failed to initialise Url from provided audio file", details: "If path contains `file://` try removing it")) + return + } + extractors[playerKey]?.cancel() + let newExtractor = try WaveformExtractor(url: audioUrl!, flutterResult: result, channel: flutterChannel) + extractors[playerKey] = newExtractor + Task { + await newExtractor + .extractWaveform(samplesPerPixel: noOfSamples, playerKey: playerKey, + onExtractionComplete: { data in + result(data) + }) + } + } catch { + result(FlutterError(code: Constants.audioWaveforms, message: "Failed to decode audio file", details: nil)) + } + } else { + result(FlutterError(code: Constants.audioWaveforms, message: "Audio file path can't be empty or null", details: nil)) + } + } +} + diff --git a/audio_waveforms/ios/Classes/Utils.swift b/audio_waveforms/ios/Classes/Utils.swift new file mode 100644 index 0000000..e224093 --- /dev/null +++ b/audio_waveforms/ios/Classes/Utils.swift @@ -0,0 +1,93 @@ +enum DurationType { + case Current + case Max +} + +struct Constants { + static let methodChannelName = "simform_audio_waveforms_plugin/methods" + static let audioWaveforms = "AudioWaveforms" + static let startRecording = "startRecording" + static let pauseRecording = "pauseRecording" + static let stopRecording = "stopRecording" + static let getDecibel = "getDecibel" + static let checkPermission = "checkPermission" + static let path = "path" + static let encoder = "encoder" + static let sampleRate = "sampleRate" + static let bitRate = "bitRate" + static let fileNameFormat = "YY-MM-dd-HH-mm-ss" + static let resumeRecording = "resumeRecording" + + static let kAudioFormatMPEG4AAC = 0 + static let kAudioFormatMPEGLayer1 = 1 + static let kAudioFormatMPEGLayer2 = 2 + static let kAudioFormatMPEGLayer3 = 3 + static let kAudioFormatMPEG4AAC_ELD = 4 + static let kAudioFormatMPEG4AAC_HE = 5 + static let kAudioFormatOpus = 6 + static let kAudioFormatAMR = 7 + static let kAudioFormatAMR_WB = 8 + static let kAudioFormatLinearPCM = 9 + static let kAudioFormatAppleLossless = 10 + static let kAudioFormatMPEG4AAC_HE_V2 = 11 + + static let readAudioFile = "readAudioFile" + static let durationEventChannel = "durationEventChannel" + static let startPlayer = "startPlayer" + static let stopPlayer = "stopPlayer" + static let pausePlayer = "pausePlayer" + static let releasePlayer = "releasePlayer" + static let seekTo = "seekTo" + static let progress = "progress" + static let setVolume = "setVolume" + static let setRate = "setRate" + static let rate = "rate" + static let volume = "volume" + static let finishMode = "finishMode" + static let finishType = "finishType" + static let getDuration = "getDuration" + static let durationType = "durationType" + static let preparePlayer = "preparePlayer" + static let onCurrentDuration = "onCurrentDuration" + static let current = "current" + static let playerKey = "playerKey" + static let stopAllPlayers = "stopAllPlayers" + static let onDidFinishPlayingAudio = "onDidFinishPlayingAudio" + static let extractWaveformData = "extractWaveformData" + static let noOfSamples = "noOfSamples" + static let onCurrentExtractedWaveformData = "onCurrentExtractedWaveformData" + static let waveformData = "waveformData" + static let onExtractionProgressUpdate = "onExtractionProgressUpdate" + static let useLegacyNormalization = "useLegacyNormalization" + static let updateFrequency = "updateFrequency" + static let overrideAudioSession = "overrideAudioSession" + static let resultFilePath = "resultFilePath" + static let resultDuration = "resultDuration" + static let linearPCMBitDepth = "linearPCMBitDepth"; + static let linearPCMIsBigEndian = "linearPCMIsBigEndian"; + static let linearPCMIsFloat = "linearPCMIsFloat"; + static let pauseAllPlayers = "pauseAllPlayers" + static let stopExtraction = "stopExtraction" + static let onAudioChunk = "onAudioChunk" + static let bytes = "bytes" + static let normalisedRms = "normalisedRms" + static let recordedDuration = "recordedDuration" + static let defaultSampleRate = 44100 +} + + +/// Creates an 2D array of floats +public typealias FloatChannelData = [[Float]] + +/// Extension to fill array with zeros +public extension RangeReplaceableCollection where Iterator.Element: ExpressibleByIntegerLiteral { + init(zeros count: Int) { + self.init(repeating: 0, count: count) + } +} + +enum FinishMode : Int{ + case loop = 0 + case pause = 1 + case stop = 2 +} diff --git a/audio_waveforms/ios/Classes/WaveformExtractor.swift b/audio_waveforms/ios/Classes/WaveformExtractor.swift new file mode 100644 index 0000000..e922d5e --- /dev/null +++ b/audio_waveforms/ios/Classes/WaveformExtractor.swift @@ -0,0 +1,209 @@ +import Accelerate +import AVFoundation + +public class WaveformExtractor { + + public private(set) var audioFile: AVAudioFile? + private var result: FlutterResult + var flutterChannel: FlutterMethodChannel + private var waveformData = Array() + var progress: Float = 0.0 + var channelCount: Int = 1 + private var currentProgress: Float = 0.0 + private let abortWaveformDataQueue = DispatchQueue( + label: "WaveformExtractor", + attributes: .concurrent + ) + + private var _abortGetWaveformData: Bool = false + + public var abortGetWaveformData: Bool { + get { _abortGetWaveformData } + set { + abortWaveformDataQueue.async(flags: .barrier) { + self._abortGetWaveformData = newValue + } + } + } + public init(url: URL, flutterResult: @escaping FlutterResult, channel: FlutterMethodChannel) throws { + result = flutterResult + self.flutterChannel = channel + do { + audioFile = try AVAudioFile(forReading: url) + } catch { + audioFile = nil + result(FlutterError(code: Constants.audioWaveforms, message: error.localizedDescription, details: "Couldn't initialise AVAudioFile from \(url.absoluteString)")) + + } + } + + deinit { + audioFile = nil + } + + public func extractWaveform( + samplesPerPixel: Int?, + offset: Int? = 0, + length: UInt? = nil, + playerKey: String, + onExtractionComplete: ([Float]?) -> Void + ) async -> Void { + guard let audioFile = audioFile else { return } + + /// Prevent division by zero, + minimum resolution + let samplesPerPixel = max(1, samplesPerPixel ?? 100) + let currentFrame = audioFile.framePosition + let totalFrames = AVAudioFrameCount(audioFile.length) + var framesPerBuffer = totalFrames / AVAudioFrameCount(samplesPerPixel) + + guard let rmsBuffer = AVAudioPCMBuffer( + pcmFormat: audioFile.processingFormat, + frameCapacity: framesPerBuffer + ) else { return } + + let channelCount = Int(audioFile.processingFormat.channelCount) + let waveformStorage = WaveformStorage( + channelCount: channelCount, + size: samplesPerPixel + ) + + let startIndex = max( + 0, offset ?? Int(currentFrame / Int64(framesPerBuffer)) + ) + let endIndex = min( + samplesPerPixel, startIndex + (length.map { Int($0) } ?? samplesPerPixel) + ) + + if startIndex > endIndex { + sendErrorToFlutter( + message: "Offset is larger than total length.", + details: "Please select less number of samples" + ) + return + } + + var startFrame: AVAudioFramePosition = offset == nil + ? currentFrame + : Int64(startIndex * Int(framesPerBuffer)) + + for i in startIndex.. totalFrames { + framesPerBuffer = totalFrames - AVAudioFrameCount(startFrame) + if framesPerBuffer <= 0 { break } + } + } + + audioFile.framePosition = currentFrame + let waveformData = await waveformStorage.getData() + let data = getChannelMean(data: waveformData) + onExtractionComplete(data); + } + + func getChannelMean(data: FloatChannelData) -> [Float] { + var resultWaveform = [Float]() + + if channelCount == 2, !data[0].isEmpty, !data[1].isEmpty { + resultWaveform = zip(data[0], data[1]).map { ($0 + $1) / 2 } + } else if !data[0].isEmpty { + resultWaveform = data[0] + } else if !data[1].isEmpty { + resultWaveform = data[1] + } else { + sendErrorToFlutter( + message: "Cannot get waveform mean", + details: "Both audio channels are null" + ) + } + return resultWaveform + } + + public func cancel() { + abortGetWaveformData = true + } + + private func sendWaveformDataToFlutter( + waveformStorage: WaveformStorage, + progress: Float, + playerKey: String + ) async { + let waveformData = await waveformStorage.getData() + let meanData = getChannelMean(data: waveformData) + + DispatchQueue.main.async { + self.flutterChannel.invokeMethod( + Constants.onCurrentExtractedWaveformData, + arguments: [ + Constants.waveformData: meanData, + Constants.progress: progress, + Constants.playerKey: playerKey + ] + ) + } + } + + private func sendErrorToFlutter(message: String, details: String? = nil) { + DispatchQueue.main.async { + self.result( + FlutterError( + code: Constants.audioWaveforms, + message: message, + details: details + ) + ) + } + } +} + +actor WaveformStorage { + private var data: [[Float]] + + init(channelCount: Int, size: Int) { + data = Array(repeating: [Float](repeating: 0, count: size), count: channelCount) + } + + func update(channel: Int, index: Int, value: Float) { + data[channel][index] = value + } + + func getData() -> [[Float]] { + return data + } +} diff --git a/audio_waveforms/ios/audio_waveforms.podspec b/audio_waveforms/ios/audio_waveforms.podspec new file mode 100644 index 0000000..db48a16 --- /dev/null +++ b/audio_waveforms/ios/audio_waveforms.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint audio_waveforms.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'audio_waveforms' + s.version = '0.0.1' + s.summary = 'A new Flutter project.' + s.description = <<-DESC +A new Flutter project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '8.0' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' +end diff --git a/audio_waveforms/lib/audio_waveforms.dart b/audio_waveforms/lib/audio_waveforms.dart new file mode 100644 index 0000000..cf60644 --- /dev/null +++ b/audio_waveforms/lib/audio_waveforms.dart @@ -0,0 +1,12 @@ +library; + +export 'src/audio_file_waveforms.dart'; +export 'src/audio_waveforms.dart'; +export 'src/base/player_wave_style.dart'; +export 'src/base/utils.dart'; +export 'src/base/wave_style.dart'; +export 'src/controllers/player_controller.dart'; +export 'src/controllers/recorder_controller.dart'; +export 'src/models/android_encoder_settings.dart'; +export 'src/models/ios_encoder_setting.dart'; +export 'src/models/recorder_settings.dart'; diff --git a/audio_waveforms/lib/src/audio_file_waveforms.dart b/audio_waveforms/lib/src/audio_file_waveforms.dart new file mode 100644 index 0000000..43b4ce9 --- /dev/null +++ b/audio_waveforms/lib/src/audio_file_waveforms.dart @@ -0,0 +1,436 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import '../audio_waveforms.dart'; +import 'base/wave_clipper.dart'; +import 'painters/player_wave_painter.dart'; + +class AudioFileWaveforms extends StatefulWidget { + /// Generate waveforms from audio file. You play those audio file using + /// [PlayerController]. + /// + /// When you play the audio file, waves change their color according to + /// how much audio has been played and how much is left. + /// + /// With seeking gesture enabled, playing audio can be seeked to + /// any position using gestures. + const AudioFileWaveforms({ + super.key, + required this.size, + required this.playerController, + this.waveformData = const [], + this.continuousWaveform = true, + this.playerWaveStyle = const PlayerWaveStyle(), + this.padding, + this.margin, + this.decoration, + this.backgroundColor, + this.animationDuration = const Duration(milliseconds: 500), + this.animationCurve = Curves.easeIn, + this.clipBehavior = Clip.none, + this.waveformType = WaveformType.long, + this.enableSeekGesture = true, + this.onDragStart, + this.onDragEnd, + this.dragUpdateDetails, + this.onTapUp, + this.seekOnTapUp = true, + this.onTapDown, + }); + + /// A size to define height and width of waveform. + final Size size; + + /// A PlayerController having different controls for audio player. + final PlayerController playerController; + + /// Directly draws waveforms from this data. Extracted waveform data + /// is ignored if waveform data is provided from this parameter. + final List waveformData; + + /// When this flag is set to true, new waves are drawn as soon as new + /// waveform data is available from [onCurrentExtractedWaveformData]. + /// If this flag is set to false then waveforms will be drawn after waveform + /// extraction is fully completed. + /// + /// This flag is ignored if [waveformData] is directly provided. + /// + /// See documentation of extractWaveformData in [PlayerController] to + /// determine which value to choose. + /// + /// Defaults to true. + final bool continuousWaveform; + + /// A PlayerWaveStyle instance controls how waveforms should look. + final PlayerWaveStyle playerWaveStyle; + + /// Provides padding around waveform. + final EdgeInsets? padding; + + /// Provides margin around waveform. + final EdgeInsets? margin; + + /// Provides box decoration to the container having waveforms. + final BoxDecoration? decoration; + + /// Color which is applied in to background of the waveform. + /// If decoration is used then use color in it. + final Color? backgroundColor; + + /// Duration for animation. Defaults to 500 milliseconds. + final Duration animationDuration; + + /// Curve for animation. Defaults to Curves.easeIn + final Curve animationCurve; + + /// A clipping behaviour which is applied to container having waveforms. + final Clip clipBehavior; + + /// Draws waveform bases on selected option. For more info, see + /// [WaveformType] documentation. + final WaveformType waveformType; + + /// Allow seeking with gestures when turned on. + final bool enableSeekGesture; + + /// Provides a callback when drag starts. + final ValueSetter? onDragStart; + + /// Provides a callback when drag ends. + final ValueSetter? onDragEnd; + + /// Provides a callback on drag updates. + final ValueSetter? dragUpdateDetails; + + /// Provides a callback when pointer has stopped contacting the screen. + /// This handler will still provide callback when [seekOnTapUp] is set to `false`. + final ValueSetter? onTapUp; + + /// When set to true, seek gesture will be performed when pointer is lifted + /// from the screen otherwise seek gesture will be performed when pointer has + /// stopped contacting the screen. + /// + /// The continuous seek gesture aren't affected by this flag. + final bool seekOnTapUp; + + /// Provides a callback when pointer has started contacting the screen. + /// This handler will still provide callback when [seekOnTapUp] is set to `true`. + final GestureTapDownCallback? onTapDown; + + @override + State createState() => _AudioFileWaveformsState(); +} + +class _AudioFileWaveformsState extends State + with SingleTickerProviderStateMixin { + late AnimationController _growingWaveController; + late Animation _growAnimation; + + double _growAnimationProgress = 0.0; + final ValueNotifier _seekProgress = ValueNotifier(0); + bool showSeekLine = false; + + late EdgeInsets? margin; + late EdgeInsets? padding; + late BoxDecoration? decoration; + late Color? backgroundColor; + late Duration? animationDuration; + late Curve? animationCurve; + late Clip? clipBehavior; + late StreamSubscription onCurrentDurationSubscription; + late StreamSubscription onCompletionSubscription; + StreamSubscription>? onCurrentExtractedWaveformData; + + double get spacing => widget.playerWaveStyle.spacing; + + double get totalWaveWidth => + widget.playerWaveStyle.spacing * _waveformData.length; + + PlayerWaveStyle get playerWaveStyle => widget.playerWaveStyle; + + PlayerController get playerController => widget.playerController; + + WaveformExtractionController get waveformExtraction => + playerController.waveformExtraction; + + @override + void initState() { + super.initState(); + _initialiseVariables(); + _growingWaveController = AnimationController( + vsync: this, + duration: widget.animationDuration, + ); + _growAnimation = CurvedAnimation( + parent: _growingWaveController, + curve: widget.animationCurve, + ); + + _growingWaveController + ..addListener(_updateGrowAnimationProgress) + ..forward(); + + onCurrentDurationSubscription = + playerController.onCurrentDurationChanged.listen((event) { + _seekProgress.value = event; + _updatePlayerPercent(); + }); + + onCompletionSubscription = playerController.onCompletion.listen((event) { + _seekProgress.value = playerController.maxDuration; + _updatePlayerPercent(); + }); + if (widget.waveformData.isNotEmpty) { + _addWaveformData(widget.waveformData); + } else { + if (waveformExtraction.waveformData.isNotEmpty) { + _addWaveformData(waveformExtraction.waveformData); + } + if (!widget.continuousWaveform) { + playerController.addListener(_addWaveformDataFromController); + } else { + onCurrentExtractedWaveformData = waveformExtraction + .onCurrentExtractedWaveformData + .listen(_addWaveformData); + } + } + } + + @override + void dispose() { + onCurrentDurationSubscription.cancel(); + onCurrentExtractedWaveformData?.cancel(); + onCompletionSubscription.cancel(); + playerController.removeListener(_addWaveformDataFromController); + _growingWaveController.dispose(); + super.dispose(); + } + + double _audioProgress = 0.0; + double _cachedAudioProgress = 0.0; + + Offset _totalBackDistance = Offset.zero; + Offset _dragOffset = Offset.zero; + + double _initialDragPosition = 0.0; + double _scrollDirection = 0.0; + + bool _isScrolled = false; + double scrollScale = 1.0; + double _proportion = 0.0; + + final List _waveformData = []; + + @override + Widget build(BuildContext context) { + return Container( + padding: widget.padding, + margin: widget.margin, + decoration: widget.decoration, + clipBehavior: widget.clipBehavior, + child: GestureDetector( + onHorizontalDragUpdate: + widget.enableSeekGesture ? _handleDragGestures : null, + onTapUp: widget.enableSeekGesture ? _handleOnTapUp : null, + onHorizontalDragStart: + widget.enableSeekGesture ? _handleHorizontalDragStart : null, + onHorizontalDragEnd: widget.enableSeekGesture ? _handleOnDragEnd : null, + onTapDown: widget.enableSeekGesture ? _handleOnTapDown : null, + child: ClipPath( + // TODO: Update extraClipperHeight when duration labels are added + clipper: WaveClipper(extraClipperHeight: 0), + child: RepaintBoundary( + child: ValueListenableBuilder( + builder: (_, __, ___) { + return CustomPaint( + isComplex: true, + painter: PlayerWavePainter( + playerWaveStyle: playerWaveStyle, + waveformData: _waveformData, + animValue: _growAnimationProgress, + totalBackDistance: _totalBackDistance, + dragOffset: _dragOffset, + audioProgress: _audioProgress, + callPushback: !_isScrolled, + pushBack: _pushBackWave, + scrollScale: scrollScale, + waveformType: widget.waveformType, + cachedAudioProgress: _cachedAudioProgress, + ), + size: widget.size, + ); + }, + valueListenable: _seekProgress, + ), + ), + ), + ), + ); + } + + void _addWaveformDataFromController() => + _addWaveformData(waveformExtraction.waveformData); + + void _updateGrowAnimationProgress() { + if (mounted) { + setState(() { + _growAnimationProgress = _growAnimation.value; + }); + } + } + + void _handleOnDragEnd(DragEndDetails dragEndDetails) { + _isScrolled = false; + scrollScale = 1.0; + if (mounted) setState(() {}); + + if (widget.waveformType.isLong) { + playerController.seekTo( + (playerController.maxDuration * _proportion).toInt(), + ); + } + widget.onDragEnd?.call(dragEndDetails); + } + + void _addWaveformData(List data) { + _waveformData + ..clear() + ..addAll(data); + if (mounted) setState(() {}); + } + + void _handleDragGestures(DragUpdateDetails details) { + switch (widget.waveformType) { + case WaveformType.fitWidth: + _handleScrubberSeekUpdate(details); + break; + case WaveformType.long: + _handleScrollUpdate(details); + break; + } + + widget.dragUpdateDetails?.call(details); + } + + /// This method handles continues seek gesture + void _handleScrubberSeekUpdate(DragUpdateDetails details) { + final localPosition = details.localPosition.dx; + + _proportion = localPosition <= 0 ? 0 : localPosition / widget.size.width; + var seekPosition = playerController.maxDuration * _proportion; + + playerController.seekTo(seekPosition.toInt()); + } + + /// This method handles tap seek gesture + void _handleOnTapUp(TapUpDetails details) { + widget.onTapUp?.call(details); + if (!widget.seekOnTapUp) return; + _proportion = details.localPosition.dx / widget.size.width; + var seekPosition = playerController.maxDuration * _proportion; + + playerController.seekTo(seekPosition.toInt()); + } + + /// This method handles tap seek gesture + void _handleOnTapDown(TapDownDetails details) { + widget.onTapDown?.call(details); + if (widget.seekOnTapUp) return; + _proportion = details.localPosition.dx / widget.size.width; + var seekPosition = playerController.maxDuration * _proportion; + + playerController.seekTo(seekPosition.toInt()); + } + + ///This method handles horizontal scrolling of the wave + void _handleScrollUpdate(DragUpdateDetails details) { + // Direction of the scroll. Negative value indicates scroll left to right + // and positive value indicates scroll right to left + _scrollDirection = details.localPosition.dx - _initialDragPosition; + playerController.setRefresh(false); + _isScrolled = true; + + scrollScale = playerWaveStyle.scrollScale; + + final spacing = playerWaveStyle.spacing; + + // Update the drag offset based on scroll direction and thresholds. + final currentPosition = -_totalBackDistance.dx + _dragOffset.dx; + final updatedPosition = currentPosition + details.delta.dx; + + // left to right + if (updatedPosition + (spacing) < spacing / 2 && _scrollDirection > 0) { + _dragOffset += details.delta; + } + + // right to left + else if (currentPosition + totalWaveWidth + details.delta.dx > + (-spacing / 2) && + _scrollDirection < 0) { + _dragOffset += details.delta; + } + + // Indicates location of first wave + var start = currentPosition - (spacing / 2); + + _proportion = _scrollDirection < 0 + ? (start.abs() + details.delta.dx) / totalWaveWidth + : (details.delta.dx - start - spacing) / totalWaveWidth; + if (mounted) setState(() {}); + } + + ///This will help-out to determine direction of the scroll + void _handleHorizontalDragStart(DragStartDetails details) { + _initialDragPosition = details.localPosition.dx; + widget.onDragStart?.call(details); + } + + /// This initialises variable in [initState] so that everytime current duration + /// gets updated it doesn't re assign them to same values. + void _initialiseVariables() { + if (waveformExtraction.waveformData.isEmpty) { + waveformExtraction.waveformData.addAll(widget.waveformData); + } + showSeekLine = false; + margin = widget.margin; + padding = widget.padding; + decoration = widget.decoration; + backgroundColor = widget.backgroundColor; + animationDuration = widget.animationDuration; + animationCurve = widget.animationCurve; + clipBehavior = widget.clipBehavior; + } + + /// calculates seek progress + void _updatePlayerPercent() { + if (playerController.maxDuration == 0) return; + _audioProgress = _seekProgress.value / playerController.maxDuration; + } + + ///This will handle pushing back the wave when it reaches to middle/end of the + ///given size.width. + /// + ///This will also handle refreshing the wave after scrolled + void _pushBackWave() { + if (!_isScrolled && widget.waveformType.isLong) { + _totalBackDistance = Offset( + (playerWaveStyle.spacing * _audioProgress * _waveformData.length) + + playerWaveStyle.spacing + + _dragOffset.dx, + 0.0, + ); + } + if (playerController.shouldClearLabels) { + _initialDragPosition = 0.0; + _totalBackDistance = Offset.zero; + _dragOffset = Offset.zero; + } + _cachedAudioProgress = _audioProgress; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() {}); + } + }); + } +} diff --git a/audio_waveforms/lib/src/audio_waveforms.dart b/audio_waveforms/lib/src/audio_waveforms.dart new file mode 100644 index 0000000..7943cc5 --- /dev/null +++ b/audio_waveforms/lib/src/audio_waveforms.dart @@ -0,0 +1,309 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import '/audio_waveforms.dart'; +import 'base/label.dart'; +import 'base/wave_clipper.dart'; +import 'painters/recorder_wave_painter.dart'; + +class AudioWaveforms extends StatefulWidget { + const AudioWaveforms({ + super.key, + required this.size, + required this.recorderController, + this.waveStyle = const WaveStyle(), + this.enableGesture = false, + this.padding, + this.margin, + this.decoration, + this.backgroundColor, + this.shouldCalculateScrolledPosition = false, + }); + + final Size size; + final RecorderController recorderController; + final WaveStyle waveStyle; + final EdgeInsets? padding; + final EdgeInsets? margin; + final BoxDecoration? decoration; + final Color? backgroundColor; + final bool enableGesture; + final bool shouldCalculateScrolledPosition; + + @override + State createState() => _AudioWaveformsState(); +} + +class _AudioWaveformsState extends State { + bool _isScrolled = false; + + /// Tracks the total horizontal offset applied when the waveform is shifted backward. + Offset _totalBackDistance = Offset.zero; + Offset _dragOffset = Offset.zero; + + double _initialOffsetPosition = 0.0; + late double _initialPosition; + Duration currentlyRecordedDuration = Duration.zero; + late StreamSubscription streamSubscription; + + late final Size _size; + late final WaveStyle _waveStyle; + late final RecorderController _recorderController; + late final bool _isRtl = widget.waveStyle.waveformRenderMode.isRtl; + + /// Duration timestamp labels shown on the waveform, added every second during recording. + final List