You've already forked immich
mirror of
https://github.com/immich-app/immich.git
synced 2025-08-07 23:03:36 +02:00
feat: sqlite video player (#19792)
* feat: video player * use remote asset id in local query * fix: error from pre-caching beyond total assets * fix: flipped local videos * incorrect aspect ratio on iOS * ignore other storage id during equals check --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
@ -25,6 +25,7 @@ sealed class BaseAsset {
|
||||
final int? height;
|
||||
final int? durationInSeconds;
|
||||
final bool isFavorite;
|
||||
final String? livePhotoVideoId;
|
||||
|
||||
const BaseAsset({
|
||||
required this.name,
|
||||
@ -36,18 +37,12 @@ sealed class BaseAsset {
|
||||
this.height,
|
||||
this.durationInSeconds,
|
||||
this.isFavorite = false,
|
||||
this.livePhotoVideoId,
|
||||
});
|
||||
|
||||
bool get isImage => type == AssetType.image;
|
||||
bool get isVideo => type == AssetType.video;
|
||||
|
||||
double? get aspectRatio {
|
||||
if (width != null && height != null && height! > 0) {
|
||||
return width! / height!;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
bool get hasRemote =>
|
||||
storage == AssetState.remote || storage == AssetState.merged;
|
||||
bool get hasLocal =>
|
||||
|
@ -3,6 +3,7 @@ part of 'base_asset.model.dart';
|
||||
class LocalAsset extends BaseAsset {
|
||||
final String id;
|
||||
final String? remoteId;
|
||||
final int orientation;
|
||||
|
||||
const LocalAsset({
|
||||
required this.id,
|
||||
@ -16,6 +17,8 @@ class LocalAsset extends BaseAsset {
|
||||
super.height,
|
||||
super.durationInSeconds,
|
||||
super.isFavorite = false,
|
||||
super.livePhotoVideoId,
|
||||
this.orientation = 0,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -38,6 +41,7 @@ class LocalAsset extends BaseAsset {
|
||||
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
||||
remoteId: ${remoteId ?? "<NA>"}
|
||||
isFavorite: $isFavorite,
|
||||
orientation: $orientation,
|
||||
}''';
|
||||
}
|
||||
|
||||
@ -45,11 +49,12 @@ class LocalAsset extends BaseAsset {
|
||||
bool operator ==(Object other) {
|
||||
if (other is! LocalAsset) return false;
|
||||
if (identical(this, other)) return true;
|
||||
return super == other && id == other.id && remoteId == other.remoteId;
|
||||
return super == other && id == other.id && orientation == other.orientation;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => super.hashCode ^ id.hashCode ^ remoteId.hashCode;
|
||||
int get hashCode =>
|
||||
super.hashCode ^ id.hashCode ^ remoteId.hashCode ^ orientation.hashCode;
|
||||
|
||||
LocalAsset copyWith({
|
||||
String? id,
|
||||
@ -63,6 +68,7 @@ class LocalAsset extends BaseAsset {
|
||||
int? height,
|
||||
int? durationInSeconds,
|
||||
bool? isFavorite,
|
||||
int? orientation,
|
||||
}) {
|
||||
return LocalAsset(
|
||||
id: id ?? this.id,
|
||||
@ -76,6 +82,7 @@ class LocalAsset extends BaseAsset {
|
||||
height: height ?? this.height,
|
||||
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
||||
isFavorite: isFavorite ?? this.isFavorite,
|
||||
orientation: orientation ?? this.orientation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ class RemoteAsset extends BaseAsset {
|
||||
super.isFavorite = false,
|
||||
this.thumbHash,
|
||||
this.visibility = AssetVisibility.timeline,
|
||||
super.livePhotoVideoId,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -65,7 +66,6 @@ class RemoteAsset extends BaseAsset {
|
||||
return super == other &&
|
||||
id == other.id &&
|
||||
ownerId == other.ownerId &&
|
||||
localId == other.localId &&
|
||||
thumbHash == other.thumbHash &&
|
||||
visibility == other.visibility;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ enum Setting<T> {
|
||||
groupAssetsBy<int>(StoreKey.groupAssetsBy, 0),
|
||||
showStorageIndicator<bool>(StoreKey.storageIndicator, true),
|
||||
loadOriginal<bool>(StoreKey.loadOriginal, false),
|
||||
loadOriginalVideo<bool>(StoreKey.loadOriginalVideo, false),
|
||||
preferRemoteImage<bool>(StoreKey.preferRemoteImage, false),
|
||||
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, false),
|
||||
;
|
||||
|
@ -2,16 +2,20 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
class AssetService {
|
||||
final RemoteAssetRepository _remoteAssetRepository;
|
||||
final DriftLocalAssetRepository _localAssetRepository;
|
||||
final Platform _platform;
|
||||
|
||||
const AssetService({
|
||||
required RemoteAssetRepository remoteAssetRepository,
|
||||
required DriftLocalAssetRepository localAssetRepository,
|
||||
}) : _remoteAssetRepository = remoteAssetRepository,
|
||||
_localAssetRepository = localAssetRepository;
|
||||
_localAssetRepository = localAssetRepository,
|
||||
_platform = const LocalPlatform();
|
||||
|
||||
Stream<BaseAsset?> watchAsset(BaseAsset asset) {
|
||||
final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).id;
|
||||
@ -21,10 +25,40 @@ class AssetService {
|
||||
}
|
||||
|
||||
Future<ExifInfo?> getExif(BaseAsset asset) async {
|
||||
if (asset is LocalAsset || asset is! RemoteAsset) {
|
||||
if (!asset.hasRemote) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return _remoteAssetRepository.getExif(asset.id);
|
||||
final id =
|
||||
asset is LocalAsset ? asset.remoteId! : (asset as RemoteAsset).id;
|
||||
return _remoteAssetRepository.getExif(id);
|
||||
}
|
||||
|
||||
Future<double> getAspectRatio(BaseAsset asset) async {
|
||||
bool isFlipped;
|
||||
double? width;
|
||||
double? height;
|
||||
|
||||
if (asset.hasRemote) {
|
||||
final exif = await getExif(asset);
|
||||
isFlipped = ExifDtoConverter.isOrientationFlipped(exif?.orientation);
|
||||
width = exif?.width ?? asset.width?.toDouble();
|
||||
height = exif?.height ?? asset.height?.toDouble();
|
||||
} else if (asset is LocalAsset) {
|
||||
isFlipped = _platform.isAndroid &&
|
||||
(asset.orientation == 90 || asset.orientation == 270);
|
||||
width = asset.width?.toDouble();
|
||||
height = asset.height?.toDouble();
|
||||
} else {
|
||||
isFlipped = false;
|
||||
}
|
||||
|
||||
final orientedWidth = isFlipped ? height : width;
|
||||
final orientedHeight = isFlipped ? width : height;
|
||||
if (orientedWidth != null && orientedHeight != null && orientedHeight > 0) {
|
||||
return orientedWidth / orientedHeight;
|
||||
}
|
||||
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ class HashService {
|
||||
final toHash = <_AssetToPath>[];
|
||||
|
||||
for (final asset in assetsToHash) {
|
||||
final file = await _storageRepository.getFileForAsset(asset);
|
||||
final file = await _storageRepository.getFileForAsset(asset.id);
|
||||
if (file == null) {
|
||||
continue;
|
||||
}
|
||||
|
@ -359,6 +359,7 @@ extension on Iterable<PlatformAsset> {
|
||||
width: e.width,
|
||||
height: e.height,
|
||||
durationInSeconds: e.durationInSeconds,
|
||||
orientation: e.orientation,
|
||||
),
|
||||
).toList();
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ class TimelineService {
|
||||
|
||||
// Pre-cache assets around the given index for asset viewer
|
||||
Future<void> preCacheAssets(int index) =>
|
||||
_mutex.run(() => _loadAssets(index, 5));
|
||||
_mutex.run(() => _loadAssets(index, math.min(5, totalAssets - index)));
|
||||
|
||||
BaseAsset getAsset(int index) {
|
||||
if (!hasRange(index, 1)) {
|
||||
|
Reference in New Issue
Block a user