mirror of
https://github.com/immich-app/immich.git
synced 2025-01-04 02:46:47 +01:00
higher res placeholder for local videos
This commit is contained in:
parent
5766551447
commit
64e23a3b5c
5 changed files with 75 additions and 37 deletions
|
@ -100,7 +100,11 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||||
if (index < totalAssets.value && index >= 0) {
|
if (index < totalAssets.value && index >= 0) {
|
||||||
final asset = loadAsset(index);
|
final asset = loadAsset(index);
|
||||||
await precacheImage(
|
await precacheImage(
|
||||||
ImmichImage.imageProvider(asset: asset),
|
ImmichImage.imageProvider(
|
||||||
|
asset: asset,
|
||||||
|
width: context.width,
|
||||||
|
height: context.height,
|
||||||
|
),
|
||||||
context,
|
context,
|
||||||
onError: onError,
|
onError: onError,
|
||||||
);
|
);
|
||||||
|
@ -313,7 +317,11 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||||
asset: asset,
|
asset: asset,
|
||||||
placeholder: Image(
|
placeholder: Image(
|
||||||
key: ValueKey(asset),
|
key: ValueKey(asset),
|
||||||
image: ImmichImage.imageProvider(asset: asset),
|
image: ImmichImage.imageProvider(
|
||||||
|
asset: asset,
|
||||||
|
width: context.width,
|
||||||
|
height: context.height,
|
||||||
|
),
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
height: context.height,
|
height: context.height,
|
||||||
width: context.width,
|
width: context.width,
|
||||||
|
@ -396,12 +404,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||||
stackIndex.value = -1;
|
stackIndex.value = -1;
|
||||||
isPlayingMotionVideo.value = false;
|
isPlayingMotionVideo.value = false;
|
||||||
|
|
||||||
// Delay setting the new asset to avoid a stutter in the page change animation
|
|
||||||
// TODO: make the scroll animation finish more quickly, and ideally have a callback for when it's done
|
|
||||||
ref.read(currentAssetProvider.notifier).set(newAsset);
|
ref.read(currentAssetProvider.notifier).set(newAsset);
|
||||||
// Timer(const Duration(milliseconds: 450), () {
|
|
||||||
// ref.read(currentAssetProvider.notifier).set(newAsset);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// Wait for page change animation to finish, then precache the next image
|
// Wait for page change animation to finish, then precache the next image
|
||||||
Timer(const Duration(milliseconds: 400), () {
|
Timer(const Duration(milliseconds: 400), () {
|
||||||
|
|
|
@ -113,11 +113,15 @@ class MemoryPage extends HookConsumerWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Precache the asset
|
// Precache the asset
|
||||||
|
final size = MediaQuery.sizeOf(context);
|
||||||
await precacheImage(
|
await precacheImage(
|
||||||
ImmichImage.imageProvider(
|
ImmichImage.imageProvider(
|
||||||
asset: asset,
|
asset: asset,
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
),
|
),
|
||||||
context,
|
context,
|
||||||
|
size: size,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,14 +7,21 @@ import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart' show ThumbnailSize;
|
import 'package:photo_manager/photo_manager.dart' show ThumbnailSize;
|
||||||
|
|
||||||
/// The local image provider for an asset
|
/// The local image provider for an asset
|
||||||
class ImmichLocalImageProvider extends ImageProvider<ImmichLocalImageProvider> {
|
class ImmichLocalImageProvider extends ImageProvider<ImmichLocalImageProvider> {
|
||||||
final Asset asset;
|
final Asset asset;
|
||||||
|
// only used for videos
|
||||||
|
final double width;
|
||||||
|
final double height;
|
||||||
|
final Logger log = Logger('ImmichLocalImageProvider');
|
||||||
|
|
||||||
ImmichLocalImageProvider({
|
ImmichLocalImageProvider({
|
||||||
required this.asset,
|
required this.asset,
|
||||||
|
required this.width,
|
||||||
|
required this.height,
|
||||||
}) : assert(asset.local != null, 'Only usable when asset.local is set');
|
}) : assert(asset.local != null, 'Only usable when asset.local is set');
|
||||||
|
|
||||||
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
|
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
|
||||||
|
@ -42,39 +49,58 @@ class ImmichLocalImageProvider extends ImageProvider<ImmichLocalImageProvider> {
|
||||||
|
|
||||||
// Streams in each stage of the image as we ask for it
|
// Streams in each stage of the image as we ask for it
|
||||||
Stream<ui.Codec> _codec(
|
Stream<ui.Codec> _codec(
|
||||||
Asset key,
|
Asset asset,
|
||||||
ImageDecoderCallback decode,
|
ImageDecoderCallback decode,
|
||||||
StreamController<ImageChunkEvent> chunkEvents,
|
StreamController<ImageChunkEvent> chunkEvents,
|
||||||
) async* {
|
) async* {
|
||||||
// Load a small thumbnail
|
ui.ImmutableBuffer? buffer;
|
||||||
final thumbBytes = await asset.local?.thumbnailDataWithSize(
|
try {
|
||||||
const ThumbnailSize.square(256),
|
final local = asset.local;
|
||||||
quality: 80,
|
if (local == null) {
|
||||||
);
|
throw StateError('Asset ${asset.fileName} has no local data');
|
||||||
if (thumbBytes != null) {
|
|
||||||
final buffer = await ui.ImmutableBuffer.fromUint8List(thumbBytes);
|
|
||||||
final codec = await decode(buffer);
|
|
||||||
yield codec;
|
|
||||||
} else {
|
|
||||||
debugPrint("Loading thumb for ${asset.fileName} failed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (asset.isImage) {
|
var thumbBytes = await local
|
||||||
final File? file = await asset.local?.originFile;
|
.thumbnailDataWithSize(const ThumbnailSize.square(256), quality: 80);
|
||||||
|
if (thumbBytes == null) {
|
||||||
|
throw StateError("Loading thumbnail for ${asset.fileName} failed");
|
||||||
|
}
|
||||||
|
buffer = await ui.ImmutableBuffer.fromUint8List(thumbBytes);
|
||||||
|
thumbBytes = null;
|
||||||
|
yield await decode(buffer);
|
||||||
|
buffer = null;
|
||||||
|
|
||||||
|
switch (asset.type) {
|
||||||
|
case AssetType.image:
|
||||||
|
final File? file = await local.originFile;
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
throw StateError("Opening file for asset ${asset.fileName} failed");
|
throw StateError("Opening file for asset ${asset.fileName} failed");
|
||||||
}
|
}
|
||||||
try {
|
buffer = await ui.ImmutableBuffer.fromFilePath(file.path);
|
||||||
final buffer = await ui.ImmutableBuffer.fromFilePath(file.path);
|
yield await decode(buffer);
|
||||||
final codec = await decode(buffer);
|
buffer = null;
|
||||||
yield codec;
|
break;
|
||||||
} catch (error) {
|
case AssetType.video:
|
||||||
throw StateError("Loading asset ${asset.fileName} failed");
|
final size = ThumbnailSize(width.ceil(), height.ceil());
|
||||||
|
thumbBytes = await local.thumbnailDataWithSize(size);
|
||||||
|
if (thumbBytes == null) {
|
||||||
|
throw StateError("Failed to load preview for ${asset.fileName}");
|
||||||
}
|
}
|
||||||
|
buffer = await ui.ImmutableBuffer.fromUint8List(thumbBytes);
|
||||||
|
thumbBytes = null;
|
||||||
|
yield await decode(buffer);
|
||||||
|
buffer = null;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw StateError('Unsupported asset type ${asset.type}');
|
||||||
}
|
}
|
||||||
|
} catch (error, stack) {
|
||||||
|
log.severe('Error loading local image ${asset.fileName}', error, stack);
|
||||||
|
buffer?.dispose();
|
||||||
|
} finally {
|
||||||
chunkEvents.close();
|
chunkEvents.close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
|
|
|
@ -28,12 +28,11 @@ class ImmichImage extends StatelessWidget {
|
||||||
// either by using the asset ID or the asset itself
|
// either by using the asset ID or the asset itself
|
||||||
/// [asset] is the Asset to request, or else use [assetId] to get a remote
|
/// [asset] is the Asset to request, or else use [assetId] to get a remote
|
||||||
/// image provider
|
/// image provider
|
||||||
/// Use [isThumbnail] and [thumbnailSize] if you'd like to request a thumbnail
|
|
||||||
/// The size of the square thumbnail to request. Ignored if isThumbnail
|
|
||||||
/// is not true
|
|
||||||
static ImageProvider imageProvider({
|
static ImageProvider imageProvider({
|
||||||
Asset? asset,
|
Asset? asset,
|
||||||
String? assetId,
|
String? assetId,
|
||||||
|
double width = 1080,
|
||||||
|
double height = 1920,
|
||||||
}) {
|
}) {
|
||||||
if (asset == null && assetId == null) {
|
if (asset == null && assetId == null) {
|
||||||
throw Exception('Must supply either asset or assetId');
|
throw Exception('Must supply either asset or assetId');
|
||||||
|
@ -48,6 +47,8 @@ class ImmichImage extends StatelessWidget {
|
||||||
if (useLocal(asset)) {
|
if (useLocal(asset)) {
|
||||||
return ImmichLocalImageProvider(
|
return ImmichLocalImageProvider(
|
||||||
asset: asset,
|
asset: asset,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return ImmichRemoteImageProvider(
|
return ImmichRemoteImageProvider(
|
||||||
|
@ -87,6 +88,8 @@ class ImmichImage extends StatelessWidget {
|
||||||
},
|
},
|
||||||
image: ImmichImage.imageProvider(
|
image: ImmichImage.imageProvider(
|
||||||
asset: asset,
|
asset: asset,
|
||||||
|
width: context.width,
|
||||||
|
height: context.height,
|
||||||
),
|
),
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
|
|
|
@ -136,6 +136,8 @@ class _BlurredBackdrop extends HookWidget {
|
||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
image: ImmichImage.imageProvider(
|
image: ImmichImage.imageProvider(
|
||||||
asset: asset,
|
asset: asset,
|
||||||
|
height: context.height,
|
||||||
|
width: context.width,
|
||||||
),
|
),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue