various fixes

This commit is contained in:
Thibault Deckers 2020-04-07 17:46:23 +09:00
parent 01d2d7ea2f
commit a2fc8bfd2f
10 changed files with 63 additions and 51 deletions

View file

@ -22,6 +22,7 @@ import com.bumptech.glide.signature.ObjectKey;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer; import java.util.function.Consumer;
import deckers.thibault.aves.decoder.VideoThumbnail; import deckers.thibault.aves.decoder.VideoThumbnail;
@ -69,10 +70,28 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
Params p = params[0]; Params p = params[0];
Bitmap bitmap = null; Bitmap bitmap = null;
if (!this.isCancelled()) { if (!this.isCancelled()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { Exception exception = null;
bitmap = getThumbnailBytesByResolver(p); try {
} else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
bitmap = getThumbnailBytesByMediaStore(p); bitmap = getThumbnailBytesByResolver(p);
} else {
bitmap = getThumbnailBytesByMediaStore(p);
}
} catch (Exception e) {
exception = e;
}
// fallback if the native methods failed
try {
if (bitmap == null) {
bitmap = getThumbnailByGlide(p);
}
} catch (Exception e) {
exception = e;
}
if (bitmap == null) {
Log.e(LOG_TAG, "failed to load thumbnail for uri=" + p.entry.uri + ", path=" + p.entry.path, exception);
} }
} else { } else {
Log.d(LOG_TAG, "getThumbnail with uri=" + p.entry.uri + " cancelled"); Log.d(LOG_TAG, "getThumbnail with uri=" + p.entry.uri + " cancelled");
@ -89,18 +108,13 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
} }
@TargetApi(Build.VERSION_CODES.Q) @TargetApi(Build.VERSION_CODES.Q)
private Bitmap getThumbnailBytesByResolver(Params params) { private Bitmap getThumbnailBytesByResolver(Params params) throws IOException {
ImageEntry entry = params.entry; ImageEntry entry = params.entry;
int width = params.width; int width = params.width;
int height = params.height; int height = params.height;
ContentResolver resolver = activity.getContentResolver(); ContentResolver resolver = activity.getContentResolver();
try { return resolver.loadThumbnail(entry.uri, new Size(width, height), null);
return resolver.loadThumbnail(entry.uri, new Size(width, height), null);
} catch (IOException e) {
Log.e(LOG_TAG, "failed to load thumbnail for uri=" + entry.uri + ", path=" + entry.path + ", width=" + width + ", height=" + height, e);
}
return null;
} }
private Bitmap getThumbnailBytesByMediaStore(Params params) { private Bitmap getThumbnailBytesByMediaStore(Params params) {
@ -108,26 +122,21 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
long contentId = ContentUris.parseId(entry.uri); long contentId = ContentUris.parseId(entry.uri);
ContentResolver resolver = activity.getContentResolver(); ContentResolver resolver = activity.getContentResolver();
try { if (entry.isVideo()) {
if (entry.isVideo()) { return MediaStore.Video.Thumbnails.getThumbnail(resolver, contentId, MediaStore.Video.Thumbnails.MINI_KIND, null);
return MediaStore.Video.Thumbnails.getThumbnail(resolver, contentId, MediaStore.Video.Thumbnails.MINI_KIND, null); } else {
} else { Bitmap bitmap = MediaStore.Images.Thumbnails.getThumbnail(resolver, contentId, MediaStore.Images.Thumbnails.MINI_KIND, null);
Bitmap bitmap = MediaStore.Images.Thumbnails.getThumbnail(resolver, contentId, MediaStore.Images.Thumbnails.MINI_KIND, null); // from Android Q, returned thumbnail is already rotated according to EXIF orientation
// from Android Q, returned thumbnail is already rotated according to EXIF orientation if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && bitmap != null && entry.orientationDegrees != 0) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && bitmap != null && entry.orientationDegrees != 0) { Matrix matrix = new Matrix();
Matrix matrix = new Matrix(); matrix.postRotate(entry.orientationDegrees);
matrix.postRotate(entry.orientationDegrees); bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
return bitmap;
} }
} catch (Exception e) { return bitmap;
Log.e(LOG_TAG, "failed to get thumbnail for uri=" + entry.uri, e);
} }
return null;
} }
private Bitmap getThumbnailByGlide(Params params) { private Bitmap getThumbnailByGlide(Params params) throws ExecutionException, InterruptedException {
ImageEntry entry = params.entry; ImageEntry entry = params.entry;
int width = params.width; int width = params.width;
int height = params.height; int height = params.height;
@ -158,13 +167,9 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
try { try {
return target.get(); return target.get();
} catch (InterruptedException e) { } finally {
Log.d(LOG_TAG, "getThumbnail with uri=" + entry.uri + " interrupted"); Glide.with(activity).clear(target);
} catch (Exception e) {
e.printStackTrace();
} }
Glide.with(activity).clear(target);
return null;
} }
@Override @Override

View file

@ -31,9 +31,11 @@ class VideoThumbnailFetcher implements DataFetcher<InputStream> {
} else { } else {
// not ideal: bitmap -> byte[] -> bitmap // not ideal: bitmap -> byte[] -> bitmap
// but simple fallback and we cache result // but simple fallback and we cache result
Bitmap bitmap = retriever.getFrameAtTime();
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos); Bitmap bitmap = retriever.getFrameAtTime();
if (bitmap != null) {
bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos);
}
callback.onDataReady(new ByteArrayInputStream(bos.toByteArray())); callback.onDataReady(new ByteArrayInputStream(bos.toByteArray()));
} }
} catch (Exception ex) { } catch (Exception ex) {

View file

@ -24,9 +24,9 @@ class MimeFilter extends CollectionFilter {
_label ??= lowMime.split('/')[0].toUpperCase(); _label ??= lowMime.split('/')[0].toUpperCase();
} else { } else {
_filter = (entry) => entry.mimeType == lowMime; _filter = (entry) => entry.mimeType == lowMime;
if (lowMime == MimeTypes.MIME_GIF) { if (lowMime == MimeTypes.GIF) {
_icon = OMIcons.gif; _icon = OMIcons.gif;
} else if (lowMime == MimeTypes.MIME_SVG) { } else if (lowMime == MimeTypes.SVG) {
_label = 'SVG'; _label = 'SVG';
} }
_label ??= lowMime.split('/')[1].toUpperCase(); _label ??= lowMime.split('/')[1].toUpperCase();

View file

@ -105,9 +105,9 @@ class ImageEntry {
bool get isFavourite => favourites.isFavourite(this); bool get isFavourite => favourites.isFavourite(this);
bool get isGif => mimeType == MimeTypes.MIME_GIF; bool get isGif => mimeType == MimeTypes.GIF;
bool get isSvg => mimeType == MimeTypes.MIME_SVG; bool get isSvg => mimeType == MimeTypes.SVG;
bool get isVideo => mimeType.startsWith('video'); bool get isVideo => mimeType.startsWith('video');
@ -117,7 +117,7 @@ class ImageEntry {
bool get canPrint => !isVideo; bool get canPrint => !isVideo;
bool get canRotate => canEdit && (mimeType == MimeTypes.MIME_JPEG || mimeType == MimeTypes.MIME_PNG); bool get canRotate => canEdit && (mimeType == MimeTypes.JPEG || mimeType == MimeTypes.PNG);
bool get rotated => ((isVideo && isCatalogued) ? _catalogMetadata.videoRotation : orientationDegrees) % 180 == 90; bool get rotated => ((isVideo && isCatalogued) ? _catalogMetadata.videoRotation : orientationDegrees) % 180 == 90;

View file

@ -1,7 +1,11 @@
class MimeTypes { class MimeTypes {
static const String MIME_GIF = 'image/gif'; static const String GIF = 'image/gif';
static const String MIME_JPEG = 'image/jpeg'; static const String JPEG = 'image/jpeg';
static const String MIME_PNG = 'image/png'; static const String PNG = 'image/png';
static const String MIME_SVG = 'image/svg+xml'; static const String SVG = 'image/svg+xml';
static const String MIME_VIDEO = 'video/*';
static const String ANY_VIDEO = 'video/*';
static const String AVI = 'video/avi';
static const String MP2T = 'video/mp2t'; // .m2ts
static const String MP4 = 'video/mp4';
} }

View file

@ -79,13 +79,13 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
source: source, source: source,
leading: const Icon(OMIcons.movie), leading: const Icon(OMIcons.movie),
title: 'Videos', title: 'Videos',
filter: MimeFilter(MimeTypes.MIME_VIDEO), filter: MimeFilter(MimeTypes.ANY_VIDEO),
); );
final gifEntry = _FilteredCollectionNavTile( final gifEntry = _FilteredCollectionNavTile(
source: source, source: source,
leading: const Icon(OMIcons.gif), leading: const Icon(OMIcons.gif),
title: 'GIFs', title: 'GIFs',
filter: MimeFilter(MimeTypes.MIME_GIF), filter: MimeFilter(MimeTypes.GIF),
); );
final favouriteEntry = _FilteredCollectionNavTile( final favouriteEntry = _FilteredCollectionNavTile(
source: source, source: source,

View file

@ -63,7 +63,7 @@ class ImageSearchDelegate extends SearchDelegate<CollectionFilter> {
children: [ children: [
_buildFilterRow( _buildFilterRow(
context: context, context: context,
filters: [FavouriteFilter(), MimeFilter(MimeTypes.MIME_VIDEO), MimeFilter(MimeTypes.MIME_GIF), MimeFilter(MimeTypes.MIME_SVG)].where((f) => containQuery(f.label)), filters: [FavouriteFilter(), MimeFilter(MimeTypes.ANY_VIDEO), MimeFilter(MimeTypes.GIF), MimeFilter(MimeTypes.SVG)].where((f) => containQuery(f.label)),
), ),
_buildFilterRow( _buildFilterRow(
context: context, context: context,

View file

@ -114,7 +114,7 @@ class ThumbnailCollection extends StatelessWidget {
icon: OMIcons.favoriteBorder, icon: OMIcons.favoriteBorder,
text: 'No favourites!', text: 'No favourites!',
) )
: collection.filters.any((filter) => filter is MimeFilter && filter.mime == MimeTypes.MIME_VIDEO) : collection.filters.any((filter) => filter is MimeFilter && filter.mime == MimeTypes.ANY_VIDEO)
? const EmptyContent( ? const EmptyContent(
icon: OMIcons.movie, icon: OMIcons.movie,
) )

View file

@ -319,6 +319,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
if (controllerEntry != null) { if (controllerEntry != null) {
_videoControllers.remove(controllerEntry); _videoControllers.remove(controllerEntry);
} else { } else {
// unsupported by video_player 0.10.8+2 (backed by ExoPlayer): AVI
final controller = VideoPlayerController.uri(uri)..initialize(); final controller = VideoPlayerController.uri(uri)..initialize();
controllerEntry = Tuple2(uri, controller); controllerEntry = Tuple2(uri, controller);
} }

View file

@ -50,8 +50,8 @@ class BasicSection extends StatelessWidget {
builder: (context, isFavourite, child) { builder: (context, isFavourite, child) {
final album = entry.directory; final album = entry.directory;
final filters = [ final filters = [
if (entry.isVideo) MimeFilter(MimeTypes.MIME_VIDEO), if (entry.isVideo) MimeFilter(MimeTypes.ANY_VIDEO),
if (entry.isGif) MimeFilter(MimeTypes.MIME_GIF), if (entry.isGif) MimeFilter(MimeTypes.GIF),
if (isFavourite) FavouriteFilter(), if (isFavourite) FavouriteFilter(),
if (album != null) AlbumFilter(album, CollectionSource.getUniqueAlbumName(album, collection?.source?.sortedAlbums)), if (album != null) AlbumFilter(album, CollectionSource.getUniqueAlbumName(album, collection?.source?.sortedAlbums)),
...tags.map((tag) => TagFilter(tag)), ...tags.map((tag) => TagFilter(tag)),