From 69e88ef985c4e4744db88e419b726ea5a32d2b00 Mon Sep 17 00:00:00 2001 From: Tom Graham Date: Tue, 4 Feb 2025 09:43:23 +1100 Subject: [PATCH] fix(mobile): #15182 Video memories no longer play (#15210) * Update current asset to play video. * Updated location of currentAssetProvider update per feedback. * Added a playbackDelayFactor to the video viewer to resolve an issue in memories. Also adjusted the scale of the memory preview image to match the ratio of the video. This still appears to jump because the video preview doesn't seem to be the first frame for some reason :\ * add video indicator --------- Co-authored-by: Tom graham Co-authored-by: Alex --- .../common/native_video_viewer.page.dart | 12 +++++++--- mobile/lib/pages/photos/memory.page.dart | 23 +++++++++++++++++++ mobile/lib/widgets/memories/memory_card.dart | 3 ++- mobile/lib/widgets/memories/memory_lane.dart | 9 ++++++++ 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/mobile/lib/pages/common/native_video_viewer.page.dart b/mobile/lib/pages/common/native_video_viewer.page.dart index ad9d53b1bb..8ab0da1b44 100644 --- a/mobile/lib/pages/common/native_video_viewer.page.dart +++ b/mobile/lib/pages/common/native_video_viewer.page.dart @@ -26,6 +26,7 @@ import 'package:wakelock_plus/wakelock_plus.dart'; class NativeVideoViewerPage extends HookConsumerWidget { final Asset asset; final bool showControls; + final int playbackDelayFactor; final Widget image; const NativeVideoViewerPage({ @@ -33,6 +34,7 @@ class NativeVideoViewerPage extends HookConsumerWidget { required this.asset, required this.image, this.showControls = true, + this.playbackDelayFactor = 1, }); @override @@ -317,12 +319,16 @@ class NativeVideoViewerPage extends HookConsumerWidget { } // Delay the video playback to avoid a stutter in the swipe animation + // Note, in some circumstances a longer delay is needed (eg: memories), + // the playbackDelayFactor can be used for this + // This delay seems like a hacky way to resolve underlying bugs in video + // playback, but other resolutions failed thus far Timer( Platform.isIOS - ? const Duration(milliseconds: 300) + ? Duration(milliseconds: 300 * playbackDelayFactor) : imageToVideo - ? const Duration(milliseconds: 200) - : const Duration(milliseconds: 400), () { + ? Duration(milliseconds: 200 * playbackDelayFactor) + : Duration(milliseconds: 400 * playbackDelayFactor), () { if (!context.mounted) { return; } diff --git a/mobile/lib/pages/photos/memory.page.dart b/mobile/lib/pages/photos/memory.page.dart index 74a94ed6ee..b082b7484f 100644 --- a/mobile/lib/pages/photos/memory.page.dart +++ b/mobile/lib/pages/photos/memory.page.dart @@ -5,6 +5,8 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/models/memories/memory.model.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/widgets/common/immich_image.dart'; import 'package:immich_mobile/widgets/memories/memory_bottom_info.dart'; @@ -13,6 +15,8 @@ import 'package:immich_mobile/widgets/memories/memory_epilogue.dart'; import 'package:immich_mobile/widgets/memories/memory_progress_indicator.dart'; @RoutePage() + +/// Expects [currentAssetProvider] to be set before navigating to this page class MemoryPage extends HookConsumerWidget { final List memories; final int memoryIndex; @@ -32,6 +36,7 @@ class MemoryPage extends HookConsumerWidget { "${currentAssetPage.value + 1}|${currentMemory.value.assets.length}", ); const bgColor = Colors.black; + final currentAsset = useState(null); /// The list of all of the asset page controllers final memoryAssetPageControllers = @@ -135,6 +140,14 @@ class MemoryPage extends HookConsumerWidget { ref.read(hapticFeedbackProvider.notifier).selectionClick(); currentAssetPage.value = otherIndex; updateProgressText(); + + final asset = currentMemory.value.assets[otherIndex]; + currentAsset.value = asset; + ref.read(currentAssetProvider.notifier).set(asset); + if (asset.isVideo || asset.isMotionPhoto) { + ref.read(videoPlaybackValueProvider.notifier).reset(); + } + // Wait for page change animation to finish await Future.delayed(const Duration(milliseconds: 400)); // And then precache the next asset @@ -274,6 +287,16 @@ class MemoryPage extends HookConsumerWidget { ), ), ), + if (currentAsset.value != null && + currentAsset.value!.isVideo) + Positioned( + bottom: 24, + right: 32, + child: Icon( + Icons.videocam_outlined, + color: Colors.grey[200], + ), + ), ], ), ), diff --git a/mobile/lib/widgets/memories/memory_card.dart b/mobile/lib/widgets/memories/memory_card.dart index 4954d0bfcc..b63a310b32 100644 --- a/mobile/lib/widgets/memories/memory_card.dart +++ b/mobile/lib/widgets/memories/memory_card.dart @@ -75,11 +75,12 @@ class MemoryCard extends StatelessWidget { key: ValueKey(asset.id), asset: asset, showControls: false, + playbackDelayFactor: 2, image: ImmichImage( asset, width: context.width, height: context.height, - fit: fit, + fit: BoxFit.contain, ), ), ), diff --git a/mobile/lib/widgets/memories/memory_lane.dart b/mobile/lib/widgets/memories/memory_lane.dart index 41e9cc628e..d5b46dab51 100644 --- a/mobile/lib/widgets/memories/memory_lane.dart +++ b/mobile/lib/widgets/memories/memory_lane.dart @@ -4,6 +4,8 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/models/memories/memory.model.dart'; import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart'; import 'package:immich_mobile/providers/memory.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; @@ -33,6 +35,13 @@ class MemoryLane extends HookConsumerWidget { ), onTap: (memoryIndex) { ref.read(hapticFeedbackProvider.notifier).heavyImpact(); + if (memories[memoryIndex].assets.isNotEmpty) { + final asset = memories[memoryIndex].assets[0]; + ref.read(currentAssetProvider.notifier).set(asset); + if (asset.isVideo || asset.isMotionPhoto) { + ref.read(videoPlaybackValueProvider.notifier).reset(); + } + } context.pushRoute( MemoryRoute( memories: memories,