improved motion photo handling

This commit is contained in:
mertalev 2024-11-16 20:18:22 -05:00
parent d1c7ed5464
commit b0a2a6ac13
No known key found for this signature in database
GPG Key ID: CA85EF6600C9E8AD
5 changed files with 49 additions and 39 deletions

View File

@ -55,23 +55,12 @@ class GalleryViewerPage extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final totalAssets = useState(renderList.totalAssets); final totalAssets = useState(renderList.totalAssets);
final isZoomed = useState(false); final isZoomed = useState(false);
final isPlayingMotionVideo = useState(false); final isPlayingMotionVideo = useValueNotifier(false);
final stackIndex = useState(0); final stackIndex = useState(0);
final localPosition = useRef<Offset?>(null); final localPosition = useRef<Offset?>(null);
final currentIndex = useValueNotifier(initialIndex); final currentIndex = useValueNotifier(initialIndex);
final loadAsset = renderList.loadAsset; final loadAsset = renderList.loadAsset;
// // Update is playing motion video
ref.listen(
videoPlaybackValueProvider.select(
(playback) => playback.state == VideoPlaybackState.playing,
), (_, isPlaying) {
final asset = ref.read(currentAssetProvider);
if (asset != null && asset.isMotionPhoto) {
isPlayingMotionVideo.value = isPlaying;
}
});
Future<void> precacheNextImage(int index) async { Future<void> precacheNextImage(int index) async {
if (!context.mounted) { if (!context.mounted) {
return; return;
@ -237,7 +226,8 @@ class GalleryViewerPage extends HookConsumerWidget {
child: NativeVideoViewerPage( child: NativeVideoViewerPage(
key: key, key: key,
asset: asset, asset: asset,
placeholder: Image( isPlayingMotionVideo: isPlayingMotionVideo,
image: Image(
key: ValueKey(asset), key: ValueKey(asset),
image: ImmichImage.imageProvider( image: ImmichImage.imageProvider(
asset: asset, asset: asset,
@ -266,7 +256,7 @@ class GalleryViewerPage extends HookConsumerWidget {
} }
} }
if (newAsset.isImage && !isPlayingMotionVideo.value) { if (newAsset.isImage && !newAsset.isMotionPhoto) {
return buildImage(context, newAsset); return buildImage(context, newAsset);
} }
return buildVideo(context, newAsset); return buildVideo(context, newAsset);

View File

@ -24,12 +24,17 @@ import 'package:wakelock_plus/wakelock_plus.dart';
class NativeVideoViewerPage extends HookConsumerWidget { class NativeVideoViewerPage extends HookConsumerWidget {
final Asset asset; final Asset asset;
final bool showControls; final bool showControls;
final Widget placeholder; final Widget image;
/// Whether to display the video part of the motion photo
/// TODO: this should probably be a provider
final ValueNotifier<bool>? isPlayingMotionVideo;
const NativeVideoViewerPage({ const NativeVideoViewerPage({
super.key, super.key,
required this.asset, required this.asset,
required this.placeholder, required this.image,
this.isPlayingMotionVideo,
this.showControls = true, this.showControls = true,
}); });
@ -44,6 +49,12 @@ class NativeVideoViewerPage extends HookConsumerWidget {
final lastVideoPosition = useRef(-1); final lastVideoPosition = useRef(-1);
final isBuffering = useRef(false); final isBuffering = useRef(false);
if (isPlayingMotionVideo != null) {
useListenable(isPlayingMotionVideo);
}
final showMotionVideo =
isPlayingMotionVideo != null && isPlayingMotionVideo!.value;
// When a video is opened through the timeline, `isCurrent` will immediately be true. // When a video is opened through the timeline, `isCurrent` will immediately be true.
// When swiping from video A to video B, `isCurrent` will initially be true for video A and false for video B. // When swiping from video A to video B, `isCurrent` will initially be true for video A and false for video B.
// If the swipe is completed, `isCurrent` will be true for video B after a delay. // If the swipe is completed, `isCurrent` will be true for video B after a delay.
@ -413,9 +424,13 @@ class NativeVideoViewerPage extends HookConsumerWidget {
return Stack( return Stack(
children: [ children: [
placeholder, // this is always under the video to avoid flickering // This remains under the video to avoid flickering
// For motion videos, this is the image portion of the asset
image,
if (aspectRatio.value != null) if (aspectRatio.value != null)
Center( Visibility.maintain(
visible: asset.isVideo || showMotionVideo,
child: Center(
key: ValueKey(asset), key: ValueKey(asset),
child: AspectRatio( child: AspectRatio(
key: ValueKey(asset), key: ValueKey(asset),
@ -428,6 +443,7 @@ class NativeVideoViewerPage extends HookConsumerWidget {
: null, : null,
), ),
), ),
),
if (showControls) const Center(child: CustomVideoPlayerControls()), if (showControls) const Center(child: CustomVideoPlayerControls()),
], ],
); );

View File

@ -1086,6 +1086,7 @@ class NativeVideoViewerRoute extends PageRouteInfo<NativeVideoViewerRouteArgs> {
Key? key, Key? key,
required Asset asset, required Asset asset,
required Widget placeholder, required Widget placeholder,
ValueNotifier<bool>? isPlayingMotionVideo,
bool showControls = true, bool showControls = true,
List<PageRouteInfo>? children, List<PageRouteInfo>? children,
}) : super( }) : super(
@ -1094,6 +1095,7 @@ class NativeVideoViewerRoute extends PageRouteInfo<NativeVideoViewerRouteArgs> {
key: key, key: key,
asset: asset, asset: asset,
placeholder: placeholder, placeholder: placeholder,
isPlayingMotionVideo: isPlayingMotionVideo,
showControls: showControls, showControls: showControls,
), ),
initialChildren: children, initialChildren: children,
@ -1108,7 +1110,8 @@ class NativeVideoViewerRoute extends PageRouteInfo<NativeVideoViewerRouteArgs> {
return NativeVideoViewerPage( return NativeVideoViewerPage(
key: args.key, key: args.key,
asset: args.asset, asset: args.asset,
placeholder: args.placeholder, image: args.placeholder,
isPlayingMotionVideo: args.isPlayingMotionVideo,
showControls: args.showControls, showControls: args.showControls,
); );
}, },
@ -1120,6 +1123,7 @@ class NativeVideoViewerRouteArgs {
this.key, this.key,
required this.asset, required this.asset,
required this.placeholder, required this.placeholder,
this.isPlayingMotionVideo,
this.showControls = true, this.showControls = true,
}); });
@ -1129,6 +1133,8 @@ class NativeVideoViewerRouteArgs {
final Widget placeholder; final Widget placeholder;
final ValueNotifier<bool>? isPlayingMotionVideo;
final bool showControls; final bool showControls;
@override @override

View File

@ -18,7 +18,13 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final assetIsVideo = ref.watch(
currentAssetProvider.select((asset) => asset != null && asset.isVideo),
);
final showControls = ref.watch(showControlsProvider); final showControls = ref.watch(showControlsProvider);
final VideoPlaybackState state =
ref.watch(videoPlaybackValueProvider.select((value) => value.state));
// A timer to hide the controls // A timer to hide the controls
final hideTimer = useTimer( final hideTimer = useTimer(
hideTimerDuration, hideTimerDuration,
@ -27,20 +33,12 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
return; return;
} }
final state = ref.read(videoPlaybackValueProvider).state;
// Do not hide on paused // Do not hide on paused
if (state == VideoPlaybackState.paused) { if (state != VideoPlaybackState.paused && assetIsVideo) {
return;
}
final asset = ref.read(currentAssetProvider);
if (asset != null && asset.isVideo) {
ref.read(showControlsProvider.notifier).show = false; ref.read(showControlsProvider.notifier).show = false;
} }
}, },
); );
final VideoPlaybackState state =
ref.watch(videoPlaybackValueProvider.select((value) => value.state));
final showBuffering = state == VideoPlaybackState.buffering; final showBuffering = state == VideoPlaybackState.buffering;
/// Shows the controls and starts the timer to hide them /// Shows the controls and starts the timer to hide them
@ -95,7 +93,7 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
iconColor: Colors.white, iconColor: Colors.white,
isFinished: state == VideoPlaybackState.completed, isFinished: state == VideoPlaybackState.completed,
isPlaying: state == VideoPlaybackState.playing, isPlaying: state == VideoPlaybackState.playing,
show: showControls, show: assetIsVideo && showControls,
onPressed: togglePlay, onPressed: togglePlay,
), ),
), ),

View File

@ -72,7 +72,7 @@ class MemoryCard extends StatelessWidget {
key: ValueKey(asset.id), key: ValueKey(asset.id),
asset: asset, asset: asset,
showControls: false, showControls: false,
placeholder: SizedBox.expand( image: SizedBox.expand(
child: ImmichImage( child: ImmichImage(
asset, asset,
fit: fit, fit: fit,