various fixes
This commit is contained in:
parent
01d2d7ea2f
commit
a2fc8bfd2f
10 changed files with 63 additions and 51 deletions
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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';
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)),
|
||||||
|
|
Loading…
Reference in a new issue