diff --git a/android/build.gradle.kts b/android/build.gradle.kts index f82adc9..441eb71 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -16,23 +16,6 @@ subprojects { project.layout.buildDirectory.value(newSubprojectBuildDir) } -subprojects { - if (name == "flutter_avif_android") { - tasks.withType().configureEach { - exclude("**/FlutterAvifPlugin.kt") - } - afterEvaluate { - extensions.configure { - compileSdk = 36 - compileOptions { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 - } - } - } - } -} - subprojects { project.evaluationDependsOn(":app") } diff --git a/lib/helpers/mxc_image.dart b/lib/helpers/mxc_image.dart index 1623029..4013397 100644 --- a/lib/helpers/mxc_image.dart +++ b/lib/helpers/mxc_image.dart @@ -1,8 +1,10 @@ +import "dart:async"; import "dart:io"; +import "dart:isolate"; import "dart:ui" as ui; +import "package:avif_native/avif_native.dart"; import "package:flutter/widgets.dart"; -import "package:flutter_avif/flutter_avif.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:nexus/controllers/client.dart"; import "package:nexus/models/requests/download_media.dart"; @@ -24,13 +26,9 @@ class MxcImage(final WidgetRef ref, final DownloadMediaRequest request) final bytes = await File(file.path).readAsBytes(); // Flutter can't decode AVIF on Linux/Windows (or older Android/iOS), so we do it ourselves - final avifType = isAvifFile(bytes.sublist(0, 16.clamp(0, bytes.length))); - if (avifType != .unknown) { - final codec = avifType == .avif - ? SingleFrameAvifCodec(bytes: bytes) - : MultiFrameAvifCodec(key: hashCode, avifBytes: bytes); - await codec.ready(); - return AvifCodecAdapter(codec); + if (String.fromCharCodes(bytes.skip(4).take(8)) + case "ftypavif" || "ftypavis") { + return AvifCodec(await Isolate.run(() => decodeAvif(bytes))); } return decode(await .fromUint8List(bytes)); @@ -44,21 +42,33 @@ class MxcImage(final WidgetRef ref, final DownloadMediaRequest request) int get hashCode => request.hashCode; } -class AvifCodecAdapter(final AvifCodec codec) implements ui.Codec { - @override - int get frameCount => codec.frameCount; +class AvifCodec(final AvifImageData data) implements ui.Codec { + var _frameIndex = 0; @override - int get repetitionCount => frameCount > 1 ? -1 : 0; + int get frameCount => data.frames.length; + + // libavif uses -2 for unknown, which we treat as infinite like browsers do + @override + int get repetitionCount => + data.repetitionCount < 0 ? -1 : data.repetitionCount; @override - Future getNextFrame() async { - final frame = await codec.getNextFrame(); - return AvifFrameInfo(frame.duration, frame.image); + Future getNextFrame() { + final frame = data.frames[_frameIndex++ % frameCount]; + final completer = Completer(); + ui.decodeImageFromPixels( + frame.rgba, + frame.width, + frame.height, + .rgba8888, + (image) => completer.complete(AvifFrameInfo(frame.duration, image)), + ); + return completer.future; } @override - void dispose() => codec.dispose(); + void dispose() {} } class AvifFrameInfo( diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index d95018b..b2ee366 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -28,9 +27,6 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); - g_autoptr(FlPluginRegistrar) flutter_avif_linux_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterAvifLinuxPlugin"); - flutter_avif_linux_plugin_register_with_registrar(flutter_avif_linux_registrar); g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin"); media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 7d212bd..eea220b 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -6,7 +6,6 @@ list(APPEND FLUTTER_PLUGIN_LIST app_links_linux dynamic_color file_selector_linux - flutter_avif_linux media_kit_libs_linux media_kit_video pasteboard diff --git a/linux/nix/devshell.nix b/linux/nix/devshell.nix index bfa5408..6b2b94d 100644 --- a/linux/nix/devshell.nix +++ b/linux/nix/devshell.nix @@ -9,7 +9,6 @@ let ]; cmakeVersions = [ "3.22.1" ]; platformVersions = [ - "31" "34" "36" ]; @@ -48,17 +47,7 @@ pkgs.mkShell { GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_HOME}/build-tools/36.0.0/aapt2"; } - // lib.optionalAttrs pkgs.stdenv.isLinux ( - let - arch = if pkgs.stdenv.hostPlatform.isAarch64 then "arm64" else "x64"; - in - { - LD_LIBRARY_PATH = lib.concatStringsSep ":" [ - "./build/native_assets/linux" - "./build/linux/${arch}/debug/bundle/lib" - "./build/linux/${arch}/release/bundle/lib" - (lib.makeLibraryPath [ pkgs.zlib ]) - ]; - } - ); + // lib.optionalAttrs pkgs.stdenv.isLinux { + LD_LIBRARY_PATH = "./build/native_assets/linux:${lib.makeLibraryPath [ pkgs.zlib ]}"; + }; } diff --git a/linux/nix/pkg/avif_native.nix b/linux/nix/pkg/avif_native.nix new file mode 100644 index 0000000..16d0fc0 --- /dev/null +++ b/linux/nix/pkg/avif_native.nix @@ -0,0 +1,49 @@ +# avif_native's build hook downloads its prebuilt library, which the sandbox +# can't do, so bundle it and point the manifest at the local copy instead +{ + stdenv, + fetchurl, + jq, +}: +let + artifacts = { + x86_64-linux = { + arch = "x64"; + sha256 = "252b064f8ff4f0f265bc44294d4a7ed703dd7199688942c272dd8aa8d3039d5a"; + }; + aarch64-linux = { + arch = "arm64"; + sha256 = "cdcf17cfd18b9b0f965b72615ddf11246121114577dc570d0bba25964e56c726"; + }; + }; + inherit (artifacts.${stdenv.hostPlatform.system}) arch sha256; + key = "linux-${arch}-decode-dav1d"; +in +{ version, src, ... }: +stdenv.mkDerivation { + pname = "avif_native"; + inherit version src; + inherit (src) passthru; + + nativeBuildInputs = [ jq ]; + + library = fetchurl { + url = "https://github.com/Zhangw1998/avif_native/releases/download/native-v0.1.0/${key}-libavif_native.so.gz"; + inherit sha256; + }; + + # The hook checksums the library, so it must stay byte-identical + dontFixup = true; + + installPhase = '' + runHook preInstall + + cp -r . $out + chmod -R u+w $out + gunzip -c $library > $out/native/libavif_native.so + jq '.artifacts["${key}"].path = "libavif_native.so"' \ + native/manifest.json > $out/native/manifest.json + + runHook postInstall + ''; +} diff --git a/linux/nix/pkg/default.nix b/linux/nix/pkg/default.nix index 2c66fb8..f2bbbff 100644 --- a/linux/nix/pkg/default.nix +++ b/linux/nix/pkg/default.nix @@ -34,6 +34,8 @@ flutter.buildFlutterApplication { autoPubspecLock = src + "/pubspec.lock"; + customSourceBuilders.avif_native = callPackage ./avif_native.nix { }; + gitHashes = { linkify = "sha256-TpMD6+0zyY6i9l+6d8ErnVufmepCv362rCtnbOht/z4="; fab_m3e = "sha256-5V9m6yY0jimx2gh7lQHYg4HDKQ4Wrl0ATmBhw5DkQMI="; diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index c801836..2a15f97 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,7 +8,6 @@ import Foundation import app_links import dynamic_color import file_selector_macos -import flutter_avif_macos import flutter_local_notifications import media_kit_libs_macos_video import media_kit_video @@ -16,7 +15,6 @@ import package_info_plus import pasteboard import screen_retriever_macos import shared_preferences_foundation -import sqflite_darwin import url_launcher_macos import wakelock_plus import webcrypto @@ -26,7 +24,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) - FlutterAvifPlugin.register(with: registry.registrar(forPlugin: "FlutterAvifPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) @@ -34,7 +31,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) - SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) WebcryptoPlugin.register(with: registry.registrar(forPlugin: "WebcryptoPlugin")) diff --git a/pubspec.lock b/pubspec.lock index bf9c326..1958e04 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -105,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.1" + avif_native: + dependency: "direct main" + description: + name: avif_native + sha256: "0a374a7ddc6e48d671792bd33efd9f46f199389cdcd42561210bd886facbc62e" + url: "https://pub.dev" + source: hosted + version: "0.1.1" boolean_selector: dependency: transitive description: @@ -313,14 +321,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" - exif: - dependency: transitive - description: - name: exif - sha256: a7980fdb3b7ffcd0b035e5b8a5e1eef7cadfe90ea6a4e85ebb62f87b96c7a172 - url: "https://pub.dev" - source: hosted - version: "3.3.0" fab_m3e: dependency: "direct overridden" description: @@ -455,70 +455,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_avif: - dependency: "direct main" - description: - name: flutter_avif - sha256: "6035f073189c1ae134affa73338c2eca79bf412e9abdb8f8628fdefcccff4444" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - flutter_avif_android: - dependency: transitive - description: - name: flutter_avif_android - sha256: "1da135ea0d74225fae3154f954f9ba4cf7aa5e56b30d9851fcfc0ddecab9d8c5" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - flutter_avif_ios: - dependency: transitive - description: - name: flutter_avif_ios - sha256: "3daaa599d8fe0193d3ade6cafa1fd4f166d4063b8d499c7c311ce1d170b2759f" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - flutter_avif_linux: - dependency: transitive - description: - name: flutter_avif_linux - sha256: dd6179edca12c720761b3acc678d7f15208eb8647adbac5a6a90f52d4ba56e2d - url: "https://pub.dev" - source: hosted - version: "3.1.0" - flutter_avif_macos: - dependency: transitive - description: - name: flutter_avif_macos - sha256: "8e39f365dfc5713d4527f07a92399184857ea30eced85f82171bfd0a45e2c73b" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - flutter_avif_platform_interface: - dependency: transitive - description: - name: flutter_avif_platform_interface - sha256: f5c110bdb7de2d4ab4902bfe2ec331fe24bc19b6d54f00ac4fa165effc0edcda - url: "https://pub.dev" - source: hosted - version: "3.1.0" - flutter_avif_web: - dependency: transitive - description: - name: flutter_avif_web - sha256: db2ff395b08cbfe9116e48ac65c428ba21a9159972ea019f8d7be580ff6cf6e8 - url: "https://pub.dev" - source: hosted - version: "3.1.0" - flutter_avif_windows: - dependency: transitive - description: - name: flutter_avif_windows - sha256: e0c7722df305a6621f4ecdd83098025c705bcf0e8ce9519e03c6f515abcf351d - url: "https://pub.dev" - source: hosted - version: "3.1.0" flutter_blurhash: dependency: "direct main" description: @@ -527,14 +463,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.1" - flutter_cache_manager: - dependency: transitive - description: - name: flutter_cache_manager - sha256: f94fc6f5480977e3eb7b622ed488f671ba96c971bd0e274e6bd0f0ba9b044816 - url: "https://pub.dev" - source: hosted - version: "3.4.5" flutter_file_dialog: dependency: "direct main" description: @@ -1265,14 +1193,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.6" - protobuf: - dependency: transitive - description: - name: protobuf - sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" - url: "https://pub.dev" - source: hosted - version: "3.1.0" pub_semver: dependency: transitive description: @@ -1486,54 +1406,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.2" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - sqflite: - dependency: transitive - description: - name: sqflite - sha256: "273218a1678360a3127fcf15e543381d9b813d413d783bc6e9a53eed4d2eb8fe" - url: "https://pub.dev" - source: hosted - version: "2.4.4+2" - sqflite_android: - dependency: transitive - description: - name: sqflite_android - sha256: e4f659c14bdd14464127fcd234a7a96f61fe0f539b8568e5a38f99f5a568a835 - url: "https://pub.dev" - source: hosted - version: "2.4.4+1" - sqflite_common: - dependency: transitive - description: - name: sqflite_common - sha256: b80e94ff0d551d5f1f8c7d14348d29b576cca3ae30f6e195a7c380552c32b1e0 - url: "https://pub.dev" - source: hosted - version: "2.5.14" - sqflite_darwin: - dependency: transitive - description: - name: sqflite_darwin - sha256: "8de68fc306040f992aaf378750c16f95e4298c4bb150dd45091d75e42b1f08ee" - url: "https://pub.dev" - source: hosted - version: "2.4.4+1" - sqflite_platform_interface: - dependency: transitive - description: - name: sqflite_platform_interface - sha256: c9177b2f3150ed0647b90a94307c28452fe0a5401863044216aed83addd6c7c5 - url: "https://pub.dev" - source: hosted - version: "2.4.2" stack_trace: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b276bea..c93eaac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -109,7 +109,7 @@ dependencies: pasteboard: 0.5.0 flutter_file_dialog: 3.3.3 mime: 2.1.0 - flutter_avif: 3.1.0 + avif_native: 0.1.1 dev_dependencies: build_runner: 2.16.2 flutter_lints: 6.0.0 @@ -133,3 +133,8 @@ flutter_launcher_icons: macos: generate: true image_path: assets/mobile.png + +hooks: + user_defines: + avif_native: + features: decode diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 13fb733..95357c4 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -25,8 +24,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); - FlutterAvifWindowsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("FlutterAvifWindowsPlugin")); MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); MediaKitVideoPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 1822f53..a7b2473 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -6,7 +6,6 @@ list(APPEND FLUTTER_PLUGIN_LIST app_links dynamic_color file_selector_windows - flutter_avif_windows media_kit_libs_windows_video media_kit_video pasteboard