diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt index 48e1a3358..e5d5d1d73 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt @@ -84,6 +84,8 @@ internal class TiffFetcher(val model: TiffImage, val width: Int, val height: Int } try { val bitmap: Bitmap? = TiffBitmapFactory.decodeFileDescriptor(fd, options) + // calling `TiffBitmapFactory.closeFd(fd)` after decoding yields a segmentation fault + if (bitmap == null) { callback.onLoadFailed(Exception("Decoding full TIFF yielded null bitmap")) } else if (customSize) { diff --git a/android/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterfaceFork.java b/android/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterfaceFork.java index 2f0f4566a..7643c4193 100644 --- a/android/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterfaceFork.java +++ b/android/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterfaceFork.java @@ -91,7 +91,7 @@ import java.util.regex.Pattern; import java.util.zip.CRC32; /* - * Forked from 'androidx.exifinterface:exifinterface:1.4.0-beta01' on 2025/01/21 + * Forked from 'androidx.exifinterface:exifinterface:1.4.0' * Named differently to let ExifInterface be loaded as subdependency. * cf https://maven.google.com/web/index.html?q=exifinterface#androidx.exifinterface:exifinterface * cf https://github.com/androidx/androidx/tree/androidx-main/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media diff --git a/lib/image_providers/uri_image_provider.dart b/lib/image_providers/uri_image_provider.dart index 20da0de75..e7ae98af9 100644 --- a/lib/image_providers/uri_image_provider.dart +++ b/lib/image_providers/uri_image_provider.dart @@ -51,15 +51,15 @@ class UriImage extends ImageProvider with EquatableMixin { // prefer Flutter for animation, as well as niche formats and SVG // prefer Android for the rest, to rely on device codecs and handle config conversion - bool _canDecodeWithFlutter(String mimeType, bool isAnimated) { - switch(mimeType) { + bool _preferPlatformDecoding(String mimeType, bool isAnimated) { + switch (mimeType) { case MimeTypes.bmp: case MimeTypes.wbmp: case MimeTypes.ico: case MimeTypes.svg: - return true; + return false; default: - return isAnimated; + return !isAnimated; } } @@ -82,22 +82,23 @@ class UriImage extends ImageProvider with EquatableMixin { }, ); try { - if (_canDecodeWithFlutter(mimeType, isAnimated)) { - // get original media bytes from platform, and rely on a codec instantiated by `ImageProvider` - final bytes = await mediaFetchService.getEncodedImage(request); - if (bytes.isEmpty) { - throw UnreportedStateError('$uri ($mimeType) image loading failed'); - } - final buffer = await ui.ImmutableBuffer.fromUint8List(bytes); - return await decode(buffer); - } else { + if (_preferPlatformDecoding(mimeType, isAnimated)) { // get decoded media bytes from platform, and rely on a codec instantiated from raw bytes final descriptor = await mediaFetchService.getDecodedImage(request); - if (descriptor == null) { - throw UnreportedStateError('$uri ($mimeType) image loading failed'); + if (descriptor != null) { + return descriptor.instantiateCodec(); } - return descriptor.instantiateCodec(); + debugPrint('failed to load decoded image for mimeType=$mimeType uri=$uri, falling back to loading encoded image'); } + + // fallback + // get original media bytes from platform, and rely on a codec instantiated by `ImageProvider` + final bytes = await mediaFetchService.getEncodedImage(request); + if (bytes.isEmpty) { + throw UnreportedStateError('$uri ($mimeType) image loading failed'); + } + final buffer = await ui.ImmutableBuffer.fromUint8List(bytes); + return await decode(buffer); } catch (error) { // loading may fail if the provided MIME type is incorrect (e.g. the Media Store may report a JPEG as a TIFF) debugPrint('$runtimeType _loadAsync failed with mimeType=$mimeType, uri=$uri, error=$error'); diff --git a/lib/widgets/settings/video/control_actions.dart b/lib/widgets/settings/video/control_actions.dart index 6919bb1fe..62dbb20f6 100644 --- a/lib/widgets/settings/video/control_actions.dart +++ b/lib/widgets/settings/video/control_actions.dart @@ -20,7 +20,7 @@ class VideoControlButtonsPage extends StatelessWidget { return AvesScaffold( appBar: AppBar( automaticallyImplyLeading: !settings.useTvLayout, - title: Text(context.l10n.settingsVideoControlsPageTitle), + title: Text(context.l10n.settingsVideoButtonsTile), ), body: SafeArea( child: Selector>( diff --git a/lib/widgets/viewer/visual/video/cover.dart b/lib/widgets/viewer/visual/video/cover.dart index 0a56c8ff3..ae7da3a51 100644 --- a/lib/widgets/viewer/visual/video/cover.dart +++ b/lib/widgets/viewer/visual/video/cover.dart @@ -58,7 +58,7 @@ class _VideoCoverState extends State { Size get videoDisplaySize => widget.videoDisplaySize; // use the high res photo as cover for the video part of a motion photo - ImageProvider get videoCoverUriImage => mainEntry.isMotionPhoto ? mainEntry.uriImage : entry.uriImage; + ImageProvider get videoCoverUriImage => (mainEntry.isMotionPhoto ? mainEntry : entry).uriImage; @override void initState() {